#!/usr/bin/env python3 """Validate the source-to-test risk map and conditional coverage graph.""" from __future__ import annotations import argparse import json from pathlib import Path from typing import Any RISK_CLASSES = {"cpu-contract", "mixed-runtime", "conditional-runtime"} def _load_object(path: Path, label: str, errors: list[str]) -> dict[str, Any]: try: value = json.loads(path.read_text(encoding="utf-8")) except (OSError, json.JSONDecodeError) as exc: errors.append(f"cannot read {label}: {exc}") return {} if not isinstance(value, dict): errors.append(f"{label} root must be an object") return {} return value def validate(risk_path: Path, quality_path: Path, conditional_path: Path) -> list[str]: """Return structural and cross-policy failures for the risk map.""" errors: list[str] = [] risk = _load_object(risk_path, "test risk map", errors) quality = _load_object(quality_path, "quality policy", errors) conditional = _load_object(conditional_path, "conditional policy", errors) if errors: return errors if risk.get("schema_version") != 1: errors.append("test risk map schema_version must be 1") if not isinstance(risk.get("owner"), str) or not risk["owner"].strip(): errors.append("test risk map requires a non-empty owner") gates = conditional.get("gates") gate_by_id = { gate.get("id"): gate for gate in gates if isinstance(gate, dict) and isinstance(gate.get("id"), str) } if isinstance(gates, list) else {} modules = risk.get("modules") if not isinstance(modules, list) or not modules: return errors + ["test risk map modules must be a non-empty list"] module_by_path: dict[str, dict[str, Any]] = {} for index, module in enumerate(modules): label = f"risk module {index}" if not isinstance(module, dict): errors.append(f"{label} must be an object") continue path = module.get("path") if not isinstance(path, str) or not path: errors.append(f"{label} requires a non-empty path") continue if path in module_by_path: errors.append(f"duplicate risk module path: {path}") module_by_path[path] = module if not Path(path).is_file(): errors.append(f"risk module maps missing source path: {path}") if module.get("risk_class") not in RISK_CLASSES: errors.append(f"risk module {path} has invalid risk_class") if not isinstance(module.get("risk"), str) or not module["risk"].strip(): errors.append(f"risk module {path} requires a non-empty risk") tests = module.get("required_tests") if not isinstance(tests, list) or not tests: errors.append(f"risk module {path} requires at least one test") else: for test_path in tests: if not isinstance(test_path, str) or not test_path.startswith("tests/"): errors.append(f"risk module {path} has invalid test path: {test_path!r}") elif not Path(test_path).is_file(): errors.append(f"risk module {path} maps missing test path: {test_path}") module_gates = module.get("conditional_gates") if not isinstance(module_gates, list): errors.append(f"risk module {path} conditional_gates must be a list") continue if len(module_gates) != len(set(module_gates)): errors.append(f"risk module {path} has duplicate conditional gates") for gate_id in module_gates: gate = gate_by_id.get(gate_id) if gate is None: errors.append(f"risk module {path} references unknown gate {gate_id}") elif path not in gate.get("coverage_paths", []): errors.append(f"risk module {path} is not covered by gate {gate_id}") exclusions = quality.get("mature_cpu_scope", {}).get("exclusions", []) if not isinstance(exclusions, list): errors.append("quality policy exclusions must be a list") exclusions = [] for exclusion in exclusions: if not isinstance(exclusion, dict): errors.append("quality policy exclusion must be an object") continue path = exclusion.get("path") gate_id = exclusion.get("conditional_gate") module = module_by_path.get(path) if module is None: errors.append(f"CPU exclusion is missing from test risk map: {path}") elif gate_id not in module.get("conditional_gates", []): errors.append(f"CPU exclusion {path} is missing conditional gate {gate_id}") critical_paths = quality.get("critical_cpu_paths", []) if not isinstance(critical_paths, list): errors.append("quality policy critical_cpu_paths must be a list") critical_paths = [] for path in critical_paths: if path not in module_by_path: errors.append(f"critical CPU path is missing from test risk map: {path}") for gate_id, gate in gate_by_id.items(): for path in gate.get("coverage_paths", []): module = module_by_path.get(path) if module is None: errors.append(f"conditional path is missing from test risk map: {path}") elif gate_id not in module.get("conditional_gates", []): errors.append(f"risk module {path} is missing conditional gate {gate_id}") return errors def main() -> int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--risk-map", type=Path, default=Path("ci/test-risk-map.json")) parser.add_argument("--quality", type=Path, default=Path("ci/test-quality-policy.json")) parser.add_argument( "--conditional", type=Path, default=Path("ci/conditional-test-policy.json"), ) args = parser.parse_args() errors = validate(args.risk_map, args.quality, args.conditional) if errors: for error in errors: print(f"ERROR: {error}") return 1 print("test risk map: valid") return 0 if __name__ == "__main__": raise SystemExit(main())