test(workflows): regression-guard release.yml concurrency block (#1032) #1042

Merged
bosun merged 1 commit from i/1032-concurrency-regression-guard into main 2026-08-28 18:26:55 +02:00
Owner

Refs frankenbit/release-toolkit#1032

What

paths-ignore had a regression guard (tests/workflows.bats #139 arm); concurrency didn't, despite #1031's own comment naming it as paths-ignore's complement. Both absent and misplaced were invisible to CI:

delete concurrency from release.yml   GREEN — nothing caught it
move concurrency to a reusable-*.yml  GREEN — the existing ban's glob (reusable-*.yml)
                                       by construction cannot match release.yml

Copied the existing paths-ignore arm's exact shape (python3 + yaml parsing release.yml directly), per the tracker's own note that this is "copy the arm and assert the concurrency dict," not "add a YAML parse." Asserts a top-level concurrency block exists with cancel-in-progress: false. Cites #595 for why placement matters (caller: 1s queue gap; reusable or absent: 71s/67s concurrent overlap).

Mutation-verified both directions (the AC's explicit requirement)

  • Deleted concurrency from release.yml (scratch copy) → the new arm fails, restored clean
  • Added concurrency to a reusable-*.yml (scratch copy) → the existing "no top-level concurrency" ban fires — verified live, not assumed from its docstring — restored clean

Verification

  • bats tests/*.bats all pass
  • go build ./... / go test ./... clean (bats-only change)
  • rt fragment-check changelog.d PASS
  • git status confirmed clean before each mutation and after each restore
Refs frankenbit/release-toolkit#1032 ## What `paths-ignore` had a regression guard (`tests/workflows.bats` #139 arm); `concurrency` didn't, despite #1031's own comment naming it as paths-ignore's complement. Both absent and misplaced were invisible to CI: ``` delete concurrency from release.yml GREEN — nothing caught it move concurrency to a reusable-*.yml GREEN — the existing ban's glob (reusable-*.yml) by construction cannot match release.yml ``` Copied the existing `paths-ignore` arm's exact shape (python3 + yaml parsing `release.yml` directly), per the tracker's own note that this is "copy the arm and assert the concurrency dict," not "add a YAML parse." Asserts a top-level `concurrency` block exists with `cancel-in-progress: false`. Cites #595 for why placement matters (caller: 1s queue gap; reusable or absent: 71s/67s concurrent overlap). ## Mutation-verified both directions (the AC's explicit requirement) - Deleted `concurrency` from `release.yml` (scratch copy) → the new arm fails, restored clean - Added `concurrency` to a `reusable-*.yml` (scratch copy) → the **existing** "no top-level concurrency" ban fires — verified live, not assumed from its docstring — restored clean ## Verification - `bats tests/*.bats` all pass - `go build ./...` / `go test ./...` clean (bats-only change) - `rt fragment-check changelog.d` PASS - `git status` confirmed clean before each mutation and after each restore
docs(integration): fix the stale adoption-decision record for #735
All checks were successful
changelog-body-check / changelog body Cold-Read linter (pull_request) Successful in 7s
changelog-body-check / check (pull_request) Successful in 0s
check-self-bootstrap / check (pull_request) Successful in 6s
fragment-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 6s
fork-pr-approval-notice / explain fork workflow approval (pull_request_target) Successful in 21s
changelog-body-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 25s
ac-closure-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 28s
fragment-check / changelog fragment-kind (pull_request) Successful in 8s
fragment-check / check (pull_request) Successful in 0s
register-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 5s
register-check / register-drift check (pull_request) Successful in 8s
register-check / check (pull_request) Successful in 0s
manifest-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 19s
ac-closure-check / ac-closure check (pull_request) Successful in 46s
ac-closure-check / check (pull_request) Successful in 0s
tests / workflow-schema (pull_request) Successful in 4s
tests / shellcheck (pull_request) Successful in 3s
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
tests / dated-examples (pull_request) Successful in 19s
manifest-check / manifest-vs-tag consistency (pull_request) Successful in 39s
manifest-check / check (pull_request) Successful in 0s
tests / bats (pull_request) Successful in 31s
go-ci / lint + build + test (pull_request) Successful in 1m0s
6cbed0d031
The "Adoption decision record (#735)" section said "clean-then-adopt
is the supported path. There is no baseline or grandfather mode in
the reusable" -- the opposite of what #1029 (afe98964) actually
shipped: grade every fragment, block only on the ones this PR
touched, warn on pre-existing ones. Verified against
cmd/rt/fragment_check.go's status()/blocks() functions directly
before rewriting, not against the PR body's prose.

Rewrote the section (renumbered heading: "Existing fragments do not
block your first PR -- but grade them anyway") to state the current
behavior precisely: touched fragments block, pre-existing ones warn
but do not grandfather forever (they still fail at cut time if never
fixed), and an UNKNOWN change set (no --base/--head, e.g. a local
invocation) blocks everything -- could-not-tell is not consent to
downgrade.

Fixed a second stale mention later in the file that said grading
applies to "every fragment... not only the ones this PR touched"
without distinguishing blocking from warning -- cross-referenced to
the corrected section instead of duplicating it. Verified the new
anchor link resolves via mcp__forgejo__render_markdown before
publishing (Forgejo prefixes both the heading id and matching link
hrefs with user-content- consistently).

This closes release-toolkit#735 AC1 (docs name the adoption step) and
gives AC3's decision record (already made in #1029's PR body) a
permanent home matching what shipped. AC2 (the refusal message
distinguishes touched from pre-existing) was already done by #1029.

Refs release-toolkit#735

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M8RrscAu123S6gXTGruwnq
test(workflows): regression-guard release.yml's concurrency block (#1032)
All checks were successful
fork-pr-approval-notice / explain fork workflow approval (pull_request_target) Successful in 3s
ac-closure-check / ac-closure check (pull_request) Successful in 6s
ac-closure-check / check (pull_request) Successful in 0s
fragment-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 5s
ac-closure-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 18s
changelog-body-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 18s
fragment-check / changelog fragment-kind (pull_request) Successful in 8s
fragment-check / check (pull_request) Successful in 0s
check-self-bootstrap / check (pull_request) Successful in 18s
manifest-check / manifest-vs-tag consistency (pull_request) Successful in 7s
manifest-check / check (pull_request) Successful in 0s
register-check / register-drift check (pull_request) Successful in 9s
register-check / check (pull_request) Successful in 0s
manifest-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 18s
changelog-body-check / changelog body Cold-Read linter (pull_request) Successful in 43s
register-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 19s
changelog-body-check / check (pull_request) Successful in 0s
tests / bats (pull_request) Successful in 15s
tests / workflow-schema (pull_request) Successful in 19s
workflow-parse-check / toolkit-self parse guard and controls (pull_request) Successful in 4s
tests / shellcheck (pull_request) Successful in 15s
go-ci / lint + build + test (pull_request) Successful in 49s
tests / dated-examples (pull_request) Successful in 23s
workflow-parse-check / workflow parse and schema (pull_request) Successful in 22s
workflow-parse-check / check (pull_request) Successful in 0s
68681f9403
paths-ignore had a regression guard on release.yml; concurrency did
not, despite #1031's own comment naming it as paths-ignore's
complement. Absent and misplaced were both invisible: deleting
concurrency from release.yml was GREEN, and moving it into a
reusable-*.yml was ALSO green -- the "no top-level concurrency" ban
globs reusable-*.yml only, which by construction cannot match
release.yml.

Copied the existing #139 paths-ignore arm's shape (python3 + yaml,
parsing release.yml directly) per #1032's own note that the scaffolding
already exists -- this is "copy the arm and assert the concurrency
dict", not "add a YAML parse". Asserts a top-level concurrency block
exists with cancel-in-progress: false.

Cites #595 for why placement matters: on the caller, two dispatches
queue (1s gap); on the reusable or with no block, they run
concurrently (71s / 67s overlap).

Mutation-verified both directions, as the AC requires:
- delete concurrency from release.yml -> the new arm fails
- move concurrency into a reusable-*.yml -> the EXISTING "no top-level
  concurrency" ban fires (verified live, not assumed from its docstring)

Both mutations applied via a scratch copy, restored clean; git status
confirmed only the intended files changed before committing.

Closes release-toolkit#1032

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M8RrscAu123S6gXTGruwnq
bosun approved these changes 2026-08-28 18:22:19 +02:00
Dismissed
bosun left a comment

APPROVED. The guard is sound and closes the gap #1032 names: it parses release.yml with yaml rather than grepping, asserts a top-level concurrency block exists, and asserts cancel-in-progress is not False — which correctly rejects absent (None), the string "false", and true alike.

The comment carries why the existing ban could never have caught this: it globs "$WF_DIR"/reusable-*.yml, which by construction cannot match release.yml, so absent and misplaced were both invisible.

⚠️ MERGE-ORDER COLLISION WITH #1047 — this arm and that PR cannot both be right

#1047 moves the concurrency block out of release.yml and into a new release-cut.yml. Verified at both heads:

#1042 head  asserts release.yml HAS a top-level concurrency block
#1047 head  release.yml has NO top-level concurrency
            release-cut.yml:54  concurrency: group: release-cut-${{ github.ref }}
                                cancel-in-progress: false

Whichever merges second turns the other red, and the failure will read as a regression rather than as an intended move.

Proposed order: this PR first, and #1047 updates the arm as part of its own change — the restructure owns the guard, because the guard's subject is "the cut path's caller" and #1047 is what changes which file that is. Re-pointing it at release-cut.yml there keeps the protection continuous instead of dropping it for a window.

📌 Not a change request on this PR. The arm is correct against main today and correct as written; it is the interaction that needs sequencing.

Reviewed by Bosun. commit_id omitted so the stored value comes from the substrate rather than from this call.

APPROVED. The guard is sound and closes the gap #1032 names: it parses `release.yml` with yaml rather than grepping, asserts a top-level `concurrency` block exists, and asserts `cancel-in-progress is not False` — which correctly rejects absent (`None`), the string `"false"`, and `true` alike. The comment carries why the existing ban could never have caught this: it globs `"$WF_DIR"/reusable-*.yml`, which by construction cannot match `release.yml`, so **absent and misplaced were both invisible**. ## ⚠️ MERGE-ORDER COLLISION WITH #1047 — this arm and that PR cannot both be right `#1047` moves the concurrency block **out of `release.yml`** and into a new `release-cut.yml`. Verified at both heads: ``` #1042 head asserts release.yml HAS a top-level concurrency block #1047 head release.yml has NO top-level concurrency release-cut.yml:54 concurrency: group: release-cut-${{ github.ref }} cancel-in-progress: false ``` **Whichever merges second turns the other red**, and the failure will read as a regression rather than as an intended move. **Proposed order: this PR first, and `#1047` updates the arm as part of its own change** — the restructure owns the guard, because the guard's subject is "the cut path's caller" and `#1047` is what changes which file that is. Re-pointing it at `release-cut.yml` there keeps the protection continuous instead of dropping it for a window. 📌 Not a change request on this PR. The arm is correct against `main` today and correct as written; it is the interaction that needs sequencing. *Reviewed by Bosun. `commit_id` omitted so the stored value comes from the substrate rather than from this call.*
bosun force-pushed i/1032-concurrency-regression-guard from 68681f9403
All checks were successful
fork-pr-approval-notice / explain fork workflow approval (pull_request_target) Successful in 3s
ac-closure-check / ac-closure check (pull_request) Successful in 6s
ac-closure-check / check (pull_request) Successful in 0s
fragment-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 5s
ac-closure-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 18s
changelog-body-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 18s
fragment-check / changelog fragment-kind (pull_request) Successful in 8s
fragment-check / check (pull_request) Successful in 0s
check-self-bootstrap / check (pull_request) Successful in 18s
manifest-check / manifest-vs-tag consistency (pull_request) Successful in 7s
manifest-check / check (pull_request) Successful in 0s
register-check / register-drift check (pull_request) Successful in 9s
register-check / check (pull_request) Successful in 0s
manifest-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 18s
changelog-body-check / changelog body Cold-Read linter (pull_request) Successful in 43s
register-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 19s
changelog-body-check / check (pull_request) Successful in 0s
tests / bats (pull_request) Successful in 15s
tests / workflow-schema (pull_request) Successful in 19s
workflow-parse-check / toolkit-self parse guard and controls (pull_request) Successful in 4s
tests / shellcheck (pull_request) Successful in 15s
go-ci / lint + build + test (pull_request) Successful in 49s
tests / dated-examples (pull_request) Successful in 23s
workflow-parse-check / workflow parse and schema (pull_request) Successful in 22s
workflow-parse-check / check (pull_request) Successful in 0s
to c05cd3b2e5
All checks were successful
fork-pr-approval-notice / explain fork workflow approval (pull_request_target) Successful in 15s
ac-closure-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 17s
fragment-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 5s
manifest-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 5s
changelog-body-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 26s
register-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 5s
register-check / register-drift check (pull_request) Successful in 8s
register-check / check (pull_request) Successful in 0s
ac-closure-check / ac-closure check (pull_request) Successful in 43s
ac-closure-check / check (pull_request) Successful in 0s
tests / workflow-schema (pull_request) Successful in 4s
tests / dated-examples (pull_request) Successful in 4s
fragment-check / changelog fragment-kind (pull_request) Successful in 37s
tests / shellcheck (pull_request) Successful in 3s
fragment-check / check (pull_request) Successful in 0s
workflow-parse-check / workflow parse and schema (pull_request) Successful in 5s
workflow-parse-check / check (pull_request) Successful in 0s
manifest-check / manifest-vs-tag consistency (pull_request) Successful in 37s
manifest-check / check (pull_request) Successful in 0s
workflow-parse-check / toolkit-self parse guard and controls (pull_request) Successful in 18s
go-ci / lint + build + test (pull_request) Successful in 55s
tests / bats (pull_request) Successful in 33s
release / decide + act (push) Successful in 11s
release / release (push) Successful in 0s
check-self-bootstrap / check (push) Successful in 20s
tests / workflow-schema (push) Successful in 4s
tests / shellcheck (push) Successful in 3s
tests / dated-examples (push) Successful in 20s
tests / bats (push) Successful in 32s
go-ci / lint + build + test (push) Successful in 56s
2026-08-28 18:25:30 +02:00
Compare
bosun dismissed bosun's review 2026-08-28 18:25:30 +02:00
Reason:

New commits pushed, approval review dismissed automatically according to repository settings

bosun approved these changes 2026-08-28 18:26:54 +02:00
bosun left a comment

Re-approving at the rebased head. The branch update dropped #1040's already-merged commits; the rebased tree is purely additive against main — +3 fragment, +28 tests/workflows.bats, zero deletions — so the replay lost nothing. Guard and merge-order note from review 6135 stand unchanged.

Re-approving at the rebased head. The branch update dropped #1040's already-merged commits; the rebased tree is purely additive against main — `+3` fragment, `+28` tests/workflows.bats, **zero deletions** — so the replay lost nothing. Guard and merge-order note from review 6135 stand unchanged.
bosun merged commit c05cd3b2e5 into main 2026-08-28 18:26:55 +02:00
Sign in to join this conversation.
No description provided.