2026-06-12-trackb-governance-dashboard-design
Track B — LOCKED Design: Governance-First Dashboard Refresh
Concept: hybrid of "evidence-first" and "authority-first," landing on a Governance Posture home. Thesis: the dashboard's visible face is _posture + proof_ — what is enforced, who holds what authority, and verifiable evidence of every decision — with routing/usage intact but demoted to Operations. The raw ledger is the drill-down, not the homepage (CloudTrail is not the AWS console home; IAM's summary is what reviewers screenshot). Every claim the UI makes must be backed by a contract that exists at that phase — no surface ships in a degraded or fabricated state.
Stack constraints honored: Vanilla TS/Vite, existing Supabase/Keycloak auth via getFreshToken(), cross-origin authFetch + X-BR-Tenant-Id, files ≤ ~500 LOC, existing token system extended not replaced. Track B invents zero API shape; every missing read is a CONTRACT REQUEST (§6).
---
0. Critique Resolution Register
Every fatal/fixable objection from the six critiques, resolved or deferred explicitly:
| # | Objection (critique) | Resolution |
|---|---|---|
| R1 | No containment path ("how do I stop it") — routes exist today (C0.1, C3.3) | Resolved, Phase 1. New containment-actions.ts mounted in every principal/evidence detail surface: kill-switch scope (POST /auth/killswitch/scope, DELETE …/{type}/{id}), cert block-tenant, API-key disable — with blast-radius copy. Grant revoke joins it in Phase 2. |
| R2 | Grant mutation flagged-open; revoke can't be SDK-only (C0.1 vs C1.2) | Resolved decision proposed to Track A: dashboard is read + simulate + revoke. Issue/attenuate stay apiKey/SDK-only ("agents hold credentials; humans observe, simulate, and contain"). POST /auth/grants/{id}/revoke for JWT humans is a blocking requirement for Phase 2 — revoke is break-glass, not CRUD. |
| R3 | "Who can do X" capability-inverse query missing (C0.2) | Resolved. Added to grants contract: GET /auth/grants?capability=tool:shell&status=active (server-side, computed over envelope_json); surfaced as a "Who can…" tab + one-click pivot from deny detail. |
| R4 | Phase 1 ledger filter pills are a facade; merged tri-source feed silently truncates (C0.3, C2.1) | Resolved. Merged tri-source ledger deleted from Phase 1. Phase 1 ships an honest "Security Events" view: single source, pills limited to type/limit/offset, explicit partial-data banner, no source/agent/verdict pills, no intersection drawer. Full chained ledger is Phase 3, gated on CR-1. |
| R5 | Scope denials / would_deny are not in security-events at all — audit-chain only, apiKey-only (C5.1) | Resolved. Phase 1 never claims to show scope denials. The Phase 1 evidence view is labeled "Policy Violations" (governance/firewall/tool/anomaly events — taxonomy that actually exists) with in-product copy: "Scope denials and would-deny evidence arrive with the audit-chain API." Denials ledger + would-deny preview are Phase 3 (CR-1, CR-3). |
| R6 | mode-dial shows stored mode as if effective; cohort/kill-switch divergence = false ENFORCE (C3.1, C5.3) | Resolved. Phase 1 dial is labeled "configured" everywhere, with a permanent sub-badge "effective state unknown — rollout state unavailable" (fetchStatus pattern). "Effective" wording, cohort/kill-switch badges, and the gated enforce-confirm flow unlock only with CR-2/CR-5 (Phase 3). |
| R7 | PATCH /auth/tenant-settings shallow-merges security → dial write clobbers guardian/toolFirewall; unversioned race (C5.2) — verified: tenant-config-ops.ts:68 "contentLogging + features merged deep, rest shallow" | Resolved. Phase 1 dial does read-merge-write of the full security object (documented race, flagged in code comment + this doc). CR-2 adds a narrow PATCH /auth/security/scope-enforcement write route; dial switches to it when available. |
| R8 | policy/dry-run is the rules engine, not the scope composer; per-source lanes/signature footer unbuildable (C2.2, C3, C4.3, C5.3) | Resolved. Phase 1 simulator is named "Policy Dry-Run" (never "PDP"), renders exactly {decision, matchedRule, ruleCount, mode} as a matched-rule card with microcopy "governance rules only — scope composition simulation arrives later." Per-source lanes render only from POST /auth/grants/{id}/check (Phase 2) or CR-4 scope-check (Phase 3). No fabricated signature footer, ever. |
| R9 | Gray "unavailable" chain badge in global chrome = wallpaper / anti-screenshot (C0-sec, C1.1, C4.1) | Resolved. chain-verify-badge is not mounted at all until GET /auth/audit/verify exists (CR-1). It then appears on the Ledger page first; promoted to global chrome only after one stable release. Absent badge is honest; perpetual gray is not. |
| R10 | Client-side Ed25519 fails on canonicalization drift; false BROKEN is max-severity UI from a serialization bug (C2.3) | Resolved. Lockstep requirement: @brainstormrouter/sdk-ts exports canonicalizeGrant (preferred) or /chain bundle includes canonical_bytes_b64 per link. Client verify failure falls back to server-attested state, never renders BROKEN. Badge copy: "signatures verified against JWKS fetched from brainstormrouter.com" — never "independently verified." BROKEN AT #n is reserved for server attestation. |
| R11 | Liaison plans relabeled "ephemeral grants" contradicts the ADR's own "Plans vs Grants vs Delegation" taxonomy (C4.2) | Resolved. Plans are never styled or labeled as grants. They render in a distinct "Plans" section (Phase 4, CR-9) with copy "Plans are request-scoped; durable grants arrive with capability delegation." #grants nav item is capability-detected — hidden until /auth/grants responds; its empty state is an issue-your-first-grant SDK snippet (empty-as-onboarding). |
| R12 | Per-principal evidence feeds unimplementable on type/limit/offset-only security-events; wrong-empty feeds (C3.2) | Resolved. CR-6 adds actor=, source=, date_from= to the existing security-events capability (cheap). Until granted, per-principal panels render "filtering unavailable — showing tenant-wide events," never a filtered-looking subset. Server-side filtering is a stated precondition in evidence-feed.ts. |
| R13 | Point-in-time composition: inspector shows _now_, denial happened _then_ (C3.2) | Resolved. CR-1/CR-3 payload schemas require the decision-time composed-scope snapshot embedded in each denial/would_deny evidence row (composer already computes it on the request path). The intersection drawer renders the snapshot, labeled with its timestamp; the live inspector is a separate, clearly-labeled "current composition" view. |
| R14 | Alarm fatigue: warn floods, plan-issuance rows, undifferentiated amber (C0-sec, C3) | Resolved. Ledger default filter = enforced DENY + REVOKE; warns appear as an aggregated count chip ("219 warned →") deep-linking to the would-deny view where they're the point. Repeated would_deny deduped via /auth/security-events/summary grouping ("would_deny × 41 · rule R · principal P"). Routine plan/grant-issued rows collapsed by default. Red strictly for enforced denials/revocations/chain-break. |
| R15 | Would-deny preview window-blindness (monthly batch agent invisible in 7d lookback) (C0-sec) | Resolved. Enforce-confirm flow shows the lookback window, lets the operator widen it (CR-3 has date_from), and displays "oldest evidence available: N days." |
| R16 | Stat cards false all-clear on fetch failure (C0-sec) | Resolved. "Data Unavailable" rendering (the state.ts fetchStatus pattern) is a stated acceptance criterion for every stat card, badge, dial, and feed on governance surfaces — enforced by test (§7). |
| R17 | "Humans" principal rows have no JWT list route (C5-sec) | Deferred. Phase 1 directory is agents + API keys only; "humans" filter pill dropped. Members-list is CR-11, lowest priority — profile.memberships covers the self case and the demand is unproven. |
| R18 | CR liaison-read may be schema-level work, not a route mirror; CR scope-check is composer-refactor-sized (C5-sec) | Acknowledged in CR list. CR-9 and CR-4 carry explicit "sizing: possibly schema/refactor work, not a thin mirror" flags so Track A can cost them honestly. |
| R19 | Mirror-tax-forever; generic JWT proxy might beat route-by-route mirrors (PA.7, C5-sec) | Deferred to Track A as an explicit architecture question attached to the CR list. Track B consumes whichever shape ships; the per-route list below is the demand statement, not the implementation mandate. |
| R20 | Cross-plane doubt: "is the /auth mirror the same chain agents write via /v1?" (C4-sec) | Resolved. Ledger page displays the chain head hash, so an apiKey-side GET /v1/audit/verify provably matches the dashboard — turns the mirror tax into a demo. |
| R21 | OPERATIONS mass vs. order; demotion blurs if sub-list ships expanded (C4-sec) | Resolved. Collapsed Operations sub-list is a hard requirement with a snapshot test, not a wireframe nicety. |
| R22 | Vanilla TS friction at chain-tree/live-feed ambition (PA.6) | Deferred with reason. Constraint holds; reuse map is verified real (delegation-tree, ev-*, forensic-detail-modal). Revisit as a flagged decision only if Phase 2 grant interactions blow the LOC budget in practice. |
---
1. Information Architecture
1.1 Nav (sidebar groups; order is the message)
BRAINSTORM ROUTER [tenant ▾] (chain badge: Phase 3+, see R9)
┌────────────────────────────┐
│ ● Posture (HOME)│ #posture NEW — default route
│ │
│ EVIDENCE │
│ Ledger │ #evidence Phase 1: "Policy Violations" scope;
│ Forensics │ #forensics Phase 3: chained audit ledger
│ Compliance │ #compliance (existing)
│ │
│ AUTHORIZATION │
│ Grants │ #grants Phase 2; capability-detected (hidden until API responds)
│ Scopes & Enforcement │ #enforcement NEW — absorbs mode dials from security.ts
│ Policy Dry-Run │ #simulator NEW — never labeled "PDP" pre-/check
│ Guardrails │ (existing)
│ Tool Firewall │ (existing)
│ │
│ IDENTITY │
│ Agents │ (roster + dossier, existing)
│ Trust & Identity │ (#anomaly → security.ts; loses mode dials to #enforcement)
│ Delegation Map │ (topology.ts, relabeled)
│ │
│ OPERATIONS (collapsed ▸) │ Analytics · Logs · Costs · Routing Brain ·
│ │ Models · Providers · Leaderboard · Evaluations
│ DEVELOP (collapsed ▸) │ Playground · Prompts · MCP
└────────────────────────────┘
Settings sub-sidebar unchanged (Router Config, API Keys, Broadcasts, Quick Start, Admin)
Mechanics: extend PAGE_IDS with posture, evidence, enforcement, simulator, grants; LEGACY_MAP entries keep every old URL working (security/evidence→evidence, etc.). Default route flips analytics → posture in Phase 1 — safe because the posture page is honest and never empty on existing contracts (see 1.2A). Analytics remains first item in Operations and gains a "Governance posture" card linking up to #posture so the old landing advertises the new face.
1.2 Key pages
A. Governance Posture (#posture, default landing). The screenshot a security reviewer pastes into a vendor assessment — and never empty: "enforcement: off/off — here's how to turn it on" is itself useful content from minute zero.
┌──────────────────────────────────────────────────────────────────────────┐
│ GOVERNANCE POSTURE last 7d ▾ │
│ ┌ ENFORCEMENT (configured) ──────────────┐ ┌ KILL SWITCH ─────────────┐ │
│ │ api_key [ off | WARN | enforce ] │ │ ○ clear · 0 scopes active│ │
│ │ br_scope [ off | warn | ENFORCE ] │ │ [view scopes] │ │
│ │ ⓘ effective state unknown — │ └──────────────────────────┘ │
│ │ rollout state unavailable (CR-2/5) │ ┌ AGENTS ──────────────────┐ │
│ └────────────────────────────────────────┘ │ 38 active · trust mix ▂▅▇│ │
│ ┌ VIOLATIONS 7d ─────────────────────────┐ └──────────────────────────┘ │
│ │ governance 12 · firewall 4 · anomaly 1 │ ┌ GRANTS (Phase 2) ────────┐ │
│ │ top rule: R-7 (×9) [→ evidence] │ │ 12 active · 3 revoked/wk │ │
│ └────────────────────────────────────────┘ └──────────────────────────┘ │
│ Phase 3 additions: chain ✓ badge · would-deny 7d sparkline · flag cohort │
└──────────────────────────────────────────────────────────────────────────┘
Phase 1 sources (all exist): /auth/tenant-settings (configured dials), /auth/killswitch/status+/scopes, /auth/security-events/summary, /auth/mesh/agents, /auth/governance/compliance. Every card inherits fetchStatus → "Data Unavailable", never a false zero (R16).
B. Evidence Ledger (#evidence). Phase 1 = honest single-source "Policy Violations" (R4, R5):
│ POLICY VIOLATIONS ⓘ Scope denials & would-deny evidence │
│ [type ▾] [limit] arrive with the audit-chain API │
│ 14:02 governance.violation rule R-7 actor research-agent-1 [detail] │
│ … detail drawer: json-viewer over raw details · containment-actions block │
Phase 3 (CR-1) = the chained ledger:
│ EVIDENCE LEDGER [chain ✓ verified · 14,203 · head a91f3c…] [Export] │
│ [verdict: DENY+REVOKE (default)] [source ▾] [actor ▾] [date] [⌕] │
│ chip: "219 warned →" (links to would-deny view) LIVE ● (SSE) │
│ 14:02:11 DENY researcher-7 model:gpt-5 source:plan entry #14,198 ✓ │
│ ── drawer: decision-time scope snapshot (R13) · scope-intersection · │
│ principal card · containment-actions · [Open in Forensics] [Simulate] │
Head hash displayed for cross-plane verification (R20). Export Phase 1 = "export visible rows" client-side; Phase 3 = /auth/audit/export (R4-adjacent, C2-sec).
C. Scopes & Enforcement (#enforcement). Phase 1: dials labeled configured, read-merge-write of full security object (R6, R7); Policy Dry-Run embedded. Phase 3: effective mode + cohort + kill-switch badges (CR-2/CR-5), would-deny preview with adjustable lookback + "oldest evidence: N days" (R15), gated enforce-confirm ("these 219 would have been blocked — proceed?"), per-principal current composition inspector via CR-4 (clearly labeled "now," distinct from decision-time snapshots).
D. Grants (#grants, Phase 2). Hidden until the API responds (R11). List with subject/status/root filters + "Who can…" tab (capability-inverse, R3). Detail = grant-chain-tree (per-hop scope narrowing, sig status, revoked-ancestor banner), json-viewer envelope, [Revoke (cascade)] (R2) + [Download evidence bundle] + [Check… → simulator]. Issue/attenuate render as SDK snippets, not buttons. Evidence-under-the-object: feed filtered to grant_context.grant_id at the bottom of the panel.
E. Policy Dry-Run / Simulator (#simulator). Phase 1: matched-rule card, exactly {decision, matchedRule, ruleCount, mode}, microcopy "governance rules only" (R8). Phase 2: grant-targeted /check with signed verdict + per-source lanes + kid footer. Phase 3: general composer lanes via CR-4.
F. Agent identity drawer (extends agent-dossier.ts): trust level, manifests, grants held (Phase 2), per-principal evidence feed (honest label until CR-6), containment-actions block (R1), envelope claims (Phase 4, CR-8).
---
2. Component Inventory (all in src/components/, ≤400 LOC each)
| Component | Phase | Consumed contracts | Notes |
|---|---|---|---|
posture-cards.ts | 1 | tenant-settings, killswitch/status+scopes, security-events/summary, mesh/agents, governance/compliance | composition of ev-stat-card; fetchStatus mandatory |
containment-actions.ts | 1 | POST /auth/killswitch/scope, DELETE …/{type}/{id}, POST /auth/certs/block-tenant, API-key disable | blast-radius copy; active-scope indicator on rows whose principal is blocked; + grant revoke in Phase 2 |
mode-dial.ts | 1→3 | P1: GET/PATCH /auth/tenant-settings (read-merge-write full security, R7); P3: CR-2 (GET+PATCH), CR-3, CR-5 | "configured" label until CR-2/5; enforce gate + lookback control P3 |
dry-run-card.ts | 1→2/3 | P1: POST /auth/governance/policy/dry-run (exact fields); P2: /check mirror; P3: CR-4 | no fabricated lanes/signatures (R8) |
evidence-feed.ts | 1→3 | P1: /auth/security-events (+summary for dedupe, R14); P3: CR-1 events + CR-6 params + CR-7 SSE | thin ev-event-feed+ev-filter-pills wrapper; "tenant-wide" label until CR-6 (R12) |
grant-chain-tree.ts | 2 | GET /auth/grants/{id}/chain, JWKS | fork of delegation-tree.ts (scope-narrowing strips replace budget slices); hop click → forensic-detail-modal+json-viewer |
chain-verify-badge.ts | 3 (mounted only then, R9) | CR-1 /auth/audit/verify; Phase 2 grant-link layer: JWKS + SDK canonicalizeGrant (R10) | states: verified ✓ / verifying / BROKEN (server-attested only) / unavailable-gray (transient only); broken-state click-through to a triage view (C3) |
scope-intersection.ts | 2/3 | /check responses; CR-1/CR-3 decision-time snapshots (R13); CR-4 | never fed by dry-run; renders enforced_dimensions so coverage is never overstated |
verdict-chip.ts | 1 | log/violation status fields that exist | color + glyph + word, never color alone |
Reused as-is: ev-stat-card, ev-event-feed, ev-filter-pills, ev-sortable-table, json-viewer, forensic-detail-modal, governance-bar, threat-banner, delegation-tree (verified present).
---
3. Routing/Usage: Demoted but Loved
Zero feature removal. All LLMS/OBSERVABILITY pages move under one collapsed OPERATIONS group (hard requirement + snapshot test, R21). Integration touches: Logs rows gain verdict chips linking into evidence; Analytics gains the governance-posture card; decision-trace gains an "authority: grant grt\_… ✓" lane when grant_context lands (routing becomes a _consumer_ of the authority story); cost-by-grant is a Phase 4 nice-to-have. LEGACY_MAP preserves every bookmark.
---
4. Design Language
Extend the existing dark-industrial token system (--ink-, --bone, --sig-*); additions go in index.html :root (tokens.css is auto-synced — never hand-edit).
- New tokens:
--verdict-allow/-deny/-warn(warn-amber visually distinct from error-red everywhere — the warn→enforce journey is the product),--verdict-revoked(desaturated strike),--authority-active/-attenuated,--chain-verified— a reserved green used only for cryptographic verification, never generic "ok." - Verdicts are always color + glyph + word (✓ ALLOW / ✗ DENY / ⚠ WARN).
- Density: operator-dense; tables over cards; monospace ids/kids/hashes with copy-on-click (numeral-parity test already guards tabular numerals); existing density toggle honored.
- Tone: declarative, forensic, zero marketing copy. "14,203 entries · chain verified," "Chain verification unavailable (fetch failed)," "No denials in range." Never "Oops." UTC timestamps, local on hover. Every evidence view has Export.
- Motion: none decorative, two exceptions — revoke cascade animating down the chain tree (200ms stagger; the demo moment) and the server-attested broken-chain banner.
- New CSS:
styles/pages/{posture,enforcement,grants,evidence}.css, each <300 LOC.
---
5. Phased Delivery
Phase 0 — Debt (no visual change, no backend). Split api.ts (3326 LOC) → src/api/index.ts barrel + src/api/{core,security,usage,agents}.ts (delete api.ts to avoid ./api resolution ambiguity, eat the one-line import churn — C2-sec); new domains api/{audit,grants,scopes}.ts start clean. Extract nav config from main.ts (<500 LOC). Rename security-center.ts→forensics.ts. Re-baseline visual snapshots.
Phase 1 — Posture + honest evidence (EXISTING CONTRACTS ONLY — zero Track A work). #posture home + default-route flip; nav re-IA + collapsed Operations; #enforcement with configured-only dials (read-merge-write, R6/R7); #evidence as "Policy Violations" (R4/R5); #simulator dry-run card (R8); containment-actions everywhere a principal appears (R1); verdict chips in Logs; alarm-fatigue defaults (R14). Not in Phase 1: chain badge, would-deny anything, source/agent pills, scope-intersection, grants nav item, "humans" pill (R17).
Phase 2 — Grants (gated on: Inc 3 shipped + grants JWT read-mirrors committed-or-cut + POST /auth/grants/{id}/revoke for humans + TS SDK with canonicalizeGrant). #grants read+simulate+revoke via @brainstormrouter/sdk-ts grants.* in api/grants.ts (lockstep rule — no hand-rolled fetch); chain tree; client JWKS verification with server-attested fallback (R10); evidence bundle download; "Who can…" tab (R3); API Keys grant-bound column; /check-fed intersection + signed verdict card; revoked-ancestor banners; capability-detected nav reveal with onboarding empty state (R11).
Phase 3 — Evidence honest (gated on contract requests; independent of Phase 2, may ship earlier/parallel as Track A delivers). CR-1 → ledger flips to /auth/audit/events, chain-verify-badge mounts (ledger first, chrome later, R9), head hash shown (R20), server export. CR-2/CR-5 → effective-mode dials, narrow enforcement write, cohort/kill-switch badges. CR-3 → would-deny preview + gated enforce flow + lookback control (R15) + decision-time snapshots in drawers (R13). CR-6 → per-principal feeds. CR-7 → SSE live tail replaces polling. CR-4 → general composer lanes in simulator.
Phase 4 — Polish (remaining CRs). Liaison "Plans" view (CR-9, distinct from grants, R11); identity envelope panel (CR-8); cost-by-grant; saved ledger filters; scheduled export; members list if CR-11 granted.
---
6. Consolidated CONTRACT REQUESTS to Track A
All /auth/* routes: JWT-bridge, tenant-scoped via X-BR-Tenant-Id, added to the CORS allowlist in src/api/server.ts and forwarded by src/gateway/api-mount.ts (CLAUDE.md triage order). Standing architecture question (R19): consider a generic JWT-bridge proxy with permission mapping instead of route-by-route mirrors — this list is the demand statement either way. Priority: CR-1 > CR-2 > CR-3 > CR-6 > grants block (with Inc 3) > CR-4 > CR-5 > CR-7 > CR-8 > CR-9 > CR-11.
- CR-1 — Audit chain read (mirror
/v1/audit/*).
GET /auth/audit/events?actor=&action=&resource_type=&resource_id=&outcome=&source=&date_from=&date_to=&q=&limit=&offset= → { events: AuditEvent[], total } — with a typed evidence payload (C2.1): details: { source, principal, requested: {models?, tools?}, enforced_dimensions, reason, scope_snapshot } where scope_snapshot is the decision-time composed scope (R13). GET /auth/audit/verify → { valid, chainLength, headHash, brokenAt? }. GET /auth/audit/export?format=csv|json|cef&date_from=&date_to= → raw download. GET/PUT /auth/audit/retention (PUT admin-gated).
- CR-2 — Scope-enforcement resolved state + narrow write (R6/R7).
GET /auth/security/scope-enforcement → { sources: { api_key: {storedMode, effectiveMode, inCohort, killSwitch}, br_scope: {…}, delegation?: {…} } } (resolved via resolveScopeMode, not the raw blob). PATCH /auth/security/scope-enforcement — narrow, enum-validated, race-free write owning only its sub-key (today's shallow PATCH /auth/tenant-settings clobbers sibling security.* keys — verified tenant-config-ops.ts:68).
- CR-3 — Would-deny evidence.
GET /auth/security/would-deny?source=&date_from=&limit=&offset=→{ events: [{requestId, source, principal, requested, reason, scope_snapshot, createdAt}], total, bySource, oldestEvidenceAt }(oldest-evidence field per R15). - CR-4 — General composer shadow check.
POST /auth/security/scope-check{principal, requested:{models?,tools?}}→{ verdict, perSource:{api_key,plan,br_scope,delegation}, enforced_dimensions }— dry composition, no enforcement side effects. Sizing flag:composed-scope.tsis interwoven with request context; this is composer-refactor work, not a thin route (R18)./checkcovers grants only; this gap survives Inc 3. - CR-5 — Feature-flag posture.
GET /auth/feature-flags→{ flags: [{name, enabled, cohort?, rolloutPercent?, tenantBlocked}] }(coversenforce_*,grants_api,tenants_blocked). - CR-6 — Security-events filter params (cheap, pre-CR-1 stopgap). Add
actor=,source=,date_from=to the existing/auth/security-eventscapability (today:type/limit/offsetonly, 200 cap). - CR-7 — SSE event types on
/auth/events(currently ping-only;sse-broadcaster.tsexists, genuinely cheap):scope_denial | would_deny | grant_issued | grant_revoked | chain_breakwith{requestId?, grantId?, source?, principal, requested?, reason, ts}. - CR-8 — Identity envelope inspection.
GET /auth/agents/{agentId}/envelope→{ trustLevel, spiffeId, claims, authMethod, kid, expiresAt, signatureValid }. - CR-9 — Liaison read surface.
GET /auth/liaison/sessions?status=&limit=;GET /auth/liaison/{id}/plans→[{planId, allowedModels, allowedTools, ceilingUsd, committed, fingerprint, issuedAt}]. Sizing flag: liaison routes are write-shaped; this may require new persistence/query schema (R18). - CR-grants block (lands with Inc 3; Phase 2 gate, committed-or-cut before Phase 2 starts — R2):
- Read mirrors:
GET /auth/grants?subject=&status=&root=&capability=(capability-inverse param per R3, computed server-side over effective attenuatedenvelope_jsonscopes),GET /auth/grants/{id},GET /auth/grants/{id}/chain,POST /auth/grants/{id}/check. POST /auth/grants/{id}/revokefor JWT humans — blocking requirement (break-glass, not CRUD). Issue/attenuate stay apiKey/SDK-only (resolved decision, R2).- Lockstep: SDK exports
canonicalizeGrantor/chainbundle includescanonical_bytes_b64per link (R10). - Confirm CORS on public
GET /.well-known/brainstorm/grant-keysfor the dashboard origin.
- CR-11 (optional, lowest): tenant members list for a "humans" principal directory (R17).
---
7. Test & Verification Obligations
Every phase: existing theme-tokens.test.ts + stat-numeral-font-parity.test.ts stay green; Playwright visual snapshots re-baselined per phase; LOC budget check (no new file >500 LOC).
Phase 0: barrel-split import-equivalence test (every former api.ts export resolvable); zero-visual-diff snapshot run.
Phase 1:
- No-fake-green suite: for each posture card / dial / feed, mock a 500 and assert "Data Unavailable" renders — never 0, never green (R16).
- No-overclaim suite: assert dial label is "configured" and effective-state badge present (R6); assert simulator card contains no per-source lanes or signature elements (R8); assert "Policy Violations" banner copy present (R5); assert chain badge is absent from the DOM (R9).
- mode-dial read-merge-write test: PATCH payload must contain the full prior
securityobject (guardian/toolFirewall preserved) (R7). - containment flow e2e: deny row → contain →
POST /auth/killswitch/scopepayload + active-scope indicator (R1). - Snapshot: Operations group collapsed by default (R21). Router: default route
posture; allLEGACY_MAPredirects.
Phase 2:
- Canonicalization round-trip against SDK fixtures:
verify(sig, canonicalizeGrant(envelope_json))for known-good vectors; drift-injection test asserts fallback to server-attested, never client-rendered BROKEN (R10). - Grant revoke e2e incl. cascade banner; capability-detected nav (hidden when
/auth/grants404s); "Plans ≠ grants" — no grant styling/status chips on plan objects (R11); "Who can…" tab returns server-computed results, no client intersection (R3). - Badge copy assertion: "verified against JWKS fetched from…" exact string (R10).
Phase 3:
- Ledger default filter = DENY+REVOKE with warn aggregate chip (R14); pills map 1:1 to CR-1 query params (no client-side-only filters — R4); truncation banner when
total > rendered. - Effective-vs-configured: cohort-off fixture must render effective OFF while stored ENFORCE (R6).
- Enforce-confirm flow: lookback control + "oldest evidence" rendered (R15). Decision-time snapshot rendered from row payload, not live recompute (R13). SSE reconnect/out-of-order tolerance. Head hash equals
/auth/audit/verify.headHash(R20).
---
8. Decomposable Work Items
Phase 0 — W0.1 api barrel split (1 PR/domain: core, security, usage, agents). W0.2 nav-config extraction from main.ts. W0.3 security-center.ts→forensics.ts rename + LEGACY_MAP. W0.4 snapshot re-baseline.
Phase 1 — W1.1 router: add ids, default→posture. W1.2 nav groups + collapsed Operations. W1.3 posture-cards.ts + #posture page + CSS. W1.4 mode-dial.ts (configured-only, read-merge-write) + #enforcement page; remove dials from security.ts. W1.5 #evidence Policy Violations view + detail drawer. W1.6 containment-actions.ts + mounts (evidence drawer, agent dossier). W1.7 dry-run-card.ts + #simulator. W1.8 verdict chips in Logs. W1.9 analytics governance card. W1.10 token additions in index.html :root. W1.11 Phase-1 test suites (§7).
Phase 2 (after gate) — W2.1 api/grants.ts over SDK. W2.2 #grants list + Who-can tab. W2.3 grant-chain-tree.ts. W2.4 detail panel: envelope viewer, bundle download, evidence feed. W2.5 revoke flow + cascade animation. W2.6 client JWKS verify layer + fallback. W2.7 /check simulator + scope-intersection.ts. W2.8 API Keys grant-bound column. W2.9 nav capability-detection + onboarding empty state. W2.10 Phase-2 tests.
Phase 3 (per-CR, independently landable) — W3.1 api/audit.ts + ledger flip + export + head hash (CR-1). W3.2 chain-verify-badge.ts on ledger, then chrome (CR-1). W3.3 dial upgrade: effective mode + narrow PATCH (CR-2/5). W3.4 would-deny preview + enforce gate + snapshots (CR-3). W3.5 per-principal feeds (CR-6). W3.6 SSE live tail (CR-7). W3.7 composer lanes in simulator (CR-4). W3.8 Phase-3 tests.
Phase 4 — W4.1 Plans view (CR-9). W4.2 envelope panel (CR-8). W4.3 cost-by-grant. W4.4 saved filters + scheduled export. W4.5 members directory (CR-11, if granted).
---
Key paths: site/dashboard/src/{router.ts,main.ts,api.ts→api/index.ts,state.ts,event-stream.ts}; pages {security.ts,security-center.ts→forensics.ts,tool-firewall.ts,guardrails.ts,compliance.ts,agents.ts,agent-dossier.ts}; components {delegation-tree,ev-event-feed,ev-filter-pills,ev-stat-card,ev-sortable-table,json-viewer,forensic-detail-modal,governance-bar,threat-banner,decision-trace}.ts; styles/tokens.css (auto-synced from index.html). Backend contract anchors: src/api/capabilities/security/audit-export.ts, src/api/capabilities/auth/{usage-insights.ts,governance-forensics.ts,tenant-config-ops.ts,budget-killswitch.ts,events.ts,api-keys.ts,agents-mesh.ts}, src/api/capabilities/liaison/*, src/security/{scope-match.ts,effective-scope.ts,trust-envelope/}, src/api/routes/completions/composed-scope.ts, src/infra/feature-flags.ts, docs/ship-log/2026-06-12-inc3-capability-grants-delegation.md (§API surface).