Track B Phase 2 gate — JWT-human grant read/simulate/revoke (the CR-grants block)
2026-06-21
LOCKSTEP TRACEABILITY MATRIX --- api_endpoints: [ "GET /auth/grants", "GET /auth/grants/{id}", "GET /auth/grants/{id}/chain", "POST /auth/grants/{id}/check", "POST /auth/grants/{id}/revoke", ] sdk_methods_updated: ["none — dashboard-internal JWT mirror of existing /v1/grants SDK surface"] mcp_tools_updated: ["none — agent grant surface unchanged (br_grant_* already covers /v1)"] ---
What We Built
Track B (the governance dashboard) is a four-phase plan; Phase 1 shipped the honest posture/evidence surfaces and containment actions. Phase 2 was gated on a backend "CR-grants block" — a committed-or-cut set of JWT-human grant routes — before any dashboard grant UI could be built. This increment delivers that gate.
Five routes now exist under /auth/grants, the human mirrors of Increment 3's apiKey /v1/grants surface, all Supabase-JWT-authed and tenant-scoped:
GET /auth/grants?subject=&status=&root=&capability=&limit=— list, including the "Who can…" capability-inverse (R3):?capability=tool:shellreturns the grants that actually authorize that capability, computed server-side over each grant's effective scope using the same exact-match algebra (matchCapabilityScope) enforcement uses — never a fabricated client-side glob.GET /auth/grants/{id}— record + a verified chain summary bound to the grant's signed-envelope subject (not the unsigned relational column).GET /auth/grants/{id}/chain— the offline-verifiable bundle (root-first envelopes + signatures + JWKS ref).POST /auth/grants/{id}/check— a signed PDP verdict via the same shared verdict core the apiKey route and the MCPbr_grant_checkadapter consume, so a dashboard simulation can never diverge from inline enforcement.POST /auth/grants/{id}/revoke— break-glass, owner/admin only (R2).
Why It Matters
The product promise is "stop it, and prove what happened." Until now a human operator could _read_ governance state but had no first-class, audited way to pull authority — revoke lived only in the SDK an agent holds. Phase 2's revoke route makes containment a human break-glass action: a tenant owner/admin can revoke any grant in their tenant, and because the human is never a chain principal, every human revocation is stamped into the tamper-evident lifecycle chain with human_break_glass + admin_override markers — forensically distinct from an issuer/ancestor-subject revocation, and impossible to silently undo.
How It Works
Each route reads tenantCtx set by supabaseJwtAuthMiddleware (newly mounted on /auth/grants and /auth/grants/* in registerPreAuthRoutes) and delegates to the same store + shared cores the apiKey routes use (getGrantDeps, evaluateGrantCheckVerdict, recordGrantLifecycle, projectGrantRow). Issue and attenuate are deliberately absent — agents hold credentials and mint authority over the SDK; humans observe, simulate, and contain.
Revoke is gated twice: the capability declares permission: grants.revoke, and the handler additionally requires membershipRole ∈ {owner, admin} — a plain member can read and simulate but never pull authority. The lifecycle row is written on every revoke attempt, including an idempotent re-revoke that flips nothing (already_revoked: true), so a direct-DB un-revoke followed by a re-revoke is detectable.
The Numbers
- 5 new JWT-human routes; 6 new tests (capability-inverse exact+unrestricted matching, the break-glass role gate, tenant-auth 401, not-found 404, audited revoke).
- 9,019 unit tests pass; OpenAPI (
docs/openapi.yaml) + static-assets + MCP manifest regenerated and in sync;pnpm checkgreen. - 0 new SDK/MCP surface — these are dashboard-internal mirrors of the already-published
/v1/grants(TS/Py SDKgrants.) andbr_grant_MCP tools.
Competitive Edge
A revoke button is table stakes. A revoke button whose every press is cryptographically committed to a tamper-evident chain — distinguishable as a human break-glass override, un-undoable without detection — is the "evidence plane" thesis applied to the operator's own most powerful action. Portkey/OpenRouter/Lasso have dashboards; none can prove who pulled authority and when.
Lockstep Checklist
- [x] API Routes:
src/api/capabilities/auth/grants.ts(5 routes), registered in_registry.ts, JWT mounted inregister-routes.ts. - [x] TS SDK: no change — JWT dashboard mirror of the existing
/v1/grantsSDK surface (grants.*already published inbrainstormrouter0.1.1). - [x] Python SDK: no change (same rationale).
- [x] MCP Schemas: no change — agent grant surface (
br_grant_*) unchanged. - [x] Generated contract:
docs/openapi.yaml(510 paths) +static-assets.generated.tsregenerated;validate:manifestin sync.
Remaining (Phase 2 frontend + Phases 3–4)
This is the backend gate only. The Phase 2 dashboard build (W2.1–W2.10: api/grants.ts over the SDK, #grants list + "Who can…" tab, grant-chain-tree.ts, detail panel + evidence bundle, revoke flow + cascade animation, client JWKS verify with server-attested fallback, /check-fed simulator, API-keys grant-bound column, capability-detected nav) and Phases 3 (evidence honest) and 4 (Plans) remain.