Drill records move from sections inside drill/RUNS.md to one file per version: drills/<version>.md. The old guard parsed headings — em-dash field matching, an optional ' — DATE' tail, a whole-version comparison so 0.2.0-rc1 could not satisfy 0.2.0, a '(NF == 5 || $6 == dash)' tail constraint to match box, and a non-blank body rule. All of that existed only because records shared one file, and this repo shipped two defects out of the complexity in review: the sed '/./,$!d' whitespace bypass, and heading-grammar drift from box's stricter form. One file per version makes nearly all of it unrepresentable — 0.2.0.md and 0.2.0-rc1.md are simply different files, so the whole-version rule is the filesystem's rather than a comparison anyone can get wrong. One rule survives: a file of only whitespace is not a record. Plain drills/, not .drills/ — dot-directories are invisible to globs without dotglob, the cause of #118/#121 here and box#116. drill/RUNS.md is deleted. It was created in this same unmerged PR and held only format documentation, no real records; the useful reasoning moves to drills/README.md. (box keeps ITS drill/RUNS.md, a genuine harness log with real run history.) The docs also drop an over-constrained ordering claim: the three repos' drills are INDEPENDENT, run in any order and any sitting. What makes that safe is that each pins the same fixed set of candidate refs — and that pinning, not sequencing, is what dissolves the box/rig recursion, since refs are static identifiers that exist as soon as the release branches do. Each repo also drills a different thing: box the isolation contract, rig convergence, cast promotion. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
125 lines
6.2 KiB
YAML
125 lines
6.2 KiB
YAML
name: ci
|
|
|
|
on:
|
|
push:
|
|
branches: [main]
|
|
pull_request:
|
|
|
|
jobs:
|
|
build:
|
|
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
|
|
# tree this size costs a second; the STRICT flag below turns any
|
|
# remaining skip red rather than green.
|
|
fetch-depth: 0
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version: "22"
|
|
cache: npm
|
|
# the secrets tests round-trip a real age identity
|
|
- run: sudo apt-get update && sudo apt-get install -y age
|
|
- run: npm ci
|
|
- run: npm run check
|
|
- run: npm run build
|
|
- run: npm test
|
|
- name: installer is valid bash
|
|
run: bash -n install.sh bin/cast scripts/*.sh .github/scripts/*.sh
|
|
# `bash -n` above is a syntax check on a hand-maintained list; it is not
|
|
# a linter and it does not notice a script it was never told about.
|
|
# This sweep derives its list from git and asserts the list covers every
|
|
# tracked *.sh, so a new script cannot go unlinted quietly (#118).
|
|
- name: shellcheck — every tracked shell script
|
|
run: bash .github/scripts/shellcheck-all.sh
|
|
- name: labels state-machine tests
|
|
run: bash test/labels-reconcile.sh
|
|
|
|
# ...and no SHIPPED release heading was deleted or DUPLICATED (#133;
|
|
# box#122's guard, box#143's ordering fix). Its own step so that when it
|
|
# goes red the log names the invariant that broke — and a DIFFERENT
|
|
# invariant from the arming rule npm test carries: 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 #133 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 }}"
|
|
|
|
# ...and a release carries its DRILL RECORD. CONTRIBUTING has always
|
|
# asked for the real-hardware drill; nothing asserted it, so it was
|
|
# performed exactly as often as a reviewer remembered to ask — which is
|
|
# never, across every release in the family, until a bot blocked on it.
|
|
# Here it is a fact about the tree instead of a fact about somebody's
|
|
# memory.
|
|
#
|
|
# No `if:` guard on the event or the label. The script keys off
|
|
# package.json itself: a `-dev` tree has no ship claim and passes
|
|
# trivially, a bare version is a release ceremony tree and must have a
|
|
# record. Gating this step on the `release` label instead would put the
|
|
# assert behind a hand-applied label — the guard would be absent from
|
|
# exactly the PR that mislabels itself, and unasserted PRs are how the
|
|
# drill went missing in the first place.
|
|
#
|
|
# It requires a RECORD, not a PASS: a maintainer waiver is legal, and is
|
|
# itself the content of drills/<version>.md. Skipping stays possible and
|
|
# stays visible.
|
|
- name: a release version has a drill record
|
|
run: bash .github/scripts/drill-recorded.sh
|
|
|
|
# The installer, proven by RUNNING it — CAST_INSTALL_SOURCE points it at
|
|
# this checkout, so CI proves the installer under review (the versioned
|
|
# layout, the current symlink, the PATH chain, the uninstall's absence
|
|
# assert), not a hand-built imitation of it. Box's CI installs box the
|
|
# same way. This is the one place the real npm ci + tsc build path runs
|
|
# end to end; the vitest installer tests cover the layout semantics
|
|
# offline with a shimmed npm.
|
|
install:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version: "22"
|
|
cache: npm
|
|
- name: install via install.sh, from this checkout
|
|
run: |
|
|
CAST_NO_MODIFY_PATH=1 CAST_INSTALL_SOURCE="$GITHUB_WORKSPACE" bash install.sh
|
|
# assert what landed: the layout, the chain, and that it answers
|
|
readlink -f "$HOME/.local/bin/cast" | grep '/versions/'
|
|
"$HOME/.local/bin/cast" --version
|
|
"$HOME/.local/bin/cast" versions
|
|
- name: converging no-op — a re-run changes nothing and builds nothing
|
|
run: |
|
|
CAST_NO_MODIFY_PATH=1 CAST_INSTALL_SOURCE="$GITHUB_WORKSPACE" bash install.sh \
|
|
| tee /tmp/rerun.log
|
|
grep -q 'already installed' /tmp/rerun.log
|
|
- name: uninstall --all — ends with the absence assert
|
|
run: |
|
|
CAST_YES=1 "$HOME/.local/bin/cast" uninstall --all
|
|
test ! -e "$HOME/.local/share/cast"
|
|
test ! -e "$HOME/.local/bin/cast"
|
|
test ! -L "$HOME/.local/bin/cast"
|