R3: immutable append-only event log sealed into the run manifest
Adds the tamper-evident event stream that makes every DV auditable. Each
measurable moment is appended as one JSON object per line to an append-only
output/sor-runs/<ts>/events.jsonl over the closed vocabulary {consent_request,
consent_accept, consent_reject, circuit_build, hop_add, bridge_forward,
churn_kill, churn_spawn, rebuild_start, rebuild_done}; on close the file is
SHA-256'd and the digest is sealed exactly once into manifest.json (the single
sanctioned None->hash completion of the manifest).
Records carry only metadata (fingerprints, byte counts, latencies, decisions),
never message plaintext, so the zero-knowledge relay property is preserved. The
deterministic replay_fixture_circuit emits a seed-driven event stream with no
engine, socket, or traffic (containment-safe) and is the basis of
instrument-validation gate item 6.
- cmd_chat/sor/events.py: EventLog (append-only, per-record schema),
replay_fixture_circuit, replay_and_seal.
- cmd_chat/sor/provenance.py: seal_manifest (seal-once events SHA + stop ts).
- tests: replay -> schema-valid JSONL whose SHA matches the manifest;
append-only (original prefix byte-identical after further appends);
seed-determinism modulo timestamps; seal-once immutability.
Live emit wiring into the _sor handlers lands with R4/R5.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -172,6 +172,27 @@ class RunManifest:
|
||||
}
|
||||
|
||||
|
||||
def seal_manifest(
|
||||
run_dir: Path, events_sha256: str, stop_utc: Optional[str] = None
|
||||
) -> Dict[str, Any]:
|
||||
"""R3 close step: seal the closed events.jsonl SHA-256 (and stop timestamp)
|
||||
into an existing manifest.json exactly once.
|
||||
|
||||
This is the one sanctioned mutation of a manifest — a single None -> hash
|
||||
transition completing the document. Re-sealing an already-sealed manifest is
|
||||
refused, preserving artifact immutability."""
|
||||
run_dir = Path(run_dir)
|
||||
path = run_dir / "manifest.json"
|
||||
doc = json.loads(path.read_text())
|
||||
if doc.get("events", {}).get("sha256") is not None:
|
||||
raise ValueError("events already sealed (manifest is immutable)")
|
||||
doc["events"]["sha256"] = events_sha256
|
||||
doc["timestamps"]["stop_utc"] = stop_utc or _utc_now_iso()
|
||||
validate_manifest(doc)
|
||||
path.write_text(json.dumps(doc, indent=2, sort_keys=True) + "\n")
|
||||
return doc
|
||||
|
||||
|
||||
def write_manifest(run_dir: Path, manifest: RunManifest) -> Dict[str, Any]:
|
||||
"""Render `manifest` to `<run_dir>/manifest.json` and return the dict.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user