The audit anchors had drifted from the implementation. Corrected against the live source as of commit ea0ba33: - PRD invariant #1: mode registry is now 24 handlers in _VALID_MODES, not "only extract". Added invariant #2: unknown modes fail with HTTP 400 + valid_modes (was 200-with-error-body). Added SECURE to the session-cookie invariant. - PRD known-gaps + change-axis: marked the mode-registration and unknown-mode items RESOLVED; flagged the live codereview-vs-code_review naming mismatch that still 400s lakehouse observer escalations. - SCRUM_FIX_WAVE items 1-2 struck through as DONE; recorded the cookie + nginx security fixes under "Recently landed". Machine-generated .memory/ state left untouched (regenerated by the scrum pipeline, not hand-edited). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
5.5 KiB
LLM Team UI — Product Requirements (for scrum-master audits)
This doc is the anchor the lakehouse scrum-master uses when auditing
this codebase. It is the "ground truth" — every scrum finding is
evaluated against what this doc says the system should be. Written
2026-04-24 to give the scrum pipeline a target surface; keep it tight
and aligned with what the code actually tries to do. Last reconciled
2026-06-20 against commit ea0ba33 — the mode registry and unknown-mode
handling described below were verified against the live source on that date.
What LLM Team UI is
Flask-based web UI that lets a user configure and run multi-model
"teams" — pipelines of LLMs (OpenRouter + Ollama Cloud + local Ollama)
that collaborate on a task with roles like executor, reviewer, critic,
sentinel. Lives at devop.live:5000 behind nginx. Main file
llm_team_ui.py handles HTTP routes, session auth, model orchestration,
SSE streaming. Backed by PostgreSQL for users + runs + audit.
Core invariants (scrum should flag if violated)
- Every mode accepted by
/api/runmust be in the_VALID_MODESregistry and have a matchingrun_<mode>handler. As of 2026-06-20 there are 24 registered modes (brainstorm, pipeline, debate, validator, roundrobin, redteam, consensus, codereview, ladder, tournament, evolution, blindassembly, staircase, drift, mesh, hallucination, timeloop, research, eval, extract, refine, adaptive, deep_analysis, distill), each with arun_<mode>function.run_team()validatesmodeagainst_VALID_MODESbefore opening the SSE stream and rejects anything else with HTTP 400 (see invariant below). The registry is kept in sync with the handlers manually — adding arun_<mode>without listing it in_VALID_MODES(or vice-versa) is the hard fail to flag. - Unknown modes must fail fast with HTTP 400 + the valid-mode list.
run_team()returns{"error": "Unknown mode: X", "valid_modes": [...]}with status 400 before streaming. Returning 200-with-error-body (the old behavior) is a hard fail — it forces callers to parse SSE to learn the request was bad. - All authenticated routes must call
@login_requireddecorator. The SESSION_COOKIE_HTTPONLY + SAMESITE=Lax + SECURE defaults must hold (SECURE added 2026-06-20; the app sits behind nginx TLS). Any route that mutates state without auth is a hard fail. - Security logging goes to
/var/log/llm-team-security.logvia thesec_loglogger. Fail2ban watches this file. Any new auth path must emit there on failure. - PostgreSQL connections come from
DB_URLenv. Direct literal connection strings are a hard fail. - SMTP alerts via
send_security_alert(subject, body)— never log secrets in the body. - SSE streams must flush incrementally — if an endpoint returns all-at-once, it should not claim SSE in the Content-Type.
- Password storage: bcrypt only. Any plain-text / SHA256 password path is a hard fail.
- Rate limiting enforced on /login, /register, and model-invocation endpoints. No unbounded loops that invoke paid APIs.
Known gaps (scrum can work on these)
OnlyRESOLVED (2026-06-20). The mode set grew from one handler to 24 (extractmode is registered.run_brainstorm…run_distill), all listed in_VALID_MODESand dispatched byrun_team(). Note the mode named in code iscodereview(notcode_review); callers using the underscored name still get a 400. Historical context: observer.ts in lakehouse was escalating to/api/run?mode=code_reviewand silently hit "Unknown mode" for weeks (see lakehouse memoryreference_llm_team_modes.md) — the underscore-vs-no-underscore mismatch is the remaining footgun, not the missing handler.- File is 13K lines. Cohesive modules (auth, teams, runs, alerts, SSE, templates) should be separated — but that's a refactor, out of scope for individual scrum-master reviews.
- Audit log schema is thin. The PostgreSQL
runstable stores the fact of a run but not fine-grained per-step outcomes. Downstream correlation is manual. - Templates inline in Python as
render_template_string(...). Makes HTML review impossible via the scrum pipeline — HTML is hidden inside Python string literals.
Change proposal axis (what the scrum should optimize for)
Every Unknown-mode endpoint should either be registered or return 404/not-a-mode.DONE (2026-06-20).run_team()now returns HTTP 400 (not 404 — chosen so it reads as "bad request / wrong mode" rather than "route missing") with avalid_modeslist, before streaming. Callers can now distinguish a wrong call from a missing mode. Remaining: see thecodereviewvscode_reviewnaming footgun in Known gaps.- Auth decorators should be typed —
@login_requiredis a runtime check; adding anAnnotated[User, Depends(auth)]-style type hint (possible via Flask'sgobject) would make review mechanical. - Security logging should be structured — current
sec_log.warning( f"...")loses fields. Move tologger.warning(msg, extra={...})so fail2ban and external tools can parse. - Secret handling —
FLASK_SECRETin env is correct, but the fallback to~/.llm-team-secretmeans dev mode silently generates a persistent secret. Not bad, but should log once.
How scrum-master audits this repo
The lakehouse scrum pipeline is repo-agnostic via env vars. See
/root/llm-team-ui/CLAUDE.md for the exact runbook.