test(config): T-1281 — canvas-version parity, using its own history as fixture

Five cases, including the failure path that matters — this is the gate with
five shipped regressions behind it.

A fixture repo does not work here, and finding out why exposed a real
inconsistency in the port: canvas_sources globs from a __file__-derived root,
so under SR_REPO_ROOT the service would diff the fixture while globbing the
real tree. Git goes through config.repo_root(); the registry does not. Harmless
in production since they are the same repo, but it is the same
no-root-override asymmetry the domain map noted about the old scripts, now
inside the new code. Not fixed here — making it dynamic means restructuring six
module-level constants in a module the old script still imports.

Real history is the better fixture anyway: both implementations see identical
input, nothing is mutated, and nothing can drift from the thing it models. Two
of the cases are genuine historical instances of the regression this gate
exists to catch — T-1237 and T-1194 both changed canvas generation and were
bumped only after the fact. The history that produced the check, used as its
own test.

The test also asserts no changed file is dropped from the failure message. That
list is the actionable half; "something changed" without saying what leaves the
reader to re-derive the intersection by hand.

Proven to fail by truncating the touched-file set, which reported both the
exit-code divergence and all three omitted filenames by name. Each case also
asserts the OLD script still behaves as the case claims, so a rewritten history
would say so rather than silently checking nothing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-31 20:07:26 +02:00
co-authored by Claude Opus 5
parent a3cbc478a0
commit 793a5239cd
3 changed files with 221 additions and 1 deletions
+78 -1
View File
@@ -195,6 +195,81 @@ def check_fact_ids(failures: list[str]) -> None:
)
# Ranges from the repo's own history, chosen so both implementations see
# identical input with nothing mutated. A fixture repo would not work here: the
# canvas registry globs from a __file__-derived root, so under SR_REPO_ROOT the
# new service would diff the fixture while globbing the real tree. Real history
# sidesteps that asymmetry entirely — and the failure cases below are genuine
# instances of the regression this gate exists to catch, not constructed ones.
#
# SHAs are hardcoded because history is immutable. If a rebase ever invalidates
# one, the test says so by name rather than silently checking nothing.
CANVAS_CASES = [
# (label, base, head, expect non-zero)
("canvas-needs-bump", "4e503c356~1", "4e503c356", True), # T-1237: bumped later
("canvas-needs-bump-2", "566b56651~1", "566b56651", True), # T-1194: same
("canvas-bumped", "9b146f9e1~1", "9b146f9e1", False), # changed AND bumped
("canvas-clean", "6b31111cd~1", "6b31111cd", False), # governance only
("canvas-no-base", "no-such-ref-anywhere", "HEAD", False), # skip, not fail
]
def check_canvas_version(failures: list[str]) -> None:
"""Parity for the gate with the worst track record — five shipped regressions.
Its failure path is the one that matters, so two of the five cases are real
commits that changed canvas generation without bumping the version. Both
were bumped after the fact, which is precisely the history that produced
this check.
"""
for label, base, head, expect_failure in CANVAS_CASES:
old = subprocess.run(
[
sys.executable,
str(REPO_ROOT / "tooling" / "check-canvas-version"),
"--base",
base,
"--head",
head,
],
capture_output=True,
text=True,
cwd=REPO_ROOT,
)
new = subprocess.run(
["reach", "check", "canvas-version", "--base", base, "--head", head],
capture_output=True,
text=True,
cwd=REPO_ROOT,
env={**os.environ, "SR_OUTPUT_FORMAT": "text"},
)
if (old.returncode != 0) != expect_failure:
failures.append(
f"[{label}] the OLD script exited {old.returncode}; this range no "
"longer sets up the case it claims to — history may have been rewritten"
)
if old.returncode != new.returncode:
failures.append(
f"[{label}] exit differs: old={old.returncode} new={new.returncode}"
)
if expect_failure:
# The changed files are the actionable half of this failure; a
# message that says "something changed" without saying what leaves
# the reader to re-derive the intersection by hand.
for stream in (old.stdout + old.stderr,):
for line in stream.splitlines():
token = line.strip()
if token.endswith((".rs", ".gd")) and token not in (
new.stdout + new.stderr
):
failures.append(
f"[{label}] new output omits a changed file the old one "
f"named: {token}"
)
def main() -> int:
if shutil.which("reach") is None:
print(
@@ -269,6 +344,7 @@ def main() -> int:
)
check_fact_ids(failures)
check_canvas_version(failures)
if failures:
print("test_check_parity: FAIL", file=sys.stderr)
@@ -278,7 +354,8 @@ def main() -> int:
print(
f"test_check_parity: OK — client-version {len(CASES)} paths, "
f"fact-ids {len(FACT_CASES)} paths, all agreeing on exit code and content"
f"fact-ids {len(FACT_CASES)} paths, canvas-version {len(CANVAS_CASES)} paths, "
"all agreeing on exit code and content"
)
return 0