Skip to content

feat(auth): let analysts read and build dashboards, and refuse every other change - #2546

Merged
Makisuo merged 4 commits into
observer/policyfrom
observer/enforcement
Nov 13, 2026
Merged

Makisuo merged 4 commits into
observer/policyfrom
observer/enforcement

feat(auth): let analysts build and share dashboards

f72b00b
Select commit
Loading
Failed to load commit list.
Maple Review Bot / Maple / review succeeded Nov 13, 2026 in 3m 20s

Confidence 10/10 · No issues found

🟢 Confidence 10/10 · safe to merge. The gate is one four-line pure function wired into every auth path, and real middleware tests pin both the refusal and the allowed reads.
no findings · tests covered · risk high · 1/1 new units observable

Refuses an analyst-role session or credential every non-GET endpoint on the internal and /v2 APIs unless the endpoint is annotated AnalystAccess or the /v2 scope registry already calls it a read. The check lives in the auth layers, so an unmarked endpoint fails closed, and it is covered by middleware-level tests.

  • checkAnalystAccess refuses analysts by default in all three auth middlewares
  • AnalystAccess annotation opens reads, own-state writes and dashboard authoring
  • /v2 dashboard writes are marked author: analysts may create, share and delete
  • LimitedRoleError (403 limited_role) added to the middleware error unions

Before merge

  • Manual · Check the Clerk role keys on dev and prd map to a known clerkKey: roleOf falls back to analyst, which now refuses every write

Production impact

Production traffic of the changed files (last 7 days)
File Calls/day Busiest operations
packages/domain/src/http/analyst-access.ts 547 GET 300/day, 0.3% err
GET 247/day, 3.1% err
packages/domain/src/http/digest.ts 53 preview 53/day, 1.1% err
packages/domain/src/http/v2/device-credentials.ts 49 POST /v2/device_tokens 49/day, 0.0% err
packages/domain/src/http/v2/dashboards.ts 43 GET /v2/dashboards 43/day, 0.0% err
What was checked
  • Gate reads endpoint then group annotations with the same Context.get idiom as withAuditedRead (audit-access.ts:156)
  • requiredScopeForRoute returning null still fails closed through ?.access !== "read" (analyst-gate.ts:31)
  • Approve handlers pass tenant.roles into the minted CLI/MCP credential, so it cannot outrank the caller
Observability coverage: 1 of 1 changes observable
Change Kind Observable Evidence
analyst refusal in the internal and /v2 auth middlewares authorization gate yes API-key and device-token denials call recordApiDenial (ApiAuthorizationLive.ts:78, ApiAuthorizationV2Live.ts:305); the 403 carries code: limited_role

f72b00b · Reply "won't fix" to dismiss a finding, or mention @maple-review-bot to ask about one.