CONTRIBUTING has always required a real-hardware drill on a release, and nothing enforced it — so no release in this family has ever carried one. Every other ceremony step is checked by a script; the one that costs an afternoon was checked by a reviewer remembering. A reviewer bot finally blocked on it. - drill/RUNS.md: rig's own run log, starting EMPTY of records. rig has no drill harness of its own yet; the harness lives in box's drill/ and this file is the record, not the instrument. - .github/scripts/drill-recorded.sh: a -dev tree asserts nothing; a bare VERSION requires a non-empty '## Release drill — X.Y.Z' section, version matched WHOLE so an -rc1 record is not evidence for the final. - Per-repo on purpose. A cross-repo lookup into box fails on a token, a fork checkout or a network blip, and all of those degrade to 'pass' on precisely the tree that ships — the UNREADABLE-vs-NONE shape #90 fixed. - It asks for a RECORD, not a RESULT, so a maintainer waiver stays possible but has to be written down under that version. - Fixtures carry their own VERSION and RUNS.md (heavy-duty/box#146: fixtures reading the repo's real VERSION exercised only the -dev branch and went red first while cutting a release). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
113 lines
6.2 KiB
YAML
113 lines
6.2 KiB
YAML
name: ci
|
|
on:
|
|
push:
|
|
branches: [main]
|
|
pull_request:
|
|
jobs:
|
|
check:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
# fetch-depth: 0, for the changelog-monotonic step below and only
|
|
# for it. That check is about a DIFF — which release headings the
|
|
# merge base had — so it needs the base branch's history present,
|
|
# and the default depth-1 checkout has none of it. An explicit
|
|
# `git fetch origin <base>` would be narrower, but it has to be
|
|
# right on both event types and on fork PRs, and getting it subtly
|
|
# wrong degrades to a SKIP (a guard that silently stops guarding —
|
|
# the exact failure this repo keeps refusing). Full history on a
|
|
# pure-bash tree costs a second; the STRICT flag below turns any
|
|
# remaining skip red rather than green.
|
|
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
|
|
# test/labels-reconcile.sh existed here since #87 but ran nowhere: the
|
|
# label state machine gates every PR on this repo and its fixtures were
|
|
# green only when someone remembered to run them by hand. Same step, same
|
|
# place as heavy-duty/box.
|
|
- name: labels state-machine tests
|
|
run: bash test/labels-reconcile.sh
|
|
- name: release-flow tests
|
|
run: bash test/release.sh
|
|
# No SHIPPED release heading was deleted or DUPLICATED (#98). Its own step
|
|
# rather than a line inside test/release.sh: that suite drives the arming
|
|
# rule against constructed VERSION + CHANGELOG.md trees that are not git
|
|
# repos, and this assert needs a git history — folding it in would make
|
|
# those cases skip or lie. It is also a DIFFERENT invariant: arming is a
|
|
# fact about this tree, monotonicity is a fact about this tree versus its
|
|
# merge base. STRICT=1 so a checkout that cannot reach the base ref fails
|
|
# here instead of skipping quietly forever.
|
|
#
|
|
# NOT pull-request-only, and that is the #98 fix at the workflow level.
|
|
# The two halves have different vacuity: DELETION is vacuous on a push to
|
|
# main (the merge base IS HEAD), but DUPLICATION is vacuous on no tree at
|
|
# all, so gating the whole script on `pull_request` left a duplicate that
|
|
# reached main by any other route unasserted forever.
|
|
#
|
|
# The `|| github.ref_name` fallback is load-bearing, not defensive. On a
|
|
# push event `github.base_ref` is EMPTY, so the argument would collapse to
|
|
# a bare `origin/`, which does not resolve — and STRICT=1 correctly
|
|
# promotes that to a hard failure, turning every push to main red. With
|
|
# the fallback it resolves to the pushed branch, whose merge base with
|
|
# HEAD is HEAD or its parent: containment passes vacuously, exactly as the
|
|
# old `if` intended, while uniqueness now runs on every push.
|
|
- name: no shipped changelog heading was deleted or duplicated
|
|
env:
|
|
CHANGELOG_MONOTONIC_STRICT: '1'
|
|
run: bash .github/scripts/changelog-monotonic.sh "origin/${{ github.base_ref || github.ref_name }}"
|
|
# The release this tree would ship has a recorded real-hardware drill
|
|
# (drill/RUNS.md). CONTRIBUTING ("Releasing") has always required one and
|
|
# nothing enforced it, so no release in this family has ever carried one
|
|
# — the drill was the single ceremony step checked by a reviewer
|
|
# remembering rather than by a script.
|
|
#
|
|
# Deliberately NOT trigger-scoped, and for the opposite reason to the
|
|
# step above. That one needs a base ref, so its argument has to be right
|
|
# on both event types; this one reads two files in the checkout and is
|
|
# VACUOUS BY CONSTRUCTION on a `-dev` VERSION, which every ordinary PR
|
|
# and every push to main carries. It has something to say on exactly one
|
|
# tree — the `release: X.Y.Z` ceremony PR — so an `if:` could only add a
|
|
# way for that one tree to slip past.
|
|
#
|
|
# PER-REPO on purpose: rig reads rig's own record, never box's repo. A
|
|
# cross-repo lookup fails on a token, a network blip or a fork checkout,
|
|
# and every one of those lands on "could not read" — which degrades to
|
|
# green on precisely the tree that ships (the UNREADABLE-vs-NONE shape
|
|
# #90 fixed).
|
|
- name: a release version has a recorded drill
|
|
run: bash .github/scripts/drill-recorded.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
|