diff --git a/docs/webui-local-dev.md b/docs/webui-local-dev.md index 2bbecff..d9cc022 100644 --- a/docs/webui-local-dev.md +++ b/docs/webui-local-dev.md @@ -59,31 +59,6 @@ status, onboarding checklist state, and the fail-closed error payloads (#635). | `/api/queue` | JSON queue export with pagination metadata | | `/traffic` | Workflow traffic-control view — runnable, leased, blocked, needs-controller, terminal-complete (#640) | | `/api/traffic` | JSON traffic-control export with state classifications and next safe role actions | - -### Traffic-control state vocabulary (#640) - -The traffic view classifies each open issue/PR into exactly one bucket: - -| Bucket | Meaning | Operator implication | -|--------|---------|----------------------| -| **runnable** | No active lease, no block reason, safe for its expected role | Next role may start work | -| **leased** | Active author claim or reviewer PR lease | Do not stomp; wait or adopt via role tools | -| **blocked** | Dependency, missing head pin, conflict, or status:blocked | Author/controller remediation first | -| **needs_controller** | Contaminated / controller-only diagnosis | Controller only | -| **terminal_complete** | Reconciler / terminal-lock territory | Reconciler cleanup path | - -**Live path contracts (do not invent):** - -- PR head pins come from `QueueItem.signals["head_sha"]` (full SHA). Display - `extra["head_sha"]` is truncated and must never be used for routing. -- Reviewer leases are keyed as `(pr, pr_number)` only — never via a linked - `issue_number` on the same lease marker. -- Issue claims come from `claim_inventory["entries"]` - (`issue_claim_heartbeat.build_claim_inventory`). There is no `active_claims` - key. -- Queue display badges are only: `blocked`, `claimed`, `duplicate`, `stale`, - `in-review`, `open`. Review verdicts (`request-changes`, `approved`) are - **not** queue badges; traffic does not invent them from the queue loader. | `/projects` | Project registry list with status and onboarding progress (#427, #635) | | `/projects/{id}` | Project detail + onboarding checklist | | `/api/v1/projects` | Versioned JSON registry export (#635) | @@ -112,6 +87,35 @@ Most routes are GET-only. POST/PUT/PATCH/DELETE return `405` with `read-only-mvp`, except `/audit` and `/api/audit` which accept POST for local validator preview only (no Gitea mutations, no server-side storage). +### Traffic-control state vocabulary (#640) + +The traffic view classifies each open issue/PR into exactly one bucket: + +| Bucket | Meaning | Operator implication | +|--------|---------|----------------------| +| **runnable** | No active lease, no block reason, safe for its expected role | Next role may start work | +| **leased** | Active author claim or reviewer PR lease | Do not stomp; wait or adopt via role tools | +| **blocked** | Dependency, missing head pin, conflict, or unmet dependency | Author remediation first | +| **needs_controller** | Contaminated, controller-only diagnosis, or `status:blocked` | Controller only | +| **terminal_complete** | Reconciler / terminal-lock territory | Reconciler cleanup path | + +`status:blocked` items route to **needs_controller**, not **blocked**: +`expected_role_for_candidate` sends them to the controller, and the blocker +reason renders in either bucket. + +**Live path contracts (do not invent):** + +- PR head pins come from `QueueItem.signals["head_sha"]` (full SHA). Display + `extra["head_sha"]` is truncated and must never be used for routing. +- Reviewer leases are keyed as `(pr, pr_number)` only — never via a linked + `issue_number` on the same lease marker. +- Issue claims come from `claim_inventory["entries"]` + (`issue_claim_heartbeat.build_claim_inventory`). There is no `active_claims` + key. +- Queue display badges are only: `blocked`, `claimed`, `duplicate`, `stale`, + `in-review`, `open`. Review verdicts (`request-changes`, `approved`) are + **not** queue badges; traffic does not invent them from the queue loader. + ## System health API (#634) `GET /api/v1/system/health` is the structured, read-only health surface for diff --git a/webui/app.py b/webui/app.py index e593860..cedcef1 100644 --- a/webui/app.py +++ b/webui/app.py @@ -82,6 +82,7 @@ def _stub_page(title: str, description: str) -> HTMLResponse: _LEGACY_PAGES = ( + ("/traffic", "Traffic", "workflow traffic-control view (#640)"), ("/queue", "Queue", "live PR and issue dashboard (#429)"), ("/projects", "Projects", "registry and onboarding (#427)"), ("/prompts", "Prompts", "canonical workflow prompt library (#428)"), diff --git a/webui/traffic_views.py b/webui/traffic_views.py index e247915..0156b93 100644 --- a/webui/traffic_views.py +++ b/webui/traffic_views.py @@ -36,7 +36,7 @@ def _render_traffic_item_row(item: TrafficItem) -> str: title_str = escape(item.title) role_str = escape(item.expected_role) badges_html = _render_badges(item.badges) - + reason_html = "" if item.block_reason: reason_html = f'
Blocker: {escape(item.block_reason)}
' @@ -60,7 +60,7 @@ def _render_traffic_item_row(item: TrafficItem) -> str: def _render_traffic_table(items: Sequence[TrafficItem], empty_message: str) -> str: if not items: return f'

{escape(empty_message)}

' - + rows = "".join(_render_traffic_item_row(item) for item in items) return f""" @@ -79,13 +79,13 @@ def _render_traffic_table(items: Sequence[TrafficItem], empty_message: str) -> s def _render_next_roles(next_roles: Sequence[dict]) -> str: if not next_roles: return "" - + cards = [] for r in next_roles: role = escape(r.get("role", "unknown")) status = r.get("status", "idle") prompt = escape(r.get("prompt", "")) - + status_cls = "badge-health-ok" if status == "safe" else ("badge-blocked" if "blocked" in status else "badge-health-skipped") cards.append(f"""
@@ -94,7 +94,7 @@ def _render_next_roles(next_roles: Sequence[dict]) -> str:

{prompt}

""") - + return f"""

Next Safe Role Actions

{"".join(cards)} @@ -144,7 +144,7 @@ def render_traffic_page(snapshot: TrafficSnapshot) -> str:

3. Blocked Items (Dependencies / Locks)

-

Items blocked by unmet dependency issues, status:blocked, or active terminal review locks. Never presented as safe.

+

Items blocked by unmet dependency issues, a missing head pin, a merge conflict, or an active terminal review lock. Items labelled status:blocked route to section 4. Never presented as safe.

{_render_traffic_table(snapshot.blocked, "No blocked items.")}