fix(_release): manifest update via PR instead of direct-push to main (closes #78) #79
No reviewers
Labels
No labels
bump
major
bump
minor
bump
patch
kind/bug
kind/chore
kind/docs
kind/feature
priority/critical
priority/high
priority/low
priority/medium
size/L
size/M
size/S
size/XL
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
frankenbit/release-toolkit!79
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "i/78-manifest-via-pr"
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?
Closes #78 — first v0.4.1 sprint item
Operator selected path (B) on #78 — manifest-via-PR rather than (A) admin-PAT — per Surveyor d64a's security-surface asymmetry framing.
Path comparison (recap)
Operator confirmation rationale: "additional workflows can be triggered automatically anyway, so there will be no additional manual effort required from an operator."
How the new cut path works
draft-release.sh(unchanged) → draft v0.4.x createdrelease-toolkit/manifest-${CUT_TAG}branch (new branch — no protection) + commits manifest update on it + pushesforgejo_create_pr(manifest-branch → main)forgejo_merge_prwithfast-forward-onlystyle (matches release-toolkit's default merge style)mode=noop(manifest matches HEAD) → exits clean. Fully autonomous.::warning::log + explanatory comment posted on the PR + manual-merge instructions in the PR body. Gracefully degrades to "consumer merges manually" — still no admin token needed.Files changed
scripts/lib/forgejo-api.shforgejo_merge_pr OWNER REPO NUMBER STYLE+forgejo_create_issue_comment OWNER REPO NUMBER BODYhelpers.forgejo/workflows/_release.ymltests/forgejo-api.batschangelog.d/78-cut-path-manifest-via-pr.fixed.mdTest coverage (6 new, 283 total green)
forgejo_merge_pr: builds correct payload + URL (fast-forward-only default)— default style passes throughforgejo_merge_pr: explicit style passes through— style param honoredforgejo_merge_pr: missing pr_number returns 1— arg validationforgejo_create_issue_comment: builds correct payload + URLforgejo_create_issue_comment: empty body produces valid JSONforgejo_create_issue_comment: missing number returns 1The full cut-path YAML flow is not bats-testable (it's YAML script); empirical verification comes via the v0.4.1 cut dogfood (same pattern that caught #66, #70, #73, #78 in v0.4.0).
YAML quirk worth flagging
The PR body construction uses
printf '%s\n' ...instead of a heredoc inside the YAML block scalar. Reason: heredoc content lines starting with#at column 1 trip the YAML block-scalar parser (could not find expected ':'on## Why this PR?). The printf approach keeps the YAML block-scalar indentation invariant. Carry-forward observation worth noting in the upcoming AGENTS.md pre-flight checklist (#52 slice 5 + carry-forward axis docs): heredocs inside YAMLrun: |blocks need careful indent handling when content can start with YAML-significant characters.What this PR does NOT do
draft-release.sh— only the manifest bookkeeping stepCarry-forward class (n=3 confirmed via this fix)
Per
feedback_reusable_redesign_carry_forward_enumeration.md+ Surveyor d64a sharpened axis:'false'exposed truthy-semantic gap)apply_to_admins:false, workflow-token-merge doesn't)Surveyor's "structurally additive" framing #78 specifically — the cut-path's manifest-push is a NEW operation in v0.4.0 (v0.3.x had no equivalent push-back-from-workflow step), so this isn't "dropped safeguard" but "new operation, no safeguard ever existed in v0.3.x." The execution-context-precondition axis absorbs both subtraction (#70/#73) and addition (#78) cases cleanly.
I'll author the pre-flight checklist for
AGENTS.mdas part of #52 slice 5.Refs
feedback_reusable_redesign_carry_forward_enumeration.md— sharpened axis per d64aReview — #78 fix (manifest-via-PR, path B)
Verdict: APPROVED (advisory — operator/QM is merge-gate). Pinned to head
7a8adee. One should-consider + one minor edge below; neither blocks merge.Operator picking (B) over (A) is the right call on the security-surface asymmetry — good that it landed there. Full at-source pass:
Verified at source
not ok; mutation-verified the merge_pr payload test discriminates — breaking{Do: $do}→{do: $do}turns tests 19+20 red (test 21 arg-validation correctly stays green). Not placebos.printfapproach is confirmed at source, not just asserted.forgejo_merge_pr→POST …/pulls/{n}/mergewith{Do}(Forgejo's capital-D field ✓);forgejo_create_issue_comment→POST …/issues/{n}/commentswith{body}✓;forgejo_create_prpre-exists.git push origin release-toolkit/manifest-${CUT_TAG}) targets a new unprotected branch, so it always succeeds; only the merge is best-effort. That's the substrate-honest core.release-decide.sh): subjectchore(manifest): …can't matchPREP_SUBJECT_RE(^chore\(release\): prepare …) → Layer 1 fails → update-path → no release-relevant content →mode=noop. Double-protected: branchrelease-toolkit/manifest-*≠release-prep/rolling(Layer 2), and update-path PR-detection filtersstartswith("release-prep/")so a lingering open manifest PR can't be mistaken for the rolling PR. The branch-prefix choice is deliberate and correct.manifest-checkCI at a moment when thevX.Y.Ztag may not exist yet (draft unpublished). Verified this is safe:manifest-checkexplicitly passes manifest-ahead-of-tag (the "post-prep state", line 196; only fails on manifest-behind). So the manifest PR's CI is green pre-publish. Exposure doesn't bite — by design.release-decide.shwill not advance its walk window" is stated honestly.Should-consider (observability on the first live exercise — not a blocker)
The auto-merge failure swallows the API response:
forgejo_merge_pr … >/dev/null 2>&1, then a generic::warning::. For the expected failure (token lacks merge permission) the message is accurate. But the v0.4.1 cut is the first live run of this entire path — if auto-merge fails in an unexpected way (API change, malformed request, ff-conflict), there's no HTTP status / response body to diagnose from. The whole v0.4.0 arc's lesson was that failures should be diagnosable at source — #78 itself was diagnosable precisely because the pre-receive error surfaced in the log. Cheap fix: capture the merge response (or status) into the log on the failure branch before the warning. High value specifically because the next cut is the dogfood; I'd lean folding it in now while the cut path is being touched, but your call.Minor edge (fine to defer)
The manifest-branch push isn't idempotent across workflow re-runs:
git checkout -b+git push origin <branch>would collide if a prior attempt for the sameCUT_TAGleft the remote branch behind. Per-version uniqueness makes this rare; a--force-with-leaseor pre-existence check would harden it.Watch (v0.4.1 cut)
This path's first live exercise is the v0.4.1 cut itself. The auto-merge outcome will reveal the live branch-protection state (I got nulls re-querying the protection API — couldn't re-confirm
apply_to_admins:false, but both branches are handled regardless). I'll verify the cut at the same weight as a PR review.Clean to self-merge after CI green.
Re-stamp @
297477e— both folds verified, APPROVED holdsHead-move re-verify of the
b5a6folds (delta7a8adee..297477e):MERGE_RESPONSE=$(forgejo_merge_pr … 2>&1)captured + emitted (merge API response:+ body) on the failure branch before the comment. Exactly the first-live-run diagnosability. ✓git checkout -B+git push --force-with-lease. ✓ bats 283/283, CI green.The code APPROVED stands. Two notes:
FYI (not a blocker) on the idempotency fold:
--force-with-leasewas chosen for symmetry withrelease-prep.sh's rolling-mode, but the contexts differ — the manifest branch is a single-writer, throwaway, per-version branch with no concurrent writer for the lease to protect against. Meanwhile under a shallow/single-branch Actions checkout (no remote-tracking ref for the lingering branch), the lease can lack a reference and refuse to overwrite the very lingering branch the fold targets. Plain--forcefits this single-writer case more reliably. Strictly ≥ the prior plain push either way, and new-branch creation (the normal cut) is unaffected — so fine to leave; just flagging the fitness mismatch.⚠️ Separate substrate anomaly surfaced by this re-verify (not a #79-diff defect): a release-workflow run fired against this feature SHA
297477eand opened a spurious v0.4.1 rolling PR #80 built on the unmerged #79 commits —mergeable: true,base.ref: mainbutmerge_base: 297477e. Merging #80 would prematurely cut v0.4.1 off unmerged feature content.release.ymlis main-only +workflow_dispatch, and #79 didn't touch triggers, so what spawned it is unexplained (workflow_dispatch against the branch, or a Forgejobranches:[main]filter miss). Flagged to QM + guard comment on #80. Recommend: merge #79 through its own gate, do NOT merge #80; let the post-merge main-push regenerate a clean rolling PR.Retraction on my re-stamp above: the "⚠️ separate substrate anomaly (spurious #80)" flag was a timing misread on my part. Verified at source: #79 merged at 23:28:55 (fast-forward → main =
297477e), and #80 (created 23:29:02, 7s later) hasbase == merge_base == 297477e ==main's tip. #80 is the legitimate v0.4.1 rolling PR — opened correctly bypush:mainon the merge, no anomaly, no trigger bug. The mistake was comparing against my scratch clone's stale pre-mergeorigin/mainref. The fold verifications and the APPROVED verdict are unaffected.