Files
hack-house/tests/test_sor_provenance.py
T
leetcrypt 7ed23704ea R2: run manifest / provenance writer for reproducible, auditable runs
write_manifest() freezes at circuit-experiment start everything needed to
reproduce and audit a SOR run and writes an immutable
output/sor-runs/<ts>/manifest.json: the R1 --sor-seed, topology/selector/churn
schedule id, one persona fingerprint per participating node, worktree git SHA,
isolated engine kind + image digest, pip/cargo dependency freeze, and
start/stop timestamps. The events.jsonl SHA-256 field is reserved for R3.

Provenance only: no forwarder, no engine spawned. Defense-in-depth to keep
containment load-bearing, the schema refuses to record a non-isolated
("local") engine at run or node level; the binding assertion still lands with
the R4 forwarder.

- cmd_chat/sor/provenance.py: Node/RunManifest, node_fingerprint (bit-for-bit
  mirror of persona.rs::fingerprint_of), git/deps capture, explicit
  dependency-free schema validator.
- hh/src/persona.rs: fingerprint_of known-vector parity test.
- tests: R2 acceptance predicate (manifest present + schema-valid, non-empty
  seed, >=1 fingerprint/node), immutability, containment rejection of local
  engines, and cross-language fingerprint parity.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-19 16:15:06 -07:00

112 lines
3.4 KiB
Python

"""R2 — provenance manifest acceptance checks.
Acceptance predicate (roadmap R2): a manifest is present + schema-valid after a
run, carries a non-empty seed, and >=1 persona fingerprint per participating
node. The fingerprint parity constant below is locked identically in
hh/src/persona.rs::tests::fingerprint_of_known_vector.
"""
import json
import pytest
from cmd_chat.sor.provenance import (
Node,
RunManifest,
node_fingerprint,
validate_manifest,
write_manifest,
)
# Known 32-byte pubkey (bytes 0..32) -> base64 -> fingerprint. Same vector as
# the Rust persona test.
KNOWN_PUB_B64 = "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8="
KNOWN_FINGERPRINT = "630dcd29"
def _fixture_manifest(root):
return RunManifest(
run_id="20260719T000000Z",
sor_seed=123456789,
topology="1-house",
selector="static",
churn_schedule_id="fixture-churn-0",
nodes=[
Node(role="host", persona_pub_b64=KNOWN_PUB_B64, engine="docker"),
Node(role="hop", persona_pub_b64="QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVphYmNk", engine="docker"),
],
engine_kind="docker",
engine_image_digest="sha256:deadbeef",
worktree_root=root,
)
def test_fingerprint_parity_with_rust():
assert node_fingerprint(KNOWN_PUB_B64) == KNOWN_FINGERPRINT
def test_manifest_written_and_schema_valid(tmp_path):
run_dir = tmp_path / "sor-runs" / "20260719T000000Z"
doc = write_manifest(run_dir, _fixture_manifest(tmp_path))
# present on disk
path = run_dir / "manifest.json"
assert path.is_file()
on_disk = json.loads(path.read_text())
assert on_disk == doc
# schema-valid, non-empty seed, >=1 fingerprint per node (the R2 predicate)
validate_manifest(on_disk)
assert on_disk["sor_seed"] == 123456789
assert len(on_disk["nodes"]) >= 1
for n in on_disk["nodes"]:
assert n["fingerprint"]
assert on_disk["nodes"][0]["fingerprint"] == KNOWN_FINGERPRINT
def test_manifest_is_immutable(tmp_path):
run_dir = tmp_path / "run"
write_manifest(run_dir, _fixture_manifest(tmp_path))
with pytest.raises(FileExistsError):
write_manifest(run_dir, _fixture_manifest(tmp_path))
def test_containment_rejects_local_engine(tmp_path):
m = _fixture_manifest(tmp_path)
m.engine_kind = "local"
with pytest.raises(ValueError):
write_manifest(tmp_path / "run2", m)
def test_containment_rejects_local_node_engine(tmp_path):
m = _fixture_manifest(tmp_path)
m.nodes[1].engine = "local"
with pytest.raises(ValueError):
write_manifest(tmp_path / "run3", m)
def test_invalid_pubkey_rejected():
with pytest.raises(ValueError):
node_fingerprint("!!! not base64 !!!")
def test_validator_catches_empty_nodes():
with pytest.raises(ValueError):
validate_manifest(
{
"schema_version": "sor-manifest/1",
"run_id": "x",
"created_utc": "t",
"sor_seed": 1,
"topology": "1-house",
"selector": "static",
"churn_schedule_id": "c",
"nodes": [],
"engine": {"kind": "docker", "image_digest": None},
"git": {},
"deps": {},
"timestamps": {"start_utc": "t", "stop_utc": None},
"events": {"path": "events.jsonl", "sha256": None},
}
)