forked from heavy-duty/ceremony
Merge pull request 'release: 0.4.1' (#190) from release/0.4.1 into main
Reviewed-on: heavy-duty/ceremony#190 Reviewed-by: kimi-reviewer-andresmgsl <andres+4@heavyduty.builders> Reviewed-by: grok-reviewer-andresmgsl <andres+3@heavyduty.builders>
This commit is contained in:
commit
0003fa1d1c
7 changed files with 118 additions and 104 deletions
2
.github/workflows/labels.yml
vendored
2
.github/workflows/labels.yml
vendored
|
|
@ -28,7 +28,7 @@ on:
|
||||||
env:
|
env:
|
||||||
# A called workflow arrives without its repository. Keep this literal pin
|
# A called workflow arrives without its repository. Keep this literal pin
|
||||||
# aligned with the ceremony release consumed by callers (issue #9 D3).
|
# aligned with the ceremony release consumed by callers (issue #9 D3).
|
||||||
CEREMONY_SELF_REF: "0.4.0"
|
CEREMONY_SELF_REF: "0.4.1"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
scope:
|
scope:
|
||||||
|
|
|
||||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
|
|
@ -129,7 +129,7 @@ env:
|
||||||
# `ref:` accepts ${{ env }}; `uses:` strings do not — which is why the
|
# `ref:` accepts ${{ env }}; `uses:` strings do not — which is why the
|
||||||
# shared logic arrives as script files via checkout, not as inner `uses:`
|
# shared logic arrives as script files via checkout, not as inner `uses:`
|
||||||
# references.
|
# references.
|
||||||
CEREMONY_SELF_REF: "0.4.0"
|
CEREMONY_SELF_REF: "0.4.1"
|
||||||
VERSION_SOURCE: ${{ inputs.version-source }}
|
VERSION_SOURCE: ${{ inputs.version-source }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
|
||||||
90
CHANGELOG.md
90
CHANGELOG.md
|
|
@ -9,6 +9,96 @@ Entries arrive as fragments — one `changelog.d/<issue>.md` per PR, never
|
||||||
an edit to this file — and the release PR assembles them into the next
|
an edit to this file — and the release PR assembles them into the next
|
||||||
section here (`bin/changelog-assemble`, #112).
|
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
|
## 0.4.0 — 2026-07-29
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
|
||||||
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
||||||
0.4.1-dev
|
0.4.1
|
||||||
|
|
|
||||||
|
|
@ -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).
|
|
||||||
|
|
@ -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).
|
|
||||||
|
|
@ -82,13 +82,32 @@ door has.
|
||||||
|
|
||||||
## Run 1 — against `9a229ee`, before the fix (FAILED)
|
## 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 |
|
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
|
||||||
| 1 | merge-door ceremony | ❌ `decide` refused: `labeled=no` for a PR that *was* labeled and *was* merged |
|
delete pending. Candidate ref
|
||||||
| 5 | tag door | ❌ `gh: command not found` at *publish the release* |
|
`cluade-reviewer-andresmgsl/ceremony@drill-0.4.1` (`f148255`), parent
|
||||||
| 6 | mismatched tag | ✅ refused, creating nothing |
|
`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
|
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
|
the runner image does not ship. `facts.sh` read *any* failure as a definite
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue