Add Command center performance pages
Epic 12243 (plan task 12246). Full settled inputs: the PRD (Task 12244) and the Architecture (Task 12245) of this epic — read them with `task_read`/`artifact_read` on those tasks; section numbers below (R…/AC…/§…) cite them. Where PRD and the original proposal (Task 12195) differ, the PRD governs. Reads the metrics child's APIs.
## Original requirement (verbatim)
> "5. Surfaces (console UI style guide): Agents › Performance profile per agent/model/configuration; Compare 2–4 configurations on identical strata; org Leaderboard (filters persona, project, period, min n); task Outcome panel shows the ledger row and attribution links; Command center widgets read the ledger in place of the weekly sample; CSV export per org. Operator plane: Fleet › Agent index with cross-org aggregates per configuration, each cell ≥ 5 orgs and ≥ 100 outcomes, never a tenant name."
(Settled: D4 show both — ledger widgets beside the unchanged `quality.avg`; D8 Owner/Admin only; Architecture A2 — the pages are tabs of the Command center, not under the grid-gated Agents section; the operator page is "Agent index" in the operator sidebar group.)
> "7. Measurement is always collected (ledger, metrics, pages); only benchmark mode changes behaviour and only behind its plan feature and project opt-in. Dogfood on the Jaah project first."
> Done means (epic, verbatim): "Agents › Performance renders a profile for each built-in provider with intervals and grey \"insufficient data\" below 20 outcomes; Compare shows two configurations on identical strata."
PRD requirements carried (verbatim): R5.1 "a profile per agent, per model and per configuration: the seven metrics, each with its interval, its n and an \"Insufficient data\" grey state below 20." R5.2 "pick 2–4 configurations. The metrics are shown side by side, computed on the strata all of them share. The page names the shared strata. If they share none, it says so and shows no numbers." R5.3 "configurations ranked, with filters for persona, project, period and min n. Owner/Admin only." R5.5 "ledger widgets (success rate, first-pass acceptance, defects per 100 merges, cost per success) are added next to the existing weekly-sample `quality.avg` widget, which stays (D4)." R5.6 CSV export Owner/Admin only. R5.8 "All pages follow `docs/standards/ui-style-guide.md` and `docs/standards/ui-copy-standards.md`. They are readable at the supported phone width." R7.1 "The ledger, metrics and pages have no flag. They are ON for every org in every environment, dev included."
## Approach (settled — realize, do not reopen)
Architecture §9.3 (Command center tabs + widgets), Architecture decision A2 (pages are Command center tabs, not under the grid-gated Agents section).
- `pages/command-center/CommandCenter.tsx` (route already `RequireSuperadmin orgAdmin`) gains a tab strip **Overview** (today's dashboard) · **Performance** · **Compare** · **Leaderboard** using shared `Tabs`/`useUrlTab` per the UI style guide.
- Performance: group switch Agent / Model / Configuration; metric table with interval cells (`72% [64–79]`), n, grey "Insufficient data (n=…)" rows; "Export CSV" in the page header; the per-project "Outcome scan: no git person" notice when the read model reports it; lane-minutes tooltip states the per-batch approximation.
- Compare: 2–4 config picker, one column per config, shared strata named above the table, the empty-intersection message; language filter.
- Leaderboard: persona, project, period (30d/90d/365d/all), min n (default 20) filters; re-ranks.
- Widgets: `outcome_success`, `outcome_first_pass`, `outcome_defects`, `outcome_cost_per_success` computed in `exec_dashboard/sections.py` from `outcome_ledger.metrics`, appended to `WIDGET_KEYS` (`exec_dashboard/__init__.py`) and to the JSON fixture both tests read; rendered next to the unchanged `quality.avg`.
- Tables scroll inside their own container at 390 px; labels wrap, never truncate. Copy per ui-copy-standards.
Credentials & reach:
- pytest ACs: none beyond the worktree (`./dc` stack) — verified (worktree).
- Dev UI flows as Jaah-org Owner and as operator: `me/jaah/debug/main/operator-login` (operator, switches into tenants); environment `https://app.dev.jaah.ai` (public dev console; the internal `dev.jaah.ai` ALB did not answer from h2 within 15 s) — verified: credential_get ok; `app.dev.jaah.ai` → HTTP 200 from h2.
- Member-seat negative checks (AC3.3, AC5.2): `org/console/dev-tpl-member-login`, dev — verified: credential_get ok.
Out of scope: the task Outcome panel and the operator Agent index (own children); the BT column and benchmark controls (benchmark UI child); the security column (security child adds it); the weekly review job and `quality.avg` widget stay unchanged.
Out of scope (epic, verbatim): "any routing behaviour (the routing epic reads this ledger); new production signal sources (the production QA epic writes deployed/incident_linked); a public index or tenant-to-tenant sharing; re-scoring tasks older than a one-off backfill of merge, lane and cost facts."
Do NOT touch (PRD A8): no change to `apps/api/app/agent_resolution.py` resolution order, `apps/api/app/jaah_scheduler.py` account placement, or `apps/api/app/jaah_routing/` (`routing_decision_id` stays null); no deploy ingestion, monitor adapters, incidents table or new trigger source kinds (`apps/api/app/triggers.py` untouched; `deployed_at`/`incident_linked` are columns/states nothing writes); no public page, no unauthenticated endpoint, no tenant-to-tenant view, no tenant name on any operator-plane aggregate; no retired or rewritten weekly outcome-review job (`apps/api/app/operator_dashboard/review.py`, `apps/api/app/operator_dashboard/quality.py`, D4); backfill writes merge, lane and cost facts only, never re-runs LLM scoring, never re-opens old tasks; benchmark mode never merges a shadow PR and never changes the origin task's dispatch. No CP forge call (ADR-0156): forge facts come only through the `outcome_scan` runner. Migrations additive only (new tables + nullable/defaulted columns).
Done means:
- AC5.1 "As an Owner of the Jaah org, open Agents › Performance [here: Command center › Performance]. Observe one profile per built-in provider (CLAUDE, CODEX, DEEPSEEK, ANTIGRAVITY, PI). Each metric shows a value with a \"±\" or [lo–hi] interval. Configurations under 20 outcomes are grey with \"Insufficient data (n=…)\". Provider and model names are shown in full, never cut to a few characters."
- AC5.2 UI half "As a Member seat, the Performance tab, Compare, Leaderboard and CSV are absent, and their direct URLs show the no-access page."
- AC5.3 "Open Compare and choose two configurations. Observe both columns with the shared strata named. Choose two with no shared strata and observe the \"no shared strata\" message."
- AC5.4 "Open Leaderboard and set the persona, project, period and min n filters. Observe the list re-rank, and observe that min n hides configurations below it."
- AC5.6 "Open the Command center. Observe the ledger widgets **and** the existing quality widget."
- AC5.7 "Click Export CSV. A file downloads, and its rows equal the ledger rows for this org only."
- AC5.9 (these pages) "At a 390 px viewport, every page above has no horizontal page scroll. Tables scroll inside their own container."
- AC7.1 "**Verify:** `./dc check full` passes with no change to existing dispatch and resolution tests. On dev, Agents › Performance renders for the Jaah org with no setting changed."
- Planner tightening (quality): vitest covers the grey insufficient row, the interval cell format, the empty-intersection state and Member absence; the pages are readable (no clipped headers, numbers aligned, units shown) in a screenshot at 390 px and desktop attached to the report — a page that only meets "no horizontal scroll" while clipping labels fails.
- Gate (epic Done means, verbatim: "Required checks: ./dc check quick green; changed-area tests green; merge lane full gate green.") — `./dc check quick` green; changed-area pytest/vitest green; merge lane `./dc check full` green; Jaah Deploy of the PR succeeds.
Activity
- Draft
- Ready