N-9 is the finding, and it PREVENTS work rather than re-scoping it.
WHAT THE PLAN GOT WRONG. N-3 concluded the 23-vs-13 smoke delta was unintended
drift, on two in-repo comments asserting parity, and called that "the strongest
evidence available that the drift was never intended". H-12 repeated it; R-14
built three options on it recommending "raise CI, +46s measured"; PR-4
specified a bidirectional parity pin as deliverable one.
A third artifact answers all of it, and none of them read it.
tests/test_cli_test_suites.py::test_cli_smoke_suite_covers_ci_smoke_gate has
pinned smoke.yml SUBSET-OF TEST_SUITES["smoke"] since before this assessment —
which is why every measurement finds CI-only == 0; a pin, not luck — beneath a
comment headed "DELIBERATELY ONE-DIRECTIONAL — do not 'complete' this by also
asserting local_paths <= ci_paths". Its history is 50fa287d (2026-07-25):
"revert(tests): drop the local-to-CI smoke parity assertion — CI is not a
gate." An earlier agent read the same comment, drew the same inference, made
the assertion symmetric, and reverted it hours later: "That was wrong, and
AGENTS.md says so in a line I had already read."
AGENTS.md:280 — GitHub is "a mirror only; its Actions are billing-locked and
produce dead signals — never chase them." §277 — the workflows are "secondary
observability only." So the local suite is the SOURCE and is deliberately
broader; the delta is the design.
- PR-4 pin 1: WITHDRAWN. It exists, in the direction that protects something.
- R-14: premise corrected in the packet. Option A spends 46s on a workflow
that does not gate; option B would delete real protection for a fiction;
option C's live half is a two-line comment fix, done here.
- N-3's exposure claim was too GENEROUS to CI: for a push that skipped the
local gate there is no automatic check at all. Larger than stated, and not
closable by editing smoke.yml.
WHAT LANDED. Measured: 749 of 877 test files are in no curated suite. The
plan's mechanism ("assign every orphan, or 749 exclusion reasons") is hollow at
that scale — glob topic-suites ("adr": tests/test_adr_*.py absorbs 172) satisfy
the assertion while changing nothing about what executes, which is precisely the
Third-Door objection the plan levels at G-7's own first formulation. All four
demonstrated incidents (#113, #136, negation-survives-articulation,
speculative-subject-lifecycle) were caused by a NEWLY LANDED orphan; none by a
legacy one.
So: a ratchet. tests/test_suite_membership.py + tests/full_only_baseline.txt.
New orphans fail. The baseline is enforced in BOTH directions — a promoted or
deleted file must leave it, so the list can only shrink — and its count is
pinned so bulk movement is a reviewed decision, in the discipline
test_volume_honesty.py uses.
Also: the gate-guarding pin now runs on the gate. test_cli_test_suites.py lived
in `fast`; the pre-push gate runs smoke + deductive. The pin guarding the gate
did not run on the gate.
Three sabotages, each OBSERVED RED: a new unregistered test file, a stale
baseline entry, the parity pin demoted out of smoke.
Note on the delta: now 12 (local 27, CI 15) because this session added three
pins locally. Under the corrected reading that is the design, not widening
drift — the invariant is CI-only == 0, and that is pinned. The registers now
say so; "ten files" was a measurement, never a target.
Process note: `git checkout --` during sabotage cleanup destroyed uncommitted
edits to core/cli_test.py, which were redone. Backups, not checkout, on files
with unstaged work.
Registers: G-7 CLOSED, H-12 amended, R-14 dissolved in the packet, PR-4
re-specified, N-9 added to plan §0.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wcw2pnMBwyvmNyQg4uPEt4
183 lines
7.3 KiB
Python
183 lines
7.3 KiB
Python
"""PR-4 / G-7 — a test file may not land outside every curated suite.
|
|
|
|
The failure mode, demonstrated four times in this repository: a pin is written
|
|
for a real defect, lands in no curated suite, therefore runs on no pre-merge
|
|
gate, and the thing it pins regresses anyway. #113 (``test_adr_index``), #136,
|
|
``test_negation_survives_articulation``, and — while fixing H-13 on 2026-07-28
|
|
— ``test_speculative_subject_lifecycle``, which pinned the exact behavior that
|
|
had silently broken. Each was repaired individually, after shipping.
|
|
|
|
**Why this is a ratchet and not a membership assertion.** The plan originally
|
|
specified "every ``tests/**/test_*.py`` belongs to >=1 suite excluding ``full``,
|
|
or to a registered exclusion list with a reason." Measured at 2026-07-28 that is
|
|
749 files against 128 curated. Both ways of satisfying it at that scale are
|
|
hollow: glob-defined topic suites (``"adr": ("tests/test_adr_*.py",)`` alone
|
|
absorbs 172) make the assertion pass while changing nothing about what executes,
|
|
and 749 hand-written exclusion reasons are ceremony. A curated suite nobody runs
|
|
is the same non-guarantee as ``full``, wearing a different name — which is the
|
|
objection the plan itself raises against G-7's first formulation.
|
|
|
|
None of the 749 legacy files caused any of the four incidents. A *new* orphan
|
|
caused all four. So the pin with teeth is the one that blocks new orphans and
|
|
makes the legacy set a declared, monotonically shrinking number.
|
|
|
|
Recorded as N-9 in ``docs/assessment/50-execution-plan.md``.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import ast
|
|
import glob
|
|
import re
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
_ROOT = Path(__file__).resolve().parents[1]
|
|
_BASELINE = _ROOT / "tests" / "full_only_baseline.txt"
|
|
|
|
|
|
def _curated_files() -> set[str]:
|
|
"""Every test path reachable from a curated suite other than ``full``.
|
|
|
|
``TEST_SUITES`` is parsed from source rather than imported so this pin keeps
|
|
working if importing the CLI ever acquires a side effect; the tuple is a
|
|
literal by construction (``test_cli_test_suites.py`` pins its shape).
|
|
"""
|
|
src = (_ROOT / "core" / "cli_test.py").read_text(encoding="utf-8")
|
|
match = re.search(r"TEST_SUITES\s*(?::[^=]+)?=\s*\{", src)
|
|
assert match is not None, "TEST_SUITES literal not found in core/cli_test.py"
|
|
start = match.end() - 1
|
|
depth = 0
|
|
end = len(src)
|
|
for i, ch in enumerate(src[start:], start):
|
|
if ch == "{":
|
|
depth += 1
|
|
elif ch == "}":
|
|
depth -= 1
|
|
if depth == 0:
|
|
end = i + 1
|
|
break
|
|
suites = ast.literal_eval(src[start:end])
|
|
|
|
curated: set[str] = set()
|
|
for name, patterns in suites.items():
|
|
if name == "full":
|
|
# ``full`` is ("tests/",) — a DIRECTORY, so it matches every test
|
|
# file trivially. Counting it would make this pin vacuous; that
|
|
# scan artifact is N-1 in the execution plan and has now produced
|
|
# three false conclusions in this repository.
|
|
continue
|
|
for pattern in patterns:
|
|
curated.update(str(Path(p).as_posix()) for p in glob.glob(pattern))
|
|
return curated
|
|
|
|
|
|
def _all_test_files() -> set[str]:
|
|
return {
|
|
str(Path(p).as_posix())
|
|
for p in glob.glob("tests/**/test_*.py", recursive=True)
|
|
}
|
|
|
|
|
|
def _baseline() -> list[str]:
|
|
lines = _BASELINE.read_text(encoding="utf-8").splitlines()
|
|
return [ln.strip() for ln in lines if ln.strip() and not ln.lstrip().startswith("#")]
|
|
|
|
|
|
def test_baseline_file_exists_and_is_sorted_and_unique() -> None:
|
|
entries = _baseline()
|
|
assert entries, "the baseline is empty — it should list the known full-only tests"
|
|
assert entries == sorted(entries), "baseline must stay sorted (stable diffs)"
|
|
assert len(entries) == len(set(entries)), "baseline contains duplicates"
|
|
|
|
|
|
def test_no_new_full_only_test_files() -> None:
|
|
"""A new test file must join a curated suite, or the gate it needs is absent.
|
|
|
|
If this fails on a file you just added: put it in the suite whose lane it
|
|
actually belongs to in ``core/cli_test.py::TEST_SUITES``. Adding it to the
|
|
baseline instead is available and is almost always the wrong answer — the
|
|
baseline is for files that predate this pin, not an opt-out.
|
|
"""
|
|
orphans = _all_test_files() - _curated_files()
|
|
unlisted = sorted(orphans - set(_baseline()))
|
|
assert not unlisted, (
|
|
"these test files are in no curated suite and are not in the recorded "
|
|
"baseline, so they run on no pre-merge gate:\n "
|
|
+ "\n ".join(unlisted)
|
|
+ "\n\nRegister each in core/cli_test.py::TEST_SUITES."
|
|
)
|
|
|
|
|
|
def test_baseline_has_no_stale_entries() -> None:
|
|
"""The list may only shrink — a promoted or deleted file must leave it.
|
|
|
|
Enforced in BOTH directions, in the discipline ``test_volume_honesty.py``
|
|
uses: a baseline that silently keeps entries it no longer needs stops being
|
|
a measurement and becomes a story about one.
|
|
"""
|
|
curated = _curated_files()
|
|
present = _all_test_files()
|
|
entries = _baseline()
|
|
|
|
vanished = sorted(e for e in entries if e not in present)
|
|
assert not vanished, (
|
|
"baseline names files that no longer exist — delete these lines:\n "
|
|
+ "\n ".join(vanished)
|
|
)
|
|
|
|
promoted = sorted(e for e in entries if e in curated)
|
|
assert not promoted, (
|
|
"these files are now in a curated suite (good) but are still listed as "
|
|
"full-only — delete their baseline lines so the count reflects reality:\n "
|
|
+ "\n ".join(promoted)
|
|
)
|
|
|
|
|
|
def test_recorded_debt_matches_the_measurement() -> None:
|
|
"""Pin the number so a bulk change to it shows up as a reviewed decision.
|
|
|
|
749 at 2026-07-28. Lowering this is the point; raising it must be argued
|
|
for, not slipped in. The assertion names the direction so a reviewer of a
|
|
failing run knows immediately which one happened.
|
|
"""
|
|
entries = _baseline()
|
|
assert len(entries) <= 749, (
|
|
f"full-only test debt grew to {len(entries)} (was 749). The ratchet only "
|
|
"turns one way — register the new files in a curated suite instead."
|
|
)
|
|
|
|
|
|
def test_the_gate_guarding_pin_runs_on_the_gate() -> None:
|
|
"""The parity pin must itself be in ``smoke`` (N-9).
|
|
|
|
``test_cli_smoke_suite_covers_ci_smoke_gate`` enforces that the local suite
|
|
is never narrower than ``smoke.yml``. It lived in ``fast``, while the
|
|
pre-push gate runs ``smoke`` + ``deductive`` — so the pin guarding the gate
|
|
did not run on the gate. This keeps it there.
|
|
"""
|
|
curated_src = (_ROOT / "core" / "cli_test.py").read_text(encoding="utf-8")
|
|
match = re.search(r"TEST_SUITES\s*(?::[^=]+)?=\s*\{", curated_src)
|
|
assert match is not None
|
|
start = match.end() - 1
|
|
depth = 0
|
|
end = len(curated_src)
|
|
for i, ch in enumerate(curated_src[start:], start):
|
|
if ch == "{":
|
|
depth += 1
|
|
elif ch == "}":
|
|
depth -= 1
|
|
if depth == 0:
|
|
end = i + 1
|
|
break
|
|
suites = ast.literal_eval(curated_src[start:end])
|
|
assert "tests/test_cli_test_suites.py" in suites["smoke"], (
|
|
"the smoke/CI parity pin must run on the gate it protects"
|
|
)
|
|
|
|
|
|
def test_this_pin_is_itself_registered() -> None:
|
|
"""No exemption for the membership pin — #136's lesson applied to itself."""
|
|
orphans = _all_test_files() - _curated_files()
|
|
assert "tests/test_suite_membership.py" not in orphans
|