fix: rig's ceremony CI guards resolve on Forgejo #113

Merged
andres merged 2 commits from build/112-ceremony-uses-absolute into main 2026-07-29 13:37:34 +00:00
4 changed files with 81 additions and 21 deletions

View file

@ -50,26 +50,38 @@ jobs:
# lives with its implementation upstream; the six pins below and the
# two workflow callers must always name the same ceremony tag.
#
# These six are ABSOLUTE and the two callers in release.yml/labels.yml
# are BARE. That asymmetry is measured, not stylistic (#112) — do not
# "tidy" either half into the other:
# a step `uses:` resolves a bare name through DEFAULT_ACTIONS_URL,
# which this forge points at code.forgejo.org, where ceremony does not
# exist — bare 404s and every guard below fails;
# a reusable-workflow `uses:` never consults DEFAULT_ACTIONS_URL at
# all — it resolves against the runner's own instance, so bare already
# works there. And release.yml's caller is the line ceremony's own
# docs-sync greps for its pin, anchored to the bare spelling: make it
# absolute and the pin goes invisible and docs-sync exits 1.
#
# changelog-armed: the version-keyed arming rule (rig#66; the
# unconditional form rig#44 reverted — this is its correct return).
- uses: heavy-duty/ceremony/actions/changelog-armed@0.3.0
- uses: https://forgejo.heavyduty.builders/heavy-duty/ceremony/actions/changelog-armed@0.3.0
# changelog-monotonic: no shipped heading deleted or duplicated
# (#98, box#122). Strict by default: an unresolvable base ref is red,
# never a quiet skip — hence the fetch-depth: 0 above.
- uses: heavy-duty/ceremony/actions/changelog-monotonic@0.3.0
- uses: https://forgejo.heavyduty.builders/heavy-duty/ceremony/actions/changelog-monotonic@0.3.0
# changelog-assembled: a release's stamped section must exactly match
# the fragments it consumed. Vacuous on non-release PRs.
- uses: heavy-duty/ceremony/actions/changelog-assembled@0.3.0
- uses: https://forgejo.heavyduty.builders/heavy-duty/ceremony/actions/changelog-assembled@0.3.0
# drill-recorded: a release version carries drills/<version>.md
# (rig's drill meaning: drills/README.md). Vacuous on -dev trees.
- uses: heavy-duty/ceremony/actions/drill-recorded@0.3.0
- uses: https://forgejo.heavyduty.builders/heavy-duty/ceremony/actions/drill-recorded@0.3.0
# runner-isolated: PR-triggered workflows never execute unreviewed
# branch code on a self-hosted runner.
- uses: heavy-duty/ceremony/actions/runner-isolated@0.3.0
- uses: https://forgejo.heavyduty.builders/heavy-duty/ceremony/actions/runner-isolated@0.3.0
# docs-sync: the .ceremony/ doctrine mirror is byte-identical to the
# pin read from release.yml (ceremony#19) — a hand edit or a
# half-done pin bump goes red here.
- uses: heavy-duty/ceremony/actions/docs-sync@0.3.0
- uses: https://forgejo.heavyduty.builders/heavy-duty/ceremony/actions/docs-sync@0.3.0
# The install LIFECYCLE against a tree install.sh itself produced — the four
# beats box and cast already run in CI (#106): install from this checkout,

View file

@ -1179,31 +1179,32 @@ FORGEJO__actions__ENABLED=true
FORGEJO__actions__DEFAULT_ACTIONS_URL=https://code.forgejo.org
```
> **`DEFAULT_ACTIONS_URL` is a single fallback, and rig's own workflows need
> two origins.** It decides where a *bare* `uses: owner/repo@ref` resolves.
> Measured:
> **`DEFAULT_ACTIONS_URL` is a single fallback, and it only governs *steps*.**
> It decides where a bare `uses: owner/repo@ref` on a **step** resolves — and
> nothing else. Measured on this instance, with a registered runner (#112):
>
> | reference | count in `.github/workflows/` | `code.forgejo.org` |
> |---|---|---|
> | `actions/checkout@v4` | 3 | **200** — mirrored |
> | `heavy-duty/ceremony/...@0.3.0` | 8 | **404** — lives on this Forgejo instead |
> | reference | count | bare resolves to | verdict |
> |---|---|---|---|
> | `actions/checkout@v4` (step) | 3 | `code.forgejo.org` | **200** — mirrored, keep bare |
> | `heavy-duty/ceremony/actions/…` (step) | 6 | `code.forgejo.org` | **404** — must be absolute |
> | `heavy-duty/ceremony/.github/workflows/…` (reusable) | 2 | **this instance** | **200** — keep bare |
>
> So with the value above, every ceremony guard (`changelog-armed`,
> `changelog-monotonic`, `changelog-assembled`, `drill-recorded`,
> `runner-isolated`, `docs-sync`, and the release/labels callers) fails to
> resolve the moment rig's CI actually runs on the forge.
> The last row is the one that surprises: a **reusable-workflow** `uses:` never
> consults `DEFAULT_ACTIONS_URL` at all. It resolves against the runner's own
> instance, so the `release.yml` and `labels.yml` callers already work bare.
>
> Forgejo accepts an absolute URL per step, so one side has to be explicit.
> **The ruling is to keep the value above**`actions/*` are exactly what
> `DEFAULT_ACTIONS_URL` is designed to resolve, and `code.forgejo.org` mirrors
> them — and to make the eight first-party references absolute:
> them — and to make only the **six** first-party *step* references absolute:
>
> ```yaml
> - uses: https://forgejo.heavyduty.builders/heavy-duty/ceremony/actions/docs-sync@0.3.0
> ```
>
> That change is tracked in forgejo#112, not made here. Until it lands,
> **rig's own CI is not expected to run on Forgejo**.
> The two reusable callers **stay bare**, and `release.yml`'s must: ceremony's
> `docs-sync` reads rig's pin out of that line with a grep anchored to the bare
> spelling, so absolutising it hides the pin and the guard exits 1. `test/cli.sh`
> pins all three shapes.
> `rig forgejo-runner` exists to run *your repositories'* workflows and does not
> depend on any of it — the value above is correct for those from the start.

3
changelog.d/112.md Normal file
View file

@ -0,0 +1,3 @@
### Fixed
- rig's six ceremony CI guards resolve on Forgejo (#112)

View file

@ -3516,6 +3516,50 @@ check "rig runner: still requires --repo, unchanged" 2 "--repo <owner/repo> is r
check "rig runner: still speaks github.com" 0 "https://github.com/" \
grep -o "https://github.com/\${repo}" "$ROOT/commands/lib/runner-config.sh"
# --- ceremony uses: resolution on this forge (#112) --------------------------
# The six ci.yml guards resolve ABSOLUTELY; the two reusable callers stay BARE.
# The two halves pull opposite ways and each was measured on the instance:
#
# guards — a step `uses:` resolves a bare name through DEFAULT_ACTIONS_URL
# (code.forgejo.org here), where heavy-duty/ceremony does not
# exist: a real run 404'd on `git clone`, so every guard failed.
# callers — a reusable-workflow `uses:` never consults DEFAULT_ACTIONS_URL;
# it resolves against the runner's own instance, so bare already
# fetches ceremony from this forge and expands.
#
# release.yml's caller additionally MUST stay bare: ceremony's docs-sync reads
# rig's pin out of that one line with an anchored grep, and an absolute prefix
# makes the pin invisible (docs-sync exits 1, `no pin line ... found none`).
# The pin regex below is that grep verbatim — if ceremony's parser and this
# file ever disagree, this is the test that says so.
wf_count() { printf 'count=%s\n' "$(grep -cE "$2" "$ROOT/.github/workflows/$1" || true)"; }
check "ci.yml: six ceremony guards, absolute at 0.3.0" 0 "count=6" \
wf_count ci.yml '^[[:space:]]*- uses: https://forgejo\.heavyduty\.builders/heavy-duty/ceremony/actions/[a-z-]+@0\.3\.0$'
check "ci.yml: no bare ceremony action survives (they 404 via DEFAULT_ACTIONS_URL)" 0 "count=0" \
wf_count ci.yml '^[[:space:]]*- uses: heavy-duty/ceremony/'
check "ci.yml: the three actions/checkout stay bare (the mirror resolves them)" 0 "count=3" \
wf_count ci.yml '^[[:space:]]*- uses: actions/checkout@v4$'
# The docs-sync pin grep, verbatim from ceremony 0.3.0
# actions/docs-sync/docs-sync.sh — exactly one line must match, or the guard
# dies with "expected exactly one" / "found none".
check "release.yml: the pin is still visible to ceremony's docs-sync grep" 0 "count=1" \
wf_count release.yml '^[[:space:]]*(-[[:space:]]*)?uses:[[:space:]]*heavy-duty/ceremony/\.github/workflows/release\.yml@'
check "labels.yml: the reusable caller stays bare too" 0 "count=1" \
wf_count labels.yml '^[[:space:]]*uses:[[:space:]]*heavy-duty/ceremony/\.github/workflows/labels\.yml@0\.3\.0$'
check "release.yml: the caller was not absolutised (docs-sync would go red)" 0 "count=0" \
wf_count release.yml '^[[:space:]]*uses:[[:space:]]*https://.*ceremony/\.github/workflows/'
check "labels.yml: the caller was not absolutised either" 0 "count=0" \
wf_count labels.yml '^[[:space:]]*uses:[[:space:]]*https://.*ceremony/\.github/workflows/'
# One tag governs all eight references — the pin may be bumped, never split.
ceremony_tags() {
grep -rhoE 'heavy-duty/ceremony/[^@]+@[^[:space:]]+' "$ROOT/.github/workflows/" \
| sed -E 's/.*@//' | sort -u | tr '\n' ' '
}
check "all eight ceremony references still name one tag" 0 "0.3.0" ceremony_tags
echo "---"
echo "$PASS passed, $FAIL failed"
[ "$FAIL" -eq 0 ]