cog-ha-matter (ADR-116 P4): witness file persistence + chain-level verify

Closes the witness audit-bundle surface. The hash-chain primitive
+ JSONL serializer from earlier iters only handled one event at a
time; this lands the file-stream surface that operations actually
need:

  * `WitnessChain::write_jsonl(&mut impl Write) -> io::Result<()>`
    — streams every event as one line + `\n`, empty chain writes
    zero bytes
  * `WitnessChain::read_jsonl(impl BufRead) -> Result<WitnessChain,
    WitnessReadError>` — parses event-by-event AND runs chain-level
    `verify()` on the loaded chain, catching reordered or replayed
    prefixes that per-event hashing alone misses

Critical security property: `read_jsonl` calls `WitnessChain::verify`
on the loaded chain BEFORE returning Ok. A forged bundle assembled
from two valid chains pasted together would slip past the
per-event hash check (each event's `this_hash` is internally
consistent) but the cross-event `prev_hash` linkage detects the
seam. Test `read_jsonl_chain_verify_catches_reordered_events`
locks this — swap two events in a 2-event bundle, see Verify error.

Error surface (new `WitnessReadError` enum):
  * `Io { line_no, msg }`           — read failure mid-stream
  * `Parse { line_no, source }`     — per-event from_jsonl_line failure
  * `Verify { source }`             — chain-level verify failure

`line_no` is 1-indexed so an auditor sees the same number their
text editor shows. Blank lines tolerated for hand-edited bundles.

7 new tests:
  * empty chain writes zero bytes
  * write→read round-trips a 3-event chain
  * exactly N newlines for N events; trailing newline present
  * blank lines / leading newline tolerated
  * parse error surfaces with correct line_no
  * reordered events caught by chain-level verify
  * no-trailing-newline still loads the final event

51/51 cog tests green (44 → 51).

Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
ruv
2026-05-23 18:19:05 -04:00
parent a3478ea3b5
commit 1f5b7b48c9
2 changed files with 181 additions and 1 deletions
+1 -1
View File
@@ -95,7 +95,7 @@ Ranked by build cost × user impact:
| **P1** | Research dossier ([`docs/research/ADR-116-ha-matter-cog-research.md`](../research/ADR-116-ha-matter-cog-research.md)) | ✅ **done** — 8 sections, 30+ citations, v1 scope ranked |
| **P2** | Cog crate scaffold (`v2/crates/cog-ha-matter/`) — Cargo.toml + `src/{lib,main,manifest}.rs`, workspace member, CLI args, `--print-manifest` flag, 2 manifest unit tests | ✅ **done**`cargo check` + `cargo test` green |
| **P3** | Wrap existing ADR-115 MQTT publisher as cog entry point | ✅ **wiring done**`main.rs` boots ADR-115's `publisher::spawn` via `runtime::spawn_publisher` thin wrapper, holds a long-lived `broadcast::Sender<VitalsSnapshot>`, awaits Ctrl-C. Live-handle test green without a broker. Next (P3.5): subscribe to sensing-server `/v1/snapshot` WS and republish into the channel. |
| **P4** | Seed-native enhancements (embedded broker, mDNS, witness) | in progress — (a) mDNS service-record builder shipped. (b) Witness hash-chain primitive shipped (append-only SHA-256, `verify()` catches tampering). (c) **Witness JSONL persistence shipped**`WitnessEvent::{to,from}_jsonl_line` round-trips with alphabetical field order for byte-stable archival hashes; parser re-verifies stored `this_hash` against canonical bytes so tampered bundles fire `HashMismatch` before loading. (d) Responder (mdns-sd) + embedded rumqttd + Ed25519 signing layer still pending. |
| **P4** | Seed-native enhancements (embedded broker, mDNS, witness) | in progress — (a) mDNS service-record builder. (b) Witness hash-chain primitive. (c) Witness JSONL line serializer. (d) **Witness file persistence shipped**`WitnessChain::{write_jsonl, read_jsonl}` accept any `Write`/`BufRead`, tolerate blank lines, surface `line_no` on parse error, run chain-level `verify()` on load to catch reordered/replayed events. 7 new tests including reorder-detection. (e) Responder (mdns-sd) + embedded rumqttd + Ed25519 signing layer still pending. |
| **P5** | RuVector-backed threshold learning (SONA adaptation) | pending |
| **P6** | Multi-Seed federation (cross-Seed dedup + witness) | pending |
| **P7** | Matter Bridge mode (depends on matter-rs / esp-matter readiness) | pending |