from __future__ import annotations import argparse import json from datetime import datetime, timezone from pathlib import Path from typing import Any ROOT = Path(__file__).resolve().parents[1] DEFAULT_OUT = ROOT / "Temp" / "artifact_freshness_gate_v1.json" DEFAULT_JSON = ROOT / "GatherTradingData.json" AUTHORITATIVE_ARTIFACTS = [ ROOT / "Temp" / "final_execution_decision_v2.json", ROOT / "Temp" / "single_truth_ledger_v1.json", ROOT / "Temp" / "operational_truth_score_v1.json", ROOT / "Temp" / "execution_readiness_matrix_v1.json", ROOT / "Temp" / "final_judgment_gate_v1.json", ROOT / "Temp" / "strategy_hardening_harness_v2.json", ROOT / "Temp" / "smart_cash_recovery_v6.json", ROOT / "Temp" / "yaml_gs_ps_coverage.json", ROOT / "Temp" / "harness_coverage_audit.json", ROOT / "Temp" / "algorithm_guidance_proof_v1.json", ] def _load(path: Path) -> dict[str, Any]: if not path.exists(): return {} try: obj = json.loads(path.read_text(encoding="utf-8")) except Exception: return {} return obj if isinstance(obj, dict) else {} def _hash(path: Path) -> str: import hashlib return hashlib.sha256(path.read_bytes()).hexdigest() def main() -> int: ap = argparse.ArgumentParser() ap.add_argument("--json", default=str(DEFAULT_JSON)) ap.add_argument("--out", default=str(DEFAULT_OUT)) ap.add_argument("--max-age-minutes", type=int, default=240) args = ap.parse_args() json_path = Path(args.json) if not json_path.is_absolute(): json_path = ROOT / json_path source_hash = _hash(json_path) if json_path.exists() else "MISSING" source_mtime = datetime.fromtimestamp(json_path.stat().st_mtime, tz=timezone.utc) if json_path.exists() else None stale_artifacts: list[dict[str, Any]] = [] artifact_rows: list[dict[str, Any]] = [] now = datetime.now(timezone.utc) for art in AUTHORITATIVE_ARTIFACTS: row = { "path": str(art.relative_to(ROOT)).replace("\\", "/"), "exists": art.exists(), } if art.exists(): row["sha256"] = _hash(art) row["age_minutes"] = round((now - datetime.fromtimestamp(art.stat().st_mtime, tz=timezone.utc)).total_seconds() / 60.0, 2) row["fresh"] = row["age_minutes"] <= args.max_age_minutes if not row["fresh"]: stale_artifacts.append(row) else: row["sha256"] = None row["age_minutes"] = None row["fresh"] = False stale_artifacts.append(row) artifact_rows.append(row) gate = "PASS" if not stale_artifacts else "STALE_BLOCK" result = { "formula_id": "ARTIFACT_FRESHNESS_GATE_V1", "gate": gate, "source_payload_hash": source_hash, "source_payload_mtime_utc": source_mtime.isoformat() if source_mtime else None, "max_age_minutes": args.max_age_minutes, "stale_artifact_count": len(stale_artifacts), "stale_artifacts": stale_artifacts, "artifacts": artifact_rows, } out_path = Path(args.out) if not out_path.is_absolute(): out_path = ROOT / out_path out_path.parent.mkdir(parents=True, exist_ok=True) out_path.write_text(json.dumps(result, ensure_ascii=False, indent=2), encoding="utf-8") print(json.dumps(result, ensure_ascii=False, indent=2)) return 0 if gate == "PASS" else 1 if __name__ == "__main__": raise SystemExit(main())