forked from chiki2bum2/SniperGold_ML
77 lines
2.7 KiB
Python
77 lines
2.7 KiB
Python
"""Run completion protocol (Layer 16, spec 22, RC_V1).
| |||
| |||
RUN_COMPLETE.json is produced ONLY after all chunks are committed, the
| |||
aggregator is flushed, evidence is computed, and the independent verifier
| |||
returned ACCEPTED. It contains no self-referential hashes; machine
| |||
verification re-checks every hash and file from disk.
| |||
"""
| |||
| |||
import os
| |||
import json
| |||
| |||
from . import versions
| |||
from .util import atomic_write_json
| |||
| |||
| |||
def build_run_complete(out_root, ckpt, evidence, verifier_report_path,
| |||
verifier_verdict, layer_hashes, outputs):
| |||
payload = {
| |||
"schema_version": versions.RUN_COMPLETE_SCHEMA_VERSION,
| |||
"status": "COMPLETED",
| |||
"run_id": ckpt["run_id"],
| |||
"source_identity": ckpt["source_identity"],
| |||
"versions": ckpt["versions"],
| |||
"rows": {
| |||
"parsed": ckpt["cumulative"]["rows_parsed"],
| |||
"canonical": ckpt["cumulative"]["rows_canonical"],
| |||
"bytes_read": ckpt["cumulative"]["bytes_read"],
| |||
"malformed_total": sum(ckpt["cumulative"]["malformed"].values()),
| |||
},
| |||
"malformed": ckpt["cumulative"]["malformed"],
| |||
"outputs": outputs,
| |||
"checkpoint": {
| |||
"last_completed_chunk": ckpt["last_completed_chunk"],
| |||
"next_chunk": ckpt["next_chunk"],
| |||
"status": ckpt["status"],
| |||
},
| |||
"hashes": {
| |||
"evidence_manifest_hash": evidence.get("evidence_manifest_hash"),
| |||
"layer_hashes": layer_hashes,
| |||
},
| |||
"verifier_report": verifier_report_path,
| |||
"verifier_verdict": verifier_verdict,
| |||
"completion_ts_utc": _utc_now_iso(),
| |||
}
| |||
atomic_write_json(_run_complete_of(out_root), payload)
| |||
return payload
| |||
| |||
| |||
def _run_complete_of(out_root):
| |||
return os.path.join(out_root, "RUN_COMPLETE.json")
| |||
| |||
| |||
def load_run_complete(out_root):
| |||
with open(_run_complete_of(out_root), "r", encoding="utf-8") as fh:
| |||
return json.load(fh)
| |||
| |||
| |||
def verify_run_complete(out_root, evidence_payload):
| |||
"""Machine re-check (spec 22): status COMPLETED and the recorded evidence
| |||
manifest hash must equal the on-disk evidence manifest hash. Returns
| |||
(ok, diffs)."""
| |||
try:
| |||
rc = load_run_complete(out_root)
| |||
except Exception as e:
| |||
return False, ["RUN_COMPLETE.json unreadable: %s" % e]
| |||
diffs = []
| |||
if rc.get("status") != "COMPLETED":
| |||
diffs.append("status != COMPLETED")
| |||
if rc.get("hashes", {}).get("evidence_manifest_hash") != \
| |||
evidence_payload.get("evidence_manifest_hash"):
| |||
diffs.append("evidence_manifest_hash mismatch")
| |||
return (not diffs), diffs
| |||
| |||
| |||
def _utc_now_iso():
| |||
import datetime
| |||
return datetime.datetime.now(datetime.timezone.utc).isoformat().replace("+00:00", "Z")
|