T3: Stalwart back to SQLite, remove FoundationDB
closedParent: bug 75966cc (cell architecture tracking). Supersedes ADR-0008 (FDB active/passive + VIP + leader lease).
Goal
Stalwart runs SQLite everywhere (dev + prod). FoundationDB leaves the stack entirely: no cluster, no client coupling, no FDB roles.
Scope (decompose as 3a/3b/3c)
- 3a packaging:
nix/pkgs/stalwart.nixdrops thefoundationdbfeature, removes thefdb-7_3pin/postPatch(one-line AGPL fork goes away) andlibfdb_clink input;Justfileretirescheck-fdb-alignmentgate and FDB build path;install.ymldrops the libfdb_c rpath dance. - 3b deploy:
site.ymlremoves the foundationdb role;serve-stalwart.shandbootstrap-stalwart.shSQLite-only (Sqlite /data/stalwart.db);group_vars/all/sovrn.ymldropssovrn_fdb_*; foundationdb role deleted. - 3c verify + docs: re-run C2 lifecycle / DKIM teardown (
cf71374) / pagination (9d84518) suites against SQLite build; ADR-0008 marked superseded by the cell-model ADR (T9).
Acceptance
- No FDB .deb, binary feature, role, var, or doc reference remains (grep clean); fresh bootstrap + converge green on SQLite; suites green.
2 Comments
T3: Stalwart back to SQLite, remove FoundationDB — Implementation Plan
Goal: Stalwart runs SQLite everywhere (dev + prod); FoundationDB leaves the stack entirely.
Architecture: Pure deletion + SQLite-only simplification: strip the
foundationdbcargo feature/link inputs, delete the Ansiblefoundationdbrole and allsovrn_fdb_*wiring, make both bootstrap entrypoints emitSqlitedatastore JSON only, then re-verify with grep + Nix build + SQLite integration suites.Tech Stack: Nix (stalwart.nix, stalwart image), Ansible (site.yml, stalwart/foundationdb/bootstrap/common roles), bash (serve-stalwart.sh, bootstrap-stalwart.sh), Go integration suites (C2 lifecycle, cf71374 DKIM teardown, 9d84518 pagination).
Owner decisions (locked 2026-09-13): - Q1 SQLite path:
/var/lib/stalwart/stalwart.dbconfirmed (matches docs/deployment.md:63). Dev stays/data/stalwart.db. - Q2 common amd64 assert: DELETE entirely (opens arm64 possibility; new T12 tracking issue evaluates amd64+arm64 cells). - Q3SOVRN_DATASTORE: REMOVE outright (no no-op compat shim). - Q4stalwart-Cargo.lock: REGENERATE after dropping the feature (don’t leave dead FDB crates vendored).Scope: 3a packaging / 3b deploy / 3c verify+docs. ADR-0008 already marked
SUPERSEDED 2026-09-13 by the cell model (bug 75966cc)— verify only, no edit.Task 1: 3a —
nix/pkgs/stalwart.nixSQLite-only buildFiles: - Modify:
nix/pkgs/stalwart.nix:45-72- Regen:nix/pkgs/stalwart-Cargo.lockDeletes: the
postPatchblock substitutingfdb-7_4→fdb-7_3,"foundationdb"frombuildFeatures,pkgs.foundationdb.libfrombuildInputs. Update header comment to drop FDB history.Run:
nix build .#stalwart --out-link /tmp/stalwart-sqlite --print-out-paths -LExpected: build succeeds (updatingcargoLockoutputHashes / lockfile if the feature drop changes resolution); thenldd /tmp/stalwart-sqlite/bin/stalwart | grep -i fdbreturns nothing.Task 2: 3a —
nix/images/stalwart.nixdrop FDB from dev imageFiles: - Modify:
nix/images/stalwart.nix:26-27Deletes both
pkgs.foundationdbandpkgs.foundationdb.liblines.Run:
nix build .#image-stalwart --print-out-pathsExpected: PASS.Task 3: 3a —
Justfileretirecheck-fdb-alignmentFiles: - Modify:
Justfile:50-85Delete
check-fdb-alignmentrecipe + comment; drop thejust check-fdb-alignmentline; retitle comment to SQLite binary. Keep GLIBC/GLIBCXX ceiling checks.Run:
just --summaryExpected: nocheck-fdb-alignmentin list.Task 4: 3a —
install.ymldrop libfdb_c rpath danceFiles: - Modify:
deployment/roles/stalwart/tasks/install.yml:60-75Replace
libfdb_cldd assert with generic"'not found' in stalwart_ldd.stdout"assert.Run:
python3 -c "import yaml; yaml.safe_load(open('deployment/roles/stalwart/tasks/install.yml')); print('OK')"Expected:OK.Task 5: 3b —
serve-stalwart.sh(dev) SQLite-only, remove SOVRN_DATASTOREFiles: - Modify:
nix/scripts/serve-stalwart.sh:22-88- Modify:devenv.nix:108(drop-e SOVRN_DATASTORE=...passthrough)Delete
SOVRN_DATASTORE/fdbbranches, fdbserver/fdbcli block,FoundationDbJSON. (Q3: remove, no compat shim.)Run:
shellcheck nix/scripts/serve-stalwart.shExpected: clean;rg -n "DATASTORE|fdb|FDB|FoundationDb" nix/scripts/serve-stalwart.sh devenv.nixempty.Task 6: 3b —
bootstrap-stalwart.sh(prod) SQLite-onlyFiles: - Modify:
deployment/roles/stalwart/files/bootstrap-stalwart.sh:7-185- Modify:deployment/roles/stalwart/tasks/bootstrap.yml:48-95Required env:
DB_PATHreplacesCLUSTER_FILE. Payload:dataStore: { "@type": "Sqlite", path: $dbpath, poolMaxConnections: 10 }. Verify block:jq -e --arg db "$DB_PATH" '.["@type"] == "Sqlite" and .path == $db'.Replace each
CLUSTER_FILE: "{{ sovrn_fdb_cluster_file }}"withDB_PATH: "{{ sovrn_stalwart_db }}".Run:
shellcheck deployment/roles/stalwart/files/bootstrap-stalwart.shExpected: clean.Task 7: 3b —
sovrn.ymldropsovrn_fdb_*, add SQLite pathFiles: - Modify:
deployment/inventory/group_vars/all/sovrn.yml:39-58Deletes
sovrn_fdb_mode,sovrn_fdb_cluster_file,sovrn_fdb_version. Reword amd64 packaging comment (FDB rationale gone; arch gate itself deleted in Task 8 per Q2).Run:
python3 -c "import yaml; d=yaml.safe_load(open('deployment/inventory/group_vars/all/sovrn.yml')); assert 'sovrn_fdb_version' not in d and d['sovrn_stalwart_db'].endswith('stalwart.db'); print('OK')"Expected:OK.Task 8: 3b —
site.ymlremove role + deletefoundationdbrole + unwire unitsFiles: - Modify:
deployment/playbooks/site.yml:47-50(delete FDB block) - Delete:deployment/roles/foundationdb/(entire dir) - Modify:deployment/roles/bootstrap/tasks/main.yml:14-16(delete Stop foundationdb task) - Modify:deployment/roles/common/tasks/main.yml:1-6(DELETE amd64 assert entirely per Q2) - Modify:deployment/roles/stalwart/templates/stalwart.service.j2:3-4→After=network.targetonly - Modify:deployment/roles/sovrnd/templates/sovrnd.service.j2:3-4→ stalwart-only After/WantsRun:
rg -n "foundationdb|FoundationDB|libfdb|CLUSTER_FILE|clusterFile|fdbserver|fdbcli" deployment/playbooks/ deployment/roles/bootstrap/ deployment/roles/common/ deployment/roles/stalwart/templates/ deployment/roles/sovrnd/templates/Expected: no output.Task 9: 3c — Docs + comment sweep
Files: - Modify:
docs/runbooks/provision-cell.md(removeTODO(T3) CLUSTER_FILEline) - Modify:deployment/scripts/persist-bootstrap-secrets:52(FoundationDB/restic restore→R2 restore) - Modify:deployment/inventory/host_vars/primary.sovrn.example/vault.yml:15(same reword) - Verify only:docs/deployment.md,docs/09-open-questions.md,docs/adr/0009*,docs/adr/0008*(historical — keep)Run:
rg -n "FDB|fdb|foundationdb|FoundationDB|libfdb|CLUSTER_FILE|clusterFile|fdbserver|fdbcli|fdb\.cluster" --glob '!nix/vendor/*' --glob '!.devenv/*' --glob '!result/*'Expected: only historical (docs/deployment.mdformer-coupling note,0008/0009ADRs) + regeneratedstalwart-Cargo.lockinternals remain.Task 10: 3c — Verify: rebuild + suites on SQLite
Run:
just build-stalwartExpected:stalwart binary ready ... OK;ldd ... | grep -ci fdb= 0.Run:
just bootstrap <cell>thenjust update <cell>Expected:BOOTSTRAP-APPLIED+LISTENERS-OK+ cert; second run green/no-changes; config"@type": "Sqlite".Run:
SOVRN_INTEGRATION=1 go test ./...Expected: green — C2 lifecycle, DKIM teardown (cf71374:dkim.DeleteForDomain→domain.Delete, no orphans), pagination (9d84518: multi-page enumeration,calculateTotal).Acceptance (from bug): No FDB .deb, binary feature, role, var, or doc reference remains (grep clean); fresh bootstrap + converge green on SQLite; suites green.
T3 implementation complete — SQLite everywhere, FoundationDB removed
Commits (jj):
xnloztru(3a packaging) +ovzuurvy(3b deploy) +e1d590dd(3c docs). No Go code touched by any of them.What changed
nix/pkgs/stalwart.nixbuildssqlite+rocksonly (fdb-7_3postPatchdeleted,pkgs.foundationdb.liblink input gone); lockfile needed no regen (feature drop doesn’t alter resolution; consistency validation passed).nix/images/stalwart.nixdrops fdbserver/fdbcli/libfdb_c.Justfileretirescheck-fdb-alignment.install.ymlkeeps the Debian loader fix, drops--set-rpath /usr/lib+libfdb_cassert (genericnot foundassert instead).serve-stalwart.sh+bootstrap-stalwart.shSQLite-only (Sqlite /data/stalwart.dbdev,Sqlite /var/lib/stalwart/stalwart.dbprod via newsovrn_stalwart_dbvar);SOVRN_DATASTOREremoved outright (incl.devenv.nixpassthrough).site.ymlFDB block gone;deployment/roles/foundationdb/deleted;bootstrapstop task gone;commonamd64 assert deleted entirely (arm64 now possible — see T12ea055d0); both.serviceunits unwired fromfoundationdb.service;sovrn_fdb_*vars deleted.provision-cell.mdT3/CLUSTER_FILE TODO gone;persist-bootstrap-secrets+ example vault reworded to R2 restore. ADR-0008 alreadySUPERSEDED, no edit needed.Verification
just build-stalwart:stalwart binary ready (... GLIBC_2.39 GLIBCXX_3.4.30 OK);ldd | grep -ci fdb= 0.deployment.md, ADR-0008/0009,09-open-questions.md) + vendored lockfile internals.ansible-playbook site.yml --syntax-check: clean.just test(SOVRN_INTEGRATION=0): all green.SOVRN_INTEGRATION=1vs SQLite dev container:internal/stalwart52⁄52 pass,internal/dkim+internal/domaingreen (DKIM teardowncf71374ordering + pagination9d84518suites).Caveats (not T3 regressions)
internal/integration: 3 failures (AppPasswordXRPC,AppViewXRPCEndToEnd,AppViewUI) from earliermailcreateAccountDID/handle API drift + appview UI drift — pre-existing, untouched files, needs a follow-up.just bootstrap+just updateon a real cell is operator-run (no Hetzner host in this environment).Follow-ups: T12
ea055d0(amd64+arm64 cell support). Bug stays open for owner review.