Saga: full domain signup to active + provisionable accounts (ADR-0009 architecture)
closedSaga: full domain signup to active + provisionable accounts
Consolidates and supersedes
ee2256bandf15633a. Reflects the actual architecture as of 2026-09-20 (ADR-0009 cells + per-domain postmasters; provision-but-disabled; DB-primary; pull-based relay). Does NOT depend on5e674f6(fixed and closed separately in commit53efea94).
Goal
A new tenant can drive the XRPC flow and end with a domain that is active, with its own per-domain PDS instance and postmaster identity, and accounts provisionable — plus a reconciler that keeps the DB, Stalwart, and the public route record consistent after crashes, drift, and duplicate submits. Spaces, permissioned record writing, login, and webmail are explicitly out of scope.
Current state (already implemented — do not rebuild)
- XRPC surface:
tenant.create,domain.create/list/getDnsState,mail.createAccount/list/remove, app passwords; server-rendered UI flow. - Stalwart domain/account provisioning (
internal/domain,internal/account,internal/stalwart) and DKIM (internal/dkim). - DNS prober + verifier sweep:
verifying→active, reap, orphan Stalwart→DB, retry-tail (internal/verifier). - Domain saga (provision-but-disabled): Stalwart
EnsureInactive→ DKIM → DB row (TID minted upfront) → PDS lookup → postmaster invite/account → route record → DB insert, with idempotent duplicate handling and provenance-aware compensations (bug5e674f6, commit53efea94). - Per-domain ZDS lifecycle: registry, byte-identical env rendering,
zds@systemd user units, Caddy origin/vanity maps, andSupervisor.Activate/Retire/Reconcile(internal/pdslifecycle). - Per-domain postmaster identity
postmaster.at.<domain>created by the saga; publicat.sovrn.domain.routerecord written (informational; the pri-20 relay now pulls domain lists from the cell endpoint, not the record).
Tasks
- Auto-provision the per-domain PDS instance in the domain saga.
- Postmaster identity creation on the newly created instance (harden existing).
- Reconciler core: DB ↔ Stalwart ↔ route-record reconciliation (DB wins).
- Accounts provisionable for an active domain.
- Signup E2E + race/conflict acceptance tests.
End state / acceptance
- Fresh tenant:
domain.createdrives the saga to create the Stalwart domain (disabled), DKIM, the per-domain PDS instance, and the postmaster identity → DNS readiness → active, with no operator step. - Accounts can be created for an active domain and are usable through the existing account/app-password flows.
- The reconciler converges DB/Stalwart/route after crash, manual drift, and duplicate submits, and never destroys live infrastructure.
- Race/duplicate/crash tests are green.
Non-goals / deferred (separate issues)
- Space wiring and permissioned record writing (
at.sovrn.domain.detail,at.sovrn.mail.account) — Spaces Stage B (6a79018,12ce549). - KMS per authority,
#atproto_spacekeys, hosted spaces-PDS (spaces GA). - Login/webmail/pending-account activation (
0ffe23b,764b8e2,31b625f,ac83db8). - Jetstream watcher (C1/C2).
Dependencies / relations
9d84518— registry pagination, required for complete enumeration in the reconciler.75966cc— cell architecture tracking (parent context).
1 Comment
Goal met (2026-10-08): domain.create drives the saga to an active domain with its own ZDS instance and postmaster identity, no operator step, verified live on mx99; accounts provisionable on the mail side. Children 63547c4, eb70bc9, 9f75370, 858fad1 closed; the remaining test and identity-reuse work is consolidated in 4e9d7ba; ATProto user accounts are a6c4223.