diff --git a/Dockerfile b/Dockerfile index 267f8ae..1de5c56 100644 --- a/Dockerfile +++ b/Dockerfile @@ -19,7 +19,17 @@ RUN go mod download # Copy source code COPY . . -# Run formatting check and linter +# Run formatting check and linter. +# +# CHECK_EPOCH must stay immediately above these RUNs. script/cibuild +# passes a fresh value on every build so the check layers can never be +# served from the layer cache: without it an unchanged tree replays +# cached layers, the checks never execute, and the build still exits 0: +# a green nothing earned. ARG scope is per-stage, so the builder +# stage declares its own. Everything above this line (apk, go.mod, +# `go mod download`) is deliberately outside the busted range and keeps +# caching. +ARG CHECK_EPOCH RUN make fmt-check RUN make lint @@ -44,7 +54,10 @@ RUN go mod download # Copy source code COPY . . -# Run tests +# Run tests. See the CHECK_EPOCH comment in the lint stage; ARG scope +# is per-stage, so this stage needs its own declaration, and it must +# stay immediately above the check RUN. +ARG CHECK_EPOCH RUN make test # Build (pure Go, no CGO required since we use modernc.org/sqlite) diff --git a/README.md b/README.md index 552d00c..fb18b16 100644 --- a/README.md +++ b/README.md @@ -617,10 +617,16 @@ them. We provide: lint findings. * `script/docker` — build the Docker image tagged via `script/projectname` -* `script/cibuild` — CI entrypoint: `docker build .` (the Dockerfile - runs the checks). This is the full CI-equivalent gate — it runs the - checks in the same containers CI does, from a clean copy of the tree, - so it also catches anything that depends on host state. +* `script/cibuild` — CI entrypoint: `docker build` (the Dockerfile runs + the checks). This is the full CI-equivalent gate — it runs the checks + in the same containers CI does, from a clean copy of the tree, so it + also catches anything that depends on host state. It passes a fresh + `--build-arg CHECK_EPOCH`, which the `Dockerfile` declares + immediately above the check `RUN`s in both the lint and builder + stages, so those layers can never be served from the Docker layer + cache: a green from this script always means the checks actually + executed. Dependency and module layers sit above the `ARG` and still + cache, so a build is not cold. * `script/precommit` — pre-commit gate: `go mod tidy` + `go fmt` (must not change files), then `script/check` * `script/install-precommit` — install the git pre-commit hook that diff --git a/TODO.md b/TODO.md index 16f67b1..992f4f4 100644 --- a/TODO.md +++ b/TODO.md @@ -19,6 +19,29 @@ 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. Reproduced on + this branch's base — a genuine changed-tree run took 162s with 14 + `ok` lines, and the immediately following unchanged-tree run took + 221ms with 0 `ok` lines, 19 cached layers, and the same exit 0, with + `RUN make fmt-check`, `RUN make lint`, and `RUN make test` all + reported `CACHED`. The fix matches the upstream one in + `sneak/prompts` #26: 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` + passing `--build-arg CHECK_EPOCH="$(date +%s)"`. Placement is the + whole 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. Verified by + re-running the reproduction: two back-to-back runs on an unchanged + tree took 167s and 174s, each with 14 `ok` lines and `0 issues.`, no + `CACHED` on any of the three check layers, while every `apk add` and + `go mod download` layer stayed `CACHED` in both. A changed-tree build + went from 162s to 176s, so this is not a cold build. `.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 diff --git a/script/cibuild b/script/cibuild index 3da5857..48fd251 100755 --- a/script/cibuild +++ b/script/cibuild @@ -8,7 +8,12 @@ ROOT="$(cd "$(dirname "$0")/.." && pwd -P)" main() { cd "$ROOT" - docker build . + # CHECK_EPOCH changes on every invocation, which invalidates the + # Dockerfile layers that run the checks. Without it an unchanged + # tree replays those layers 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. + docker build --build-arg CHECK_EPOCH="$(date +%s)" . } main "$@"