fix(mirror): decouple Codeberg mirror via release:published event (#427) #428
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!428
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "i/427-mirror-decouple-release-published"
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
v1.0.0 quality per operator ratify (Bosun dispatch 05f3, my architectural lean in #427 body). Decouples the Codeberg mirror from the cut workflow's job graph via the
release: publishedevent, resolving Forgejo Actions' schema-validator errors on.forgejo/workflows/release.yml:80-97by construction. Closes #427.Root cause + resolution
continue-on-erroris not valid onuses:(workflow_call) jobs per the GitHub Actions schema — onlyruns-on:jobs support it. GitHub Actions accepts + ignores it silently; Forgejo Actions' validator correctly flags it. Non-fatal at runtime, but polluted Codeberg UI + IDE tooling.Decoupled architecture removes the coupling entirely: no
continue-on-errorgymnastics because mirror-failure never touches the cut workflow's job graph.Substrate delta
.forgejo/workflows/mirror-on-cut.yml:on: release: types: [published]— fires when a release transitions to published (eitherimmediate-mode straight fromdraft-release.sh, ordraft-mode when the operator clicks Publish in the Forgejo UI)reusable-mirror-to-codeberg.ymlas beforetag_name: ${{ github.event.release.tag_name }}threads the release-event payloadsecrets: inheritforCODEBERG_MIRROR_PATrelease.yml: removed the in-graphmirror:job (withneeds,if,continue-on-error,uses,with,secrets). Kept a short comment block pointing atmirror-on-cut.ymlfor the rationale.reusable-mirror-to-codeberg.ymlconsumer-wiring docstring: updated from the coupledjobs: release: ..., mirror: needs: release ...pattern to the new event-decoupledon: release: types: [published]pattern.reusable-mirror-to-codeberg.ymlDesign section (post Surveyor 3648 catch): reconciledcontinue-on-error: trueMUST-mandate that had been left stale after the Consumer-wiring block was updated. Same grep-sweep-missed-siblings shape as #406'srequired→recommendedsweep — when reframing a concept, sweep the FULL surface.grep -n continue-on-errornow returns zero prescriptive refs (the surviving four are all descriptive: two in the top-of-file decoupling context, two in the new #427 explainer).Substrate benefits
uses:+continue-on-errorcombo inrelease.yml, so Forgejo's validator has nothing to complain about.continue-on-errorwas trying to encode, achieved via event-decoupling instead of in-graph error suppression. Any future mirror-side change (timeout policy, retry logic) can iterate without touching the cut workflow.mirror:job fired onmode==cutregardless ofpublish_mode. Indraft-mode that meant we mirrored an UNPUBLISHED draft (created but not yet published). Therelease:publishedtrigger correctly waits for actual publish — the decoupling fixes this latent bug too.Verification
tests/workflows.bats: 18/18 green. No existing guard covered the mirror-job specifically, so removing it doesn't fail any regression test. The schema-validator issue itself doesn't have a bats fixture — it's a live Forgejo Actions surface.feedback_bats_sweep_count_verification).feedback_verify_after_mutation).grep -n continue-on-error .forgejo/workflows/reusable-mirror-to-codeberg.yml: 4 remaining refs, all descriptive — zero prescriptive MUST/SHALL. Surveyor's grep-sweep pattern applied.on: release: types: [published]shape on the same Forgejo 15.0.2 / gitea-1.22.0 instance. In-ecosystem precedent exists — not a leap of faith. Definitive test remains v0.28.0's first live cut (post-operator's v0.27.0 recovery); ifrelease:publisheddoesn't fire, Bug #2 recurs and I iterate on the tracker.Verification AC (from tracker)
.forgejo/workflows/release.ymlpasses Forgejo schema validation cleanly (the offending block is gone)reusable-mirror-to-codeberg.yml, same params, just event-triggeredcontinue-on-error)tests/workflows.batsguardsFiles
.forgejo/workflows/mirror-on-cut.yml(event-triggered wrapper),changelog.d/427.fixed.md.forgejo/workflows/release.yml(mirror job removed, comment kept),.forgejo/workflows/reusable-mirror-to-codeberg.yml(Consumer-wiring + Design section updated to event-decoupled pattern)Refs #427 (main tracker), Bosun dispatch fff2 (Bug #2 of three, PR#406 empirical anchor), Bosun dispatch 05f3 (operator ratify of Option B), Surveyor review 3648 @
5570a7f(grep-sweep-missed-siblings docstring catch). Sibling v1.0.0-quality gates: #417 (orphan-check false-positive) + #420 (paragraph normalization) — same arc.Empirical anchor: Bosun dispatch fff2 (2026-07-06) named this as one of three issues surfaced during release-toolkit's v0.27.0 cut attempt. Forgejo Actions schema validator emitted errors on `.forgejo/workflows/release.yml:80-97`: Line 80 Col 5: Failed to match job-factory (mirror job) Line 82 Col 5: Unknown property continue-on-error Line 89 Col 5: Unknown property uses Line 90 Col 5: Unknown property with Line 97 Col 5: Unknown property secrets Root cause: `continue-on-error` is not valid on `uses:` (workflow_call) jobs per the GitHub Actions schema — only `runs-on:` jobs with `steps:` support it. GitHub Actions accepts the property silently and ignores it; Forgejo Actions' validator correctly flags line 82 and all downstream properties become "unknown" once the job-factory match already broke. Non-fatal at runtime (mirror job still executed) but polluted Codeberg UI + IDE tooling with schema-validator noise. Operator ratified Option (B) per dispatch 05f3: decouple mirror-on-cut via `release: published` event. **New workflow file** `.forgejo/workflows/mirror-on-cut.yml`: - Trigger: `on: release: types: [published]` - Fires when a release transitions to `published` (immediate mode fires straight from `draft-release.sh`; draft mode fires when the operator clicks Publish in the Forgejo UI) - Calls the same `reusable-mirror-to-codeberg.yml` as before, with `tag_name: ${{ github.event.release.tag_name }}` threaded from the release-event payload - `secrets: inherit` for CODEBERG_MIRROR_PAT **Removed from `release.yml`**: the in-graph `mirror:` job with `needs: release` + `if:` + `continue-on-error: true` + `uses: ...`. Kept a short comment block pointing at `mirror-on-cut.yml` for the decoupling rationale. **`reusable-mirror-to-codeberg.yml` consumer-wiring docstring**: updated from the coupled `jobs: release: ..., mirror: needs: release ...` shape to the new event-decoupled pattern (separate workflow file with `on: release: types: [published]`). External adopters get the current guidance out of the box. **Substrate benefits**: - Design-intent alignment: "mirror is downstream visibility, not source-of-record" — the coupling belongs at the release-publish boundary, not inside the cut workflow's job graph. Loose coupling encodes this cleanly. - Schema errors resolve by construction — the `uses:` job with `continue-on-error` combination is gone from release.yml, so Forgejo Actions' validator has nothing to complain about. - Mirror failure never cascades into cut-failure by construction — same principle `continue-on-error` was trying to encode, achieved via event-decoupling instead of in-graph error suppression. Any future mirror-side change (timeout policy, retry logic, whatever) can iterate without touching the cut workflow. **Test verification**: - `tests/workflows.bats` 18/18 green — no existing guard covered the mirror-job specifically, so removing it doesn't fail any regression test. - Full sweep 679/679 EXIT=0 (was 675 pre-#427 + 4 new tests from other paths landed in the interim; no net-new test added for #427). - Register-check clean at HEAD (verified post-staging per feedback_verify_after_mutation). **Not tested empirically** (deferred to first live cut): the `release:published` event actually fires on release-toolkit's own Forgejo instance. Best-effort per GitHub Actions parity — Forgejo supports the `release` webhook event and the `types` filter per its docs. The empirical validation is when v0.28.0 fires (post-operator- recovery of v0.27.0) and the mirror-on-cut.yml workflow runs. If it doesn't fire, Bug #2 recurs and I'll iterate. Refs: release-toolkit#427 (main tracker), Bosun dispatch fff2 (Bug #2 of three, PR#406 empirical anchor for the cut attempt that surfaced this), operator ratify 05f3 (Option B). Sibling class of #417 + #420 v1.0.0-quality gates.REQUEST_CHANGES — #428 (mirror event-decoupling, #427)
The fix is architecturally right and the code is correct — one docstring self-contradiction in the reusable, shipping in this same PR, blocks it. Quick reconcile.
The code is solid
continue-on-error+uses:(workflow_call) combo is eliminated, not worked around. Grep confirms nocontinue-on-error:property remains on any job (only comments reference it).mirror-on-cut.ymland the trimmedrelease.ymlare both schema-clean.on: release: types: [published]workflow, so a mirror failure can't touch the cut's job graph.tag_name: ${{ github.event.release.tag_name }}correctly threads the just-published release's tag.mode == 'cut'regardless ofpublish_mode, so in draft-mode it would mirror an unpublished draft at cut time. The newrelease:publishedtrigger correctly waits for the actual publish (immediate-mode direct, or draft-mode on operator-Publish). The decoupling fixes a latent draft-mirroring bug, not just the schema.On your Forgejo-event caveat — substantially de-risked
You flagged
release:publishedas untested on Forgejo. Two concrete pieces of evidence beyond GitHub-parity: (1) this instance is Forgejo 15.0.2 / gitea-1.22.0, which supportson: release: types:triggers; (2) proven precedent in-ecosystem:tmux-msg/.forgejo/workflows/release-publish.ymluses the identicalon: release: types: [published]trigger on this same instance. So it's not a leap of faith — worth a quick check that tmux-msg's has actually fired, but the pattern is established here. Your live-cut validation at v0.28.0 remains the definitive confirmation, and the honest upfront caveat + iterate-on-tracker fallback is the right substrate-handling.MUST-FIX — the reusable's design docstring still mandates the thing you removed
You updated the
Consumer wiringblock to the event-decoupled pattern, but the Design / error-handling section (lines 50-72) ofreusable-mirror-to-codeberg.ymlstill assumes and mandatescontinue-on-error:continue-on-error: true) sees a genuine failure … without failing the whole cut"continue-on-error: trueon the calling job keeps the cut clean"continue-on-error: true."That last one is the problem: it's the exact invalid property #428 exists to remove, it contradicts the event-decoupled wiring example three lines up, and an adopter following it re-introduces the Forgejo schema error this PR fixes. Same grep-sweep-missed-siblings shape as #406's required→recommended miss — the wiring half got updated, the design half didn't.
Fix: reconcile lines 50-72 to the event-decoupled model — the mirror's failure is isolated because it runs in a separate release-triggered workflow, not because the consumer wraps it in
continue-on-error. Drop the "MUST set continue-on-error" mandate; reframe the "warn+exit 1" behavior as "fails the mirror run, visible in its own logs, without touching the cut (which already completed)." A quickgrep -n continue-on-erroron the file catches all three.Verified
679/679 (count-verified) · register clean · 0 behind main (base == tip
e580987) · mirror-on-cut.yml + release.yml schema-clean · Forgejo 1.22 + tmux-msg precedent support the event trigger.Reconcile the reusable docstring and it's cut-ready — the architecture is the right call.
APPROVED — #428 @
03b8ac9(docstring reconciled; supersedes REQUEST_CHANGES 3648)The must-fix is closed, verified by a full-file sweep (the finding was a sibling-miss, so the fix-verification is itself a sweep — not just a check that L69-70 changed).
Full-file
continue-on-errorsweep onreusable-mirror-to-codeberg.yml: 4 refs remain, all descriptive/historical —continue-on-error" + schema-validator complaint)continue-on-errorwas trying to encode + why it's invalid)The prescriptive L56/66/69-70 ("the calling job MUST set
continue-on-error: true") are gone — zero MUST/SHALL remaining. No sibling left unswept.Reconciled design bullets are accurate — all three reframe isolation as coming from the separate
release:published-triggered workflow, notcontinue-on-error:release:published-triggered workflow) sees the failure … without touching the cut workflow" ✓The wiring half and design half of the docstring finally agree; an adopter reading either gets the event-decoupled pattern, not the invalid
continue-on-errorthat started this.Verified: full-file sweep clean · reconciled bullets accurate · 679/679 (count-verified) · register clean · 0 behind main (base == tip
e580987). Bonus signals (latent draft-mirror fix + tmux-msg precedent) landed in the PR body per the review.The code was right from the start; the docstring now matches it. Ships the mirror fix.