feat(ci): make this repo's own suite a gating context, and pin what could un-require it [DO NOT MERGE: gate refused] - #45
Open
NSchatz wants to merge 4 commits into
Open
feat(ci): make this repo's own suite a gating context, and pin what could un-require it [DO NOT MERGE: gate refused]#45NSchatz wants to merge 4 commits into
NSchatz wants to merge 4 commits into
Conversation
…ould un-require it
The 339-case suite in `scripts/` ran on every pull request and could block none of
them. A green check that cannot block a merge is documentation.
`scripts` is added to ruleset `ci-required-checks`, alongside the contexts already
there. The precondition was met and checked rather than assumed: `self-check.yml`
has no `paths:` filter and no job-level `if:`, the job has completed on `main`, and
`scripts` was read off the real `pull_request` check runs of every open pull request
before the ruleset moved. The standing record read the job as path conditional; it is
not, and never was. It simply postdated three branches that have since gone.
`test/self-check.test.mjs` closes the two ways a required job stops gating with no
ruleset edit and no error anywhere:
- Renaming the job id detaches the ruleset entry silently, leaving a context that
is required and emitted by nothing, so every pull request strands pending. The
job ids are pinned, so the rename reds in the pull request that made it.
- A required job gates its steps, not the selection those steps operate on.
`node --test "test/*.test.mjs"` exits 0 as happily over one file as over all of
them. The glob is read out of the workflow and compared against the test files
on disk, and a plausible narrowing is exhibited as a failing case rather than
asserted to be catchable.
A `paths:` filter and a job-level `if:` are refused for the same reason: a
conditional required context strands every pull request that does not match it.
Every assertion was mutation checked. Seven mutations, seven reds, each on the
assertion that names it: rename, narrow, add `paths:`, add a job `if:`, drop the
`push` trigger, unquote the glob, drop the checkout.
Also records the pre-publish census. Turning both layers on made them RUN in all
thirteen callers; it did not make them BLOCK in any. They live in their own job, so
`ci / prepublish` is a separate check-run context and a caller requiring the `verify`
matrix does not thereby require it. Censused 2026-08-07 off real check runs: all
thirteen emit it, one requires it. That is one line per caller in that caller's own
ruleset and is not reachable from here, so it is written down rather than fixed.
No workflow logic moved. `ci.yml` and `release.yml`, the two called at `@main` by
thirteen repositories, are untouched. `self-check.yml` and `no-emdash.yml` were
parsed with a real YAML parser at base and at head and flattened to leaf paths: both
are identical leaf for leaf, comments only.
Gate run by hand, since `verify.sh` has no ladder here and exits 66: `node --test`
346/346, `actionlint -shellcheck` clean, `no-emdash` clean over every tracked file.
…refusing its own remedy Pass 1 of the `gate-refuter` returned NOT REFUTED with four minor `INTRODUCED` findings, all sentences rather than machinery. Applied as prescribed. No guard was grown to chase them. 1. The diff wrote the rule against in-repo ruleset state and then asserted in-repo ruleset state, which is the defect it was correcting. Nothing now claims a context IS required: the test header, the workflow comment and the derivation comment all state the intent and the derivation, and the test file says outright that whether a context is required is not knowable from inside a repository. 2. "THE TWO WAYS a required job stops gating" was an exhaustiveness claim and there is a third, one line of YAML away. A STEP-level `if:`, and `continue-on-error: true` on a step, do not strand anything: the job succeeds and the required context reports GREEN OVER NOTHING, which is the worse direction. The README said an actor `if:` "leaves the check permanently pending", true at job level and false at step level, where it is the milder characterisation of the worse case. Both are now named as open and unguarded rather than implied to be covered. Not closed, deliberately: closing them is one rule per step attribute, and this repo has already paid twice for a deny-list whose second round closed nothing new. 3. The "one owner" claim was not achieved. The precondition sits at three sites and the procedure at one; that is now what the pointer says, rather than claiming a single owner for both. 4. The guard forbade the growth its own comment prescribes. `globToRegExp` could not read `**`, so widening to `test/**/*.test.mjs`, the remedy the comments ask for when a directory of tests appears, reded. A guard that refuses its own documented remedy gets deleted rather than obeyed. `**` now translates, in ONE pass with no sentinel character round-tripped through the string, and both depths are asserted. The same finding surfaced a conflation: pinning the glob's literal value made the shape case red on a widening as loudly as on a narrowing. The shape case now owns the invocation (`node --test`, one quoted argument, no selector smuggled in beside it) and the load-bearing case owns whether the selection is complete. Mutation checked again, wider than before. Narrowing the glob, adding `paths:`, adding a job `if:`, dropping `push`, unquoting, dropping the checkout, renaming the job, appending `--test-name-pattern`, replacing the command with `echo`, and dropping a test file into a subdirectory all still red. WIDENING now passes, which is the correction. Gate: `node --test` 346/346, `actionlint -shellcheck` clean, `no-emdash` clean over all 49 tracked files. `ci.yml` and `release.yml` remain untouched; `self-check.yml` and `no-emdash.yml` re-parsed at base and head and still identical leaf for leaf.
…rrect the glob translator
`gate-refuter` pass 2 returned REFUTED on `5b81279` with one INTRODUCED major. It was
right, and the remedy is the one it prescribed: cut, do not rewrite.
THE MAJOR. `5b81279` claimed a job-level `if:` strands a pull request while a
step-level one goes green, and used that contrast to justify leaving the step-level
case open. GitHub's own documentation says a skipped job reports its status as
success and does not prevent a pull request from merging, even as a required check.
So the contrast does not exist and the ranking was meaningless. Worse, this repo
ships `ci.yml` with `actionlint` behind `if: inputs.run-actionlint`, and
`ci / actionlint` is a required context in caller rulesets, so a maintainer setting
`run-actionlint: false` was being told by this README to expect a loud strand across
13 repos.
Every sentence about which direction a skipped context fails is now DELETED, from the
README and from the test file. Nothing replaces it with a better answer, because this
repo has not settled that question against a primary source and a live run, and
`deid`'s `ci.yml` records the same question as open against the same input. It is
escalated as its own item. `cli#35` is the precedent: that note was rewritten twice,
produced a fresh false claim each time, and was correctly cut out rather than rewritten
a third time.
The `if:` and `paths:` guard STAYS, on a reason that does not need the answer: a
context that does not run on every pull request cannot gate every pull request,
whichever way a skip resolves.
Also from pass 2:
- The precondition was said to be "load-bearing at three sites". It is stated at more
than three. A count is exactly what this commit series forbids elsewhere, so it is
removed rather than corrected to a new number.
- `globToRegExp` mapped a bare `**` to `.*`, which crosses a separator. Node does not:
measured on a real run, `test/**/*.test.mjs` selects 347 tests and `test/**.test.mjs`
selects 346, over the same tree. The old mapping failed in the dangerous direction,
reporting a selection wider than the one CI runs. `**` now crosses a separator only
as a whole segment, and both readings are pinned.
- "both belong in the set this repository requires" was the fourth site of pass 1's
ruleset-state finding and had been missed.
Mutation checked again. Narrowing, `paths:`, a job `if:`, dropping `push`, unquoting,
dropping the checkout, renaming the job, `--test-name-pattern`, replacing the command
with `echo`, a test file dropped into a subdirectory, and now a `**` widening that does
NOT cross a separator all red. A correct `test/**/*.test.mjs` widening passes.
Gate: `node --test` 346/346, `actionlint -shellcheck` clean, `no-emdash` clean over all
49 tracked files, zero control bytes in any tracked file. `ci.yml` and `release.yml`
remain untouched.
⚠ THIS COMMIT IS UNGRADED. `gate-refuter` pass 3 of 3 returned REFUTED on `3bc2dad`
with an INTRODUCED blocker, and ADR 0016 allows no fourth pass. Per that ADR the
action is stop, escalate and cut back, so this is the cut-back and nothing else. THE
SLICE IS NOT TO BE MERGED ON THIS COMMIT'S AUTHORITY; it stops for a human.
WHAT PASS 3 CAUGHT, and it was right. `3bc2dad` deleted the false claim that a
job-level `if:` strands a pull request while a step-level one goes green, and then
wrote in its place that this page and this file state nothing about what a skipped
required context does. Both files still did, 56 lines later in one case. That is a
rewrite wearing a deletion's clothes, and it is the third round in a row in which this
slice shipped a checkable wrong self-description.
So this commit is PURELY SUBTRACTIVE. Every sentence characterising what a SKIPPED
required context does to a merge is gone, and no sentence replaces it, including the
meta-sentence claiming they are gone. Cut:
- the two paragraphs at README "Two ways a required job stops gating", down to the
one clause that survives all three passes and needs no theory: a context that does
not run on every pull request cannot gate every pull request;
- the same claim in `test/self-check.test.mjs`, in both the header and the
conditional case;
- the pre-existing false ones at README "Two jobs, and only one of them may ever be
required" and in `no-emdash.yml`, which asserted an actor `if:` leaves a required
context permanently pending. GitHub documents the opposite. They are pre-existing
and out of this slice's obligation, but they are false, they govern thirteen repos
at `@main`, and deleting a false sentence adds no claim;
- the "load-bearing wherever a context is discussed" assertion, which was still
over-general;
- the "it is being escalated on its own" promise, which had no artifact. The
escalation is raised to a human instead of asserted in a comment.
What survives about PENDING is a different mechanism and is correct: a context that is
required and emitted by NOTHING (never run, or detached by a rename) has no check run
at all. That is not a skipped job, and pass 3 confirmed the distinction.
`globToRegExp` keeps the measured fix and now discloses its own residual rather than
claiming to encode node's rule: a `**` that is neither a whole segment nor the whole
pattern is not translated faithfully and is not refused either.
Guard strength unchanged. Narrowing, `paths:`, a job `if:`, dropping `push`,
unquoting, dropping the checkout, renaming the job, `--test-name-pattern`, replacing
the command with `echo`, a subdirectory test file, and a non-spanning `**` widening
all still red. A correct `test/**/*.test.mjs` widening still passes.
Gate: `node --test` 346/346, `actionlint -shellcheck` clean, `no-emdash` clean over 49
tracked files. `ci.yml` 78 leaves, `release.yml` 104, `self-check.yml` 14,
`no-emdash.yml` 28: all identical to `origin/main`, comments only.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
CI-REQUIRED-CHECKS, theorg/.githubleg.The item
#39named aPRE-EXISTINGand left it: rulesetci-required-checksdid not requireself-check.yml'sscriptsjob, so this repository's own unit suite ran on every pull request andcould block none of them. A green check that cannot block a merge is documentation.
What this changes
scriptsbecomes a required context. The precondition was checked rather than assumed, in thisorder:
self-check.ymlhas nopaths:filter and no job-levelif:, so the job is unconditional. Thestanding record read it as path conditional; it is not, and never was. It simply postdated three
branches that have since gone, which is the corrected cause and the one that makes this reversible
without a workflow change.
mainat41f883e. A context required before its workflow has completedon
mainleaves every pull request pending and unmergeable with nothing saying why.scriptswas read off the realpull_requestcheck runs of every open pull request, and off thelast twelve consecutive
pull_requestruns of the workflow, all of which carry both jobs.Contexts are read off check runs here, never off a workflow's
name:: this workflow is titled"Self-check" and its contexts are the bare job ids.
test/self-check.test.mjspins what a ruleset cannot see. Nothing inside a repository can observeits own ruleset, so two edits un-require a job with no ruleset change and no error anywhere:
emitted by nothing, so every pull request sits pending, which is worse than red because nothing says
why. The job ids are pinned, so a rename reds in the pull request that made it, beside a comment
saying to move the ruleset entry too.
operate on.
node --test "test/*.test.mjs"exits 0 as happily over one file as over all of them.The glob is read out of the workflow and compared against the test files actually on disk, so this
is a real comparison and not a spelling check.
A
paths:filter and a job-levelif:are refused too, on a reason that needs no theory about how askip resolves: a context that does not run on every pull request cannot gate every pull request.
What a skipped required context does to a merge is deliberately stated nowhere in this change.
This repository has not settled that against a primary source and a live run,
deid'sci.ymlrecords the same question as open against
run-actionlintand says not to change that input until itis settled, and whatever the answer is it governs thirteen repositories at
@main. It is raised asits own item rather than answered in a comment here.
Every assertion was mutation checked. Narrowing the glob, adding
paths:, adding a jobif:,dropping the
pushtrigger, unquoting the glob, dropping the checkout, renaming the job, appending--test-name-pattern, replacing the command withecho, dropping a test file into a subdirectory,and a
**widening that does not actually cross a separator all red, each on the assertion that namesit. A correct
test/**/*.test.mjswidening passes, which is deliberate: the file's own commentstell a future author to widen the glob when a directory of tests appears, and a guard that reds on its
own documented remedy gets deleted rather than obeyed.
The glob translator follows node's rule rather than a guess, measured on a real run over the same
tree:
test/**/*.test.mjsselects 347 tests andtest/**.test.mjsselects 346, so**crosses aseparator only as a whole segment. Reading a bare
**as crossing one fails in the dangerousdirection, reporting a selection wider than the one CI runs.
The case that asserts the invocation owns the shape (
node --test, one quoted argument, noselector smuggled in beside it). Whether the selection is complete is the next case's job. Pinning
the glob's literal value in the first would have conflated the two and reded on a widening as loudly
as on a narrowing.
The
ci / prepublishcensus, recorded rather than fixedTurning both pre-publish layers on by default made them run in all thirteen callers. It did not
make them block in any. The layers live in their own job, so
ci / prepublishis a separatecheck-run context, and a caller whose ruleset requires the
verifymatrix andci / actionlintdoesnot thereby require it: a red pre-publish gate shows a red X and the merge button stays green.
Censused 2026-08-07 against real
pull_requestcheck runs rather than workflow names. All thirteencallers emit
ci / prepublish, as expected now that both layers default on and no caller passeseither input.
cliis the only one whose ruleset requires it. That is one line per caller, inthat caller's own ruleset, and it is not reachable from this repository, so it is written down with
its derivation command instead of being fixed here. It belongs to each caller's own
CI-REQUIRED-CHECKSpass.The backlog's reading that
clirequires "only the twoverifymatrix contexts plusactionlint" isstale:
cliis in fact the one caller that does require the pre-publish context.No context count is written anywhere, and nothing asserts a context IS required
Nothing in a repository can observe its own ruleset, so a list or a count goes stale the next time
this workflow grows a job, and prose asserting the current state is the same defect wearing different
clothes. Every addition here states the rule and gives the derivation command instead; the test file
says outright that whether a context is required is not knowable from inside a repository, so nothing
in it asserts that.
The long "how to require a context" procedure previously duplicated in
no-emdash.ymlis replaced bya pointer to the one place it is now set out in full. The precondition itself is short and
load-bearing at each site, so it stays stated at each; only the procedure is stated once. That file
keeps the half that is genuinely local to it: which of its two jobs may ever be required.
No workflow logic moved
ci.ymlandrelease.yml, the two called at@mainby thirteen repositories, are untouched.self-check.ymlandno-emdash.ymlwere parsed with a real YAML parser at base and at head andflattened to leaf paths. Both are identical leaf for leaf: comments only, no job id, step, condition,
run:body,uses:ref, input or output name moved.Gate
scripts/verify.sh .githubhas no ladder here (no lockfile) and exits 66, as documented. The realgate, run by hand:
node --test "test/*.test.mjs": 346 pass, 0 fail (339 before this change).actionlint -shellcheck shellcheck: clean, exit 0.node scripts/check-no-emdash.mjs: clean over every tracked file, none exempt.Deliberately not done here
no-emdash-messagesis not required and must not be, per the standing exemption. Dependabotpastes the dependency's upstream release notes into the pull request body, and
dependabot.ymlkeeps a standing weekly
github-actionsschedule, so such a pull request arrives on someone else'sclock indefinitely.
ci / prepublishwithout requiring it. Reported above, notchanged: each is that repository's own ruleset.
resolvedSelection()divergence (vitest list --filesOnlyin the gate againstvitest runin a different CI job) belongs to whichever repository owns that policy, not here.
cli'srelease.ymlcomment, which carries a wrong context count and a claim about Version PRsarriving with zero check runs. That claim was true when written and was wrong for about six days,
not months, which is exactly why it is its own slice with the dated instance kept.
codeql-actioninit/upload-sarifpair.A set that must move together has to land on one sha in one pull request, and that is not this one.
🛑 DO NOT MERGE WITHOUT A HUMAN DECISION
Three
gate-refuterpasses, the ADR 0016 maximum, and the last one refused. Reported as theycame, including the refusals.
7f14fe7: NOT REFUTED, four minorINTRODUCEDfindings, no blockers.5b81279: REFUTED, oneINTRODUCEDmajor. The remedy for pass 1 had claimed ajob-level
if:strands a pull request while a step-level one goes green. GitHub documents that askipped job reports success and does not prevent a merge even as a required check, so the contrast
was false. This repo ships
ci.ymlwithactionlintbehindif: inputs.run-actionlintwhileci / actionlintis a required context in caller rulesets, so a maintainer setting that inputfalse was being told to expect a loud strand across thirteen repos.
3bc2dad: REFUTED,INTRODUCEDmajor, called a blocker. The skip-semanticstheory had been deleted, but a claim that the page and the file now state nothing about it was
written in its place, and both files still did, further down. A rewrite wearing a deletion's
clothes.
7a762d9is the ADR 0016 cut-back and it is UNGRADED, because there is no fourth pass. It ispurely subtractive: every sentence characterising what a skipped required context does to a merge is
deleted, and no sentence replaces it, including the meta-sentence that claimed they were gone. It
also deletes the two pre-existing copies of the same false claim, here and in
no-emdash.yml.The guard, the ruleset act and the census are unaffected by any of that and were never the subject of
a finding. Every finding across all three passes was about prose.
The question that has to be settled by a human, in its own change, and that this pull request
deliberately no longer answers: what does a skipped required context do to a merge?
deid'sci.ymlrecords the same question againstrun-actionlintand says not to change that input untilit is settled. It is live across thirteen repos through
ci.yml'sif: ${{ inputs.run-actionlint }}.