#!/usr/bin/env python3 from __future__ import annotations import argparse import json from pathlib import Path from typing import Any SCHEMA = "navsea.cm95.s52-runtime-capability-audit/v1" def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser( description=( "Audit runtime portrayal kinds required by a release-scoped " "S-52 style usage audit against a concrete target capability registry." ) ) parser.add_argument("--plan", required=True) parser.add_argument("--style-audit", required=True) parser.add_argument("--capabilities", required=True) parser.add_argument("--output", required=True) parser.add_argument( "--required-kind", action="append", default=[], help=( "Optional explicit runtime kind. When omitted, every non-zero " "runtime_coverage kind in the style audit is required." ), ) return parser.parse_args() def load_json(path: Path) -> dict[str, Any]: obj = json.loads(path.read_text(encoding="utf-8")) if not isinstance(obj, dict): raise SystemExit(f"JSON root must be object: {path}") return obj def verify_evidence( project_root: Path, kind: str, capability: dict[str, Any], ) -> list[dict[str, Any]]: failures: list[dict[str, Any]] = [] evidence = capability.get("evidence") or [] if not evidence: failures.append( { "kind": kind, "failure": "implemented_capability_has_no_evidence", } ) return failures for item in evidence: if not isinstance(item, dict): failures.append( { "kind": kind, "failure": "evidence_entry_not_object", } ) continue raw_path = item.get("path") if not raw_path: failures.append( { "kind": kind, "failure": "evidence_path_missing", } ) continue path = (project_root / str(raw_path)).resolve() if not path.is_file(): failures.append( { "kind": kind, "path": str(raw_path), "failure": "evidence_file_missing", } ) continue try: text = path.read_text(encoding="utf-8") except Exception as exc: failures.append( { "kind": kind, "path": str(raw_path), "failure": f"evidence_read_error:{type(exc).__name__}", } ) continue for marker in item.get("contains") or []: if str(marker) not in text: failures.append( { "kind": kind, "path": str(raw_path), "marker": str(marker), "failure": "evidence_marker_missing", } ) return failures def main() -> None: args = parse_args() plan_path = Path(args.plan) style_audit_path = Path(args.style_audit) capability_path = Path(args.capabilities) plan = load_json(plan_path) style_audit = load_json(style_audit_path) registry = load_json(capability_path) if registry.get("schema") != ( "navsea.cm95.s52-runtime-capability-registry/v1" ): raise SystemExit("runtime capability registry schema mismatch") runtime_coverage = style_audit.get("runtime_coverage") or {} observed = { str(kind): int((row or {}).get("feature_count") or 0) for kind, row in runtime_coverage.items() if int((row or {}).get("feature_count") or 0) > 0 } if args.required_kind: required = sorted(set(str(item) for item in args.required_kind)) else: required = sorted(observed) capabilities = registry.get("capabilities") or {} missing: list[str] = [] noncompliant: list[dict[str, Any]] = [] evidence_failures: list[dict[str, Any]] = [] checked: list[dict[str, Any]] = [] project_root = Path(__file__).resolve().parents[1] for kind in required: capability = capabilities.get(kind) if not isinstance(capability, dict): missing.append(kind) continue status = str(capability.get("status") or "") checked.append( { "kind": kind, "feature_count": int(observed.get(kind, 0)), "status": status, "fidelity": capability.get("fidelity"), } ) if status != "IMPLEMENTED": noncompliant.append( { "kind": kind, "status": status, "fidelity": capability.get("fidelity"), } ) continue evidence_failures.extend( verify_evidence(project_root, kind, capability) ) failures: list[str] = [] if style_audit.get("hard_gate_failures"): failures.append("style_audit_hard_gate_failures") if missing: failures.append("runtime_capability_missing") if noncompliant: failures.append("runtime_capability_not_implemented") if evidence_failures: failures.append("runtime_capability_evidence_failure") result = { "schema": SCHEMA, "status": "PASS" if not failures else "FAIL", "plan": str(plan_path), "plan_id": plan.get("plan_id"), "style_audit": str(style_audit_path), "capability_registry": str(capability_path), "target": registry.get("target"), "observed_runtime_kinds": observed, "required_runtime_kinds": required, "checked_runtime_kinds": checked, "missing_runtime_kinds": missing, "noncompliant_runtime_kinds": noncompliant, "evidence_failure_count": len(evidence_failures), "evidence_failures": evidence_failures[:100], "failures": failures, } output = Path(args.output) output.parent.mkdir(parents=True, exist_ok=True) output.write_text( json.dumps(result, ensure_ascii=False, indent=2), encoding="utf-8", ) print(json.dumps(result, ensure_ascii=False, indent=2)) if result["status"] != "PASS": raise SystemExit(2) if __name__ == "__main__": main()