test: constrain risk overlays to measured coverage

This commit is contained in:
Joseph Magly
2026-08-14 21:19:38 -04:00
parent b4c69a468b
commit 7a2a49b435
5 changed files with 76 additions and 11 deletions
+4 -1
View File
@@ -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
-1
View File
@@ -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"
+2 -8
View File
@@ -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",
+40 -1
View File
@@ -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")
+30
View File
@@ -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):