"""Status-only checker for the evidence summary_report.md. Read-only: does NOT modify the summary and never marks anything PASS. It reads the file (and, when available, checks whether the Gemini output image exists on disk) and reports a lifecycle-aware status: summary_status_check: OK | WARN | WARN_WITH_FAILURE_EVIDENCE | FAIL Accepted (non-fatal) Gemini real-call states, all with Pilot Ready NOT CONFIRMED: - gemini_real_call: NOT RUN (pre-call; human QA PENDING) - gemini_real_call: RUN_FAILED - ... (attempted+failed) -> WARN_WITH_FAILURE_EVIDENCE - gemini_real_call: RUN - output generated (success; output image must exist) FAIL (unsafe / inconsistent): - Pilot Ready: CONFIRMED - single-device smoke: PASS - human QA PASS while real call is NOT RUN or RUN_FAILED - gemini_real_call RUN but output image missing - gemini_output_image PRESENT but the actual file is missing - "actual Gemini result verified" claimed but no output image - an API-key-like string exposed in the summary """ from __future__ import annotations import re import sys from pathlib import Path DEFAULT_SUMMARY = Path( r"C:\Users\Admin\Documents\헤어\field-test-evidence" r"\v0.1.0-staging\2026-05-31-single-device-smoke-01\summary_report.md" ) _SEVERITY = {"OK": 0, "WARN": 1, "WARN_WITH_FAILURE_EVIDENCE": 2, "FAIL": 3} _API_KEY_RE = re.compile(r"AIza[0-9A-Za-z_\-]{20,}") def _worst(a: str, b: str) -> str: return a if _SEVERITY[a] >= _SEVERITY[b] else b def _values(text: str, key: str) -> list[str]: prefix = key.lower() + ":" out = [] for line in text.splitlines(): stripped = line.strip().lstrip("-* ").strip() if stripped.lower().startswith(prefix): out.append(stripped.split(":", 1)[1].strip()) return out def _first(text: str, key: str) -> str | None: vals = _values(text, key) return vals[0] if vals else None def _find_api_key_like(text: str) -> str | None: return "AIza...(redacted)" if _API_KEY_RE.search(text) else None def _output_png_exists(summary_path: Path) -> bool: out = summary_path.parent / "gemini-smoke" / "output" return out.exists() and any(out.glob("gemini-output-*.png")) def check_summary(path: Path | str = DEFAULT_SUMMARY) -> dict: path = Path(path) if not path.exists(): return {"result": "FAIL", "reasons": [f"FAIL: summary not found: {path}"], "observed": {}} text = path.read_text(encoding="utf-8", errors="replace") output_png_exists = _output_png_exists(path) result = "OK" reasons: list[str] = [] real_call = _first(text, "gemini_real_call") human_qa = _first(text, "gemini_human_visual_qa") output_line = _first(text, "gemini_output_image") local_storage = _first(text, "localStorage_check") rc = (real_call or "").strip().upper() hq = (human_qa or "").strip().upper() oi = (output_line or "").strip().upper() is_not_run = rc.startswith("NOT RUN") is_run_failed = rc.startswith("RUN_FAILED") is_run = rc.startswith("RUN") and not is_run_failed hq_pass = hq.startswith("PASS") or "CONFIRMED" in hq output_present_claim = oi.startswith("PRESENT") output_missing_claim = oi.startswith("MISSING") # --- always-FAIL danger rules ------------------------------------- pilot = _values(text, "Pilot Ready") if not pilot: result = _worst(result, "WARN") reasons.append("WARN: Pilot Ready line not found") for val in pilot: u = val.upper() if "CONFIRMED" in u and "NOT CONFIRMED" not in u: result = _worst(result, "FAIL") reasons.append(f"FAIL: Pilot Ready is '{val}' (must be NOT CONFIRMED)") for val in _values(text, "single-device smoke"): if val.upper().startswith("PASS"): result = _worst(result, "FAIL") reasons.append("FAIL: single-device smoke is marked PASS") if _find_api_key_like(text): result = _worst(result, "FAIL") reasons.append("FAIL: possible API key value exposed in summary: AIza...(redacted)") if output_present_claim and not output_png_exists: result = _worst(result, "FAIL") reasons.append("FAIL: gemini_output_image PRESENT but no output file found on disk") if "actual gemini result verified" in text.lower() and not output_png_exists: result = _worst(result, "FAIL") reasons.append("FAIL: 'actual Gemini result verified' claimed but no output image") # --- gemini_real_call lifecycle ----------------------------------- if real_call is None: result = _worst(result, "WARN") reasons.append("WARN: gemini_real_call line not found") elif is_not_run: if hq_pass: result = _worst(result, "FAIL") reasons.append("FAIL: human QA PASS while gemini_real_call is NOT RUN") elif is_run_failed: if hq_pass: result = _worst(result, "FAIL") reasons.append("FAIL: human QA PASS while gemini_real_call is RUN_FAILED") else: result = _worst(result, "WARN_WITH_FAILURE_EVIDENCE") reasons.append("INFO: gemini_real_call RUN_FAILED - failure evidence recorded (not a safety violation)") elif is_run: if output_missing_claim or not output_png_exists: result = _worst(result, "FAIL") reasons.append("FAIL: gemini_real_call RUN but output image is missing") else: result = _worst(result, "WARN") reasons.append(f"WARN: unrecognized gemini_real_call value '{real_call}'") if human_qa is None: result = _worst(result, "WARN") reasons.append("WARN: gemini_human_visual_qa line not found") if local_storage is None: result = _worst(result, "WARN") reasons.append("WARN: localStorage_check line not found") elif not local_storage.upper().startswith("PASS"): result = _worst(result, "WARN") reasons.append(f"WARN: localStorage_check is '{local_storage}' (expected PASS)") observed = { "gemini_real_call": real_call, "gemini_human_visual_qa": human_qa, "gemini_output_image": output_line, "output_png_on_disk": output_png_exists, "localStorage_check": local_storage, "pilot_ready": pilot[0] if pilot else None, } return {"result": result, "reasons": reasons, "observed": observed} def main(argv: list[str] | None = None) -> int: argv = list(sys.argv[1:] if argv is None else argv) path = argv[0] if argv else DEFAULT_SUMMARY report = check_summary(path) print(f"summary: {path}") if report["observed"]: print("observed:") for k, v in report["observed"].items(): print(f" {k}: {v}") if report["reasons"]: print("notes:") for r in report["reasons"]: print(f" - {r}") print(f"summary_status_check: {report['result']}") return 1 if report["result"] == "FAIL" else 0 if __name__ == "__main__": sys.exit(main())