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 556b841..ba110cd 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..5d3e8bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,96 @@ 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 + +- `forge_release_exists`, `forge_commit_pulls`, `forge_tag_create`, + `forge_release_create` and `forge_pr_create` on both backends, so the + release path names no client (#191). + +- The forgejo backend serves one PR object at `/commits/{sha}/pull` where + GitHub serves an array at `/pulls`; both verbs emit the array shape, so + the call site carries one expression (#191). + +- Forgejo creates tags at `POST /tags` — it serves `/git/refs` GET-only, + so GitHub's ref-POST would have 404'd there forever (#191). + +- `forgejo_api_base` refuses when `REPO` is empty. Every verb interpolates + it and every call reaches the network through there, so `repos//…` — + whose 404 reads as "no release" and "no PRs" — is now impossible (#191). + +- Release asset names are percent-encoded. The hook contract permits any + filename, and the name travels as a query value: a space made curl reject + the URL and `&`/`#`/`+`/`%` silently renamed the asset (#191). +- `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 + +- `docs/CONSUMERS.md`'s artifact-hook recovery no longer tells operators to + run `gh release create` by hand — on a Forgejo runner there is no `gh`. + It names the forge-neutral tag-door path first, with both clients shown + (#191). +- `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 + +- The release doors run on a Forgejo consumer. `lib/facts.sh` and + `release.yml` gathered and published through `gh`, which the runner image + does not ship, so the merge door read `labeled=no` for a correctly + labeled ceremony PR and the tag door died at the publish (#191). + +- A release fact that could not be read is no longer reported as a definite + `no`. A completed read finding no label is still `no` and still + fail-closed; a read that did not complete refuses and emits no fact + (#191). +- `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). diff --git a/changelog.d/191.md b/changelog.d/191.md deleted file mode 100644 index 4ca1fcd..0000000 --- a/changelog.d/191.md +++ /dev/null @@ -1,39 +0,0 @@ -### Fixed - -- The release doors run on a Forgejo consumer. `lib/facts.sh` and - `release.yml` gathered and published through `gh`, which the runner image - does not ship, so the merge door read `labeled=no` for a correctly - labeled ceremony PR and the tag door died at the publish (#191). - -- A release fact that could not be read is no longer reported as a definite - `no`. A completed read finding no label is still `no` and still - fail-closed; a read that did not complete refuses and emits no fact - (#191). - -### Added - -- `forge_release_exists`, `forge_commit_pulls`, `forge_tag_create`, - `forge_release_create` and `forge_pr_create` on both backends, so the - release path names no client (#191). - -- The forgejo backend serves one PR object at `/commits/{sha}/pull` where - GitHub serves an array at `/pulls`; both verbs emit the array shape, so - the call site carries one expression (#191). - -- Forgejo creates tags at `POST /tags` — it serves `/git/refs` GET-only, - so GitHub's ref-POST would have 404'd there forever (#191). - -- `forgejo_api_base` refuses when `REPO` is empty. Every verb interpolates - it and every call reaches the network through there, so `repos//…` — - whose 404 reads as "no release" and "no PRs" — is now impossible (#191). - -- Release asset names are percent-encoded. The hook contract permits any - filename, and the name travels as a query value: a space made curl reject - the URL and `&`/`#`/`+`/`%` silently renamed the asset (#191). - -### Changed - -- `docs/CONSUMERS.md`'s artifact-hook recovery no longer tells operators to - run `gh release create` by hand — on a Forgejo runner there is no `gh`. - It names the forge-neutral tag-door path first, with both clients shown - (#191). diff --git a/drills/0.4.1.md b/drills/0.4.1.md index 0f79350..579278d 100644 --- a/drills/0.4.1.md +++ b/drills/0.4.1.md @@ -82,13 +82,32 @@ door has. ## Run 1 — against `9a229ee`, before the fix (FAILED) -Recorded in full because the failure is the reason #191 exists. +Recorded because the failure is the reason #191 exists — including its +deviations, which no later success retires. -| # | probe | result | -|---|---|---| -| 1 | merge-door ceremony | ❌ `decide` refused: `labeled=no` for a PR that *was* labeled and *was* merged | -| 5 | tag door | ❌ `gh: command not found` at *publish the release* | -| 6 | mismatched tag | ✅ refused, creating nothing | +Where: disposable private repo `heavy-duty/ceremony-drill-0.4.1` — a +different consumer from run 2's, archived at the end with the operator's +delete pending. Candidate ref +`cluade-reviewer-andresmgsl/ceremony@drill-0.4.1` (`f148255`), parent +`9a229ee`, its only extra commit rewriting both `CEREMONY_SELF_REF` carriers +to that SHA. + +**Deviation, disclosed and not retired by run 2:** run 1's scratch repo was +flipped **public for roughly 8 minutes** to read job logs — Forgejo's web log +route 404s for a token-authenticated private repo and the `/api/v1` log +routes 404 outright — then restored to private and archived. That is a real +departure from `drills/README.md`'s "scratch **private** repo", and it stays +in the record. Run 2 did not repeat it: it read no logs at all, which is why +every run-2 row is a repository-state measurement. + +| # | 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 no-op | 9, and the fixture push | ✅ green no-op, nothing created | +| 3 | bare version, no `release` label | — | ⚠️ not run independently; probe 1 refused through exactly this path, because the label fact read `no` | +| 4 | re-run of a completed ceremony | — | ⚠️ **not reachable** — no ceremony ever completed to re-run | +| 5 | tag door | 14 | ❌ **FAILED** — `gh: command not found` at *publish the release*; no release | +| 6 | mismatched tag | 16 | ✅ `tag '9.9.9' does not match the tree's version '0.4.2' — creating nothing` | Root cause, both doors: `lib/facts.sh` and `release.yml` spoke `gh`, which the runner image does not ship. `facts.sh` read *any* failure as a definite