Files
settled-reach/tooling/domains/check/schemas.py
T
jpmschweitzerandClaude Opus 5 05bf1732d4 feat(config): T-1281 — dataflow-graph and systems-db-stamp join the check domain
Both were already Python, so these are moves rather than rewrites, and both
produce byte-identical output to their originals on the live tree with the same
exit codes.

The E402 debt evaporated on contact, which is the first concrete evidence for
T-1274's premise. check-systems-db-stamp reached generator_sources through a
sys.path.insert and a noqa suppression, because tooling/ was not a package. It
now imports as `from tooling import generator_sources` — no hack, no
suppression.

The stamp gate's six failure modes are preserved as a StampState enum rather
than collapsed into pass/fail, because they carry different remedies and one
carries a different exit code: UNSTAMPED exits 2 while every other failure
exits 1, and the pre-push hook has relied on that distinction since T-857.

One deliberate behavioural difference, flagged rather than hidden: the old
stamp script was silent on success unless given --verbose, and the new one
always prints its verdict. No fact is lost, so parity holds, and it makes the
gate consistent with client-version and dataflow-graph which both always print
— the old script was the odd one out. Its per-command --verbose gives way to
the global one, which is the consolidation this initiative is for.

Also corrects a claim in the ticket itself: check-dataflow-graph.py does not
parse git output, it globs the filesystem. Only check-canvas-version parses
git, so only that fixture needs a real repo.

Still open and recorded as such: check-canvas-version, and parity tests for
these two — both were verified side by side on the live tree, which proves the
happy path and nothing else.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 17:55:28 +02:00

124 lines
3.9 KiB
Python

"""Data shapes for the `check` domain.
pydantic, as every domain's `schemas.py` should be (D-263). The earlier stdlib
dataclass here was a carve-out justified by a timing-parity budget that D-263
withdrew on 2026-08-20 — and a reference implementation carrying a footnote is a
worse reference than one that is simply normal.
Shapes are frozen. A result object is a statement about what was found, and
nothing downstream should be able to edit the finding on its way to being
reported.
"""
from __future__ import annotations
from enum import Enum
from pydantic import BaseModel, ConfigDict
class UnknownFact(BaseModel):
"""A fact_id referenced by content that no knowledge catalog defines."""
model_config = ConfigDict(frozen=True)
fact_id: str
locations: list[str] = []
class FactIdCheck(BaseModel):
"""The outcome of validating content fact_id references against the catalogs.
`advisory` is the mode where the catalogs hold no definitions at all. The
check then reports what it found and exits 0 — deliberately, because failing
every commit until the catalogs are populated would train people to bypass
the hook, and a gate people route around protects nothing.
"""
model_config = ConfigDict(frozen=True)
advisory: bool
canonical_count: int
referenced: list[str] = []
unknown: list[UnknownFact] = []
@property
def ok(self) -> bool:
return not self.unknown
class DiagramCheck(BaseModel):
"""The outcome of checking that paths named in diagrams still resolve.
`checked` matters as much as `unresolved`. A diagram whose label format
changed yields zero tokens, and a checker that finds nothing to check
reports success — so a count of zero is itself a failure, not a clean run.
"""
model_config = ConfigDict(frozen=True)
diagrams: int
checked: int
unresolved: list[str] = []
missing: list[str] = []
@property
def ok(self) -> bool:
return not self.unresolved and not self.missing and self.checked > 0
class StampState(str, Enum):
"""Why the systems.db stamp check reached its verdict.
Distinguished rather than collapsed into pass/fail because they carry
different remedies and, in one case, a different exit code: UNSTAMPED exits
2 while every other failure exits 1, and the pre-push hook has treated that
2 as meaningful since T-857.
"""
OK = "ok"
ABSENT = "absent" # no DB at all — not a failure, nothing to verify
UNSTAMPED = "unstamped" # no meta table or empty: exit 2
BAD_VERSION = "bad_version" # schema_version is not semver
CONFLICT = "conflict" # generators disagree on schema_version
UNKNOWN = "unknown" # a generator not in the registry
BROKEN = "broken" # a registered source file is gone
STALE = "stale" # sources changed since the stamp was written
class StampCheck(BaseModel):
"""The outcome of comparing systems.db's meta stamp against its sources."""
model_config = ConfigDict(frozen=True)
state: StampState
generators: int = 0
details: list[str] = []
@property
def ok(self) -> bool:
return self.state in (StampState.OK, StampState.ABSENT)
@property
def exit_code(self) -> int:
if self.ok:
return 0
return 2 if self.state is StampState.UNSTAMPED else 1
class VersionCheck(BaseModel):
"""The outcome of comparing project.yaml against client/project.godot.
`problem` and `ok` are deliberately separate. A check can fail because the
versions disagree (`ok=False`, both versions known) or because it could not
read them at all (`problem` set) — and those want different messages, since
only the first has a remedy the caller can act on.
"""
model_config = ConfigDict(frozen=True)
ok: bool
yaml_version: str | None = None
godot_version: str | None = None
problem: str | None = None