Skip to content
GitHub
Decisions

ADR-047: Operations is its own deployable, not a section of the customer dashboard

Context

The operations console is the control plane through which Spectral runs the technology that is the business. Its complexity trajectory and the auth boundary it carries warrant first-class deployable status — its own app, its own subdomain, its own process, and a deployment-level auth boundary.

Three forces make operations its own deployable rather than a section of the customer dashboard:

  • Auth boundary lives at the deployment layer. Staff scopes (OrganizationRole.OPERATIONS + OPERATIONS_SCOPES per ADR-039 D11) are never granted to customer-facing users. Coupling staff-only routes inside the customer SPA mixes two failure surfaces and forces the auth check into application code rather than the deployment edge. On Cloudflare Pages, that edge boundary is the ops. Pages Function middleware.
  • Independent deploy cadence. Operations evolves with the operator surface tools (DLQ inspection per ADR-060 D7, agent chat, World Agent operator UI); the customer dashboard evolves with the customer surface. Coupling their deploys is incidental.
  • Audience-aligned subdomain layout. ADR-046 D2 picks app.runspectral.com for the customer dashboard and ops.runspectral.com for operations. Subdomain separation makes the auth gate trivial (Pattern A JWKS-local middleware on the Operations Pages project) and is invisible in the customer surface.

Decision

D1 — Operations is its own deployable

apps/operations/ is its own TanStack/Vite SPA project, deployed as its own Cloudflare Pages project, reachable at ops.runspectral.com.

D2 — Operations Start carries Pattern A JWKS-local auth middleware

Per ADR-046 D9. The Pages Function middleware validates the Supabase JWT against JWKS in-process, checks for the operations scope, and gates every non-public route before the SPA shell is served. Contract test enforces parity with FastAPI’s auth check.

D3 — Operations does not import customer-dashboard code

The two SPAs are separate Vite builds. Shared types live in workspace packages under packages/ if and only if they are genuinely shared contract; ad-hoc shared utilities are not allowed across the boundary.

D4 — Codex docs (codex.runspectral.com) share the operations auth gate

Per ADR-052 D4, codex runs on Cloudflare Pages with a Pages Function that validates the JWT against the Supabase JWKS and checks the same operations scope — the auth check is a direct port of Operations Start’s Pattern A. Session cookie scoped at runspectral.com eTLD+1 (per ADR-046 D8) is naturally visible to codex.runspectral.com.

Alternatives considered

Operations as a section inside the customer dashboard. Rejected for the three forces above. Doable, but mixes staff and customer failure surfaces and pushes the auth gate inward.

Operations as a separate app but sharing the same TanStack Start process via host-routing. Rejected — a single Start process handling both customer and staff routes mixes the boundary; the auth boundary is cleanest at the deployment edge. (ADR-052 D1 drops the static-mount approach to subdomain routing.)

Operations as a server-rendered page set inside apps/api (no separate frontend). Rejected. Operations needs interactive UI density; serving HTML from FastAPI conflates the API tier with frontend concerns.

Consequences

  • Two TanStack/Vite SPA projects (apps/dashboard, apps/operations) with no shared frontend code beyond explicit workspace packages. Independent deploy cadence.
  • The Operations app and Codex docs site run on Cloudflare Pages with JWKS-local auth Pages Functions (per ADR-052 D4) — the same operations scope gate.
  • The Operations Start auth-parity contract test is load-bearing — JWKS-local validation in TypeScript (getClaims()) must agree with the Python FastAPI middleware on the same JWT inputs (signature, expiry, audience, issuer, scope set).
  • apps/operations/ directory must be properly populated as a parallel-to-dashboard app, not the vestigial Next.js scaffold the v0.2.0 carry-over left.
  • The cross-subdomain cookie scope (Domain=runspectral.com, per ADR-046 D8) is what makes both Operations Start and the Codex Pages Function readable from the same login session.

References

  • ADR-039 — Pattern A JWKS-local + scope taxonomy
  • ADR-046 — foundational alpha decisions; D4 points here for operations-as-deployable
  • ADR-050 — TanStack Start adoption
  • ADR-052 — TA-22 (Pages Function reuses JWKS-local pattern)
  • TA-21 disposition — SPEC-324 comment 8e48c86a
  • Codex system-design/foundations/architecture.mdx — close-pass updates (4-runtime-service model)
  • Codex system-design/topology/frontend-architecture.mdx — close-pass new page