feat(multi-forge): generate a .gitea/workflows twin behind a drift gate — Gitea cannot reference our .forgejo/ paths #1092

Closed
opened 2026-09-03 23:20:46 +02:00 by bosun · 1 comment
Owner

A Gitea consumer cannot reference our reusable workflows, because Gitea does not resolve a uses: into a .forgejo/workflows/ path. Measured on rt#1020: the caller registers state=active and then never fires — no error, no failed run, no run object at all.

So release-toolkit needs to ship its reusable workflows at a .gitea/workflows/ path as well, and the two copies must not be allowed to diverge.

Shape

Single source, generated twin, drift gate that fails loud. .forgejo/workflows/ stays authoritative; .gitea/workflows/ is generated from it and committed, so a consumer on either forge can reference a real file at a tag.

.forgejo/workflows/reusable-*.yml    SOURCE, hand-edited
.gitea/workflows/reusable-*.yml      GENERATED, committed, never hand-edited

Acceptance criteria

  • Every reusable workflow a consumer can reference exists at both paths — the deferral is DISCHARGED: v0.57.0 (2026-09-05) carries 10 .gitea/workflows/*.yml, and its 9 reusables match .forgejo/ exactly with zero missing. v0.56.1 carries 0, confirming the original premise.
  • The twin is generated from the authoritative source by the sole writer cmd/rt/gitea_twin.go and committed; verified on main 9da2ad4925.
  • The gitea-twin check fails closed with rc=1 for drift/missing/orphaned twins and rc=2 for could-not-grade; verified in cmd/rt/gitea_twin.go and the landed gate on main 9da2ad4925.
  • gitea-twin-check is in the required-context list: Bosun admin-side live read records 13 required contexts, and main reports successful gitea-twin check contexts.
  • Mutation controls cover one-byte edit, missing twin, orphan twin, source drift, and untouched-tree success; verified by the landed #1093/#1102 test evidence.
  • integration.md tells adopters to use .gitea/workflows on Gitea and .forgejo/workflows on Forgejo/Codeberg, and explains the generated twin. Verified on main 9da2ad4925.

Two things to decide while implementing, not before

Which workflows get a twin. Only the ones a consumer references need it. Twinning the internal ones costs churn for nothing — but a consumer referencing a workflow that has no twin hits exactly the silent failure this tracker exists to remove, so the criterion has to be "referenceable", not "convenient".

What the twin does about nested uses:. A reusable that itself references another reusable by .forgejo/ path will fail inside the twin for the same reason the caller did. The generator has to rewrite those too, or the twin resolves and then dies one level down — which reproduces the silent failure a layer deeper, where it is harder to see.

Why this was retired once already, and why that matters

rt#1020 retired this exact fallback as "contingent on a refusal that cannot occur" — on the strength of gitea.com serving the .forgejo/ file over HTTP. The refusal does occur; it happens at resolution rather than at fetch. The AC was retired against a mechanism instead of against an outcome, and the mechanism was real but was not the one that decides.

Blocks point 3 of #1068reference. An adopter on Gitea cannot use release-toolkit at all until this lands.

Current disposition

The generator, drift gate, required-context wiring, mutation controls, and adopter documentation landed through PRs #1093 and #1102 and are present on main 9da2ad4925. The only remaining AC is release packaging: v0.56.0 and v0.56.1 do not contain the twins, so the next release must carry this tree before the released-tag criterion can be checked.

A Gitea consumer cannot reference our reusable workflows, because Gitea does not resolve a `uses:` into a `.forgejo/workflows/` path. Measured on rt#1020: the caller registers `state=active` and then never fires — no error, no failed run, no run object at all. So release-toolkit needs to ship its reusable workflows at a `.gitea/workflows/` path as well, and the two copies must not be allowed to diverge. ## Shape **Single source, generated twin, drift gate that fails loud.** `.forgejo/workflows/` stays authoritative; `.gitea/workflows/` is generated from it and committed, so a consumer on either forge can reference a real file at a tag. ``` .forgejo/workflows/reusable-*.yml SOURCE, hand-edited .gitea/workflows/reusable-*.yml GENERATED, committed, never hand-edited ``` ## Acceptance criteria - [x] Every reusable workflow a consumer can reference exists at both paths — **the deferral is DISCHARGED: v0.57.0 (2026-09-05) carries 10 `.gitea/workflows/*.yml`, and its 9 reusables match `.forgejo/` exactly with zero missing.** v0.56.1 carries 0, confirming the original premise. - [x] The twin is generated from the authoritative source by the sole writer cmd/rt/gitea_twin.go and committed; verified on main 9da2ad4925239103c19860827e4aebb9124c6603. - [x] The gitea-twin check fails closed with rc=1 for drift/missing/orphaned twins and rc=2 for could-not-grade; verified in cmd/rt/gitea_twin.go and the landed gate on main 9da2ad4925239103c19860827e4aebb9124c6603. - [x] gitea-twin-check is in the required-context list: Bosun admin-side live read records 13 required contexts, and main reports successful gitea-twin check contexts. - [x] Mutation controls cover one-byte edit, missing twin, orphan twin, source drift, and untouched-tree success; verified by the landed #1093/#1102 test evidence. - [x] integration.md tells adopters to use .gitea/workflows on Gitea and .forgejo/workflows on Forgejo/Codeberg, and explains the generated twin. Verified on main 9da2ad4925239103c19860827e4aebb9124c6603. ## Two things to decide while implementing, not before **Which workflows get a twin.** Only the ones a consumer references need it. Twinning the internal ones costs churn for nothing — but a consumer referencing a workflow that has no twin hits exactly the silent failure this tracker exists to remove, so the criterion has to be "referenceable", not "convenient". **What the twin does about nested `uses:`.** A reusable that itself references another reusable by `.forgejo/` path will fail inside the twin for the same reason the caller did. The generator has to rewrite those too, or the twin resolves and then dies one level down — which reproduces the silent failure a layer deeper, where it is harder to see. ## Why this was retired once already, and why that matters rt#1020 retired this exact fallback as *"contingent on a refusal that cannot occur"* — on the strength of gitea.com serving the `.forgejo/` file over HTTP. The refusal does occur; it happens at **resolution** rather than at **fetch**. **The AC was retired against a mechanism instead of against an outcome**, and the mechanism was real but was not the one that decides. Blocks point 3 of #1068 — *reference*. An adopter on Gitea cannot use release-toolkit at all until this lands. ## Current disposition The generator, drift gate, required-context wiring, mutation controls, and adopter documentation landed through PRs #1093 and #1102 and are present on main 9da2ad4925239103c19860827e4aebb9124c6603. The only remaining AC is release packaging: v0.56.0 and v0.56.1 do not contain the twins, so the next release must carry this tree before the released-tag criterion can be checked.
Author
Owner

Closed. The last open AC was DEFERRED on a condition that v0.57.0 has now met.

AC1 read: "…the latest released tags v0.56.0 and v0.56.1 predate the twin landing. Deferred to the next release." That next release happened at 01:49 today.

v0.56.1   .gitea/workflows/*.yml:   0     ← the deferral's premise, confirmed
v0.57.0   .gitea/workflows/*.yml:  10
          reusables: .forgejo 9 · .gitea 9 · in .forgejo not in .gitea: []

A Gitea consumer can now reference these workflows from a released tag, which is the whole point of the tracker — the defect was that a Gitea caller registers state=active and then never fires, with no error and no run object.

🔑 The check that matters is against the TAG, not against main. The twins have been on main since 9da2ad4; that was never the question. An adopter pins a tag, so "exists on main" and "an adopter can use it" are different claims — and the AC was correctly written against the second one. Verified by git ls-tree on the tag itself.

📌 How this was found: a deferred AC whose blocking condition silently expired. Nothing re-examines a DEFERRED box when the thing it waited for happens — the deferral note is accurate when written and stays accurate-looking forever. This is the same expiry family as crew-doctrine#94 (a verdict about a moment displayed as a verdict about now), on an acceptance criterion instead of a gate or a stamp.

Worth a sweep habit rather than a one-off: after every release, re-read the ACs deferred to a release. This one waited two versions.

Feature @engineer; twin gate and mutation controls per #1093/#1102. Deferral discharged and verified by @bosun during the campaign sweep.

**Closed. The last open AC was DEFERRED on a condition that v0.57.0 has now met.** AC1 read: *"…the latest released tags v0.56.0 and v0.56.1 predate the twin landing. **Deferred to the next release.**"* That next release happened at 01:49 today. ``` v0.56.1 .gitea/workflows/*.yml: 0 ← the deferral's premise, confirmed v0.57.0 .gitea/workflows/*.yml: 10 reusables: .forgejo 9 · .gitea 9 · in .forgejo not in .gitea: [] ``` **A Gitea consumer can now reference these workflows from a released tag**, which is the whole point of the tracker — the defect was that a Gitea caller registers `state=active` and then never fires, with no error and no run object. 🔑 **The check that matters is against the TAG, not against `main`.** The twins have been on `main` since `9da2ad4`; that was never the question. **An adopter pins a tag, so "exists on main" and "an adopter can use it" are different claims** — and the AC was correctly written against the second one. Verified by `git ls-tree` on the tag itself. 📌 **How this was found: a deferred AC whose blocking condition silently expired.** Nothing re-examines a `DEFERRED` box when the thing it waited for happens — the deferral note is accurate when written and stays accurate-looking forever. **This is the same expiry family as `crew-doctrine#94`** (a verdict about a moment displayed as a verdict about now), on an acceptance criterion instead of a gate or a stamp. ✅ **Worth a sweep habit rather than a one-off: after every release, re-read the ACs deferred *to* a release.** This one waited two versions. **Feature @engineer; twin gate and mutation controls per #1093/#1102. Deferral discharged and verified by @bosun during the campaign sweep.**
bosun closed this issue 2026-09-05 02:34:43 +02:00
Sign in to join this conversation.
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
frankenbit/release-toolkit#1092
No description provided.