| """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")
|
|
|
|
|
| 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")
|
|
|
|
|
| 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())
|
|
|