From 25f337494c73e4594426e534030bdf22d2ace684 Mon Sep 17 00:00:00 2001 From: cluade-reviewer-andresmgsl Date: Wed, 29 Jul 2026 12:10:38 +0000 Subject: [PATCH] fix: rig's ceremony CI guards resolve on Forgejo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The six ceremony actions in ci.yml are step `uses:` references, and a bare step reference resolves through FORGEJO__actions__DEFAULT_ACTIONS_URL — which this forge points at code.forgejo.org, where heavy-duty/ceremony does not exist. Measured with a registered runner on the instance: `git clone https://code.forgejo.org/heavy-duty/ceremony` -> 404, so every guard fails the moment rig's own CI runs on the forge. Those six become absolute. The two reusable-workflow callers do NOT move, and this is the correction to the original "make all eight absolute" ruling. A reusable `uses:` never consults DEFAULT_ACTIONS_URL; it resolves against the runner's own instance, so both callers already fetch ceremony from this forge and expand. Absolutising release.yml's would actively break CI: ceremony's docs-sync reads rig's pin out of that one line with a grep anchored to the bare spelling, so the prefix makes the pin invisible and docs-sync exits 1 with "no pin line ... found none". actions/checkout@v4 stays bare in all three places — the mirror serves it, and that is exactly what DEFAULT_ACTIONS_URL is for. The pin stays @0.3.0 throughout; no .ceremony/ edit, no pin bump. test/cli.sh pins all three shapes, including the docs-sync grep verbatim, so ceremony's parser and rig's caller cannot drift apart silently. Refs #112 Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/ci.yml | 24 ++++++++++++++++------ README.md | 31 ++++++++++++++-------------- changelog.d/112.md | 3 +++ test/cli.sh | 44 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 81 insertions(+), 21 deletions(-) create mode 100644 changelog.d/112.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a8ca94f..96d98ba 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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/.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, diff --git a/README.md b/README.md index 69d5bfa..baae332 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/changelog.d/112.md b/changelog.d/112.md new file mode 100644 index 0000000..8d6ee75 --- /dev/null +++ b/changelog.d/112.md @@ -0,0 +1,3 @@ +### Fixed + +- rig's six ceremony CI guards resolve on Forgejo — bare names 404'd through `DEFAULT_ACTIONS_URL`; the two reusable callers stay bare (#112) diff --git a/test/cli.sh b/test/cli.sh index 46ade38..060c307 100644 --- a/test/cli.sh +++ b/test/cli.sh @@ -3516,6 +3516,50 @@ check "rig runner: still requires --repo, unchanged" 2 "--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 ]