> ## Documentation Index
> Fetch the complete documentation index at: https://prism.ntecdev.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Administrative console plan

# Implementation Plan — Prism Administrative Console

**Status:** active — mobilizing.
**Console Lead:** Porsche (Frank-appointed 2026-05-14 — drives all four lanes to shipped outcome).
**Pairs with:** `docs/proposals/administrative-console-proposal.md` (v1.0 — the what/why). This is the how/who/when.
**Date:** 2026-05-14
**Target:** all functions done today; one mode-aware, scope-driven console; WorkOS access control exercised from Wave 1.

***

## 0. How to read this plan

* **§1** — the load-bearing decision that gates everything else.
* **§2** — work breakdown by lane, every item concrete.
* **§3** — sequencing + critical path + the integration handoffs.
* **§4** — acceptance: what "done" means, per function.
* **§5** — risk register.
* **§6** — what gets filed (Plan + SPEC) and what stays deferred.

## 1. Gate-zero decision — the two seams (must land first)

Everything in this plan rests on **two pluggable seams** being shaped right.
If they're wrong, backend and UI build against a bad abstraction and Cloud
later becomes a rewrite instead of an additive swap.

1. **Auth / identity seam** — `IdentityProvider` interface. The console and
   every verb ask the seam "who is this + what is their scope + what role" —
   never the provider directly. **Two bindings, WorkOS is the one we exercise
   now:**
   * **WorkOS binding (primary, exercised from Wave 1).** WorkOS AuthKit session
     * WorkOS Organization Roles drive the real three-tier
       `owner`/`admin`/`member` gating. This is the binding Frank exercises the
       full console access mechanism with — the bootstrap-key path is
       single-superuser and has no RBAC to test.
   * **Bootstrap-key binding (kept, not the test path).** Local/LAN
     bootstrap-admin-key + localhost access. Still exists for pure-local use;
     it simply isn't where the access mechanism gets exercised.
2. **Tenant-bootstrap seam** — "a tenant exists with an owner" is produced by a
   bootstrapper. Local/LAN = `bootstrap_personal_install()` (exists). Cloud =
   `prism_tenant_create`. Downstream code consumes the *result* identically.

**Action:** Texi reviews and blesses the seam interfaces **before** backend or
UI write against them.

### GATE-ZERO RESULT — BLESSED 2026-05-14 (Texi, note `2146983a`)

Verdict: **BLESS\_WITH\_INTERFACE\_CLARIFICATIONS.** The two-seam shape is correct;
WorkOS-primary / bootstrap-key-secondary is right; Cloud stays additive if
backend + UI target **normalized Prism authorization/bootstrap outputs**, never
WorkOS details. **Released:** Lane A A1w+A2, Lane B B3, Lane C C3 may all
proceed. Build against these six interface clarifications:

1. **Name the seam** `ConsoleIdentityProvider` returning a `ConsoleAuthContext`
   — distinct from the existing `OAuthProvider`.
2. `ConsoleAuthContext` carries: normalized `user`, `tenant scope`, `tenant_role`
   (`owner`/`admin`/`member`/`none`), `capabilities`, and `provider_context`
   (audit only).
3. **The WorkOS raw role claim is never read for authorization.** It maps
   through backend validation to `memberships.role` / `capabilities`;
   authorization reads the **Prism-normalized role**, not the raw provider
   claim. (This is ADR-53 made concrete.)
4. Provider Organization maps to **Prism Tenant only** — never Prism org/dept
   authority.
5. **Unknown / missing role fails closed** — except the bootstrap-key local
   single-superuser path.
6. The **tenant-bootstrap seam** returns the *same postcondition* for
   Local/LAN/Cloud: tenant + owner + default org/dept + console entry scope,
   **idempotently**.

## 2. Work breakdown by lane

### Lane A — Backend verbs + middleware (owner: Donna)

| #   | Item                                                                                                                                                                                                                                                                                                                                                                                          | Depends on | Size |
| --- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | ---- |
| A1  | Scaffold the `IdentityProvider` seam interface + the tenant-bootstrap seam interface                                                                                                                                                                                                                                                                                                          | —          | S    |
| A1w | **WorkOS binding (primary).** Bind the auth seam to WorkOS AuthKit (reuses shipped SPEC-089 backbone). Extend the session / `/whoami` to carry the WorkOS Organization **role claim** (`owner`/`admin`/`member`) — net-new; SPEC-089 shipped WorkOS auth but not role-claim consumption.                                                                                                      | A1         | M    |
| A1k | Bootstrap-key binding — bind the seam to the local bootstrap-admin-key path (kept for pure-local; not the exercised path). **Must also seed a real local-superuser User row + tenant Membership(role=owner)** — `bootstrap_personal_install` creates tenant/org/dept/api\_key but no User, and `create_invite`/`accept_invite` + role-gating need a real `ctx.user_id` (Lafonda C3/C5 trace). | A1         | S    |
| A2  | Role-gating middleware — three-tier `owner`/`admin`/`member`, reads the auth seam (role claim from A1w), enforced server-side on every console verb                                                                                                                                                                                                                                           | A1w        | M    |
| A3  | `prism_org_create` + `POST /orgs` — thin CRUD, slug-unique per tenant                                                                                                                                                                                                                                                                                                                         | A1         | S    |
| A4  | `prism_department_create` + `POST /departments` — thin CRUD                                                                                                                                                                                                                                                                                                                                   | A1, A3     | S    |
| A5  | `prism_team_preseed` + team templates (`default`/`minimal`/`full` archetype subsets) — batch persona create, name-binding                                                                                                                                                                                                                                                                     | A1         | M    |
| A6  | `prism_tenant_backup` — tenant-scoped logical export, pluggable destination seam (local disk default, S3 binding stubbed)                                                                                                                                                                                                                                                                     | A1         | L    |
| A7  | `prism_tenant_archive` — lifecycle state + cold-storage move, `owner`-gated                                                                                                                                                                                                                                                                                                                   | A6         | M    |
| A8  | `prism_tenant_restore` — rehydrate; highest test-risk; merges only when its test pass is green                                                                                                                                                                                                                                                                                                | A6         | L    |
| A9  | `prism_tenant_create` (Cloud path) — layers on the tenant-bootstrap seam; Frank-only gate                                                                                                                                                                                                                                                                                                     | A1         | M    |

Backend acceptance: every verb has structured-failure returns + `duration_s`,
every console verb enforces role-gating server-side (UI gate is UX only).

### Lane B — Console UI (owner: Porsche)

| #  | Item                                                                                                                    | Depends on             | Size |
| -- | ----------------------------------------------------------------------------------------------------------------------- | ---------------------- | ---- |
| B1 | `dashboard/web/src/pages/console/*` shell — route tree, mode detection (`PRISM_MODE`), scope resolution                 | — (starts immediately) | M    |
| B2 | Scope-driven rendering — the console shows everything in the caller's authorized scope; one render path, scope-filtered | B1                     | M    |
| B3 | Role-gating UI — owner/admin/member affordances; wired to the auth seam                                                 | B1, A2                 | M    |
| B4 | Hierarchy tree — Tenant→Org→Dept→Project, create/rename/archive at each level                                           | B2, A3, A4             | L    |
| B5 | Agent-team preseed UI — pick template, name each seat, submit                                                           | B2, A5                 | M    |
| B6 | Backup / archive controls — trigger, list, status; restore surfaced when A8 lands                                       | B2, A6, A7             | M    |
| B7 | GitHub update-notification surface — "update available" + changelog                                                     | B1                     | S    |

UI acceptance: no Zustand selector allocation (React #185), uPlot not Recharts
for any charting, Ansel-Adams palette, every state derived in render. Tested in
a browser against a live local backend before "done."

### Lane C — Install / invite (owner: Lafonda)

| #  | Item                                                                                                                                                                                                                                                                                                                                        | Depends on            | Size |
| -- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- | ---- |
| C1 | Verify `bootstrap_personal_install()` end-to-end on a clean Local install                                                                                                                                                                                                                                                                   | —                     | S    |
| C2 | Verify the same on a clean LAN install (shared backend, second machine)                                                                                                                                                                                                                                                                     | C1                    | M    |
| C3 | LAN-teammate invite/join flow — invite scoped to a LAN, teammate joins with `admin`/`member` role; reuses `invites` + `memberships`. Primitives already exist (create\_invite/accept\_invite + router + verbs); A2 wiring surface = routers/invites.py:33-34. Role-validation piece ({admin,member} constraint) is non-gated, building now. | A2 + A1k User-seed    | M    |
| C4 | Distribution-invite helper — authorize a collaborator's GitHub ID on the Prism repo + emit install-doc link. Manual `gh` first, automation as follow-up                                                                                                                                                                                     | —                     | S    |
| C5 | Install-script ↔ console handoff — fresh install lands the user in a working console as local superuser. Hook point: cli/src/index.ts \~line 2842 (install summary block) → emit "console ready at /console".                                                                                                                               | C1, B1, A1k User-seed | S    |

### Lane D — Reviews (owners: Texi architecture, Andora security)

| #  | Item                                                                                                                     | When                      |
| -- | ------------------------------------------------------------------------------------------------------------------------ | ------------------------- |
| D1 | Texi — bless the two seam interfaces (§1)                                                                                | **Step zero, before A2+** |
| D2 | Texi — verb-surface review (Lane A), GitHub-handle storage call                                                          | as Lane A lands           |
| D3 | Andora — tenant-isolation on the backup export (A6): does the `WHERE tenant_id` scoping actually hold across every table | as A6 lands               |
| D4 | Andora — bootstrap-key trust boundary + role-gating paths (A2)                                                           | as A2 lands               |
| D5 | Andora — customer-facing/internal split inside one app (console routes vs ops dashboard)                                 | as B1+ lands              |

## 3. Sequencing + critical path

```
STEP 0  ──  D1 Texi blesses the two seams ──┐
                                            │
        ┌───────────────────────────────────┼───────────────────────────┐
        │ Lane A (backend)                  │ Lane B (UI)                │ Lane C (install)
        │                                   │                           │
WAVE 1  │ A1 seams ── A1w WorkOS binding     │ B1 shell ── B2 scope       │ C1 verify Local
        │ A1k bootstrap-key binding          │   render                  │ C4 dist-invite
        │ A2 role-gating (needs A1w)         │ B7 update-notif            │
        │ A3 org_create  A4 dept_create      │                           │
        │                                   │                           │
WAVE 2  │ A5 team_preseed                    │ B3 role UI (needs A2/A1w)  │ C2 verify LAN
        │ A6 tenant_backup                   │ B4 hierarchy tree          │ C3 LAN invite
        │                                   │   (needs A3,A4)            │   (needs A2)
        │                                   │                           │
WAVE 3  │ A7 archive  A9 tenant_create       │ B5 preseed UI (needs A5)   │ C5 install↔console
        │ A8 restore (test-gated)            │ B6 backup UI (needs A6,A7) │   handoff
        │                                   │                           │
INTEGRATE ── clean Local install smoke ── WorkOS-authed access-control exercise ── all 6 functions
```

**Critical path:** D1 → A1 → **A1w (WorkOS binding)** → A2 → (A3,A4 → B4) and
(A2 → B3,C3). WorkOS binding is now *on* the critical path — it is what makes
the role-gating real and exercisable. Backup (A6→A7→A8) is the longest *single*
chain but runs parallel to the UI tree work.

**No-dependency fast starts (begin immediately, parallel to Step 0):**

* B1 console shell + B2 scope rendering + B7 update-notification — pure UI,
  no backend contract needed.
* C1 verify Local install, C4 distribution-invite helper.

**Integration handoffs (the points where lanes must sync):**

* A3/A4 verbs → B4 tree: backend publishes the org/dept verb contracts; UI wires.
* A2 middleware → B3 + C3: role-gating contract is shared by UI and the LAN invite.
* A5 → B5, A6/A7 → B6: verb-ready signal triggers UI wiring.
* A1 seam stubs → everyone: the seam interface is the shared contract; it must
  be published (even as a stub) before A2/B3/C3 start.

## 4. Acceptance — what "done" means

| Function              | Done when                                                                                                                                                       |
| --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 1. Create Tenant      | Local/LAN: console surfaces the auto-bootstrapped tenant/org/dept. Cloud: `prism_tenant_create` works Frank-gated (can trail same-day).                         |
| 2. Invite             | LAN owner invites a teammate, teammate joins with a role and sees their scope. Distribution-invite: a GitHub ID is authorized + install doc delivered.          |
| 3. Access control     | Three-tier role gating enforced server-side + reflected in UI. Local = single superuser holds all; LAN = owner + tiered teammates exercised with ≥2 real users. |
| 4. Org/Dept/Project   | Console creates/renames/archives at every hierarchy level; changes persist and re-render.                                                                       |
| 5. Agent team preseed | Superuser picks a template, names the seats, and a full agent team exists bound to archetypes.                                                                  |
| 6. Backup & archive   | `prism_tenant_backup` produces a restorable export; `prism_tenant_archive` moves a scope to cold storage. Restore ships when its test pass is green.            |
| GitHub update notif   | Console shows "update available" against the live Prism repo release.                                                                                           |
| **Whole console**     | A clean Local install AND a clean LAN install both land the user in a working console; all six functions exercised end-to-end in a browser.                     |

## 5. Risk register

| Risk                                                               | Lane         | Mitigation                                                                                                                                                     |
| ------------------------------------------------------------------ | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Seams shaped wrong → Cloud becomes a rewrite                       | D1/A1        | Texi blesses interfaces at Step 0, before anything builds against them.                                                                                        |
| `prism_tenant_restore` corrupts a live DB                          | A8           | Restore is test-gated — merges only on a green test pass, not a clock. Andora reviews collision/idempotency.                                                   |
| Backup export leaks across tenant boundary                         | A6           | Andora D3 explicitly audits the `WHERE tenant_id` scoping across every table.                                                                                  |
| LAN second-machine verification slips                              | C2           | C1 (Local) de-risks first; LAN reuses the same bootstrap path.                                                                                                 |
| UI builds against unpublished verb contracts                       | A↔B handoffs | Backend publishes verb contract stubs early; UI wires to stubs, swaps to live.                                                                                 |
| Customer-facing console routes leak internal ops-dashboard surface | B1/D5        | Route-gated + role-gated separation inside `dashboard/web`; Andora D5 audits.                                                                                  |
| A1w blocked on WorkOS dashboard setup                              | A1w          | Operator prerequisites (§6a) flagged to Frank at mobilization, before the backend lane reaches A1w.                                                            |
| Scope-creep into full multi-tenant Cloud today                     | all          | A9 + multi-tenant scaling are explicitly deferred; the bar for "done today" is the console working + WorkOS access control exercised against one Organization. |

## 6. What gets filed + what's deferred

**Mobilization (Porsche as Console Lead, Frank-appointed 2026-05-14):**

* **Texi** — step zero: bless the two seam interfaces (§1), WorkOS as a
  first-class binding. Everything waits on this 30-min call.
* **Donna** — Lane A backend verbs + middleware; A1w WorkOS binding on the
  critical path.
* **Lafonda** — Lane C install / invite.
* **Andora** — Lane D security, incl. the WorkOS role-claim trust boundary.
* **Porsche** — Lane B Console UI shell immediately + drives all four lanes.
* **Frank** — operator prerequisites §6a before A1w closes.
* File a formal **Plan** (`prism_plan`) + **SPEC** ("SPEC-1xx — Administrative
  Console") as the SOR, cross-ref SPEC-089 / SPEC-114 / SPEC-115 / ADR-53/54/55.

**In scope today (Frank 2026-05-14 — pulled forward):**

* **WorkOS binding on the auth seam (A1w)** + the WorkOS-authed access-control
  exercise. WorkOS is now the *primary exercised binding* — it is how the full
  three-tier console access mechanism gets tested. On the critical path.

**Deferred (not today, explicitly):**

* Full Cloud multi-tenant exercise across many tenants — the WorkOS binding and
  role-gating are built + exercised today against one Organization; scaling to
  the full multi-tenant Cloud topology is the later additive step.
* Distribution-invite automation (manual `gh` repo-collaborator add works today).
* `prism_tenant_create` Cloud path may trail same-day — it's not on the
  role-gating critical path.

## 6a. Operator prerequisites — Frank's WorkOS setup (blocks A1w)

A1w cannot complete until these exist. They are operator actions only Frank can
do; flag immediately so the backend lane isn't blocked mid-wave:

1. **WorkOS Organization** — confirm the Organization that the console exercises
   against (the SPEC-089 cutover used one; confirm `WORKOS_ORGANIZATION_ID`).
2. **WorkOS Roles defined** — the three roles `owner` / `admin` / `member` must
   exist in the WorkOS dashboard for that Organization, so the session carries a
   readable role claim. Without them A2 role-gating has nothing to read.
3. **`WORKOS_*` env on the target backend** — `WORKOS_API_KEY`,
   `WORKOS_CLIENT_ID`, `WORKOS_ORGANIZATION_ID`, `WORKOS_ISSUER` present on
   whichever backend the console is exercised against (cloud `prism-server` has
   these per SPEC-089 §6.1; a local backend would need them added).
4. **At least two test humans in WorkOS** with different roles, so the
   three-tier gating is exercised with real role differences — not just `owner`.

## 7. Open items for the reviews (not plan blockers)

* GitHub-handle storage location (`users` vs `memberships` vs
  `user_external_identities`) — Texi call during D2.
* Backup destination seam: confirm local-disk default path + the S3 binding
  shape — Donna + Andora during A6/D3.
* Update-notification cadence + where the GitHub-release check runs — Porsche +
  Donna during B7.
