Relationships
#4097 Datastore rework Phase 5 (team mode): config edits name the version they replace, so a stale edit is refused instead of overwriting another member's
Opened by stack72 · 10/28/2026
Background
Datastore rework Phase 5 (team mode). With managedConfig, a repository's configuration lives in the datastore: behavior and workflow definitions, vault configs and the extension lockfile. On v3 each definition is already a config partition of its own (config/models/<type>/<name>.yaml, and so on).
Two people editing one definition still means last writer wins:
putops can carryexpect: "absent" | {sha256}(src/domain/datastore_v3/op.ts), and tombstonesexpect: "present", but no edit flow sends them;- swamp-uat#490 (closed) recorded today's outcome as a baseline: A and B load workflow
deployat the same revision, A pushes an edit, then B pushes an edit to a different field. B's write replaces A's, and nobody is told. The design calls that "the baseline for the future compare-and-swap rejection".
Design: design/enablers/datastore-commit-log.md, the Definition row ("optimistic concurrency, expect: sha") and the edit rule: "put with expect: {sha256} of the version the user started from. A stale edit fails with who changed it and when."
Content-level authorisation of forwarded config writes shipped in #3922.
What to build
- Every config edit names what it replaces. A create puts with
expect: "absent". An edit puts withexpect: {sha256}of the stored version the edit started from, recorded when the definition was loaded, not when it is saved. A delete is a tombstone withexpect: "present". This covers every config writer:behavior create,editanddelete(and theirmodelspellings), and workflow create, edit and delete;- vault create, edit and delete, where they write config;
- the lockfile writers (
extension pull,update,rm); - the
--serverhandlers that do the same edits on serve.
- A stale edit is refused, not merged. The commit log refuses a put whose
expectdoesn't match. The user gets a stable code (for exampleconfig_edit_conflict) that names the definition, says who changed it and when (the commit's principal and time, which serve stamps), and says how to proceed: reload, re-apply the change, and save again. Nothing is written. - Through serve and solo alike. The same rule holds when a serve-mode checkout forwards the edit (#3985), when serve's own handlers edit, and in a solo checkout. In team mode serve decides the expectation inside its commit transaction, so two members racing get exactly one success.
- Lockfile edits compare on the lockfile's sha256. A conflict there means another checkout changed extensions meanwhile, and the message says to pull and retry.
Decisions for the plan (recommendations included)
- Where the expected sha comes from. Recommend: the content hash the read model already records for the record when it was loaded, carried through the edit flow (the editor session, or
--from-filewith the hash of what was read). Don't re-read at save time, which would hide the race. - Team mode without
managedConfig. #3985 documents that behavior definitions are then repository files: serve judges a forwarded run against its own checkout's files, and a behavior defined only on a laptop is refused. Recommend: keep that as the documented limit for now, and say in the team-mode docs that teams run withmanagedConfig. Don't widen this issue to move non-managed definitions through serve.
Out of scope
- swamp-club#2612 (pulled extension archives stored in the config tier, with every checkout converging to the shared lockfile) is its own issue, linked here. It may follow this one.
- Merging non-conflicting field edits. A stale edit is refused whole; a merge is a later decision.
Tests
Red-check each against main:
- The uat#490 scenario in-process: two checkouts load
deploy, A saves, B's save is refused withconfig_edit_conflictnaming A and the time, and A's edit stands. - Create against an existing name, and delete of a changed or already-deleted definition, are each refused.
- Through serve: two members race one edit and exactly one succeeds; a serve handler's edit and a forwarded edit race the same way.
- A lockfile conflict between two
extension pulls is refused with the pull-and-retry message. - Solo v3 and v2
managedConfigkeep their behaviour where this issue doesn't change it. Say in the PR what v2 does.
Where
src/domain/datastore_v3/op.ts (the expectations), the definition, workflow, vault and lockfile repositories and their edit flows in src/libswamp/ and src/cli/commands/, serve's handlers, and the design docs.
After this ships, swamp-uat#490's concurrent-edit assertion is updated deliberately to the rejection. That's a separate swamp-uat issue.
In Progress
Click a lifecycle step above to view its details.
Sign in to post a ripple.