Skip to main content
← Back to list
01Issue
FeatureShippedSwamp CLIPublic
Assigneesstack72

Relationships

#3534 Datastore rework Phase 4 E: v3 on S3 and GCS (solo mode): setup --format 3 on buckets, replication as a datastore property, end to end

Opened by stack72 · 10/16/2026· Shipped 10/16/2026

Background (read this first)

swamp is reworking its datastore layer (tracking: swamp-club#2865). In the new format, v3, every change is a commit in a single-writer SQLite commits.db. Content is stored as content-addressed chunks, and each checkout rebuilds its files from the commits. Today v3 is opt-in for filesystem datastores only (swamp-club#3279).

Phase 4 is "solo mode on buckets": one writer at a time (the fenced lease), with Litestream replicating each checkout's local commits.db to the bucket. Everything it needs has shipped, dormant, behind the test-only SWAMP_V3_REPLICA setting:

Issue What
B #3358 the pinned, hash-checked Litestream binary
C #3359 the fenced writer lease
D1 #3416 the idle sidecar; a write confirms replication before it succeeds
D2 #3482 a checkout-local commits.db copy; lineages published under _control/lineage/; restore, refresh and takeover
D3 #3489 serve confirms each write request (group commit)
F #3490, #3506 the commit outbox
#3491 D2 follow-ups: migration seed, chunk-GC deferral while other lineages exist
#3507 replicaTarget(): the provider says where its replica lives
#3531 replicaPaths(): removing abandoned lineage paths on buckets

The extensions, @swamp/s3-datastore and @swamp/gcs-datastore, provide:

  • the writable datastore-wide control plane (datastoreWideLease), keeping _control/writer/, _control/lineage/ and _control/datastore-format at the root;
  • storage/1;
  • replicaTarget() and replicaPaths() (#3508). The release that ships #3508 is this issue's minimum extension version.

Design records: design/enablers/datastore-litestream.md (Decision; Phase 4A; D1, D2 and D3; the replica target; replica paths) and design/enablers/datastores.md ("v3 …" sections).

Goal

swamp datastore setup s3|gcs --format 3 creates a v3 datastore on a bucket, and every command and swamp serve work on it end to end. It's still opt-in: v2 stays the default, and v2 datastores are untouched.

What to build

  1. Setup. --format 3 for extension datastores:
    • refuse unless the provider has datastoreWideLease, datastoreControlPlaneStore(), storage(), replicaTarget() and replicaPaths(). The message names the minimum extension version;
    • refuse on Windows while the Litestream pin is below 0.5.18 (litestreamAvailability);
    • refuse a target that isn't new and empty: no v2 data under the prefix and no format marker, as inspectV3Target does for filesystem;
    • write the format marker through datastoreControlPlaneStore();
    • surface replicaTarget()'s refusals as they are (for example GCS with a custom apiEndpoint).
  2. Replication is a property of the datastore. For a v3 bucket datastore, the replica comes from resolveV3ReplicaSetting with the provider's target, not from SWAMP_V3_REPLICA (which stays, test-only, and still wins in tests).
    • Every checkout replicates. A checkout can't turn it off, because one that didn't would write a commits.db nothing reads.
    • Remove the dormancy pins in integration/datastore_v3_rules_test.ts that keep resolveV3ReplicaSetting, resolveV3ReplicaPaths and replicaTarget() out of production. Pin the new production callers instead.
  3. Chunks go through the provider's storage(), and chunk GC lists and deletes through it with its grace period. Collection runs once #3491's deferral lifts, that is once no other lineage is registered.
  4. The lease and the lineage records use datastoreControlPlaneStore() at the datastore root.
  5. Lineage path GC on buckets uses replicaPaths() (#3531), now wired in production.
  6. Narrow the sidecar's environment to the table in datastore-litestream.md ("the replica target"): credentials, HOME, PATH, proxies and CA bundles. D1 passes the whole environment today.
  7. No v2 sync on a v3 bucket datastore. The extension's file sync service, datastore sync and v2's pollers don't run. Refuse datastore sync with the same message as filesystem-v3.
  8. Second machines join through the clone route, as on filesystem-v3: the repo's .swamp.yaml, then a first command restores from the current lineage (D2). Make the first-clone cost visible: one check, plus one restore before the first command.
  9. Retention (swamp-club#3473): the CLI's hourly end-of-command pass can run at the end of a read-only command and commit. Its replication sync must never fail the user's command. Retention swallows its own datastore_v3_replication_unconfirmed and logs it; the user's own commits still fail as today.
  10. Serve on a v3 bucket datastore: the lease for its lifetime, D3's per-request confirmation, the hourly maintenance timer, and retention through V3MaintenanceTimer.
  11. Docs: design/enablers/datastores.md "v3 opt-in", the setup command's help, the extension READMEs (the minimum version), and the bundled skill's datastore references. Run verify-skills.

Already handled (don't redo)

  • Migration of an existing shared commits.db into the first lineage: #3491. Not needed for buckets, since a v3 bucket datastore is always new.
  • The pre-4E list from D2: #3491.
  • Real-endpoint timings: deferred to Phase 4 dogfooding (I), per swamp-club#3417.

Tests

  • Integration on the in-memory remote (packages/testing) with storage, replicaTarget (a file replica under the test's temp root) and replicaPaths:
    • setup refusals;
    • two checkouts: write, clone, restore, takeover, zombie fenced;
    • chunk GC, then lineage path GC;
    • retention's sync failure not failing a read command;
    • datastore sync refused.
  • The opt-in cases against ministack and fake-gcs-server, as D1 and D2 have them.
  • v2 doesn't change; apply the v2 gate.
  • swamp-uat's s3-v3 and gcs-v3 backends (Phase 4 H) follow this issue. Note anything H needs.

Blocked by:

  • the extension release with swamp-club#3508 (the minimum version);
  • swamp-club#3532 (v3 version numbers never go back), because several machines sharing one datastore is exactly where reused version numbers show.

Everything else it needs has shipped.

02Bog Flow
✓OPEN✓TRIAGED✓IN PROGRESS✓SHIPPED+ 1 MOREASSIGNED+ 11 MOREREVIEW+ 21 MOREPR_MERGED+ 2 MORESESSION_SUMMARIZED

Shipped

10/16/2026, 10:55:21 PM

Click a lifecycle step above to view its details.

03Sludge Pulse
stack72 assigned stack7210/16/2026, 4:00:35 PM
Editable. Press Enter to edit.

stack72 commented 10/16/2026, 3:11:43 PM

Addition from the #3508 review: when E narrows the Litestream sidecar's environment (item 6), pass STORAGE_EMULATOR_HOST through when it is set. #3507's table leaves it off as test-only, but it's the only way Litestream reaches a GCS emulator, and swamp-uat's GCS jobs (and Phase 4 H's gcs-v3 backend) all run on fake-gcs-server. The extensions are being asked to accept an apiEndpoint when STORAGE_EMULATOR_HOST names the same host; without this pass-through the sidecar still couldn't reach it. Add an integration case on fake-gcs alongside the ministack one.

stack72 commented 10/16/2026, 3:32:25 PM

Minimum extension version: @swamp/s3-datastore and @swamp/gcs-datastore 2026.10.16.1 (swamp-extensions #504, swamp-club#3508 + #3531). It's the first release with replicaTarget() and replicaPaths(), alongside storage() and the datastore-wide control plane. It also accepts a GCS apiEndpoint when STORAGE_EMULATOR_HOST names the same host and port (test-only, for Phase 4 H). E's setup refusal should name 2026.10.16.1.

stack72 commented 10/16/2026, 6:44:34 PM

Two items from #3549's planning (extensions guard for pre-guard binaries): (1) please add a test that on a v3 bucket datastore no sync-service method (pullChanged / pushChanged / preparePush / commitPush / markDirty) is ever called. The in-memory remote's recording sync service shows it. #3549's extension refusal relies on that invariant. (2) If #3549's extension release lands before this PR merges, raise V3_BUCKET_MIN_EXTENSION_VERSION to that release, so every v3 bucket repo's lockfile pins an extension with the guard from day one.

stack72 commented 10/16/2026, 9:03:41 PM

#3549 has shipped: @swamp/s3-datastore and @swamp/gcs-datastore 2026.10.16.5 (swamp-extensions #508) exclude _litestream/ from the file sync and refuse a format-3 marker at the first pull or push. Since it landed before this PR, raise V3_BUCKET_MIN_EXTENSION_VERSION to 2026.10.16.5 (and the setup refusal message and docs with it), so every v3 bucket repo's lockfile pins an extension with the guard from day one.

Sign in to post a ripple.