Skip to content

Commit 297e043

Browse files
hyperpolymathclaude
andcommitted
fix(#26): unify ProvenanceRecord/ProvenanceEntry
Canonical type: `abi::ProvenanceEntry`. Rationale — `abi::ProvenanceEntry` is the richer, persistence-boundary type: it carries the full chain API (`genesis` / `chain` / `verify` / domain-tagged length-prefixed `compute_hash`), is the type the SQLite write path (`append_provenance` / `verify_chain`) and the Idris2 ABI / Zig FFI bridge are written against, and is the type the threat-model doc names as the implementation. `tier1::provenance::ProvenanceRecord` was a byte-for-byte duplicate struct (identical 8 fields) whose `compute_hash`/`verify` had already been reduced to thin shims that just delegated to `ProvenanceEntry`. It was orphaned — nothing in the tree constructed it. No field divergence: the two structs had identical fields and field order, so this is a pure dedup, not a unification of diverged shapes. No `From`/alias impls were needed. Changes: - Deleted `tier1::provenance::ProvenanceRecord` struct + impl block (the duplicate `compute_hash`/`verify` shims). - Replaced with `pub use crate::abi::ProvenanceEntry;` so any external caller of `tier1::provenance::*` resolves to the single canonical definition. `tier1/provenance.rs` now holds only write-path logic (`append_provenance`, `verify_chain`, `SIDECAR_DDL`, `init_sidecar_schema`) — no type definitions. - Updated stale references in `docs/architecture/TOPOLOGY.md` and `ROADMAP.adoc` from `ProvenanceRecord` to `ProvenanceEntry`. Refs migrated: 0 code call sites (the tier1 type was orphaned; only its own self-references plus 2 documentation mentions). `grep -r ProvenanceRecord src/` now returns zero hits. On-disk / JSON stability: unchanged. `ProvenanceEntry` and the deleted `ProvenanceRecord` had identical fields; the deleted shims already computed the canonical hash via `ProvenanceEntry::compute_hash`. No serde field names, SQL column names, hash preimage, or DB schema are touched — provenance integrity is preserved bit-for-bit. Build/test: `cargo build` clean (only the pre-existing unrelated `RetentionConfig` unused-import warning in gc.rs). `cargo test` green: 87 lib + 9 integration + 2 sqlite-e2e tests, 0 failed. No offline-cache failures. Acceptance: - [x] `grep -r ProvenanceRecord src/` returns zero hits - [x] `cargo build` clean, `cargo test` green - [x] `tier1/provenance.rs` contains only write-path logic, no type defs Unblocks #31/#32 (they touch the same provenance types — there is now exactly one type and one `compute_hash` to evolve). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 88e5528 commit 297e043

3 files changed

Lines changed: 16 additions & 59 deletions

File tree

ROADMAP.adoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
* [x] CLI with subcommands (init, start, drift, provenance, history, status, octad, doctor, gc, validate, version)
1010
* [x] Manifest parser (verisimiser.toml with `[tier1]`/`[tier2]`/`[retention]` config)
1111
* [x] Concerns octad fixed canonical (ADR-0004): Data, Metadata, Provenance, Lineage, Constraints, AccessControl, Temporal, Simulation
12-
* [x] Tier 1 data types per concern (ProvenanceRecord, TemporalVersion, DriftReport, AccessPredicate)
12+
* [x] Tier 1 data types per concern (ProvenanceEntry, TemporalVersion, DriftReport, AccessPredicate)
1313
* [x] ABI module stubs (Idris2 + Zig FFI) and domain-tagged `compute_hash`
1414
* [x] Drift categories pinned (ADR-0003): input / distance / threshold per category
1515
* [x] README rewritten around concerns octad (V-L1-A2, #20)

docs/architecture/TOPOLOGY.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ verisimiser/
1212
│ ├── src/manifest/ — TOML manifest parsing (verisimiser.toml)
1313
│ ├── src/tier1/ — Tier 1 piggyback data types
1414
│ │ ├── drift.rs — DriftReport, DriftCategory (8 categories)
15-
│ │ ├── provenance.rs — ProvenanceRecord, SHA-256 hash chain
15+
│ │ ├── provenance.rs — SHA-256 hash-chain write path (canonical ProvenanceEntry lives in src/abi/)
1616
│ │ └── temporal.rs — TemporalVersion, point-in-time snapshots
1717
│ ├── src/tier2/ — Tier 2 overlay stubs (graph, vector, tensor, semantic, document, spatial)
1818
│ ├── src/intercept/ — Per-backend interception strategies

src/tier1/provenance.rs

Lines changed: 14 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -14,69 +14,26 @@
1414
// `abi::ProvenanceEntry::compute_hash` (domain-tagged + length-prefixed
1515
// — see ADR-0002 / #27); this module just persists the entries.
1616

17-
use crate::abi::ProvenanceEntry;
1817
use chrono::{DateTime, Utc};
1918
use rusqlite::{params, Connection, TransactionBehavior};
20-
use serde::{Deserialize, Serialize};
2119

2220
// =========================================================================
23-
// Public re-export: the canonical entry shape
21+
// Canonical entry shape
2422
// =========================================================================
2523

26-
/// A single link in the provenance hash chain. Mirrors
27-
/// `abi::ProvenanceEntry` 1:1 — kept here for backward compatibility
28-
/// with code that imported `tier1::provenance::ProvenanceRecord`. New
29-
/// callers should prefer the canonical type in `abi`.
30-
#[derive(Debug, Clone, Serialize, Deserialize)]
31-
pub struct ProvenanceRecord {
32-
pub hash: String,
33-
pub previous_hash: String,
34-
pub entity_id: String,
35-
pub operation: String,
36-
pub actor: String,
37-
pub timestamp: DateTime<Utc>,
38-
pub before_snapshot: Option<String>,
39-
pub transformation: Option<String>,
40-
}
41-
42-
impl ProvenanceRecord {
43-
/// Backward-compat shim. Computes the canonical hash via
44-
/// `abi::ProvenanceEntry::compute_hash` rather than the older
45-
/// string-based form.
46-
pub fn compute_hash(
47-
previous_hash: &str,
48-
entity_id: &str,
49-
operation: &str,
50-
actor: &str,
51-
timestamp: &DateTime<Utc>,
52-
before_snapshot: Option<&str>,
53-
transformation: Option<&str>,
54-
) -> String {
55-
ProvenanceEntry::compute_hash(
56-
previous_hash,
57-
entity_id,
58-
operation,
59-
actor,
60-
timestamp,
61-
before_snapshot,
62-
transformation,
63-
)
64-
}
65-
66-
/// Verify that this record's stored hash matches a fresh recompute.
67-
pub fn verify(&self) -> bool {
68-
let expected = Self::compute_hash(
69-
&self.previous_hash,
70-
&self.entity_id,
71-
&self.operation,
72-
&self.actor,
73-
&self.timestamp,
74-
self.before_snapshot.as_deref(),
75-
self.transformation.as_deref(),
76-
);
77-
self.hash == expected
78-
}
79-
}
24+
// The provenance entry type is defined once, in `crate::abi`. It is the
25+
// canonical representation used across the Rust CLI, the Idris2 ABI
26+
// proofs, and the Zig FFI bridge, and it is the type persisted at the
27+
// SQLite boundary by `append_provenance` below.
28+
//
29+
// This module previously carried a byte-for-byte duplicate struct
30+
// (same fields, its own `compute_hash`/`verify`) under a different
31+
// name. It was orphaned — nothing constructed it — and a second copy of
32+
// the hash function is an integrity hazard: a future change to one
33+
// `compute_hash` would silently leave the other broken (#26). The
34+
// duplicate has been deleted; the canonical type is re-exported here so
35+
// `tier1::provenance::ProvenanceEntry` resolves to the one definition.
36+
pub use crate::abi::ProvenanceEntry;
8037

8138
// =========================================================================
8239
// SQLite sidecar schema

0 commit comments

Comments
 (0)