2026-08-04 11:31:06 +00:00
|
|
|
### Fixed
|
|
|
|
|
|
|
|
|
|
- The release doors run on a Forgejo consumer. `lib/facts.sh` and
|
|
|
|
|
`release.yml` gathered and published through `gh`, which the runner image
|
2026-08-04 11:37:00 +00:00
|
|
|
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).
|
2026-08-04 11:31:06 +00:00
|
|
|
|
|
|
|
|
- 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
|
2026-08-04 11:37:00 +00:00
|
|
|
fail-closed; a read that did not complete refuses and emits no fact
|
|
|
|
|
(#191).
|
2026-08-04 11:31:06 +00:00
|
|
|
|
|
|
|
|
### Added
|
|
|
|
|
|
|
|
|
|
- `forge_release_exists`, `forge_commit_pulls`, `forge_tag_create`,
|
|
|
|
|
`forge_release_create` and `forge_pr_create` on both backends, so the
|
2026-08-04 11:37:00 +00:00
|
|
|
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).
|
fix(forge): an empty REPO cannot become a fact, and the backend verbs are tested
Both panel blockers on c63a550.
@kimi found the one that mattered: facts.sh got the REPO fix, release.yml's
own four call sites did not. A workflow `run:` shell carries no `set -u`, so
an unset REPO expands empty and the verb addresses `repos//…` — which 404s,
and the 404 is then read as an ANSWER. Reproduced read-only against this
instance before fixing:
forge_release_exists 0.4.1 -> "no", rc 0
forge_commit_pulls 7fc9afe4 -> "[]", rc 0 (the !189 merge, which HAS a
merged PR behind it)
The first would have let the nothing-exists assert proceed to CREATE; the
second is the drill's original fabricated `labeled=no`, one step after the
fix meant to kill it.
Fixed once rather than at four call sites, as kimi suggested: forge_select
defaults REPO from GITHUB_REPOSITORY, and forgejo_api_base — which every
verb reaches the network through — refuses an empty REPO outright. No fifth
call site can forget it.
@grok and @kimi both blocked on the same AC gap: the backend suite did not
cover the five new verbs, so the two measured asymmetries had no offline
coverage. test/forge-backends.test.sh now has 15 cases for them — singular
/pull wrapped to an array, 404 as an empty array, 500 refusing, release
present/absent/unreadable, POST /tags vs /git/refs, the publish body, and
the REPO-empty must-fail. Mutation-checked: reading the plural path fails
one case, dropping the REPO guard fails the two must-fails.
1029 assertions, 22 suites, shellcheck-all and actionlint clean.
Refs #191
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 11:53:28 +00:00
|
|
|
|
|
|
|
|
- `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).
|
fix(forge): percent-encode asset names, and stop the docs naming a client
Both findings are @codex's on !193 (#1583), and both are real.
The asset name travels as a QUERY VALUE, and the artifact-hook contract
permits any file the consumer drops in RELEASE_ASSETS_DIR. Raw
interpolation meant `release asset.tgz` made curl reject the URL outright
(exit 3), and '&', '#', '+', '%' silently changed the name or the query's
shape. `gh release create` handled all of those, so a 1:1 port had to.
Encoded through one boundary — jq's @uri, since jq is already a hard
dependency of this backend and a hand-rolled sed class is how the next
unescaped character gets through. Six backend cases cover it: the encoder
on a space and on the delimiters, uploads under both names, the created
release id in the path, and the multipart attachment. Mutation-checked:
dropping the encoder fails exactly the two name assertions.
docs/CONSUMERS.md's artifact-hook recovery still told operators to "run
`gh release create` by hand" and described the hook as running "before
`gh release create`" — on a Forgejo runner that is precisely the failure
this PR fixes. It now names the forge-neutral tag-door recovery first and
shows both clients for the manual path, without regressing the GitHub
guidance.
1035 assertions, 22 suites, shellcheck-all and actionlint clean.
Refs #191
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 12:11:06 +00:00
|
|
|
|
|
|
|
|
- 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).
|