The Answer That Never Came Back
Compare-and-swap protects a write from other writers. It does not protect a writer from itself. Picture a write sent from a laptop to the Mac that holds the store. The store commits it, and the reply is lost on the way back: the network blinks, the laptop's process is killed, the lid closes. The laptop now has no idea whether its write happened.
Both obvious reactions are wrong. Sending it again risks doing it twice; and with compare-and-swap, the second attempt would fail anyway with revision_conflict, because the note's revision is now the one the first attempt produced. The writer would be told "someone else changed this" when the someone else was itself. Giving up risks losing a write that may never have landed.
Every Write Has a Name
Soul Vault's root refuses any write without a request_id, the caller's own name for this one request. On a store that declares durable_idempotency (Delta does), the first thing write_batch does under the vault's lock, before the identity-note rule and before any comparison, is ask the child whether this request id already completed. Two kinds of check come earlier: the input checks (an actor, a request id, a non-empty batch in one vault), and the refusals that taking the lock itself can raise (the write gate, the seal, a moved pointer). A replay never slips past those.
- Not seen before: the write proceeds normally.
- Seen, same content: the store returns the first result, flagged
replayed=True. Nothing is applied a second time, and there is no conflict against the caller's own earlier success. - Seen, different content:
policy_denied, "a request id names one request". Reusing an id for a different write is a caller bug, and the store says so instead of guessing which one was meant.
"Same content" is decided by a digest the root computes over the whole batch: for each change, the operation, the note id, the SHA-256 of the new text, the expected revision and whether the note must exist. The Delta store keeps one row per completed request (id, vault, digest, result, batch), written in the same SQLite transaction that makes the batch real, so a request is recorded as completed exactly when it is.
Naming the Unknown
Contract v5 also gave the lost answer a name. When a write left the caller and no answer came back, the caller does not report success or failure. It raises outcome_unknown with the request id. The caller then has two honest ways to find out:
- Ask
request_outcome(request_id)(the CLI'ssoul-vault outcome): it returns what that request published, or nothing if it never completed. - Send the very same request again, same id and same content. If the first one landed, the replay answers with its result; if not, this one applies.
That pairing was measured on the live store on 2026-11-10 from three Macs, using throwaway test souls: a write from another Mac took 72 to 83 ms and made one commit; the same request id sent again was answered as replayed in 5 to 7 ms, and the history still held one commit.
A Detail That Bit
Request ids travel through Delta's write call, which accepts only letters, digits and . _ : -. The daily canary once built its cleanup request ids with a vault name inside, guest-souls/<slug>, slash included. Those removals never left the Mac that sent them, and two test fixtures were left on the live store until they were removed by hand the same minute. The ids are now built without the slash, and the tests' fake Delta refuses any id the real one refuses, so the fake can no longer be more forgiving than production.