plan(#31,#32): fork-impossibility fix — design + failing test#104
Merged
Conversation
#31 (per-entity write lock) and #32 (UNIQUE INDEX(entity_id, previous_hash)) together design the provenance chain so that a forked history *cannot be represented*. Issues #31/#32 frame forks as purely adversarial; that is incomplete. Network-partitioned/replicated honest writers and simulation branches (ADR-0006) are legitimate divergence. A UNIQUE INDEX on (entity_id, previous_hash) does not *detect* a fork — it *rejects the second row at insert time*, discarding honest history. A fork that cannot be written cannot be detected or audited. That is the integrity defect. This is a planning skeleton — design doc + a failing-by-design test. No implementation; the parent session controls merges. Contents: * docs/decisions/0010-provenance-forks-are-first-class.adoc — ADR (estate .adoc default). Decision: forks are first-class. Concretely: - #32: do NOT add UNIQUE INDEX(entity_id, previous_hash). The `hash` PRIMARY KEY (preimage is domain-tagged + covers every field, per ADR-0002) already rejects exact-duplicate rows — that is the correct duplicate guard. Add a NON-unique idx_provenance_predecessor(entity_id, previous_hash) for O(log n) fork detection. - #31: verisimdb_provenance_chain_head (entity_id PK, one head) becomes verisimdb_provenance_chain_heads (PK(entity_id, head_hash), a SET of tips). `append_provenance` keeps BEGIN IMMEDIATE (still serialises racing *duplicate* appends from one node) but removes parent-tip + adds new-tip on linear append; a new `append_provenance_fork(... from_hash ...)` adds a head without removing one. - Detection surface: `fork_points(conn, entity)`; `verify_chain` becomes per-branch (each head -> genesis walk hash-consistent), so divergence is never conflated with tampering. - Data migration: idempotent CREATE-IF-NOT-EXISTS + INSERT..SELECT copy of the head table guarded by a sqlite_master check; old table left for one release (no destructive step ships). The log table is unchanged. Because the unique index is never created, an existing sidecar that already contains a legitimate fork cannot fail to open — a hazard that WOULD exist had #32 shipped first (flagged in the #32 thread). * tests/provenance_fork_test.rs — failing-by-design test. Writes genesis + branch A (supported linear path) + branch B (a second legitimate child of genesis). Asserts both children persist AND the entity records two heads. Compiles against the current public surface; the assertions, not the compile, fail. Verified red on this branch: child_count==2 passes (log keeps both rows) but head_count is 1 not 2 — the single-head table collapses branch B. Exactly the #31 defect, in executable form. (With #32's unique index applied, the branch-B insert would additionally fail with a constraint violation — also encoded in the ADR test plan.) Build/test: lib builds clean (only the pre-existing unrelated gc.rs RetentionConfig warning). New test compiles and FAILS as intended (1 failed, by design). Unblocked by #26 / PR #103 — there is now one ProvenanceEntry and one compute_hash for the fork-aware append/verify to evolve. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This was referenced May 16, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
plan(#31,#32): fork-impossibility fix — design + failing test
#31 (per-entity write lock) and #32 (UNIQUE INDEX(entity_id,
previous_hash)) together design the provenance chain so that a forked
history cannot be represented. Issues #31/#32 frame forks as purely
adversarial; that is incomplete. Network-partitioned/replicated honest
writers and simulation branches (ADR-0006) are legitimate divergence.
A UNIQUE INDEX on (entity_id, previous_hash) does not detect a fork —
it rejects the second row at insert time, discarding honest history.
A fork that cannot be written cannot be detected or audited. That is
the integrity defect.
This is a planning skeleton — design doc + a failing-by-design test.
No implementation; the parent session controls merges.
Contents:
docs/decisions/0010-provenance-forks-are-first-class.adoc — ADR
(estate .adoc default). Decision: forks are first-class. Concretely:
hashPRIMARY KEY (preimage is domain-tagged + covers every field, per
ADR-0002) already rejects exact-duplicate rows — that is the
correct duplicate guard. Add a NON-unique
idx_provenance_predecessor(entity_id, previous_hash) for O(log n)
fork detection.
becomes verisimdb_provenance_chain_heads (PK(entity_id,
head_hash), a SET of tips).
append_provenancekeeps BEGINIMMEDIATE (still serialises racing duplicate appends from one
node) but removes parent-tip + adds new-tip on linear append; a
new
append_provenance_fork(... from_hash ...)adds a headwithout removing one.
fork_points(conn, entity);verify_chainbecomes per-branch (each head -> genesis walk hash-consistent),
so divergence is never conflated with tampering.
copy of the head table guarded by a sqlite_master check; old
table left for one release (no destructive step ships). The log
table is unchanged. Because the unique index is never created,
an existing sidecar that already contains a legitimate fork
cannot fail to open — a hazard that WOULD exist had V-L2-L2: UNIQUE INDEX(entity_id, previous_hash) makes forks structurally impossible #32 shipped
first (flagged in the V-L2-L2: UNIQUE INDEX(entity_id, previous_hash) makes forks structurally impossible #32 thread).
tests/provenance_fork_test.rs — failing-by-design test. Writes
genesis + branch A (supported linear path) + branch B (a second
legitimate child of genesis). Asserts both children persist AND the
entity records two heads. Compiles against the current public
surface; the assertions, not the compile, fail. Verified red on
this branch: child_count==2 passes (log keeps both rows) but
head_count is 1 not 2 — the single-head table collapses branch B.
Exactly the V-L2-L1: per-entity serialisation prevents chain forks (write-path lock) #31 defect, in executable form. (With V-L2-L2: UNIQUE INDEX(entity_id, previous_hash) makes forks structurally impossible #32's unique
index applied, the branch-B insert would additionally fail with a
constraint violation — also encoded in the ADR test plan.)
Build/test: lib builds clean (only the pre-existing unrelated
gc.rs RetentionConfig warning). New test compiles and FAILS as
intended (1 failed, by design).
Unblocked by #26 / PR #103 — there is now one ProvenanceEntry and one
compute_hash for the fork-aware append/verify to evolve.
Co-Authored-By: Claude Opus 4.7 noreply@anthropic.com
🤖 Generated with Claude Code