Super-admin (the platform view above orgs)

September 12, 2026 · View on GitHub

Everything else is org-scoped; super-admin is the one capability that sees across all orgs. It's deliberately separate from org roles.

Authorization (require_superadmin in domain.identity.access) — hybrid

A caller is a super-admin if EITHER:

  • the presented X-Treg-Token equals the env admin_token (get_settings().admin_token, from TREG_ADMIN_TOKEN), compared with hmac.compare_digest → principal "env-admin"; OR
  • the token resolves to a Membership whose User.is_superadmin is set (and not suspended) → principal = that user's email.

Otherwise 403. The env key bootstraps; POST /admin/users/{id}/superadmin then grants named users the flag (so a web portal can log in with either). Returns a principal string (for audit).

The dependency lives in domain.identity.access and every consumer imports it from there; the transitional api.py re-export retired with the rest of the stage-3 compatibility surface.

On the admin pool. The gate takes Depends(get_admin_session), and so does every /admin/* handler it guards — 3 connections, no overflow, separate from the API's (ops/deploy.md § Database pools). It must be the SAME dependency callable on both: FastAPI caches dependencies per request by identity, so a gate on get_session would put admin traffic back on the API pool through the back door. Staff pages are therefore bounded by construction — a panel that polls itself into saturation costs admins their own 503s, not the product's.

The cross-tenant read, mutation, and reconciliation handlers live in three ordered blocks in routers.admin. The mutation block shares the org deletion and member-rule cleanup helpers from routers.orgs.

Suspension enforcement (in require_member)

Two flags gate the org-scoped path: require_member raises 403 if user.suspended ("account suspended") or org.suspended ("org suspended"). Set by the admin endpoints below. Super-admin endpoints are unaffected (they use require_superadmin).

Endpoints (all under /admin/*, gated by require_superadmin)

  • Feedback: GET /admin/feedback (routers.feedback.admin_feedback) returns private reports, filtered by optional category, with limit and descending-ID before pagination. It shares get_admin_session with the super-admin gate. See feedback.
  • Reads: admin_stats (totals, tools_by_injector/tools_by_host, credential_health rollup, call volume + success rate, growth counts — computed in-process over small result sets), admin_orgs (every org + member/role/tool/secret/bundle counts), admin_org_detail, admin_users (+ their memberships), admin_tools, admin_calls, admin_health (non-ok secrets).
  • Failure evidence: admin_errors (?days=7&limit=100&provider=&status=&tier=) — failed calls at every credential tier, including plain own tools (tier: null), with CallRecord.error_request / error_response, the redacted capture of what the caller sent and what the provider answered (see data-model). tier filters an exact marketplace tier; an empty value selects plain own tools. Superadmin and not org-admin because the rows hold customers' request content; GET /calls deliberately does not expose these columns, and it defers them so they are not even fetched. This route also performs the 14-day retention pass (_purge_expired_error_evidence, blanking to '<expired>' on its own committed session) — ageing lives here because there is no scheduler and the request path cannot hold a lazy marker, get_admin_session never committing one.
  • Reconciliation (Phase 5): admin_reconcile_drift|spend|repeats (?since_days=30) — cross-org aggregates over platform-tier spend, so super-admin and not org-admin: price drift per endpoint, settled spend per provider (the invoice comparison), and the repeat-query rate. Query-time reports over existing rows; no scheduler. Logic in src/treg/reconcile.py; scripts/provider_balances.py is the manual companion that reads the providers' own balances. The async-task settlement report follows the same super-admin and dedicated admin-pool boundary.
  • Mutations (Phase 2): admin_set_superadmin, admin_suspend_user, admin_delete_user (removes memberships, then cascade_delete_org (in domain/governance/teams.py) any org left with zero members, and promotes a survivor to owner in any org left without one), admin_suspend_org, admin_delete_org (force, cross-tenant). Org deletion shares cascade_delete_org (in domain/governance/teams.py) with the owner's own delete_org (one cascade helper: tools, secrets, bundles, pending-oauth, call records, memberships, then the org).
  • Last-superadmin floor: require_superadmin returns the principal ("env-admin" or the user's email); the three destructive user ops refuse (409) when demoting/suspending/deleting would drop the count of active (is_superadmin and not suspended) users to zero — so a superadmin can't self-lock the platform out of /admin/*. The env token bypasses the floor (it can always recover).
  • Org credit: admin_credit_org (POST /admin/orgs/{org_id}/credit) — the HTTP equivalent of scripts/manual_grant.py. Credits an org with promotional balance through money.grant(), preserving the invariant that balance = sum(blocks) - sum(holds). Requires amount_usd, ref (idempotency key — a duplicate ref for the same org returns 409), and reason. Always uses kind="promotional" so the credit burns before purchased (non-refundable marketing expense). The script remains valid for airgapped / direct-DB ops, and the route removes the need to open Render Postgres IP allowlists.

Model + migration

User gains is_superadmin + suspended; Org gains suspended (booleans). The columns are part of the Alembic baseline schema; a live DB picks up schema changes through the explicit python -m treg upgrade release phase, never on restart.

CLI (interface/cli.md)

treg admin login --token (saves the env key), admin stats|orgs|org <id>|users|tools|calls|health, admin grant|revoke|suspend-user|rm-user|suspend-org|rm-org, and admin credit <org_id> --amount-usd <n> --ref <ticket> --reason <text>. _admin_client sends the saved admin_token if present, else the active org token (works for an is_superadmin user).