Skip to content

Commit fbe397b

Browse files
committed
ci: gate that every self-test CI ships is a self-test CI runs
A gate whose defect class is its matching rule cannot detect its own regression on a clean tree: green means the finding set is empty, weakening the rule can only shrink that set, and the empty set is the fixed point of shrinking. `--self-test` is the only instrument watching such a rule, and nothing required it to keep running. Adds scripts/check-self-test-wired.mjs: every script a workflow runs whose code carries `--self-test` must have that self-test run by CI, with a shrink-only, evidence-checked ledger for the scripts whose self-test CI runs by another route. Wires the one genuine gap the enumeration found (the release-coverage renderer). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015ahemw8RcTgqtxrj15PEZx
1 parent 1f6d047 commit fbe397b

3 files changed

Lines changed: 661 additions & 1 deletion

File tree

.github/workflows/lint.yml

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -736,6 +736,58 @@ jobs:
736736
node scripts/check-step-collectors.mjs --self-test
737737
node scripts/check-step-collectors.mjs
738738
739+
# Self-test wiring gate (#11150) — the guard over the `--self-test` legs
740+
# themselves, in this file and every other workflow.
741+
#
742+
# A gate whose defect class is its MATCHING RULE cannot detect its own
743+
# regression on a clean tree. Green means the finding set is empty;
744+
# weakening the rule can only SHRINK that set; and the empty set is the
745+
# fixed point of shrinking. So the production verdict is byte-identical
746+
# before and after the rule breaks, and `--self-test` — which supplies an
747+
# adversarial input a clean tree by construction does not contain — is the
748+
# ONLY instrument watching it.
749+
#
750+
# Measured rather than argued, one ablation per gate with the mutation
751+
# confirmed on disk before any reading was taken: loosening
752+
# `check-auth-mount-ledger`'s exact `METHOD path` accounting into a
753+
# strict-prefix credit, and deleting the recognizer `check-error-code-
754+
# casing` needed for the lowercase codes that shipped past it, each left
755+
# the production run GREEN and reddened only the self-test. So the
756+
# `<gate> --self-test` + `<gate>` steps throughout this file are not
757+
# ceremony beside the live run — for that family the first line is the
758+
# only line carrying coverage, and deleting it to save CI time would
759+
# remove the coverage while every available signal still said the gate
760+
# works.
761+
#
762+
# What this step holds is the mechanically decidable SUPERSET: every
763+
# script CI runs that ships a `--self-test` must have that self-test run
764+
# by CI too. Membership needs no classifier, and that is the point —
765+
# membership in the family itself is NOT statically decidable. The same
766+
# kind of ablation on `check-dispatcher-error-vocabulary` REDDENS its
767+
# production run, because a row it declares stops being reached and its
768+
# ledger is an equality rather than an emptiness; while a second rule
769+
# inside `check-route-envelope` moves neither run. Two rules in one file
770+
# can sit on opposite sides, so a family enumerated by name would wire the
771+
# wrong set and then read as complete.
772+
#
773+
# Scripts whose self-test CI runs by some route other than the flag (a
774+
# wrapper that spawns another tool's, a gate that runs its own cases on
775+
# every invocation) are a shrink-only ledger inside the script, and every
776+
# row carries evidence that is re-checked on each run rather than
777+
# believed.
778+
#
779+
# ⚠️ No count is written into this comment on purpose: a number in CI
780+
# prose goes stale in silence, which is the same class of defect as the
781+
# card that produced this step. The gate prints its own scope line.
782+
#
783+
# Invoked as `node scripts/…` rather than through a `pnpm check:*` alias:
784+
# see the GATE INVOCATION IDIOM note at the top of this file. Reads
785+
# `scripts/` and `.github/workflows/` off disk, no network, ~1 s.
786+
- name: Self-test wiring gate
787+
run: |
788+
node scripts/check-self-test-wired.mjs --self-test
789+
node scripts/check-self-test-wired.mjs
790+
739791
# Verify-lock entry-point self-test (#9661). `scripts/pm/os-verify-lock.sh`
740792
# is the ONE way an agent takes the container's shared heavy-verify lock,
741793
# and it is the enforcement of a rule that used to live only in prose: the

.github/workflows/release-coverage-patrol.yml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,16 @@ jobs:
241241
PROVENANCE: >-
242242
run [${{ github.run_id }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})
243243
· commit `${{ github.sha }}` · trigger `${{ github.event_name }}`
244-
run: node scripts/render-release-coverage-anchor.mjs > "$RUNNER_TEMP/body.md"
244+
# Runs its own `--self-test` first (#11150). That leg is not ceremony
245+
# here: the renderer's assertions pin `verdict()` — the mapping from the
246+
# sweep's two exit codes onto did-not-run / findings / clean — and a
247+
# broken mapping still renders a body. The live run below would stay
248+
# exit 0 and publish a WRONG report, including the one reading #4690
249+
# forbids, a broken instrument rendered as a clean corpus. Only the live
250+
# run is redirected; the self-test's own verdict goes to the step log.
251+
run: |
252+
node scripts/render-release-coverage-anchor.mjs --self-test
253+
node scripts/render-release-coverage-anchor.mjs > "$RUNNER_TEMP/body.md"
245254
246255
# The rendered body is the product of the run, so it goes to the step
247256
# summary on EVERY trigger — including the pull_request runs that must not

0 commit comments

Comments
 (0)