Files
clawhdf5/research/03_provenance.md
T
Omar Sobh b08df7b628 clawmates: phase work
Mission: 01a00c41-bac0-7eb3-a8c8-8b7044f3086d
Phase: 01a00c41-bac2-71e3-a58b-c473421200ee

Committed by the ClawMates delivery pipeline from the agents' working tree. Authored by agents, not by the named committer.
2026-08-16 20:44:28 +00:00

5.5 KiB

Research: Provenance — clawhdf5

Scope: data lineage, source attribution, and tamper-evidence for both the low-level HDF5 format layer and the higher-level agent-memory / migration tools built on top of it.

Finding PR1 — SHINES provenance (SHA-256 + creator/timestamp/source) is fully built and tested, but zero production write paths use it

Location: crates/clawhdf5-format/src/provenance.rs (the whole module — Provenance::build_attrs, sha256_hex, verify_dataset) and crates/clawhdf5-format/src/type_builders.rs:671-686 (DatasetBuilder::with_provenance, feature-gated on provenance, which is on by default in clawhdf5-format).

Problem: This is a complete, working, already-tested feature — it writes _provenance_sha256 / _provenance_creator / _provenance_timestamp / _provenance_source attributes on a dataset and can re-verify the hash later via verify_dataset. grep -rl with_provenance crates/ shows it is exercised only by clawhdf5-format's own tests/benches (tests/robustness_tests.rs, tests/writer_h5py_tests.rs, benches/bench.rs). Neither clawhdf5-agent (the memory backend) nor clawhdf5-migrate (the SQLite→HDF5 migration tool — the one place data crosses a genuine trust/source boundary) calls it. Concretely, crates/clawhdf5-migrate/src/hdf5_writer.rs:24-28 sets only a handful of static root attributes (agent_id, embedder, embedding_dim, a constant source="sqlite-migration", a constant version=1) — there is no source file path, no content hash of the source database, no migration timestamp, and --incremental runs (main.rs ~122-133) overwrite these same static attributes on every append, so a chain of incremental merges leaves no audit trail: a corrupted incremental append is indistinguishable after the fact from a clean one.

Fix implemented (INT-03): Wired the existing SHINES provenance mechanism into the migration write path instead of inventing a new one:

  • clawhdf5-migrate/src/hdf5_writer.rs: the embeddings and text chunk datasets are now built with .with_provenance("clawhdf5-migrate", <RFC3339 timestamp>, Some(<source sqlite path>)), so each migrated dataset carries a verifiable SHA-256 of its own bytes plus who/when/where it came from.
  • clawhdf5-migrate/src/sqlite_reader.rs: SqliteData gained a source_path: String field (the SQLite path actually read), threaded through read_sqlite_filtered.
  • clawhdf5-migrate/src/main.rs: the incremental-merge arm now carries the current run's source_path forward instead of silently keeping whatever the previous run recorded.
  • clawhdf5-migrate/src/validate.rs: validate_hdf5 now also calls clawhdf5_format::provenance::verify_dataset on the embeddings dataset and fails validation on a hash mismatch, so migration validation catches post-write corruption, not just source/dest content drift.

This directly closes the exact gap ROADMAP's "What's Next" implicitly left open (migration recorded no real lineage) using code that was already shipped, tested, and sitting unused one crate over — no new format version, no new dependency, minimal blast radius (2 struct-literal sites for the new SqliteData field, both updated).

Finding PR2 — agent-level MemoryProvenance/AnomalyDetector are dead code on the real save path (ROADMAP claims Track 5 "complete")

Location: crates/clawhdf5-agent/src/lib.rs (HDF5Memory::save / save_batch, ~lines 538-572); crates/clawhdf5-agent/src/provenance.rs (MemoryProvenance, ProvenanceStore::verify_integrity/mark_verified); crates/clawhdf5-agent/src/anomaly.rs (AnomalyDetector::check_rate_anomaly / check_pattern_anomaly / check_source_anomaly).

Problem: ROADMAP.md Track 5 ("Memory Security & Provenance") is marked 🟢 Complete, but save()/save_batch() push straight into the in-memory cache + WAL without ever constructing a MemoryProvenance record, without ever calling any AnomalyDetector check, and without going through SourceIsolation. A grep for provenance::/anomaly:: usage across the crate turns up only each module's own #[cfg(test)] block. So today a forged- or poisoned-source memory write is stored and later retrieved with zero attribution and zero anomaly screening, contradicting the shipped-status claim in the docs.

Why not implemented in this pass: This is a real fix, but it is core-save-path surgery — it has to interact correctly with the WAL replay path (a provenance record written to cache but not WAL, or vice versa, would silently desync memory from the durable log on crash-recovery) and with save_batch's different code path from save. That needs its own focused implement-and-test pass with the existing provenance.rs/anomaly.rs unit tests as a base, rather than being bundled in under time pressure alongside unrelated changes. Tracked as INT-05 below.

Finding PR3 — nothing on the retrieval path ever calls verify_integrity

Location: crates/clawhdf5-agent/src/provenance.rs:128 (ProvenanceStore::verify_integrity), vs. search.rs/hybrid.rs (no callers).

Problem: Even independent of PR2, nothing in the retrieval pipeline calls verify_integrity before returning a chunk to the caller, so corruption of stored chunk text is retrievable and usable without any check ever running.

Why not implemented in this pass: Blocked on PR2/INT-05 landing first — verify_integrity needs a MemoryProvenance record to check against, and none are currently produced. Tracked as INT-06, sequenced after INT-05.