From 9a229ee643291c27dbd404fa97e4dcc7e6ed3b17 Mon Sep 17 00:00:00 2001 From: cluade-reviewer-andresmgsl Date: Tue, 4 Aug 2026 10:01:21 +0000 Subject: [PATCH 1/2] release: stamp 0.4.1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The three stamps, in one commit as the ceremony requires: VERSION goes bare, the self-ref pin follows it, and the changelog section is the assembler's output rather than a hand edit. 0.4.1 is the forge release. Everything in the section comes from #188's single fragment: ceremony stops being gh-only. `lib/forge.sh` selects a backend from the runner's own environment, `lib/forge-github.sh` and `lib/forge-forgejo.sh` implement one call surface twice, and the reconcilers preflight before they sweep — so a GitHub-shaped client pointed at a Forgejo instance is a named refusal instead of a sweep that reads nothing and reports success. Verified on this instance before stamping: the first post-merge `labels` run on main (7fc9afe, task 467) came back SUCCESS — the reconciler's first green run on Forgejo, and the evidence the section's claims are not merely asserted. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/labels.yml | 2 +- .github/workflows/release.yml | 2 +- CHANGELOG.md | 59 +++++++++++++++++++++++++++++++++++ VERSION | 2 +- changelog.d/188.md | 56 --------------------------------- 5 files changed, 62 insertions(+), 59 deletions(-) delete mode 100644 changelog.d/188.md diff --git a/.github/workflows/labels.yml b/.github/workflows/labels.yml index 86328ae..6e2b194 100644 --- a/.github/workflows/labels.yml +++ b/.github/workflows/labels.yml @@ -28,7 +28,7 @@ on: env: # A called workflow arrives without its repository. Keep this literal pin # aligned with the ceremony release consumed by callers (issue #9 D3). - CEREMONY_SELF_REF: "0.4.0" + CEREMONY_SELF_REF: "0.4.1" jobs: scope: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d604f0e..9081740 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -129,7 +129,7 @@ env: # `ref:` accepts ${{ env }}; `uses:` strings do not — which is why the # shared logic arrives as script files via checkout, not as inner `uses:` # references. - CEREMONY_SELF_REF: "0.4.0" + CEREMONY_SELF_REF: "0.4.1" VERSION_SOURCE: ${{ inputs.version-source }} jobs: diff --git a/CHANGELOG.md b/CHANGELOG.md index fc33418..a667eea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,65 @@ Entries arrive as fragments — one `changelog.d/.md` per PR, never an edit to this file — and the release PR assembles them into the next section here (`bin/changelog-assemble`, #112). +## 0.4.1 — 2026-08-04 + +### Added + +- `lib/forge.sh` — the forge selector: `forge_detect` names the forge from + the runner's own environment, `forge_client` names the client it needs, and + `forge_preflight` refuses loudly before any sweep when the two disagree + (#188). +- The reconcilers and `labels-scope` run that preflight first, so a + GitHub-shaped client on a Forgejo instance is a named refusal instead of a + sweep that reads nothing and reports success (#188). +- `lib/closes_references.sh` — the closing-keyword parser, sibling of + `refs_references`, so "which issues does this PR close" is answered from a + PR body rather than from GitHub's GraphQL API (#188). +- `lib/forge-github.sh` and `lib/forge-forgejo.sh` — one call surface, two + backends, selected by `forge_select`; no forge branching at the call sites + (#188). +- The forgejo backend proves each paginated gather complete against the + server's `x-total-count` and refuses loudly when it cannot — a missing + header is a refusal, not a pass (#188). + +### Changed + +- `issueflow-reconcile` gathers open and merged PRs over REST instead of + `gh api graphql`. Forgejo serves no GraphQL at all, so the two queries were + replaced rather than translated; both forges return `number` and `body` + from `/pulls` in the same shape (#188). + +- `forge_api` owns the page size, because each forge silently ignores the + other's parameter: `per_page=100` reads 30 items on Forgejo and `limit=100` + reads 30 on GitHub, both HTTP 200. No call site names one (#188). + +- Outstanding review requests are derived from the reviews on the current head + rather than from `requested_reviewers`, which Forgejo never clears — read + raw there, a PR would sit at `state:bots-reviewing` forever (#188). + +### Fixed + +- `labels-scope` no longer fails to compile its jq program on jq 1.6, which + the Forgejo runner image ships: `label` is a reserved word in jq's grammar, + so `$label` is a syntax error there and every scope derivation died before + reading the config (#188). + +- `labels-reconcile` and `labels-scope` no longer exit 0 on a Forgejo + consumer having read zero facts — measured on `heavy-duty/rig`, where the + sweep printed `reconciled.` over an empty PR list and scope reported "no + labeler.yml" for a file that exists (#188). + +- `forge_timeline` projects Forgejo's label events (`.type` / `.body` / + `.user.login`) into the GitHub shape (`.event` / `.actor.login`) so the + ruling ladder reads the same board on both forges (#188). + +- `forge_pr_activity` no longer calls `/pulls/{n}/comments` on Forgejo + (HTTP 404); inline review comments come from reviews with + `comments_count > 0` (#188). + +- CI installs shellcheck before linting, matching actionlint — the Forgejo + runner image does not ship it (#188). + ## 0.4.0 — 2026-07-29 ### Added diff --git a/VERSION b/VERSION index 1351681..267577d 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.4.1-dev +0.4.1 diff --git a/changelog.d/188.md b/changelog.d/188.md deleted file mode 100644 index f85084c..0000000 --- a/changelog.d/188.md +++ /dev/null @@ -1,56 +0,0 @@ -### Added - -- `lib/forge.sh` — the forge selector: `forge_detect` names the forge from - the runner's own environment, `forge_client` names the client it needs, and - `forge_preflight` refuses loudly before any sweep when the two disagree - (#188). -- The reconcilers and `labels-scope` run that preflight first, so a - GitHub-shaped client on a Forgejo instance is a named refusal instead of a - sweep that reads nothing and reports success (#188). -- `lib/closes_references.sh` — the closing-keyword parser, sibling of - `refs_references`, so "which issues does this PR close" is answered from a - PR body rather than from GitHub's GraphQL API (#188). -- `lib/forge-github.sh` and `lib/forge-forgejo.sh` — one call surface, two - backends, selected by `forge_select`; no forge branching at the call sites - (#188). -- The forgejo backend proves each paginated gather complete against the - server's `x-total-count` and refuses loudly when it cannot — a missing - header is a refusal, not a pass (#188). - -### Changed - -- `issueflow-reconcile` gathers open and merged PRs over REST instead of - `gh api graphql`. Forgejo serves no GraphQL at all, so the two queries were - replaced rather than translated; both forges return `number` and `body` - from `/pulls` in the same shape (#188). - -- `forge_api` owns the page size, because each forge silently ignores the - other's parameter: `per_page=100` reads 30 items on Forgejo and `limit=100` - reads 30 on GitHub, both HTTP 200. No call site names one (#188). - -- Outstanding review requests are derived from the reviews on the current head - rather than from `requested_reviewers`, which Forgejo never clears — read - raw there, a PR would sit at `state:bots-reviewing` forever (#188). - -### Fixed - -- `labels-scope` no longer fails to compile its jq program on jq 1.6, which - the Forgejo runner image ships: `label` is a reserved word in jq's grammar, - so `$label` is a syntax error there and every scope derivation died before - reading the config (#188). - -- `labels-reconcile` and `labels-scope` no longer exit 0 on a Forgejo - consumer having read zero facts — measured on `heavy-duty/rig`, where the - sweep printed `reconciled.` over an empty PR list and scope reported "no - labeler.yml" for a file that exists (#188). - -- `forge_timeline` projects Forgejo's label events (`.type` / `.body` / - `.user.login`) into the GitHub shape (`.event` / `.actor.login`) so the - ruling ladder reads the same board on both forges (#188). - -- `forge_pr_activity` no longer calls `/pulls/{n}/comments` on Forgejo - (HTTP 404); inline review comments come from reviews with - `comments_count > 0` (#188). - -- CI installs shellcheck before linting, matching actionlint — the Forgejo - runner image does not ship it (#188). -- 2.45.2 From abb1c818a34fd4f0e8e687d05662bbe3bee8496d Mon Sep 17 00:00:00 2001 From: cluade-reviewer-andresmgsl Date: Tue, 4 Aug 2026 10:44:02 +0000 Subject: [PATCH 2/2] =?UTF-8?q?drill(0.4.1):=20record=20the=20rehearsal=20?= =?UTF-8?q?=E2=80=94=20both=20doors=20are=20inoperable=20here?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The drill ran and FAILED, and the record says so. Merge door: lib/facts.sh reads the release label with `gh api`, the runner image has no gh, the fact comes back "no", and decide fail-closes on a wrong fact — reproduced twice. Tag door: clears every gate the merge door fails, then dies at `gh release create`. Release count in the scratch repo at the end: 0. Every refusal created nothing, which is the property the drill exists to check. drill-recorded wants a record, not a passing result — this is the honest one, and it says 0.4.1 cannot publish from this instance until facts.sh and the publish call sites are ported off gh. Co-Authored-By: Claude Opus 5 (1M context) --- drills/0.4.1.md | 79 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 drills/0.4.1.md diff --git a/drills/0.4.1.md b/drills/0.4.1.md new file mode 100644 index 0000000..33bf2b3 --- /dev/null +++ b/drills/0.4.1.md @@ -0,0 +1,79 @@ +# 0.4.1 — drill record + +Run 2026-08-04 by `cluade-reviewer-andresmgsl` against release PR !190 head +`9a229ee643291c27dbd404fa97e4dcc7e6ed3b17`. + +**This drill FAILED. Both doors of the release workflow are inoperable on +`forgejo.heavyduty.builders`.** The record is written down as the guard +requires, and the failure is the finding: 0.4.1 cannot publish from this +instance until `lib/facts.sh` and the publish steps are ported off `gh`. + +Where: disposable private repo `heavy-duty/ceremony-drill-0.4.1`, armed at +`0.4.1-dev` with the `docs/CONSUMERS.md` release caller, a fragment-mode +fixture (`changelog.d/README.md` + one fragment), and a non-blank drill +record. Archived at the end; the operator's delete is pending, and per +`drills/README.md` cleanup gates nothing. + +## Deviations + +* **Candidate ref.** The pure consumer path cannot resolve + `CEREMONY_SELF_REF: "0.4.1"` before the candidate creates that tag, and + `release.yml`'s self-checkout is hardcoded to `heavy-duty/ceremony`. No + `0.4.1` branch was created there. The caller used + `cluade-reviewer-andresmgsl/ceremony@drill-0.4.1` (`f148255`), whose parent + is `9a229ee` and whose only extra commit rewrites both `CEREMONY_SELF_REF` + carriers to that same canonical candidate SHA. +* **Log access.** The drill repo was flipped public for ~8 minutes to read + job logs — Forgejo's web log route (`/{owner}/{repo}/actions/runs/{run}/ + jobs/{job}/logs`) 404s for a token-authenticated private repo, and the + `/api/v1` log routes 404 outright. Restored to private, then archived. + Recorded because it is a real deviation from "scratch **private** repo". + +## Probes + +| # | probe | run | result | +|---|---|---|---| +| 1 | merge-door ceremony | 12 (and 7, at `0.4.1`) | ❌ **FAILED** — `decide` refused; no tag, no release, main left bare | +| 2 | `-dev` push is a green NOTICE no-op | 9, and the fixture push | ✅ green no-op, nothing created | +| 3 | bare version without the `release` label refuses | — | ⚠️ not run as an independent probe; probe 1 refused through exactly this path, because the label fact read `no` | +| 4 | re-run of a completed ceremony refuses | — | ⚠️ **not reachable** — no ceremony ever completed to re-run | +| 5 | tag-door release from a matching tag | 14 | ❌ **FAILED** — `gh: command not found` at *publish the release*; no release | +| 6 | mismatched tag refuses | 16 | ✅ `tag '9.9.9' does not match the tree's version '0.4.2' — creating nothing` | + +Release count in the scratch repo at the end: **0**. Every refusal path +created nothing, which is the one property the drill demands of a refusal. + +## Finding 1 — the merge door cannot read its own label (`lib/facts.sh`) + +Probe 1, reproduced twice (`0.4.1`, then `0.4.2` after re-arming): + +``` +facts: ver=0.4.2 base_ver=0.4.2-dev released= labeled=no +decide: the version transitioned ('0.4.2-dev' -> '0.4.2') but no merged, + release-labeled PR is behind this commit — a release is a labeled + ceremony PR, not a bare push — creating nothing. +``` + +The PR *was* labeled `release` and *was* merged. `lib/facts.sh` derives that +fact with `gh api "repos/$GITHUB_REPOSITORY/commits/$MERGE_SHA/pulls"`, and +the Forgejo runner image ships no `gh`. Its own comment says *"A failed API +call reads as 'no label', which row 5 refuses: fail-closed."* — so the guard +behaved exactly as designed on a fact that was wrong. + +`#188` ported the reconcilers and `labels-scope` onto `lib/forge.sh`; +`lib/facts.sh` was outside that scope and is still gh-only. + +## Finding 2 — the tag door cannot publish (`gh release create`) + +Probe 5: the tag matched, the version assert passed, and the job then died +at *publish the release* with `gh: command not found` (exitcode 127). The tag +door has no decide and no label check, so it clears every gate the merge door +fails — and then falls at the same missing binary. + +## Consequence + +Merging !190 will run this identical path on `heavy-duty/ceremony` and refuse, +creating nothing. **There is no door through which 0.4.1 can publish on this +instance today.** The fix is the same shape as #188's, one layer out: +`lib/facts.sh` and `release.yml`'s `gh release view` / `gh release create` +call sites onto `forge_*`. That is new work, not a re-review of !190. -- 2.45.2