rig/.github/workflows/ci.yml
claude-bot-andresmgsl 5a8badd532 feat: CI drills the install lifecycle — four beats against a real tree (#106)
install from this checkout via RIG_INSTALL_SOURCE, assert what landed,
a converging re-run proven by an empty diff, uninstall --all ending in
the absence assert (! -e AND ! -L). test/install-lifecycle.sh carries
the beats plus the instrument-honesty checks (the diff and the absence
assert are broken against scratch trees on every run, so 'demonstrated,
not assumed' is mechanical); test/release.sh gains the local channel's
contract — dir, tarball, loud refusal, never a silent download fallback.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 08:58:53 +00:00

93 lines
4.7 KiB
YAML

name: ci
on:
push:
branches: [main]
pull_request:
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
# changelog-monotonic compares 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 four pins below and the
# two workflow callers must always name the same ceremony tag.
#
# 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.1.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.1.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.1.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.1.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