Contents: 1. Overview 2. Architecture 3. Data flow 4. Sequences 5. Interconnections 6. Key functions & files 7. Invariants & gotchas

Admin Console (Superadmin) — Architecture

Module: apps/admin · ExcelRewind monorepo · doc generated for the architecture documentation suite

1. Overview

apps/admin is the owner-only superadmin console for ExcelRewind. It is a small zero-router React 18 + TypeScript single-page app (Vite, port 5175 in dev) that gives the product owner a UI over metrics and every destructive/administrative action in the system: tier design, feature entitlements, user tier changes and revocation/deletion, coupons, referral/promo configuration, pricing, cohort (group-licence) management, and a global maintenance kill switch plus a one-shot pre-launch data wipe.

It is deliberately not a normal Supabase-auth application. Instead of RLS-scoped user sessions, the admin app talks to two purpose-built Supabase Edge Functions — metrics (GET, read-only analytics) and admin-ops (POST, all mutations) — authenticated with a static bearer token (OWNER_METRICS_TOKEN, stored in the browser's localStorage) plus a second factor: a live Supabase session for one hardcoded superadmin email, obtained via email OTP and carried on every admin-ops call as the x-superadmin-jwt header. Both edge functions run under the Supabase service role (bypasses RLS) — admin-ops is explicitly documented as "the ONLY write path to these [tables] from outside SQL".

Deployment: a static Cloudflare Pages site at admin.excelrewind.com, built with vite build from this workspace. apps/admin/public/_headers sets X-Robots-Tag: noindex on every route (the console must never appear in search) and a strict CSP whose connect-src is pinned to the Supabase project origin only — no third-party script/analytics origins are allowed. The app has no server component of its own; all business logic and authorization checks live in the edge function (supabase/functions/admin-ops/index.ts) and the Postgres functions/RPCs it calls under the service role. A ?demo=1 query flag skips the gate entirely and renders hardcoded sample data (src/demo.ts) with every mutating call short-circuited — it touches no real database and is deliberately left open (documented in src/api.ts) as a backend-independent preview mode.

2. Architecture diagram

flowchart TB
  subgraph ENTRY["Entry / bootstrap"]
    main["main.tsx"] --> App["App.tsx<br/>top-level auth gate + tab router"]
  end

  subgraph AUTHUI["Auth UI"]
    Gate["Gate.tsx<br/>2-factor sign-in: owner token + email OTP"]
  end

  subgraph SHELL["Shell / chrome"]
    ShellC["Shell.tsx<br/>sidebar nav (11 tabs) + topbar + demo banner"]
    UI["ui.tsx<br/>Icons, Avatar, TierBadge, StatusBadge,<br/>ToastHost/useToast, Modal, DemoFallbackBanner, ThemeToggle"]
  end

  subgraph SCREENS["screens/ (11 tabs)"]
    Overview["Overview.tsx"]
    Growth["Growth.tsx"]
    Revenue["Revenue.tsx"]
    Cohorts["Cohorts.tsx"]
    Users["Users.tsx"]
    Coupons["Coupons.tsx"]
    Referrals["Referrals.tsx"]
    Pricing["Pricing.tsx"]
    Tiers["Tiers.tsx"]
    Features["Features.tsx"]
    System["System.tsx"]
  end

  subgraph SHARED["Shared client modules"]
    Api["api.ts<br/>store (token/url), adminOps.*, fetchMetrics,<br/>ApiError, friendly(), isMissing(), types"]
    Supa["supabase.ts<br/>supabase-js client (OTP-only),<br/>SUPERADMIN_EMAIL, superadminJwt()"]
    Demo["demo.ts<br/>demoUsers/demoTiers/demoFeatures/<br/>demoPromo/demoAccountLookup"]
    Hook["useTierOptions.ts<br/>shared tier-picker hook (calls tierList)"]
    Util["util.ts<br/>fmtNum, fmtDate"]
    Charts["charts.tsx<br/>sparkline / bar chart primitives"]
  end

  main --> AUTHUI
  App -->|"authed=false"| Gate
  App -->|"authed=true"| ShellC
  ShellC --> SCREENS
  ShellC --> UI
  Gate --> Api
  Gate --> Supa
  SCREENS --> Api
  SCREENS --> UI
  SCREENS --> Demo
  SCREENS --> Hook
  SCREENS --> Util
  Overview --> Charts
  Growth --> Charts
  Revenue --> Charts
  Hook --> Api
  Api --> Supa
  System -->|"direct read only:<br/>app_config select"| Supa

Notes on the diagram: there is no client-side router library — Tab is a plain string union persisted to localStorage (rewind.admin.tab) and switched with a ternary chain in App.tsx. There is no global state manager (Redux/Zustand); each screen owns its own useState/useEffect data-loading, following one repeated shape: load() → try adminOps.X() → on 404 fall back to demo.ts data + a toast, on other errors show a toast and set an empty/last-good state. System.tsx is the one screen that reads Postgres directly via supabase-js (app_config, RLS-readable by any signed-in session) instead of going through admin-ops, purely to reflect real maintenance state on load.

3. Data-flow diagram(s)

flowchart LR
  subgraph BROWSER["Admin browser (owner only)"]
    LS["localStorage<br/>rewind.metrics-url, rewind.metrics-token"]
    SS["sessionStorage<br/>Supabase OTP session (via supabase-js)"]
    UIState["React component state<br/>(no persistence beyond active tab)"]
  end

  subgraph EDGE["Supabase Edge Functions (Deno, service role)"]
    Metrics["metrics<br/>GET ?days=N"]
    Ops["admin-ops<br/>POST {action, ...args}"]
  end

  subgraph DB["Postgres (service role — bypasses RLS)"]
    Tables["profiles, tiers, feature_flags,<br/>coupons, coupon_redemptions, referrals,<br/>promo_config, pricing_config, orgs,<br/>app_config, exams, ..."]
    RPCs["RPCs: user_delete, tier_create/set/delete,<br/>account_lookup, admin_data_reset,<br/>feature_set/reset, cohort_* "]
  end

  LS -->|"Authorization: Bearer OWNER_METRICS_TOKEN"| Metrics
  LS -->|"Authorization: Bearer OWNER_METRICS_TOKEN"| Ops
  SS -->|"x-superadmin-jwt: <access_token> (2nd factor)"| Ops
  UIState -->|"JSON body {action, user_id, tier, ...}"| Ops
  Metrics -->|"JSON: {overview, addin, feedback, bugs,<br/>revenue, growth}"| UIState
  Ops -->|"JSON: {ok} / {users:[...]} / {tiers:[...]} / {error}"| UIState
  Metrics -.->|"read-only SELECT / views"| Tables
  Ops --> RPCs
  Ops -->|"direct table read/write<br/>(app_config, coupons, promo_config, ...)"| Tables
  RPCs --> Tables

  UIState -.->|"direct: supabase.from('app_config').select(...)"| Tables

The admin app never receives or stores .rewind file bytes, session events, or spreadsheet content — its payloads are exclusively administrative JSON (tier keys, feature states, user rows, coupon config, pricing numbers). Nothing here handles the timeline.json/annotations.json/meta.json components of the .rewind format; that lives entirely in packages/format and the add-in/viewer.

4. Process / sequence diagrams

4.1 Two-factor sign-in (owner token + email OTP)

sequenceDiagram
  participant Owner
  participant GateUI as "Gate.tsx"
  participant Api as "api.ts (store/verifyToken)"
  participant Metrics as "metrics fn"
  participant SupaJS as "supabase.ts (supabase-js)"
  participant Auth as "Supabase Auth"
  participant Ops as "admin-ops fn"

  Owner->>GateUI: enter metrics URL + owner token
  GateUI->>Api: store.url = url, store.token = token
  GateUI->>Api: verifyToken()
  Api->>Metrics: GET ?days=1 (Authorization: Bearer token)
  alt token rejected (401)
    Metrics-->>Api: 401
    Api-->>GateUI: false
    GateUI-->>Owner: "Token rejected." (stays on step 1)
  else network/other error
    Metrics-->>Api: error (non-401)
    Api-->>GateUI: true (treated as "maybe fine")
  else ok
    Metrics-->>Api: 200 JSON
    Api-->>GateUI: true
  end
  GateUI->>GateUI: setStep('otp')
  GateUI->>SupaJS: supabase.auth.signInWithOtp({ email: SUPERADMIN_EMAIL })
  SupaJS->>Auth: send OTP email
  Owner->>GateUI: enter 6-digit code
  GateUI->>SupaJS: supabase.auth.verifyOtp({ email, token: code, type:'email' })
  SupaJS->>Auth: verify
  alt bad code
    Auth-->>GateUI: error
    GateUI-->>Owner: "Code rejected"
  else ok
    Auth-->>SupaJS: session (sessionStorage)
    GateUI->>App: onAuthed()
  end
  Note over Api,Ops: Every later admin-ops call carries BOTH the owner token AND<br/>x-superadmin-jwt = current session access_token (superadminJwt()).<br/>admin-ops independently re-validates the JWT against Supabase Auth<br/>and rejects any email other than SUPERADMIN_EMAIL — the client UI<br/>gate is convenience only, not the real security boundary.

4.2 Feature entitlement write (global / tier / account exception cascade)

sequenceDiagram
  participant Admin as "Superadmin"
  participant FeaturesUI as "Features.tsx"
  participant Ops as "admin-ops fn"
  participant RPC as "feature_set() / feature_reset() (Postgres)"
  participant Flags as "feature_flags table"

  Admin->>FeaturesUI: search email in "Account exception" card
  FeaturesUI->>Ops: POST {action:'account_lookup', email}
  Ops->>RPC: account_lookup(p_email)
  RPC-->>Ops: {user_id, email, tier, org_id, member_count, scope, subject}
  Ops-->>FeaturesUI: {account}
  FeaturesUI->>Ops: POST {action:'features_get', scope, subject}
  Ops-->>FeaturesUI: {features:[{feature, global, own}]}
  Admin->>FeaturesUI: toggle a feature row to "Disabled"
  alt global state for that feature is already 'disabled'
    FeaturesUI-->>Admin: control rendered disabled client-side (lockedByGlobal)
  else
    FeaturesUI->>Ops: POST {action:'feature_set', scope, subject, feature, state}
    Ops->>RPC: feature_set(...) [service role, no RLS]
    RPC->>Flags: upsert override row at (scope, subject, feature)
    RPC-->>Ops: ok / error (bad_scope, bad_state, subject_required, bad_subject)
    alt success
      Ops-->>FeaturesUI: {ok:true}
      FeaturesUI-->>Admin: toast "ok", optimistic row update
    else error
      Ops-->>FeaturesUI: {error} (400/500)
      FeaturesUI-->>Admin: toast "err" via friendly(e)
    end
  end
  Note over RPC,Flags: Resolution order enforced server-side too: account > tier > global,<br/>EXCEPT a global 'disabled' is a hard-kill floor no narrower scope can lift.

4.3 User tier change / revoke / hard delete

sequenceDiagram
  participant Admin as "Superadmin"
  participant UsersUI as "Users.tsx"
  participant Ops as "admin-ops fn"
  participant DB as "Postgres (service role)"

  Admin->>UsersUI: click "Change tier" -> pick tier + optional free months
  UsersUI->>Ops: POST {action:'user_set_tier', user_id, tier, months?}
  Ops->>DB: UPDATE profiles SET tier=..., tier_expires_at = months ? now()+months : null
  DB-->>Ops: ok
  Ops-->>UsersUI: {ok:true}
  UsersUI-->>Admin: toast + optimistic row update

  Admin->>UsersUI: click "Revoke"
  UsersUI->>Ops: POST {action:'user_set_status', user_id, status:'revoked'}
  Ops->>DB: UPDATE profiles SET status='revoked'
  Ops-->>UsersUI: {ok:true}

  Admin->>UsersUI: click "Delete" -> type exact email to confirm
  UsersUI->>Ops: POST {action:'user_delete', user_id, email}
  Ops->>DB: rpc user_delete(p_user, p_email)
  alt email mismatch or user owns an org
    DB-->>Ops: error (user_owns_org / mismatch)
    Ops-->>UsersUI: {error} (400)
    UsersUI-->>Admin: toast "This account owns an org - reassign or delete that org first."
  else ok
    DB-->>Ops: {deleted:true, email}
    Ops-->>UsersUI: {deleted:true, email}
    UsersUI-->>Admin: row removed from table, toast "ok"
  end
  Note over UsersUI: Bulk actions (multi-select) run this same setTier/setStatus call<br/>once per selected user, sequentially, client-side (no batch RPC) —<br/>see runBulk() in Users.tsx.

4.4 Maintenance-mode kill switch

sequenceDiagram
  participant Admin as "Superadmin"
  participant SystemUI as "System.tsx"
  participant SupaJS as "supabase-js (session-authed read)"
  participant Ops as "admin-ops fn"
  participant Cfg as "app_config (id=1)"

  SystemUI->>SupaJS: on mount: select enabled,message from app_config where id=1
  SupaJS->>Cfg: SELECT (RLS: any signed-in session may read)
  Cfg-->>SystemUI: {enabled, message}
  SystemUI->>SystemUI: on = !enabled  (enabled=true means the app is UP)

  Admin->>SystemUI: flip the Maintenance Mode toggle ON
  SystemUI->>Ops: POST {action:'maintenance_set', enabled:false, message}
  Ops->>Cfg: UPDATE app_config SET enabled=false, message=... WHERE id=1
  Ops-->>SystemUI: {ok:true}
  SystemUI-->>Admin: banner "Maintenance mode is live" + toast
  Note over Cfg: Only the Excel add-in and the Google Sheets add-on read<br/>app_config.enabled to gate/block usage and show the message — the viewer<br/>and team console do not check it (verified: no app_config reference in either app).

4.5 Danger-zone pre-launch data reset

sequenceDiagram
  participant Admin as "Superadmin"
  participant SystemUI as "System.tsx (DangerZone)"
  participant Browser as "window.confirm"
  participant Ops as "admin-ops fn"
  participant RPC as "admin_data_reset() (Postgres)"

  Admin->>SystemUI: type exact phrase "RESET DATA"
  SystemUI->>SystemUI: armed = (phrase === 'RESET DATA')
  Admin->>SystemUI: click "Reset all data"
  SystemUI->>Browser: confirm("FINAL CONFIRM: permanently wipe ... Proceed?")
  alt cancelled
    Browser-->>SystemUI: false
    SystemUI-->>Admin: no-op
  else confirmed
    SystemUI->>Ops: POST {action:'data_reset', confirm:'RESET DATA'}
    Ops->>Ops: re-check body.confirm === 'RESET DATA' (guard #3, server-side)
    Ops->>RPC: admin_data_reset(p_confirm:'RESET DATA')
    RPC->>RPC: wipes users + telemetry, KEEPS tiers/flags/pricing/promo/app_config
    RPC-->>Ops: {ok, kept:{tiers,global_flags,...}, cleared:{...}}
    Ops-->>SystemUI: {ok, kept, cleared}
    SystemUI-->>Admin: "Done. ... Sign in again to recreate your superadmin account."
  end

5. Interconnections

flowchart LR
  Admin["apps/admin<br/>(admin.excelrewind.com)"]
  Metrics["Edge fn: metrics"]
  Ops["Edge fn: admin-ops"]
  Auth["Supabase Auth<br/>(email OTP)"]
  PG["Supabase Postgres<br/>(service role, RLS bypassed by admin-ops;<br/>RLS-read for direct app_config read)"]
  CFPages["Cloudflare Pages<br/>(static hosting)"]

  Admin -->|"HTTPS GET, Bearer OWNER_METRICS_TOKEN"| Metrics
  Admin -->|"HTTPS POST, Bearer OWNER_METRICS_TOKEN + x-superadmin-jwt"| Ops
  Admin -->|"supabase-js: signInWithOtp / verifyOtp / getSession"| Auth
  Admin -->|"supabase-js: select on app_config (session-RLS)"| PG
  Metrics --> PG
  Ops --> PG
  Ops -->|"validates JWT signature/expiry + email"| Auth
  CFPages -->|"serves static build"| Admin
External systems this module talks to
TargetProtocol / callPurpose
metrics edge functionHTTPS GET /functions/v1/metrics?days=N, header Authorization: Bearer OWNER_METRICS_TOKENRead-only analytics contract (Metrics type) rendered by Overview, Growth, Revenue tabs. Also used as the cheap token-validity probe in the Gate (verifyToken(), days=1).
admin-ops edge functionHTTPS POST /functions/v1/admin-ops, body {action, ...args}, headers Authorization: Bearer OWNER_METRICS_TOKEN + x-superadmin-jwtEvery mutation and most reads in the console: users, tiers, features, coupons, promo/referrals, pricing, cohorts, maintenance, data reset. 27 actions (see BACKEND_CONTRACT.md and §6).
Supabase Authsupabase-js: auth.signInWithOtp, auth.verifyOtp, auth.getSession, auth.signOutSecond-factor login as the fixed SUPERADMIN_EMAIL (email OTP). Session persisted to sessionStorage only (fresh browser session ⇒ re-OTP). The resulting access token is what admin-ops validates as x-superadmin-jwt.
Postgres table app_configsupabase-js: .from('app_config').select('enabled,message').eq('id',1).maybeSingle()The only direct-to-DB read in the app (System.tsx) — loads real maintenance state on mount via the OTP session's RLS-scoped read, so the toggle reflects reality before any write.
Postgres (via admin-ops, service role)direct table reads/writes + RPC calls (user_delete, tier_create/set/delete, account_lookup, admin_data_reset, feature_set/feature_reset, cohort RPCs)All administrative state: profiles, tiers, feature_flags, coupons, coupon_redemptions, referrals, promo_config, pricing_config, orgs, app_config.
Cloudflare Pagesstatic hosting, deployed via npm run deploy:admin (wrangler) — a separate script from the root npm run deploy, which only covers the add-in/viewer/websiteServes the built SPA at admin.excelrewind.com; public/_headers sets CSP, HSTS, and X-Robots-Tag: noindex.
None: no Office.js / Apps Script hostThe admin console is a standalone web app; it never runs inside Excel or Google Sheets and has no relationship to the add-in/add-on hosts.

6. Key functions & files

Client (apps/admin/src)
FileFunction / exportWhat it doesWhy it matters
App.tsxApp()Top-level gate: renders Gate until authed, then Shell + the tab whose id matches tab state. Re-checks a stored token against a live OTP session on mount via superadminJwt().A stored OWNER_METRICS_TOKEN alone is not enough to reach the console after a refresh — the effect forces re-verification of the live Supabase OTP session before setting authed.
Gate.tsxGate(), submitToken(), sendCode(), submitCode()Two-step sign-in UI: (1) metrics URL + owner token, verified with a cheap fetchMetrics(1) probe; (2) email OTP to the fixed superadmin address.The metrics URL field is deliberately left blank (never prefilled) — "a stranger who reaches the gate must already know the metrics URL," and the OTP step never names the destination address, so a leaked owner token alone reveals nothing about where the code lands.
api.tsstoreThin localStorage wrapper for the metrics URL/token, reusing the same keys as apps/website/admin.html so a signed-in owner's config carries over between UIs.Auth "session" for the owner-token half of the two-factor scheme lives entirely client-side in localStorage; it is not itself sufficient to call admin-ops (see ops()).
api.tsops<T>(action, body)Generic POST wrapper for every admin-ops call: attaches the owner-token bearer plus x-superadmin-jwt (from superadminJwt()), throws ApiError on non-2xx.Single choke point for the second factor — every mutation goes through here, so the JWT header is never forgotten by an individual screen.
api.tsadminOps.usersList()Calls users_list then normalizes each row's iduser_id.Explicitly documented gotcha: the server returns rows keyed id, not user_id; skipping this normalization makes every subsequent tier/revoke write a silent no-op (undefined user_id matches no profile) while the UI still reports success.
api.tsfriendly(e), isMissing(e), ApiErrorTurns a raw fetch/HTTP failure into a toast-ready message; isMissing flags a 404/501 (the whole admin-ops function not deployed yet) so callers fall back to demo data instead of showing a raw error.Explains why the console is usable pre-deploy — every screen degrades to demo.ts data on 404 rather than breaking.
api.tsadminOps.userDelete, dataReset, tierDelete, featureReset, etc.Typed wrappers for all 27 admin-ops actions (see full list in §5's Interconnections table and the sequence diagrams).This object is the client-side contract; cross-reference against supabase/functions/admin-ops/index.ts's switch(action) (line ~124) when auditing for drift.
supabase.tsSUPERADMIN_EMAILEnv-driven (VITE_SUPERADMIN_EMAIL), lowercased, fallback tm@excelrewind.com.Must exactly match the SUPERADMIN_EMAIL secret read by admin-ops's Deno runtime — a mismatch locks the owner out with 401 otp_required even with a correct OTP.
supabase.tssuperadminJwt()Returns the current session's access token only if the signed-in email equals SUPERADMIN_EMAIL; '' otherwise.Client-side mirror of the server check — but the server (isSuperadminJwt in admin-ops) re-validates independently against Supabase Auth, so this function is a UX nicety, not the security boundary.
screens/Tiers.tsxTiers(), patch(), remove(), NewTier()Tier designer: list/edit tiers inline (name, price, has-members, seat_limit, active), create new tiers, delete tiers with zero users. Optimistic UI mirrors the server's "seat_limit snaps to 1 when has_members turns off" rule.public.tiers is the canonical tier list per CLAUDE.md — this screen is its only write UI. Deleting a tier is blocked client- and server-side while user_count > 0.
screens/Features.tsxFeatures(), AccountExceptionCard(), PerTierCard(), lockedByGlobal(), FEATURE_METAThe full entitlement cascade UI: global state per feature, per-tier override, and one-off account exception (resolved by email via account_lookup to either an org subject or a user subject).lockedByGlobal(global) = global === 'disabled' is the client-side mirror of the server's hard-kill floor — a global disabled greys out every narrower control so an admin can't be misled into thinking a lower-level toggle will re-enable it (it will be rejected server-side too).
screens/Users.tsxUsers(), setStatus(), runBulk(), DeleteUserModal, TierModalSearchable user table with per-row and bulk (multi-select) tier change / revoke / restore, plus a guarded hard-delete flow.runBulk() is a plain client-side sequential loop over setTier/setStatus calls (commented ponytail: — no batch RPC), so a large bulk action is O(n) network round-trips, one per selected user, with a live progress modal.
screens/Users.tsxDeleteUserModalHard-delete confirm gated behind typing the user's exact email; surfaces the server's user_owns_org error as a friendly message.Deletion is irreversible and blocked entirely if the account owns an org — the UI must guide the admin to reassign the org first rather than silently failing.
screens/System.tsxMaintenanceCard()Global maintenance kill switch: reads real state from app_config on mount (direct supabase-js read), writes via adminOps.maintenanceSet(!next, msg).Inverted boolean convention: UI's "Maintenance ON" = app_config.enabled = false. Getting the inversion wrong in either direction takes the product down or leaves it silently up during an incident — called out twice in code comments.
screens/System.tsxDangerZone(), RESET_PHRASETriple-guarded pre-launch data wipe: (1) exact-phrase text input arms the button, (2) window.confirm, (3) server re-checks the phrase inside admin_data_reset().Wipes users + all telemetry but explicitly keeps config (tiers, feature flags, pricing, promo, maintenance) — meant to run exactly once before public launch to clear beta clutter.
useTierOptions.tsuseTierOptions(includeFree?)Shared hook wrapping adminOps.tierList() for every tier <select> in the app (Cohorts, Coupons, Users, …), with demo/offline fallback.Prevents any screen from hardcoding a tier list — enforces the "tiers table is the single source of truth" rule from CLAUDE.md at the client level too.
Shell.tsxNAV, TITLE, CRUMBStatic config for the 11-tab sidebar (Overview, Growth, Revenue, Cohorts, Users, Coupons, Referrals, Pricing, Tiers, Features, System).Adding a new admin screen means adding an entry here and to Tab in the same file and to the ternary in App.tsx — three places, no central registry.
demo.tsdemoUsers, demoTiers, demoFeatures, demoAccountLookup, demoPromoHardcoded sample data used both for the explicit ?demo=1 preview mode and as the automatic fallback whenever admin-ops 404s.Every screen's demo fallback path is exercised even in production if the edge function isn't deployed yet — worth knowing when a screen "looks fine" but is actually showing fake data (watch for the DemoFallbackBanner).
Server (supabase/functions/admin-ops/index.ts) — the module's true authorization boundary
LocationFunctionWhat it doesWhy it matters
lines ~97–112Deno.serve handler entryRejects non-POST with 405; checks Authorization: Bearer against OWNER_METRICS_TOKEN with timingSafeEqual; then checks x-superadmin-jwt via isSuperadminJwt().Both checks happen before the request body is even parsed for the action — a caller with only one of the two factors gets a flat 401 regardless of what action they tried.
~87–95timingSafeEqual(a,b)Constant-time string compare for the owner token.Explicit defense against timing side-channel leaking the token's length/prefix.
~67–83ALLOWED_ORIGINS, cors(origin)CORS allowlist: admin.excelrewind.com, excelrewind.com, localhost:5175, plus a dev Pages preview host. Echoes the origin only if allowlisted; otherwise falls back to the prod host (never a wildcard).A browser-based caller from any other origin is blocked at the CORS layer even with valid tokens.
~244–252userDelete(b)Wraps Postgres RPC user_delete(p_user, p_email); requires user_id AND a matching email.Server-side email confirmation, not just the client's type-to-confirm modal — defends against a stale/wrong user_id silently deleting the wrong account.
~257–263maintenanceSet(b)Direct UPDATE app_config SET enabled, message WHERE id=1.No RPC/trigger layer — a bug here has an immediate, global blast radius (every client's kill switch).
~453–462dataReset(b)Re-checks confirm === 'RESET DATA' server-side (independent of the client's armed state) before calling RPC admin_data_reset(p_confirm).The phrase check is duplicated deliberately — a compromised or buggy client alone cannot trigger the wipe.
~534–548, ~549–565, ~577–583tierCreate, tierSet, tierDeleteThin validating wrappers around RPCs tier_create/tier_set/tier_delete; map SQL errors (bad_key, tier_exists, tier_in_use, no_tier) to 400s via rethrowFeature.This is the only mutation path for public.tiers, the single source of truth referenced throughout CLAUDE.md's Tiers section.
~570–576accountLookup(b)RPC account_lookup(p_email) → resolves an email to either an org subject (Team/Enterprise, so all members inherit the exception) or a user subject (solo tiers).The scope/subject resolution logic (org vs. user) lives entirely server-side in this RPC — the client (Features.tsx) just renders whatever scope/subject comes back.
~585–602+referralStats()Aggregates referrals + tiers (paid = key != 'free') in JS after two service-role reads; explicitly returns only referrer emails, never referee lists.Commented ponytail: — a deliberate "small pre-launch scale, JS aggregation beats a bespoke SQL rollup" shortcut with a named upgrade path (move to a SECURITY DEFINER SQL rollup if it grows).

7. Invariants & gotchas

admin-ops is the only external write path to these tables. Every mutation the console performs (tiers, features, users, coupons, promo, pricing, cohorts, maintenance, data reset) funnels through one Deno edge function running under the Postgres service role. There is no separate REST/GraphQL admin API and no direct table write from the browser except the read-only app_config select in System.tsx.
Two independent factors gate every mutating call — the static OWNER_METRICS_TOKEN bearer and a live OTP session for one hardcoded email (x-superadmin-jwt). The client-side Gate.tsx flow is explicitly documented as convenience only: "admin-ops enforces BOTH server-side, so this UI is convenience, not security." A leaked owner token alone yields 401 otp_required; a stolen JWT for a different email is rejected regardless of the token.
Global disabled is a hard-kill floor in the feature cascade (global → tier → account). No tier or account-level override can re-enable a feature the superadmin has disabled globally — enforced both in the UI (lockedByGlobal greys out the control) and, per the backend contract, server-side too. forced mandates ON everywhere below unless a narrower target explicitly turns it off; allowed defers to the level below.
users_list returns rows keyed id, not user_idadminOps.usersList() normalizes this client-side. Skipping the normalization makes every subsequent tier-change or revoke write target an undefined id, matching no profile — a silent no-op where the UI still shows a success toast. This is called out explicitly in api.ts as a trap for anyone rewriting that wrapper.
Maintenance-mode boolean is inverted between UI and storage. app_config.enabled = true means the app is up; the admin toggle's on state means Maintenance Mode is on (app down). Every read and write in MaintenanceCard negates across this boundary (setOn(!data.enabled) on load, maintenanceSet(!next, msg) on write) — flipping the negation in either direction either bricks the app or leaves it silently live during an incident.

Other invariants and platform notes: