rig/.github/workflows/ci.yml
cluade-reviewer-andresmgsl 25f337494c fix: rig's ceremony CI guards resolve on Forgejo
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) <noreply@anthropic.com>
2026-07-29 12:10:38 +00:00

112 lines
6.1 KiB
YAML

name: ci
on:
push:
branches: [main]
pull_request:
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
# changelog-monotonic and changelog-assembled compare HEAD against
# the merge base; a
# checkout that cannot resolve it is a hard failure in CI, not
# a skip (a guard that can quietly stop guarding is the failure
# shape these checks exist to refuse).
fetch-depth: 0
- name: shellcheck
# -x follows the `source=SCRIPTDIR/...` directives into commands/lib/.
# globstar so a script in a new subdirectory is linted without anyone
# remembering to edit this list; bin/* covers the extensionless entrypoints.
# dotglob because globs skip dot-prefixed names: without it `**/` never
# descends into `.github/`, so `.github/scripts/*.sh` — release-lib.sh
# among them — was swept up by nothing (#70). It also makes `**`
# descend into `.git/`, which holds no tracked `.sh` on a checkout.
# The file list is printed so under-coverage shows up in the log, and
# the comm below turns under-coverage into a failure rather than a
# thing someone has to notice: every tracked `.sh` must be in the set.
run: |
shopt -s globstar dotglob
files=(bin/* **/*.sh)
printf 'shellcheck: %s\n' "${files[@]}"
uncovered=$(comm -23 <(git ls-files '*.sh' | sort) <(printf '%s\n' "${files[@]}" | sort))
if [ -n "$uncovered" ]; then
printf 'tracked .sh files the glob does not lint:\n%s\n' "$uncovered" >&2
exit 1
fi
shellcheck -x "${files[@]}"
- name: cli tests
run: bash test/cli.sh
- name: release tests — rig's own surfaces
run: bash test/release.sh
# The drill harness's honesty: refusals, the skip/pass/fail classifier,
# the idempotence capture-and-diff, the record emitter. Network-free and
# root-free — the live four-leg run is a release's drill, not CI's.
- name: drill harness tests — the instrument itself
run: bash test/drill.sh
# The release guards, doctrine in heavy-duty/ceremony's README (#13's
# conversion). Each one's war story — why it exists, what it refuses —
# 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: 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: 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: 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: 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: 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: 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,
# assert what landed, a converging re-run proven by an EMPTY DIFF (never an
# exit code), uninstall --all ending in the absence assert (`! -e` AND
# `! -L` — only the second sees a dangling symlink). Separate from `check`
# for the same reason db-integration is: fast feedback first. The runner's
# real $HOME is the point — no throwaway roots here; the suite refuses to
# run where a rig is already installed, so it cannot eat a real install.
install:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: the install lifecycle — four beats against a real tree
run: bash test/install-lifecycle.sh
# Kept SEPARATE from `check` on purpose: this job pulls a Postgres image and
# stands up throwaway containers, and a slow image pull must never delay the
# fast shellcheck + cli.sh feedback above. ubuntu-latest ships Docker running
# and passwordless sudo, so test/db-integration.sh EXECUTES here (it only
# skips where Docker is absent). It is the automated proof that dump/restore
# actually round-trips, not just that the args parse.
db-integration:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: db dump/restore round-trip
run: bash test/db-integration.sh