fix(mirror): open the adopter-pin PR with a credential that lets it be graded (#1393) #1396

Merged
bosun merged 1 commit from i/1393-the-pin-pr-must-be-graded into main 2026-09-06 23:25:49 +02:00
Owner

🔴 STACKED ON #1395. Both edit the same step of mirror-release.yml, so this branch is based on #1394's head rather than on main — two PRs into one file is the composition hazard cd#157 is about, and hand-composing them afterwards is what that row refuses. Merge #1395 first and this becomes a one-commit diff.


readme-pin-check grades every human PR in this repo and did not grade the one PR that moves the pins it exists to grade. An absent required context and a failing one are indistinguishable from the board (¶34), so the gate's context list is complete while simply omitting one PR.

I measured the cause before choosing a route, because the three candidates fix different things

@bosun's tracker offered three routes and named them as if interchangeable. They are not: pull_request_target changes who TRIGGERS; a job-posted status changes who REPORTS. If the cause is the token class, only one of them addresses it. (He agreed and asked me to edit the AC rather than work around it; done.)

the SAME TREE under a human credential   ->  graded within seconds        (@bosun)
300-PR window, state=all, positive control at 30 release-bot rows:
  forgejo-actions-authored PRs            1   #1391, ungraded
  release-bot PAT-authored PRs           30   all graded

The variable is the token class — not the content, not the branch, not the target. ⚠️ n=1 on the failing arm. The controlled comparison is the identical-tree re-push, not the census; the census only rules out "bot PRs are not graded" as the general claim, which it does decisively at 30 rows.

The change

Open the PR with RELEASE_TOOLKIT_TOKEN when provisioned, defaulted in bash${{ secrets.A || secrets.B }} does not fall back on Forgejo's expression engine (#41/#47's TC-1, the form release.yml already uses at three sites). The two arms differ in whether the resulting PR can be graded at all, so each announces itself.

Then read back the opened PR's status count and refuse at zero. A disclosure that cannot change the exit status is punctuation; this one exit 1s and names both remedies. The job that opened the PR is the only actor placed to notice that nothing is grading it.

Mutations — and two of them passed the first draft

Each run separately, against a snapshot, applied-count printed, unmutated control at both ends at applied=0.

mutation arm
statuses never read refusal
zero count does not reach exit 1 refusal
refusal not annotated ::error:: refusal — passed the first draft
remedy stops naming the PAT refusal — passed the first draft
remedy loses the human-credential route refusal
override env removed credential
selected token computed but unused credential
fallback arm goes silent credential

🔴 ::error:: and RELEASE_TOOLKIT_TOKEN each occur elsewhere in the same step — in the missing-head-sha error and in the fallback warning — so a needle over the whole block was satisfied by occurrences unrelated to the fact it checks. That is crew-doctrine#163's first region, on my own arm, for the second time tonight. Both assertions are now scoped to the refusal line, and both mutations fire.

Not changed

What the verb grades. This is the INVOCATION one layer further out than #1382: that one widened what the gate is asked to grade; this is whether it is asked at all. Third time on the same axis tonight.

Verification

go build · go vet · gofmt -l · golangci-lint run 0 issues · go test ./... · workflow-parse-check · rt gitea-twin --check rc=0 (regenerated, not hand-edited) · bats 126/126 · rt fragment-check rc=0.

📌 The fragment failed check 7 first — a 31-word sentence joined across a colon. Second time tonight in the same place: the colon is where I join two claims, and it is where check 7 fires.

Requesting @surveyor.

🤖 Generated with Claude Code

https://claude.ai/code/session_01DbnWrAAh3iGuPAQF53nuXG

🔴 **STACKED ON `#1395`.** Both edit the same step of `mirror-release.yml`, so this branch is based on `#1394`'s head rather than on main — two PRs into one file is the composition hazard `cd#157` is about, and hand-composing them afterwards is what that row refuses. **Merge `#1395` first and this becomes a one-commit diff.** --- `readme-pin-check` grades every human PR in this repo and did not grade the one PR that moves the pins it exists to grade. **An absent required context and a failing one are indistinguishable from the board** (`¶34`), so the gate's context list is complete while simply omitting one PR. ## I measured the cause before choosing a route, because the three candidates fix different things @bosun's tracker offered three routes and named them as if interchangeable. They are not: **`pull_request_target` changes who TRIGGERS; a job-posted status changes who REPORTS.** If the cause is the token class, only one of them addresses it. *(He agreed and asked me to edit the AC rather than work around it; done.)* ``` the SAME TREE under a human credential -> graded within seconds (@bosun) 300-PR window, state=all, positive control at 30 release-bot rows: forgejo-actions-authored PRs 1 #1391, ungraded release-bot PAT-authored PRs 30 all graded ``` **The variable is the token class — not the content, not the branch, not the target.** ⚠️ **`n=1` on the failing arm.** The controlled comparison is the identical-tree re-push, not the census; the census only rules out *"bot PRs are not graded"* as the general claim, which it does decisively at 30 rows. ## The change **Open the PR with `RELEASE_TOOLKIT_TOKEN` when provisioned**, defaulted in **bash** — `${{ secrets.A || secrets.B }}` does not fall back on Forgejo's expression engine (`#41`/`#47`'s TC-1, the form `release.yml` already uses at three sites). The two arms differ in whether the resulting PR can be graded at all, so **each announces itself**. **Then read back the opened PR's status count and refuse at zero.** A disclosure that cannot change the exit status is punctuation; this one `exit 1`s and names both remedies. The job that opened the PR is the only actor placed to notice that nothing is grading it. ## Mutations — and two of them passed the first draft Each run separately, against a **snapshot**, applied-count printed, unmutated control at both ends at `applied=0`. | mutation | arm | |---|---| | statuses never read | refusal | | zero count does not reach `exit 1` | refusal | | refusal not annotated `::error::` | refusal — **passed the first draft** | | remedy stops naming the PAT | refusal — **passed the first draft** | | remedy loses the human-credential route | refusal | | override env removed | credential | | selected token computed but unused | credential | | fallback arm goes silent | credential | 🔴 **`::error::` and `RELEASE_TOOLKIT_TOKEN` each occur elsewhere in the same step** — in the missing-head-sha error and in the fallback warning — **so a needle over the whole block was satisfied by occurrences unrelated to the fact it checks.** That is `crew-doctrine#163`'s *first* region, on my own arm, for the second time tonight. Both assertions are now scoped to the refusal **line**, and both mutations fire. ## Not changed **What the verb grades.** This is the INVOCATION one layer further out than `#1382`: that one widened what the gate is *asked* to grade; this is whether it is asked at all. Third time on the same axis tonight. ## Verification `go build` · `go vet` · `gofmt -l` · **`golangci-lint run` 0 issues** · `go test ./...` · `workflow-parse-check` · `rt gitea-twin --check` rc=0 (regenerated, not hand-edited) · `bats` 126/126 · `rt fragment-check` rc=0. 📌 The fragment failed check 7 first — a **31-word** sentence joined across a colon. Second time tonight in the same place: **the colon is where I join two claims, and it is where check 7 fires.** Requesting @surveyor. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01DbnWrAAh3iGuPAQF53nuXG
fix(mirror): give the adopter-pin PR a body that can pass fragment-check
All checks were successful
readme-pin-check / check (pull_request) Successful in 32s
tests / workflow-schema (pull_request) Successful in 29s
tests / bats (pull_request) Successful in 31s
tests / dated-examples (pull_request) Successful in 31s
tests / shellcheck (pull_request) Successful in 23s
go-ci / lint + build + test (pull_request) Successful in 1m9s
go-ci / page landing-tree failure (pull_request) Has been skipped
tests / contract-paths (pull_request) Successful in 28s
register-check / register-drift check (pull_request) Successful in 49s
register-check / check (pull_request) Successful in 0s
workflow-parse-check / toolkit-self parse guard and controls (pull_request) Successful in 27s
workflow-parse-check / workflow parse and schema (pull_request) Successful in 28s
workflow-parse-check / check (pull_request) Successful in 0s
toolkit-self-gates / toolkit-self gates (PR's own rt) (pull_request) Successful in 50s
gitea-twin-check / check (push) Successful in 5s
prepared-uncut-check / toolkit-self prepared-uncut controls (push) Successful in 6s
tests / workflow-schema (push) Successful in 4s
go-ci / record reviewed vs landed commit (push) Successful in 16s
check-self-bootstrap / check (push) Successful in 24s
tests / shellcheck (push) Successful in 19s
tests / bats (push) Successful in 30s
tests / dated-examples (push) Successful in 28s
tests / contract-paths (push) Successful in 26s
prepared-uncut-check / prepared-but-uncut release (push) Successful in 44s
prepared-uncut-check / check (push) Successful in 0s
go-ci / lint + build + test (push) Successful in 1m6s
release / decide + act (push) Successful in 1m4s
release / release (push) Successful in 0s
go-ci / page landing-tree failure (push) Has been skipped
release / fire-cut (push) Has been skipped
8d2b16f31e
set-adopter-pin opens a PR after every mirror publish. It carried neither a
changelog.d/ fragment nor a `no-changelog:` declaration, and fragment-check is a
REQUIRED context on main -- so the job opened a PR that could not merge, for a
reason with nothing to do with its diff. #1391 sat red until a human edited the
body by hand.

A DECLARATION rather than a fragment, decided rather than defaulted: a fragment
written here would land in the NEXT release's notes while describing the pins of
the release that just published. That is the case #498 built the declaration for.

The body is now a FILE, .forgejo/pr-body/adopter-pin.md, not a shell string. That
is what lets an arm grade the exact bytes the job sends: a heredoc inside the
workflow's block scalar cannot do it without carrying YAML indentation into the PR
body, and extracting a shell assignment by regex would grade a reconstruction.

Two arms, because one is not enough:

  TestAdopterPinBodySatisfiesFragmentCoverage   runs the real FragmentCoverage
                                                over the file
  TestSetAdopterPinSendsTheBodyFileItIsGradedFrom  asserts the workflow READS that
                                                path and assigns it to the $body
                                                the payload carries

The second exists because a correct body that nothing reads is worth nothing --
crew-doctrine#163's WIRE region, where producer and consumer are each fine.

Mutations, each run separately against a SNAPSHOT of the file (never against HEAD,
per crew-doctrine#144), with the applied-count printed beside every result and an
unmutated control at both ends reporting applied=0:

  body loses the declaration        -> body arm
  declaration with an empty reason  -> body arm
  two declarations                  -> body arm
  workflow reads a different path   -> wire arm
  payload stops carrying $body      -> wire arm
  file read but not assigned        -> wire arm

The wire arm strips comments, and that clause is measured rather than assumed: the
step's comment names the body path, so with the path removed from the CODE only,
strip ON reddens and strip OFF goes GREEN. The comment was added to the workflow
for a reader's sake and it made the mutation discriminate -- the first draft could
not, because nothing in the comments carried the needle.

No change to fragment-check's rules. The gate is right; the generated body was
wrong.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DbnWrAAh3iGuPAQF53nuXG
fix(mirror): open the adopter-pin PR with a credential that lets it be graded
All checks were successful
tests / shellcheck (pull_request) Successful in 4s
ac-closure-check / ac-closure check (pull_request) Successful in 45s
changelog-body-check / check (pull_request) Successful in 0s
fragment-check / check (pull_request) Successful in 0s
ac-closure-check / check (pull_request) Successful in 0s
readme-pin-check / check (pull_request) Successful in 29s
tests / workflow-schema (pull_request) Successful in 31s
workflow-parse-check / toolkit-self parse guard and controls (pull_request) Successful in 24s
go-ci / lint + build + test (pull_request) Successful in 1m8s
workflow-parse-check / workflow parse and schema (pull_request) Successful in 29s
workflow-parse-check / check (pull_request) Successful in 0s
go-ci / page landing-tree failure (pull_request) Has been skipped
tests / bats (pull_request) Successful in 1m22s
toolkit-self-gates / toolkit-self gates (PR's own rt) (pull_request) Successful in 1m5s
check-self-bootstrap / check (push) Successful in 6s
tests / dated-examples (push) Successful in 5s
tests / contract-paths (push) Successful in 4s
go-ci / record reviewed vs landed commit (push) Successful in 15s
tests / shellcheck (push) Successful in 4s
gitea-twin-check / check (push) Successful in 21s
prepared-uncut-check / toolkit-self prepared-uncut controls (push) Successful in 21s
tests / workflow-schema (push) Successful in 24s
prepared-uncut-check / prepared-but-uncut release (push) Successful in 42s
prepared-uncut-check / check (push) Successful in 0s
release / decide + act (push) Successful in 58s
release / release (push) Successful in 0s
go-ci / lint + build + test (push) Successful in 1m6s
tests / bats (push) Successful in 1m19s
release / fire-cut (push) Has been skipped
go-ci / page landing-tree failure (push) Has been skipped
a2d88eeea3
readme-pin-check grades every human PR in this repo and did not grade the one PR
that moves the pins it exists to grade. #1391 carried ZERO statuses and ZERO
action tasks over four minutes while the scheduler was demonstrably busy, and the
identical tree re-pushed under a human credential was graded within seconds.

An ABSENT required context and a FAILING one are indistinguishable from the board
(¶34), so the gate's context list is complete while simply omitting one PR, and
every surface anyone checks reads healthy.

MEASURED THE CAUSE BEFORE CHOOSING A ROUTE, because the tracker's three candidate
routes are remedies for DIFFERENT causes -- pull_request_target changes who
triggers, a job-posted status changes who reports, and they are not substitutes.

  the same tree under a human credential  -> graded within seconds  (@bosun)
  300-PR window, state=all, positive control at 30 release-bot rows:
    forgejo-actions-authored PRs           1   (#1391, ungraded)
    release-bot PAT-authored PRs          30   (all graded)

So the variable is the token class, not the content, the branch or the target.
n=1 on the failing arm -- the controlled comparison is the identical-tree
re-push, not the census, and the census only rules out "bot PRs are not graded"
as the general claim.

The PR is opened with RELEASE_TOOLKIT_TOKEN when provisioned, defaulted in BASH
because `${{ secrets.A || secrets.B }}` does not fall back on Forgejo's expression
engine (#41/#47's TC-1, the form release.yml already uses at three sites). The two
arms differ in whether the resulting PR can be graded at all, so each announces
itself.

And the job then READS BACK the opened PR's status count and refuses at zero. A
disclosure that cannot change the exit status is punctuation; this one exits 1 and
names both remedies, because the job that opened the PR is the only actor placed
to notice that nothing is grading it.

Mutations, each separate, against a SNAPSHOT, with the applied-count printed and an
unmutated control at both ends at applied=0:

  statuses never read              -> refusal arm
  zero count does not exit 1       -> refusal arm
  refusal not annotated ::error::  -> refusal arm   (see below)
  remedy stops naming the PAT      -> refusal arm   (see below)
  remedy loses the human route     -> refusal arm
  override env removed             -> credential arm
  selected token computed, unused  -> credential arm
  fallback arm goes silent         -> credential arm

TWO OF THOSE PASSED THE FIRST DRAFT AND THE REASON IS THIS FILE'S OWN SUBJECT:
`::error::` and `RELEASE_TOOLKIT_TOKEN` each occur elsewhere in the same step -- in
the missing-head-sha error and in the fallback warning -- so a needle over the
whole block was satisfied by occurrences unrelated to the fact it checks. That is
crew-doctrine#163's FIRST region, on my own arm, again. Both assertions are now
scoped to the refusal LINE and both mutations fire.

No widening of what the verb grades. This is the INVOCATION, one layer further out
than #1382: that one widened what the gate is asked to grade, this one is whether
it is asked at all.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DbnWrAAh3iGuPAQF53nuXG
surveyor approved these changes 2026-09-06 23:15:56 +02:00
surveyor left a comment

APPROVE — reviewed at a2d88eeea3a2afa688a75beedf7a68485e328b16.

📌 The stacking resolved itself while I read: #1395 merged, main IS 8d2b16f3, so this is behind 0 and is the one-commit diff your body predicted. Announcing the stack and its remedy in the body is why that cost nothing — I graded the delta against the right base from the first command.

The route choice, which is the part I would have pushed on if you had not

You are right that the three routes are not interchangeable, and the distinction is the reviewable one: pull_request_target changes who TRIGGERS; a job-posted status changes who REPORTS. If the cause is the token class, only the credential route addresses it — the other two would have produced a green board with the same defect underneath, which is the failure this PR exists to remove.

⚠️ And your n=1 caveat is correctly placed rather than decorative. The census rules out "bot PRs are not graded" as a general claim at 30 rows with a positive control; it does not establish the mechanism. The identical-tree re-push does, and that is the controlled comparison. Two claims, two instruments, and you said which carries which.

TC-1

${{ secrets.A || secrets.B }} on a non-comment line : 0
the one occurrence : inside the comment WARNING about the form
selection : PR_TOKEN_OVERRIDE into env, then `pr_token="${PR_TOKEN_OVERRIDE:-}"` in bash

Defaulted in bash, not in the expression engine. My grep flagged the comment and reading the context resolved it — ¶39, the change that avoids a hazard describing it.

Mutations, run separately, each asserted APPLIED before its verdict was read

statuses never read (whole block commented)  -> TestSetAdopterPinRefusesAnUngradedPR
zero count no longer reaches exit 1          -> TestSetAdopterPinRefusesAnUngradedPR
::error:: stripped from the refusal LINE     -> TestSetAdopterPinRefusesAnUngradedPR   ← inert in your first draft
remedy stops naming the PAT                  -> TestSetAdopterPinRefusesAnUngradedPR   ← inert in your first draft
override env removed                         -> TestSetAdopterPinPrefersThePATAndDefaultsInBash
selected token computed but unused           -> TestSetAdopterPinPrefersThePATAndDefaultsInBash
fallback arm goes silent                     -> TestSetAdopterPinPrefersThePATAndDefaultsInBash
gitea-twin --check                           -> PASS, 11 twins match
control                                      -> 4 arms selected, 0 failures

🔑 Both previously-inert assertions fire now, and scoping them to the refusal LINE is the correct repair rather than a tighter needle. ::error:: and RELEASE_TOOLKIT_TOKEN each occur elsewhere in the same step — in the missing-head-sha error and in the fallback warning — so a block-wide needle was satisfied by occurrences unrelated to the fact it checks. That is crew-doctrine#163's first region and you caught it on your own arm.

The refusal can change the exit statusexit 1, not a warning — and names both remedies. A disclosure that cannot alter control flow is punctuation; this one holds the job. And the job that opened the PR is genuinely the only actor placed to notice that nothing is grading it.

Two of my own mutations graded nothing first

⚠️ LN=$(grep -n … | cut -d: -f1) matched TWO lines, so the variable held 137\n199 and sed died — I assumed one match and did not assert it.

⚠️ And my first "statuses never read" was an UNFAIR mutation: I commented only the assignment's first line, leaving /statuses on an uncommented continuation, so the arm was right not to fire. Commenting the whole block fires it — which also demonstrates that setAdopterPinRun strips comments, so the needle cannot be satisfied by commented-out code. I nearly reported a coverage gap that was my mutation's fault.

Scope

What the verb grades is unchanged. This is the invocation one layer further out than #1382: that widened what the gate is asked to grade; this is whether it is asked at all. Third time tonight on that axis, and the ladder is worth naming somewhere durable — no verb, no invocation, no trigger.

**APPROVE** — reviewed at `a2d88eeea3a2afa688a75beedf7a68485e328b16`. 📌 **The stacking resolved itself while I read: `#1395` merged, `main` IS `8d2b16f3`, so this is behind 0 and is the one-commit diff your body predicted.** *Announcing the stack and its remedy in the body is why that cost nothing — I graded the delta against the right base from the first command.* ## The route choice, which is the part I would have pushed on if you had not ✅ **You are right that the three routes are not interchangeable, and the distinction is the reviewable one:** *`pull_request_target` changes who TRIGGERS; a job-posted status changes who REPORTS.* **If the cause is the token class, only the credential route addresses it** — the other two would have produced a green board with the same defect underneath, which is the failure this PR exists to remove. ⚠️ **And your `n=1` caveat is correctly placed rather than decorative.** *The census rules out "bot PRs are not graded" as a general claim at 30 rows with a positive control; it does not establish the mechanism. The identical-tree re-push does, and that is the controlled comparison.* **Two claims, two instruments, and you said which carries which.** ## TC-1 ``` ${{ secrets.A || secrets.B }} on a non-comment line : 0 the one occurrence : inside the comment WARNING about the form selection : PR_TOKEN_OVERRIDE into env, then `pr_token="${PR_TOKEN_OVERRIDE:-}"` in bash ``` ✅ **Defaulted in bash, not in the expression engine.** *My grep flagged the comment and reading the context resolved it — `¶39`, the change that avoids a hazard describing it.* ## Mutations, run separately, each asserted APPLIED before its verdict was read ``` statuses never read (whole block commented) -> TestSetAdopterPinRefusesAnUngradedPR zero count no longer reaches exit 1 -> TestSetAdopterPinRefusesAnUngradedPR ::error:: stripped from the refusal LINE -> TestSetAdopterPinRefusesAnUngradedPR ← inert in your first draft remedy stops naming the PAT -> TestSetAdopterPinRefusesAnUngradedPR ← inert in your first draft override env removed -> TestSetAdopterPinPrefersThePATAndDefaultsInBash selected token computed but unused -> TestSetAdopterPinPrefersThePATAndDefaultsInBash fallback arm goes silent -> TestSetAdopterPinPrefersThePATAndDefaultsInBash gitea-twin --check -> PASS, 11 twins match control -> 4 arms selected, 0 failures ``` 🔑 **Both previously-inert assertions fire now, and scoping them to the refusal LINE is the correct repair rather than a tighter needle.** *`::error::` and `RELEASE_TOOLKIT_TOKEN` each occur elsewhere in the same step — in the missing-head-sha error and in the fallback warning — so a block-wide needle was satisfied by occurrences unrelated to the fact it checks.* **That is `crew-doctrine#163`'s first region and you caught it on your own arm.** ✅ **The refusal can change the exit status** — `exit 1`, not a warning — **and names both remedies.** *A disclosure that cannot alter control flow is punctuation; this one holds the job.* **And the job that opened the PR is genuinely the only actor placed to notice that nothing is grading it.** ## Two of my own mutations graded nothing first ⚠️ **`LN=$(grep -n … | cut -d: -f1)` matched TWO lines, so the variable held `137\n199` and `sed` died** — I assumed one match and did not assert it. ⚠️ **And my first "statuses never read" was an UNFAIR mutation**: I commented only the assignment's first line, leaving `/statuses` on an uncommented continuation, so the arm was right not to fire. **Commenting the whole block fires it — which also demonstrates that `setAdopterPinRun` strips comments, so the needle cannot be satisfied by commented-out code.** *I nearly reported a coverage gap that was my mutation's fault.* ## Scope **What the verb grades is unchanged.** This is the invocation one layer further out than `#1382`: that widened what the gate is *asked* to grade; this is whether it is asked at all. **Third time tonight on that axis, and the ladder is worth naming somewhere durable — no verb, no invocation, no trigger.**
bosun merged commit a2d88eeea3 into main 2026-09-06 23:25:49 +02:00
bosun deleted branch i/1393-the-pin-pr-must-be-graded 2026-09-06 23:25:49 +02:00

Landing identity record

  • PR: #1396
  • landed commit (server merge_commit_sha): a2d88eeea3a2afa688a75beedf7a68485e328b16
  • effective official approval(s):
    • @surveyor, review #6845, stamped commit: a2d88eeea3a2afa688a75beedf7a68485e328b16
  • replay comparison: no identity change (stamped SHA equals landed SHA)

This is a post-merge identity record. It does not retroactively review the landed object; it records whether the server landed the object that an official approval named.

<!-- release-toolkit:landing-review-record-v1 landed=a2d88eeea3a2afa688a75beedf7a68485e328b16 --> ## Landing identity record - PR: #1396 - landed commit (server merge_commit_sha): `a2d88eeea3a2afa688a75beedf7a68485e328b16` - effective official approval(s): - @surveyor, review #6845, stamped commit: `a2d88eeea3a2afa688a75beedf7a68485e328b16` - replay comparison: no identity change (stamped SHA equals landed SHA) This is a post-merge identity record. It does not retroactively review the landed object; it records whether the server landed the object that an official approval named.
Sign in to join this conversation.
No description provided.