feat(multi-forge): can a FORGEJO consumer reference gitea.com over an absolute-URL uses: — the Codeberg route #793 closed #1189

Closed
opened 2026-09-05 20:30:57 +02:00 by bosun · 3 comments
Owner

A Forgejo repo may be able to reference our workflows on gitea.com over an absolute-URL uses:, which would make the toolkit adoptable on Codeberg without hosting any of our code there.

That matters because Codeberg is Forgejo, and the Codeberg mirror was retired in #793 because Codeberg does not accept largely-LLM-generated code. If a Forgejo runner resolves a gitea.com URL, adopters on Codeberg are served by the mirror we already have.

Why this is not already answered

#1020 asked the neighbouring question — can a Gitea consumer reference our path — and retired the cross-forge arm as inapplicable: "a consumer on gitea.com references the MIRROR unqualified, same-forge, so no cross-forge resolution occurs." True for that consumer. It is the reason nobody tested the direction asked here.

What is known

gitea/act parses the absolute form and has no host allowlist:

^(https?://.*)/([^/]+)/([^/]+)/\.([^/]+)/workflows/([^@]+)@(.*)$

⚠️ Parseable is not resolvable. docs/integration.md records that this exact parser fact was once read as an answer about the outcome, and resolution failed before that code was reached. Host egress to gitea.com is open (HTTP 200, measured 2026-09-05).

Measurement — a matrix, not one arm

Both directory twins return HTTP 200 on gitea.com, so raw-fetchability discriminates nothing. Only a run does.

consumer forge   referenced path                             expect
Forgejo (ours)   https://gitea.com/…/.gitea/workflows/…@tag    ?
Forgejo (ours)   https://gitea.com/…/.forgejo/workflows/…@tag  ?
control          local ./.forgejo/workflows/…                  RAN
control          a URL that cannot resolve                     NO RUN

🔴 The failure mode is SILENT — the caller registers state=active and never fires: no error, no failed run, no run object. Grade on a run object existing, never on the absence of a red.

AC

  • All four arms run on a live Forgejo runner; the two controls behave as predicted — DONE 2026-09-05, with Forgejo's own egress to gitea.com verified (HTTP 303) to exclude the network confound.
  • The verdict is stated as a run object exists, not as nothing failedDONE, and it is why the result is trustworthy: a silent non-resolution and a passing run are the same absence of red.
  • ~~If either arm resolves: docs/integration.md gains the Codeberg-via-gitea.com route, and #1068's "reference" leg is restated to name the mechanism that actually carries it
  • If neither resolves: recorded here with the arms, so the next person does not re-derive it — DONE; #1068 carries the retirement with the same measurement.
  • #1068 — the v1.0 adoptability roadmap; this is its "(3) reference" leg for a forge we do not host on
  • #1020 — the neighbouring measurement, and the retired arm that left this open
  • #1092 — the generated .gitea/ twin, which exists because .forgejo/ does not resolve on Gitea
  • #793 — the Codeberg retirement this would route around

Anchor

Operator question, 2026-09-05: "could we use the gitea.com release-toolkit repo from Forgejo to fetch the workflows?"

A Forgejo repo may be able to reference our workflows on gitea.com over an absolute-URL `uses:`, which would make the toolkit adoptable on Codeberg without hosting any of our code there. That matters because Codeberg is Forgejo, and the Codeberg mirror was retired in #793 because Codeberg does not accept largely-LLM-generated code. If a Forgejo runner resolves a gitea.com URL, adopters on Codeberg are served by the mirror we already have. ## Why this is not already answered #1020 asked the neighbouring question — can a *Gitea* consumer reference our path — and retired the cross-forge arm as inapplicable: *"a consumer on gitea.com references the MIRROR unqualified, same-forge, so no cross-forge resolution occurs."* True for that consumer. It is the reason nobody tested the direction asked here. ## What is known `gitea/act` parses the absolute form and has no host allowlist: ```go ^(https?://.*)/([^/]+)/([^/]+)/\.([^/]+)/workflows/([^@]+)@(.*)$ ``` ⚠️ **Parseable is not resolvable.** `docs/integration.md` records that this exact parser fact was once read as an answer about the outcome, and resolution failed before that code was reached. Host egress to gitea.com is open (HTTP 200, measured 2026-09-05). ## Measurement — a matrix, not one arm Both directory twins return HTTP 200 on gitea.com, so raw-fetchability discriminates nothing. Only a run does. ``` consumer forge referenced path expect Forgejo (ours) https://gitea.com/…/.gitea/workflows/…@tag ? Forgejo (ours) https://gitea.com/…/.forgejo/workflows/…@tag ? control local ./.forgejo/workflows/… RAN control a URL that cannot resolve NO RUN ``` 🔴 **The failure mode is SILENT** — the caller registers `state=active` and never fires: no error, no failed run, no run object. Grade on a run object existing, never on the absence of a red. ## AC - [x] All four arms run on a live Forgejo runner; the two controls behave as predicted — **DONE** 2026-09-05, with Forgejo's own egress to gitea.com verified (HTTP 303) to exclude the network confound. - [x] The verdict is stated as *a run object exists*, not as *nothing failed* — **DONE**, and it is why the result is trustworthy: a silent non-resolution and a passing run are the same absence of red. - [x] ~~If either arm resolves: `docs/integration.md` gains the Codeberg-via-gitea.com route, and #1068's "reference" leg is restated to name the mechanism that actually carries it - [x] If neither resolves: recorded here with the arms, so the next person does not re-derive it — **DONE**; `#1068` carries the retirement with the same measurement. ## Related - #1068 — the v1.0 adoptability roadmap; this is its "(3) reference" leg for a forge we do not host on - #1020 — the neighbouring measurement, and the retired arm that left this open - #1092 — the generated `.gitea/` twin, which exists because `.forgejo/` does not resolve on Gitea - #793 — the Codeberg retirement this would route around ## Anchor Operator question, 2026-09-05: *"could we use the gitea.com release-toolkit repo from Forgejo to fetch the workflows?"*
Author
Owner

📌 First run: apparatus INVALID, no URL verdict — and the positive control is the only reason we know that.

@pullings ran four arms on tic-tac-toe probe/rt1189-gitea-urls @ be0ebd67. Four dispatches accepted (placeholder ids 20262-20265), zero runs for the probe SHA, including the same-repo local control. release.yml untouched, no cut dispatched.

🔑 Arm C failing is what makes this could-not-grade rather than a finding. With only the two treatment arms, "no run object" reads as the URL does not resolve — which is #1020's exact failure, on the tracker filed to avoid it.

Three faults, measured 2026-09-05 20:48

① Runner label. rt1189-local-reusable.yml asks runs-on: codeberg-small-lazy. On git.frankenbit.de the runner is alcatraz-runner (http://forgejo:3000), labels dotnet, godot, python, go, playwright, squadron-test, docker-build, squadron-deploy. That label belongs to the gitea.com-bound runner, which is why it looked right. (Found by @pullings.)

② The referenced file does not exist at @v0.26.0. The .gitea/ twins are generated and postdate #1092:

            .gitea/reusable-changelog-body-check.yml   .forgejo/ same file
v0.26.0                404                                  200
v0.55.0                404                                  200
v0.57.0                200                                  200
v0.57.4                200                                  200

③ Which collapses the negative control into the treatment arms. At v0.26.0, arm A (.gitea/, expected to resolve) and arm D (does-not-exist, expected not to) are the same condition — file absent. A negative control identical to the treatment is not a control.

⚠️ The inversion this would have produced. At v0.26.0 the only arm whose file exists is B — the .forgejo/ path, the direction expected NOT to work. Had a runner picked these up, B alone could have resolved, and we would have published the opposite conclusion with four clean-looking arms behind it.

Corrected apparatus

The called workflow is runs-on: ${{ inputs.runs_on }}, so the caller supplies the label.

A  .gitea/…@v0.57.4    with runs_on: go
B  .forgejo/…@v0.57.4  with runs_on: go
C  ./.forgejo/workflows/rt1189-local-reusable.yml, runs-on: go   MUST RUN
D  .gitea/workflows/rt1189-does-not-exist.yml @v0.57.4           MUST NOT RUN

Owner case checked: FrankenBit and frankenbit behave identically, so case is not a variable.

No operator decision needed — this was an apparatus fault, not a missing runner.

📌 **First run: apparatus INVALID, no URL verdict — and the positive control is the only reason we know that.** @pullings ran four arms on `tic-tac-toe` `probe/rt1189-gitea-urls` @ `be0ebd67`. Four dispatches accepted (placeholder ids 20262-20265), **zero runs for the probe SHA, including the same-repo local control.** `release.yml` untouched, no cut dispatched. 🔑 **Arm C failing is what makes this could-not-grade rather than a finding.** With only the two treatment arms, "no run object" reads as *the URL does not resolve* — which is `#1020`'s exact failure, on the tracker filed to avoid it. ## Three faults, measured 2026-09-05 20:48 **① Runner label.** `rt1189-local-reusable.yml` asks `runs-on: codeberg-small-lazy`. On `git.frankenbit.de` the runner is `alcatraz-runner` (`http://forgejo:3000`), labels `dotnet, godot, python, go, playwright, squadron-test, docker-build, squadron-deploy`. That label belongs to the **gitea.com-bound** runner, which is why it looked right. *(Found by @pullings.)* **② The referenced file does not exist at `@v0.26.0`.** The `.gitea/` twins are generated and postdate `#1092`: ``` .gitea/reusable-changelog-body-check.yml .forgejo/ same file v0.26.0 404 200 v0.55.0 404 200 v0.57.0 200 200 v0.57.4 200 200 ``` **③ Which collapses the negative control into the treatment arms.** At `v0.26.0`, arm A (`.gitea/`, expected to resolve) and arm D (`does-not-exist`, expected not to) are **the same condition — file absent.** A negative control identical to the treatment is not a control. ⚠️ **The inversion this would have produced.** At `v0.26.0` the only arm whose file exists is **B** — the `.forgejo/` path, the direction expected NOT to work. Had a runner picked these up, B alone could have resolved, and we would have published the opposite conclusion with four clean-looking arms behind it. ## Corrected apparatus The called workflow is `runs-on: ${{ inputs.runs_on }}`, so the caller supplies the label. ``` A .gitea/…@v0.57.4 with runs_on: go B .forgejo/…@v0.57.4 with runs_on: go C ./.forgejo/workflows/rt1189-local-reusable.yml, runs-on: go MUST RUN D .gitea/workflows/rt1189-does-not-exist.yml @v0.57.4 MUST NOT RUN ``` Owner case checked: `FrankenBit` and `frankenbit` behave identically, so case is not a variable. **No operator decision needed** — this was an apparatus fault, not a missing runner.
Author
Owner

🔴 VERDICT: NO. A Forgejo consumer does NOT resolve an absolute gitea.com uses:, for either directory. Measured by @pullings on a valid apparatus, 2026-09-05 20:52.

Repo frankenbit/tic-tac-toe on git.frankenbit.de, probe 20169863, all arms runs_on: go, target tag v0.57.4 (4c4c5c65):

A  https://gitea.com/…/.gitea/workflows/reusable-changelog-body-check.yml@v0.57.4
                                     no canonical run object (placeholder 20277 waiting)
B  https://gitea.com/…/.forgejo/…    no canonical run object (20278 waiting)
C  ./.forgejo/workflows/rt1189-local-reusable.yml
                                     RAN — parent/child 40050/40051, both success
D  .gitea/workflows/rt1189-does-not-exist.yml@v0.57.4
                                     no run object

C ran and D did not, so the controls discriminate and the apparatus is valid. This is the second run; the first was correctly reported could-not-grade when C also stayed silent.

The confound I excluded before publishing this

Resolution happens server-side in Forgejo, not in the runner — so the runner's egress is irrelevant and Forgejo's is decisive. Measured inside the forgejo container:

curl https://gitea.com/     HTTP 303
getent hosts gitea.com      2600:1f14:2b50:701:89b3:c221:9ea7:46b4
[actions] ENABLED = true · DEFAULT_ACTIONS_URL = self

Forgejo can reach gitea.com. A and B did not fail for a network reason.

🔑 And this confirms docs/integration.md's own warning from the other side. gitea/act's parser accepts the absolute form and has no host allowlist — but "resolution fails BEFORE that code is reached." The server never creates a job, so act's parser is never consulted. The caller registers and never fires: no error, no failed run, no run object. Same silent signature as the .forgejo/-on-Gitea case.

Consequence for #1068

The Codeberg-via-gitea.com route is closed. A Codeberg adopter's uses: resolves against codeberg.org, and an absolute URL out to gitea.com does not work — so the toolkit must be on the forge that runs it. #793 retired the Codeberg mirror because Codeberg does not accept largely-LLM-generated code, and that retirement therefore still costs us Codeberg adopters.

What remains for a Codeberg adopter is vendoring — copying the reusable workflow into their own repo rather than referencing ours. It works, and it gives up pin-by-tag and drift-tracking. That should be written down in integration.md as the honest answer rather than left as an inference.

Version: 15.0.7+gitea-1.22.0. This is a fact about this Forgejo version, not a law — re-run the four arms after a major upgrade.

Closing: the question is answered.

🔴 **VERDICT: NO. A Forgejo consumer does NOT resolve an absolute gitea.com `uses:`, for either directory.** Measured by @pullings on a valid apparatus, 2026-09-05 20:52. Repo `frankenbit/tic-tac-toe` on `git.frankenbit.de`, probe `20169863`, all arms `runs_on: go`, target tag `v0.57.4` (`4c4c5c65`): ``` A https://gitea.com/…/.gitea/workflows/reusable-changelog-body-check.yml@v0.57.4 no canonical run object (placeholder 20277 waiting) B https://gitea.com/…/.forgejo/… no canonical run object (20278 waiting) C ./.forgejo/workflows/rt1189-local-reusable.yml RAN — parent/child 40050/40051, both success D .gitea/workflows/rt1189-does-not-exist.yml@v0.57.4 no run object ``` **C ran and D did not, so the controls discriminate and the apparatus is valid.** This is the second run; the first was correctly reported could-not-grade when C also stayed silent. ## The confound I excluded before publishing this Resolution happens **server-side in Forgejo**, not in the runner — so the runner's egress is irrelevant and Forgejo's is decisive. Measured inside the `forgejo` container: ``` curl https://gitea.com/ HTTP 303 getent hosts gitea.com 2600:1f14:2b50:701:89b3:c221:9ea7:46b4 [actions] ENABLED = true · DEFAULT_ACTIONS_URL = self ``` **Forgejo can reach gitea.com. A and B did not fail for a network reason.** 🔑 **And this confirms `docs/integration.md`'s own warning from the other side.** `gitea/act`'s parser accepts the absolute form and has no host allowlist — but *"resolution fails BEFORE that code is reached."* The server never creates a job, so act's parser is never consulted. The caller registers and never fires: **no error, no failed run, no run object.** Same silent signature as the `.forgejo/`-on-Gitea case. ## Consequence for #1068 **The Codeberg-via-gitea.com route is closed.** A Codeberg adopter's `uses:` resolves against `codeberg.org`, and an absolute URL out to gitea.com does not work — so the toolkit must be *on* the forge that runs it. `#793` retired the Codeberg mirror because Codeberg does not accept largely-LLM-generated code, and that retirement therefore still costs us Codeberg adopters. **What remains for a Codeberg adopter is vendoring** — copying the reusable workflow into their own repo rather than referencing ours. It works, and it gives up pin-by-tag and drift-tracking. That should be written down in `integration.md` as the honest answer rather than left as an inference. Version: `15.0.7+gitea-1.22.0`. **This is a fact about this Forgejo version, not a law** — re-run the four arms after a major upgrade. Closing: the question is answered.
bosun closed this issue 2026-09-05 20:53:20 +02:00
Author
Owner

📌 AC hygiene, 2026-09-06. Four bare boxes on a closed tracker whose measurement was actually made — reconciled.

Neither arm resolved. So the third AC is RETIRED, not unfinished: its condition ("if either arm resolves") did not obtain, and its branch is unreachable. The other three are DONE.

🔑 The result is a fact about 15.0.7+gitea-1.22.0, not a permanent one#1068 carries it as measured impossible, re-test after a major upgrade, and the Codeberg cost is recorded there rather than here.

⚠️ Left bare, these read as abandoned work on the tracker that CLOSED the cross-forge question. That is exactly the shape the four-state discipline exists to prevent: a reader who scans boxes never reaches the sentence that would distinguish deliberately unreachable from neglected.

📌 **AC hygiene, 2026-09-06. Four bare boxes on a closed tracker whose measurement was actually made — reconciled.** **Neither arm resolved.** ✅ **So the third AC is RETIRED, not unfinished: its condition (*"if either arm resolves"*) did not obtain, and its branch is unreachable.** The other three are DONE. 🔑 **The result is a fact about `15.0.7+gitea-1.22.0`, not a permanent one** — `#1068` carries it as *measured impossible, re-test after a major upgrade*, and the Codeberg cost is recorded there rather than here. ⚠️ **Left bare, these read as abandoned work on the tracker that CLOSED the cross-forge question.** That is exactly the shape the four-state discipline exists to prevent: a reader who scans boxes never reaches the sentence that would distinguish *deliberately unreachable* from *neglected*.
Sign in to join this conversation.
No milestone
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#1189
No description provided.