ADR-046: Foundational alpha decisions — subdomains, frontend, auth pattern, dev tooling, Supabase
Context
A foundational spike for Spectral 0.3.0 settled, alongside the compute-hosting choice (now ADR-109), a set of decisions that still stand: the audience-aligned subdomain layout, the frontend framework, the cross-subdomain auth pattern, build/dev orchestration, supply-chain hardening, and the Supabase substrate. They are recorded here; provisioning and secrets are ADR-110.
Decision
D2 — Audience-aligned subdomain layout
api.runspectral.com→ API (FastAPI)app.runspectral.com→ Dashboard (customer-facing)ops.runspectral.com→ Operations (staff-facing, server-side auth-gated)codex.runspectral.com→ Codex docs (Cloudflare Pages + Pages Function for staff auth)docs.runspectral.com→ public docs (Cloudflare Pages)
D3 — TanStack Start as the frontend framework
Canonical in ADR-050. Dashboard and operations are TanStack Start projects (Router + loaders + route definitions); SPA mode at alpha, SSR-per-route where demanded. No Next.js; no @supabase/ssr for data.
D4 — Operations as its own deployable
Canonical in ADR-047: operations is its own deployable, with its own subdomain, process, and deployment-level auth boundary.
D5 — Two frontend processes; docs on Cloudflare Pages
Dashboard serves app. and Operations serves ops. as separate Cloudflare Pages projects. Docs (codex., docs.) also run on Cloudflare Pages — ops. and codex. are gated by Pages Functions for JWKS-local auth that mirrors Operations’ Pattern A; docs. is public. Routing posture is per ADR-052.
D7 — API runs uvicorn directly
uvicorn $APP --host 0.0.0.0 --port $PORT. No gunicorn wrapping; the container supervisor handles process supervision. Worker count is env-configurable per service tier.
D8 — Cross-subdomain session cookie
Authentication via @supabase/supabase-js PKCE; the JWT rides a cookie scoped at Domain=runspectral.com (eTLD+1) for cross-subdomain session sharing across app., ops., codex. (see ADR-039 D6). Data queries always go SPA → Bearer JWT → FastAPI → psycopg → Postgres; Supabase JS is used only for auth.
D9 — Operations auth: Pattern A (JWKS-local validation)
Operations’ Pages Function, the in-app route gate, and the codex Pages Function validate the Supabase JWT against JWKS in-process and check app_metadata.organization_role == "operations" (the JWKS-local pattern is canonical in ADR-039 D4a). A contract test enforces parity with FastAPI’s auth check. No internal HTTP per gated request.
D13 — Build and dev orchestration
Build orchestration via turbo (build, typecheck, lint pipelines in turbo.json). Dev orchestration via a concurrently ladder: pnpm dev runs the full local stack; per-surface scripts (pnpm dev:api, pnpm dev:dashboard, …) for focused work. Frontends default to the localhost API in dev; local Supabase via supabase start is a prerequisite.
D14 — Supply-chain hardening: minimumReleaseAge: 10080 (7 days)
In pnpm-workspace.yaml — quarantines newly-published packages for 7 days. Cheap to adopt; mitigates supply-chain-compromise risk in the JS dependency surface.
D15 — Supabase managed
Used for Postgres, Auth (PKCE + JWKS), pgvector, the local-dev CLI, and managed backups + PITR (ADR-040). Not used: Realtime (replaced by sse-starlette per ADR-034 D2), PostgREST (FastAPI handles data), the JS query client, Edge Functions, Storage at alpha, Studio. Supabase is a managed service of apps/api — the API owns the connection, RLS is the backstop (ADR-033), and the frontend reaches data through apps/api (ADR-034).
Alternatives considered
The framework-level alternatives are canonical in the ADRs each decision points to: Next.js / @supabase/ssr for the frontend (rejected — ADR-050, ADR-034); an internal-HTTP auth check per gated request instead of JWKS-local validation (rejected — ADR-039); operations as a section of the dashboard rather than its own deployable (rejected — ADR-047).
Consequences
apps/dashboardandapps/operationsare TanStack/Vite SPA projects (Router + TanStack Query) deployed as Cloudflare Pages projects.@supabase/supabase-jsis initialized withcookieOptions.domain = "runspectral.com"for cross-subdomain session sharing.- Operations Start carries server-side auth middleware (Pattern A: JWKS-local validation); a contract test enforces parity with FastAPI’s auth check.
- Supabase is framed as a managed service of
apps/api, not a standalone architectural surface.