Uniqueness is a property of HEAD alone — no base ref, no merge base, no base blob. It sat downstream of all three, so every degradation path returned success on a tree carrying a duplicate. The base-blob path was the worst: a branch that introduces CHANGELOG.md hit a bare `exit 0` on a message that was true about deletion and silent about the duplicate in front of it. STRICT could not reach it — STRICT guards the two skip() calls, and that is not one of them. That inverted the two halves, and it inverted them hardest here. Deletion needs a diff to see; duplication is the one release-notes.sh actually mis-renders, and cast has the ABSORBING extractor — no `exit`, so `grab` re-arms on the second heading and the published body swallows whatever sits between the copies (box#118). The half with the live extraction bug behind it had the most ways to silently not run. Moved, not rewritten. The skip messages now say containment skipped and that uniqueness already passed. The CI step is no longer pull_request-only, with a `github.ref_name` fallback because base_ref is empty on a push and a bare `origin/` under STRICT would redden every push to main. Found by claude-bot-andresmgsl and codex-bot-andresmgsl reviewing #134. cast inherited the ordering from box, fixed there in heavy-duty/box#144 (#143). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
98 lines
4.6 KiB
YAML
98 lines
4.6 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
|
|
- 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 }}"
|
|
|
|
# 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"
|