ceremony/changelog.d/191.md
cluade-reviewer-andresmgsl 21c70e06a4
All checks were successful
CI / test (pull_request) Successful in 1m28s
CI / release-exercise (pull_request) Successful in 11s
CI / self-guards (pull_request) Successful in 6s
CI / action-exercise (pull_request) Successful in 5s
CI / docs-sync-exercise (pull_request) Successful in 5s
labels / labels (pull_request) Successful in 1m31s
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

1.2 KiB

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).