Enforce the four unenforced reviewer checks with guard tests

The reviewer skill recorded four deterministic checks as unenforced or partial. Add tests so they fail the build instead of relying on a reviewer noticing.

tests/test_model_contract_guards.py:
- Status vocabulary: flags string literals compared against or assigned to status/purpose attributes, plus a narrower sweep that requires every status-valued literal in the package to be a known non-status use.
- Relationship loading: every Relationship must declare lazy='raise' except documented exceptions, and the exception set must match the Relationship Loading Contract in docs/schema.md.
- Schema fidelity: the Field-Accurate Table Contracts tables must match db/models.py on table coverage, field names, and declaration order, and the Authoritative Enumerations section must match the enum members.

tests/test_orphan_sweep.py:
- Locks the set of unreferenced public definitions. Route handlers registered by decorator are exempt, string entrypoint references count, and tests/ and tools/ count as consumers. KNOWN_ORPHANS records the four current orphans with rationale; a new one fails the build.

Each guard was mutation-tested: reverting the fix below, dropping a documented field, widening a lazy strategy, and adding a stranded function each fail their respective test.

Also fix the one violation the status guard found: sources_page.py compared attempt.status.value to the literal 'transcribed' instead of JobSourceStatus.TRANSCRIBED, which would survive an enum rename.

Co-authored-by: Copilot App <[email protected]>
This commit is contained in:
Jim Lancaster
2026-08-23 16:35:48 -05:00
co-authored by Copilot App
parent 5566f48fc0
commit c6ed3126e0
4 changed files with 429 additions and 5 deletions
+278
View File
@@ -0,0 +1,278 @@
"""Deterministic guards for the model and persistence contracts.
These cover three checks that `.github/skills/python-code-reviewer/skill.md` requires
on every review but that previously had no automated enforcement:
* **Status vocabulary conformance** - status comparisons and assignments must use the
`JobStatus` / `JobSourceStatus` / `JobPurpose` enums rather than string literals.
* **Relationship loading contract** - relationships declare ``lazy="raise"`` so read
paths must eager-load explicitly, and the documented exceptions in `docs/schema.md`
must match the code exactly.
* **Schema contract fidelity** - the "Field-Accurate Table Contracts" tables in
`docs/schema.md` must list exactly the fields each SQLModel table declares.
See `docs/requirements.md` (REQ-4-102), `docs/schema.md` ("Relationship Loading
Contract"), and `.github/instructions/services.instructions.md`.
"""
from __future__ import annotations
import ast
import re
from pathlib import Path
from sqlmodel import SQLModel
from transcription.db import models as models_module
from transcription.db.models import JobPurpose
from transcription.db.models import JobSourceStatus
from transcription.db.models import JobStatus
PROJECT_ROOT = Path(__file__).resolve().parents[1]
SOURCE_DIR = PROJECT_ROOT / "src" / "transcription"
MODELS_PATH = SOURCE_DIR / "db" / "models.py"
SCHEMA_DOC = PROJECT_ROOT / "docs" / "schema.md"
STATUS_ENUMS = (JobStatus, JobSourceStatus, JobPurpose)
STATUS_VALUES = frozenset(member.value for enum in STATUS_ENUMS for member in enum)
# Attribute names that carry a status enum. A string literal compared against or
# assigned to one of these is a stringly-typed status, even if it happens to match.
STATUS_ATTRIBUTES = frozenset({"status", "purpose"})
# `JobSource.execution_attempts` loads attempt evidence on demand rather than raising,
# because evidence is fetched deliberately by the services that own it. Documented in
# `docs/schema.md` under "Relationship Loading Contract".
DOCUMENTED_LOADING_EXCEPTIONS = {"execution_attempts": "noload"}
def _python_files() -> list[Path]:
return sorted(SOURCE_DIR.rglob("*.py"))
def _relative(path: Path) -> str:
return path.relative_to(PROJECT_ROOT).as_posix()
def _is_status_target(node: ast.expr) -> bool:
"""True for `x.status`, `x.purpose`, and their `.value` unwrappings."""
if isinstance(node, ast.Attribute):
if node.attr in STATUS_ATTRIBUTES:
return True
if node.attr == "value":
return _is_status_target(node.value)
return False
def _string_constant(node: ast.expr) -> str | None:
return node.value if isinstance(node, ast.Constant) and isinstance(node.value, str) else None
def _status_literal_violations(tree: ast.Module) -> list[tuple[int, str]]:
found: list[tuple[int, str]] = []
for node in ast.walk(tree):
if isinstance(node, ast.Compare):
operands = [node.left, *node.comparators]
if not any(_is_status_target(operand) for operand in operands):
continue
for operand in operands:
literal = _string_constant(operand)
if literal is not None:
found.append((node.lineno, literal))
elif isinstance(node, ast.Call):
for keyword in node.keywords:
if keyword.arg not in STATUS_ATTRIBUTES:
continue
literal = _string_constant(keyword.value)
if literal is not None:
found.append((node.lineno, literal))
return found
def test_status_enums_expose_expected_vocabulary():
"""Guard the guard: the scan below is meaningless if the enums are empty."""
assert {member.value for member in JobStatus} == {
"queued",
"processing",
"transcribed",
"partial_success",
"failed",
}
assert {member.value for member in JobSourceStatus} == {
"pending",
"transcribed",
"failed",
"cancelled",
}
assert {member.value for member in JobPurpose} == {"transcription", "retranscription"}
def test_no_stringly_typed_status_comparisons_or_assignments():
"""Status handling must go through the enums, never raw strings.
`attempt.status.value == "transcribed"` silently survives an enum rename and
compares a projection of the value rather than the value itself.
"""
violations: dict[str, list[tuple[int, str]]] = {}
for path in _python_files():
tree = ast.parse(path.read_text(encoding="utf-8"))
found = _status_literal_violations(tree)
if found:
violations[_relative(path)] = found
assert violations == {}
def test_status_string_literals_outside_models_are_accounted_for():
"""Any bare status-valued literal in the package must be a known non-status use.
This is deliberately narrower than the comparison scan: it catches literals that
merely *look* like statuses, so a genuine new one cannot slip in unnoticed.
"""
allowed = {
# `JobStatus` / `JobSourceStatus` / `JobPurpose` member definitions.
"src/transcription/db/models.py",
# `WorkerHealthState` is a separate Literal vocabulary that reuses "failed".
"src/transcription/worker.py",
# Distribution name lookups for `transcription`, not the JobPurpose member.
"src/transcription/config.py",
"src/transcription/providers/evidence.py",
# UI placeholder copy where a value is absent, not a status render.
"src/transcription/ui/pages/jobs_page.py",
}
unexpected: dict[str, list[tuple[int, str]]] = {}
for path in _python_files():
relative = _relative(path)
if relative in allowed:
continue
tree = ast.parse(path.read_text(encoding="utf-8"))
found = [
(node.lineno, node.value)
for node in ast.walk(tree)
if isinstance(node, ast.Constant) and isinstance(node.value, str) and node.value in STATUS_VALUES
]
if found:
unexpected[relative] = found
assert unexpected == {}
def _relationship_loading_strategies() -> dict[str, dict[str, str | None]]:
"""Map each model attribute defined via `Relationship(...)` to its lazy strategy."""
tree = ast.parse(MODELS_PATH.read_text(encoding="utf-8"))
strategies: dict[str, dict[str, str | None]] = {}
for class_node in tree.body:
if not isinstance(class_node, ast.ClassDef):
continue
for statement in class_node.body:
if not isinstance(statement, ast.AnnAssign) or statement.value is None:
continue
call = statement.value
if not isinstance(call, ast.Call) or getattr(call.func, "id", None) != "Relationship":
continue
attribute = statement.target.id if isinstance(statement.target, ast.Name) else "<unknown>"
lazy: str | None = None
for keyword in call.keywords:
if keyword.arg != "sa_relationship_kwargs" or not isinstance(keyword.value, ast.Dict):
continue
for key, value in zip(keyword.value.keys, keyword.value.values, strict=True):
if isinstance(key, ast.Constant) and key.value == "lazy" and isinstance(value, ast.Constant):
lazy = value.value
strategies.setdefault(class_node.name, {})[attribute] = lazy
return strategies
def test_relationships_are_discovered():
"""Guard the guard: the loading rules below are meaningless if nothing is scanned."""
strategies = _relationship_loading_strategies()
assert {"Document", "Job", "JobSource", "Source"} <= set(strategies)
assert sum(len(attributes) for attributes in strategies.values()) >= 25
def test_relationships_declare_lazy_raise_except_documented_cases():
"""REQ-4-102: relationships raise on implicit load so read shape stays explicit."""
violations: dict[str, str | None] = {}
for model_name, attributes in _relationship_loading_strategies().items():
for attribute, lazy in attributes.items():
expected = DOCUMENTED_LOADING_EXCEPTIONS.get(attribute, "raise")
if lazy != expected:
violations[f"{model_name}.{attribute}"] = lazy
assert violations == {}
def test_documented_loading_exceptions_match_schema_doc():
"""The exception list is only trustworthy while `docs/schema.md` agrees with it."""
schema_text = SCHEMA_DOC.read_text(encoding="utf-8")
contract = schema_text.split("## Relationship Loading Contract", 1)[1]
for attribute, strategy in DOCUMENTED_LOADING_EXCEPTIONS.items():
assert attribute in contract, f"{attribute} is exempted in code but not documented"
assert f'`lazy="{strategy}"`' in contract
def _documented_table_fields() -> dict[str, list[str]]:
schema_text = SCHEMA_DOC.read_text(encoding="utf-8")
section = schema_text.split("## Field-Accurate Table Contracts", 1)[1]
documented: dict[str, list[str]] = {}
for block in re.split(r"\n### ", section)[1:]:
heading = block.splitlines()[0].strip().strip("`")
documented[heading] = re.findall(r"^\| `([^`]+)` \|", block, re.MULTILINE)
return documented
def _table_models() -> dict[str, type[SQLModel]]:
return {
name: attribute
for name, attribute in vars(models_module).items()
if isinstance(attribute, type)
and issubclass(attribute, SQLModel)
and attribute is not SQLModel
and getattr(attribute, "__table__", None) is not None
}
def test_schema_doc_documents_every_table_model():
"""Every persisted table needs a field contract, and vice versa."""
documented = set(_documented_table_fields())
actual = set(_table_models())
assert actual, "no table models discovered"
assert documented - actual == set(), "schema.md documents tables that no longer exist"
assert actual - documented == set(), "schema.md is missing tables that exist in models.py"
def test_schema_doc_field_contracts_match_models():
"""Check 6: `docs/schema.md` stays field-accurate with `db/models.py`."""
documented = _documented_table_fields()
drift: dict[str, dict[str, list[str]]] = {}
for name, model in _table_models().items():
expected = set(model.model_fields)
listed = set(documented.get(name, []))
if expected != listed:
drift[name] = {
"undocumented_fields": sorted(expected - listed),
"stale_doc_entries": sorted(listed - expected),
}
assert drift == {}
def test_schema_doc_lists_fields_in_declaration_order():
"""Ordering drift is how a doc silently stops being reviewable against the model."""
documented = _documented_table_fields()
misordered = {
name: {"documented": documented[name], "declared": list(model.model_fields)}
for name, model in _table_models().items()
if documented.get(name, []) != list(model.model_fields)
}
assert misordered == {}
def test_schema_doc_enumerations_match_status_enums():
"""The "Authoritative Enumerations" section must list the real members."""
schema_text = SCHEMA_DOC.read_text(encoding="utf-8")
section = schema_text.split("## Authoritative Enumerations", 1)[1].split("\n## ", 1)[0]
drift: dict[str, dict[str, list[str]]] = {}
for enum in STATUS_ENUMS:
block = re.split(rf"\n### {enum.__name__}\n", section)
assert len(block) == 2, f"{enum.__name__} has no section in docs/schema.md"
listed = re.findall(r"^- `([^`]+)`", block[1].split("\n### ", 1)[0], re.MULTILINE)
expected = [member.value for member in enum]
if listed != expected:
drift[enum.__name__] = {"documented": listed, "declared": expected}
assert drift == {}