SoW: Clerk AuthN Migration

RFC: auth-clerk-migration (Accepted 2026-07-02)

Seven PRs. 1 is the foundation; 2–5 are independent flows once 1 lands (2 before 3 keeps the dual-stack story testable end to end); 6 can start after 1 and must land before cutover; 7 is post-cutover cleanup.

PR 1 — Clerk Backend API client

Add the Backend API client the clerk/mod.rs comment anticipates: create user (incl. password_digest + password_hasher: "argon2id" import form), delete user, mark email verified. Un-dead-code ClerkConfig and plumb it through state.rs. No behavior change; everything stays gated on config presence.

PR 2 — Login via Clerk (dual-stack)

Login branches on clerk_id.is_some(): Clerk users verify via FAPI create_sign_inattempt_second_factor; on success mint the AX session token exactly as today (RFC §2a). Users without clerk_id keep the homerolled path. Covers the fail-closed Clerk-outage posture and the CLAUDE.md lifecycle test matrix (disconnect/restart/revocation; API keys unaffected).

PR 3 — Signup via Clerk + email verification

Re-wire signup (public_routes.rs:182-280): invite gate unchanged → Backend API create-user → identity-only AX provisioning with clerk_id set. Clerk email verification becomes required for new signups, proxied through the BFF. Compensation on partial failure (invite decrement + Clerk user delete). GUI signup/verification screens included.

PR 4 — MFA enrollment + challenge (the deliverable)

Wire the existing FAPI create_totp/verify_totp/delete_totp into new auth routes; expose backup codes and SMS. GUI enrollment/challenge screens. Login challenge path from PR 2 lights up for enrolled users.

PR 5 — Password change + reset via Clerk

Change-password and reset-password proxy Clerk's flows for clerk_id users (FAPI change_password exists already); homerolled password_reset_codes path remains only for pre-import stragglers. GUI included.

PR 6 — Bulk import + reconciler

admin-cli command: import all users' Argon2id digests verbatim via Backend API, mark emails verified, write back clerk_id. Lazy fallback on homerolled login (create Clerk user from just-verified plaintext, set clerk_id). Backstop sweep for Clerk-created-but-AX-failed rows (same pattern as the lazy- provisioning reconciler).

PR 7 — Retirement (post-cutover)

After sandbox → demo → prod are fully on Clerk: delete totp_manager.rs, the setup_2fa/confirm_2fa/disable_2fa routes and verify_2fa_if_enabled; drop enabled_2fa/totp_cipher/nonce and password_reset_codes from db/postgres/1.sql; drop the homerolled login branch and, finally, hashed_password.

Rollout gates