#!/usr/bin/env python3 """Validate per-file coverage against an explicit contract. Why: the repo is ratcheting from an overall coverage floor toward per-file coverage guarantees, and CI needs a machine-checkable list of the files that are in phase 1 of that ratchet. Inputs: - `coverage_json`: slipcover JSON artifact with per-file summaries. - `contract_json`: JSON object containing a `files` map of file paths to minimum coverage percentages. Output: - Exit 0 when every contracted file meets or exceeds its minimum. - Exit 1 with a concise report when any file falls short or is missing. """ from __future__ import annotations import argparse import json from pathlib import Path from typing import Any def _load_json(path: Path) -> dict[str, Any]: """Load a JSON file and return the parsed object.""" return json.loads(path.read_text(encoding="utf-8")) def _file_percent(entry: dict[str, Any]) -> float | None: summary = entry.get("summary") if isinstance(entry.get("summary"), dict) else {} percent = summary.get("percent_covered") return float(percent) if isinstance(percent, (int, float)) else None def check_coverage_contract(coverage_json: Path, contract_json: Path) -> int: """Check the supplied coverage artifact against the contract file.""" coverage = _load_json(coverage_json) contract = _load_json(contract_json) files = coverage.get("files") if isinstance(coverage.get("files"), dict) else {} contracted = contract.get("files") if isinstance(contract.get("files"), dict) else {} failures: list[str] = [] for path, minimum in sorted(contracted.items()): entry = files.get(path) if not isinstance(entry, dict): failures.append(f"{path}: missing from coverage report (minimum {minimum}%)") continue percent = _file_percent(entry) if percent is None: failures.append(f"{path}: no per-file percentage in coverage report") continue if percent < float(minimum): failures.append(f"{path}: {percent:.1f}% < {float(minimum):.1f}%") if failures: print("Coverage contract failed:") for failure in failures: print(f"- {failure}") return 1 print(f"Coverage contract passed for {len(contracted)} files.") return 0 def main() -> int: parser = argparse.ArgumentParser() parser.add_argument("coverage_json", type=Path) parser.add_argument("contract_json", type=Path) args = parser.parse_args() return check_coverage_contract(args.coverage_json, args.contract_json) if __name__ == "__main__": raise SystemExit(main())