"""Check the scope passport against canonical applicability and WEB-002 limits. Document-only experiment; no product runtime or provider connection is simulated. """ import json import re from pathlib import Path ROOT = Path(__file__).resolve().parents[2] ID = re.compile(r"\| ((?:[A-Z]+-)+\d{3}) \| (.+?) \| (.+?) \|") STATES = {"foreground", "hidden", "closed-tab", "browser-terminated", "permission-denied", "offline", "logged-out"} def check(document, requirements): excluded = {r["id"] for r in requirements if r["applicability"] == "out_of_scope"} rows = ID.findall(document) assert len(rows) == len({r[0] for r in rows}), "Duplicate excluded ID" assert {r[0] for r in rows} == excluded, "Excluded coverage differs from canonical registry" for rid, reason, retained in rows: assert len(reason.strip()) >= 40, f"Missing substantive reason: {rid}" assert len(retained.strip()) >= 30, f"Missing Web boundary: {rid}" entry = next(r for r in requirements if r["id"] == rid) assert f"**{rid}.**" in (ROOT / entry["source"]).read_text(), rid lifecycle = {} for line in document.splitlines(): cells = [c.strip() for c in line.split("|")[1:-1]] if cells and cells[0] in STATES: assert len(cells) == 5 and cells[0] not in lifecycle, "Malformed lifecycle row" lifecycle[cells[0]] = cells[1:] assert set(lifecycle) == STATES, "Incomplete lifecycle scenarios" assert all(v in {"conditional", "none", "not-guaranteed"} for row in lifecycle.values() for v in row), "Unsupported guarantee" for state in {"closed-tab", "browser-terminated", "offline", "logged-out"}: row = lifecycle[state] assert row[0] == "none" and row[2:] == ["none", "none"], f"False session/media promise: {state}" assert lifecycle["hidden"][0] == "not-guaranteed", "Hidden socket continuity promise" assert lifecycle["closed-tab"][1] == "conditional", "Push confused with session or guaranteed delivery" assert lifecycle["permission-denied"][1:] == ["none"] * 3, "Denied permissions bypassed" assert lifecycle["logged-out"] == ["none"] * 4, "Logout reactivation promise" return len(excluded), len(lifecycle) def main(): document = (ROOT / "docs/decisions/0000-scope.md").read_text() requirements = json.loads((ROOT / "planning/requirements.json").read_text()) counts = check(document, requirements) mutations = [ re.sub(r"^\| ACC-003 \|.*\n", "", document, flags=re.M), re.sub(r"^\| ACC-003 \|.*$", "| ACC-003 | | |", document, flags=re.M), document.replace("| closed-tab | none | conditional | none | none |", "| closed-tab | conditional | conditional | none | none |"), document.replace("| closed-tab | none | conditional | none | none |", "| closed-tab | none | conditional | conditional | none |"), ] for index, mutant in enumerate(mutations, 1): assert mutant != document, "Negative fixture did not mutate document" try: check(mutant, requirements) except AssertionError: continue raise AssertionError(f"False passport accepted: mutation {index}") print(f"PASS: {counts[0]} excluded IDs match registry/specs with reasons; " f"{counts[1]} lifecycle scenarios; {len(mutations)} negative mutations rejected.") print("LIMIT: document validation only; browser/provider behavior is untested.") if __name__ == "__main__": main()