Files
vaultik/script/cibuild
clawbot 50816b7415
All checks were successful
check / check (push) Successful in 3m2s
Make a missing CHECK_EPOCH fail the build (closes #91)
PR #89 stopped script/cibuild replaying cached check layers, but left a
gap: a bare `docker build .` with no --build-arg still faked. An unset
ARG is an empty string, an empty string is a stable cache key, and the
check layers replay from it. That gap mattered because REPO_POLICIES.md
names `docker build .` verbatim as a command that must be green, so the
documented command was the one that lied.

Both check stages now carry `RUN [ -n "$CHECK_EPOCH" ] || exit 1`
immediately under their own ARG. Failed steps are never cached, so this
fails on every invocation rather than once - a bare build now stops with
a named error instead of reporting a green it did not earn. Each stage
needs its own guard because ARG scope is per-stage; a gate-carrying stage
without one is a silent hole if ordering ever changes.

The check RUNs now reference the value (`echo "check epoch: ${CHECK_EPOCH}"
&& make <target>`), so the cache miss is contractual rather than resting
on BuildKit's current treatment of unreferenced ARGs, and the epoch is
visible in the build log.

The epoch becomes "$(date +%s%N)$$" so concurrent invocations in the same
second cannot collide. busybox silently drops %N and exits 0, so $$ is
what makes it correct there. The bare-assignment form is retained
deliberately: inlining the substitution into --build-arg would, under
set -eu, yield an empty and therefore constant epoch without aborting.

script/docker gets the same treatment - it is not the gate, but two
entrypoints disagreeing about whether the tree is green is its own
hazard, and local builds are almost always warm.

Verified by negative control rather than inspection: a bare build fails
twice consecutively here and succeeds twice on the parent commit, so the
change is demonstrably not a no-op. The builder-stage guard was fired
directly with a targeted probe build, since the lint stage otherwise
fails first and would leave it unexercised.
2026-08-09 10:09:27 +02:00

43 lines
2.0 KiB
Bash
Executable File

#!/bin/sh
# script/cibuild: run the CI build. The Dockerfile does not run
# script/check; it runs `make fmt-check` and `make lint` in its lint
# stage and `make test` in its builder stage. A successful build
# implies those three passed, provided they actually ran -- which is
# what the CHECK_EPOCH below is for.
# Generic: needs no adaptation. The Gitea workflow runs this on push.
set -eu
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
main() {
cd "$ROOT"
# The Dockerfile's check layers are keyed on CHECK_EPOCH, so a
# fresh value here is what forces them to re-run: without it an
# unchanged tree replays them from cache, the checks never execute,
# and the build still exits 0. The ARG sits immediately above the
# check RUNs, so dependency and module layers still cache. The
# Dockerfile also refuses to build at all when CHECK_EPOCH is empty,
# so a missing value fails loudly here rather than passing quietly.
#
# The value must be unique per invocation, not per second. `date +%s`
# is second-granular, so two concurrent invocations in the same
# second get identical epochs and the later one can be served from
# cache -- the original defect in miniature. `%N` alone does not fix
# it: busybox silently drops %N, exits 0, and hands back second
# granularity with no warning. `$$` is what makes this correct
# regardless, since concurrent invocations have different pids.
#
# Assign the epoch on its own line rather than inline in the
# argument. Under `set -eu` a command substitution that fails
# inside an argument does NOT abort the script: CHECK_EPOCH would
# become an empty string, an empty string is a constant, and a
# constant CHECK_EPOCH is exactly the cached-check false green this
# script exists to prevent -- so the guard would disarm itself and
# still exit 0. As a bare assignment, `set -e` catches a failing
# `date` and no build starts.
epoch="$(date +%s%N)$$"
docker build --build-arg CHECK_EPOCH="$epoch" .
}
main "$@"