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.
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: theembeddingsandtextchunk 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:SqliteDatagained asource_path: Stringfield (the SQLite path actually read), threaded throughread_sqlite_filtered.clawhdf5-migrate/src/main.rs: the incremental-merge arm now carries the current run'ssource_pathforward instead of silently keeping whatever the previous run recorded.clawhdf5-migrate/src/validate.rs:validate_hdf5now also callsclawhdf5_format::provenance::verify_dataseton 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.