The reachability gap turns by PROMOTION for the first time: 15 -> 14. PR-3b's
earlier turn was by deletion, which is free. This one costs 22.1s on every push,
which is exactly why it is a decision rather than a default.
THE RECOMMENDATION WAS RE-DERIVED, NOT EXECUTED — and it did not survive as
written. PR-4b recommended `teaching` on the strength of G-22:
test_ratification_ceremony.py was red on main, in `teaching`, run by no gate.
That hole was already closed a different way — G-22's fix promoted that single
file into smoke. So `teaching` would have added 9 net-new files, NONE with a
demonstrated escape, and promoting on a justification that has already been
spent is precisely the failure this arc exists to close.
Promoted on a stronger, doctrinal basis instead.
tests/test_epistemic_invariants.py enforces AGENTS.md's "Teaching and mutation
safety" non-negotiables in substance — INV-22/INV-23: an unmarked proposal and
an unmarked reviewed example default to SPECULATIVE, the status enum has exactly
its four positions, a pack-mutation proposal carries no hardening flag. Durable
corpus mutation is the one irreversible thing in this system, and the suite
guarding it ran on no pre-merge gate. That justification does not require an
incident to have happened yet, which makes it the better one.
Measured: 22.1s / 122 tests across 9 net-new files (the tenth was already on the
gate). Both invocation sites updated, GATE_SUITES extended, `teaching` removed
from UNREACHABLE_BASELINE in the same edit, ratchet lowered <= 15 to <= 14 —
the both-directions discipline that has caught this author twice.
A HOLLOW SPOT IN PIN 3 ITSELF, found by sabotage-testing the promotion rather
than trusting it. _suites_invoked_by_gate_tiers() takes the UNION of
scripts/hooks/pre-push and scripts/ci/local-ci.sh, so a suite present in either
satisfies it. Removing `teaching` from local-ci.sh alone PASSED SILENTLY: the
two scripts could drift from each other invisibly while local-ci.sh states in
prose that it runs "the same four steps as scripts/hooks/pre-push". A developer
running one would get coverage the other advertises and does not deliver.
Closed by test_the_two_gate_scripts_invoke_the_same_suites, scoped to the
numbered (n/m) gate steps — a discriminator both files already use — so it reads
the gate tier without parsing shell control flow. The identical sabotage now
fires. Verified in three directions: removed from one script (fires, and did not
before), removed from both (the pre-existing assertion fires), restored (green).
THIS IS NOT THE CI-PARITY PIN N-9 KILLED, and the distinction is recorded at the
assertion site so it cannot be re-litigated a third time. That one asserted
TEST_SUITES["smoke"] == smoke.yml and was reverted in 50fa287d because
AGENTS.md:280 makes GitHub Actions billing-locked dead signals — parity with a
dead signal buys nothing. Both scripts here are LIVE local merge bars.
Gate is now four steps: smoke + warmed_session + deductive + teaching.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wcw2pnMBwyvmNyQg4uPEt4