Skip to content

plan(#31,#32): fork-impossibility fix — design + failing test#125

Closed
hyperpolymath wants to merge 1 commit into
mainfrom
plan/31-32-fork-fix
Closed

plan(#31,#32): fork-impossibility fix — design + failing test#125
hyperpolymath wants to merge 1 commit into
mainfrom
plan/31-32-fork-fix

Conversation

@hyperpolymath
Copy link
Copy Markdown
Owner

#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:

  • 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.

Summary

Changes

RSR Quality Checklist

Required

  • Tests pass (just test or equivalent)
  • Code is formatted (just fmt or equivalent)
  • Linter is clean (no new warnings or errors)
  • No banned language patterns (no TypeScript, no npm/bun, no Go/Python)
  • No unsafe blocks without // SAFETY: comments
  • No banned functions (believe_me, unsafeCoerce, Obj.magic, Admitted, sorry)
  • SPDX license headers present on all new/modified source files
  • No secrets, credentials, or .env files included

As Applicable

  • .machine_readable/STATE.a2ml updated (if project state changed)
  • .machine_readable/ECOSYSTEM.a2ml updated (if integrations changed)
  • .machine_readable/META.a2ml updated (if architectural decisions changed)
  • Documentation updated for user-facing changes
  • TOPOLOGY.md updated (if architecture changed)
  • CHANGELOG or release notes updated
  • New dependencies reviewed for license compatibility (PMPL-1.0-or-later / MPL-2.0)
  • ABI/FFI changes validated (src/interface/abi/ and src/interface/ffi/ consistent)

Testing

Screenshots

#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>
@hyperpolymath
Copy link
Copy Markdown
Owner Author

Superseded — this plan/test pair already landed via PR #104 (e51d345), and the implementation it gates landed via PR #109 (1a73d3a, feat(provenance): fork-first-class chain model — ADR-0010 (#31; supersedes #32)). After git rebase origin/main this branch is empty (5a646e5 skipped as already applied). Closing — nothing left to merge.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant