"""run_release_ci_gate_v2.py — RELEASE_CI_GATE_V2 P1-024: 100% 완료 정의를 코드로 강제하는 CI 게이트. schema → source_recheck → formula_coverage → golden → pass100 → execution_precedence → outcome_readiness → LLM_freedom 순서로 순차 검증. 하나라도 FAIL이면 release_ci_gate=BLOCK_DEPLOYMENT. """ from __future__ import annotations import json from datetime import datetime, timezone from pathlib import Path from typing import Any from v7_hardening_common import ROOT, TEMP, load_json, save_json DEFAULT_OUT = TEMP / "release_ci_gate_v2.json" def _f(v: Any, default: float = 0.0) -> float: try: return float(v) except Exception: return default def _check(step: str, condition: bool, actual: Any, target: str, source: str) -> dict[str, Any]: return { "step": step, "status": "PASS" if condition else "FAIL", "actual": actual, "target": target, "source": source, } def main() -> int: checks: list[dict[str, Any]] = [] # ── Step 1: Schema Validity ────────────────────────────────────────────── dq = load_json(TEMP / "data_quality_reconciliation_v1.json") schema_score = _f(dq.get("schema_presence_score")) checks.append(_check( "1_SCHEMA_VALIDITY", schema_score >= 99.0, schema_score, ">= 99", "data_quality_reconciliation_v1.json", )) # ── Step 2: Source Recheck (Single Truth) ──────────────────────────────── stl = load_json(TEMP / "single_truth_ledger_v2.json") conflict = int(stl.get("conflict_count") or 0) checks.append(_check( "2_SOURCE_RECHECK", conflict == 0, conflict, "== 0", "single_truth_ledger_v2.json", )) # ── Step 3: Formula Coverage ───────────────────────────────────────────── cov = load_json(TEMP / "harness_coverage_audit.json") true_missing = int(cov.get("true_missing_count") or 0) checks.append(_check( "3_FORMULA_COVERAGE", true_missing == 0, true_missing, "== 0", "harness_coverage_audit.json", )) # ── Step 4: Golden Test ────────────────────────────────────────────────── golden = load_json(TEMP / "formula_behavioral_coverage_v3.json") golden_fail = int(golden.get("failed_cases") or 0) checks.append(_check( "4_GOLDEN_TEST", golden_fail == 0, golden_fail, "failed_cases == 0", "formula_behavioral_coverage_v3.json", )) # ── Step 5: PASS_100 Active (v3) ───────────────────────────────────────── p100 = load_json(TEMP / "pass_100_criteria_v3.json") is_active = bool(p100.get("is_active")) checks.append(_check( "5_PASS_100_ACTIVE", is_active, is_active, "is_active == True", "pass_100_criteria_v3.json", )) # ── Step 6: Execution Precedence ───────────────────────────────────────── v4 = load_json(TEMP / "final_execution_decision_v4.json") audit_hts = str(v4.get("global_execution_gate") or "") == "AUDIT_ONLY" and int(v4.get("hts_order_count") or 0) == 0 hts_ready = str(v4.get("global_execution_gate") or "") == "HTS_READY" and int(v4.get("hts_order_count") or 0) > 0 precedence_ok = audit_hts or hts_ready checks.append(_check( "6_EXECUTION_PRECEDENCE", precedence_ok, f"gate={v4.get('global_execution_gate')},hts={v4.get('hts_order_count')}", "AUDIT_ONLY→hts=0 OR HTS_READY→hts>0", "final_execution_decision_v4.json", )) # ── Step 7: Outcome Readiness (honest) ─────────────────────────────────── truth = load_json(TEMP / "operational_truth_score_v1.json") truth_gate = str(truth.get("gate") or "") # readiness is expected to be WATCH_PENDING_SAMPLE until T+20 accumulates — not a hard block readiness_ok = truth_gate != "BLOCK_EXECUTION" checks.append(_check( "7_OUTCOME_READINESS", readiness_ok, truth_gate, "!= BLOCK_EXECUTION (WATCH acceptable)", "operational_truth_score_v1.json", )) # ── Step 8: LLM Freedom ────────────────────────────────────────────────── honesty = load_json(TEMP / "truthfulness_guard_v1.json") violations = int(honesty.get("contradiction_count") or 0) checks.append(_check( "8_LLM_FREEDOM", violations == 0, violations, "contradiction_count == 0", "truthfulness_guard_v1.json", )) # ── 결과 집계 ──────────────────────────────────────────────────────────── failed = [c for c in checks if c["status"] == "FAIL"] gate = "PASS" if not failed else "BLOCK_DEPLOYMENT" result = { "formula_id": "RELEASE_CI_GATE_V2", "gate": gate, "checks_total": len(checks), "checks_passed": len(checks) - len(failed), "checks_failed": len(failed), "failed_steps": [c["step"] for c in failed], "deploy_allowed": gate == "PASS", "checks": checks, "generated_at": datetime.now(timezone.utc).isoformat(), "source_path": "Temp/release_ci_gate_v2.json", } save_json(str(DEFAULT_OUT), result) summary = {k: v for k, v in result.items() if k != "checks"} print(json.dumps(summary, indent=2, ensure_ascii=True)) if gate == "PASS": print("RELEASE_CI_GATE_V2_PASS") else: print(f"RELEASE_CI_GATE_V2_BLOCK_DEPLOYMENT ({len(failed)} checks failed)") for c in failed: print(f" FAIL {c['step']}: actual={c['actual']} target={c['target']}") return 0 if gate == "PASS" else 1 if __name__ == "__main__": raise SystemExit(main())