Why review files never conflict
Two people review different code on different branches. Both append to
.vidi/reviews.jsonl. That should collide on every merge, and if reviewing
meant resolving a conflict each time, people would quietly stop reviewing.
It does not collide, and the reason is four properties stacked on top of each other.
One claim per line, never rewritten
The ledgers are JSONL: one review per line, appended. Nothing is ever edited in place, and nothing is deleted. Revoking adds a record rather than removing one.
That already removes most of the problem. Two people appending different lines are not editing the same text, so there is nothing for git to arbitrate.
The git half: merge=union
vidi init writes one line into .gitattributes:
.vidi/*.jsonl merge=union
This tells git to resolve these files by keeping every line from both sides instead of asking you to choose. No conflict markers, ever.
Writers append rather than rewrite for exactly this reason. A whole-file rewrite would clobber lines contributed on another branch, which is what union merge exists to prevent.
Why union alone would be wrong
Keeping every line says nothing about what the lines mean together. Union on its own leaves two holes:
- the same claim can arrive twice: a replay, or one review present in both its bare and its signed form
- a revocation and the review it kills can both be present
So the reader applies an algebra over the union
(crates/vidi-core/src/ledger/store.rs):
| Rule | Effect |
|---|---|
content_id dedup | content-identical lines collapse to one entry |
| revoke-wins | a revocation dominates its target forever, whichever arrived first |
| monotone | reviews.jsonl only ever adds claims; revocations.jsonl only ever tightens |
Order cannot change the answer
None of those rules depend on order. Two people who merge the same set of records compute the same coverage, no matter what sequence the merges happened in, and no matter where in the file a line sits.
No coordination is needed, because there is no ordering to agree on. (The formal name for this property is a monotone CRDT, if you want to look it up.)
It is also what makes re-approving after a revocation work. A revocation names
one content_id and dominates it permanently. But vouching again produces a
record with a different content_id, which the revocation does not name. The
old claim stays dead, the new one is live, and neither outcome depends on
arrival order.
Choosing which copy survives
Dedup has to keep one entry, and choosing which one is harder than it looks.
The same review can be written down three ways: plainly, wrapped in a signature,
or wrapped in a signature plus a notary receipt. Same claim, different envelopes,
and all three share one content_id.
So when several of them arrive, something must decide which to keep. Last-write-wins would make the survivor depend on merge order, and the whole guarantee above would collapse.
Instead the store ranks the forms (plain, then signed, then signed with a receipt) and always keeps the highest, breaking ties on the bytes themselves. The form it retains therefore depends only on which forms turned up, never on the order they arrived in.
That tie-break is also deliberately inert. The gate never reads a record’s form, receipt, or signatures, so which representative survives can never move a gate outcome. It exists only to make the answer deterministic.
A set, not a log
There is no repo-side hash chain and no sequence number. The ledger is an unordered set of claims, and any chain integrity lives notary-side only.
This is a deliberate trade. A chain would give tamper-evident ordering, but ordering is the one thing that cannot survive two people merging branches in different sequences. Vidi gives up the chain in the repository to keep the convergence.
Whitespace cannot dodge a revocation
There is a hard edge here. A content_id is computed after the surrounding
whitespace is stripped, so two records that differ only in spacing get the same
id (crates/vidi-core/src/ledger/codec.rs).
Without that, a whitespace twin of a revoked review would hash to a different
content_id, which the revocation does not name, and the resurrected claim
would read fresh. Stripping the whitespace first closes that door.
What this does not cover
The guarantee is about merging, not about agreement. Two people can hold contradictory positions on the same code, one approving and one rejecting, and the merge keeps both lines without complaint. Resolving that disagreement is a human problem; the ledger only promises that it will not lose either claim and that everyone computes the same answer from the pair.
It also depends on everyone having the .gitattributes rule. Commit that line
with .vidi/. A clone missing it gets ordinary conflicting-file behavior on
the one file that should never conflict.