From 7a2a49b435125dff7c9fcfc5e91917cfe4fd73ae Mon Sep 17 00:00:00 2001 From: Joseph Magly <1159087+jmagly@users.noreply.github.com> Date: Fri, 14 Aug 2026 21:15:38 -0400 Subject: [PATCH] test: constrain risk overlays to measured coverage --- CONTRIBUTING.md | 5 +++- ci/conditional-test-policy.json | 1 - ci/test-risk-map.json | 10 ++------ scripts/check_test_risk_map.py | 41 ++++++++++++++++++++++++++++++++- tests/test_test_risk_map.py | 30 ++++++++++++++++++++++++ 5 files changed, 76 insertions(+), 11 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b3c1c9f..34cd83c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -83,7 +83,10 @@ module under `obliteratus/`; every production file must belong to exactly one named contract surface with an owner, contract types, and existing test files. Adding a module without updating that graph fails `check_test_risk_map.py`. Targeted risk-module entries add stricter conditional-gate and critical-path -requirements on top of the exhaustive contract ownership layer. +requirements on top of the exhaustive contract ownership layer. They are +limited to the measured `obliteratus` coverage root so normalized CI evidence +can account for every targeted module; top-level `app.py` remains owned and is +exercised by its explicit operator-UI contract test. Each behavior-changing PR must add a focused regression or contract test, relevant negative/boundary coverage, and propagation/runtime evidence for diff --git a/ci/conditional-test-policy.json b/ci/conditional-test-policy.json index a50443f..8616da5 100644 --- a/ci/conditional-test-policy.json +++ b/ci/conditional-test-policy.json @@ -67,7 +67,6 @@ "prerequisites": "locked spaces extra; no public listener", "expected_cost": "under 10 runner-minutes", "coverage_paths": [ - "app.py", "obliteratus/interactive.py", "obliteratus/local_ui.py", "obliteratus/ui_watchtower.py" diff --git a/ci/test-risk-map.json b/ci/test-risk-map.json index f95e66f..2bbf2dc 100644 --- a/ci/test-risk-map.json +++ b/ci/test-risk-map.json @@ -2,7 +2,8 @@ "schema_version": 2, "owner": "OBLITERATUS maintainers", "source_inventory": { - "roots": ["app.py", "obliteratus"] + "roots": ["app.py", "obliteratus"], + "coverage_roots": ["obliteratus"] }, "contract_surfaces": [ { @@ -226,13 +227,6 @@ } ], "modules": [ - { - "path": "app.py", - "risk_class": "conditional-runtime", - "risk": "optional application construction, UI wiring, authentication, and listener safety", - "required_tests": ["tests/conditional/test_operator_ui.py"], - "conditional_gates": ["operator-ui"] - }, { "path": "obliteratus/cli.py", "risk_class": "cpu-contract", diff --git a/scripts/check_test_risk_map.py b/scripts/check_test_risk_map.py index dc14a86..c712300 100644 --- a/scripts/check_test_risk_map.py +++ b/scripts/check_test_risk_map.py @@ -117,6 +117,37 @@ def _inventory_sources( return sources +def _coverage_roots( + inventory: object, + *, + project_root: Path, + errors: list[str], +) -> tuple[str, ...]: + if not isinstance(inventory, dict): + return () + values = inventory.get("coverage_roots") + if not isinstance(values, list) or not values: + errors.append("test risk map source_inventory requires non-empty coverage_roots") + return () + if len(values) != len(set(map(str, values))): + errors.append("test risk map source_inventory has duplicate coverage_roots") + + roots: list[str] = [] + for value in values: + if not isinstance(value, str) or not value or Path(value).is_absolute(): + errors.append(f"source_inventory has invalid coverage root: {value!r}") + continue + if not (project_root / value).exists(): + errors.append(f"source_inventory coverage root does not exist: {value}") + continue + roots.append(value.rstrip("/")) + return tuple(roots) + + +def _is_in_coverage_scope(path: str, roots: tuple[str, ...]) -> bool: + return any(path == root or path.startswith(f"{root}/") for root in roots) + + def _validate_contract_surfaces( surfaces: object, *, @@ -208,8 +239,14 @@ def validate(risk_path: Path, quality_path: Path, conditional_path: Path) -> lis if not isinstance(risk.get("owner"), str) or not risk["owner"].strip(): errors.append("test risk map requires a non-empty owner") + source_inventory = risk.get("source_inventory") inventory_sources = _inventory_sources( - risk.get("source_inventory"), + source_inventory, + project_root=project_root, + errors=errors, + ) + coverage_roots = _coverage_roots( + source_inventory, project_root=project_root, errors=errors, ) @@ -247,6 +284,8 @@ def validate(risk_path: Path, quality_path: Path, conditional_path: Path) -> lis errors.append(f"risk module maps missing source path: {path}") if path not in source_contracts: errors.append(f"risk module is missing a contract owner: {path}") + if not _is_in_coverage_scope(path, coverage_roots): + errors.append(f"risk module is outside measured coverage roots: {path}") risk_class = module.get("risk_class") if not isinstance(risk_class, str) or risk_class not in RISK_CLASSES: errors.append(f"risk module {path} has invalid risk_class") diff --git a/tests/test_test_risk_map.py b/tests/test_test_risk_map.py index 34f8d7b..4802446 100644 --- a/tests/test_test_risk_map.py +++ b/tests/test_test_risk_map.py @@ -162,6 +162,12 @@ def test_risk_map_rejects_legacy_schema_and_invalid_inventory_roots(tmp_path): risk = json.loads((ROOT / "ci" / "test-risk-map.json").read_text()) risk["schema_version"] = 1 risk["source_inventory"]["roots"] = ["missing-source-root", "/absolute/source.py"] + risk["source_inventory"]["coverage_roots"] = [ + "obliteratus", + "obliteratus", + "missing-coverage-root", + "/absolute/coverage", + ] errors = check_test_risk_map.validate( _write(tmp_path / "risk.json", risk), @@ -172,6 +178,30 @@ def test_risk_map_rejects_legacy_schema_and_invalid_inventory_roots(tmp_path): assert "test risk map schema_version must be 2" in errors assert "source_inventory root does not exist: missing-source-root" in errors assert "source_inventory has invalid root: '/absolute/source.py'" in errors + assert "test risk map source_inventory has duplicate coverage_roots" in errors + assert "source_inventory coverage root does not exist: missing-coverage-root" in errors + assert "source_inventory has invalid coverage root: '/absolute/coverage'" in errors + + +def test_targeted_risk_module_must_be_inside_measured_coverage_roots(tmp_path): + risk = json.loads((ROOT / "ci" / "test-risk-map.json").read_text()) + risk["modules"].append( + { + "path": "app.py", + "risk_class": "conditional-runtime", + "risk": "optional application construction", + "required_tests": ["tests/conditional/test_operator_ui.py"], + "conditional_gates": [], + } + ) + + errors = check_test_risk_map.validate( + _write(tmp_path / "risk.json", risk), + ROOT / "ci" / "test-quality-policy.json", + ROOT / "ci" / "conditional-test-policy.json", + ) + + assert "risk module is outside measured coverage roots: app.py" in errors def test_loader_rejects_unreadable_and_nonobject_documents(tmp_path):