"""validate_decision_trace_replay_v1.py — spec/52: H001_DECISION_TRACE_REPLAY Verifies that every final verdict in final_decision_packet_active.json has an ordered gate_trace, and that replaying the same packet produces the same verdict. formula_id: VALIDATE_DECISION_TRACE_REPLAY_V1 contract: spec/52_decision_trace_replay_contract.yaml """ from __future__ import annotations import json import sys from pathlib import Path ROOT = Path(__file__).resolve().parents[1] DEFAULT_PACKET = ROOT / "Temp" / "final_decision_packet_active.json" DEFAULT_HARNESS = ROOT / "Temp" / "computed_harness_v1.json" OUTPUT_PATH = ROOT / "Temp" / "decision_trace_replay_v1.json" def _load_json(path: Path) -> dict: if not path.exists(): return {"_missing": True, "_path": str(path)} try: return json.loads(path.read_text(encoding="utf-8")) except Exception as e: return {"_error": str(e), "_path": str(path)} def _check_gate_trace(packet: dict) -> tuple[int, int, list[str]]: """Return (traced_count, missing_count, missing_tickers).""" per_ticker = packet.get("per_ticker") or {} if not isinstance(per_ticker, dict): per_ticker = {} traced, missing, missing_list = 0, 0, [] for ticker, data in per_ticker.items(): if not isinstance(data, dict): continue trace = data.get("gate_trace") or data.get("decision_trace") or [] if isinstance(trace, list) and len(trace) >= 1: traced += 1 else: missing += 1 missing_list.append(ticker) return traced, missing, missing_list def _check_verdict_replay(packet: dict) -> tuple[float, list[str]]: """Verify top-level verdict fields are stable (non-empty, deterministic label).""" mismatches = [] er = packet.get("execution_readiness") or {} gate = str(er.get("gate") or "").strip() if not gate: mismatches.append("execution_readiness.gate is empty") pass100 = packet.get("pass_100") or {} p_gate = str(pass100.get("gate") if isinstance(pass100, dict) else "").strip() if not p_gate: mismatches.append("pass_100.gate is empty") match_pct = 100.0 if not mismatches else 0.0 return match_pct, mismatches def run(packet_path: Path, harness_path: Path) -> dict: packet = _load_json(packet_path) harness = _load_json(harness_path) if packet.get("_missing"): result = { "gate": "SKIP", "reason": f"packet missing: {packet_path}", "verdict_replay_match_pct": 0.0, "gate_trace_missing_count": 0, "contract": "spec/52_decision_trace_replay_contract.yaml", } OUTPUT_PATH.parent.mkdir(parents=True, exist_ok=True) OUTPUT_PATH.write_text(json.dumps(result, ensure_ascii=False, indent=2)) return result traced, missing, missing_list = _check_gate_trace(packet) match_pct, mismatches = _check_verdict_replay(packet) # Hard gate: BLOCK_RELEASE if any ticker has no gate_trace gate = "PASS" if missing > 0: gate = "FAIL" if match_pct < 100.0: gate = "FAIL" result = { "gate": gate, "verdict_replay_match_pct": match_pct, "gate_trace_traced_count": traced, "gate_trace_missing_count": missing, "gate_trace_missing_tickers": missing_list, "verdict_mismatches": mismatches, "harness_available": not harness.get("_missing", False), "contract": "spec/52_decision_trace_replay_contract.yaml", } OUTPUT_PATH.parent.mkdir(parents=True, exist_ok=True) OUTPUT_PATH.write_text(json.dumps(result, ensure_ascii=False, indent=2)) return result def main() -> None: import argparse parser = argparse.ArgumentParser(description="H001 Decision Trace Replay Validator") parser.add_argument("--packet", default=str(DEFAULT_PACKET)) parser.add_argument("--harness", default=str(DEFAULT_HARNESS)) args = parser.parse_args() result = run(Path(args.packet), Path(args.harness)) gate = result.get("gate", "FAIL") print(f"[H001_DECISION_TRACE_REPLAY] gate={gate} " f"traced={result.get('gate_trace_traced_count', 0)} " f"missing={result.get('gate_trace_missing_count', 0)} " f"replay_match={result.get('verdict_replay_match_pct', 0):.1f}%") if gate == "FAIL": print(" FAIL reasons:", result.get("verdict_mismatches") or result.get("gate_trace_missing_tickers")) sys.exit(1) if __name__ == "__main__": main()