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>
112 lines
6.1 KiB
YAML
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
|