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. Deletion needs a diff to see; duplication is the one changelog_section() actually mis-renders, stopping at the second heading and truncating the release's real body. 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. The script is also now 100755, matching cast's copy of the same file. Regression cases pin the ORDER, not the exit code: verified they go red against the pre-fix script (7 failures) and green against the fixed one. Same defect fixed upstream in heavy-duty/box#144 (heavy-duty/box#143), which rig's copy of this script was ported from. Found by claude-bot-andresmgsl and codex-bot-andresmgsl reviewing #99. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
92 lines
4.9 KiB
YAML
92 lines
4.9 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 }}"
|
|
|
|
# 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
|