Relationships
#3395 Datastore rework Phase 4 G: S3 and GCS implement the writable datastore-wide control plane and keep _control/writer/ at the bucket root
Opened by stack72 · 10/13/2026
Background
swamp-club#3364 (datastore rework Phase 4 E) gives core a writable datastore-wide control plane so the v3 fenced writer lease (swamp-club#3359) can live at _control/writer/ at the datastore root, outside every namespace. Core side: datastoreControlPlaneStore() may now return a WritableDatastoreControlPlaneStore (get, put, create-only putIfAbsent, delete, list, all at the root _control/, binding no namespace), and @swamp-club/swamp-testing ships assertWritableDatastoreControlPlaneStoreConformance. Core's datastoreWriterLeaseStore refuses a provider without a writable one.
This is Phase 4 G: the S3 and GCS extensions' side (swamp-extensions has GitHub issues disabled, so it is tracked here). Tracking: swamp-club#2865.
What is needed (S3 and GCS)
- Implement
datastoreControlPlaneStore()on both providers, returning a writable store keyed at<prefix>/_control/<key>. Neither extension implements even the read-only hook today. Cheap to build: no sync service, no namespace binding, no request until a call, one request per call, no HeadBucket preflight.getreturns null only for a missing record and rejects on any other failure (access denied included) and when its signal aborts.putIfAbsentuses the conditional put the sync service's control plane already uses (S3If-None-Match, GCSifGenerationMatch=0). - Skip
_control/writer/inmigrateRootControlPlaneToNamespace(S3s3_cache_sync.ts, GCSgcs_cache_sync.ts). Today a namespaced push into a namespace that has the legacy index<namespace>/.datastore-index.json(one a solo datastore was upgraded to) copies every root_control/*record into<namespace>/_control/and deletes the original. It runs on push only; a namespace with only the current_index/layout triggers nothing. Checked on ministack with@swamp/s3-datastore2026.10.13.1: with the legacy index planted, a rootwriter/record moved on the first namespaced push; a copy that skips_control/writer/passes. A fence key moved under a live writer looks like a lost lease or lets a second writer take a fence already in use. Consider skipping_control/datastore-formattoo (today the marker writer avoids it by leaving no legacy namespaced index behind). - Run
assertWritableDatastoreControlPlaneStoreConformanceagainst ministack and fake-gcs-server, passingopenNamespacedSyncFixtureandmarkNamespaceUpgradedFromSolo(write an empty legacy v1 index,{"version":1,"lastPulled":...,"entries":{}}, at<prefix>/<namespace>/.datastore-index.json; a bare{}makes the push throw), against an empty bucket or prefix (the case skips when the root already holds records, since the move takes all of them) so the namespaced-push case runs; without both it is skipped. It checks awriter/record stays at the root after a namespaced push into the upgraded namespace. Red-check it against the current migration.
Acceptance
- On S3 and GCS, the lease's records live at
_control/writer/at the datastore root, whatever namespace a checkout uses. - A namespaced pull or push never moves or copies them.
- Both extensions pass the writable datastore-wide conformance suite, namespaced-push case included.
- The Phase 4 bucket v3 opt-in must require an extension version that has this change, so v3 is never enabled on a bucket against an older extension that would move the lease.
Open
No activity in this phase yet.
system commented 10/13/2026, 8:37:52 PM
Classified automatically when this issue was filed.
- Source: Extensions
If you feel this classification is incorrect, add a ripple to tell us so.
stack72 commented 10/13/2026, 11:04:45 PM
Core now refuses the v3 writer lease unless the provider declares datastoreWideLease: true (swamp-club#3414, swamp-club/swamp#3088). Ship that declaration on the S3 and GCS providers in the same release as the _control/writer/ skip in migrateRootControlPlaneToNamespace. In that release's assertWritableDatastoreControlPlaneStoreConformance test, also assert result.namespacedPushSkipped === undefined, so the namespaced-push case is known to have run.
Sign in to post a ripple.