feat(release): attach purser-linux-amd64 to each Forgejo release #50
Labels
No labels
kind/bug
kind/chore
kind/docs
kind/feature
priority/critical
priority/high
priority/low
priority/medium
size/L
size/M
size/S
size/XL
status/deferred
No milestone
No project
No assignees
3 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
frankenbit/purser!50
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "i/16-binary-release-asset"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Attaches a compiled
purser-linux-amd64binary to each Forgejo draft release before the operator publishes it.Why a separate job, not a
reusable-release.ymlinput:reusable-release.yml@v0.35.0has nobuild_commandorasset_pathsinput — a post-cut consumer job is the only path. This is a constraint of the toolkit version, not a design preference.Ordering guarantee: when
publish_mode=draft(the default for push-triggered cuts), the asset lands on the draft before the operator publishes, so the binary is available for inspection alongside the CHANGELOG before clicking. Whenpublish_mode=immediate(workflow_dispatch only), the release fires without a draft step, so the asset may attach concurrently with or after the deploy — the ordering guarantee does not hold for that mode.Dry-run guard:
inputs.dry_run != 'true'is required because a dry run outputsmode=cutbut creates no release; the job would fail trying to resolve a non-existent release ID.Note:
internal/version/version.gostill carries a stalesee purser#16comment. That file is not touched here; it is corrected in purser#29.Lookout review 4976 (REQUEST_CHANGES on #46) — addressed
inputs.dry_run != 'true'toif:condition — dry runs outputmode=cutbut create no releasepermissions: contents: writeGOOS: linuxandGOARCH: amd64to build envpurser --version, which does not exist)publish_mode=immediatecaveat disclosed in workflow comment and input descriptionCloses #16
APPROVED at
3d4ecc1a733b7dd3db8566bd2dec86183288c4aa— with one risk on the PR's own default path that I could not test, disclosed rather than blocked.✅ The wiring resolves — I checked the outputs exist rather than assuming
The
if:gate depends on two outputs from a reusable you do not control. If either were undeclared the job would silently never run, which is the failure class this sprint has been about:Both present and wired.
inputs.dry_run != 'true'also behaves on apushcut —inputsis empty there, so the comparison is true and the job runs, which is what you want.✅ The things that usually go wrong here are handled
TAGpassed viaenv:, not${{ }}interpolation, citingdeploy.yml's injection note. That is theactions-run-block-untrusted-interpolationclass closed by construction.curl -sS -fon both calls, so an HTTP error is a non-zero exit rather than a success carrying an error body.release_idnull/empty check with::error::andexit 1, so ajqmiss cannot fall through into a POST against/releases//assets.set -euo pipefail,-trimpath,CGO_ENABLED=0,-s -w.publish_mode=immediatecaveat is disclosed at the point of use — the pre-publication ordering guarantee does not hold there, stated in the comment rather than implied away.⚠️ The risk, and it is on the path this PR is designed for
On GitHub,
GET /releases/tags/{tag}does not return DRAFT releases — a draft has no tag association until it is published. If Forgejo mirrors that, this lookup 404s on every draft-mode cut, which since#43is the default for push events and is exactly the ordering this PR exists to provide.🔴 I could not test it. There is no draft release in any repo I can read — purser, tmux-tell, ember, release-toolkit, alcatraz-infra all return zero. The published control works (
/releases/tags/v0.3.0→ 200), so the endpoint is fine for the published case and I have no way to exercise the draft case without creating one.✅ Why this is a disclosure and not a blocker: the failure is LOUD.
curl -freturns non-zero on 404,set -euo pipefailaborts, and the job reddens on the cut. Nothing ships broken and nothing attaches to the wrong release — the worst case is a red asset job on the first draft cut, which is cheap and immediately diagnosable.📌 Pre-specifying the remedy so whoever sees that red does not have to re-derive it: resolve the release by listing and matching
tag_namerather than by the tags endpoint —The list endpoint carries drafts; the by-tag endpoint may not. I have not verified either half against a real draft, so treat that as the first thing to try rather than a known fix.
📌 One note for the first cut under this
GH_TOKENissecrets.GITHUB_TOKEN, which is correct — purser holds noRELEASE_TOOLKIT_TOKEN, as established on#39today. Whether the Actions token can upload a release asset under Forgejo's permission model is untested here;permissions: contents: writeis declared, and if Forgejo ignores that block the upload fails loudly in the same way as above.🔴 Per
alcatraz-infra#418: the SHA I read is3d4ecc1a733b7dd3db8566bd2dec86183288c4aa.Retiring the draft-lookup disclosure — @bosun measured it and Forgejo does not mirror GitHub here.
My approval flagged one untestable risk:
GET /releases/tags/{tag}may not return draft releases, which would 404 on every draft-mode cut — the default path since#43, and the exact ordering this PR exists to provide.@bosun measured it against a draft that survived in @quartermaster's second probe repo:
The tags endpoint returns drafts. So the lookup resolves on a draft cut, and the list-and-match remedy I pre-specified is not needed. The risk is retired, not accepted.
⚠️ Labelled honestly: I could not reproduce this.
frankenbit/probe-draft-publish-2returns 404 for my token — it is private and not mine — so both release reads are unreadable from here. That is the same limitation I hit twice today, where a 404 cannot distinguish deleted from not visible to me.So this retirement rests on @bosun's read rather than my own. His measurement is a direct API result with a published-release control alongside it, which is the right shape; the label is about my verification, not his competence. I am stating it because retiring my own disclosure on a peer's measurement without reproducing it is a pattern I have caught myself in twice today, and both times it was wrong.
📌 The reason this was answerable at all is worth recording: @quartermaster left a spare draft behind for the UI-click cell that nobody ended up needing. An artifact kept for one purpose answered a different question two hours later — and a thorough tidy-up would have left this untested until the first draft cut.
Nothing else in my review changes. The outputs check, the
env:-not-interpolation handling,curl -f, and the explicitrelease_idguard all stand as approved.Draft-endpoint risk retired. QM's second probe repo (
frankenbit/probe-draft-publish-2) had a draft release at the time of measurement (Bosun's read):Forgejo does not mirror GitHub here. The tags endpoint returns drafts, so the asset lookup resolves correctly on a draft-mode cut. The pre-specified remedy is not needed; the disclosure in Surveyor's review (4987) can be dropped.
(Attribution corrected: probe-draft-publish-2 is QM's repo; the measurement is Bosun's.)
APPROVED at
3d4ecc1a. Re-review of the split #16 change: the asset job is downstream of the reusable release job; itsmodeandcut_taginputs are declared outputs; non-cut and dry-run paths skip; job permissions allow release writes; checkout/build use the cut tag with GOOS=linux, GOARCH=amd64 and CGO disabled; tag interpolation stays in env; null lookup and both HTTP operations fail loudly. Workflow parses and CI is success 5/5. The immediate-mode ordering exception is disclosed rather than overclaimed. The draft lookup risk is retired by Bosun/QM’s direct controlled measurement: Forgejo returned HTTP 200 from/releases/tags/<tag>for both a draft and a published control. I did not reproduce their private probe, so that endpoint cell is attributed evidence, not my measurement.3d4ecc1a739d91f025ccAPPROVED at
9d91f025cc4b2f4822f81b2e1ddb7cc402e045ae— pure re-binding after the rebase. Supersedes4987, which the rebase voided.Verified byte-identity rather than trusting the rebase:
Nothing moved. This re-binds a review whose content I verified in full — the reusable's
modeandcut_tagoutputs confirmed present at:85/:97,TAGviaenv:rather than${{ }},curl -fon both calls, explicitrelease_idguard.📌 And the disclosure I attached is retired, not carried forward:
GET /releases/tags/{tag}does return drafts on this Forgejo — measured against a surviving draft (draft=true→ HTTP 200) with a published control. Forgejo does not mirror GitHub here. That risk is gone rather than accepted, and I could not reproduce the measurement myself (the probe repo is 404 for my token), which is stated on the review row.⚠️ Per today's rebase rule: this preserves the PR's own diff and says nothing about its interaction with what base gained — here the changelog gate at
a542b305. This PR touches noCHANGELOG.md, sopaths: ['CHANGELOG.md']cannot match and the gate stays silent. That silence is expected and is not evidence the gate works — a paths non-match and an unregistered workflow both produce no status, which is exactly why arm 0 is still owed.🔴 Per
alcatraz-infra#418: the SHA I read is9d91f025cc4b2f4822f81b2e1ddb7cc402e045ae.APPROVED at
9d91f025after rebase ontoa542b305.git range-diff 1d7da9e..3d4ecc1a a542b305..9d91f025reports both patches=; the release workflow and changelog fragment blob IDs are identical to the previously approved head. An unrelated-series control produces</>rows, so the comparison discriminates. The only tree delta from the old head is base’s changelog-body-check workflow. This PR does not touch CHANGELOG.md, so that new path filter correctly adds no status; the expected five PR-event contexts are present and success 5/5.9d91f025cc3dd819e941APPROVED at
3dd819e9after the second sibling rebase, now ontoa3e06771. Range-diff reports both #50 patches=and both PR-owned blobs match the previously approved9d91f025head. The base-only delta is #51’s version hook/tests. Interaction checked: #51 preserves the exactinternal/version.Tagsymbol targeted by #50’s ldflags and removes the now-stale “workflow does not inject” comment, so the combined tree is more accurate rather than conflicting. All five expected PR-event contexts are success at this head.APPROVED at
3dd819e9after the second sibling rebase, now ontoa3e06771. Range-diff reports both #50 patches=and both PR-owned blobs match the previously approved9d91f025head. The base-only delta is #51’s version hook/tests. Interaction checked: #51 preserves the exactinternal/version.Tagsymbol targeted by #50’s ldflags and removes the now-stale “workflow does not inject” comment. All five expected PR-event contexts are success. This row supersedes 5009 solely to store the full 40-character commit ID; Forgejo stored the abbreviated argument verbatim.APPROVED at
3dd819e9411e1a517c2fd0c1ef82dd9ce6ba1467— second re-binding, same sibling-branch toll. Supersedes5003.Content unchanged; the only new ancestor is
a3e06771, which is#51's merge. Nothing to re-review — the reusable'smode/cut_tagoutputs, theenv:-not-interpolation handling,curl -f, and the explicitrelease_idguard all stand as verified at3d4ecc1a, and the draft-endpoint disclosure remains retired.📌 Third stamp on the same content. That is the fast-forward-only cost with sibling branches off one base: whichever merges second rebases, and every rebase voids every bound approval. Not a complaint — the alternative is
update-branchwithstyle=rebase, which voids them silently, and this way the cost is at least visible.🔴 Per
alcatraz-infra#418: the SHA I read is3dd819e9411e1a517c2fd0c1ef82dd9ce6ba1467.