All checks were successful
check / check (push) Successful in 7s
script/cibuild was a plain `docker build .`, and the Dockerfile does `COPY . .` followed by `RUN make check`. Docker invalidates a COPY layer only when the copied content changes, so on an unchanged tree the check layer was served from cache, the suite never ran, and the build still exited 0. Measured here: run 1 took 18.5s and ran the suite; run 2 on a byte-identical tree took 0.286s with `RUN make check` CACHED. script/cibuild and script/docker now assign a per-invocation nonce on its own line and pass it as --build-arg CHECK_EPOCH. The Dockerfile declares ARG CHECK_EPOCH, guards it with `[ -n "$CHECK_EPOCH" ] || exit 1`, and expands it into the check command. Post-fix, two consecutive runs both execute make check (17.4s / 8.1s) with `RUN script/bootstrap` still CACHED, so dependency layers are untouched and the build ceiling is not at risk. The guard is what makes a bare `docker build .` — the command REPO_POLICIES named verbatim — fail closed rather than reuse the empty and therefore stable cache key; verified failing in 0.455s. Holding the epoch constant restores the false green (run 2 fully CACHED), which pins the varying value as the operative mechanism rather than a coincidence. Because the guard references $CHECK_EPOCH it is itself value-keyed: BuildKit renders the epoch into that layer's description and re-runs the layer when the value changes. Each stage therefore has two independent invalidation points, the guard and the expansion, and the guard always precedes the check RUN. Both are kept and the prose now records this; the expansion remains defence in depth and is what puts the epoch in the build log. The false guarantee was org-canonical text in more than one document, so it is corrected everywhere it appeared rather than only where the issue first found it. REPO_POLICIES.md carried it in two places, and its Go multistage template had check steps in two stages; ARG is stage-scoped, so both stages get the treatment or the fleet inherits the half-fixed shape. CODE_STYLEGUIDE_GO.md restated the guarantee for the bare command this change makes fail closed. NEW_REPO_CHECKLIST.md specified the pre-fix script/cibuild verbatim, so every new repo would have been born with the false green, and EXISTING_REPO_CHECKLIST.md ended on a `docker build` acceptance item that the guard makes unsatisfiable by design — an agent working that checklist would have been led to delete the guard to tick the last box. Both checklists' Dockerfile criteria were also satisfiable by a Dockerfile whose check layers are still frozen, and now require the ARG and guard in every check-running stage.
29 lines
1.2 KiB
Docker
29 lines
1.2 KiB
Docker
# node 22-alpine, 2026-02-22
|
|
FROM node@sha256:e4bf2a82ad0a4037d28035ae71529873c069b13eb0455466ae0bc13363826e34
|
|
|
|
WORKDIR /app
|
|
|
|
# script/bootstrap installs all prerequisites (make via apk here; node
|
|
# and yarn are already in the base image, so those steps are skipped).
|
|
# Dependency manifests are copied first so the bootstrap layer is
|
|
# cached until they change.
|
|
COPY script/ script/
|
|
COPY package.json yarn.lock ./
|
|
RUN script/bootstrap
|
|
|
|
COPY . .
|
|
|
|
# CHECK_EPOCH is a per-invocation nonce supplied by script/cibuild and
|
|
# script/docker. Without it an unchanged tree serves this layer from
|
|
# cache and the build reports a green it never ran. ARG is stage-scoped,
|
|
# so it must be redeclared in every stage that runs checks. The guard
|
|
# makes a bare `docker build .` fail loudly instead of silently reusing
|
|
# the empty (and therefore stable) cache key. Expand the value into the
|
|
# command so the cache miss does not depend on BuildKit's handling of an
|
|
# unreferenced ARG. Both the guard and the check RUN reference the value,
|
|
# so both are value-keyed: there are two independent invalidation points
|
|
# here, not one. Keep both.
|
|
ARG CHECK_EPOCH
|
|
RUN [ -n "$CHECK_EPOCH" ] || exit 1
|
|
RUN echo "check epoch: ${CHECK_EPOCH}" && make check
|