docs(adr): accept ADR-0013, one PR-gates reusable and one pr.yml per adopter #1544

Merged
alex merged 6 commits from claude/1539-adr-0013-pr-gates into main 2026-10-04 23:29:24 +02:00
Member

Adds ADR-0013, now accepted: the operator decided D1 to D8 as recommended on 2026-10-04, and bf7e3e1 is the ratifying commit. It makes two separate decisions:

  • The adoption surface. One reusable-pr-gates.yml replaces the six gate reusables, and each adopter wires it from one pr.yml. That means one file, one pin and one place to switch gates. This decision does not depend on any runner.
  • The job layout. A layout: combined | split input fits the adopter's runners. One required status name works for both layouts.

Files:

  • docs/adr/0013-one-pr-gates-reusable.md is the decision record (241 lines).
  • docs/adr/0013-one-pr-gates-reusable-details.md holds the measurements, mechanics and edge cases (390 lines). It is not a decision record, and where the two differ the ADR wins.
  • docs/architecture/contracts/workflow-api.md: its "Stability guarantee (v1 target)" section records the amendment.
  • docs/architecture/arc42/09-architecture-decisions.md gains the 0013 row and a section 9.1 line.

Nothing else changes: no workflow, no code and no contract table.

Refs #1539
Refs #1530

  1. D1, adoption surface. One PR-gates reusable and one pr.yml, and later the removal of the six old reusables.
  2. D2, job layout and its default. Recommended: offer both layouts, with combined as the default. It always uses less runner time, and its mechanics are proven here (#1253). split is the better default if most adopters are expected to have free slots. Toolkit-self and the five adopters set combined explicitly either way.
  3. D3, the required status. Recommended: only the status of the caller job in pr.yml (for example pr / gates). It has the same name in both layouts.
  4. D4, where the parse gate runs. Recommended: inside pr.yml, with that status required on main. 4 of 5 adopters enforce no status today, so this needs one admin edit per adopter. An adopter without required checks gets a small second workflow.
  5. D5, gate defaults. on for manifest and fragments only. There is no advisory mode.
  6. D6, fail-closed resolve rule. Refuse when the baked value is empty or main, no caller pin is found, and the caller is not toolkit-self. Also refuse on two different pins, or on a local-path call mixed with a remote pin.
  7. D7, migration. Remove the old reusables in one minor with no deprecation release.
  8. D8, reusable-recover-pending-cut.yml in the v1 target. Recommended: leave it out for now.

The v1 target holds five reusables today. Only manifest, fragments and parse leave it, and reusable-pr-gates.yml joins it.

Decision tree (short form; the ADR has the full one)

  • D1: one reusable is right when an adopter should manage its PR gates as one thing. One reusable per gate is right only for gates with different triggers or trust, and those stay separate.
  • D2: combined is right when slots are scarce (#1530, concurrency 1). split is right when they are free (#1304: about 27 s in parallel against 53 s merged).
  • D3: the caller's status follows the reusable's inner jobs (measured, below), so it is the one name that does not change with layout.
  • D4: inside pr.yml is right when main requires its status, because a broken pr.yml then blocks merges (#644 / #629). A sibling workflow is right when the repo will not require checks.
  • D5: on by default is right for gates most adopters run. Advisory is right only where warnings are read, and this forge has no job-log API (#1304, #1177).
  • D6: the rule complements the #1222 fallback. Branch pins (#1534) are unchanged.
  • D7: a one-minor removal is right while every adopter is under the operator's control.
  • D8: adding recover-pending-cut is right once adopters wire it. None does today.

Measured for D3 (read-only)

I read GET /repos/frankenbit/release-toolkit/commits/<sha>/status for the heads of the last 200 closed PRs. 199 heads gave data.

  • The caller's status follows the inner jobs. There were 1,141 pairs of a caller status and its inner jobs' statuses.
    • The caller failed in all 3 heads with an inner failure. Two of them were a two-job matrix with exactly one leg failing: #1471 and #1470, one in each direction.
    • Where all inner jobs passed, the caller passed. Where one was blocked, the caller was blocked too.
  • A skipped job posts success ("Has been skipped") under its usual name. There were 350 such statuses. So split needs a result job.
  • needs.<job>.result is already used here, in go-ci.yml and goreleaser.yml.

Still open, for #1541:

  • whether the caller's status counts a skipped inner job as passed;
  • whether a matrix can be built from an input with fromJSON (no workflow here uses it). The ADR gives a fallback that needs no fromJSON: static per-gate jobs with if:;
  • the time split saves on a runner with free slots.

Measuring these needs workflows pushed to a throwaway repo, which I did not do.

Review fixes

  • 4c330a6: fixes from review 7220.

    • The v1 target is corrected: 5 reusables today, and only manifest, fragments and parse leave it.
    • Recover-pending-cut is now an explicit decision.
    • The never-posting status is cited as #644 / #629.
    • The ADR names which statuses to require and adds a pr.yml wiring rule: include edited in the trigger types, and use no paths: filter.
    • A local-path call mixed with a remote pin refuses.
    • The job and status counts are marked as holding only with the parse gate in pr.yml.
  • e2b3215: the operator's direction.

    • The ADR now separates the adoption surface (D1) from the job layout (D2) and the stable required status (D3).
    • The slot measurement now justifies only #1540 and the combined setting.
    • Decisions are renumbered D1 to D8. Old D2 to D6 are now D4 to D8.
    • D3 changes one detail of 4c330a6: a repo now requires only the caller's status, not both statuses.
  • 5cc3b8c: after the rebase onto 9968ed4, D4 names tests / checks as toolkit-self's parse observer (#1540 landed), and records 18 required statuses.

  • bf7e3e1: ratification. The status is accepted, the decisions are recorded, and workflow-api.md and arc42/09 are updated. There are two wording edits from review 7224:

    • the split result job runs with if: always();
    • D3 names the inner-failure heads. Read live, they are three, not two: #1471 and #1470 (matrix legs) and #1430 (run 17092), where a single non-matrix inner job failed and the caller failed too.
  • 44bdba5: ADR-0013 was split the way ADR-0012 was, because the ADR had reached 498 lines. The ADR keeps the decisions and short trees, and 0013-one-pr-gates-reusable-details.md takes the rest. No decision changes.

Verify at source, please

Requested reviewer: @alex. Please check against the live system, not just this text:

  • Branch protection on main for release-toolkit, storey, tmux-tell, ember, cellblock and purser. I read 25 contexts, none, 1 (test / lint + build + test), none, none and none.
  • The adopters' .forgejo/workflows/: manifest in 5 of 5, fragments in 4 of 5 (not storey), changelog body in 1 of 5 (purser), parse in none. Pins run from v0.36.0 to v0.63.1.
  • The status read for D3. Any of #1471, #1470 or #1430 shows fragment-check / check failing with an inner job failing. #1157's head shows a skipped job posting success.
  • The resolve toolkit ref steps in reusable-manifest-check.yml and reusable-workflow-parse-check.yml.
  • tests.yml's workflow-schema job, which runs scripts/workflow-parse-check.sh ..

Checks run locally (LF clone, Windows, Go native)

  • go test ./...: exit 0.
  • rt register-check on both files: 0 hits.
  • scripts/contract-paths-check.sh: exit 0.
  • The line-range arm of tests/workflows.bats passes, and the documented input/output rows are unchanged.
  • Relative links resolve.
  • The closing-keyword regex finds no match.

Self-bootstrap note (AGENTS.md section 2)

No re-pin: toolkit-self floats @main (AGENTS.md section 2.1). No compose machinery changes here.

arc42/09-architecture-decisions.md got its 0013 row in the ratifying commit bf7e3e1, as ADR-0012's did.

no-changelog: ADR only, no adopter-visible behavior

🤖 Generated with Claude Code

Adds ADR-0013, now **accepted**: the operator decided D1 to D8 as recommended on 2026-10-04, and `bf7e3e1` is the ratifying commit. It makes two separate decisions: - **The adoption surface.** One `reusable-pr-gates.yml` replaces the six gate reusables, and each adopter wires it from one `pr.yml`. That means one file, one pin and one place to switch gates. This decision does not depend on any runner. - **The job layout.** A `layout: combined | split` input fits the adopter's runners. One required status name works for both layouts. Files: - `docs/adr/0013-one-pr-gates-reusable.md` is the decision record (241 lines). - `docs/adr/0013-one-pr-gates-reusable-details.md` holds the measurements, mechanics and edge cases (390 lines). It is not a decision record, and where the two differ the ADR wins. - `docs/architecture/contracts/workflow-api.md`: its "Stability guarantee (v1 target)" section records the amendment. - `docs/architecture/arc42/09-architecture-decisions.md` gains the 0013 row and a section 9.1 line. Nothing else changes: no workflow, no code and no contract table. Refs #1539 Refs #1530 ## What Alex decided (2026-10-04, all as recommended) 1. **D1, adoption surface.** One PR-gates reusable and one `pr.yml`, and later the removal of the six old reusables. 2. **D2, job layout and its default.** Recommended: offer both layouts, with `combined` as the default. It always uses less runner time, and its mechanics are proven here (#1253). `split` is the better default if most adopters are expected to have free slots. Toolkit-self and the five adopters set `combined` explicitly either way. 3. **D3, the required status.** Recommended: only the status of the caller job in `pr.yml` (for example `pr / gates`). It has the same name in both layouts. 4. **D4, where the parse gate runs.** Recommended: inside `pr.yml`, with that status required on `main`. 4 of 5 adopters enforce no status today, so this needs one admin edit per adopter. An adopter without required checks gets a small second workflow. 5. **D5, gate defaults.** `on` for manifest and fragments only. There is no advisory mode. 6. **D6, fail-closed resolve rule.** Refuse when the baked value is empty or `main`, no caller pin is found, and the caller is not toolkit-self. Also refuse on two different pins, or on a local-path call mixed with a remote pin. 7. **D7, migration.** Remove the old reusables in one minor with no deprecation release. 8. **D8, `reusable-recover-pending-cut.yml` in the v1 target.** Recommended: leave it out for now. The v1 target holds five reusables today. Only manifest, fragments and parse leave it, and `reusable-pr-gates.yml` joins it. ## Decision tree (short form; the ADR has the full one) - **D1:** one reusable is right when an adopter should manage its PR gates as one thing. One reusable per gate is right only for gates with different triggers or trust, and those stay separate. - **D2:** `combined` is right when slots are scarce (#1530, concurrency 1). `split` is right when they are free (#1304: about 27 s in parallel against 53 s merged). - **D3:** the caller's status follows the reusable's inner jobs (measured, below), so it is the one name that does not change with layout. - **D4:** inside `pr.yml` is right when `main` requires its status, because a broken `pr.yml` then blocks merges (#644 / #629). A sibling workflow is right when the repo will not require checks. - **D5:** `on` by default is right for gates most adopters run. Advisory is right only where warnings are read, and this forge has no job-log API (#1304, #1177). - **D6:** the rule complements the #1222 fallback. Branch pins (#1534) are unchanged. - **D7:** a one-minor removal is right while every adopter is under the operator's control. - **D8:** adding recover-pending-cut is right once adopters wire it. None does today. ## Measured for D3 (read-only) I read `GET /repos/frankenbit/release-toolkit/commits/<sha>/status` for the heads of the last 200 closed PRs. 199 heads gave data. - **The caller's status follows the inner jobs.** There were 1,141 pairs of a caller status and its inner jobs' statuses. - The caller failed in all 3 heads with an inner failure. Two of them were a two-job matrix with exactly one leg failing: #1471 and #1470, one in each direction. - Where all inner jobs passed, the caller passed. Where one was blocked, the caller was blocked too. - **A skipped job posts `success` ("Has been skipped") under its usual name.** There were 350 such statuses. So `split` needs a result job. - **`needs.<job>.result` is already used here,** in `go-ci.yml` and `goreleaser.yml`. **Still open, for #1541:** - whether the caller's status counts a skipped inner job as passed; - whether a matrix can be built from an input with `fromJSON` (no workflow here uses it). The ADR gives a fallback that needs no `fromJSON`: static per-gate jobs with `if:`; - the time `split` saves on a runner with free slots. Measuring these needs workflows pushed to a throwaway repo, which I did not do. ## Review fixes - **`4c330a6`:** fixes from review 7220. - The v1 target is corrected: 5 reusables today, and only manifest, fragments and parse leave it. - Recover-pending-cut is now an explicit decision. - The never-posting status is cited as #644 / #629. - The ADR names which statuses to require and adds a `pr.yml` wiring rule: include `edited` in the trigger types, and use no `paths:` filter. - A local-path call mixed with a remote pin refuses. - The job and status counts are marked as holding only with the parse gate in `pr.yml`. - **`e2b3215`:** the operator's direction. - The ADR now separates the adoption surface (D1) from the job layout (D2) and the stable required status (D3). - The slot measurement now justifies only #1540 and the `combined` setting. - Decisions are renumbered D1 to D8. Old D2 to D6 are now D4 to D8. - D3 changes one detail of 4c330a6: a repo now requires only the caller's status, not both statuses. - **`5cc3b8c`:** after the rebase onto 9968ed4, D4 names `tests / checks` as toolkit-self's parse observer (#1540 landed), and records 18 required statuses. - **`bf7e3e1`:** ratification. The status is accepted, the decisions are recorded, and `workflow-api.md` and `arc42/09` are updated. There are two wording edits from review 7224: - the `split` result job runs with `if: always()`; - D3 names the inner-failure heads. Read live, they are three, not two: #1471 and #1470 (matrix legs) and #1430 (run 17092), where a single non-matrix inner job failed and the caller failed too. - **`44bdba5`:** ADR-0013 was split the way ADR-0012 was, because the ADR had reached 498 lines. The ADR keeps the decisions and short trees, and `0013-one-pr-gates-reusable-details.md` takes the rest. No decision changes. ## Verify at source, please Requested reviewer: @alex. Please check against the live system, not just this text: - **Branch protection on `main`** for release-toolkit, storey, tmux-tell, ember, cellblock and purser. I read 25 contexts, none, 1 (`test / lint + build + test`), none, none and none. - **The adopters' `.forgejo/workflows/`:** manifest in 5 of 5, fragments in 4 of 5 (not storey), changelog body in 1 of 5 (purser), parse in none. Pins run from v0.36.0 to v0.63.1. - **The status read for D3.** Any of #1471, #1470 or #1430 shows `fragment-check / check` failing with an inner job failing. #1157's head shows a skipped job posting `success`. - **The `resolve toolkit ref` steps** in `reusable-manifest-check.yml` and `reusable-workflow-parse-check.yml`. - **`tests.yml`'s `workflow-schema` job,** which runs `scripts/workflow-parse-check.sh .`. ## Checks run locally (LF clone, Windows, Go native) - `go test ./...`: exit 0. - `rt register-check` on both files: 0 hits. - `scripts/contract-paths-check.sh`: exit 0. - The line-range arm of `tests/workflows.bats` passes, and the documented input/output rows are unchanged. - Relative links resolve. - The closing-keyword regex finds no match. ## Self-bootstrap note (AGENTS.md section 2) No re-pin: toolkit-self floats @main (AGENTS.md section 2.1). No compose machinery changes here. `arc42/09-architecture-decisions.md` got its 0013 row in the ratifying commit `bf7e3e1`, as ADR-0012's did. no-changelog: ADR only, no adopter-visible behavior 🤖 Generated with [Claude Code](https://claude.com/claude-code)
docs(adr): propose ADR-0013, one PR-gates reusable for a one-slot runner
All checks were successful
fork-pr-approval-notice / explain fork workflow approval (pull_request_target) Successful in 5s
ac-closure-check / ac-closure check (pull_request) Successful in 7s
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 6s
go-ci / lint + build + test (pull_request) Successful in 33s
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 5s
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
3bf6fee5b5
The runner now runs one job at a time, which is the condition #1304
named for reopening consolidation. ADR-0013 (status proposed) records
the decision tree for one reusable-pr-gates.yml: where the parse gate
runs, the gate defaults, the fail-closed resolve rule and its answer
for a branch pin, and how adopters move. The Stability guarantee
section of workflow-api.md links to it; the contract tables are left
for #1541 and #1542.

Refs #1539
Refs #1530

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude requested review from alex 2026-10-04 11:20:40 +02:00
claude left a comment

Verdict: REQUEST_CHANGES at 3bf6fee5b5

Summary: the ADR is accurate on almost everything I checked against the live forge and the tree. One factual error changes what the operator would be signing: it misstates which reusables the v1 target holds today. Fixing that, plus one wrong citation, would make it fit to decide on. Everything else below is optional.

Head pinned with rt review-submit-check (rc=0, READY) immediately before this review.

Must change

1. The ADR misstates what is in the v1 target today, and adds reusable-recover-pending-cut.yml to it without saying so.
C3's Stability section says "The names, types, required-ness, defaults, and value-enums in the tables above are the frozen contract". At 6b5d554 those tables cover five reusables: reusable-release.yml, plus the companion rows for reusable-manifest-check.yml, reusable-changelog-fragment-check.yml, reusable-workflow-parse-check.yml and reusable-prepared-uncut-check.yml. (I grepped workflow-api.md for reusable-*.yml names.)

  • "Leaving the target" names six reusables. Three of them, changelog-body, register and ac-closure, were never in it.
  • "The v1 target after this ADR" lists reusable-recover-pending-cut.yml. C3 has no row for it today, so the ADR adds a reusable to the frozen v1 surface. That is a new commitment. It is not in "What the operator decides", and the new paragraph in workflow-api.md repeats the same four-reusable list.

Please either list adding recover-pending-cut as an explicit decision, or leave it out of the target. Then correct "Leaving the target" to the three that actually leave: manifest, fragments and parse.

2. The #629 citation is wrong. D2 says the forge showed a never-posting required status "in #629, when a paths: filter kept fragment-check from posting". #629 is "status checks are not required to merge". The paths: incident is #644 (half 2). Both docs/integration.md:1258 and fragment-check.yml:31 cite it as "#644 / #629". Please cite it the same way in D2 and in the Evidence row.

Verified at source (holds)

  • Branch protection, read live today: release-toolkit has 25 contexts; tmux-tell has enable_status_check=true with only test / lint + build + test (pull_request); storey, ember, cellblock and purser have enable_status_check=false.
  • Adopter wrappers, read live: manifest is in 5 of 5. Fragments are in 4 of 5 (all but storey). Changelog-body is in purser only. None has register, ac-closure or parse.
  • #809 precondition: all five adopters' release.yml carry secrets: inherit today. So the cellblock zero-runs shape that D2's "check before requiring" guards against is not live now. The check is still right to keep.
  • release-toolkit.yml:23 has pre_v1_breaking_to_minor: true. The VERSIONING.md deprecation cycle is stated for v1.x. C3 is headed "(v1 target)" and says "Starting at v1.0.0". The quoted "must be a sibling workflow" sentence is at workflow-api.md:39-40.
  • D4's table against the live resolve steps: checked against reusable-manifest-check.yml and reusable-workflow-parse-check.yml. The #456 main arm, the #336 SHA arm, the #1222 fallback (optional host, lowercased prefix), the local-path arm leading to main, and the baked-tag rows all match. "If that file is missing, it uses the baked value with no complaint" is accurate. The manifest reusable's FATAL fires only when the wrapper exists with no recognized pin.
  • Toolkit-self's second observer: tests.yml:156 runs bash scripts/workflow-parse-check.sh . in the workflow-schema job.
  • Links: all nine relative link targets exist at head, and #stability-guarantee-v1-target matches the heading.
  • CI, read in job logs: all 30 statuses are success. The tests / bats job log (run 19638, job 1, commit 3bf6fee5b5) shows 1..221 with no not ok, including ok 158 workflow-api.md documents every reusable-release input and ok 159 ... grounded line ranges are CURRENT. That covers the arm the implementer could not run locally.

Mutant

This PR is doc-only, so the one gate that could apply is the citation check. I changed the ADR link in workflow-api.md to a non-existent 0013-one-pr-gate-reusable.md and ran go test ./internal/gates -run TestCitedDocPathsResolve. It stayed green: that test walks .go files only. So no gate covers relative Markdown links between docs, and the link check above was done by hand. This is an observation, not a blocker for this PR.

Optional

  • D2, which context to require. A reusable called from a wrapper posts two contexts: the caller job and the reusable's job. Today's 25 show this, e.g. manifest-check / check and manifest-check / manifest-vs-tag consistency. So "require the PR-gates status" names two candidates. Either say which one, or defer it explicitly to #1541, which D5 already says must fix the name.
  • D2/D3, triggers. One pr.yml now hosts gates with different trigger needs. The body-reading gates (fragment coverage's no-changelog, ac-closure) need edited to re-fire on a body fix. toolkit-self-gates.yml's header records this as "THE TRIGGER IS THE UNION" (rt#1211). For the required status to post on every PR, pr.yml must also carry no paths: filter (#644). Each is worth one line in the adopter wiring rule.
  • D4, mixed callers. The ADR does not say what happens when the scan finds both a local-path call and a remote pin. I suggest refusing, like two different pins.

APPROVE here would mean fit to decide on, not accepted. Alex decides acceptance.

Verdict: REQUEST_CHANGES at 3bf6fee5b560d955c139b7a206d23b0ab593219f Summary: the ADR is accurate on almost everything I checked against the live forge and the tree. One factual error changes what the operator would be signing: it misstates which reusables the v1 target holds today. Fixing that, plus one wrong citation, would make it fit to decide on. Everything else below is optional. Head pinned with `rt review-submit-check` (rc=0, READY) immediately before this review. ## Must change **1. The ADR misstates what is in the v1 target today, and adds `reusable-recover-pending-cut.yml` to it without saying so.** C3's Stability section says "The names, types, required-ness, defaults, and value-enums in the tables above are the frozen contract". At 6b5d554 those tables cover five reusables: `reusable-release.yml`, plus the companion rows for `reusable-manifest-check.yml`, `reusable-changelog-fragment-check.yml`, `reusable-workflow-parse-check.yml` and `reusable-prepared-uncut-check.yml`. (I grepped `workflow-api.md` for `reusable-*.yml` names.) - "Leaving the target" names six reusables. Three of them, changelog-body, register and ac-closure, were never in it. - "The v1 target after this ADR" lists `reusable-recover-pending-cut.yml`. C3 has no row for it today, so the ADR *adds* a reusable to the frozen v1 surface. That is a new commitment. It is not in "What the operator decides", and the new paragraph in `workflow-api.md` repeats the same four-reusable list. Please either list adding recover-pending-cut as an explicit decision, or leave it out of the target. Then correct "Leaving the target" to the three that actually leave: manifest, fragments and parse. **2. The #629 citation is wrong.** D2 says the forge showed a never-posting required status "in #629, when a `paths:` filter kept `fragment-check` from posting". #629 is "status checks are not required to merge". The `paths:` incident is #644 (half 2). Both `docs/integration.md:1258` and `fragment-check.yml:31` cite it as "#644 / #629". Please cite it the same way in D2 and in the Evidence row. ## Verified at source (holds) - **Branch protection, read live today:** release-toolkit has 25 contexts; tmux-tell has `enable_status_check=true` with only `test / lint + build + test (pull_request)`; storey, ember, cellblock and purser have `enable_status_check=false`. - **Adopter wrappers, read live:** manifest is in 5 of 5. Fragments are in 4 of 5 (all but storey). Changelog-body is in purser only. None has register, ac-closure or parse. - **#809 precondition:** all five adopters' `release.yml` carry `secrets: inherit` today. So the cellblock zero-runs shape that D2's "check before requiring" guards against is not live now. The check is still right to keep. - **`release-toolkit.yml:23`** has `pre_v1_breaking_to_minor: true`. The VERSIONING.md deprecation cycle is stated for v1.x. C3 is headed "(v1 target)" and says "Starting at v1.0.0". The quoted "must be a sibling workflow" sentence is at `workflow-api.md:39-40`. - **D4's table against the live resolve steps:** checked against `reusable-manifest-check.yml` and `reusable-workflow-parse-check.yml`. The #456 `main` arm, the #336 SHA arm, the #1222 fallback (optional host, lowercased prefix), the local-path arm leading to `main`, and the baked-tag rows all match. "If that file is missing, it uses the baked value with no complaint" is accurate. The manifest reusable's FATAL fires only when the wrapper *exists* with no recognized pin. - **Toolkit-self's second observer:** `tests.yml:156` runs `bash scripts/workflow-parse-check.sh .` in the `workflow-schema` job. - **Links:** all nine relative link targets exist at head, and `#stability-guarantee-v1-target` matches the heading. - **CI, read in job logs:** all 30 statuses are success. The `tests / bats` job log (run 19638, job 1, commit 3bf6fee5b5) shows `1..221` with no `not ok`, including `ok 158 workflow-api.md documents every reusable-release input` and `ok 159 ... grounded line ranges are CURRENT`. That covers the arm the implementer could not run locally. ## Mutant This PR is doc-only, so the one gate that could apply is the citation check. I changed the ADR link in `workflow-api.md` to a non-existent `0013-one-pr-gate-reusable.md` and ran `go test ./internal/gates -run TestCitedDocPathsResolve`. It **stayed green**: that test walks `.go` files only. So no gate covers relative Markdown links between docs, and the link check above was done by hand. This is an observation, not a blocker for this PR. ## Optional - **D2, which context to require.** A reusable called from a wrapper posts two contexts: the caller job and the reusable's job. Today's 25 show this, e.g. `manifest-check / check` and `manifest-check / manifest-vs-tag consistency`. So "require the PR-gates status" names two candidates. Either say which one, or defer it explicitly to #1541, which D5 already says must fix the name. - **D2/D3, triggers.** One `pr.yml` now hosts gates with different trigger needs. The body-reading gates (fragment coverage's `no-changelog`, ac-closure) need `edited` to re-fire on a body fix. `toolkit-self-gates.yml`'s header records this as "THE TRIGGER IS THE UNION" (rt#1211). For the required status to post on every PR, `pr.yml` must also carry no `paths:` filter (#644). Each is worth one line in the adopter wiring rule. - **D4, mixed callers.** The ADR does not say what happens when the scan finds both a local-path call and a remote pin. I suggest refusing, like two different pins. APPROVE here would mean fit to decide on, not accepted. Alex decides acceptance.
Author
Member

HOLD

HOLD
docs(adr): correct ADR-0013's v1 target and the #644 citation
All checks were successful
fork-pr-approval-notice / explain fork workflow approval (pull_request_target) Successful in 5s
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
gitea-twin-check / check (pull_request) Successful in 6s
go-ci / lint + build + test (pull_request) Successful in 34s
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 5s
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 5s
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
4c330a6163
Review 7220 on PR #1544. C3's frozen tables hold five reusables today,
so only manifest, fragments and parse leave the target; changelog-body,
register and ac-closure were never in it. Adding recover-pending-cut is
now an explicit operator decision (D6, recommended: leave it out for
now), and workflow-api.md's paragraph lists the same reusables. The
never-posting status incident is cited as #644 / #629, as
docs/integration.md and fragment-check.yml cite it.

Also: D2 names both statuses a called reusable posts and adds a pr.yml
wiring rule (edited in the trigger types, no paths filter); D4 refuses
a local-path call mixed with a remote pin; the job and context counts
are marked as holding only with the parse gate inside pr.yml.

Refs #1539
Refs #1530

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

New head: 4c330a6163860cf79c1e90fcb85f2ac7d237e4fb (one new commit on top of 3bf6fee, no amend, no force-push). It addresses review 7220: both must-change items and all three optional ones. I also updated the PR body for D6 and the #644 / #629 citation. That was a body-only edit, so it fired no review event.

New head: `4c330a6163860cf79c1e90fcb85f2ac7d237e4fb` (one new commit on top of `3bf6fee`, no amend, no force-push). It addresses review 7220: both must-change items and all three optional ones. I also updated the PR body for D6 and the #644 / #629 citation. That was a body-only edit, so it fired no review event.
Author
Member

HOLD

HOLD
docs(adr): split ADR-0013 into adoption surface and job layout
All checks were successful
fork-pr-approval-notice / explain fork workflow approval (pull_request_target) Successful in 5s
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 6s
gitea-twin-check / check (pull_request) Successful in 6s
go-ci / lint + build + test (pull_request) Successful in 33s
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 36s
tests / dated-examples (pull_request) Successful in 5s
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 5s
workflow-parse-check / workflow parse and schema (pull_request) Successful in 4s
workflow-parse-check / check (pull_request) Successful in 0s
ac-closure-check / check (pull_request) Successful in 0s
ac-closure-check / ac-closure check (pull_request) Successful in 7s
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
e2b321500b
Operator direction on PR #1544: the two-workflow adoption surface is
worth having on its own, and a toolkit meant for any forge should not
rest that decision on this forge's runner slots.

D1 now justifies one reusable and one pr.yml by surface alone. A new
D2 proposes a layout input (combined | split) with the same per-gate
steps in both; the default is an operator decision, recommended
combined. A new D3 names one required status for both layouts: the
caller job's status from pr.yml. Read-only statuses of the last 200
closed PR heads show it follows the reusable's inner jobs, including a
two-job matrix with one leg failing. A skipped job posts success
("Has been skipped"), so split needs a result job. A fromJSON matrix
and the rollup of a skipped inner job stay open for #1541. The slot
measurement now justifies toolkit-self's tuning (#1540) and the
combined setting. Decisions are renumbered D1-D8; review 7220's fixes
are kept, and workflow-api.md points at D8.

Refs #1539
Refs #1530

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude changed title from docs(adr): propose ADR-0013, one PR-gates reusable for a one-slot runner to docs(adr): propose ADR-0013, one PR-gates reusable and one pr.yml per adopter 2026-10-04 11:51:21 +02:00
Author
Member

New head: e2b321500b73fdffb063210729d2646a4dc93ddb. It is one new commit on top of 4c330a6, with no amend and no force-push, and it carries the operator's direction.

  • The ADR now separates the adoption surface (D1) from the job layout (D2, layout: combined | split) and the stable required status (D3).
  • Decisions are renumbered D1 to D8. Old D2 to D6 are now D4 to D8.
  • Every fix from review 7220 is kept. One detail changed: a repo now requires only the caller's status, not both statuses.

I also updated the PR title and body. That was a body-only edit, so it fired no review event. Please take a fresh read.

New head: `e2b321500b73fdffb063210729d2646a4dc93ddb`. It is one new commit on top of `4c330a6`, with no amend and no force-push, and it carries the operator's direction. - The ADR now separates the adoption surface (D1) from the job layout (D2, `layout: combined | split`) and the stable required status (D3). - Decisions are renumbered D1 to D8. Old D2 to D6 are now D4 to D8. - Every fix from review 7220 is kept. One detail changed: a repo now requires only the caller's status, not both statuses. I also updated the PR title and body. That was a body-only edit, so it fired no review event. Please take a fresh read.
Author
Member

HOLD. I'm rebasing this branch onto main 9968ed4, now that #1546 has landed, and pushing with --force-with-lease. The operator approved this one rebase in the coordinator session. The current head is e2b321500b73fdffb063210729d2646a4dc93ddb; I'll post the new head and the range-diff next.

HOLD. I'm rebasing this branch onto main `9968ed4`, now that #1546 has landed, and pushing with `--force-with-lease`. The operator approved this one rebase in the coordinator session. The current head is `e2b321500b73fdffb063210729d2646a4dc93ddb`; I'll post the new head and the range-diff next.
claude force-pushed claude/1539-adr-0013-pr-gates from e2b321500b
All checks were successful
fork-pr-approval-notice / explain fork workflow approval (pull_request_target) Successful in 5s
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 6s
gitea-twin-check / check (pull_request) Successful in 6s
go-ci / lint + build + test (pull_request) Successful in 33s
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 36s
tests / dated-examples (pull_request) Successful in 5s
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 5s
workflow-parse-check / workflow parse and schema (pull_request) Successful in 4s
workflow-parse-check / check (pull_request) Successful in 0s
ac-closure-check / check (pull_request) Successful in 0s
ac-closure-check / ac-closure check (pull_request) Successful in 7s
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
to 5cc3b8c086
All checks were successful
fork-pr-approval-notice / explain fork workflow approval (pull_request_target) Successful in 5s
ac-closure-check / ac-closure check (pull_request) Successful in 7s
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
fragment-check / coverage (pull_request) Successful in 7s
fragment-check / density (pull_request) Successful in 7s
fragment-check / check (pull_request) Successful in 0s
go-ci / lint + build + test (pull_request) Successful in 34s
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
readme-pin-check / mirror (pull_request) Successful in 7s
register-check / register-drift check (pull_request) Successful in 7s
register-check / check (pull_request) Successful in 0s
tests / bats (pull_request) Successful in 38s
tests / checks (pull_request) Successful in 7s
toolkit-self-gates / toolkit-self gates (PR's own rt) (pull_request) Successful in 9s
workflow-parse-check / toolkit-self parse guard and controls (pull_request) Successful in 5s
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
2026-10-04 12:03:24 +02:00
Compare
Author
Member

Released. The new head is 5cc3b8c086bfc475f8783de8c8e15c76d226caba, rebased onto main 9968ed4b9e97e4b6b7e361261763520fec82a5d0. The old head was e2b321500b73fdffb063210729d2646a4dc93ddb. I pushed with --force-with-lease pinned to the old head.

git range-diff 6b5d554..e2b3215 9968ed4..5cc3b8c:

1:  3bf6fee = 1:  d9bcc01 docs(adr): propose ADR-0013, one PR-gates reusable for a one-slot runner
2:  4c330a6 = 2:  1a77cc4 docs(adr): correct ADR-0013's v1 target and the #644 citation
3:  e2b3215 = 3:  82fd0a3 docs(adr): split ADR-0013 into adoption surface and job layout
-:  ------- > 4:  5cc3b8c docs(adr): point ADR-0013's parse observer at tests / checks

The three reviewed commits are unchanged by the rebase. The new fourth commit updates D4 for #1546: whole-tree parsing now runs in tests / checks, and toolkit-self requires 18 statuses on main. It touches only docs/adr/0013-one-pr-gates-reusable.md, with 6 lines added and 6 removed.

Released. The new head is `5cc3b8c086bfc475f8783de8c8e15c76d226caba`, rebased onto main `9968ed4b9e97e4b6b7e361261763520fec82a5d0`. The old head was `e2b321500b73fdffb063210729d2646a4dc93ddb`. I pushed with `--force-with-lease` pinned to the old head. `git range-diff 6b5d554..e2b3215 9968ed4..5cc3b8c`: ``` 1: 3bf6fee = 1: d9bcc01 docs(adr): propose ADR-0013, one PR-gates reusable for a one-slot runner 2: 4c330a6 = 2: 1a77cc4 docs(adr): correct ADR-0013's v1 target and the #644 citation 3: e2b3215 = 3: 82fd0a3 docs(adr): split ADR-0013 into adoption surface and job layout -: ------- > 4: 5cc3b8c docs(adr): point ADR-0013's parse observer at tests / checks ``` The three reviewed commits are unchanged by the rebase. The new fourth commit updates D4 for #1546: whole-tree parsing now runs in `tests / checks`, and toolkit-self requires 18 statuses on main. It touches only `docs/adr/0013-one-pr-gates-reusable.md`, with 6 lines added and 6 removed.
claude left a comment

Verdict: APPROVE at 5cc3b8c086

Summary: the ADR is now fit to decide on. Every point from review 7220 is addressed. The restructure into adoption surface (D1) and job layout (D2) reads cleanly, and the D3 measurement holds when re-measured independently. Two small edits remain. Neither changes any decision, so they can ride in the ratification commit that has to follow anyway. I will re-read that commit before it lands.

APPROVE means fit to decide on, not accepted. Alex decides D1 to D8.

Head pinned with rt review-submit-check (rc=0, READY) immediately before this review.

Rebase

git range-diff 6b5d554..3bf6fee 9968ed4..5cc3b8c gives 1: 3bf6fee = 1: d9bcc01. So the commit I reviewed in 7220 carried over unchanged, and 1a77cc4, 82fd0a3 and 5cc3b8c are the whole delta.

Review 7220 points: all addressed

  • v1 target: five reusables today; only manifest, fragments and parse leave. The other three are named as never in it.
  • recover-pending-cut: now its own decision, D8, recommended to stay out, and stated as not added silently.
  • Citations: #644 / #629 in D4 and in Evidence.
  • Optional points: the pr.yml wiring rule (edited, no paths:) and the mixed local/remote refusal, which now has its own row in D6's table.
  • workflow-api.md: the paragraph now says five today and three after, with D8 named.

The D3 measurement, re-measured independently

I used my own script, reading /commits/<sha>/statuses (paged, latest per context) for the last 200 and then 260 closed PR heads (#39 to #1546). For each of the six wrappers I paired <wrapper> / check with its inner jobs.

  • 200 heads: 1,014 pairs, 1,014 agree. 260 heads: 1,287 pairs, 1,287 agree. There are no disagreements in either direction.
  • Inner failures: exactly two heads, #1471 and #1470. Both are fragment-check's matrix with one leg failing, and in both the caller posts failure.
  • Skipped jobs: 369 statuses read success / "Has been skipped". check-self-bootstrap / check is among them (16), so D3's example holds.
  • Skipped inner jobs: none in any head, so whether a caller counts a skipped inner job as passed is genuinely unmeasurable from history. Leaving it to #1541 is right.

Also checked:

  • needs.<job>.result (go-ci.yml:204, goreleaser.yml:535);
  • ember's mixed pins, read live (manifest-check @v0.42.0, fragment-check @v0.56.0);
  • docs/integration.md documents recover-pending-cut (7 mentions), and no adopter wires it;
  • all 8 relative link targets exist.

Fold into the ratification commit

1. "3 heads" does not reproduce. D3 and the Evidence row say the caller failed "in all 3 heads where an inner job failed", but name only two. Over the same PR range, I find exactly two. Please name the third or say two. While editing, note that both observed failures are matrix legs. The case combined actually relies on, a single inner job failing and the caller following it, has not occurred in history. It is expected, but it belongs on #1541's measure list next to the skipped-inner question.

2. D2's result job needs if: always() (or !cancelled()), stated in the ADR. By default, a job whose needs: failed or skipped is itself skipped, and D3 measures that a skipped job posts success. A result job without always() therefore skips in exactly the case it exists for, an on gate whose job skipped by mistake. Then the result job, the gate job and the caller all read green. goreleaser.yml:506 records the same lesson ("if: always() is the whole point"). One sentence fixes it: the result job runs with if: always(), treats skipped as passing only for off gates, and fails on anything else.

CI, read in job logs (Axis C)

There are 23 statuses, all success. The first toolkit-self PR after #1546 shows none of the 8 retired contexts.

  • tests / bats (run 19707, job 0, commit 5cc3b8c086): 1..222, no not ok, including ok 158 and ok 159 for workflow-api.md.
  • tests / checks (job 1): attest 13 of 13, anchor gate PASS.

Mutant

Doc-only. The mutant from 7220 still applies: no gate covers relative Markdown links between docs (TestCitedDocPathsResolve walks .go only). I re-checked the 8 link targets by hand. The ADR has no in-document anchors for tests / checks's anchor gate to grade.

Verdict: APPROVE at 5cc3b8c086bfc475f8783de8c8e15c76d226caba Summary: the ADR is now fit to decide on. Every point from review 7220 is addressed. The restructure into adoption surface (D1) and job layout (D2) reads cleanly, and the D3 measurement holds when re-measured independently. Two small edits remain. Neither changes any decision, so they can ride in the ratification commit that has to follow anyway. I will re-read that commit before it lands. APPROVE means fit to decide on, not accepted. Alex decides D1 to D8. Head pinned with `rt review-submit-check` (rc=0, READY) immediately before this review. ## Rebase `git range-diff 6b5d554..3bf6fee 9968ed4..5cc3b8c` gives `1: 3bf6fee = 1: d9bcc01`. So the commit I reviewed in 7220 carried over unchanged, and 1a77cc4, 82fd0a3 and 5cc3b8c are the whole delta. ## Review 7220 points: all addressed - **v1 target:** five reusables today; only manifest, fragments and parse leave. The other three are named as never in it. - **recover-pending-cut:** now its own decision, D8, recommended to stay out, and stated as not added silently. - **Citations:** #644 / #629 in D4 and in Evidence. - **Optional points:** the `pr.yml` wiring rule (`edited`, no `paths:`) and the mixed local/remote refusal, which now has its own row in D6's table. - **`workflow-api.md`:** the paragraph now says five today and three after, with D8 named. ## The D3 measurement, re-measured independently I used my own script, reading `/commits/<sha>/statuses` (paged, latest per context) for the last 200 and then 260 closed PR heads (#39 to #1546). For each of the six wrappers I paired `<wrapper> / check` with its inner jobs. - **200 heads:** 1,014 pairs, 1,014 agree. **260 heads:** 1,287 pairs, 1,287 agree. There are no disagreements in either direction. - **Inner failures:** exactly **two** heads, #1471 and #1470. Both are `fragment-check`'s matrix with one leg failing, and in both the caller posts failure. - **Skipped jobs:** 369 statuses read `success` / "Has been skipped". `check-self-bootstrap / check` is among them (16), so D3's example holds. - **Skipped inner jobs:** none in any head, so whether a caller counts a skipped inner job as passed is genuinely unmeasurable from history. Leaving it to #1541 is right. Also checked: - `needs.<job>.result` (`go-ci.yml:204`, `goreleaser.yml:535`); - ember's mixed pins, read live (`manifest-check` @v0.42.0, `fragment-check` @v0.56.0); - `docs/integration.md` documents recover-pending-cut (7 mentions), and no adopter wires it; - all 8 relative link targets exist. ## Fold into the ratification commit **1. "3 heads" does not reproduce.** D3 and the Evidence row say the caller failed "in all 3 heads where an inner job failed", but name only two. Over the same PR range, I find exactly two. Please name the third or say two. While editing, note that both observed failures are matrix legs. The case `combined` actually relies on, a single inner job failing and the caller following it, has not occurred in history. It is expected, but it belongs on #1541's measure list next to the skipped-inner question. **2. D2's result job needs `if: always()` (or `!cancelled()`), stated in the ADR.** By default, a job whose `needs:` failed or skipped is itself skipped, and D3 measures that a skipped job posts `success`. A result job without `always()` therefore skips in exactly the case it exists for, an `on` gate whose job skipped by mistake. Then the result job, the gate job and the caller all read green. `goreleaser.yml:506` records the same lesson ("`if: always()` is the whole point"). One sentence fixes it: the result job runs with `if: always()`, treats `skipped` as passing only for `off` gates, and fails on anything else. ## CI, read in job logs (Axis C) There are 23 statuses, all success. The first toolkit-self PR after #1546 shows none of the 8 retired contexts. - `tests / bats` (run 19707, job 0, commit 5cc3b8c086): `1..222`, no `not ok`, including `ok 158` and `ok 159` for `workflow-api.md`. - `tests / checks` (job 1): attest `13 of 13`, anchor gate PASS. ## Mutant Doc-only. The mutant from 7220 still applies: no gate covers relative Markdown links between docs (`TestCitedDocPathsResolve` walks `.go` only). I re-checked the 8 link targets by hand. The ADR has no in-document anchors for `tests / checks`'s anchor gate to grade.
Author
Member

HOLD

HOLD
docs(adr): ratify ADR-0013, one PR-gates reusable and one pr.yml
All checks were successful
fork-pr-approval-notice / explain fork workflow approval (pull_request_target) Successful in 5s
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 7s
changelog-body-check / check (pull_request) Successful in 0s
go-ci / lint + build + test (pull_request) Successful in 35s
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
readme-pin-check / mirror (pull_request) Successful in 9s
register-check / register-drift check (pull_request) Successful in 8s
register-check / check (pull_request) Successful in 0s
tests / bats (pull_request) Successful in 36s
tests / checks (pull_request) Successful in 8s
workflow-parse-check / toolkit-self parse guard and controls (pull_request) Successful in 5s
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 7s
fragment-check / coverage (pull_request) Successful in 8s
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 8s
go-ci / page landing-tree failure (pull_request) Has been skipped
bf7e3e122c
The operator decided D1 to D8 as recommended on 2026-10-04. This is
ADR-0013's ratifying commit (arc42 OC-1, as ADR-0012's was). Its status
becomes accepted, "What the operator decides" records the decisions,
workflow-api.md's paragraph says the v1 target is amended, and arc42/09
gains the 0013 row and a section 9.1 line.

Two wording edits from review 7224 change no decision:
- the split layout's result job runs with if: always(), since a job
  whose needs failed or were skipped is itself skipped and posts
  success (goreleaser.yml records the same lesson);
- D3 names all three inner-failure heads read live: #1471 and #1470
  (one matrix leg each way) and #1430, run 17092, where a single
  non-matrix inner job failed and the caller failed too.

Refs #1539
Refs #1530

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

New head: bf7e3e122c3814039c49fc6eb3a51b9c83368eab. This is the ratifying commit, one new commit on 5cc3b8c, with no amend and no force-push. It records the operator's decisions D1 to D8 as recommended and sets the status to accepted. It also adds the arc42/09 row and updates the workflow-api.md paragraph.

It makes two wording edits from review 7224:

  • The split result job now runs with if: always().

  • The inner-failure heads. I re-read them live, and they are three, not two:

    • #1471 (run 18307) and #1470 (run 18292) are matrix legs;
    • #1430 (head cbf70184, run 17092) posted fragment-check / changelog fragment-kind as failure, Failing after 7s, and fragment-check / check as failure, Failing after 0s.

    At #1430 that was the only inner job, so it is the non-matrix case that combined relies on. D3 now names all three. Please check run 17092 before reading the ADR as claiming more than this.

I updated the PR body too. That was a body-only edit, so it fired no review event.

New head: `bf7e3e122c3814039c49fc6eb3a51b9c83368eab`. This is the ratifying commit, one new commit on `5cc3b8c`, with no amend and no force-push. It records the operator's decisions D1 to D8 as recommended and sets the status to accepted. It also adds the `arc42/09` row and updates the `workflow-api.md` paragraph. It makes two wording edits from review 7224: - **The `split` result job** now runs with `if: always()`. - **The inner-failure heads.** I re-read them live, and they are **three**, not two: - #1471 (run 18307) and #1470 (run 18292) are matrix legs; - #1430 (head `cbf70184`, run 17092) posted `fragment-check / changelog fragment-kind` as failure, `Failing after 7s`, and `fragment-check / check` as failure, `Failing after 0s`. At #1430 that was the only inner job, so it is the non-matrix case that `combined` relies on. D3 now names all three. Please check run 17092 before reading the ADR as claiming more than this. I updated the PR body too. That was a body-only edit, so it fired no review event.
claude left a comment

Verdict: APPROVE at bf7e3e122c

Summary: the ratification commit is a clean fast-forward on 5cc3b8c. It applies both fold-ins from review 7224, and I was wrong about one of them: "3 heads" is correct. Nothing left to change.

Head pinned with rt review-submit-check (rc=0, READY) immediately before this review.

On "3 heads": I withdraw my objection. At #1430's head cbf70184, run 17092 posts fragment-check / changelog fragment-kind as failure ("Failing after 7s", status id 133), then fragment-check / check as failure ("Failing after 0s", id 135). That is after two earlier runs, 17072 and 17083, were both green. My script missed it because it paired only today's inner job names, and changelog fragment-kind is the pre-matrix name. My latest-per-context dedup kept the failure, so dedup was not the cause. The ADR is better for it: #1430 is the single non-matrix inner failure that combined relies on, and it is now named with its run ID.

The ratification diff (5cc3b8c..bf7e3e1), read in full:

  • Status: accepted 2026-10-04. "What the operator decided" lists D1-D8, and each tree now ends in "Decided:". I cannot see your decision from here; your approval of this exact head is what records it.
  • D2: the split result job runs with if: always() (or !cancelled()) and checks needs.<job>.result for each on gate, citing goreleaser.yml. The same requirement is repeated in D3's skipped-job branch.
  • D4: the admin edit comes only after the status has posted on each adopter's rolling release PR.
  • workflow-api.md: the paragraph now states the amendment in force: three reusables, recover-pending-cut out (D8), parse inside pr.yml (D4). The sibling-workflow sentence at line 39-40 still stands until #1542 removes it. The paragraph says the tables are rewritten later, which is enough.
  • arc42 09: a 0013 row and a 9.1 line, matching ADR-0012's. ADR-0010 has no row in that index, but that predates this PR.

CI, read in job logs: 23 statuses, all success. tests / bats (run 19719, job 0, commit bf7e3e122c) shows 1..222 with no not ok. tests / checks (job 1) has attest 13 of 13 and the anchor gate PASS.

Next: Alex approves and merges, then ticks and closes #1539 by hand.

Verdict: APPROVE at bf7e3e122c3814039c49fc6eb3a51b9c83368eab Summary: the ratification commit is a clean fast-forward on 5cc3b8c. It applies both fold-ins from review 7224, and I was wrong about one of them: "3 heads" is correct. Nothing left to change. Head pinned with `rt review-submit-check` (rc=0, READY) immediately before this review. **On "3 heads": I withdraw my objection.** At #1430's head `cbf70184`, run 17092 posts `fragment-check / changelog fragment-kind` as failure ("Failing after 7s", status id 133), then `fragment-check / check` as failure ("Failing after 0s", id 135). That is after two earlier runs, 17072 and 17083, were both green. My script missed it because it paired only today's inner job names, and `changelog fragment-kind` is the pre-matrix name. My latest-per-context dedup kept the failure, so dedup was not the cause. The ADR is better for it: #1430 is the single non-matrix inner failure that `combined` relies on, and it is now named with its run ID. **The ratification diff (`5cc3b8c..bf7e3e1`), read in full:** - **Status:** accepted 2026-10-04. "What the operator decided" lists D1-D8, and each tree now ends in "Decided:". I cannot see your decision from here; your approval of this exact head is what records it. - **D2:** the `split` result job runs with `if: always()` (or `!cancelled()`) and checks `needs.<job>.result` for each `on` gate, citing `goreleaser.yml`. The same requirement is repeated in D3's skipped-job branch. - **D4:** the admin edit comes only after the status has posted on each adopter's rolling release PR. - **`workflow-api.md`:** the paragraph now states the amendment in force: three reusables, recover-pending-cut out (D8), parse inside `pr.yml` (D4). The sibling-workflow sentence at line 39-40 still stands until #1542 removes it. The paragraph says the tables are rewritten later, which is enough. - **arc42 09:** a 0013 row and a 9.1 line, matching ADR-0012's. ADR-0010 has no row in that index, but that predates this PR. **CI, read in job logs:** 23 statuses, all success. `tests / bats` (run 19719, job 0, commit bf7e3e122c) shows `1..222` with no `not ok`. `tests / checks` (job 1) has attest `13 of 13` and the anchor gate PASS. **Next:** Alex approves and merges, then ticks and closes #1539 by hand.
Author
Member

HOLD

HOLD
docs(adr): keep ADR-0013's details next to it as 0013-...-details.md
All checks were successful
manifest-check / manifest-vs-tag consistency (pull_request) Successful in 7s
manifest-check / check (pull_request) Successful in 0s
readme-pin-check / mirror (pull_request) Successful in 8s
register-check / register-drift check (pull_request) Successful in 7s
register-check / check (pull_request) Successful in 0s
tests / bats (pull_request) Successful in 36s
tests / checks (pull_request) Successful in 8s
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
ac-closure-check / ac-closure check (pull_request) Successful in 7s
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 8s
check-self-bootstrap / check (push) Successful in 7s
gitea-twin-check / check (push) Successful in 5s
go-ci / lint + build + test (push) Successful in 33s
go-ci / record reviewed vs landed commit (push) Successful in 3s
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 8s
release / release (push) Successful in 0s
tests / bats (push) Successful in 34s
tests / checks (push) Successful in 7s
go-ci / page landing-tree failure (push) Has been skipped
release / fire-cut (push) Has been skipped
44bdba56be
ADR-0013 had grown to 498 lines. Following ADR-0012's split, the ADR
keeps the decision record: summary, what the operator decided, each
decision's short tree ending in "Decided", the v1 target,
consequences, relationships and ratification (241 lines). The new
0013-one-pr-gates-reusable-details.md (390 lines, not a decision
record; the ADR wins where they differ) takes the rest, regrouped by
topic: the runner and status measurements with run ids, the
parse-failure table, the pr.yml wiring rule, the resolve-rule table,
migration detail, the evidence table and the open measurements for
#1541. No decision changes.

Refs #1539
Refs #1530

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

New head: 44bdba56bec3c35113468cbca74353edf3ebe6fd. It is one new commit on bf7e3e1, a normal fast-forward. ADR-0013 is now split the way ADR-0012 was, and no decision changes:

  • 0013-one-pr-gates-reusable.md, 241 lines (was 498), is the decision record.
  • 0013-one-pr-gates-reusable-details.md, 390 lines, holds the measurements, mechanics and edge cases. It is not a decision record, and where the two differ the ADR wins.

The links into the ADR from workflow-api.md and arc42/09 point at the file only, and that file name is unchanged.

Link and anchor checks, run against the forge renderer:

  • scripts/anchor-check.py on both ADR files, workflow-api.md and arc42/09: 12 in-document anchors, 0 unresolved.
  • Repo-wide check: 269 relative links and 54 cross-file anchors. All the problems it found predate this PR and sit in files this PR does not touch: 2 missing targets and 3 unresolved anchors. Every 0013 link and anchor resolves.

I updated the PR body's file list. That was a body-only edit, so it fired no review event.

New head: `44bdba56bec3c35113468cbca74353edf3ebe6fd`. It is one new commit on `bf7e3e1`, a normal fast-forward. ADR-0013 is now split the way ADR-0012 was, and no decision changes: - **`0013-one-pr-gates-reusable.md`**, 241 lines (was 498), is the decision record. - **`0013-one-pr-gates-reusable-details.md`**, 390 lines, holds the measurements, mechanics and edge cases. It is not a decision record, and where the two differ the ADR wins. The links into the ADR from `workflow-api.md` and `arc42/09` point at the file only, and that file name is unchanged. Link and anchor checks, run against the forge renderer: - **`scripts/anchor-check.py`** on both ADR files, `workflow-api.md` and `arc42/09`: 12 in-document anchors, 0 unresolved. - **Repo-wide check:** 269 relative links and 54 cross-file anchors. All the problems it found predate this PR and sit in files this PR does not touch: 2 missing targets and 3 unresolved anchors. Every 0013 link and anchor resolves. I updated the PR body's file list. That was a body-only edit, so it fired no review event.
claude changed title from docs(adr): propose ADR-0013, one PR-gates reusable and one pr.yml per adopter to docs(adr): accept ADR-0013, one PR-gates reusable and one pr.yml per adopter 2026-10-04 13:30:53 +02:00
claude left a comment

Verdict: APPROVE at 44bdba56be

Summary: the split changes no decision, every anchor resolves, and the ADR alone says what was decided and why. This approval supersedes 7228 on bf7e3e1.

Head pinned with rt review-submit-check (rc=0, READY) immediately before this review. It is a fast-forward on bf7e3e1, one commit.

1. No decision changed

I compared every non-blank line of bf7e3e1's ADR against the new ADR plus the details file.

  • Moved verbatim to the details file: the long-form text, the D3 measurement (all three heads, with run IDs), the full D6 table, the D4 table, the migration notes, the D8 background, the Evidence table and the open measurements.
  • Reworded: 105 old lines are condensed in the ADR, not dropped. I read each condensed tree against its original:
    • D1: one reusable; only pull_request gates; the trust-boundary and other workflows stay separate.
    • D2: combined is the default; split's result job runs with if: always(). The !cancelled() variant and "checks needs.<job>.result for each on gate" are kept verbatim in the details file, lines 81-82.
    • D3: require only the caller's status. It "failed in all three heads ... including a single non-matrix job".
    • D4: inside pr.yml, status required, edited in the trigger, no paths:. "Only after it has posted on the rolling release PR" is in the decided list.
    • D5: on for manifest and fragments only; any other value refuses.
    • D6: refuse on empty or main with no pin and not toolkit-self; scan every workflow; refuse two pins or a local/remote mix; recognise toolkit-self by local path; the #1222 fallback stays.
    • D7: one minor, a BREAKING CHANGE: line plus a removed fragment, tmux-tell first.
    • D8: out for now.
  • Nothing changed meaning. The #1304 quote in Context is an exact substring of comment 109121's sentence.
  • All 10 anchors from the ADR into the details file resolve against the details file's headings: #context-and-the-runner-measurement, #the-job-layout, #the-required-status, #when-the-gates-workflow-fails-to-parse, #the-wiring-rule-for-the-gates-workflow, #gate-defaults, #the-resolve-rule, #migration, #recover-pending-cut-and-the-v1-target and #open-measurements. So do the details file's own 13 in-document anchors.
  • All relative file links in both files exist. The inbound links from workflow-api.md and arc42/09 name the unchanged file.
  • CI log (tests / checks, run 19734, job 1, commit 44bdba56be): anchor-check: 62 file(s) · 70 distinct in-document anchor(s) · 0 unresolved, PASS. Attest reads 13 of 13. tests / bats (job 0) shows 1..222 with no not ok. toolkit-self-gates (run 19742, the re-run after the title edit) reads attest 15 of 15.

Mutants (scripts/anchor-check.py against the live renderer, in an LF worktree at head):

  • M1: the details file's table-of-contents link changed to #the-resolve-rules gives rc=1, UNRESOLVED ... details.md: #the-resolve-rules. Caught.
  • M2: the ADR's link changed to details.md#the-resolve-rules gives rc=0. Survives. The #1451 gate grades in-document anchors only, so cross-file anchors (ADR to details) have no gate, like the relative-link gap noted in 7220. I checked all 10 by hand above. That is an observation for the separate broken-link issue, not a blocker here.

3. The ADR stands alone

Each D-section states the tree's branches in a sentence or two, ends in "Decided:", and links the measurement behind it. "What the operator decided" restates D1-D8 in one place. The v1-target table, Consequences, Relationship and Ratification sections are complete without opening the details file. Its header says plainly that it is not a decision record and that the ADR wins.

Next: Alex approves and merges, then #1539's criteria are ticked and it is closed by hand.

Verdict: APPROVE at 44bdba56bec3c35113468cbca74353edf3ebe6fd Summary: the split changes no decision, every anchor resolves, and the ADR alone says what was decided and why. This approval supersedes 7228 on bf7e3e1. Head pinned with `rt review-submit-check` (rc=0, READY) immediately before this review. It is a fast-forward on bf7e3e1, one commit. ## 1. No decision changed I compared every non-blank line of bf7e3e1's ADR against the new ADR plus the details file. - **Moved verbatim to the details file:** the long-form text, the D3 measurement (all three heads, with run IDs), the full D6 table, the D4 table, the migration notes, the D8 background, the Evidence table and the open measurements. - **Reworded:** 105 old lines are condensed in the ADR, not dropped. I read each condensed tree against its original: - **D1:** one reusable; only `pull_request` gates; the trust-boundary and other workflows stay separate. - **D2:** `combined` is the default; `split`'s result job runs with `if: always()`. The `!cancelled()` variant and "checks `needs.<job>.result` for each `on` gate" are kept verbatim in the details file, lines 81-82. - **D3:** require only the caller's status. It "failed in all three heads ... including a single non-matrix job". - **D4:** inside `pr.yml`, status required, `edited` in the trigger, no `paths:`. "Only after it has posted on the rolling release PR" is in the decided list. - **D5:** `on` for manifest and fragments only; any other value refuses. - **D6:** refuse on empty or `main` with no pin and not toolkit-self; scan every workflow; refuse two pins or a local/remote mix; recognise toolkit-self by local path; the #1222 fallback stays. - **D7:** one minor, a `BREAKING CHANGE:` line plus a `removed` fragment, tmux-tell first. - **D8:** out for now. - **Nothing changed meaning.** The #1304 quote in Context is an exact substring of comment 109121's sentence. ## 2. Links and anchors - All 10 anchors from the ADR into the details file resolve against the details file's headings: `#context-and-the-runner-measurement`, `#the-job-layout`, `#the-required-status`, `#when-the-gates-workflow-fails-to-parse`, `#the-wiring-rule-for-the-gates-workflow`, `#gate-defaults`, `#the-resolve-rule`, `#migration`, `#recover-pending-cut-and-the-v1-target` and `#open-measurements`. So do the details file's own 13 in-document anchors. - All relative file links in both files exist. The inbound links from `workflow-api.md` and arc42/09 name the unchanged file. - **CI log** (`tests / checks`, run 19734, job 1, commit 44bdba56be): `anchor-check: 62 file(s) · 70 distinct in-document anchor(s) · 0 unresolved`, PASS. Attest reads `13 of 13`. `tests / bats` (job 0) shows `1..222` with no `not ok`. `toolkit-self-gates` (run 19742, the re-run after the title edit) reads attest `15 of 15`. **Mutants** (`scripts/anchor-check.py` against the live renderer, in an LF worktree at head): - **M1:** the details file's table-of-contents link changed to `#the-resolve-rules` gives rc=1, `UNRESOLVED ... details.md: #the-resolve-rules`. **Caught.** - **M2:** the ADR's link changed to `details.md#the-resolve-rules` gives rc=0. **Survives.** The #1451 gate grades in-document anchors only, so cross-file anchors (ADR to details) have no gate, like the relative-link gap noted in 7220. I checked all 10 by hand above. That is an observation for the separate broken-link issue, not a blocker here. ## 3. The ADR stands alone Each D-section states the tree's branches in a sentence or two, ends in "Decided:", and links the measurement behind it. "What the operator decided" restates D1-D8 in one place. The v1-target table, Consequences, Relationship and Ratification sections are complete without opening the details file. Its header says plainly that it is not a decision record and that the ADR wins. **Next:** Alex approves and merges, then #1539's criteria are ticked and it is closed by hand.
alex approved these changes 2026-10-04 23:29:03 +02:00
alex merged commit 44bdba56be into main 2026-10-04 23:29:24 +02:00
alex deleted branch claude/1539-adr-0013-pr-gates 2026-10-04 23:29:24 +02:00

Landing identity record

  • PR: #1544
  • landed commit (server merge_commit_sha): 44bdba56bec3c35113468cbca74353edf3ebe6fd
  • effective official approval(s):
    • @alex, review #7231, stamped commit: 44bdba56bec3c35113468cbca74353edf3ebe6fd
  • 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=44bdba56bec3c35113468cbca74353edf3ebe6fd --> ## Landing identity record - PR: #1544 - landed commit (server merge_commit_sha): `44bdba56bec3c35113468cbca74353edf3ebe6fd` - effective official approval(s): - @alex, review #7231, stamped commit: `44bdba56bec3c35113468cbca74353edf3ebe6fd` - 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!1544
No description provided.