#!/usr/bin/env python3 """SBOM diff — ADR-002 build-time exclusion enforcement. Asserts that the **deployment** SBOM is a strict subset of the **research** SBOM and that the deployment SBOM does NOT contain components excluded for airborne builds (R02 enforcement: `vins_mono`, `salad`, `c11_tile_manager`). Bootstrap (AZ-263) ships the executable with a JSON-array contract so the CI step can validate even before the build pipeline emits real SBOMs. """ from __future__ import annotations import argparse import json import sys from pathlib import Path EXCLUDED_FROM_DEPLOYMENT = frozenset({"vins_mono", "salad", "c11_tile_manager"}) def _component_name(item: object) -> str: """Extract a component name from any of the accepted SBOM item shapes.""" if isinstance(item, str): return item if isinstance(item, dict): name = item.get("name") if isinstance(name, str) and name: return name # CycloneDX-style `purl` (e.g. `pkg:pypi/numpy@1.26.4`). purl = item.get("purl") if isinstance(purl, str) and "/" in purl: return purl.split("/", 1)[1].split("@", 1)[0] raise ValueError(f"Cannot extract component name from SBOM item: {item!r}") def _load_components(path: Path) -> set[str]: if not path.exists(): return set() data = json.loads(path.read_text()) if isinstance(data, list): return {_component_name(c) for c in data} if isinstance(data, dict) and "components" in data: components = data["components"] if isinstance(components, list): return {_component_name(c) for c in components} raise ValueError(f"Unrecognised SBOM shape in {path}") def main(argv: list[str] | None = None) -> int: parser = argparse.ArgumentParser(description="Deployment ⊂ Research SBOM diff (ADR-002).") parser.add_argument("--deployment", type=Path, required=True) parser.add_argument("--research", type=Path, required=True) args = parser.parse_args(argv) deployment = _load_components(args.deployment) research = _load_components(args.research) extras = deployment - research forbidden = deployment & EXCLUDED_FROM_DEPLOYMENT if extras: print( f"FAIL: deployment SBOM has components not in research: {sorted(extras)}", file=sys.stderr, ) if forbidden: print( f"FAIL: deployment SBOM contains forbidden components: {sorted(forbidden)}", file=sys.stderr, ) if extras or forbidden: return 1 print("OK: deployment ⊂ research and no R02-excluded components present.") return 0 if __name__ == "__main__": raise SystemExit(main())