coulomb-social/tests/usecases/registry.py
tegwick a1989f19db
Some checks failed
Use-case regression gate / usecase-regressions (push) Failing after 6m2s
Establish use-case regression collection and real browser gate
Assistant: codex
Assistant-Model: gpt-6.1-sol
Assistant-Session: 01a10e2d-a5ef-7e92-b03d-b1694e915680
2026-10-06 14:02:01 +02:00

68 lines
3.4 KiB
Python

"""Versioned journey registry and mandatory collected-test coverage validation."""
import json
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
REGISTRY = ROOT / "docs/usecases/registry.json"
def load_cases():
data = json.loads(REGISTRY.read_text(encoding="utf-8"))
if data.get("schema_version") != 1 or not isinstance(data.get("cases"), list):
raise ValueError("Unsupported use-case registry schema")
cases = data["cases"]
if not cases:
raise ValueError("The use-case registry cannot be empty")
ids = set()
for case in cases:
uid = case["id"]
if uid in ids:
raise ValueError(f"Duplicate use-case id: {uid}")
ids.add(uid)
if case["confirmation"] not in {"inferred", "observed", "user-confirmed"}:
raise ValueError(f"Unknown confirmation for {uid}")
if case["transfer_status"] not in {"pending-user-review", "accepted", "deferred"}:
raise ValueError(f"Unknown transfer status for {uid}")
if case["transfer_status"] == "accepted" and not case.get("acceptance_ref"):
raise ValueError(f"Accepted case {uid} needs a user acceptance reference")
for field in ("narrative", "fixture"):
path = (ROOT / case[field]).resolve()
if not path.is_relative_to(ROOT) or not path.is_file():
raise ValueError(f"Missing or outside-repo {field} for {uid}")
fixture = json.loads((ROOT / case["fixture"]).read_text(encoding="utf-8"))
if fixture.get("synthetic") is not True or fixture.get("usecase_id") != uid:
raise ValueError(f"Fixture must be synthetic and linked to {uid}")
if not isinstance(case["established"], bool):
raise ValueError(f"established must be a boolean for {uid}")
if set(case["tests"]) != {"fast", "browser"}:
raise ValueError(f"Expected fast and browser test layers for {uid}")
if case["established"]:
if case["confirmation"] != "user-confirmed" or not case["contract"]:
raise ValueError(f"Established case {uid} needs user confirmation and contract")
for layer in ("fast", "browser"):
if not case["tests"].get(layer):
raise ValueError(f"Established case {uid} missing {layer} coverage")
return cases
def validate_coverage(cases, collected):
"""Require exact node IDs, UC marker, and correct fast/browser layer."""
ids = {case["id"] for case in cases}
for nodeid, markers in collected.items():
if markers["usecases"] - ids:
raise ValueError(f"Use-case test has an unregistered ID: {nodeid}")
for case in cases:
if not case["established"]:
continue
for layer, nodeids in case["tests"].items():
for nodeid in nodeids:
markers = collected.get(nodeid)
if markers is None:
raise ValueError(f"{case['id']} missing collected {layer} test: {nodeid}")
if case["id"] not in markers["usecases"]:
raise ValueError(f"{nodeid} is not marked for {case['id']}")
if markers.get("disabled"):
raise ValueError(f"Protected use-case test cannot skip/xfail: {nodeid}")
if markers["browser"] != (layer == "browser"):
raise ValueError(f"Wrong regression layer for {nodeid}")