A pushed git tag is filed by Forgejo under /tags; only an explicit release
object appears under /releases. release-plugin.mjs only ever made a tag, so
every plugin's public releases page sat a version behind the ref the catalog
pinned -- llm-security showed v7.8.3 against a v8.0.0 tag.
Measured 2026-09-18 against the instance API: 24 repos in org `open`, 21 with
at least one tag, 11 of those 21 with no release object for their newest tag.
That reproduces the order's own independently-measured list exactly.
- parseForgejoRepo / planForgejoRelease / ensureForgejoRelease: pure, tested.
The release body is the tag's own message VERBATIM or empty -- never
generated prose. Read via %(contents:subject)+%(contents:body), never
%(contents), which drags the SSH signature block into the notes.
- The step fires only on a run that PUBLISHES (--create-tag --write, or
--push): filing a release object is itself a publish and must not ride
along on a local --write past the operator's one-shot push token.
- Synchronous (curl via execFileSync), like check-versions.mjs's
checkHomepage: runRelease is called without an await and its return value
becomes the exit code, so an async step would let a rejected POST surface
after the run had already exited 0 and called the release complete.
- 429 and the 502/503/504 family are retried with backoff, never swallowed.
An unthrottled sweep drew 17 HTTP 429s and the first version of that sweep
read every one as an empty list -- "verified nothing" was indistinguishable
from "verified everything, all clean".
- The token reaches curl through a 0600 header file, never argv.
scripts/backfill-forgejo-releases.mjs covers the backlog and retries the one
step, reusing the same planner and API shell so the two cannot drift. Only
the newest tag is considered. Documented exception: ktg-plugin-marketplace
pre-polyrepo-archive, an archive marker, not a release; the register is keyed
by repo AND tag so that repo's next real release is still backfilled.
Tests written red first: 20 new (12 release path, 8 backfill), and the two
real-git integration tests were probed known-negative -- breaking the wiring
turns 68/0 into 66/2. Suite 193/193; check-versions 12/12 OK.
The backfill of the 10 outstanding release objects is NOT done: it was denied
in-session as a public-surface write and is the operator's call.
Order: 20260917T235642Z-730962924-from-from-ai-to-chitta
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>