- TypeScript 100%
#5: drop "secrets" from DATASTORE_SUBDIRS and guard every sync leg with isSecretsPath (pull skip while still advancing the watermark, markDirty drop, pushOneRel guard, hydrateFile guard). The local_encryption vault's .key sat next to its .enc ciphertext, so syncing the tier put both in the shared MongoDB and defeated encryption-at-rest. Pre-existing remote secrets/* docs are now skipped on pull and tombstoned by the next full-walk push. README claim corrected + remediation note (rotate creds, purge _paths/_blobs for ^secrets/). #4: add a pushBootstrapped sidecar flag. The first push from a cache must full-walk to bootstrap the remote from whatever's on disk, but a pullChanged that writes a clean sidecar (the setup migrate-then-hydrate sequence) erased the bulkInvalidated cold-start signal, so a migrated cache was never pushed. pushBootstrapped survives a pull and only flips true on a completed push, closing the gap; sidecars predating the field read false and self-heal on their next push. #6 (migration relocates+deletes repo-root .swamp/secrets, breaking local_encryption vault.get) is NOT fixable here — it's a swamp core bug in datastoreSetupExtension / DEFAULT_DATASTORE_SUBDIRS. Filed upstream as swamp-club Lab #650. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|---|---|---|
| extensions/datastores/mongodb | ||
| .env.example | ||
| .gitignore | ||
| CLAUDE.md | ||
| deno.json | ||
| LICENSE.txt | ||
| manifest.yaml | ||
| README.md | ||
| SWAMP.md | ||
MongoDB Datastore
Custom swamp DatastoreProvider backed by MongoDB.
Built for a mega swamp — one shared .swamp that many users and agents
read/write concurrently.
Replaces the coarse per-model file lock of the filesystem/S3 backends with finer-grained, event-driven coordination.
Requirements
- MongoDB 4.0+ running as a replica set in any configuration - single node is fine.
- Swamp CLI with extension support.
Install
Three steps. You need a swamp repo and a MongoDB replica set you can reach.
1. Pull the extension into your swamp repo:
swamp extension pull @keeb/mongodb-datastore
2. Add it as the datastore in your repo's .swamp.yaml:
datastore:
type: "@keeb/mongodb-datastore"
config:
uri: "mongodb://mongo.example.com:27017/?replicaSet=rs0&authSource=admin"
username: "swamp-user"
passwordEnv: "MONGO_PASSWORD"
database: "swamp"
tenantId: "my-org"
namespace: "my-repo"
See Configuration for field-by-field descriptions.
3. Put your MongoDB password in <repoDir>/.env (gitignored):
MONGO_PASSWORD=...
Swamp picks it up on the next invocation.
Configuration
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
uri |
string | yes | — | MongoDB URI. Must resolve to a replica set. |
username |
string | yes | — | Mongo user, passed to the driver as an auth option. |
passwordEnv |
string | no | MONGO_PASSWORD |
Env var name holding the password. Loaded from <repoDir>/.env at startup. |
database |
string | no | swamp |
Shared database; per-repo isolation is by collection prefix. |
tenantId |
string | no | default |
Tenant identifier; part of the collection prefix. |
namespace |
string | yes | — | Per-repo identifier; part of the collection prefix. |
defaultLockTtlMs |
number | no | 30000 |
Default lock TTL. Must exceed your longest critical section. |
Collections are prefixed t_<tenantId>_r_<namespace>_* — _locks for lock
docs, _paths for the manifest, _blobs for content-addressed bytes.
What it does
- Distributed lock.
findOneAndUpdateon a lock doc, TTL + heartbeat refresh, nonce fenced onreleaseandforceRelease. Global + per-model keys. - Manifest + content-addressed blob sync. The datastore-tier cache tree
(
.swamp/<cache>/{data,outputs,workflow-runs,...}) is split across two collections:_pathsholds one doc per file ({_id: relPath, hash, size, updatedAt, deletedAt}) and_blobsholds bytes keyed by their sha256. Pull = cursor over_pathssince the last watermark + bulk$inover_blobsfor the unique hashes the host doesn't already have. Push = hash locally, upsert any blob that's missing (idempotent on the hash_id), upsert path docs in bulk. Identical bytes pushed by N agents collapse to one blob server-side; renames are free; the cursor itself is the wire transport (no per-file roundtrips). - Health verifier. Rejects non-replica-set clusters and reports primary/secondary state, latency, and namespace.
Important Information
-
Vault secrets do not travel. Swamp's
local_encryptionvault reads and writes<repoDir>/.swamp/secrets/...on local disk regardless of datastore. This extension excludes thesecrets/tier from sync entirely — neither the symmetric.keyfiles nor their.encciphertext are ever pushed to MongoDB, and anysecrets/*docs left in the remote by an older version are skipped on pull. Vault contents stay per-host; use a non-local (KMS-backed) vault if you need cross-host secrets.Security note (versions ≤ 2026.05.25.1): earlier releases listed
secretsin the synced tier, so a repo that switched to this datastore pushed every vault.keynext to its.encciphertext into the shared MongoDB — anyone with read access could decrypt them (CVE-class encryption-at-rest defeat). After upgrading, rotate every secret that was synced and purge the leaked docs from MongoDB, e.g.:// hashes of the now-orphaned secret blobs, to drop after tombstoning paths const hashes = db["<prefix>_paths"].find( { _id: /^secrets\// }, { hash: 1 }, ).map((d) => d.hash); db["<prefix>_paths"].deleteMany({ _id: /^secrets\// }); db["<prefix>_blobs"].deleteMany({ _id: { $in: hashes } }); -
TTL must exceed your critical section. The lock's nonce fences
release/forceReleaseonly; it does not fence writes performed inside the critical section. If a holder pauses past TTL, another process can legitimately take over while the first still believes it holds the lock. SizedefaultLockTtlMswith margin. -
swamp datastore setupcan OOM on large existing.swamp/trees. Swamp core's migrator reads the tree into memory; at ~1 GB / ~100k files it dies. Purge.swamp/first, or use--skip-migrationand let workflows repopulate.
Related
@keeb/mongodb — sibling extension for
querying MongoDB collections from swamp workflows. Different extension (a
model , not a datastore).
Development
Contributor notes: CLAUDE.md and SWAMP.md.
License
MIT.