Finding
.github/workflows/ci-pipeline.yml pins the exemption ledger checkout at line 884:
and the comment block immediately above it (lines 876–879) states the rule that governs it:
The pin and the first entry move in the SAME commit: whoever adds a slug must
bump this ref to the commit that carries it, or their own exemption will not
be read.
This invariant cannot be satisfied. A ref: must name a commit that already exists. The commit that carries the new ledger entry does not exist until it is created, so no single commit can both add a slug and pin itself. The rule as written asks for a SHA to be written into the commit that produces that SHA.
Measured evidence
Under squash merge the landed commit is not even the branch head that was authored, so the target SHA is unknowable in advance. Last 12 merged PRs on this repository, mergeCommit.oid vs headRefOid:
gh pr list --repo hyperpolymath/standards --state merged --limit 12 \
--json number,mergeCommit,headRefOid
| PR |
merge SHA |
head SHA |
equal? |
| #977 |
68acee77 |
f5cde2f6 |
no |
| #974 |
417094b2 |
48415f6b |
no |
| #973 |
2eb9bb19 |
529296b5 |
no |
| #971 |
8aea869b |
7ff3ae0b |
no |
| #970 |
436a89b3 |
3f654c6f |
no |
| #965 |
12d76e89 |
c522733a |
no |
| #962 |
051f98ec |
04593c85 |
no |
| #961 |
3a7c90ae |
d3fc4d61 |
no |
| #959 |
38d211f1 |
0c261f12 |
no |
| #954 |
9c256b67 |
e60e3ad6 |
no |
| #952 |
be6c9580 |
2c602c44 |
no |
| #948 |
e977cc67 |
d88410af |
no |
12 of 12 differ. Repository settings return allow_merge_commit=true, allow_squash_merge=true, allow_rebase_merge=true, so the strategy is chosen per merge and is not predictable even in principle.
This is a documentation defect, not a security hole
The gate fails closed, exactly as the surrounding comment claims. At a2ff696a the ledger file does not exist; the checkout is continue-on-error: true and the following step blocks when it cannot read the ledger, so "could not read the ledger" and "empty ledger" yield the same verdict. Nobody is wrongly exempted. The cost is a round trip, never a false pass.
The harm is that the rule misdirects whoever first seeds the ledger: following it literally is impossible, and the natural workaround (pin @main) is explicitly and correctly forbidden four lines below.
Proposed cure
Replace the simultaneity requirement with the ordering that actually works, and name the third step, which is the one most likely to be missed:
- PR A adds the slug(s) to
.machine_readable/pipeline-allow.txt, carrying the column-0 Ratchet-exception: .machine_readable/pipeline-allow.txt — <why> trailer. It lands at some SHA S1.
- PR B bumps
ref: to S1. It lands at S2.
- Between the two, the gate stays blocked — which is the intended conservative state.
- ⚠ Callers must then be re-pinned. A caller pinned to
ci-pipeline.yml@<sha> older than S2 checks out the ledger at the old ref, so its exemption is not read. Every caller relying on an exemption must move to S2.
Acceptance criteria
Surfaced while executing the dry-run wave that seeds this ledger by observation. Decision sheet: #787.
🤖 Generated with Claude Code
https://claude.ai/code/session_01Ji1bq3TypfycfUPAR7hSxR
Finding
.github/workflows/ci-pipeline.ymlpins the exemption ledger checkout at line 884:and the comment block immediately above it (lines 876–879) states the rule that governs it:
This invariant cannot be satisfied. A
ref:must name a commit that already exists. The commit that carries the new ledger entry does not exist until it is created, so no single commit can both add a slug and pin itself. The rule as written asks for a SHA to be written into the commit that produces that SHA.Measured evidence
Under squash merge the landed commit is not even the branch head that was authored, so the target SHA is unknowable in advance. Last 12 merged PRs on this repository,
mergeCommit.oidvsheadRefOid:68acee77f5cde2f6417094b248415f6b2eb9bb19529296b58aea869b7ff3ae0b436a89b33f654c6f12d76e89c522733a051f98ec04593c853a7c90aed3fc4d6138d211f10c261f129c256b67e60e3ad6be6c95802c602c44e977cc67d88410af12 of 12 differ. Repository settings return
allow_merge_commit=true,allow_squash_merge=true,allow_rebase_merge=true, so the strategy is chosen per merge and is not predictable even in principle.This is a documentation defect, not a security hole
The gate fails closed, exactly as the surrounding comment claims. At
a2ff696athe ledger file does not exist; the checkout iscontinue-on-error: trueand the following step blocks when it cannot read the ledger, so "could not read the ledger" and "empty ledger" yield the same verdict. Nobody is wrongly exempted. The cost is a round trip, never a false pass.The harm is that the rule misdirects whoever first seeds the ledger: following it literally is impossible, and the natural workaround (pin
@main) is explicitly and correctly forbidden four lines below.Proposed cure
Replace the simultaneity requirement with the ordering that actually works, and name the third step, which is the one most likely to be missed:
.machine_readable/pipeline-allow.txt, carrying the column-0Ratchet-exception: .machine_readable/pipeline-allow.txt — <why>trailer. It lands at some SHA S1.ref:to S1. It lands at S2.ci-pipeline.yml@<sha>older than S2 checks out the ledger at the old ref, so its exemption is not read. Every caller relying on an exemption must move to S2.Acceptance criteria
ci-pipeline.yml:876–879no longer asserts that the pin and the entry move in the same commit.ref:bump do not see the exemption and must be re-pinned.@maindirectly below is retained unchanged.Surfaced while executing the dry-run wave that seeds this ledger by observation. Decision sheet: #787.
🤖 Generated with Claude Code
https://claude.ai/code/session_01Ji1bq3TypfycfUPAR7hSxR