- setup: SetupAdminIn (KILLS the 3rd named 500 — a non-string password reaching
argon2/len()), SetupTestEmailIn; setup_config stays a typed dict[str, Any] (a
genuinely dynamic key→value settings map, not a fixed schema).
- admin: AddInviteIn / SetRoleIn / SetSuspendedIn / AddDemoWhitelistIn. The
role/status business-rule 400s are unchanged; type errors are now 422.
All 3 ROADMAP-named 500-producers now fixed (channels priority + tag_id, setup
password). Gate: ruff clean, pytest 164 green.
- POST /api/setup/admin (create the first admin: active+verified+admin), /config (persist any
registry-backed settings — Google creds, SMTP, quota…), /test-email, and /finish (mark
configured + invalidate the token). All behind require_setup (valid token AND not configured).
- GET /api/setup/info reports secrets_manageable so the wizard hides the Google/SMTP secret steps
on a box without TOKEN_ENCRYPTION_KEY.
- app_state gains configured + setup_token_hash (migration 0025); existing installs (any users)
are marked configured so they never see the wizard, a fresh DB starts in setup mode.
- On first boot the app mints a one-time setup token and logs the wizard URL (only the hash is
stored); the token gates the setup steps (added in 6c).
- A middleware locks all /api + /auth routes (except /api/setup, /api/version, /healthz, static)
to 503 until configured, with a DB-free cached fast-path once setup completes.
- GET /api/setup/status tells the SPA whether to show the wizard.