A snapshot: read at commit 707a04c7ce8 on 2026-10-18T00:06:01Z by majordomus 0.29.0. Nothing on this page is read again. See it live
test/cases/104_published_site.sh
An object of kind test
What it is
- URI
majordomus://test/test/cases/104_published_site.sh- Kind
test- Identity
test/cases/104_published_site.sh- Path
test/cases/104_published_site.sh- Provenance
- declared
- Media type
text/plain- As a capability
- test.test/cases/104_published_site.sh
Front matter
{}Content
# The published-site gate, against fixture repositories rather than against the network.
#
# `scripts/ci/pages-check` answers four questions, and two of them are decidable offline.
#
# Is the commit gh-pages says it was built from an ancestor of the trunk? That is the half
# that failed in production on 2026-09-09, when an unmerged branch was served for half an
# hour with every tree-level gate green.
#
# And has the trunk moved past it? That is the converse, and it was owned by nobody until
# 2026-09-10, when twenty pull requests landed in an afternoon, the site sat hours behind
# master, and this gate said `ok` every time it ran — because a publication that is on master
# is on master however old it is. A one-way check is this repository's most repeated defect
# class, so the direction that was missing is held to a case here beside the one that was not.
#
# The other two halves ask the network — the live site, and GitHub's own build of gh-pages —
# and are not simulated: a case that stood up an HTTP server would be testing the fixture, and
# one that reached the real site would fail whenever the network did. `--offline` is the seam
# the gate declares for exactly this, and the case asserts that the seam says so rather than
# silently skipping.
#
# Every fixture is a repository this case builds, so the assertions do not depend on the
# checkout's own history.
. "$ROOT/test/lib.sh"
GATE="$ROOT/scripts/ci/pages-check"
[ -x "$GATE" ] || { echo " scripts/ci/pages-check is missing or not executable"; exit 1; }
# A repository with a trunk, a published commit on it, and a gh-pages branch that says so.
fixture() {
local dir="$1" published="$2"
rm -rf "$dir"; mkdir -p "$dir"
(
cd "$dir" || exit 1
git init -q .
git config user.email t@e; git config user.name t
git commit -q --allow-empty -m "first"
git branch -M master
git commit -q --allow-empty -m "second"
# the branch that is not on master, for the unmerged case
git checkout -q -b elsewhere
git commit -q --allow-empty -m "work nobody merged"
UNMERGED="$(git rev-parse --short HEAD)"
git checkout -q master
MERGED="$(git rev-parse --short HEAD)"
case "$published" in
merged) SHA="$MERGED" ;;
unmerged) SHA="$UNMERGED" ;;
esac
# gh-pages carries the deploy subject the gate reads, and nothing else needs to be real
git checkout -q --orphan gh-pages
git rm -rq --cached . 2>/dev/null || true
git commit -q --allow-empty -m "deploy: site from $SHA (registry deadbeefcafe), 800 routes"
git checkout -q master
# the gate reads `$REMOTE/$BRANCH`, so the fixture is its own remote
git remote add origin "$dir"
git fetch -q origin 'refs/heads/*:refs/remotes/origin/*'
mkdir -p scripts/ci
cp "$GATE" scripts/ci/pages-check
)
}
# Master moves past the publication, the way a merged pull request moves it.
advance() {
(
cd "$1" || exit 1
git checkout -q master
: > moved.txt; git add moved.txt
git commit -q -m "something the site is built from"
git fetch -q origin 'refs/heads/*:refs/remotes/origin/*'
)
}
# --- a publication from the trunk is accepted
fixture "$T/ok" merged
if ( cd "$T/ok" && ./scripts/ci/pages-check --offline > "$T/ok.out" 2>&1 ); then rc=0; else rc=$?; fi
[ "$rc" = 0 ] || { echo " the gate refused a publication that is on master (exit $rc):"; cat "$T/ok.out"; exit 1; }
grep -q "is on master" "$T/ok.out" || { echo " the gate did not say the commit is on master:"; cat "$T/ok.out"; exit 1; }
# the seam is declared rather than silent: --offline says the live site was not asked
grep -q "not asked" "$T/ok.out" || {
echo " --offline skipped the live check without saying so; a check that is silently"
echo " absent is indistinguishable from one that passed"; cat "$T/ok.out"; exit 1; }
# --- a publication from a branch that is not on the trunk is refused
#
# This is 2026-09-09 in a fixture. The exit code is the repository's contract for a refusal
# (10), and the message must name the commit, because a refusal nobody can act on is noise.
fixture "$T/bad" unmerged
if ( cd "$T/bad" && ./scripts/ci/pages-check --offline > "$T/bad.out" 2>&1 ); then rc=0; else rc=$?; fi
[ "$rc" = 10 ] || { echo " the gate accepted an unmerged publication (exit $rc):"; cat "$T/bad.out"; exit 1; }
grep -q "NOT on master" "$T/bad.out" || { echo " the refusal does not say what is wrong:"; cat "$T/bad.out"; exit 1; }
grep -q "any-ref" "$T/bad.out" || { echo " the refusal does not name the cause or the remedy:"; cat "$T/bad.out"; exit 1; }
# --- a deploy commit that does not say where it came from is unauditable, and refused
#
# The subject is the only record of which tree was published. A deploy that stopped writing
# it would make every later answer a guess, so the gate refuses to guess (12, missing
# artifact) rather than passing.
fixture "$T/mute" merged
if ( cd "$T/mute" && git checkout -q gh-pages && git commit -q --allow-empty -m "chore: something else entirely" \
&& git checkout -q master && git fetch -q origin 'refs/heads/*:refs/remotes/origin/*' \
&& ./scripts/ci/pages-check --offline > "$T/mute.out" 2>&1 ); then rc=0; else rc=$?; fi
[ "$rc" = 12 ] || { echo " a deploy commit naming no source was accepted (exit $rc):"; cat "$T/mute.out"; exit 1; }
grep -q "unauditable" "$T/mute.out" || { echo " the refusal does not say why it cannot proceed:"; cat "$T/mute.out"; exit 1; }
# --- a trunk that has moved past the publication, and the deploy never happened
#
# This is 2026-09-10 in a fixture: gh-pages still names the commit it named an hour ago while
# master carries commits it does not. The window is the whole judgement — the same tree is a
# deploy in flight when it is a minute old and a stale site when it is an hour old — so it is
# asked both ways over one fixture rather than by waiting.
fixture "$T/owed" merged
advance "$T/owed"
# inside the window: owed, young, and not a refusal
if ( cd "$T/owed" && ./scripts/ci/pages-check --offline --owed-after 3600 > "$T/young.out" 2>&1 ); then rc=0; else rc=$?; fi
[ "$rc" = 0 ] || { echo " the gate refused a publication that is merely in flight (exit $rc):"; cat "$T/young.out"; exit 1; }
grep -q "inside the .* deploy window" "$T/young.out" || {
echo " the gate did not say a publication is owed and young:"; cat "$T/young.out"; exit 1; }
# past the window: the site owes a publication, and that is the refusal that was missing
if ( cd "$T/owed" && ./scripts/ci/pages-check --offline --owed-after 0 > "$T/owed.out" 2>&1 ); then rc=0; else rc=$?; fi
[ "$rc" = 10 ] || { echo " the gate accepted a site the trunk had moved past (exit $rc):"; cat "$T/owed.out"; exit 1; }
grep -q "owes a publication" "$T/owed.out" || {
echo " the refusal does not say the publication is owed:"; cat "$T/owed.out"; exit 1; }
grep -q "cancelled by the next push" "$T/owed.out" || {
echo " the refusal names no cause a reader could act on:"; cat "$T/owed.out"; exit 1; }
# --- a check that could not be made is never reported as a check that passed
#
# The fixture has no publication model and no scripts/pages, so the trigger paths cannot be
# read. The gate must say so: an absent measurement that prints `ok` is the disease this whole
# file exists to treat.
grep -q "^note " "$T/owed.out" || {
echo " the gate could not read the trigger paths and did not say so:"; cat "$T/owed.out"; exit 1; }
# --- the gate carries no URL of its own
#
# Where the site lives is declared in site/config.toml; a URL written into the gate would be
# a second declaration of it, and the two would drift the first time the site moved.
if grep -nE 'https?://[a-z]' "$GATE" | grep -v '^\s*#' | grep -q .; then
echo " the gate contains a literal URL; it must read site/config.toml:"
grep -nE 'https?://[a-z]' "$GATE" | grep -v '^\s*#' | head -3
exit 1
fi
# --- and no identity path of its own either
#
# `.ai/repo/ci/pages.yaml` declares where the site states which commit it is; scripts/pages
# reads it there, and so must this.
grep -q 'pages.yaml' "$GATE" || {
echo " the gate does not read the identity path from the publication model"; exit 1; }