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>
124 lines
3.9 KiB
Python
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
|