c6a567122e
gates / gates (push) Failing after 50s
An admission and its mechanism. Building 0.316.3 published another session's work: the shared DooPlex clone held 31 uncommitted files, and the build directory's build.sh is a symlink into that clone's WORKING COPY — which predated the FELHOM_REPO_DIR override, so the override was ignored and the build copied the shared tree. The banner printed 'Commit: 0225255' and no 'Repo:' line; the evidence was on screen and the image was already pushed. Nobody deployed it, which is luck, not a control. The clean-tree rule has been a sentence in a runbook all along. A rule without a mechanism is a wish, so it is now two questions the build asks and refuses on: is every tracked file committed, and is HEAD what origin/main has. A directory that is not a git checkout is refused too — a build whose source cannot be identified is not reproducible. Four new cases in the test (ten in total), each driving the real script; red-proved by deleting the gate, which fails all four. 0.316.3 must never be deployed. It is not re-pushed or deleted: a version number is pushed once, and that applies to a number I spoiled. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0117kRTSgs3rJWUFGxyoWN6Y
152 lines
6.6 KiB
Bash
152 lines
6.6 KiB
Bash
#!/bin/bash
|
|
# A VERSION NUMBER IS PUSHED ONCE — the test for build.sh's registry check (v0.316.0).
|
|
#
|
|
# WHY IT EXISTS: 0.315.0 was built and pushed three times from three different commits. A box that
|
|
# pulled between the first and the last runs different code under the same number and will never
|
|
# fetch the fix, because to the box that number is already current. The check in build.sh is the
|
|
# mechanism; this is the test that it convicts.
|
|
#
|
|
# HOW IT TESTS WITHOUT A REGISTRY: a fake `docker` is put first on PATH. Every case drives the REAL
|
|
# build.sh, so the test cannot pass while the check has been deleted — which is the red-proof.
|
|
# Nothing is ever pushed: the fake refuses `build` and `push` loudly, so a build that got past the
|
|
# check would fail the test rather than reach the network.
|
|
#
|
|
# Run from controller/: bash scripts/test_build_push_once.sh
|
|
set -u
|
|
HERE="$(cd "$(dirname "$0")" && pwd)"
|
|
BUILD_SH="$(dirname "$HERE")/build.sh"
|
|
fails=0; ran=0
|
|
|
|
[[ -f "$BUILD_SH" ]] || { echo "FAIL: build.sh not found at $BUILD_SH"; exit 1; }
|
|
|
|
# A fake `docker` whose answer to `manifest inspect` is set by FAKE_MODE.
|
|
make_fake_docker() {
|
|
local dir="$1"
|
|
mkdir -p "$dir"
|
|
cat > "$dir/docker" <<'FAKE'
|
|
#!/bin/bash
|
|
case "$1 $2" in
|
|
"manifest inspect")
|
|
case "${FAKE_MODE}" in
|
|
present) echo '{"schemaVersion":2}'; exit 0 ;;
|
|
absent) echo "manifest unknown: manifest unknown" >&2; exit 1 ;;
|
|
down) echo "Get \"https://gitea.dooplex.hu/v2/\": dial tcp: connection refused" >&2; exit 1 ;;
|
|
denied) echo "unauthorized: authentication required" >&2; exit 1 ;;
|
|
esac ;;
|
|
"build "*|"buildx "*|"push "*)
|
|
echo "FAKE-DOCKER-REACHED: $*" ; exit 0 ;;
|
|
esac
|
|
# anything else (version checks etc.) succeeds quietly
|
|
exit 0
|
|
FAKE
|
|
chmod +x "$dir/docker"
|
|
}
|
|
|
|
# Run build.sh with the fake docker, from a throwaway cwd, and capture everything.
|
|
run_build() {
|
|
local mode="$1"; shift
|
|
local tmp; tmp="$(mktemp -d)"
|
|
make_fake_docker "$tmp/bin"
|
|
FAKE_MODE="$mode" PATH="$tmp/bin:$PATH" bash "$BUILD_SH" "$@" >"$tmp/out" 2>&1
|
|
rc=$?
|
|
out="$(cat "$tmp/out")"
|
|
rm -rf "$tmp"
|
|
return $rc
|
|
}
|
|
|
|
check() {
|
|
local name="$1" want_rc="$2" want_text="$3" forbid_text="${4:-}"
|
|
ran=$((ran+1))
|
|
if [[ "$rc" != "$want_rc" ]]; then
|
|
fails=$((fails+1)); echo " FAIL $name: exit $rc, want $want_rc"
|
|
echo "$out" | tail -6 | sed 's/^/ /'; return
|
|
fi
|
|
if [[ -n "$want_text" ]] && ! grep -qi -- "$want_text" <<<"$out"; then
|
|
fails=$((fails+1)); echo " FAIL $name: output does not contain '$want_text'"
|
|
echo "$out" | tail -6 | sed 's/^/ /'; return
|
|
fi
|
|
if [[ -n "$forbid_text" ]] && grep -qi -- "$forbid_text" <<<"$out"; then
|
|
fails=$((fails+1)); echo " FAIL $name: output contains '$forbid_text', which must not happen"
|
|
echo "$out" | tail -6 | sed 's/^/ /'; return
|
|
fi
|
|
echo " ok $name"
|
|
}
|
|
|
|
echo "build.sh — a version number is pushed once, from a clean tree"
|
|
|
|
# 1. The tag is already in the registry -> refuse, before building, and push nothing.
|
|
run_build present 0.999.0 --push
|
|
check "--push, tag present -> refuses" 1 "already exists in the registry" "FAKE-DOCKER-REACHED"
|
|
|
|
# 2. The tag is new -> say so and carry on INTO the build.
|
|
# The exit code is deliberately not asserted: past the check, build.sh runs the real build steps,
|
|
# which cannot complete in a test sandbox. What matters is that the check let it through, so the
|
|
# assertion is its verdict line plus the ABSENCE of a refusal.
|
|
run_build absent 0.999.0 --push
|
|
check "--push, tag absent -> proceeds" "$rc" "is not present in the registry" "already exists in the registry"
|
|
|
|
# 3. --multiarch gets the same refusal (the task's edge case).
|
|
run_build present 0.999.0 --multiarch
|
|
check "--multiarch, tag present -> refuses" 1 "already exists in the registry" "FAKE-DOCKER-REACHED"
|
|
|
|
# 4. The registry cannot be asked -> refuse. "I could not ask" is not proof the tag is new.
|
|
run_build down 0.999.0 --push
|
|
check "--push, registry unreachable -> refuses" 1 "Could not establish whether" "FAKE-DOCKER-REACHED"
|
|
|
|
# 5. An auth failure is just as unanswered as a network failure.
|
|
run_build denied 0.999.0 --push
|
|
check "--push, auth failure -> refuses" 1 "Could not establish whether" "FAKE-DOCKER-REACHED"
|
|
|
|
# 6. A LOCAL build is untouched — reusing a version while iterating is the normal case, and the
|
|
# check must not even run (no registry question is asked, so `present` must not refuse).
|
|
run_build present 0.999.0
|
|
check "local build, tag present -> not refused" 0 "" "already exists in the registry"
|
|
|
|
# ── THE SOURCE TREE MUST BE CLEAN AND PUSHED (v0.316.4) ─────────────────────────────────────────
|
|
# Earned the same evening: a build from the shared clone, which held 31 files of another session's
|
|
# work in progress, published their unreleased code under this repo's version number.
|
|
scratch_repo() {
|
|
local d; d="$(mktemp -d)"
|
|
git -C "$d" init -q 2>/dev/null
|
|
git -C "$d" config user.email t@t; git -C "$d" config user.name t
|
|
mkdir -p "$d/controller"; echo x > "$d/controller/f"
|
|
git -C "$d" add -A >/dev/null; git -C "$d" commit -qm one >/dev/null
|
|
git -C "$d" update-ref refs/remotes/origin/main HEAD
|
|
echo "$d"
|
|
}
|
|
run_build_in() { # $1 = mode, $2 = repo dir, rest = args
|
|
local mode="$1" repo="$2"; shift 2
|
|
local tmp; tmp="$(mktemp -d)"
|
|
make_fake_docker "$tmp/bin"
|
|
FAKE_MODE="$mode" FELHOM_REPO_DIR="$repo" PATH="$tmp/bin:$PATH" bash "$BUILD_SH" "$@" >"$tmp/out" 2>&1
|
|
rc=$?; out="$(cat "$tmp/out")"; rm -rf "$tmp"; return $rc
|
|
}
|
|
|
|
CLEAN_REPO="$(scratch_repo)"
|
|
run_build_in absent "$CLEAN_REPO" 0.999.0 --push
|
|
check "clean + at origin/main -> proceeds" "$rc" "Source tree is clean and at origin/main" "refusing to push"
|
|
|
|
echo "dirt" > "$CLEAN_REPO/controller/uncommitted"
|
|
run_build_in absent "$CLEAN_REPO" 0.999.0 --push
|
|
check "dirty tree -> refuses" 1 "uncommitted change" "FAKE-DOCKER-REACHED"
|
|
rm -f "$CLEAN_REPO/controller/uncommitted"
|
|
|
|
echo y > "$CLEAN_REPO/controller/f2"
|
|
git -C "$CLEAN_REPO" add -A >/dev/null; git -C "$CLEAN_REPO" commit -qm two >/dev/null
|
|
run_build_in absent "$CLEAN_REPO" 0.999.0 --push
|
|
check "ahead of origin/main -> refuses" 1 "not at origin/main" "FAKE-DOCKER-REACHED"
|
|
rm -rf "$CLEAN_REPO"
|
|
|
|
NOT_A_REPO="$(mktemp -d)"; mkdir -p "$NOT_A_REPO/controller"
|
|
run_build_in absent "$NOT_A_REPO" 0.999.0 --push
|
|
check "not a git checkout -> refuses" 1 "not a git checkout" "FAKE-DOCKER-REACHED"
|
|
rm -rf "$NOT_A_REPO"
|
|
|
|
echo
|
|
if [[ $fails -eq 0 ]]; then
|
|
echo "all $ran cases behaved — a pushed version cannot be overwritten"
|
|
exit 0
|
|
fi
|
|
echo "$fails of $ran cases FAILED"
|
|
exit 1
|