Git Can Go Backwards
Putting the pointer in git buys history and availability. It also brings a danger a service does not have: a checkout can move backwards. Someone checks out an old branch to look at something. A backup of the repository is restored. A Mac that has been asleep for a week still holds last week's tree. In each case the record on disk is valid JSON with a valid schema, and it names a store that is no longer the vault.
The defence is a single integer. Every pointer record carries a generation, and the rule is that it only grows. Each move is a new, higher number: generation 3 added the store's tree name, generation 4 switched the backend to Delta, generation 5 removed the retired implementation's metadata.
Each Mac Remembers the Highest It Saw
A rule that only grows needs a memory. Every Mac keeps one small file, pointer-seen.json under Soul Vault's state root, holding the highest generation it has ever loaded and that record's hash. Every time the pointer is loaded, the reader compares:
- A higher generation than remembered: accepted, and remembered from now on (written atomically, under a file lock, so two sessions starting at once cannot interleave). Remembering is best effort: a Mac whose state root cannot be written still reads its pointer but keeps no record, and a write that fails is skipped. The rule protects only what was remembered.
- The same generation: accepted.
- A lower generation:
pointer_regressed, and nothing loads. The cause says what happened and what to do: "the checkout moved backwards (pull cwkFirelink, or Dad moves the pointer forward by hand)".
One more case is easy to miss. If the remembered file exists but cannot be read, the reader does not shrug and treat it as zero. It raises pointer_regressed too: "a regression cannot be ruled out, so nothing is loaded until the record is repaired". Treating an unreadable memory as "never seen anything" would quietly reopen exactly the hole the file exists to close.
A Write Checks the Pointer Again
Reads check the generation when they load the pointer. Writes are stricter, because a write can be prepared under one record and arrive under another. A long-running service might open the store at generation 4 and try to write an hour later, after Dad has moved the pointer to 5.
So every write re-reads the live record at the moment it is admitted, through refuse_a_moved_pointer. If the generation or backend differs from the one the store was opened under, the write is refused with stale_pointer_generation: "Nothing was written: open the store again." The canonical store's share does the mirror-image check: a request from a Mac whose checkout still names a store that has since been replaced is told so by name, and is never handed the replaced store's notes as the current vault.
Why Not a Timestamp?
A timestamp seems like it would do the same job. It does not, for two reasons. Clocks on different Macs disagree, and a restored record carries its original timestamp, which can be later than a record written on a Mac whose clock runs slow. And a timestamp invites arithmetic ("close enough"). A generation is a plain count that one hand advances deliberately; the only question it answers is "is this older than something I have already seen?", and that question has an exact answer.