bootstrap-pin: generate the pin, stop failing PRs that cannot fix it, and terminate - #78
Merged
Merged
Conversation
The gate added in #73 was right about the facts and wrong about where it reported them. Every PR touching a fetched file went red on FRESHNESS, and none of them could do anything about it: SUM_* is content-addressed and knowable on a branch, but PIN is a COMMIT and cannot name the merge commit before that commit exists. The pair has to agree with each other, so recording the new digests early just moves the red to INTEGRITY. #75 hit this, #77 hit it again, and an expected-red teaches people to ignore red. Worse, and unmentioned so far: org-defaults.yml also runs on push to main, so main went red the moment such a PR merged and stayed red until a human hand-typed a follow-up. That is a failing default branch as routine. Three changes, following the registry-graph.yml pattern already here: - gen-bootstrap-pin.mjs generates the pin and digests, and backs the gate's --check. #71 and #72 were both a human hand-copying 64 hex characters; the gate that caught them decided "correct" in code separate from the code that produces correct, which is the same bug one level up. - FRESHNESS is asserted on push and reported (t.diagnostic) on pull_request. INTEGRITY stays hard everywhere — a digest that does not describe its pin is a live bug on any branch, fixable where it is made. - A `pin` job regenerates on push to main and opens the bump PR itself. It terminates: the bump only edits .claude/README.md, which is deliberately not in the fetch set, so it cannot invalidate the pin it just wrote. A fixed branch, not registry-graph's timestamped one, so consecutive pushes update one PR instead of queueing PRs that pin superseded commits. Not auto-merged: the org requires a review on the default branch, and a generator that writes the trust anchor is the last place to drop that. Two bugs found while writing the tests, both now covered: - renderBootstrap anchored on `\s*$`, which under the `m` flag matches the newline too — each rewrite ate the following blank line and shifted the document up. Inherited from the old parse-only regex, where it was harmless; it is not harmless in a writer. - The documented confirm loop omitted stop-hook-git-check.sh, so anyone following the docs would never recompute that digest — how #72 shipped a stale one. Gated now, not just fixed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SG6WQtQtqLZg3gNeqJ3jTG
…pin is wrong The previous commit claims the loop terminates "because the bump only edits .claude/README.md, which is deliberately not in the fetch set, so it cannot invalidate the pin it just wrote". That claim is true and it is not sufficient, which running the loop showed in two commits: round 1 a fetched file changes on main → bump PR opened (correct) round 2 that bump PR merges → bump PR opened (wrong) round 3 anything at all pushes to main → bump PR opened (wrong) The README-is-not-fetched argument establishes that the CONTENT stays consistent across a bump. It says nothing about the commit id — and the commit id is what the rewrite keyed on. `next === source` was the only guard, and after a bump lands, `next` always differs from `source`, because PIN names the new commit rather than the one before it. So main proposed a pin bump after every push, forever, with no fetched file ever having changed. An automation that opens a PR on every push to the default branch is worse than the expected-red it was written to remove. So the decision moves off commit identity and onto content. planBump writes only when the pin is WRONG — INTEGRITY broken (the bootstrap is refusing a file right now) or STALE (the pin serves different bytes than this tree). A pin that trails HEAD but still serves byte-identical files is correct, and correct is a no-op. Round 2 is then silent and the loop stops there. Split out of main() rather than left inline: this is the termination argument in code, and it is the part that was wrong, so a test has to be able to reach it without a git repository to push around. gen-bootstrap-pin.test.mjs walks the same four rounds against a fixture world, including the commit a merged bump lands as — a new id serving identical bytes, which is precisely the case the old guard could not see. Deleting the gate fails that test and only that test; verified rather than assumed. Also kept: a test that renderBootstrap alone still does not converge at round 2. That is not a defect in renderBootstrap — it rewrites what it is told to — but it is the reason `next !== source` cannot be reused as a "something changed" signal across commits, and it should fail loudly if anyone tries. Verified end to end on a scratch branch, both directions: with the generator as it stood in the previous commit, round 2 writes and opens a second bump; with this one it reports "already serves this content" and opens nothing, while `--check` still calls the tree correct. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SG6WQtQtqLZg3gNeqJ3jTG
…d green The `pin` job added two commits ago never ran, and neither did `schema` — the job that runs every test in this repo. Its `gh pr create --body "..."` put prose at column 0 inside a `run: |` block scalar. A non-empty line at column 0 ends the scalar, and the `---` rule then starts a new YAML document, so org-defaults.yml stopped being a workflow at all: .github/workflows/org-defaults.yml, line 74, column 1 could not find expected ':' What makes this worth more than a fix is how it presented. GitHub does not report an unparseable workflow as a failing check on the PR — it records a run named by the file's raw path, and the jobs inside simply do not happen. PR #78 showed seven checks, every one green, with the test suite never executed. A break that removes your gates does not look like a break; it looks like success with fewer lines. Two changes: - The bump PR body moves to .github/bootstrap-pin-bump.md and is passed with --body-file. Prose wants blank lines, a `---` rule and a leading `_`, and none of those can survive inline in a block scalar. Keeping it in a file also makes the body reviewable as prose, which is what it is. - workflows.test.mjs asserts, for all twelve workflows, that nothing but a top-level key or a comment starts at column 0. Deliberately not a YAML parser: node has none built in, this repo's tests take no dependencies, and the one structural property that broke is exactly checkable. Verified against the real thing — restoring the previous file fails this test and names line 74. The test runs from `schema`, i.e. from inside org-defaults.yml, so it fully covers the other eleven and covers this one only before a push. The other half of the argument is that `schema` is a required check: one that never reports blocks the merge, which is what catches this file being already broken on a branch. Neither half is sufficient alone, and that is written down in the test's header rather than left for the next person to rediscover. Found by checking why `schema` was absent from an all-green PR rather than by reading the seven that were there. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SG6WQtQtqLZg3gNeqJ3jTG
…ly wired
The header claimed the residual gap — this test cannot run when the workflow it
runs from is the broken one — was closed by `schema` being a required status
check. Checked before merging rather than after: it is not one.
GET /repos/bounded-systems/.github/rules/branches/main
pull_request, required_signatures, required_linear_history,
non_fast_forward, deletion — no required_status_checks
`.github` is absent from the `ci-green` ruleset, which names
`front-desk-scheduler` only, and nothing else on this repo's default branch
looks at CI. A PR here can merge with every check red.
That makes the sentence a mechanism asserted from how it ought to be wired
rather than from how it is — the same species of claim as "it terminates
because the bump only edits README.md" two commits ago, and as the seven green
checks that were green because the tests never ran. Three times in one chain is
enough to stop writing it down as settled.
So the header now states the gap is OPEN, dates the measurement, and names the
fix: `.github` meets `merge-gate.md`'s documented prerequisite for joining
`ci-green` — one always-run job with a stable name, which `schema` is — so the
change is to org/rulesets/ci-green.json in `.github-private`, not here. Left
for its own PR rather than smuggled in: this repo cannot edit that ruleset, and
a claim about org config belongs where the org config is reviewed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SG6WQtQtqLZg3gNeqJ3jTG
bdelanghe
marked this pull request as ready for review
July 31, 2026 17:08
bdelanghe
added a commit
that referenced
this pull request
Jul 31, 2026
A session with both .github and .github-private attached got the org context TWICE — two byte-identical copies of .github-private/claude/context.md, joined by the dispatcher's "---" separator, so "Keep this LEAN, it counts against the context window every session" was paid twice. Neither hook is removable: without .github-private attached, .github's is the only one that can reach the context; without .github attached, .github-private's is. They collide only when BOTH are attached — exactly the session that verifies the chain. The duplication is a property of merging, so it is fixed at the merge point. mergeContexts compares trimmed text and keeps the first spelling, since the two producers differ only by a trailing newline, and logs what it dropped rather than absorbing it silently. Merged after #78, which makes FRESHNESS a diagnostic on pull_request: this PR changes a fetched file, so it could not have gone green before that landed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This was referenced Jul 31, 2026
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.
Follow-up to #77, which went red on FRESHNESS and could not do anything about it. Branches from
main, not from #77 — the two are independent, but the merge order matters (see the note on #77: this one first, then #77 goes green with no change to its diff).Three commits, and the second and third are both cases of the first being wrong in a way only running it would show.
1. Generate the pin, and stop failing PRs that cannot fix it
PINand theSUM_*digests are one atomic pair.SUM_*is content-addressed and knowable on a branch;PINis a commit, and cannot name a merge commit before that commit exists. So every PR touching a fetched file is red on FRESHNESS with no honest fix — recording the new digests early just moves the red to INTEGRITY. #75 hit it, #77 hit it again, and an expected-red teaches people to ignore red. Worse:org-defaults.ymlalso runs on push tomain, somainwent red the moment such a PR merged and stayed red until a human hand-typed a follow-up.gen-bootstrap-pin.mjsgenerates the pin and digests, and backs the gate's--check. register-mcp: register each repo's MCP servers at user scope #71 and bootstrap: verify fetched scripts before executing, and bump the pin #72 were both a human hand-copying 64 hex characters; the gate that caught them decided "correct" in code separate from the code that produces correct, which is the same bug one level up.t.diagnostic) onpull_request. INTEGRITY stays hard everywhere.pinjob regenerates on push tomainand opens the bump PR itself, on a fixed branch so consecutive pushes update one PR rather than queueing PRs that pin superseded commits. Not auto-merged: the org requires a review on the default branch, and a generator that writes the trust anchor is the last place to drop that.2. It did not terminate
That commit claims the loop terminates "because the bump only edits
.claude/README.md, which is not in the fetch set, so it cannot invalidate the pin it just wrote." Running the loop showed that is true and not sufficient:mainmainThe README-is-not-fetched argument establishes that the content stays consistent across a bump. It says nothing about the commit id — and the commit id is what the rewrite keyed on.
next === sourcewas the only guard, and after a bump landsnextalways differs, becausePINnames the new commit rather than the one before it. An automation that opens a PR on every push to the default branch is worse than the expected-red it was written to remove.So the decision moves off commit identity and onto content.
planBumpwrites only when the pin is wrong — INTEGRITY broken, or STALE against this tree. A pin that trailsHEADbut still serves byte-identical files is correct, and correct is a no-op.3. The
pinjob made the workflow unparseable, and CI showed greenThe job in commit 1 never ran. Neither did
schema, the job that runs every test in this repo. Itsgh pr create --body "..."put prose at column 0 inside arun: |block scalar; a non-empty line at column 0 ends the scalar, and the---rule then starts a new YAML document:The first push of this PR showed seven checks, every one green, with the test suite never executed. GitHub does not report an unparseable workflow as a failing check — it records a run named by the file's raw path, and the jobs inside simply do not happen. A break that removes your gates does not look like a break; it looks like success with fewer lines.
.github/bootstrap-pin-bump.md, passed with--body-file. Prose wants blank lines, a---rule and a leading_, none of which survive inline in a block scalar — and keeping it in a file makes the body reviewable as prose, which is what it is.workflows.test.mjsasserts, across all twelve workflows, that nothing but a top-level key or a comment starts at column 0. Not a YAML parser: node has none built in, this repo's tests take no dependencies, and the one structural property that broke is exactly checkable.It runs from
schema, i.e. from insideorg-defaults.yml, so it fully covers the other eleven and covers this one only before a push. The other half is thatschemais a required check — one that never reports blocks the merge, which is what catches this file being already broken on a branch. Neither half is sufficient alone; that is written into the test's header rather than left to be rediscovered.Verification
org-defaults,workflows,session-start-dispatch,register-mcp,gen-bootstrap-pin,bootstrap-pin.gen-bootstrap-pin.test.mjswalks the four rounds above against a fixture world, including the commit a merged bump lands as — a new id serving identical bytes, the case the old guard could not see. Deleting the gate fails that test and only that test; checked by deleting it, not assumed.already serves this contentand opens nothing, while--checkstill calls the tree correct.workflows.test.mjschecked against the real break: restoring the previousorg-defaults.ymlfails it and names line 74.Note on branch naming
The session's designated branch is
claude/ci-chain-verification-r8bpoh, which already carries #77. This is a separate change offmain, so it is on its own branch rather than stacked into that PR — say the word if you would rather have them together.