You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
V-L2-L2: UNIQUE INDEX(entity_id, previous_hash) makes forks structurally impossible #32
V-L2-L1 prevents forks at the application layer. Defence in depth:
make forks structurally impossible by adding a DB-level UNIQUE
constraint on (entity_id, previous_hash). A second writer that
somehow sneaks past the chain_head lock still cannot insert because
the unique constraint rejects the second row with the same predecessor.
What to do
In src/codegen/overlay.rs::generate_provenance_table, add:
CREATEUNIQUE INDEXIF NOT EXISTS ux_provenance_chain
ON verisimdb_provenance_log(entity_id, previous_hash);
(Genesis records all have previous_hash = ''; one genesis per
entity is exactly what we want, so the unique index correctly
forbids duplicate geneses too.)
In tests, assert that the generated DDL contains the unique
index, and that an attempted duplicate insert fails with a
constraint violation.
Acceptance
Generated DDL contains the UNIQUE INDEX
DDL test asserts the index is present
Integration test: with V-L2-L1 in place, attempt two parallel
appends; one succeeds, the other gets a constraint-violation
error and retries against the new head (graceful)
Documented in README §"Provenance Tracking" + the threat
model doc
Declining the UNIQUE INDEX — it makes legitimate forks impossible (draft PR #104)
Investigating this alongside #31 surfaced a defect. The proposed CREATE UNIQUE INDEX ux_provenance_chain ON verisimdb_provenance_log(entity_id, previous_hash) does not detect
a fork — it rejects the second row at insert time, discarding
honest history. Network-partitioned/replicated honest writers and
simulation branches (ADR-0006) are legitimate divergence; a fork that
cannot be written cannot be detected or audited. That inverts the
integrity goal (tamper-evidence + no silent loss, not linearity).
Do NOT add UNIQUE INDEX(entity_id, previous_hash). The
duplicate guard already exists: hash is PRIMARY KEY, and the
preimage is domain-tagged and covers every field (ADR-0002 / V-L2-C1: hash full audit (actor + before_snapshot + transformation) with domain separation #27),
so an exact-duplicate row necessarily collides on hash and is
rejected — while two different entries that share a predecessor
(a real fork) are correctly allowed.
Add a non-uniqueidx_provenance_predecessor(entity_id, previous_hash) so fork detection (fork_points, GROUP BY … HAVING COUNT(*) > 1) is O(log n).
"Single writer per entity" becomes an opt-in policy (reject on
1 head) in application code, not a schema-welded invariant.
Migration note specific to this issue: because the unique index
is never created, an existing sidecar that already legitimately
contains a fork cannot fail to open. Had #32 shipped first, the
migration would have had to detect and quarantine such rows — a
hazard this plan avoids by declining the index.
The retained duplicate-rejection guarantee (the thing #32 actually
wanted) is covered by exact_duplicate_entry_is_rejected in the test
plan; the failing-by-design test in the draft PR proves a legitimate
fork can be written and detected.
Draft PR (design + failing test, not for merge): #104
Closing as superseded by ADR-0010 (docs/decisions/0010-provenance-forks-are-first-class.adoc), per that ADR's explicit instruction to note this here.
This issue proposes CREATE UNIQUE INDEX ux_provenance_chain ON verisimdb_provenance_log(entity_id, previous_hash). ADR-0010rejects that change by design: a unique index does not detect a fork, it rejects the second writer's row at insert time — discarding legitimate divergent history (partitioned/replicated/offline writers, simulation branches). "A fork that cannot be written cannot be detected or audited."
The correct duplicate guard is the existing hash PRIMARY KEY (domain-tagged preimage over all tamper-relevant fields ⇒ exact duplicates already collide). #32's only valid residue — a non-uniqueidx_provenance_predecessor for O(log n) fork detection — is folded into the #31 / ADR-0010 implementation. No UNIQUE INDEX(entity_id, previous_hash) should ever be created (also avoids a destructive migration hazard for sidecars that already legitimately contain a fork). Superseded — not implementing as written.
Activity
prov: tamper-evident hash chain — V-L1-B1 + V-L2-N1 + V-L2-C1..C4 + V…
hyperpolymath commentedon May 16, 2026
Declining the UNIQUE INDEX — it makes legitimate forks impossible (draft PR #104)
Investigating this alongside #31 surfaced a defect. The proposed
CREATE UNIQUE INDEX ux_provenance_chain ON verisimdb_provenance_log(entity_id, previous_hash)does not detecta fork — it rejects the second row at insert time, discarding
honest history. Network-partitioned/replicated honest writers and
simulation branches (ADR-0006) are legitimate divergence; a fork that
cannot be written cannot be detected or audited. That inverts the
integrity goal (tamper-evidence + no silent loss, not linearity).
Decision (full design: docs/decisions/0010-provenance-forks-are-first-class.adoc)
UNIQUE INDEX(entity_id, previous_hash). Theduplicate guard already exists:
hashisPRIMARY KEY, and thepreimage is domain-tagged and covers every field (ADR-0002 / V-L2-C1: hash full audit (actor + before_snapshot + transformation) with domain separation #27),
so an exact-duplicate row necessarily collides on
hashand isrejected — while two different entries that share a predecessor
(a real fork) are correctly allowed.
idx_provenance_predecessor(entity_id, previous_hash)so fork detection (fork_points,GROUP BY … HAVING COUNT(*) > 1) is O(log n).Migration note specific to this issue: because the unique index
is never created, an existing sidecar that already legitimately
contains a fork cannot fail to open. Had #32 shipped first, the
migration would have had to detect and quarantine such rows — a
hazard this plan avoids by declining the index.
The retained duplicate-rejection guarantee (the thing #32 actually
wanted) is covered by
exact_duplicate_entry_is_rejectedin the testplan; the failing-by-design test in the draft PR proves a legitimate
fork can be written and detected.
Draft PR (design + failing test, not for merge): #104
fix(#26): unify ProvenanceRecord/ProvenanceEntry (#103)
plan(#31,#32): fork-impossibility fix — design + failing test (#104)
hyperpolymath commentedon May 17, 2026
Closing as superseded by ADR-0010 (
docs/decisions/0010-provenance-forks-are-first-class.adoc), per that ADR's explicit instruction to note this here.This issue proposes
CREATE UNIQUE INDEX ux_provenance_chain ON verisimdb_provenance_log(entity_id, previous_hash). ADR-0010 rejects that change by design: a unique index does not detect a fork, it rejects the second writer's row at insert time — discarding legitimate divergent history (partitioned/replicated/offline writers, simulation branches). "A fork that cannot be written cannot be detected or audited."The correct duplicate guard is the existing
hashPRIMARY KEY (domain-tagged preimage over all tamper-relevant fields ⇒ exact duplicates already collide). #32's only valid residue — a non-uniqueidx_provenance_predecessorfor O(log n) fork detection — is folded into the #31 / ADR-0010 implementation. NoUNIQUE INDEX(entity_id, previous_hash)should ever be created (also avoids a destructive migration hazard for sidecars that already legitimately contain a fork). Superseded — not implementing as written.feat(provenance): fork-first-class chain model — ADR-0010 (#31; super…
feat(provenance): fork-first-class chain model — ADR-0010 (#31; super…
feat(provenance): fork-first-class chain model — ADR-0010 (#31; super…
feat(provenance): fork-first-class chain model — ADR-0010 (#31; super…