Cory Policht
← All references
REF · US-OUI-1-6-PERMISSION-SCOPING · STORY

Us Oui 1 6 Permission Scoping

source: ADP-Orchestration-UI/Epic-1-Stories/US-OUI-1.6-Permission-Scoping-Enforcement.mdscrubbed extract

Scrubbed extract of ADP-Orchestration-UI/Epic-1-Stories/US-OUI-1.6-Permission-Scoping-Enforcement.md — cited by the Chronicle (03-the-platform-rises). Redactions: phone / service detail / secrets / clearance / personal / internal hosts.


type: implementation-plan category: Work project: ADP-Orchestration-UI priority: High status: done created: 2026-05-14 epic: Epic-1-Foundation user-story: US-OUI-1.6 effort: 8h precedence:

  • “[[Projects/ADP-Orchestration-UI/Epic-1-Stories/US-OUI-1.2-Design-System]]”
  • “[[US-OUI-1.4-API-Client]]”
  • “[[Projects/ADP-Orchestration-UI/Epic-1-Stories/US-OUI-1.5b-Login-Authentication-Page]]” parallel_group: review-profile: standard source: wireframe-gap-analysis-2026-05 depends-on:
  • ADR-013
  • ADR-018 tags:
  • implementation-plan
  • epic-1
  • permissions
  • rbac
  • cross-cutting

US-OUI-1.6: Permission Scoping Enforcement (role-aware rendering primitives)

Epic: [[README|Epic 1: Project Foundation & Design System]] Owner Agent: \xe2\x80\x94 Effort: 8h Priority: 🔼 High Status: 🔵 Backlog

Mission

Build the cross-cutting role-aware rendering primitives that every downstream OUI epic will consume to gate UI surfaces by the authenticated user’s resolved roles. Provide a PermissionContext, a <RoleGate> component, a useHasRole() / useHasSurface() hook pair, and a typed Surface → Role[] map derived from [[../../AI-Dev-Platform/ADR/ADR-013-Permission-And-Role-Model|ADR-013]] §5. Do not invent new role names or extend the platform role set; consume the five platform roles plus the adp-sensitive-viewer modifier as defined in ADR-013. Claims are sourced from the Keycloak access token issued per [[../../AI-Dev-Platform/ADR/ADR-018-Identity-Federation-Strategy|ADR-018]] — do not call a separate authorization endpoint.

Context

The Wireframe Gap Analysis §6 names role/scope-aware view rendering as a missing capability: regular users see their own sessions, operators see fleet view, admins see provider config and the policy editor. ADR-013 defines the two-axis platform×project role model, five platform roles, and a surface→role table; ADR-018 defines the Keycloak claim shape and composite-role expansion. This story produces the React primitives so that subsequent stories in Epic 2 (Spawn Flow, Platform Services panel), Epic 3 (Approval Policy editor, Modify flow), Epic 5 (Provider config), and Epic 6 (admin surfaces, audit feeds) all gate consistently with one mechanism. The non-elevating read-only impersonation flow from ADR-013 §7 is wired here so impersonation context decorates the same primitives — no parallel auth path.

Context Bootstrap

Read the following before writing any code, in order:

  1. [[../../AI-Dev-Platform/ADR/ADR-017-Human-Review-Branching-And-TDD-Workflow|ADR-017]] — review profile (standard), four-checkpoint workflow, TDD enforcement, PR description schema
  2. [[../../AI-Dev-Platform/ADR/ADR-013-Permission-And-Role-Model|ADR-013]] — §2 (five platform roles + adp-sensitive-viewer), §5 (surface→role table), §7 (read-only impersonation rules)
  3. [[../../AI-Dev-Platform/ADR/ADR-018-Identity-Federation-Strategy|ADR-018]] — Keycloak claim shape, composite-role expansion, token format
  4. [[../Wireframe-Gap-Analysis|Wireframe Gap Analysis]] §6 — capability rows for “Permission scoping for unified dashboard” and “Account-switcher / impersonation”
  5. [[../Architecture|OUI Architecture]] — Keycloak auth-broker mermaid sequence
  6. [[Projects/ADP-Orchestration-UI/Epic-1-Stories/US-OUI-1.5b-Login-Authentication-Page]] — token storage, refresh flow, channel selector
  7. [[../_pkf|OUI _pkf]] — current project state, branch protection

Gate Policy

Trigger Gate Type Condition / Reasoning
New role name introduced outside the ADR-013 §2 enumeration Trust Gate Role model is ADR-governed; surface for ADR amendment
Surface added to Surface → Role[] map without a row in ADR-013 §5 Trust Gate Surface mapping is part of the role model contract
Impersonation flow grants any write capability Trust Gate ADR-013 §7 mandates read-only, non-elevating
Token claim parse fails or contains an unrecognized claim Confidence Gate Surface to user with a clear error rather than silently treating user as anonymous
Test failure during Verify & Refactor Trust Gate Do not mark complete with failing tests
Change touches Projects/AI-Dev-Platform/ADR/ Trust Gate Forces rigorous profile; ADR work is rigorous per ADR-017

Deliverables

src/auth/PermissionContext.tsx (new)

  • PermissionContext React context exposing { roles: PlatformRole[]; projectRoles: ProjectRoleEntry[]; sensitiveViewer: boolean; impersonating: ImpersonationState | null }
  • PermissionProvider derives the value from the JWT in the auth store; re-derives on token refresh
  • PlatformRole and ProjectRoleEntry types exported and match ADR-013 §2 exactly (no extra members)

src/auth/roles.ts (new)

  • PLATFORM_ROLES literal union: adp-developer | adp-operator | adp-admin | adp-auditor (+ adp-sensitive-viewer as additive modifier) — corrected 2026-07-03 (W9): the earlier bullet listed adp-viewer/adp-platform-owner, roles that do not exist in ADR-013 §2; the original implementer followed the stale bullet and shipped an invented adp-platform-owner role (fixed in OUI PR #38). ADR-013 §2 is authoritative, per this story’s own Mission text.
  • SENSITIVE_VIEWER_MODIFIER constant adp-sensitive-viewer
  • SURFACE_ROLE_MAP typed Record<SurfaceId, PlatformRole[]> mirroring ADR-013 §5 row-for-row
  • expandCompositeRoles(claims) resolves Keycloak composite expansion per ADR-018

src/auth/RoleGate.tsx (new)

  • <RoleGate require="adp-operator">…children…</RoleGate> renders only if user holds the required role
  • require accepts a single role, an array (OR), or { allOf: [...] } (AND)
  • fallback prop renders an explicit denied state; defaults to rendering nothing
  • Honors impersonation: if impersonating != null, only read capabilities resolve true

src/auth/useHasRole.ts and src/auth/useHasSurface.ts (new)

  • useHasRole(role) returns boolean from PermissionContext
  • useHasSurface(surfaceId) looks up SURFACE_ROLE_MAP and resolves against current roles
  • Both hooks are pure derivations of context; no network calls

src/auth/Impersonation.tsx (new)

  • useImpersonation() exposes start(targetUserId) and stop(); populates impersonating in context
  • Imposed read-only constraint: any write-capable surface returns false from useHasSurface when impersonating != null
  • Impersonation start/stop emits an audit log event via the API client (POST /api/audit/impersonation) per ADR-013 §7

src/auth/__tests__/permission-context.test.tsx (new)

  • Renders <RoleGate> with various role permutations including composite expansion
  • Asserts surface→role map matches ADR-013 §5 every row (snapshot pinned to ADR section)
  • Asserts impersonation forces read-only resolution
  • Asserts unknown claim raises a Confidence Gate marker through the error boundary, not silent allow

Execution Phases

TDD-ordered per [[../../AI-Dev-Platform/ADR/ADR-017-Human-Review-Branching-And-TDD-Workflow|ADR-017]] §4. Plan / Test / PR review checkpoints fire between phases per review-profile: standard.

  1. Branch Creation — Create agent/US-OUI-1.6-permission-scoping from main. No commits to protected branches; AIS fires an IRREVERSIBLE Trust Gate on violation (ADR-017 §3).
  2. Load Context — Read all Context Bootstrap files. Confirm US-OUI-1.5a token storage shape is finalized. (Plan Review checkpoint fires.)
  3. Write Failing Tests — Author the four test groups in permission-context.test.tsx. Run pnpm test --run; every test must fail before implementation. Failing transitions logged via ADR-017 §4 TestTransition. (Test Review checkpoint fires.)
  4. Implement — Add roles.ts, PermissionContext.tsx, RoleGate.tsx, the two hooks, and Impersonation.tsx. Re-run tests; all pass.
  5. Verify & Refactor — pnpm build, pnpm test --run, pnpm lint, pnpm typecheck all green. Update status to review.
  6. PR Submission — Push branch; open one PR to main with the structured PR description below. (PR Review checkpoint fires; merge is human-only.)

PR Submission Requirements

PR description schema enforced at PR-open time per ADR-017 §5 (pr-description-schema-version: 1.0). Required: Reasoning Chain, Gate History (with gateOrigin: review/<phase>), Citations, Review Profile (standard, source: project default), Test Plan with TestTransition rows.

Implementation Notes

Surface ID conventions

SurfaceId is a typed string literal union covering every gated surface: 'sessions.fleet' | 'sessions.spawn' | 'gates.policy.edit' | 'gates.policy.view' | 'routing.providers.edit' | 'platform-services.mcp' | 'audit.sensitivity' | 'audit.impersonation' | 'admin.config'. Add IDs only when a downstream story needs one and there is an ADR-013 §5 row to back it.

Claim source

Roles come from the JWT’s realm_access.roles and resource_access.<client>.roles claims per ADR-018. Composite expansion is performed client-side using the ADR-018 §Claim mapping section’s documented composite list — do not call out to Keycloak at render time.

Files to Modify

  • src/auth/* — new files above
  • src/App.tsx — wrap children in <PermissionProvider>
  • src/api/client.ts — add the audit-impersonation endpoint call

Definition of Done

  • All Deliverables checked
  • All Execution Phases verified
  • No new dependencies introduced
  • Tests pass: pnpm test --run src/auth
  • ADR-013 §5 surface map verified row-for-row in test snapshot
  • Status updated to review
  • [[../../AI-Dev-Platform/ADR/ADR-013-Permission-And-Role-Model|ADR-013]] — role model consumed
  • [[../../AI-Dev-Platform/ADR/ADR-018-Identity-Federation-Strategy|ADR-018]] — claim source
  • [[../../AI-Dev-Platform/ADR/ADR-017-Human-Review-Branching-And-TDD-Workflow|ADR-017]] — review workflow
  • [[US-OUI-1.7-Sensitivity-Classification-UI]] — sibling design-system primitive
  • [[../Wireframe-Gap-Analysis|Wireframe Gap Analysis]] §6
  • [[../_pkf|OUI _pkf]]