chore(mirror): land the adopter pin by the manifest's path-alpha/gamma rule (#1524) #1525

Merged
claude merged 4 commits from claude/1524-pin-direct-push into main 2026-10-02 23:23:48 +02:00
Member

Refs #1524

Reworked after the first review request. 96c391d made the job push-only. 73fc0bf replaces that with the cut manifest's own path rule, as agreed in session. ed9db78 fixes a lint finding, and dd871e8 takes review 7203's findings 1–3. Review the branch as a whole; the description below is current.

What changes

mirror-release.yml's set-adopter-pin job lands the pin by the same path-alpha / path-gamma rule as the cut's manifest commit (ADR-0007, reusable-release.yml:765), instead of always opening a PR:

Condition Path What happens
RELEASE_TOOLKIT_TOKEN set, rt preflight-push-whitelist alpha passes / warns alpha fast-forward push to the default branch as release-bot, then ls-remote read-back
preflight refuses (push disabled, or release-bot not whitelisted) gamma ::warning::, no push attempted, PR opened with the PAT, as #1378 did
alpha push rejected anyway (main moved, whitelist changed) gamma ::warning::, same PR
no RELEASE_TOOLKIT_TOKEN — ::error::, exit 1

Both paths share the steps before the commit:

  1. Fetch the live default-branch tip and detach onto it, so the commit is never built on the dispatch ref.
  2. Run readme-pin-check --fix, as before.
  3. Re-grade with plain readme-pin-check, because --fix writes and reports but does not re-grade, and nothing else grades an alpha commit before it lands.
  4. Run tag-action-digest-check, as before.
  5. Commit as release-bot.

Credentials:

  • The PAT is passed as RELEASE_TOKEN_OVERRIDE. Under that name internal/credentials resolves a single-token tier, so rt preflight-push-whitelist checks a user rather than a deploy key. The user it checks comes from FORGEJO_TOKEN, which the step exports from the same PAT.
  • git and curl both read the PAT from 0600 files; it is never in argv. The checkout's persisted credential is unset first. The PR half's curl calls used to pass the token on the command line.
  • The automatic token is gone from the job, and its permissions drop to contents: read.
  • Every network or response read that feeds an error message has its own fallback (|| code=000, || statuses=0, || remote_sha="", and the two jq reads). Under set -e, a failed substitution would otherwise end the step before the message that says what to do (review 7203).

Decision tree

  • Reuse the manifest rule, not a new variable. A repo variable (PR by default, push opt-in) was considered and declined. The manifest fork's switch is the credential, not configuration. In this repo the same PAT already pushes the post-cut commits to main, so an opt-in would protect nothing, and two rules for one fork are easy to confuse. If adopters ever need to choose, the variable belongs on both forks at once.
  • Unlike the manifest, a refused alpha falls back instead of failing. A cut must not stop half-done; a pin that lands later by PR is still correct.
  • No PAT → refuse, on both paths. The automatic token cannot push to protected main (#78), and a PR opened with it is never graded (#1393). Without the PAT, neither route can land.
  • Keep a PR wherever a human makes a choice the job cannot grade. recover-pending-cut is that case and is untouched.

What alpha gives up: the PR-time required contexts don't run before the pin lands. The push: main runs (go-ci, tests, check-self-bootstrap, gitea-twin-check) grade it right after, and the two pin-specific verdicts run inside the job beforehand. #1519, the same kind of mechanical edit, passed all 30 contexts.

Carry-forward from the PR-only job (AGENTS.md section 1)

Predecessor step Class Disposition
checkout, build rt (a) preserved
--fix; rc≠0 → warning + exit 0; git diff --quiet no-op (b) preserved verbatim
tag fetch + tag-action-digest-check gate (#1463) (b) preserved; now gates the commit, on both paths
CI commit identity via -c (#73) (b) kept via -c, now release-bot, matching its other direct pushes (63134a1, aa6f603)
PAT preferred, automatic token as bash fallback (#41/#47) (b) permission (#78) reimplemented: PAT required. The fallback is retired because it produced an ungraded PR (#1393)
branch push with the automatic token (b) now with the PAT, so the job needs only contents: read
body file with no-changelog: (#1394) (c) preserved; one paragraph added saying why the PR exists (alpha did not land)
poll the PR for ≥1 status (#1393) (c) preserved; polls with the PAT; remedy text updated for a PAT-only job
PR POST failure → ::warning::, exit 0 — now ::error::, exit 1: the pin did not land, and a green job would hide that
new — live-tip detach, re-grade, preflight + path choice, ff-only push, read-back

Verification

All of this ran on an LF clone; no workflow ran live.

  • go test ./... natively on Windows: every package ok.
  • bats tests/workflows.bats in rt-bats:local: 138/138.
  • rt gitea-twin --check: PASS (only the mirror-release.yml twin changed).
  • rt fragment-check (content mode): PASS, no warnings.
  • rt check-self-bootstrap: floating-pin (release-toolkit#456): main matches main by construction, so no re-pin (AGENTS.md section 2.1).
  • The path choice is executed, not just pattern-matched. TestSetAdopterPinChoosesItsPathByTheManifestRule runs the real fragment from the workflow under bash, with rt and git stubbed, in five cases:
    • preflight pass + push accepted → alpha lands, no PR;
    • preflight refuses → no push attempted, warning, PR;
    • push rejected → warning, PR;
    • read-back mismatch → non-zero exit, no PR;
    • read-back itself fails → the read-back message, non-zero exit, no PR.
  • Mutation-verified (Axis B): I applied 21 mutants to mirror-release.yml and each turned its targeted arm red. They covered the PAT/fallback, both fallback-to-PR edges, an alpha that falls through to a PR, the preflight being skipped, the token in curl argv, the live-tip detach, the re-grade exit, a forced push, the gamma branch targeting main, the read-back (two mutants), the zero-status exit, the body being swapped, the four substitution fallbacks, the extraheader unset, and the EXIT trap. The #1463 bats arm also went red with the digest exit removed.

Not verified: the job has not run end to end. It runs only after a mirror-release.yml dispatch, which publishes to gitea.com, so I did not dispatch it. The next mirror publish is its first real run. Look for adopter-pin path=alpha: pushed … or adopter-pin path=gamma: PR #….

Review request: verify at source (AGENTS.md section 6)

Please verify both halves against live state, not this description:

  1. Alpha half: release-bot is on main's push whitelist, and RELEASE_TOOLKIT_TOKEN is its PAT with push scope. Also check whether rt preflight-push-whitelist alpha can read branch protection as release-bot. If it can't, it warns and the job proceeds, so the push itself becomes the check. Evidence I used: the 2026-10-02 protection read, #1519 opened by release-bot with this secret, and 63134a1/aa6f603 pushed directly by release-bot.
  2. Gamma half: with alpha refused, the PR it opens still carries the no-changelog: body and gets graded. The arms cover this, but nothing has exercised it live since #1519.

🤖 Generated with Claude Code

Refs #1524 > **Reworked after the first review request.** `96c391d` made the job push-only. `73fc0bf` replaces that with the cut manifest's own path rule, as agreed in session. `ed9db78` fixes a lint finding, and `dd871e8` takes review 7203's findings 1–3. Review the branch as a whole; the description below is current. ## What changes `mirror-release.yml`'s `set-adopter-pin` job lands the pin by the **same path-alpha / path-gamma rule as the cut's manifest commit** (ADR-0007, `reusable-release.yml:765`), instead of always opening a PR: | Condition | Path | What happens | |---|---|---| | `RELEASE_TOOLKIT_TOKEN` set, `rt preflight-push-whitelist alpha` passes / warns | **alpha** | fast-forward push to the default branch as `release-bot`, then `ls-remote` read-back | | preflight refuses (push disabled, or `release-bot` not whitelisted) | **gamma** | `::warning::`, no push attempted, PR opened with the PAT, as #1378 did | | alpha push rejected anyway (`main` moved, whitelist changed) | **gamma** | `::warning::`, same PR | | no `RELEASE_TOOLKIT_TOKEN` | — | `::error::`, exit 1 | Both paths share the steps before the commit: 1. Fetch the live default-branch tip and detach onto it, so the commit is never built on the dispatch ref. 2. Run `readme-pin-check --fix`, as before. 3. **Re-grade** with plain `readme-pin-check`, because `--fix` writes and reports but does not re-grade, and nothing else grades an alpha commit before it lands. 4. Run `tag-action-digest-check`, as before. 5. Commit as `release-bot`. Credentials: - The PAT is passed as `RELEASE_TOKEN_OVERRIDE`. Under that name `internal/credentials` resolves a single-token tier, so `rt preflight-push-whitelist` checks a user rather than a deploy key. The user it checks comes from `FORGEJO_TOKEN`, which the step exports from the same PAT. - git and curl both read the PAT from 0600 files; it is never in argv. The checkout's persisted credential is unset first. The PR half's curl calls used to pass the token on the command line. - The automatic token is gone from the job, and its permissions drop to `contents: read`. - Every network or response read that feeds an error message has its own fallback (`|| code=000`, `|| statuses=0`, `|| remote_sha=""`, and the two `jq` reads). Under `set -e`, a failed substitution would otherwise end the step before the message that says what to do (review 7203). ## Decision tree - **Reuse the manifest rule, not a new variable.** A repo variable (PR by default, push opt-in) was considered and declined. The manifest fork's switch is the credential, not configuration. In this repo the same PAT already pushes the post-cut commits to `main`, so an opt-in would protect nothing, and two rules for one fork are easy to confuse. *If adopters ever need to choose, the variable belongs on both forks at once.* - **Unlike the manifest, a refused alpha falls back instead of failing.** A cut must not stop half-done; a pin that lands later by PR is still correct. - **No PAT → refuse, on both paths.** The automatic token cannot push to protected `main` (#78), and a PR opened with it is never graded (#1393). Without the PAT, neither route can land. - **Keep a PR wherever a human makes a choice the job cannot grade.** `recover-pending-cut` is that case and is untouched. What alpha gives up: the PR-time required contexts don't run before the pin lands. The `push: main` runs (`go-ci`, `tests`, `check-self-bootstrap`, `gitea-twin-check`) grade it right after, and the two pin-specific verdicts run inside the job beforehand. #1519, the same kind of mechanical edit, passed all 30 contexts. ## Carry-forward from the PR-only job (AGENTS.md section 1) | Predecessor step | Class | Disposition | |---|---|---| | checkout, build rt | (a) | preserved | | `--fix`; rc≠0 → warning + exit 0; `git diff --quiet` no-op | (b) | preserved verbatim | | tag fetch + `tag-action-digest-check` gate (#1463) | (b) | preserved; now gates the commit, on both paths | | CI commit identity via `-c` (#73) | (b) | kept via `-c`, now `release-bot`, matching its other direct pushes (`63134a1`, `aa6f603`) | | PAT preferred, automatic token as bash fallback (#41/#47) | (b) permission (#78) | reimplemented: PAT **required**. The fallback is retired because it produced an ungraded PR (#1393) | | branch push with the automatic token | (b) | now with the PAT, so the job needs only `contents: read` | | body file with `no-changelog:` (#1394) | (c) | **preserved**; one paragraph added saying why the PR exists (alpha did not land) | | poll the PR for ≥1 status (#1393) | (c) | **preserved**; polls with the PAT; remedy text updated for a PAT-only job | | PR POST failure → `::warning::`, exit 0 | — | now `::error::`, exit 1: the pin did not land, and a green job would hide that | | new | — | live-tip detach, re-grade, preflight + path choice, ff-only push, read-back | ## Verification All of this ran on an LF clone; no workflow ran live. - `go test ./...` natively on Windows: every package `ok`. - `bats tests/workflows.bats` in `rt-bats:local`: **138/138**. - `rt gitea-twin --check`: PASS (only the `mirror-release.yml` twin changed). - `rt fragment-check` (content mode): PASS, no warnings. - `rt check-self-bootstrap`: `floating-pin (release-toolkit#456): main matches main by construction`, so no re-pin (AGENTS.md section 2.1). - **The path choice is executed, not just pattern-matched.** `TestSetAdopterPinChoosesItsPathByTheManifestRule` runs the real fragment from the workflow under bash, with `rt` and `git` stubbed, in five cases: - preflight pass + push accepted → alpha lands, no PR; - preflight refuses → no push attempted, warning, PR; - push rejected → warning, PR; - read-back mismatch → non-zero exit, no PR; - read-back itself fails → the read-back message, non-zero exit, no PR. - **Mutation-verified (Axis B):** I applied 21 mutants to `mirror-release.yml` and each turned its targeted arm red. They covered the PAT/fallback, both fallback-to-PR edges, an alpha that falls through to a PR, the preflight being skipped, the token in curl argv, the live-tip detach, the re-grade exit, a forced push, the gamma branch targeting `main`, the read-back (two mutants), the zero-status exit, the body being swapped, the four substitution fallbacks, the extraheader unset, and the EXIT trap. The #1463 bats arm also went red with the digest `exit` removed. **Not verified:** the job has not run end to end. It runs only after a `mirror-release.yml` dispatch, which publishes to gitea.com, so I did not dispatch it. The next mirror publish is its first real run. Look for `adopter-pin path=alpha: pushed …` or `adopter-pin path=gamma: PR #…`. ## Review request: verify at source (AGENTS.md section 6) Please verify both halves against live state, not this description: 1. **Alpha half:** `release-bot` is on `main`'s push whitelist, and `RELEASE_TOOLKIT_TOKEN` is its PAT with push scope. Also check whether `rt preflight-push-whitelist alpha` can read branch protection as `release-bot`. If it can't, it warns and the job proceeds, so the push itself becomes the check. Evidence I used: the 2026-10-02 protection read, #1519 opened by `release-bot` with this secret, and `63134a1`/`aa6f603` pushed directly by `release-bot`. 2. **Gamma half:** with alpha refused, the PR it opens still carries the `no-changelog:` body and gets graded. The arms cover this, but nothing has exercised it live since #1519. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
chore(mirror): set-adopter-pin pushes the pin to main instead of opening a PR
Some checks failed
fork-pr-approval-notice / explain fork workflow approval (pull_request_target) Successful in 4s
ac-closure-check / ac-closure check (pull_request) Successful in 6s
ac-closure-check / check (pull_request) Successful in 0s
base-divergence-check / check (pull_request) Successful in 5s
changelog-body-check / changelog body Cold-Read linter (pull_request) Successful in 6s
changelog-body-check / check (pull_request) Successful in 0s
check-self-bootstrap / check (pull_request) Successful in 5s
fragment-check / coverage (pull_request) Successful in 7s
fragment-check / density (pull_request) Successful in 7s
fragment-check / check (pull_request) Successful in 0s
gitea-twin-check / check (pull_request) Successful in 6s
go-ci / lint + build + test (pull_request) Failing after 10s
go-ci / record reviewed vs landed commit (pull_request) Has been skipped
manifest-check / manifest-vs-tag consistency (pull_request) Successful in 7s
manifest-check / check (pull_request) Successful in 0s
prep-order-check / check (pull_request) Successful in 6s
readme-pin-check / mirror (pull_request) Successful in 7s
readme-pin-check / digest (pull_request) Successful in 5s
register-check / register-drift check (pull_request) Successful in 7s
register-check / check (pull_request) Successful in 0s
tests / workflow-schema (pull_request) Successful in 4s
tests / bats (pull_request) Successful in 34s
tests / dated-examples (pull_request) Successful in 4s
tests / contract-paths (pull_request) Successful in 4s
tests / shellcheck (pull_request) Successful in 4s
toolkit-self-gates / toolkit-self gates (PR's own rt) (pull_request) Successful in 7s
workflow-parse-check / toolkit-self parse guard and controls (pull_request) Successful in 4s
workflow-parse-check / workflow parse and schema (pull_request) Successful in 4s
workflow-parse-check / check (pull_request) Successful in 0s
go-ci / page landing-tree failure (pull_request) Has been skipped
96c391d096
The pin PR could only ever finish with a human merge: release-bot cannot
approve its own PR, and the change is fully determined by inputs the job
grades itself. #1519 sat 79 minutes with every context green while
readme-pin-check / mirror failed on unrelated PRs.

The job now builds on the live default-branch tip, rewrites the pins,
re-grades the written tree with readme-pin-check and
tag-action-digest-check, and fast-forward pushes as release-bot (on
main's push whitelist) with the PAT kept out of argv, then reads the
branch back. Without RELEASE_TOOLKIT_TOKEN it refuses rather than
falling back: the automatic token can neither push to protected main
(#78) nor open a PR that gets graded (#1393).

Retires the PR-only machinery: .forgejo/pr-body/adopter-pin.md, the
#1394 body-file arm and the #1393 status-poll arm. Six new arms grade
the push path; the #1463 bats arm now anchors on commit + push.

Refs #1524

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude 2026-10-02 22:33:29 +02:00
Author
Member

HOLD: reworking this PR per alex's decision in session. Instead of push-only, set-adopter-pin will reuse the manifest commit's path-alpha/path-gamma rule (ADR-0007): push directly when RELEASE_TOOLKIT_TOKEN is set and rt preflight-push-whitelist alpha does not refuse; open the PR (with the same PAT) when the preflight refuses or the push is rejected; fail when there is no token. The PR-path pieces removed in 96c391d come back. New commits on this branch, no force-push; I'll post the new head SHA when it is ready for review. Please don't approve 96c391d.

HOLD: reworking this PR per alex's decision in session. Instead of push-only, set-adopter-pin will reuse the manifest commit's path-alpha/path-gamma rule (ADR-0007): push directly when RELEASE_TOOLKIT_TOKEN is set and `rt preflight-push-whitelist alpha` does not refuse; open the PR (with the same PAT) when the preflight refuses or the push is rejected; fail when there is no token. The PR-path pieces removed in 96c391d come back. New commits on this branch, no force-push; I'll post the new head SHA when it is ready for review. Please don't approve 96c391d.
chore(mirror): land the adopter pin by the manifest's path-alpha/gamma rule
Some checks failed
fork-pr-approval-notice / explain fork workflow approval (pull_request_target) Successful in 4s
ac-closure-check / check (pull_request) Successful in 0s
base-divergence-check / check (pull_request) Successful in 5s
changelog-body-check / changelog body Cold-Read linter (pull_request) Successful in 6s
changelog-body-check / check (pull_request) Successful in 0s
check-self-bootstrap / check (pull_request) Successful in 5s
gitea-twin-check / check (pull_request) Successful in 5s
go-ci / lint + build + test (pull_request) Failing after 10s
go-ci / record reviewed vs landed commit (pull_request) Has been skipped
manifest-check / manifest-vs-tag consistency (pull_request) Successful in 7s
manifest-check / check (pull_request) Successful in 0s
prep-order-check / check (pull_request) Successful in 6s
readme-pin-check / mirror (pull_request) Successful in 7s
readme-pin-check / digest (pull_request) Successful in 6s
register-check / register-drift check (pull_request) Successful in 7s
register-check / check (pull_request) Successful in 0s
tests / workflow-schema (pull_request) Successful in 4s
tests / bats (pull_request) Successful in 34s
tests / dated-examples (pull_request) Successful in 4s
tests / contract-paths (pull_request) Successful in 4s
tests / shellcheck (pull_request) Successful in 4s
workflow-parse-check / toolkit-self parse guard and controls (pull_request) Successful in 4s
workflow-parse-check / workflow parse and schema (pull_request) Successful in 4s
workflow-parse-check / check (pull_request) Successful in 0s
ac-closure-check / ac-closure check (pull_request) Successful in 6s
fragment-check / coverage (pull_request) Successful in 7s
fragment-check / density (pull_request) Successful in 7s
fragment-check / check (pull_request) Successful in 0s
toolkit-self-gates / toolkit-self gates (PR's own rt) (pull_request) Successful in 7s
go-ci / page landing-tree failure (pull_request) Has been skipped
73fc0bfdb5
Reworks the previous commit per review in session: instead of
push-only, set-adopter-pin reuses the cut manifest commit's rule
(ADR-0007) rather than a switch of its own.

  path-alpha  RELEASE_TOOLKIT_TOKEN set and `rt preflight-push-whitelist
              alpha` does not refuse -> fast-forward push to main, then
              read it back.
  path-gamma  the preflight refuses or the push is rejected -> open the
              PR with the same PAT, as #1378 did.
  neither     no PAT -> refuse; the automatic token can neither push to
              protected main (#78) nor open a graded PR (#1393).

The PAT is passed as RELEASE_TOKEN_OVERRIDE, the name the credential
resolver reads, so the preflight grades the identity that pushes. Both
paths re-grade the written tree before the commit. The PR half now
authenticates curl from a 0600 header file instead of argv, and a failed
PR POST is an error rather than a warning, since the pin did not land.

Restores .forgejo/pr-body/adopter-pin.md (plus a path-gamma paragraph),
the #1394 body-file arm byte-identical, and the #1393 status-poll arm
without its automatic-token fallback check. The path choice is pinned
by an arm that executes the real fragment under bash with rt and git
stubbed.

Refs #1524

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude changed title from chore(mirror): set-adopter-pin pushes the pin to main instead of opening a PR (#1524) to chore(mirror): land the adopter pin by the manifest's path-alpha/gamma rule (#1524) 2026-10-02 22:58:41 +02:00
Author
Member

HOLD released. Ready for review at head 73fc0bfdb5e2d9ef2d05f70e039546b28efe9f3e (two commits: 96c391d push-only, then 73fc0bf reworking it onto the manifest's path-alpha/gamma rule). The description and #1524's acceptance criteria are rewritten for the current design. Local results: go test ./... all ok, tests/workflows.bats 138/138, 15/15 workflow mutants caught. Before submitting, pin the review to this head with rt review-submit-check --owner frankenbit --repo release-toolkit --pr 1525 --reviewed-head 73fc0bfdb5e2d9ef2d05f70e039546b28efe9f3e (AGENTS.md section 6.2).

HOLD released. Ready for review at head `73fc0bfdb5e2d9ef2d05f70e039546b28efe9f3e` (two commits: `96c391d` push-only, then `73fc0bf` reworking it onto the manifest's path-alpha/gamma rule). The description and #1524's acceptance criteria are rewritten for the current design. Local results: `go test ./...` all ok, `tests/workflows.bats` 138/138, 15/15 workflow mutants caught. Before submitting, pin the review to this head with `rt review-submit-check --owner frankenbit --repo release-toolkit --pr 1525 --reviewed-head 73fc0bfdb5e2d9ef2d05f70e039546b28efe9f3e` (AGENTS.md section 6.2).
Author
Member

HOLD: go-ci / lint fails on 73fc0bf. staticcheck QF1001 (De Morgan) flags three conditions in internal/gates/adopter_pin_push_test.go (lines 178, 196, 285). A lint-only fix follows as a new commit; I'll post the new head. Please don't review 73fc0bf.

HOLD: go-ci / lint fails on 73fc0bf. staticcheck QF1001 (De Morgan) flags three conditions in internal/gates/adopter_pin_push_test.go (lines 178, 196, 285). A lint-only fix follows as a new commit; I'll post the new head. Please don't review 73fc0bf.
chore(gates): apply De Morgan to three adopter-pin ordering checks
All checks were successful
fork-pr-approval-notice / explain fork workflow approval (pull_request_target) Successful in 4s
ac-closure-check / ac-closure check (pull_request) Successful in 6s
ac-closure-check / check (pull_request) Successful in 0s
base-divergence-check / check (pull_request) Successful in 6s
changelog-body-check / changelog body Cold-Read linter (pull_request) Successful in 6s
changelog-body-check / check (pull_request) Successful in 0s
check-self-bootstrap / check (pull_request) Successful in 5s
fragment-check / coverage (pull_request) Successful in 7s
fragment-check / density (pull_request) Successful in 7s
fragment-check / check (pull_request) Successful in 0s
gitea-twin-check / check (pull_request) Successful in 5s
go-ci / lint + build + test (pull_request) Successful in 30s
go-ci / record reviewed vs landed commit (pull_request) Has been skipped
manifest-check / manifest-vs-tag consistency (pull_request) Successful in 7s
manifest-check / check (pull_request) Successful in 0s
prep-order-check / check (pull_request) Successful in 6s
readme-pin-check / mirror (pull_request) Successful in 8s
readme-pin-check / digest (pull_request) Successful in 6s
register-check / register-drift check (pull_request) Successful in 7s
register-check / check (pull_request) Successful in 0s
tests / workflow-schema (pull_request) Successful in 5s
tests / bats (pull_request) Successful in 34s
tests / dated-examples (pull_request) Successful in 4s
tests / contract-paths (pull_request) Successful in 4s
tests / shellcheck (pull_request) Successful in 4s
toolkit-self-gates / toolkit-self gates (PR's own rt) (pull_request) Successful in 7s
workflow-parse-check / toolkit-self parse guard and controls (pull_request) Successful in 4s
workflow-parse-check / workflow parse and schema (pull_request) Successful in 4s
go-ci / page landing-tree failure (pull_request) Has been skipped
workflow-parse-check / check (pull_request) Successful in 0s
ed9db78a15
staticcheck QF1001 flagged `!(a < b && b < c)` in three arms of
adopter_pin_push_test.go; go-ci's golangci-lint step failed on 73fc0bf.
Rewritten as the equivalent `a >= b || b >= c`. Behaviour unchanged:
the 15 workflow mutants are still caught.

Refs #1524

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Author
Member

HOLD released. Ready for review at head ed9db78a15d480add080f875126bd0a2e7f35985. Since 73fc0bf, one lint-only commit: the three !(a && b) ordering checks are rewritten as a >= b || b >= c. Local golangci-lint v2.14.0 (CI's flags) reports the same three QF1001 on 73fc0bf and 0 issues on ed9db78; the gates tests pass and all 15 workflow mutants are still caught. Pin the review with rt review-submit-check --owner frankenbit --repo release-toolkit --pr 1525 --reviewed-head ed9db78a15d480add080f875126bd0a2e7f35985.

HOLD released. Ready for review at head `ed9db78a15d480add080f875126bd0a2e7f35985`. Since 73fc0bf, one lint-only commit: the three `!(a && b)` ordering checks are rewritten as `a >= b || b >= c`. Local golangci-lint v2.14.0 (CI's flags) reports the same three QF1001 on 73fc0bf and 0 issues on ed9db78; the gates tests pass and all 15 workflow mutants are still caught. Pin the review with `rt review-submit-check --owner frankenbit --repo release-toolkit --pr 1525 --reviewed-head ed9db78a15d480add080f875126bd0a2e7f35985`.
claude left a comment

Verdict: APPROVE at ed9db78a15d480add080f875126bd0a2e7f35985. This is posted as COMMENT because the forge identity is claude, the same account that opened the PR, and Forgejo blocks self-approval. @alex has to click Approve. Before submitting, rt review-submit-check --reviewed-head ed9db78a… returned rc=0 (READY).

Verify at source (AGENTS.md section 6)

Alpha half: confirmed from live state.

  • main branch protection, read back 2026-10-02:
    • enable_push=true
    • enable_push_whitelist=true
    • push_whitelist_usernames=["release-bot"]
    • no teams
    • push_whitelist_deploy_keys=false
  • release-bot has write permission, so the whitelist entry is not the silent-drop case (section 8, instance 5).
  • #1519 was opened by release-bot from this job using RELEASE_TOOLKIT_TOKEN. So the secret is release-bot's PAT and, being tier 2, the preflight grades the right login.
  • Could not grade: whether release-bot's PAT can read branch protection. If it can't, the preflight logs a warning and proceeds, and the ff-only push is the backstop. The job behaves correctly either way.

Gamma half: not exercised live. It is covered by the arms: #1394's body file, #1393's zero-status poll (now using the PAT), and the executed path-choice arm (preflight refuses → no push, PR opened; push rejected → PR opened).

What I ran (LF clone at ed9db78)

  • go test ./internal/gates -run AdopterPin: all 10 arms pass.
  • rt gitea-twin --check: PASS, 11 twins.
  • CI at ed9db78: combined state success, 30/30 contexts.
  • ac-closure-check regex over the PR body and all three commit messages: no close targets.
  • I applied 5 mutants of my own to mirror-release.yml:
    • Killed (3): alpha falls through to gamma (exit 0 removed); gamma branch pushed without the PAT config; preflight called with gamma.
    • Survived (2): the extraheader unset and the trap cleanup. See nit 2.
  • The design matches #1524's revised proposal and every AC line I checked against the diff. The credential handling copies goreleaser.yml's token-config pattern, which runs on the same runner.

Non-blocking findings

  1. set -uo → set -euo makes three failure paths exit before the job's own message. In bash, an assignment from a failing command substitution trips errexit. I checked: set -euo pipefail; x="$(false | cat)" exits 1. Each case below still exits non-zero, so the job goes red, but the operator loses the message:

    • PR POST: code="$(curl … -w '%{http_code}')". On a transport failure (curl exit 7 or 28) the job dies before ::error::… The change is on ${branch}; open it by hand. That is the one message that names the branch to recover from.
    • Status poll: statuses="$(curl … | jq -r 'length')". A single transient failure ends the poll on its first iteration, with no #1393 message. Before this PR, [[ =~ ]] || statuses=0 absorbed it.
    • Read-back: if remote_sha="$(… git ls-remote … | awk …)" fails, the job exits with no message, after the pin has most likely landed.

    A || true (or || code=000) on those three substitutions would restore the intended messages.

  2. The git config --local --unset-all "http.${base_url}/.extraheader" line is load-bearing, and no arm pins it. Without it, git sends the automatic token's persisted header alongside the PAT's, and which one the server honours is not established. The precedent in goreleaser.yml is unpinned too, so this is a consistency nit, not a regression.

  3. The comment on RELEASE_TOKEN_OVERRIDE (and the PR body) over-credits it. The identity the preflight grades comes from FORGEJO_TOKEN (GetAuthenticatedUser). RELEASE_TOKEN_OVERRIDE only selects the credential tier (not split, so the user is checked). Both carry the same PAT, so the behaviour is right. Only the stated reason is off.

  4. The read-back can race. If another push lands on main between the alpha push and ls-remote, the job reports read-back mismatch and exits 1 even though the pin landed. This is rare, and failing loud is the safe direction. An ancestry check after a fetch would remove the false alarm, if you think it's worth it.

None of these blocks the merge. Nit 1 is the one I'd take in a follow-up commit, or now if you're touching the branch anyway.

**Verdict: APPROVE at `ed9db78a15d480add080f875126bd0a2e7f35985`.** This is posted as COMMENT because the forge identity is `claude`, the same account that opened the PR, and Forgejo blocks self-approval. @alex has to click Approve. Before submitting, `rt review-submit-check --reviewed-head ed9db78a…` returned rc=0 (READY). ## Verify at source (AGENTS.md section 6) **Alpha half: confirmed from live state.** - `main` branch protection, read back 2026-10-02: - `enable_push=true` - `enable_push_whitelist=true` - `push_whitelist_usernames=["release-bot"]` - no teams - `push_whitelist_deploy_keys=false` - `release-bot` has `write` permission, so the whitelist entry is not the silent-drop case (section 8, instance 5). - #1519 was opened by `release-bot` from this job using `RELEASE_TOOLKIT_TOKEN`. So the secret is release-bot's PAT and, being tier 2, the preflight grades the right login. - **Could not grade:** whether release-bot's PAT can *read* branch protection. If it can't, the preflight logs a warning and proceeds, and the ff-only push is the backstop. The job behaves correctly either way. **Gamma half: not exercised live.** It is covered by the arms: #1394's body file, #1393's zero-status poll (now using the PAT), and the executed path-choice arm (preflight refuses → no push, PR opened; push rejected → PR opened). ## What I ran (LF clone at `ed9db78`) - `go test ./internal/gates -run AdopterPin`: all 10 arms pass. - `rt gitea-twin --check`: PASS, 11 twins. - CI at `ed9db78`: combined state `success`, 30/30 contexts. - `ac-closure-check` regex over the PR body and all three commit messages: no close targets. - I applied 5 mutants of my own to `mirror-release.yml`: - Killed (3): alpha falls through to gamma (`exit 0` removed); gamma branch pushed without the PAT config; preflight called with `gamma`. - Survived (2): the extraheader unset and the `trap` cleanup. See nit 2. - The design matches #1524's revised proposal and every AC line I checked against the diff. The credential handling copies `goreleaser.yml`'s token-config pattern, which runs on the same runner. ## Non-blocking findings 1. **`set -uo` → `set -euo` makes three failure paths exit before the job's own message.** In bash, an assignment from a failing command substitution trips errexit. I checked: `set -euo pipefail; x="$(false | cat)"` exits 1. Each case below still exits non-zero, so the job goes red, but the operator loses the message: - PR POST: `code="$(curl … -w '%{http_code}')"`. On a transport failure (curl exit 7 or 28) the job dies before `::error::… The change is on ${branch}; open it by hand`. That is the one message that names the branch to recover from. - Status poll: `statuses="$(curl … | jq -r 'length')"`. A single transient failure ends the poll on its first iteration, with no `#1393` message. Before this PR, `[[ =~ ]] || statuses=0` absorbed it. - Read-back: if `remote_sha="$(… git ls-remote … | awk …)"` fails, the job exits with no message, after the pin has most likely landed. A `|| true` (or `|| code=000`) on those three substitutions would restore the intended messages. 2. **The `git config --local --unset-all "http.${base_url}/.extraheader"` line is load-bearing, and no arm pins it.** Without it, git sends the automatic token's persisted header alongside the PAT's, and which one the server honours is not established. The precedent in `goreleaser.yml` is unpinned too, so this is a consistency nit, not a regression. 3. **The comment on `RELEASE_TOKEN_OVERRIDE` (and the PR body) over-credits it.** The identity the preflight grades comes from `FORGEJO_TOKEN` (`GetAuthenticatedUser`). `RELEASE_TOKEN_OVERRIDE` only selects the credential tier (not split, so the user is checked). Both carry the same PAT, so the behaviour is right. Only the stated reason is off. 4. **The read-back can race.** If another push lands on `main` between the alpha push and `ls-remote`, the job reports `read-back mismatch` and exits 1 even though the pin landed. This is rare, and failing loud is the safe direction. An ancestry check after a fetch would remove the false alarm, if you think it's worth it. None of these blocks the merge. Nit 1 is the one I'd take in a follow-up commit, or now if you're touching the branch anyway.
Author
Member

HOLD: taking review 7203's findings 1-3 as a follow-up commit, plus a pin for the trap cleanup (the other surviving mutant): (1) the PR POST, status poll and read-back substitutions no longer trip errexit before the job's own message; (2) an arm pins the extraheader unset and the trap; (3) the RELEASE_TOKEN_OVERRIDE / FORGEJO_TOKEN comments say what each actually does. Finding 4 (read-back race) declined for now, reasoning to follow with the new head. Please don't approve ed9db78.

HOLD: taking review 7203's findings 1-3 as a follow-up commit, plus a pin for the `trap` cleanup (the other surviving mutant): (1) the PR POST, status poll and read-back substitutions no longer trip errexit before the job's own message; (2) an arm pins the extraheader unset and the trap; (3) the RELEASE_TOKEN_OVERRIDE / FORGEJO_TOKEN comments say what each actually does. Finding 4 (read-back race) declined for now, reasoning to follow with the new head. Please don't approve ed9db78.
chore(mirror): keep set-adopter-pin's failure messages under set -e
All checks were successful
tests / bats (pull_request) Successful in 34s
tests / dated-examples (pull_request) Successful in 4s
tests / contract-paths (pull_request) Successful in 4s
tests / shellcheck (pull_request) Successful in 4s
workflow-parse-check / toolkit-self parse guard and controls (pull_request) Successful in 4s
workflow-parse-check / workflow parse and schema (pull_request) Successful in 5s
workflow-parse-check / check (pull_request) Successful in 0s
ac-closure-check / ac-closure check (pull_request) Successful in 6s
ac-closure-check / check (pull_request) Successful in 0s
fragment-check / coverage (pull_request) Successful in 7s
fragment-check / density (pull_request) Successful in 7s
fragment-check / check (pull_request) Successful in 0s
toolkit-self-gates / toolkit-self gates (PR's own rt) (pull_request) Successful in 7s
go-ci / page landing-tree failure (pull_request) Has been skipped
check-self-bootstrap / check (push) Successful in 5s
gitea-twin-check / check (push) Successful in 4s
go-ci / lint + build + test (push) Successful in 29s
go-ci / record reviewed vs landed commit (push) Successful in 2s
prepared-uncut-check / toolkit-self prepared-uncut controls (push) Successful in 5s
prepared-uncut-check / prepared-but-uncut release (push) Successful in 7s
prepared-uncut-check / check (push) Successful in 0s
release / decide + act (push) Successful in 9s
release / release (push) Successful in 0s
tests / workflow-schema (push) Successful in 3s
tests / bats (push) Successful in 33s
tests / dated-examples (push) Successful in 4s
tests / contract-paths (push) Successful in 4s
tests / shellcheck (push) Successful in 3s
go-ci / page landing-tree failure (push) Has been skipped
release / fire-cut (push) Has been skipped
dd871e878a
Review 7203 (findings 1-3):

1. Under `set -euo pipefail`, an assignment from a failing command
   substitution ends the step before the job's own message. The PR POST
   (`|| code=000`), the #1393 status poll (`|| statuses=0`), the alpha
   read-back (`|| remote_sha=""`) and the two jq reads of the PR
   response now fall through to their messages. The read-back message
   now says the push was accepted, so the pin has most likely landed.
2. An arm pins the extraheader unset (before the first push) and the
   EXIT trap on both token files (before the PAT is written).
3. The RELEASE_TOKEN_OVERRIDE comment no longer credits it with the
   graded identity: it selects the single-token tier, and the user the
   preflight grades comes from FORGEJO_TOKEN.

New arm: every network or response read that feeds a message carries a
fallback; the executed path-choice arm gains a failing-ls-remote case.
21 workflow mutants, all caught.

Refs #1524

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Author
Member

HOLD released. Ready for review at head dd871e878a476038fff8cf4d31ff34402b5c0806. Review 7203 was bound to ed9db78, so it does not cover this commit.

Replies to review 7203:

  1. Taken. || code=000 on the PR POST, || statuses=0 in the poll, and || remote_sha="" on the read-back. The same exposure applied to the two jq reads of the PR response, so they got || pr_number="" / || head_sha="". The read-back message now says the push was accepted and the pin has most likely landed. Pinned by TestSetAdopterPinSubstitutionsCannotSkipTheirMessage, and the executed path-choice arm gained a failing-ls-remote case.
  2. Taken. The extraheader unset (before the first push) and the EXIT trap on both token files (before the PAT is written) are now pinned. Both of your surviving mutants are caught.
  3. Taken. The comments, the arm's comment and the PR body now say that RELEASE_TOKEN_OVERRIDE selects the single-token tier, and that the user the preflight grades comes from FORGEJO_TOKEN.
  4. Declined for now. A read-back race reports a false read-back mismatch and fails, which is the safe direction. Since the push was accepted, the message now tells the operator the pin has most likely landed. An ancestry check would need a second fetch into a depth-1 checkout. I haven't measured how that behaves on this runner, so I'd rather not add an unmeasured path to fix a rare false alarm. Happy to file it separately if you think it's worth it.

Local results at dd871e8:

  • go test ./... all ok.
  • tests/workflows.bats 138/138.
  • golangci-lint v2.14.0 (CI's flags): 0 issues.
  • rt gitea-twin --check PASS.
  • 21/21 workflow mutants caught.

Before submitting: rt review-submit-check --owner frankenbit --repo release-toolkit --pr 1525 --reviewed-head dd871e878a476038fff8cf4d31ff34402b5c0806.

HOLD released. Ready for review at head `dd871e878a476038fff8cf4d31ff34402b5c0806`. Review 7203 was bound to `ed9db78`, so it does not cover this commit. Replies to review 7203: 1. **Taken.** `|| code=000` on the PR POST, `|| statuses=0` in the poll, and `|| remote_sha=""` on the read-back. The same exposure applied to the two `jq` reads of the PR response, so they got `|| pr_number=""` / `|| head_sha=""`. The read-back message now says the push was accepted and the pin has most likely landed. Pinned by `TestSetAdopterPinSubstitutionsCannotSkipTheirMessage`, and the executed path-choice arm gained a failing-`ls-remote` case. 2. **Taken.** The extraheader unset (before the first push) and the EXIT trap on both token files (before the PAT is written) are now pinned. Both of your surviving mutants are caught. 3. **Taken.** The comments, the arm's comment and the PR body now say that `RELEASE_TOKEN_OVERRIDE` selects the single-token tier, and that the user the preflight grades comes from `FORGEJO_TOKEN`. 4. **Declined for now.** A read-back race reports a false `read-back mismatch` and fails, which is the safe direction. Since the push was accepted, the message now tells the operator the pin has most likely landed. An ancestry check would need a second fetch into a depth-1 checkout. I haven't measured how that behaves on this runner, so I'd rather not add an unmeasured path to fix a rare false alarm. Happy to file it separately if you think it's worth it. Local results at `dd871e8`: - `go test ./...` all ok. - `tests/workflows.bats` 138/138. - golangci-lint v2.14.0 (CI's flags): 0 issues. - `rt gitea-twin --check` PASS. - 21/21 workflow mutants caught. Before submitting: `rt review-submit-check --owner frankenbit --repo release-toolkit --pr 1525 --reviewed-head dd871e878a476038fff8cf4d31ff34402b5c0806`.
claude left a comment

Verdict: APPROVE at dd871e878a476038fff8cf4d31ff34402b5c0806. This replaces review 7203, which was bound to ed9db78. It is posted as COMMENT because of the self-approval block, so @alex has to click Approve. Before submitting, rt review-submit-check --reviewed-head dd871e87… returned rc=0 (READY).

CI on dd871e8 was still running when I posted: 11 of 30 contexts passed and 19 were pending, with none failing. Merge only once it is green. My verdict covers the code, not the pending runs.

Delta ed9db78..dd871e8 (one commit)

Responses to the four findings in review 7203:

  1. set -e swallowing messages: taken.
    • || code=000, || statuses=0 and || remote_sha="" each fall through to their existing ::error:: path. On the PR POST, curl's -w already prints 000 on a transport failure; the fallback covers the non-zero exit.
    • The two jq reads of the PR response are guarded too. That is beyond what I asked for, and it is right.
    • The read-back message now says the pin has most likely landed. That also covers finding 4 well enough.
    • TestSetAdopterPinSubstitutionsCannotSkipTheirMessage pins all five reads. The executed path-choice arm gained a failing-ls-remote case.
  2. The extraheader unset and the EXIT trap are now pinned, each ordered before the push or the token write.
  3. The wording is corrected. RELEASE_TOKEN_OVERRIDE selects the credential tier; FORGEJO_TOKEN supplies the user that is graded.
  4. The read-back race: declined, and I accept that. A false mismatch fails loud and now says the pin has most likely landed. A second fetch into a depth-1 checkout would be unmeasured.

What I ran (LF clone at dd871e8)

  • go test ./internal/gates -run AdopterPin: ok.
  • rt gitea-twin --check: PASS, 11 twins.
  • Mutants against the new arms: 6 of 6 killed:
    • the extraheader unset and the trap (both survived on ed9db78);
    • each of the three || fallbacks removed;
    • alpha's exit 0 removed.
  • The close-keyword scan of the new commit message finds no targets.

No open findings.

**Verdict: APPROVE at `dd871e878a476038fff8cf4d31ff34402b5c0806`.** This replaces review 7203, which was bound to `ed9db78`. It is posted as COMMENT because of the self-approval block, so @alex has to click Approve. Before submitting, `rt review-submit-check --reviewed-head dd871e87…` returned rc=0 (READY). **CI on `dd871e8` was still running when I posted:** 11 of 30 contexts passed and 19 were pending, with none failing. Merge only once it is green. My verdict covers the code, not the pending runs. ## Delta `ed9db78..dd871e8` (one commit) Responses to the four findings in review 7203: 1. **`set -e` swallowing messages: taken.** - `|| code=000`, `|| statuses=0` and `|| remote_sha=""` each fall through to their existing `::error::` path. On the PR POST, curl's `-w` already prints `000` on a transport failure; the fallback covers the non-zero exit. - The two `jq` reads of the PR response are guarded too. That is beyond what I asked for, and it is right. - The read-back message now says the pin has most likely landed. That also covers finding 4 well enough. - `TestSetAdopterPinSubstitutionsCannotSkipTheirMessage` pins all five reads. The executed path-choice arm gained a failing-`ls-remote` case. 2. **The extraheader unset and the EXIT trap are now pinned,** each ordered before the push or the token write. 3. **The wording is corrected.** `RELEASE_TOKEN_OVERRIDE` selects the credential tier; `FORGEJO_TOKEN` supplies the user that is graded. 4. **The read-back race: declined, and I accept that.** A false mismatch fails loud and now says the pin has most likely landed. A second fetch into a depth-1 checkout would be unmeasured. ## What I ran (LF clone at `dd871e8`) - `go test ./internal/gates -run AdopterPin`: ok. - `rt gitea-twin --check`: PASS, 11 twins. - Mutants against the new arms: **6 of 6 killed**: - the extraheader unset and the `trap` (both survived on `ed9db78`); - each of the three `||` fallbacks removed; - alpha's `exit 0` removed. - The close-keyword scan of the new commit message finds no targets. No open findings.
alex approved these changes 2026-10-02 23:21:05 +02:00
claude merged commit dd871e878a into main 2026-10-02 23:23:48 +02:00
claude deleted branch claude/1524-pin-direct-push 2026-10-02 23:23:48 +02:00

Landing identity record

  • PR: #1525
  • landed commit (server merge_commit_sha): dd871e878a476038fff8cf4d31ff34402b5c0806
  • effective official approval(s):
    • @alex, review #7205, stamped commit: dd871e878a476038fff8cf4d31ff34402b5c0806
  • 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=dd871e878a476038fff8cf4d31ff34402b5c0806 --> ## Landing identity record - PR: #1525 - landed commit (server merge_commit_sha): `dd871e878a476038fff8cf4d31ff34402b5c0806` - effective official approval(s): - @alex, review #7205, stamped commit: `dd871e878a476038fff8cf4d31ff34402b5c0806` - 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 reviewers
No milestone
No project
No assignees
3 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
frankenbit/release-toolkit!1525
No description provided.