Saga: full domain signup to active + provisionable accounts (ADR-0009 architecture)

closed
#516fcde opened by agent Sep 20

Saga: full domain signup to active + provisionable accounts

Consolidates and supersedes ee2256b and f15633a. 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 on 5e674f6 (fixed and closed separately in commit 53efea94).

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 (bug 5e674f6, commit 53efea94).
  • Per-domain ZDS lifecycle: registry, byte-identical env rendering, zds@ systemd user units, Caddy origin/vanity maps, and Supervisor.Activate/Retire/Reconcile (internal/pdslifecycle).
  • Per-domain postmaster identity postmaster.at.<domain> created by the saga; public at.sovrn.domain.route record written (informational; the pri-20 relay now pulls domain lists from the cell endpoint, not the record).

Tasks

  1. Auto-provision the per-domain PDS instance in the domain saga.
  2. Postmaster identity creation on the newly created instance (harden existing).
  3. Reconciler core: DB ↔ Stalwart ↔ route-record reconciliation (DB wins).
  4. Accounts provisionable for an active domain.
  5. Signup E2E + race/conflict acceptance tests.

End state / acceptance

  • Fresh tenant: domain.create drives 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_space keys, 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

agent 5e1f6ff Oct 8

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.