Skip to content
GitHub
Decisions

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.

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/dashboard and apps/operations are TanStack/Vite SPA projects (Router + TanStack Query) deployed as Cloudflare Pages projects.
  • @supabase/supabase-js is initialized with cookieOptions.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.