Make script/cibuild unable to report an unearned green (closes #85)
All checks were successful
check / check (push) Successful in 3m13s

script/cibuild was a bare `docker build .`. On an unchanged tree Docker
served the check RUN layers from cache, so make fmt-check, make lint and
make test never executed - and the build still exited 0. Measured at
221ms with zero ok lines and every check layer CACHED, against 162s for a
real run. CI showed the same signature: 6 second "successes" on main.

An ARG CHECK_EPOCH now sits immediately above the check RUNs in both
stages - each stage declares its own, since ARG scope is per-stage - and
script/cibuild passes a fresh value per invocation. Dependency and module
layers sit above the ARG and still cache, so this does not make every
build cold.

The epoch is assigned before the build rather than inlined into the
--build-arg. 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, a constant CHECK_EPOCH restores
the cached false green, and the guard would silently disarm itself while
still exiting 0. As a bare assignment, set -e catches a failing date and
no build starts.

The README and Dockerfile state the guarantee conditionally. It holds per
build context and CHECK_EPOCH value, and depends on script/cibuild
passing a fresh one - a bare `docker build .` with no --build-arg still
replays the check layers from the second consecutive run onward. That
residual gap is tracked in #91 along with the remaining upstream
hardening.

Verification is recorded once, in the PR's verification comment, rather
than restated with differing numbers in three places.
This commit was merged in pull request #89.
This commit is contained in:
2026-08-09 09:37:55 +02:00
parent 3bcdbcfd83
commit c3bb3b5580
4 changed files with 97 additions and 9 deletions

25
TODO.md
View File

@@ -19,6 +19,31 @@ or delete the branch.
# Completed Steps
- 2026-08-09: Stopped `script/cibuild` from reporting a green it did
not earn (issue #85). A bare `docker build .` let Docker serve the
check layers from the layer cache whenever the tree had not changed:
the checks never executed and the build still exited 0. The fix is an
`ARG CHECK_EPOCH` declared immediately above the check `RUN`s in both
the lint stage and the builder stage (`ARG` scope is per-stage, so
each declares its own), with `script/cibuild` assigning
`epoch="$(date +%s)"` and passing `--build-arg CHECK_EPOCH="$epoch"`.
The assignment is separate on purpose: under `set -eu` a command
substitution that fails inside an argument does not abort the script,
which would leave an empty constant `CHECK_EPOCH` and restore the
very false green being fixed. Placement is the rest of the point —
the `ARG` sits below the `apk add`, `COPY go.mod go.sum`, and `go mod
download` layers, so only the checks are invalidated and the
dependency layers still cache. The guarantee is conditional on a
fresh value rather than absolute: a bare `docker build .` gets an
empty `CHECK_EPOCH` and can still serve the check layers from cache,
which `README.md` and the `Dockerfile` now say plainly, with issue
#91 tracking the upstream hardening (expanded `ARG` form, unset
guard, per-invocation epoch, `script/docker`) that would close it.
Verified by re-running the reproduction plus the withheld-`--build-arg`
counterfactual; the measurements are recorded once, in the PR #89
verification comment, rather than restated here. `.golangci.yml`, the
lint-stage `FROM` line and its digest, `script/lint`, and
`.gitea/workflows/check.yml` are all untouched.
- 2026-08-09: Corrected the `Vaultik.UI` doc comment (issue #84). It
claimed the cli layer replaces the writer with a discarding one in
`--cron` mode; the actual mechanism is `UI.SetQuiet(true)` in