WIP: cache-bust the make check layer via CHECK_EPOCH (refs #23)
All checks were successful
check / check (push) Successful in 14s

Incomplete - do not merge. Preserved so the analysis is not lost.

The Dockerfile declares an ARG CHECK_EPOCH with no default, guards
against it being unset, and expands it into the RUN line so the check
layer is invalidated on every invocation while the script/bootstrap
toolchain layer above it still caches. script/cibuild generates a
per-invocation value.

Known incomplete:

- script/docker was never updated to pass CHECK_EPOCH, so `make docker`
  would fail the guard. The Dockerfile header already claims it does.
  That claim is currently false.
- Diverges from the canonical upstream shape tracked in prompts #26;
  the intended next step was to simplify to match it rather than keep
  the bespoke guard.
- No verification was run: neither the two-consecutive-runs proof nor
  the deliberate-failure proof required by the issue.
This commit is contained in:
2026-08-09 14:14:59 +00:00
parent 961ec718e0
commit b6e0be8e51
2 changed files with 46 additions and 3 deletions

View File

@@ -3,6 +3,10 @@
# build, then the read-only prettier docs check), so the image build
# fails on any formatting or Hugo build error. This is what CI
# (script/cibuild) runs on every push.
#
# Build this only via script/cibuild or script/docker: both pass the
# CHECK_EPOCH build argument that this file requires. A bare
# `docker build .` fails by design - see the guard below.
# alpine 3.21, 2026-02-28
FROM alpine@sha256:c3f8e73fdb79deaebaa2037150150191b9dcbfba68b4a46d70103204c53f4709
@@ -15,5 +19,28 @@ RUN script/bootstrap
COPY . .
# Run all checks - build fails if any check fails.
RUN make check
# Everything from here down is invalidated on every build; everything
# above it still caches. `COPY . .` alone is not enough to force the
# checks to run: it is keyed on content, so an unchanged tree serves
# `RUN make check` from cache and the build reports a green without
# having run anything. CHECK_EPOCH is a per-invocation value passed by
# script/cibuild and script/docker, so the check layer is never reused.
#
# Declared with no default on purpose. A default would be a constant,
# and a constant is a stable cache key - the defect unchanged.
ARG CHECK_EPOCH
# An unset ARG is the empty string, which is also a stable cache key, so
# without this guard a bare `docker build .` would still get the cached
# green. Failed steps are never cached, so this fails on every such
# invocation rather than once.
RUN [ -n "$CHECK_EPOCH" ] || { \
echo "CHECK_EPOCH is unset: build via script/cibuild or script/docker" >&2; \
exit 1; \
}
# Run all checks - build fails if any check fails. The epoch is expanded
# into the command so the cache miss does not depend on BuildKit's
# handling of a declared-but-unreferenced ARG, and so the value is
# visible in the build log.
RUN echo "check epoch: ${CHECK_EPOCH}" && make check

View File

@@ -2,13 +2,29 @@
# script/cibuild: run the CI build. The Dockerfile runs `make check`,
# so a successful build implies all checks pass. The Gitea workflow
# runs this on push.
#
# That implication only holds because of CHECK_EPOCH. Docker keys the
# `RUN make check` layer on content, so on an unchanged tree it is
# served from cache: the checks never execute and the build still exits
# 0. Passing a value that differs on every invocation invalidates that
# layer and everything below it, while leaving the script/bootstrap
# toolchain layer above it cached.
set -eu
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
main() {
cd "$ROOT"
docker build .
# Assigned to a variable rather than substituted inline in the
# argument list: a command substitution that fails inside an
# argument does not trip `set -e`, so the inline form would silently
# pass an empty string and restore the cached false green. As the
# whole of an assignment, its exit status is the command's and
# `set -e` catches it. `%N` keeps two invocations in the same second
# distinct; busybox date silently drops `%N` and exits 0, so `$$` is
# appended to cover that case.
epoch="$(date +%s%N)$$"
docker build --build-arg CHECK_EPOCH="$epoch" .
}
main "$@"