Long-lived next branch for the current cycle; one issue-closing commit
per work unit, merged to main when the cycle lands.
Commits so far:
beb865a Run all linting in Docker via Dockerfile.lint
(closes #46) — new root Dockerfile.lint builds the digest-pinned linter image and lints as
a build step; script/lint reduced to building it; the golangci-lint install is gone from script/bootstrap; the main Dockerfile's lint stage invokes the linter directly and its build
stage runs the test and fmt-check gates rather than the aggregate, so
nothing inside an image build shells back out to Docker. script/verify-linter-pin is retired — no linter binary crosses
between stages and bootstrap pins nothing — and replaced by script/verify-lint-image-pin, which fails when the FROM lines in Dockerfile and Dockerfile.lint disagree.
Long-lived `next` branch for the current cycle; one issue-closing commit
per work unit, merged to `main` when the cycle lands.
Commits so far:
- `beb865a` Run all linting in Docker via `Dockerfile.lint`
(closes https://git.eeqj.de/sneak/sfdupes/issues/46) — new root
`Dockerfile.lint` builds the digest-pinned linter image and lints as
a build step; `script/lint` reduced to building it; the
`golangci-lint` install is gone from `script/bootstrap`; the main
`Dockerfile`'s lint stage invokes the linter directly and its build
stage runs the test and fmt-check gates rather than the aggregate, so
nothing inside an image build shells back out to Docker.
`script/verify-linter-pin` is retired — no linter binary crosses
between stages and bootstrap pins nothing — and replaced by
`script/verify-lint-image-pin`, which fails when the `FROM` lines in
`Dockerfile` and `Dockerfile.lint` disagree.
Per the owner ruling, the linter runs inside a container invoked
through the script/ entrypoint and is never installed on a host. A new
root Dockerfile.lint COPYs the repo into the digest-pinned
golangci/golangci-lint:v2.12.2 image and runs `golangci-lint config
verify` and `golangci-lint run` as build steps, so a successful build
IS a clean lint. script/lint is reduced to building it, which also
works when the docker daemon is remote and bind mounts are impossible.
script/bootstrap loses the `go install`, the pin constants, the
version parser and verify_golangci_lint outright rather than being
hardened: with nothing linting on the host, the $GOPATH/bin versus
PATH shadowing problem those existed to diagnose has no subject. It
keeps the git/make/go presence checks and `go mod download`, and warns
rather than fails when docker is absent.
Two traps.
A lint build on an unchanged tree returns success in well under a
second having run no linter, which is #32 and #39 over again. Caching
is waived by ruling, so Dockerfile.lint carries ARG CHECK_EPOCH
referenced inside every gate RUN -- BuildKit hashes the expanded
command, not the declaration, so a declared but unreferenced ARG
invalidates nothing -- and script/lint passes "$(date +%s)-$$". The
PID is in that value because two lint runs land inside the same second
easily and a bare epoch would cache the second one.
Nothing inside an image build may shell out to docker. The main
Dockerfile's lint stage therefore invokes golangci-lint directly
instead of `make lint`, and its build stage runs `make test` and
`make fmt-check` instead of the `make check` aggregate, which reaches
script/lint. Those two remain `make` invocations rather than the bare
scripts because the Makefile's `export CGO_ENABLED = 0` only applies
to what it invokes, and today's `make check` gets it.
COPY --from=lint /usr/bin/golangci-lint is replaced by
COPY --from=lint /src/go.sum /dev/null. The copied binary was the only
edge forcing BuildKit to finish linting before the build stage starts;
dropping it without replacing the edge would have ended fail-fast
linting silently under a still-green build. That no-op copy is the
ordering edge canonical REPO_POLICIES.md prescribes. Nothing in the
build stage runs the linter any more, so the binary itself is not
wanted there, and ENV PATH=/home/builder/go/bin:$PATH goes with the
`go install` that justified it.
script/verify-linter-pin is retired -- deleted along with its README
entry -- because both of its subjects ceased to exist in this same
change: it compared a linter binary against GOLANGCI_LINT_VERSION in
script/bootstrap, and there is now neither a binary crossing between
stages nor a version pin in bootstrap. The drift it guarded has not
gone away, it has moved. The linter is still pinned twice, now as the
FROM line of Dockerfile.lint and the FROM line of the Dockerfile lint
stage, with nothing syncing them, which is exactly what #42 made a
build failure. Its replacement is one new script/verify-lint-image-pin
that compares those two references to each other and deliberately
restates neither pin: a hardcoded expected digest would be a third
copy and the same drift one file further out. It runs as a gate in
both files, so `make lint`, `make check` and `make docker` all catch
drift, and an unreadable reference is a hard failure rather than a
vacuous pass.
`golangci-lint config verify` is included per the ruling. The concern
about its unpinned live HTTPS schema fetch was measured rather than
assumed: under --network none the pinned binary both passes a valid
config and rejects an invalid one with the jsonschema error, so it
validates against a schema it embeds and linting needs no network
beyond pulling the image. The README states that rather than a
requirement that does not exist.
Verified. `make lint` green with every PATH directory containing a
golangci-lint removed and `command -v golangci-lint` empty. Two
consecutive script/lint runs on an untouched tree both executed the
linter, 27.7s and 28.7s in the lint step under distinct epochs with
the COPY layer CACHED above them. A planted unused variable failed
script/lint with that exact finding and failed `make docker` at the
lint stage with the build stage stopped before its COPY --from=lint,
then reverted clean. The drift guard fails on tag-only, digest-only
and unreadable-reference cases, naming both sides. `make check` green;
`make docker` green in 5m35s with all six gates executing and the test
gate reporting real coverage rather than a cached ok. In the builder
image with the Go test cache off, --user 0:0 still fails
TestScanHardlinkRunFailsTogether where the unprivileged user passes,
so the non-root quirk is intact.
Adversarial review of #47 at beb865a, against #46 and the binding spec at #46 (comment).
Reviewed in an independent clone; nothing pushed, nothing changed.
Verdict: FAIL — needs-rework
The mechanism is sound and every load-bearing claim in the evidence
comment reproduced. Three blocking findings, all documentation
accuracy, all one-line fixes.
Findings
1. README.md §Entrypoints, script/lint: "nothing here needs the network" is false (blocking, minor)
The entry states: "Once the pinned image is local, nothing here needs
the network". A cold BuildKit cache reaches Dockerfile.lint:19 RUN go mod download, and the module has 14 external requirements.
Proved with a build scoped to that one image
(docker build --no-cache --network none -f Dockerfile.lint .):
#8 [4/8] RUN go mod download
#8 0.067 go: github.com/dustin/go-humanize@v1.0.1: Get "https://proxy.golang.org/...": dial tcp: ... network is unreachable
ERROR: failed to build: failed to solve: process "/bin/sh -c go mod download" did not complete successfully: exit code: 1
The evidence comment on #46 already says the same
thing in passing ("it re-runs and fails at go mod download before
reaching the gate"), so the README overstates a result the author
measured correctly. Why it matters: this is the entry a developer
reads to decide whether an air-gapped or offline machine can run make check, and it will send them to the wrong conclusion on a first
build. Acceptable: scope the claim to the gates — the linter and config verify need no network; a first build still downloads
modules.
2. script/cibuild:2-4 header describes gates this change removed (blocking, minor)
# script/cibuild: run the CI build. The Dockerfile runs make fmt-check,
# make lint and make check (via the script/ entrypoints) as build steps,
# so a successful build implies all checks pass.
After this commit the Dockerfile runs neither make lint (the lint
stage invokes golangci-lint directly) nor make check (the build
stage runs make test and make fmt-check). This is the same defect
class the spec required fixing in script/bootstrap's header —
"update the header comment so it no longer describes an install that
does not happen" — and it is worse here, because the file is the CI
entrypoint and the comment is a reader's basis for believing CI gates
on lint at all. Acceptable: restate the actual gates.
3. script/docker:7-8 refers to a make check that is gone (blocking, minor)
"...this exits 0 having run neither the lint stage nor the builder
stage's make check." The builder stage no longer runs make check.
Same fix.
Disclosures and non-blocking observations
Canonical REPO_POLICIES.md:92 ("All Dockerfiles must run make check as a build step") and its worked example at line 123
(RUN make lint in the lint stage) are violated by both
Dockerfiles. The owner ruling and the spec on #46 make this unavoidable,
and the author disclosed it on the issue rather than editing the
vendored policy. Not held against the change; flagged so the
divergence stays on the record.
Cold-cache cost, reported rather than treated as a defect: a build
with the builder stage forced uncached
(--no-cache-filter=builder) took real 5m37.788s here, matching
the author's 5m35s and exceeding REPO_POLICIES.md:231's
five-minute limit. It is not a regression — before this change lint
ran twice per image build, and now it runs once. Warm build: real 1m16.243s; CI reports 1m5s.
script/lint builds with neither -t nor --output=type=cacheonly, so each run exports a full image
(15-21s of the 33-70s wall clock observed) and leaves a dangling
image behind. On a machine where make check and every pre-commit
run this, that accumulates. Cosmetic, not a gate defect.
The make test versus script/test justification (Makefile export CGO_ENABLED = 0) holds mechanically — probed inside the
builder image, a make recipe child sees make-env-CGO_ENABLED=[0] while a bare shell sees empty — but note
the practical difference is nil in this image: go env CGO_ENABLED
is already 0 in the golang:1.25-alpine base. The reasoning is
still correct and the choice is fine.
.dockerignore:2 and .gitignore:36 name agent tool directories.
Both pre-date this PR and are unchanged by it.
Could not read the CI job log: the Actions API returns 403 for this
account. CI green is taken from the commit status on beb865a
(check / check (push), success, 1m5s). The epoch mechanism I
verified locally is what makes that green mean anything.
Verified by execution
Cache-buster is load-bearing.CHECK_EPOCH is referenced in the
expanded command of every gate RUN in both files and both stages,
with the required second per-stage ARG in the builder stage
(Dockerfile:107, after USER builder). Two consecutive script/lint runs on an untouched tree both executed the linter — #12 ... 20.04 0 issues. / #12 DONE 25.1s (33.5s wall) and, with #9 [5/8] COPY . . CACHED above it, #12 ... 36.72 0 issues. / #12 DONE 38.1s (49.7s wall), under
distinct epochs. Adversarial control the author did not run:
holding the epoch constant across two builds
(--build-arg CHECK_EPOCH=review47-fixed) brought the false green
straight back — second build #12 [8/8] RUN echo "gate lint, epoch review47-fixed" ... CACHED, real 0m0.336s. The buster is doing
the work, not incidental.
Ordering edge is live. Planted var reviewPR47OrderingSentinel = 1 in report.go: make docker
failed at #17 [lint 9/9] with report.go:172:5: var reviewPR47OrderingSentinel is unused (unused)
in 44.9s; the builder stage reached only [builder 1/12] FROM docker.io/library/golang@sha256:56961d79...,
with COPY --from=lint, script/bootstrap, gate test and make build at 0 occurrences in the log. Since BuildKit does not
build unreferenced stages, the lint stage running at all proves the COPY --from=lint /src/go.sum /dev/null edge exists. Reverted;
working tree clean.
Non-root quirk intact, Go test cache off (-count=1, plus GOFLAGS=-count=1): --user 0:0 gives --- FAIL: TestScanHardlinkRunFailsTogether (0.01s) / scan_test.go:817: stats = {added:2 ...}, want both hardlink paths skipped; default uid 1000 gives ok sneak.berlin/go/sfdupes 0.012s.
Drift guard, 12 cases run directly: baseline passes; tag-only,
digest-only, missing FROM (found 0, not a vacuous empty-string
pass), duplicate golangci FROM (found 2), unpinned FROM golangci/golangci-lint, registry-qualified docker.io/golangci/..., and a missing Dockerfile all exit 1 with
a named diagnosis; lowercase from, extra whitespace, FROM --platform=..., and a # FROM golangci/... comment line are
all handled correctly. It runs as a gate in both files, so script/lint, make check, make docker and script/cibuild all
reach it — confirmed in the build logs of each.
Host linter genuinely gone. No host golangci-lint invocation
or install remains anywhere in script/, Makefile or .gitea/. Masking method: every PATH entry containing an
executable golangci-lint removed
(/home/user/go/bin, /home/user/.local/bin, /usr/local/bin), command -v golangci-lint and which -a golangci-lint both empty, docker/go/make still resolving. Under that PATH, make lint exit 0 (#12 37.91 0 issues., real 1m9.502s) and make check exit 0 (ok sneak.berlin/go/sfdupes 1.117s coverage: 88.5% of statements, #12 25.60 0 issues., real 0m41.957s).
No nested Docker: nothing invoked inside either image build
calls docker, make lint or make check; script/bootstrap only
warns. Probed uncached rather than trusting a CACHED line — --no-cache-filter=builder re-executed #21 [builder 7/12] RUN script/bootstrap after the COPY --from=lint /src/go.sum /dev/null clobber and it behaved: bootstrap: WARNING: docker not found; ... then bootstrap complete. The >/dev/null redirections survive the
clobber because the runtime mounts /dev per RUN, as claimed.
config verify offline claim confirmed on the pinned image
under --network none: valid config exit 0; a config with bogus-review47-key still rejected, jsonschema: "linters" does not validate ... additional properties 'bogus-review47-key' not allowed, exit 3. The author's deviation
from the ruling's "state the network requirement" is correct: there
is none for that step.
make docker green end to end, real 1m16.243s, every gate
executing under one epoch and the test gate reporting ok sneak.berlin/go/sfdupes 1.544s coverage: 88.5% of statements
rather than (cached).
Spec conformance otherwise satisfied: Dockerfile.lint shape and
pin comment, script/lint reduced to the build with "$(date +%s)-$$", script/bootstrap stripped to the presence
checks plus go mod download with a docker warning, ENV PATH=/home/builder/go/bin:$PATH removed and re-justified in
comment, chown plus USER builder still before the gates, script/verify-linter-pin deleted with its README entry and the
reasoning recorded in both the commit message and the top of TODO.md's Completed Steps.
CI success on beb865a; branch is a fast-forward of main, no
conflicts; commit subject carries (closes #46); no attribution
trailers and no vendor references anywhere in the diff or commit
message; inclusive terminology clean; make fmt-check clean; .golangci.yml untouched; no scope creep.
Adversarial review of
https://git.eeqj.de/sneak/sfdupes/pulls/47 at `beb865a`, against
https://git.eeqj.de/sneak/sfdupes/issues/46 and the binding spec at
https://git.eeqj.de/sneak/sfdupes/issues/46#issuecomment-53177.
Reviewed in an independent clone; nothing pushed, nothing changed.
## Verdict: FAIL — `needs-rework`
The mechanism is sound and every load-bearing claim in the evidence
comment reproduced. Three blocking findings, all documentation
accuracy, all one-line fixes.
## Findings
### 1. `README.md` §Entrypoints, `script/lint`: "nothing here needs the network" is false (blocking, minor)
The entry states: "Once the pinned image is local, nothing here needs
the network". A cold BuildKit cache reaches `Dockerfile.lint:19`
`RUN go mod download`, and the module has 14 external requirements.
Proved with a build scoped to that one image
(`docker build --no-cache --network none -f Dockerfile.lint .`):
```
#8 [4/8] RUN go mod download
#8 0.067 go: github.com/dustin/go-humanize@v1.0.1: Get "https://proxy.golang.org/...": dial tcp: ... network is unreachable
ERROR: failed to build: failed to solve: process "/bin/sh -c go mod download" did not complete successfully: exit code: 1
```
The evidence comment on
https://git.eeqj.de/sneak/sfdupes/issues/46 already says the same
thing in passing ("it re-runs and fails at `go mod download` before
reaching the gate"), so the README overstates a result the author
measured correctly. Why it matters: this is the entry a developer
reads to decide whether an air-gapped or offline machine can run
`make check`, and it will send them to the wrong conclusion on a first
build. Acceptable: scope the claim to the gates — the linter and
`config verify` need no network; a first build still downloads
modules.
### 2. `script/cibuild:2-4` header describes gates this change removed (blocking, minor)
```
# script/cibuild: run the CI build. The Dockerfile runs make fmt-check,
# make lint and make check (via the script/ entrypoints) as build steps,
# so a successful build implies all checks pass.
```
After this commit the `Dockerfile` runs neither `make lint` (the lint
stage invokes `golangci-lint` directly) nor `make check` (the build
stage runs `make test` and `make fmt-check`). This is the same defect
class the spec required fixing in `script/bootstrap`'s header —
"update the header comment so it no longer describes an install that
does not happen" — and it is worse here, because the file is the CI
entrypoint and the comment is a reader's basis for believing CI gates
on lint at all. Acceptable: restate the actual gates.
### 3. `script/docker:7-8` refers to a `make check` that is gone (blocking, minor)
"...this exits 0 having run neither the lint stage nor the builder
stage's make check." The builder stage no longer runs `make check`.
Same fix.
## Disclosures and non-blocking observations
- Canonical `REPO_POLICIES.md:92` ("All Dockerfiles must run
`make check` as a build step") and its worked example at line 123
(`RUN make lint` in the lint stage) are violated by both
Dockerfiles. The owner ruling and the spec on
https://git.eeqj.de/sneak/sfdupes/issues/46 make this unavoidable,
and the author disclosed it on the issue rather than editing the
vendored policy. Not held against the change; flagged so the
divergence stays on the record.
- Cold-cache cost, reported rather than treated as a defect: a build
with the builder stage forced uncached
(`--no-cache-filter=builder`) took `real 5m37.788s` here, matching
the author's 5m35s and exceeding `REPO_POLICIES.md:231`'s
five-minute limit. It is not a regression — before this change lint
ran twice per image build, and now it runs once. Warm build:
`real 1m16.243s`; CI reports 1m5s.
- `script/lint` builds with neither `-t` nor
`--output=type=cacheonly`, so each run exports a full image
(15-21s of the 33-70s wall clock observed) and leaves a dangling
image behind. On a machine where `make check` and every pre-commit
run this, that accumulates. Cosmetic, not a gate defect.
- The `make test` versus `script/test` justification (Makefile
`export CGO_ENABLED = 0`) holds mechanically — probed inside the
builder image, a make recipe child sees
`make-env-CGO_ENABLED=[0]` while a bare shell sees empty — but note
the practical difference is nil in this image: `go env CGO_ENABLED`
is already `0` in the `golang:1.25-alpine` base. The reasoning is
still correct and the choice is fine.
- `.dockerignore:2` and `.gitignore:36` name agent tool directories.
Both pre-date this PR and are unchanged by it.
- Could not read the CI job log: the Actions API returns 403 for this
account. CI green is taken from the commit status on `beb865a`
(`check / check (push)`, `success`, 1m5s). The epoch mechanism I
verified locally is what makes that green mean anything.
## Verified by execution
- **Cache-buster is load-bearing.** `CHECK_EPOCH` is referenced in the
expanded command of every gate `RUN` in both files and both stages,
with the required second per-stage `ARG` in the builder stage
(`Dockerfile:107`, after `USER builder`). Two consecutive
`script/lint` runs on an untouched tree both executed the linter —
`#12 ... 20.04 0 issues. / #12 DONE 25.1s` (33.5s wall) and, with
`#9 [5/8] COPY . . CACHED` above it,
`#12 ... 36.72 0 issues. / #12 DONE 38.1s` (49.7s wall), under
distinct epochs. Adversarial control the author did not run:
holding the epoch constant across two builds
(`--build-arg CHECK_EPOCH=review47-fixed`) brought the false green
straight back — second build `#12 [8/8] RUN echo "gate lint, epoch
review47-fixed" ... CACHED`, `real 0m0.336s`. The buster is doing
the work, not incidental.
- **Ordering edge is live.** Planted
`var reviewPR47OrderingSentinel = 1` in `report.go`: `make docker`
failed at `#17 [lint 9/9]` with
`report.go:172:5: var reviewPR47OrderingSentinel is unused (unused)`
in 44.9s; the builder stage reached only
`[builder 1/12] FROM docker.io/library/golang@sha256:56961d79...`,
with `COPY --from=lint`, `script/bootstrap`, `gate test` and
`make build` at 0 occurrences in the log. Since BuildKit does not
build unreferenced stages, the lint stage running at all proves the
`COPY --from=lint /src/go.sum /dev/null` edge exists. Reverted;
working tree clean.
- **Non-root quirk intact**, Go test cache off (`-count=1`, plus
`GOFLAGS=-count=1`): `--user 0:0` gives
`--- FAIL: TestScanHardlinkRunFailsTogether (0.01s)` /
`scan_test.go:817: stats = {added:2 ...}, want both hardlink paths
skipped`; default uid 1000 gives `ok sneak.berlin/go/sfdupes
0.012s`.
- **Drift guard**, 12 cases run directly: baseline passes; tag-only,
digest-only, missing `FROM` (`found 0`, not a vacuous empty-string
pass), duplicate golangci `FROM` (`found 2`), unpinned
`FROM golangci/golangci-lint`, registry-qualified
`docker.io/golangci/...`, and a missing `Dockerfile` all exit 1 with
a named diagnosis; lowercase `from`, extra whitespace,
`FROM --platform=...`, and a `# FROM golangci/...` comment line are
all handled correctly. It runs as a gate in both files, so
`script/lint`, `make check`, `make docker` and `script/cibuild` all
reach it — confirmed in the build logs of each.
- **Host linter genuinely gone.** No host `golangci-lint` invocation
or install remains anywhere in `script/`, `Makefile` or
`.gitea/`. Masking method: every `PATH` entry containing an
executable `golangci-lint` removed
(`/home/user/go/bin`, `/home/user/.local/bin`, `/usr/local/bin`),
`command -v golangci-lint` and `which -a golangci-lint` both empty,
`docker`/`go`/`make` still resolving. Under that `PATH`,
`make lint` exit 0 (`#12 37.91 0 issues.`, `real 1m9.502s`) and
`make check` exit 0 (`ok sneak.berlin/go/sfdupes 1.117s coverage:
88.5% of statements`, `#12 25.60 0 issues.`, `real 0m41.957s`).
- **No nested Docker**: nothing invoked inside either image build
calls `docker`, `make lint` or `make check`; `script/bootstrap` only
warns. Probed uncached rather than trusting a `CACHED` line —
`--no-cache-filter=builder` re-executed
`#21 [builder 7/12] RUN script/bootstrap` after the
`COPY --from=lint /src/go.sum /dev/null` clobber and it behaved:
`bootstrap: WARNING: docker not found; ...` then
`bootstrap complete`. The `>/dev/null` redirections survive the
clobber because the runtime mounts `/dev` per `RUN`, as claimed.
- **`config verify` offline claim confirmed** on the pinned image
under `--network none`: valid config exit 0; a config with
`bogus-review47-key` still rejected,
`jsonschema: "linters" does not validate ... additional properties
'bogus-review47-key' not allowed`, exit 3. The author's deviation
from the ruling's "state the network requirement" is correct: there
is none for that step.
- `make docker` green end to end, `real 1m16.243s`, every gate
executing under one epoch and the test gate reporting
`ok sneak.berlin/go/sfdupes 1.544s coverage: 88.5% of statements`
rather than `(cached)`.
- Spec conformance otherwise satisfied: `Dockerfile.lint` shape and
pin comment, `script/lint` reduced to the build with
`"$(date +%s)-$$"`, `script/bootstrap` stripped to the presence
checks plus `go mod download` with a docker warning,
`ENV PATH=/home/builder/go/bin:$PATH` removed and re-justified in
comment, `chown` plus `USER builder` still before the gates,
`script/verify-linter-pin` deleted with its README entry and the
reasoning recorded in both the commit message and the top of
`TODO.md`'s Completed Steps.
- CI `success` on `beb865a`; branch is a fast-forward of `main`, no
conflicts; commit subject carries `(closes #46)`; no attribution
trailers and no vendor references anywhere in the diff or commit
message; inclusive terminology clean; `make fmt-check` clean;
`.golangci.yml` untouched; no scope creep.
Manager: review FAILed on three documentation-accuracy blockers, no
code change required. needs-rework; rework dispatched, fresh reviewer
after. The mechanism itself reproduced independently, including the
constant-epoch control the author had not run.
Manager: review FAILed on three documentation-accuracy blockers, no
code change required. `needs-rework`; rework dispatched, fresh reviewer
after. The mechanism itself reproduced independently, including the
constant-epoch control the author had not run.
Documentation only — no behaviour change. The Dockerfiles, the drift
guard script/verify-lint-image-pin, and script/lint's docker build
invocation are untouched. Amended into the single commit, subject
unchanged: beb865a -> 215539c, still Run all linting in Docker via Dockerfile.lint (closes #46)
(#46). Five files, +45/-21.
Accepted; the claim was wrong. "Once the pinned image is local, nothing
here needs the network" is replaced with an entry that separates the
gates from the build around them, because that is where the truth
divides:
the gates make no network calls — neither golangci-lint run nor config verify, the latter validating against a schema the pinned
binary embeds, still cited as measured under --network none to
both pass a valid config and reject an invalid one (the part of the
old text that was true and worth keeping);
Dockerfile.lint runs go mod download above them and this module
has external dependencies, so a first lint on a cold BuildKit cache
reaches the network there, and under --network none fails at that
step before any gate — exactly what the review reproduced;
that layer sits above the gates and stays cached, so once warm script/lint, and with it make check, runs entirely offline
until go.mod or go.sum changes and the layer goes cold again.
A developer reading the entry to decide whether an offline machine can
run make check now gets the correct answer in both directions: yes on
a warm cache, no on a first build.
Finding 2 — script/cibuild header
Accepted. The header no longer claims the Dockerfile runs make fmt-check, make lint and make check. It now names what actually
runs — lint stage: make fmt-check, script/verify-lint-image-pin, golangci-lint config verify, golangci-lint run; build stage,
dropped to the unprivileged user: make test and make fmt-check —
states why neither aggregate appears (both reach script/lint, which
is itself a docker build, and a docker build cannot run inside one),
and states why lint is nonetheless gated: the linter is invoked
directly in the lint stage and the build stage's COPY --from=lint makes that stage a prerequisite. The concluding
sentence — that between the two stages everything make check would
run has run — is now earned rather than asserted. The CHECK_EPOCH
paragraph below it is unchanged.
Finding 3 — script/docker header
Accepted. "neither the lint stage nor the builder stage's make check"
becomes "neither the lint stage's gates nor the builder stage's test
and fmt-check gates".
Same defect class, swept
Two more instances of the same inaccuracies, neither called out
individually in the review:
script/lint's own header carried finding 1's false claim verbatim
("once the pinned image is present nothing here reaches the
network"). Corrected the same way. The docker build invocation
below it is untouched.
TODO.md's Completed Steps entry concluded "linting needs no
network" from the config verify measurement — the same
overgeneralisation from a true premise. Now scoped to the step, with
the go mod download caveat recorded.
Disclosed, deliberately not fixed
Dockerfile.lint:49-50 says "Linting therefore needs no network beyond
pulling the pinned image." That is the identical overstatement as
finding 1, in the very file whose line 19 go mod download disproves
it. I was instructed not to touch the Dockerfiles in this rework, so I
have left it and am flagging it rather than deviating silently. It is a
comment-only, one-sentence fix and should be authorised as a follow-up.
Verification
make check green, exit 0, real 0m39.995s under BUILDKIT_PROGRESS=plain. The linter genuinely executed rather
than being served from cache:
The pin guard and config verify likewise ran under the same epoch
(#10 DONE 0.2s, #11 DONE 3.3s). Only three layers reported CACHED — the .dockerignore load, COPY go.mod go.sum and RUN go mod download, i.e. exactly the dependency layers above ARG CHECK_EPOCH that are designed to stay warm.
Tests re-run with the Go test cache defeated, since make check's
second invocation reported (cached) on an unchanged Go tree: ok sneak.berlin/go/sfdupes 0.995s coverage: 88.5% of statements.
No Go file is touched by this rework.
make fmt run; a no-op, as script/fmt is gofmt -s -w and no Go
source changed. Markdown is not formatted by this repo's tooling,
so the prose was rewrapped by hand to the files' existing width.
Not re-run, and stated rather than implied: make docker and script/cibuild. This rework changes no Dockerfile, no build step
and no Go code — only comments in three script/ files and two
markdown files — so the image build is unaffected. CI will run script/cibuild on the pushed commit.
git fetch origin immediately before pushing confirmed origin/next was still at beb865a; the force-push used --force-with-lease pinned to that SHA.
Rework of the FAIL review at
https://git.eeqj.de/sneak/sfdupes/pulls/47#issuecomment-53806.
Documentation only — no behaviour change. The Dockerfiles, the drift
guard `script/verify-lint-image-pin`, and `script/lint`'s `docker build`
invocation are untouched. Amended into the single commit, subject
unchanged: `beb865a` -> **`215539c`**, still
`Run all linting in Docker via Dockerfile.lint (closes #46)`
(https://git.eeqj.de/sneak/sfdupes/issues/46). Five files, +45/-21.
## Finding 1 — `README.md` §Entrypoints, `script/lint`: false offline claim
Accepted; the claim was wrong. "Once the pinned image is local, nothing
here needs the network" is replaced with an entry that separates the
gates from the build around them, because that is where the truth
divides:
- the gates make no network calls — neither `golangci-lint run` nor
`config verify`, the latter validating against a schema the pinned
binary embeds, still cited as measured under `--network none` to
both pass a valid config and reject an invalid one (the part of the
old text that was true and worth keeping);
- `Dockerfile.lint` runs `go mod download` above them and this module
has external dependencies, so a first lint on a cold BuildKit cache
reaches the network there, and under `--network none` fails at that
step before any gate — exactly what the review reproduced;
- that layer sits above the gates and stays cached, so once warm
`script/lint`, and with it `make check`, runs entirely offline
until `go.mod` or `go.sum` changes and the layer goes cold again.
A developer reading the entry to decide whether an offline machine can
run `make check` now gets the correct answer in both directions: yes on
a warm cache, no on a first build.
## Finding 2 — `script/cibuild` header
Accepted. The header no longer claims the Dockerfile runs `make
fmt-check`, `make lint` and `make check`. It now names what actually
runs — lint stage: `make fmt-check`, `script/verify-lint-image-pin`,
`golangci-lint config verify`, `golangci-lint run`; build stage,
dropped to the unprivileged user: `make test` and `make fmt-check` —
states why neither aggregate appears (both reach `script/lint`, which
is itself a docker build, and a docker build cannot run inside one),
and states why lint is nonetheless gated: the linter is invoked
directly in the lint stage and the build stage's
`COPY --from=lint` makes that stage a prerequisite. The concluding
sentence — that between the two stages everything `make check` would
run has run — is now earned rather than asserted. The `CHECK_EPOCH`
paragraph below it is unchanged.
## Finding 3 — `script/docker` header
Accepted. "neither the lint stage nor the builder stage's make check"
becomes "neither the lint stage's gates nor the builder stage's test
and fmt-check gates".
## Same defect class, swept
Two more instances of the same inaccuracies, neither called out
individually in the review:
- `script/lint`'s own header carried finding 1's false claim verbatim
("once the pinned image is present nothing here reaches the
network"). Corrected the same way. The `docker build` invocation
below it is untouched.
- `TODO.md`'s Completed Steps entry concluded "linting needs no
network" from the `config verify` measurement — the same
overgeneralisation from a true premise. Now scoped to the step, with
the `go mod download` caveat recorded.
## Disclosed, deliberately not fixed
`Dockerfile.lint:49-50` says "Linting therefore needs no network beyond
pulling the pinned image." That is the identical overstatement as
finding 1, in the very file whose line 19 `go mod download` disproves
it. I was instructed not to touch the Dockerfiles in this rework, so I
have left it and am flagging it rather than deviating silently. It is a
comment-only, one-sentence fix and should be authorised as a follow-up.
## Verification
- `make check` green, exit 0, `real 0m39.995s` under
`BUILDKIT_PROGRESS=plain`. The linter genuinely executed rather
than being served from cache:
```
#12 [8/8] RUN echo "gate lint, epoch 1786368322-3261701" && golangci-lint run --config .golangci.yml ./...
#12 0.155 gate lint, epoch 1786368322-3261701
#12 22.34 0 issues.
#12 DONE 26.8s
```
The pin guard and config verify likewise ran under the same epoch
(`#10 DONE 0.2s`, `#11 DONE 3.3s`). Only three layers reported
`CACHED` — the `.dockerignore` load, `COPY go.mod go.sum` and
`RUN go mod download`, i.e. exactly the dependency layers above
`ARG CHECK_EPOCH` that are designed to stay warm.
- Tests re-run with the Go test cache defeated, since `make check`'s
second invocation reported `(cached)` on an unchanged Go tree:
`ok sneak.berlin/go/sfdupes 0.995s coverage: 88.5% of statements`.
No Go file is touched by this rework.
- `make fmt` run; a no-op, as `script/fmt` is `gofmt -s -w` and no Go
source changed. Markdown is not formatted by this repo's tooling,
so the prose was rewrapped by hand to the files' existing width.
- Not re-run, and stated rather than implied: `make docker` and
`script/cibuild`. This rework changes no Dockerfile, no build step
and no Go code — only comments in three `script/` files and two
markdown files — so the image build is unaffected. CI will run
`script/cibuild` on the pushed commit.
- `git fetch origin` immediately before pushing confirmed
`origin/next` was still at `beb865a`; the force-push used
`--force-with-lease` pinned to that SHA.
Follow-up to #47 (comment): the
deferral disclosed there is now closed, not carried. Authorised as
comment-text-only.
New SHA: 73ea8a5 (was 215539c, originally beb865a). Still
exactly one commit on the branch, subject byte-identical and still Run all linting in Docker via Dockerfile.lint (closes #46)
(#46). Total against beb865a
is now 6 files, +51/-23 — all comments and prose, no behaviour change
anywhere in the unit.
The fourth line
Dockerfile.lint:49-50 carried the same overstatement as blocking
finding 1, in the file whose line 19 go mod download disproves it. It
now matches the wording landed in README.md and script/lint:
# `--network none` this both passes on a valid config and still rejects
-# an invalid one with the jsonschema error. Linting therefore needs no
-# network beyond pulling the pinned image.
+# an invalid one with the jsonschema error. That holds for the gate
+# steps generally — none of them makes a network call — but not for
+# this build as a whole: `go mod download` above needs the network on a
+# cold cache, and under `--network none` a first build fails there
+# before reaching any gate. That layer stays cached, so only a warm
+# cache lints offline, until go.mod or go.sum changes.
RUN echo "gate config verify, epoch ${CHECK_EPOCH}" && \
golangci-lint config verify --config .golangci.yml
Every changed line begins with #, checked mechanically rather than by
eye: filtering the diff's +/- lines for any not starting with #
returns nothing. No FROM, no RUN, no ARG, no reordering. The config verify reasoning that was true — the schema is embedded, so the
step needs no network — is kept; only the conclusion drawn from it is
narrowed.
That completes the sweep. Grepping the tree for network and offline
claims and for descriptions of the pre-change lint arrangement now
returns no inaccurate line. Dockerfile:42 was checked again and is
correct as written: it scopes "needs no network" to the config verify
step, not to the build.
Verification after the change
make check green, exit 0, real 0m46.180s, BUILDKIT_PROGRESS=plain.
The gates ran rather than being served from cache, under a fresh epoch:
Tests re-run with the Go test cache defeated, since make check
reported (cached) on an unchanged Go tree: ok sneak.berlin/go/sfdupes 2.657s coverage: 88.5% of statements.
Cache behaviour, stated precisely rather than claimed: the three
dependency layers above ARG CHECK_EPOCH — WORKDIR, COPY go.mod go.sum and RUN go mod download — all reported CACHED,
so editing a comment in this file cost nothing above the gates, which is
the property that matters. COPY . . re-ran (4.3s) because the edited
file is part of the build context, which is expected and unrelated. This
run does not isolate the narrower claim that BuildKit strips comments
before hashing a RUN: the gates were going to re-execute regardless,
because script/lint passes a fresh CHECK_EPOCH every time. So the
prediction is unconfirmed by this evidence rather than confirmed by it,
and nothing here depends on it.
git fetch origin immediately before pushing confirmed origin/next was
still at 215539c; the force-push used --force-with-lease pinned to
that SHA.
Follow-up to
https://git.eeqj.de/sneak/sfdupes/pulls/47#issuecomment-53892: the
deferral disclosed there is now closed, not carried. Authorised as
comment-text-only.
**New SHA: `73ea8a5`** (was `215539c`, originally `beb865a`). Still
exactly one commit on the branch, subject byte-identical and still
`Run all linting in Docker via Dockerfile.lint (closes #46)`
(https://git.eeqj.de/sneak/sfdupes/issues/46). Total against `beb865a`
is now 6 files, +51/-23 — all comments and prose, no behaviour change
anywhere in the unit.
## The fourth line
`Dockerfile.lint:49-50` carried the same overstatement as blocking
finding 1, in the file whose line 19 `go mod download` disproves it. It
now matches the wording landed in `README.md` and `script/lint`:
```diff
# `--network none` this both passes on a valid config and still rejects
-# an invalid one with the jsonschema error. Linting therefore needs no
-# network beyond pulling the pinned image.
+# an invalid one with the jsonschema error. That holds for the gate
+# steps generally — none of them makes a network call — but not for
+# this build as a whole: `go mod download` above needs the network on a
+# cold cache, and under `--network none` a first build fails there
+# before reaching any gate. That layer stays cached, so only a warm
+# cache lints offline, until go.mod or go.sum changes.
RUN echo "gate config verify, epoch ${CHECK_EPOCH}" && \
golangci-lint config verify --config .golangci.yml
```
Every changed line begins with `#`, checked mechanically rather than by
eye: filtering the diff's `+`/`-` lines for any not starting with `#`
returns nothing. No `FROM`, no `RUN`, no `ARG`, no reordering. The
`config verify` reasoning that was true — the schema is embedded, so the
step needs no network — is kept; only the conclusion drawn from it is
narrowed.
That completes the sweep. Grepping the tree for network and offline
claims and for descriptions of the pre-change lint arrangement now
returns no inaccurate line. `Dockerfile:42` was checked again and is
correct as written: it scopes "needs no network" to the `config verify`
step, not to the build.
## Verification after the change
`make check` green, exit 0, `real 0m46.180s`, `BUILDKIT_PROGRESS=plain`.
The gates ran rather than being served from cache, under a fresh epoch:
```
#10 [6/8] RUN echo "gate lint-image-pin, epoch 1786368508-3299841" && script/verify-lint-image-pin
#10 0.191 verify-lint-image-pin: Dockerfile.lint and Dockerfile agree on golangci/golangci-lint:v2.12.2@sha256:5cceeef04e53efe1470638d4b4b4f5ceefd574955ab3941b2d9a68a8c9ad5240
#10 DONE 0.6s
#11 [7/8] RUN echo "gate config verify, epoch 1786368508-3299841" && golangci-lint config verify --config .golangci.yml
#11 DONE 0.6s
#12 [8/8] RUN echo "gate lint, epoch 1786368508-3299841" && golangci-lint run --config .golangci.yml ./...
#12 29.90 0 issues.
#12 DONE 33.0s
```
Tests re-run with the Go test cache defeated, since `make check`
reported `(cached)` on an unchanged Go tree:
`ok sneak.berlin/go/sfdupes 2.657s coverage: 88.5% of statements`.
Cache behaviour, stated precisely rather than claimed: the three
dependency layers above `ARG CHECK_EPOCH` — `WORKDIR`,
`COPY go.mod go.sum` and `RUN go mod download` — all reported `CACHED`,
so editing a comment in this file cost nothing above the gates, which is
the property that matters. `COPY . .` re-ran (4.3s) because the edited
file is part of the build context, which is expected and unrelated. This
run does **not** isolate the narrower claim that BuildKit strips comments
before hashing a `RUN`: the gates were going to re-execute regardless,
because `script/lint` passes a fresh `CHECK_EPOCH` every time. So the
prediction is unconfirmed by this evidence rather than confirmed by it,
and nothing here depends on it.
`git fetch origin` immediately before pushing confirmed `origin/next` was
still at `215539c`; the force-push used `--force-with-lease` pinned to
that SHA.
Fresh adversarial re-review of #47 at 73ea8a5, against #46 and the binding spec at #46 (comment).
Independent clone; nothing pushed, nothing changed, tree left clean.
Verdict: FAIL — needs-rework
One blocking finding. The three prose defects from #47 (comment) are
genuinely fixed and the in-tree sweep is complete — but the same
sentence survives in the commit message, which is the one copy that
becomes permanent on merge.
Finding 1 — the landing commit message still carries the corrected overstatement (blocking)
git log -1 73ea8a5, body lines 65-68:
assumed: under --network none the pinned binary both passes a valid
config and rejects an invalid one with the jsonschema error, so it
validates against a schema it embeds and linting needs no network
beyond pulling the image. The README states that rather than a
requirement that does not exist.
Two things are wrong, both of them the exact defect this rework was
dispatched to eliminate:
"linting needs no network beyond pulling the image" is blocking
finding 1 verbatim. It is false for the same reason: Dockerfile.lint
line 19 go mod download needs the network on a cold cache. The
premise before it (config verify validates from an embedded
schema) is true; the conclusion drawn from it is the
overgeneralisation that was rejected.
"The README states that" is now false as a matter of fact. The
README no longer states it — this same rework replaced that text
with the correctly scoped version. The commit message describes a
README that ceased to exist in the commit it is attached to.
Why it matters: the tree was corrected in five places and the commit
that carries those corrections still asserts the uncorrected claim. It
is also the only copy that cannot be fixed in place later — after merge
a git log reader gets the wrong answer permanently, which is the harm
finding 1 was about. The rework comment's "That completes the sweep"
is therefore not quite earned; the sweep covered the tree but not the
commit.
Acceptable: amend the sentence to match the wording already landed in README.md, script/lint and Dockerfile.lint — the gate steps make
no network call, the build around them does on a cold cache, only a
warm cache lints offline — and drop or correct the stale "The README
states that" clause. Comment-text-only, no code change, and the commit
has already been amended twice.
Disclosure, since it is a judgement call at a scope boundary: the
re-review brief scoped the sweep to "the tree", and a commit message is
not in the tree. I am calling it blocking anyway because it is the same
defect class in the landing commit, because commit-message accuracy is
in the standing review checklist, and because the fix is an amend. If
you would rather waive it as trivial, the rest of the PR passes and
nothing else stands in the way.
Regression scope: clean
beb865a -> 73ea8a5 is comment and prose only, verified two ways
rather than by eye: filtering every +/- line across Dockerfile.lint, Dockerfile, Makefile and script/ for anything not a comment or
blank returns nothing; and the comment-stripped SHA-256 of every one of
those files is byte-identical across the two commits (Dockerfile.lint, Dockerfile, Makefile, script/cibuild, script/docker, script/lint, script/bootstrap, script/check, script/verify-lint-image-pin). Identical file sets, no adds or
deletes. No FROM, RUN, ARG, ordering edge or script logic changed.
Verified by execution
Network story, both halves. Cold cache under --network none
(--no-cache -f Dockerfile.lint, scoped to that one image, no prune
of any kind): fails at go mod download, dial tcp: lookup proxy.golang.org ... network is unreachable, 3.6s.
The three gates themselves are genuinely offline-clean, run in the
pinned image with docker run --network none: pin guard exit 0, config verify exit 0, golangci-lint run0 issues. in 36.2s.
Both adversarial controls fired offline rather than passing
vacuously — a planted rereview47-bogus-key was rejected with jsonschema: "linters" does not validate ... additional properties ... not allowed, exit 3, and a planted unused variable was caught, report.go:172:5: var rereview47Sentinel is unused (unused), exit 1.
Anomaly worth recording, does not change the verdict. --network none cannot be used to demonstrate the warm-cache half,
because BuildKit puts the network mode in the layer cache key: with
the download layer demonstrably warm (a normal build immediately
prior reported #8 [4/8] RUN go mod download / #8 CACHED), the
very next --network none build re-executed that step and failed in
1.6s. So the flag invalidates the layer it would be testing. The
tree's claim is nonetheless correct for the case it describes — a
genuinely offline machine passes no such flag and gets the cached
layer — and the two halves it decomposes into are both measured
above. Recording it because the claim is true but not testable the
obvious way, and the next person to reach for --network none will
get a failure that looks like a documentation bug and is not.
script/cibuild's header is accurate. Every gate it names ran,
in the stage it names, in that order, under one epoch: lint stage #14 fmt-check, #15 lint-image-pin, #16 config verify, #17
lint; builder stage after USER builder#26 test, #27
fmt-check. Its ordering claim holds — proven, not inherited: with a
planted var rereview47OrderingSentinel = 1, make docker failed
at #17 25.33 report.go:172:5: var rereview47OrderingSentinel is unused (unused); the builder stage reached only [builder 1/12] FROM docker.io/library/golang@sha256:56961d79...,
with COPY --from=lint, script/bootstrap, gate test and make build at 0 occurrences in the log. Reverted; tree clean.
make check green, linter demonstrably executing, real 1m1.557s under BUILDKIT_PROGRESS=plain, fresh epoch:
Only the three dependency layers above ARG CHECK_EPOCH reported CACHED. Tests ran rather than reporting (cached): ok sneak.berlin/go/sfdupes 1.809s coverage: 88.5% of statements.
make docker green end to end, real 3m15.407s, all six gates
executing under epoch 1786369106, test gate reporting ok sneak.berlin/go/sfdupes 1.130s coverage: 88.5% of statements
rather than a cached ok.
The author's disclosed-unconfirmed prediction is now measured, and
it was right. BuildKit does strip comments before hashing a RUN:
a minimal two-step build, rebuilt with a comment inserted directly
above the RUN, reported #5 [2/2] RUN echo rereview47-cachetest / #5 CACHED. Nothing depends on it, but it is no longer standing as
an untested assertion.
In-tree sweep for the defect class, done independently rather than
on the six changed files: every network/offline claim in Dockerfile, Dockerfile.lint, README.md, script/lint and TODO.md is now correctly scoped, Dockerfile:42 included (it
scopes to the config verify step, not the build). Every header
comment in script/ read against the code as it stands; no
remaining description of the pre-change arrangement outside the
passages that deliberately explain the retirement of script/verify-linter-pin.
CI success on 73ea8a5 (check / check (push), 1m23s); next
exactly one commit ahead of main and zero behind, merge-tree
reports no conflicts; subject carries (closes #46); make fmt
leaves the tree clean; inclusive terminology clean; no attribution
trailers or vendor references in the diff, tree or commit message;
no scope creep.
Not run, stated rather than implied: a cold-cache make docker, since
no build step changed. Out of scope per instruction and untouched here: sneak/prompts#40, #43, #48.
Fresh adversarial re-review of
https://git.eeqj.de/sneak/sfdupes/pulls/47 at `73ea8a5`, against
https://git.eeqj.de/sneak/sfdupes/issues/46 and the binding spec at
https://git.eeqj.de/sneak/sfdupes/issues/46#issuecomment-53177.
Independent clone; nothing pushed, nothing changed, tree left clean.
## Verdict: FAIL — `needs-rework`
One blocking finding. The three prose defects from
https://git.eeqj.de/sneak/sfdupes/pulls/47#issuecomment-53806 are
genuinely fixed and the in-tree sweep is complete — but the same
sentence survives in the commit message, which is the one copy that
becomes permanent on merge.
## Finding 1 — the landing commit message still carries the corrected overstatement (blocking)
`git log -1 73ea8a5`, body lines 65-68:
```
assumed: under --network none the pinned binary both passes a valid
config and rejects an invalid one with the jsonschema error, so it
validates against a schema it embeds and linting needs no network
beyond pulling the image. The README states that rather than a
requirement that does not exist.
```
Two things are wrong, both of them the exact defect this rework was
dispatched to eliminate:
1. "linting needs no network beyond pulling the image" is blocking
finding 1 verbatim. It is false for the same reason: `Dockerfile.lint`
line 19 `go mod download` needs the network on a cold cache. The
premise before it (`config verify` validates from an embedded
schema) is true; the conclusion drawn from it is the
overgeneralisation that was rejected.
2. "The README states that" is now false as a matter of fact. The
README no longer states it — this same rework replaced that text
with the correctly scoped version. The commit message describes a
README that ceased to exist in the commit it is attached to.
Why it matters: the tree was corrected in five places and the commit
that carries those corrections still asserts the uncorrected claim. It
is also the only copy that cannot be fixed in place later — after merge
a `git log` reader gets the wrong answer permanently, which is the harm
finding 1 was about. The rework comment's "That completes the sweep"
is therefore not quite earned; the sweep covered the tree but not the
commit.
Acceptable: amend the sentence to match the wording already landed in
`README.md`, `script/lint` and `Dockerfile.lint` — the gate steps make
no network call, the build around them does on a cold cache, only a
warm cache lints offline — and drop or correct the stale "The README
states that" clause. Comment-text-only, no code change, and the commit
has already been amended twice.
Disclosure, since it is a judgement call at a scope boundary: the
re-review brief scoped the sweep to "the tree", and a commit message is
not in the tree. I am calling it blocking anyway because it is the same
defect class in the landing commit, because commit-message accuracy is
in the standing review checklist, and because the fix is an amend. If
you would rather waive it as trivial, the rest of the PR passes and
nothing else stands in the way.
## Regression scope: clean
`beb865a` -> `73ea8a5` is comment and prose only, verified two ways
rather than by eye: filtering every `+`/`-` line across `Dockerfile.lint`,
`Dockerfile`, `Makefile` and `script/` for anything not a comment or
blank returns nothing; and the comment-stripped SHA-256 of every one of
those files is byte-identical across the two commits (`Dockerfile.lint`,
`Dockerfile`, `Makefile`, `script/cibuild`, `script/docker`,
`script/lint`, `script/bootstrap`, `script/check`,
`script/verify-lint-image-pin`). Identical file sets, no adds or
deletes. No `FROM`, `RUN`, `ARG`, ordering edge or script logic changed.
## Verified by execution
- **Network story, both halves.** Cold cache under `--network none`
(`--no-cache -f Dockerfile.lint`, scoped to that one image, no prune
of any kind): fails at `go mod download`,
`dial tcp: lookup proxy.golang.org ... network is unreachable`, 3.6s.
The three gates themselves are genuinely offline-clean, run in the
pinned image with `docker run --network none`: pin guard exit 0,
`config verify` exit 0, `golangci-lint run` `0 issues.` in 36.2s.
Both adversarial controls fired offline rather than passing
vacuously — a planted `rereview47-bogus-key` was rejected with
`jsonschema: "linters" does not validate ... additional properties
... not allowed`, exit 3, and a planted unused variable was caught,
`report.go:172:5: var rereview47Sentinel is unused (unused)`, exit 1.
- **Anomaly worth recording, does not change the verdict.**
`--network none` cannot be used to demonstrate the warm-cache half,
because BuildKit puts the network mode in the layer cache key: with
the download layer demonstrably warm (a normal build immediately
prior reported `#8 [4/8] RUN go mod download` / `#8 CACHED`), the
very next `--network none` build re-executed that step and failed in
1.6s. So the flag invalidates the layer it would be testing. The
tree's claim is nonetheless correct for the case it describes — a
genuinely offline machine passes no such flag and gets the cached
layer — and the two halves it decomposes into are both measured
above. Recording it because the claim is true but not testable the
obvious way, and the next person to reach for `--network none` will
get a failure that looks like a documentation bug and is not.
- **`script/cibuild`'s header is accurate.** Every gate it names ran,
in the stage it names, in that order, under one epoch: lint stage
`#14` fmt-check, `#15` lint-image-pin, `#16` config verify, `#17`
lint; builder stage after `USER builder` `#26` test, `#27`
fmt-check. Its ordering claim holds — proven, not inherited: with a
planted `var rereview47OrderingSentinel = 1`, `make docker` failed
at `#17 25.33 report.go:172:5: var rereview47OrderingSentinel is
unused (unused)`; the builder stage reached only
`[builder 1/12] FROM docker.io/library/golang@sha256:56961d79...`,
with `COPY --from=lint`, `script/bootstrap`, `gate test` and
`make build` at 0 occurrences in the log. Reverted; tree clean.
- **`make check` green, linter demonstrably executing**, `real
1m1.557s` under `BUILDKIT_PROGRESS=plain`, fresh epoch:
```
#13 [8/8] RUN echo "gate lint, epoch 1786369030-3754970" && golangci-lint run --config .golangci.yml ./...
#13 0.203 gate lint, epoch 1786369030-3754970
#13 43.70 0 issues.
#13 DONE 45.4s
```
Only the three dependency layers above `ARG CHECK_EPOCH` reported
`CACHED`. Tests ran rather than reporting `(cached)`:
`ok sneak.berlin/go/sfdupes 1.809s coverage: 88.5% of statements`.
- `make docker` green end to end, `real 3m15.407s`, all six gates
executing under epoch `1786369106`, test gate reporting
`ok sneak.berlin/go/sfdupes 1.130s coverage: 88.5% of statements`
rather than a cached ok.
- **The author's disclosed-unconfirmed prediction is now measured, and
it was right.** BuildKit does strip comments before hashing a `RUN`:
a minimal two-step build, rebuilt with a comment inserted directly
above the `RUN`, reported `#5 [2/2] RUN echo rereview47-cachetest` /
`#5 CACHED`. Nothing depends on it, but it is no longer standing as
an untested assertion.
- In-tree sweep for the defect class, done independently rather than
on the six changed files: every `network`/`offline` claim in
`Dockerfile`, `Dockerfile.lint`, `README.md`, `script/lint` and
`TODO.md` is now correctly scoped, `Dockerfile:42` included (it
scopes to the `config verify` step, not the build). Every header
comment in `script/` read against the code as it stands; no
remaining description of the pre-change arrangement outside the
passages that deliberately explain the retirement of
`script/verify-linter-pin`.
- CI `success` on `73ea8a5` (`check / check (push)`, 1m23s); `next`
exactly one commit ahead of `main` and zero behind, `merge-tree`
reports no conflicts; subject carries `(closes #46)`; `make fmt`
leaves the tree clean; inclusive terminology clean; no attribution
trailers or vendor references in the diff, tree or commit message;
no scope creep.
Not run, stated rather than implied: a cold-cache `make docker`, since
no build step changed. Out of scope per instruction and untouched here:
https://git.eeqj.de/sneak/prompts/issues/40,
https://git.eeqj.de/sneak/sfdupes/issues/43,
https://git.eeqj.de/sneak/sfdupes/issues/48.
Re-review finding at #47 (comment) accepted
in full — including the scope call. The sweep covered the tree and
stopped at the tree; the commit message is the copy that cannot be
corrected after merge, and it still asserted the claim the rework
existed to remove. Calling it blocking was right.
New SHA: d4eaf5f (was 73ea8a5, before that 215539c, originally beb865a).
Commit message only — the tree did not change
git rev-parse ^{tree} is 85fa3ae0483d1cb79046f7f759bd31bd6b59f477
on both 73ea8a5 and d4eaf5f, and git diff 73ea8a5 d4eaf5f is
empty. Subject byte-identical, still Run all linting in Docker via Dockerfile.lint (closes #46)
(#46). Branch is still exactly
one commit ahead of main.
The corrected paragraph
Was:
validates against a schema it embeds and linting needs no network
beyond pulling the image. The README states that rather than a
requirement that does not exist.
Now:
against a schema it embeds. That holds for the gate steps, none of
which makes a network call, but not for the build around them --
Dockerfile.lint runs `go mod download` above the gates, so a cold cache
needs the network and only a warm one lints offline, until go.mod or
go.sum changes.
Both defects addressed: the false conclusion is replaced with the same
scoping already landed in README.md, script/lint and Dockerfile.lint, and the stale "The README states that" clause — which
described a README this rework had itself replaced — is dropped rather
than reworded. The true premise (config verify validates from an
embedded schema, measured under --network none) is kept.
Body tightened
Applied alongside, per a standing instruction from the owner dated
today: commit and PR prose should not be verbose — keep what a reader
needs to avoid a trap, cut the reasoning, history and self-justification.
Narration removed ("outright rather than being hardened", "and today's make check gets it", "Its replacement is one new", and similar).
Nothing else went: every trap, the REPO_POLICIES.md ordering-edge
provenance, and the whole Verified. paragraph with its timings and
negative controls are intact. Net 83 body lines to 78, with the
accuracy fix adding about three, so roughly seven lines of narration
came out.
Not re-verified, deliberately
No make check, no make docker. The tree is bit-identical to the one
the re-review exercised, so re-running would measure the same tree twice
and prove nothing new. CI runs script/cibuild on d4eaf5f.
git fetch origin immediately before pushing confirmed origin/next
was still at 73ea8a5; the force-push used --force-with-lease pinned
to that SHA.
One thing left alone and flagged rather than changed: the body still
cites #32, #39 and #42 bare rather than as full URLs. Those
predate this rework, two reviews have passed over them, and Gitea
autolinks them in the commit view — but the authorised scope here was
the network sentence plus narration cuts, so expanding them would have
been a change nobody asked for. Say the word and they become full URLs
in a fourth amend.
Re-review finding at
https://git.eeqj.de/sneak/sfdupes/pulls/47#issuecomment-54233 accepted
in full — including the scope call. The sweep covered the tree and
stopped at the tree; the commit message is the copy that cannot be
corrected after merge, and it still asserted the claim the rework
existed to remove. Calling it blocking was right.
**New SHA: `d4eaf5f`** (was `73ea8a5`, before that `215539c`, originally
`beb865a`).
## Commit message only — the tree did not change
`git rev-parse ^{tree}` is `85fa3ae0483d1cb79046f7f759bd31bd6b59f477`
on both `73ea8a5` and `d4eaf5f`, and `git diff 73ea8a5 d4eaf5f` is
empty. Subject byte-identical, still
`Run all linting in Docker via Dockerfile.lint (closes #46)`
(https://git.eeqj.de/sneak/sfdupes/issues/46). Branch is still exactly
one commit ahead of `main`.
## The corrected paragraph
Was:
```
validates against a schema it embeds and linting needs no network
beyond pulling the image. The README states that rather than a
requirement that does not exist.
```
Now:
```
against a schema it embeds. That holds for the gate steps, none of
which makes a network call, but not for the build around them --
Dockerfile.lint runs `go mod download` above the gates, so a cold cache
needs the network and only a warm one lints offline, until go.mod or
go.sum changes.
```
Both defects addressed: the false conclusion is replaced with the same
scoping already landed in `README.md`, `script/lint` and
`Dockerfile.lint`, and the stale "The README states that" clause — which
described a README this rework had itself replaced — is dropped rather
than reworded. The true premise (`config verify` validates from an
embedded schema, measured under `--network none`) is kept.
## Body tightened
Applied alongside, per a standing instruction from the owner dated
today: commit and PR prose should not be verbose — keep what a reader
needs to avoid a trap, cut the reasoning, history and self-justification.
Narration removed ("outright rather than being hardened", "and today's
`make check` gets it", "Its replacement is one new", and similar).
Nothing else went: every trap, the `REPO_POLICIES.md` ordering-edge
provenance, and the whole `Verified.` paragraph with its timings and
negative controls are intact. Net 83 body lines to 78, with the
accuracy fix adding about three, so roughly seven lines of narration
came out.
## Not re-verified, deliberately
No `make check`, no `make docker`. The tree is bit-identical to the one
the re-review exercised, so re-running would measure the same tree twice
and prove nothing new. CI runs `script/cibuild` on `d4eaf5f`.
`git fetch origin` immediately before pushing confirmed `origin/next`
was still at `73ea8a5`; the force-push used `--force-with-lease` pinned
to that SHA.
One thing left alone and flagged rather than changed: the body still
cites `#32`, `#39` and `#42` bare rather than as full URLs. Those
predate this rework, two reviews have passed over them, and Gitea
autolinks them in the commit view — but the authorised scope here was
the network sentence plus narration cuts, so expanding them would have
been a change nobody asked for. Say the word and they become full URLs
in a fourth amend.
Re-review of d4eaf5f (message-amend only), scoped to the one finding from #47 (comment). PASS.
Tree proved unchanged, not accepted: 73ea8a5 and d4eaf5f share tree 85fa3ae0483d1cb79046f7f759bd31bd6b59f477, same parent e6a9171, same author date; git diff 73ea8a5 d4eaf5f empty. Only the committer date moved. Prior finding fixed: the false "linting needs no network beyond pulling the image" and the stale README-describing sentence are both gone, replaced by an accurate account that matches Dockerfile.lint:19,49-54, script/lint:6-10 and README.md:478-492. Every other claim in the body checks out against the tree. Narration cut is -83 words and removed no trap, no disclosure and no evidence line; the cold-cache network caveat is a net-new disclosure.
Anomalies and disclosures, none blocking:
Largest cut is "so make lint, make check and make docker all catch drift" from the drift-guard paragraph. It is a coverage claim rather than evidence, and it survives verbatim at README.md:522-523 and in the script/verify-lint-image-pin header, so nothing left the record.
The evidence line lost the qualifier "in the lint step" from "27.7s and 28.7s". The numbers and the distinct-epoch/CACHED-COPY evidence are intact; only the scope of the timing is now less precise.
The body no longer says the image pull itself needs network. Covered implicitly by "a cold cache needs the network" and stated explicitly at script/lint:9 and README.md:486, so not a false claim, but it is narrower than the old wording.
CI: terminal success. check / check (push) = success, "Successful in 1m38s", updated_at 2026-08-10T15:53:50+02:00. Read four times across both the combined-status and statuses-list endpoints; the statuses list holds exactly two rows, id=1 pending "Waiting to run" 15:48:01 and id=2 success 15:53:50, with no spurious duplicate. Disclosure: the Actions runs API 403s for this account, so the job log was not read and green rests on the commit status alone.
Confirmation the tree is the one that passed: make check green here, linter demonstrably executed, not cached — #12 [8/8] RUN echo "gate lint, epoch 1786369933-139490" && golangci-lint run with #12 52.64 0 issues. / #12 DONE 56.1s, under the CACHED COPY . . layer. make fmt-check exit 0. Merges cleanly against main at e6a9171. No attribution trailers, no non-inclusive terms, subject 58 chars with (closes #46), body wrapped at 72.
Mechanism, gate ordering and planted-sentinel controls were not re-run: the tree is bit-identical to the one already verified.
Verdict: PASS.
Re-review of `d4eaf5f` (message-amend only), scoped to the one finding from https://git.eeqj.de/sneak/sfdupes/pulls/47#issuecomment-54233. **PASS.**
Tree proved unchanged, not accepted: `73ea8a5` and `d4eaf5f` share tree `85fa3ae0483d1cb79046f7f759bd31bd6b59f477`, same parent `e6a9171`, same author date; `git diff 73ea8a5 d4eaf5f` empty. Only the committer date moved. Prior finding fixed: the false "linting needs no network beyond pulling the image" and the stale README-describing sentence are both gone, replaced by an accurate account that matches `Dockerfile.lint:19,49-54`, `script/lint:6-10` and `README.md:478-492`. Every other claim in the body checks out against the tree. Narration cut is -83 words and removed no trap, no disclosure and no evidence line; the cold-cache network caveat is a net-new disclosure.
Anomalies and disclosures, none blocking:
- Largest cut is "so `make lint`, `make check` and `make docker` all catch drift" from the drift-guard paragraph. It is a coverage claim rather than evidence, and it survives verbatim at `README.md:522-523` and in the `script/verify-lint-image-pin` header, so nothing left the record.
- The evidence line lost the qualifier "in the lint step" from "27.7s and 28.7s". The numbers and the distinct-epoch/CACHED-COPY evidence are intact; only the scope of the timing is now less precise.
- The body no longer says the image pull itself needs network. Covered implicitly by "a cold cache needs the network" and stated explicitly at `script/lint:9` and `README.md:486`, so not a false claim, but it is narrower than the old wording.
- CI: terminal **success**. `check / check (push)` = `success`, "Successful in 1m38s", `updated_at` 2026-08-10T15:53:50+02:00. Read four times across both the combined-status and statuses-list endpoints; the statuses list holds exactly two rows, id=1 `pending` "Waiting to run" 15:48:01 and id=2 `success` 15:53:50, with no spurious duplicate. Disclosure: the Actions runs API 403s for this account, so the job log was not read and green rests on the commit status alone.
- Confirmation the tree is the one that passed: `make check` green here, linter demonstrably executed, not cached — `#12 [8/8] RUN echo "gate lint, epoch 1786369933-139490" && golangci-lint run` with `#12 52.64 0 issues.` / `#12 DONE 56.1s`, under the CACHED `COPY . .` layer. `make fmt-check` exit 0. Merges cleanly against `main` at `e6a9171`. No attribution trailers, no non-inclusive terms, subject 58 chars with `(closes #46)`, body wrapped at 72.
Mechanism, gate ordering and planted-sentinel controls were not re-run: the tree is bit-identical to the one already verified.
Verdict: **PASS**.
clawbot
merged commit d43c1d31ac into main2026-08-10 15:55:43 +02:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Long-lived
nextbranch for the current cycle; one issue-closing commitper work unit, merged to
mainwhen the cycle lands.Commits so far:
beb865aRun all linting in Docker viaDockerfile.lint(closes #46) — new root
Dockerfile.lintbuilds the digest-pinned linter image and lints asa build step;
script/lintreduced to building it; thegolangci-lintinstall is gone fromscript/bootstrap; the mainDockerfile's lint stage invokes the linter directly and its buildstage runs the test and fmt-check gates rather than the aggregate, so
nothing inside an image build shells back out to Docker.
script/verify-linter-pinis retired — no linter binary crossesbetween stages and bootstrap pins nothing — and replaced by
script/verify-lint-image-pin, which fails when theFROMlines inDockerfileandDockerfile.lintdisagree.Adversarial review of
#47 at
beb865a, against#46 and the binding spec at
#46 (comment).
Reviewed in an independent clone; nothing pushed, nothing changed.
Verdict: FAIL —
needs-reworkThe mechanism is sound and every load-bearing claim in the evidence
comment reproduced. Three blocking findings, all documentation
accuracy, all one-line fixes.
Findings
1.
README.md§Entrypoints,script/lint: "nothing here needs the network" is false (blocking, minor)The entry states: "Once the pinned image is local, nothing here needs
the network". A cold BuildKit cache reaches
Dockerfile.lint:19RUN go mod download, and the module has 14 external requirements.Proved with a build scoped to that one image
(
docker build --no-cache --network none -f Dockerfile.lint .):The evidence comment on
#46 already says the same
thing in passing ("it re-runs and fails at
go mod downloadbeforereaching the gate"), so the README overstates a result the author
measured correctly. Why it matters: this is the entry a developer
reads to decide whether an air-gapped or offline machine can run
make check, and it will send them to the wrong conclusion on a firstbuild. Acceptable: scope the claim to the gates — the linter and
config verifyneed no network; a first build still downloadsmodules.
2.
script/cibuild:2-4header describes gates this change removed (blocking, minor)After this commit the
Dockerfileruns neithermake lint(the lintstage invokes
golangci-lintdirectly) normake check(the buildstage runs
make testandmake fmt-check). This is the same defectclass the spec required fixing in
script/bootstrap's header —"update the header comment so it no longer describes an install that
does not happen" — and it is worse here, because the file is the CI
entrypoint and the comment is a reader's basis for believing CI gates
on lint at all. Acceptable: restate the actual gates.
3.
script/docker:7-8refers to amake checkthat is gone (blocking, minor)"...this exits 0 having run neither the lint stage nor the builder
stage's make check." The builder stage no longer runs
make check.Same fix.
Disclosures and non-blocking observations
REPO_POLICIES.md:92("All Dockerfiles must runmake checkas a build step") and its worked example at line 123(
RUN make lintin the lint stage) are violated by bothDockerfiles. The owner ruling and the spec on
#46 make this unavoidable,
and the author disclosed it on the issue rather than editing the
vendored policy. Not held against the change; flagged so the
divergence stays on the record.
with the builder stage forced uncached
(
--no-cache-filter=builder) tookreal 5m37.788shere, matchingthe author's 5m35s and exceeding
REPO_POLICIES.md:231'sfive-minute limit. It is not a regression — before this change lint
ran twice per image build, and now it runs once. Warm build:
real 1m16.243s; CI reports 1m5s.script/lintbuilds with neither-tnor--output=type=cacheonly, so each run exports a full image(15-21s of the 33-70s wall clock observed) and leaves a dangling
image behind. On a machine where
make checkand every pre-commitrun this, that accumulates. Cosmetic, not a gate defect.
make testversusscript/testjustification (Makefileexport CGO_ENABLED = 0) holds mechanically — probed inside thebuilder image, a make recipe child sees
make-env-CGO_ENABLED=[0]while a bare shell sees empty — but notethe practical difference is nil in this image:
go env CGO_ENABLEDis already
0in thegolang:1.25-alpinebase. The reasoning isstill correct and the choice is fine.
.dockerignore:2and.gitignore:36name agent tool directories.Both pre-date this PR and are unchanged by it.
account. CI green is taken from the commit status on
beb865a(
check / check (push),success, 1m5s). The epoch mechanism Iverified locally is what makes that green mean anything.
Verified by execution
CHECK_EPOCHis referenced in theexpanded command of every gate
RUNin both files and both stages,with the required second per-stage
ARGin the builder stage(
Dockerfile:107, afterUSER builder). Two consecutivescript/lintruns on an untouched tree both executed the linter —#12 ... 20.04 0 issues. / #12 DONE 25.1s(33.5s wall) and, with#9 [5/8] COPY . . CACHEDabove it,#12 ... 36.72 0 issues. / #12 DONE 38.1s(49.7s wall), underdistinct epochs. Adversarial control the author did not run:
holding the epoch constant across two builds
(
--build-arg CHECK_EPOCH=review47-fixed) brought the false greenstraight back — second build
#12 [8/8] RUN echo "gate lint, epoch review47-fixed" ... CACHED,real 0m0.336s. The buster is doingthe work, not incidental.
var reviewPR47OrderingSentinel = 1inreport.go:make dockerfailed at
#17 [lint 9/9]withreport.go:172:5: var reviewPR47OrderingSentinel is unused (unused)in 44.9s; the builder stage reached only
[builder 1/12] FROM docker.io/library/golang@sha256:56961d79...,with
COPY --from=lint,script/bootstrap,gate testandmake buildat 0 occurrences in the log. Since BuildKit does notbuild unreferenced stages, the lint stage running at all proves the
COPY --from=lint /src/go.sum /dev/nulledge exists. Reverted;working tree clean.
-count=1, plusGOFLAGS=-count=1):--user 0:0gives--- FAIL: TestScanHardlinkRunFailsTogether (0.01s)/scan_test.go:817: stats = {added:2 ...}, want both hardlink paths skipped; default uid 1000 givesok sneak.berlin/go/sfdupes 0.012s.digest-only, missing
FROM(found 0, not a vacuous empty-stringpass), duplicate golangci
FROM(found 2), unpinnedFROM golangci/golangci-lint, registry-qualifieddocker.io/golangci/..., and a missingDockerfileall exit 1 witha named diagnosis; lowercase
from, extra whitespace,FROM --platform=..., and a# FROM golangci/...comment line areall handled correctly. It runs as a gate in both files, so
script/lint,make check,make dockerandscript/cibuildallreach it — confirmed in the build logs of each.
golangci-lintinvocationor install remains anywhere in
script/,Makefileor.gitea/. Masking method: everyPATHentry containing anexecutable
golangci-lintremoved(
/home/user/go/bin,/home/user/.local/bin,/usr/local/bin),command -v golangci-lintandwhich -a golangci-lintboth empty,docker/go/makestill resolving. Under thatPATH,make lintexit 0 (#12 37.91 0 issues.,real 1m9.502s) andmake checkexit 0 (ok sneak.berlin/go/sfdupes 1.117s coverage: 88.5% of statements,#12 25.60 0 issues.,real 0m41.957s).calls
docker,make lintormake check;script/bootstraponlywarns. Probed uncached rather than trusting a
CACHEDline —--no-cache-filter=builderre-executed#21 [builder 7/12] RUN script/bootstrapafter theCOPY --from=lint /src/go.sum /dev/nullclobber and it behaved:bootstrap: WARNING: docker not found; ...thenbootstrap complete. The>/dev/nullredirections survive theclobber because the runtime mounts
/devperRUN, as claimed.config verifyoffline claim confirmed on the pinned imageunder
--network none: valid config exit 0; a config withbogus-review47-keystill rejected,jsonschema: "linters" does not validate ... additional properties 'bogus-review47-key' not allowed, exit 3. The author's deviationfrom the ruling's "state the network requirement" is correct: there
is none for that step.
make dockergreen end to end,real 1m16.243s, every gateexecuting under one epoch and the test gate reporting
ok sneak.berlin/go/sfdupes 1.544s coverage: 88.5% of statementsrather than
(cached).Dockerfile.lintshape andpin comment,
script/lintreduced to the build with"$(date +%s)-$$",script/bootstrapstripped to the presencechecks plus
go mod downloadwith a docker warning,ENV PATH=/home/builder/go/bin:$PATHremoved and re-justified incomment,
chownplusUSER builderstill before the gates,script/verify-linter-pindeleted with its README entry and thereasoning recorded in both the commit message and the top of
TODO.md's Completed Steps.successonbeb865a; branch is a fast-forward ofmain, noconflicts; commit subject carries
(closes #46); no attributiontrailers and no vendor references anywhere in the diff or commit
message; inclusive terminology clean;
make fmt-checkclean;.golangci.ymluntouched; no scope creep.Manager: review FAILed on three documentation-accuracy blockers, no
code change required.
needs-rework; rework dispatched, fresh reviewerafter. The mechanism itself reproduced independently, including the
constant-epoch control the author had not run.
beb865ae68to215539cd15Rework of the FAIL review at
#47 (comment).
Documentation only — no behaviour change. The Dockerfiles, the drift
guard
script/verify-lint-image-pin, andscript/lint'sdocker buildinvocation are untouched. Amended into the single commit, subject
unchanged:
beb865a->215539c, stillRun all linting in Docker via Dockerfile.lint (closes #46)(#46). Five files, +45/-21.
Finding 1 —
README.md§Entrypoints,script/lint: false offline claimAccepted; the claim was wrong. "Once the pinned image is local, nothing
here needs the network" is replaced with an entry that separates the
gates from the build around them, because that is where the truth
divides:
golangci-lint runnorconfig verify, the latter validating against a schema the pinnedbinary embeds, still cited as measured under
--network nonetoboth pass a valid config and reject an invalid one (the part of the
old text that was true and worth keeping);
Dockerfile.lintrunsgo mod downloadabove them and this modulehas external dependencies, so a first lint on a cold BuildKit cache
reaches the network there, and under
--network nonefails at thatstep before any gate — exactly what the review reproduced;
script/lint, and with itmake check, runs entirely offlineuntil
go.modorgo.sumchanges and the layer goes cold again.A developer reading the entry to decide whether an offline machine can
run
make checknow gets the correct answer in both directions: yes ona warm cache, no on a first build.
Finding 2 —
script/cibuildheaderAccepted. The header no longer claims the Dockerfile runs
make fmt-check,make lintandmake check. It now names what actuallyruns — lint stage:
make fmt-check,script/verify-lint-image-pin,golangci-lint config verify,golangci-lint run; build stage,dropped to the unprivileged user:
make testandmake fmt-check—states why neither aggregate appears (both reach
script/lint, whichis itself a docker build, and a docker build cannot run inside one),
and states why lint is nonetheless gated: the linter is invoked
directly in the lint stage and the build stage's
COPY --from=lintmakes that stage a prerequisite. The concludingsentence — that between the two stages everything
make checkwouldrun has run — is now earned rather than asserted. The
CHECK_EPOCHparagraph below it is unchanged.
Finding 3 —
script/dockerheaderAccepted. "neither the lint stage nor the builder stage's make check"
becomes "neither the lint stage's gates nor the builder stage's test
and fmt-check gates".
Same defect class, swept
Two more instances of the same inaccuracies, neither called out
individually in the review:
script/lint's own header carried finding 1's false claim verbatim("once the pinned image is present nothing here reaches the
network"). Corrected the same way. The
docker buildinvocationbelow it is untouched.
TODO.md's Completed Steps entry concluded "linting needs nonetwork" from the
config verifymeasurement — the sameovergeneralisation from a true premise. Now scoped to the step, with
the
go mod downloadcaveat recorded.Disclosed, deliberately not fixed
Dockerfile.lint:49-50says "Linting therefore needs no network beyondpulling the pinned image." That is the identical overstatement as
finding 1, in the very file whose line 19
go mod downloaddisprovesit. I was instructed not to touch the Dockerfiles in this rework, so I
have left it and am flagging it rather than deviating silently. It is a
comment-only, one-sentence fix and should be authorised as a follow-up.
Verification
make checkgreen, exit 0,real 0m39.995sunderBUILDKIT_PROGRESS=plain. The linter genuinely executed ratherthan being served from cache:
The pin guard and config verify likewise ran under the same epoch
(
#10 DONE 0.2s,#11 DONE 3.3s). Only three layers reportedCACHED— the.dockerignoreload,COPY go.mod go.sumandRUN go mod download, i.e. exactly the dependency layers aboveARG CHECK_EPOCHthat are designed to stay warm.Tests re-run with the Go test cache defeated, since
make check'ssecond invocation reported
(cached)on an unchanged Go tree:ok sneak.berlin/go/sfdupes 0.995s coverage: 88.5% of statements.No Go file is touched by this rework.
make fmtrun; a no-op, asscript/fmtisgofmt -s -wand no Gosource changed. Markdown is not formatted by this repo's tooling,
so the prose was rewrapped by hand to the files' existing width.
Not re-run, and stated rather than implied:
make dockerandscript/cibuild. This rework changes no Dockerfile, no build stepand no Go code — only comments in three
script/files and twomarkdown files — so the image build is unaffected. CI will run
script/cibuildon the pushed commit.git fetch originimmediately before pushing confirmedorigin/nextwas still atbeb865a; the force-push used--force-with-leasepinned to that SHA.215539cd15to73ea8a536fFollow-up to
#47 (comment): the
deferral disclosed there is now closed, not carried. Authorised as
comment-text-only.
New SHA:
73ea8a5(was215539c, originallybeb865a). Stillexactly one commit on the branch, subject byte-identical and still
Run all linting in Docker via Dockerfile.lint (closes #46)(#46). Total against
beb865ais now 6 files, +51/-23 — all comments and prose, no behaviour change
anywhere in the unit.
The fourth line
Dockerfile.lint:49-50carried the same overstatement as blockingfinding 1, in the file whose line 19
go mod downloaddisproves it. Itnow matches the wording landed in
README.mdandscript/lint:Every changed line begins with
#, checked mechanically rather than byeye: filtering the diff's
+/-lines for any not starting with#returns nothing. No
FROM, noRUN, noARG, no reordering. Theconfig verifyreasoning that was true — the schema is embedded, so thestep needs no network — is kept; only the conclusion drawn from it is
narrowed.
That completes the sweep. Grepping the tree for network and offline
claims and for descriptions of the pre-change lint arrangement now
returns no inaccurate line.
Dockerfile:42was checked again and iscorrect as written: it scopes "needs no network" to the
config verifystep, not to the build.
Verification after the change
make checkgreen, exit 0,real 0m46.180s,BUILDKIT_PROGRESS=plain.The gates ran rather than being served from cache, under a fresh epoch:
Tests re-run with the Go test cache defeated, since
make checkreported
(cached)on an unchanged Go tree:ok sneak.berlin/go/sfdupes 2.657s coverage: 88.5% of statements.Cache behaviour, stated precisely rather than claimed: the three
dependency layers above
ARG CHECK_EPOCH—WORKDIR,COPY go.mod go.sumandRUN go mod download— all reportedCACHED,so editing a comment in this file cost nothing above the gates, which is
the property that matters.
COPY . .re-ran (4.3s) because the editedfile is part of the build context, which is expected and unrelated. This
run does not isolate the narrower claim that BuildKit strips comments
before hashing a
RUN: the gates were going to re-execute regardless,because
script/lintpasses a freshCHECK_EPOCHevery time. So theprediction is unconfirmed by this evidence rather than confirmed by it,
and nothing here depends on it.
git fetch originimmediately before pushing confirmedorigin/nextwasstill at
215539c; the force-push used--force-with-leasepinned tothat SHA.
Fresh adversarial re-review of
#47 at
73ea8a5, against#46 and the binding spec at
#46 (comment).
Independent clone; nothing pushed, nothing changed, tree left clean.
Verdict: FAIL —
needs-reworkOne blocking finding. The three prose defects from
#47 (comment) are
genuinely fixed and the in-tree sweep is complete — but the same
sentence survives in the commit message, which is the one copy that
becomes permanent on merge.
Finding 1 — the landing commit message still carries the corrected overstatement (blocking)
git log -1 73ea8a5, body lines 65-68:Two things are wrong, both of them the exact defect this rework was
dispatched to eliminate:
finding 1 verbatim. It is false for the same reason:
Dockerfile.lintline 19
go mod downloadneeds the network on a cold cache. Thepremise before it (
config verifyvalidates from an embeddedschema) is true; the conclusion drawn from it is the
overgeneralisation that was rejected.
README no longer states it — this same rework replaced that text
with the correctly scoped version. The commit message describes a
README that ceased to exist in the commit it is attached to.
Why it matters: the tree was corrected in five places and the commit
that carries those corrections still asserts the uncorrected claim. It
is also the only copy that cannot be fixed in place later — after merge
a
git logreader gets the wrong answer permanently, which is the harmfinding 1 was about. The rework comment's "That completes the sweep"
is therefore not quite earned; the sweep covered the tree but not the
commit.
Acceptable: amend the sentence to match the wording already landed in
README.md,script/lintandDockerfile.lint— the gate steps makeno network call, the build around them does on a cold cache, only a
warm cache lints offline — and drop or correct the stale "The README
states that" clause. Comment-text-only, no code change, and the commit
has already been amended twice.
Disclosure, since it is a judgement call at a scope boundary: the
re-review brief scoped the sweep to "the tree", and a commit message is
not in the tree. I am calling it blocking anyway because it is the same
defect class in the landing commit, because commit-message accuracy is
in the standing review checklist, and because the fix is an amend. If
you would rather waive it as trivial, the rest of the PR passes and
nothing else stands in the way.
Regression scope: clean
beb865a->73ea8a5is comment and prose only, verified two waysrather than by eye: filtering every
+/-line acrossDockerfile.lint,Dockerfile,Makefileandscript/for anything not a comment orblank returns nothing; and the comment-stripped SHA-256 of every one of
those files is byte-identical across the two commits (
Dockerfile.lint,Dockerfile,Makefile,script/cibuild,script/docker,script/lint,script/bootstrap,script/check,script/verify-lint-image-pin). Identical file sets, no adds ordeletes. No
FROM,RUN,ARG, ordering edge or script logic changed.Verified by execution
Network story, both halves. Cold cache under
--network none(
--no-cache -f Dockerfile.lint, scoped to that one image, no pruneof any kind): fails at
go mod download,dial tcp: lookup proxy.golang.org ... network is unreachable, 3.6s.The three gates themselves are genuinely offline-clean, run in the
pinned image with
docker run --network none: pin guard exit 0,config verifyexit 0,golangci-lint run0 issues.in 36.2s.Both adversarial controls fired offline rather than passing
vacuously — a planted
rereview47-bogus-keywas rejected withjsonschema: "linters" does not validate ... additional properties ... not allowed, exit 3, and a planted unused variable was caught,report.go:172:5: var rereview47Sentinel is unused (unused), exit 1.Anomaly worth recording, does not change the verdict.
--network nonecannot be used to demonstrate the warm-cache half,because BuildKit puts the network mode in the layer cache key: with
the download layer demonstrably warm (a normal build immediately
prior reported
#8 [4/8] RUN go mod download/#8 CACHED), thevery next
--network nonebuild re-executed that step and failed in1.6s. So the flag invalidates the layer it would be testing. The
tree's claim is nonetheless correct for the case it describes — a
genuinely offline machine passes no such flag and gets the cached
layer — and the two halves it decomposes into are both measured
above. Recording it because the claim is true but not testable the
obvious way, and the next person to reach for
--network nonewillget a failure that looks like a documentation bug and is not.
script/cibuild's header is accurate. Every gate it names ran,in the stage it names, in that order, under one epoch: lint stage
#14fmt-check,#15lint-image-pin,#16config verify,#17lint; builder stage after
USER builder#26test,#27fmt-check. Its ordering claim holds — proven, not inherited: with a
planted
var rereview47OrderingSentinel = 1,make dockerfailedat
#17 25.33 report.go:172:5: var rereview47OrderingSentinel is unused (unused); the builder stage reached only[builder 1/12] FROM docker.io/library/golang@sha256:56961d79...,with
COPY --from=lint,script/bootstrap,gate testandmake buildat 0 occurrences in the log. Reverted; tree clean.make checkgreen, linter demonstrably executing,real 1m1.557sunderBUILDKIT_PROGRESS=plain, fresh epoch:Only the three dependency layers above
ARG CHECK_EPOCHreportedCACHED. Tests ran rather than reporting(cached):ok sneak.berlin/go/sfdupes 1.809s coverage: 88.5% of statements.make dockergreen end to end,real 3m15.407s, all six gatesexecuting under epoch
1786369106, test gate reportingok sneak.berlin/go/sfdupes 1.130s coverage: 88.5% of statementsrather than a cached ok.
The author's disclosed-unconfirmed prediction is now measured, and
it was right. BuildKit does strip comments before hashing a
RUN:a minimal two-step build, rebuilt with a comment inserted directly
above the
RUN, reported#5 [2/2] RUN echo rereview47-cachetest/#5 CACHED. Nothing depends on it, but it is no longer standing asan untested assertion.
In-tree sweep for the defect class, done independently rather than
on the six changed files: every
network/offlineclaim inDockerfile,Dockerfile.lint,README.md,script/lintandTODO.mdis now correctly scoped,Dockerfile:42included (itscopes to the
config verifystep, not the build). Every headercomment in
script/read against the code as it stands; noremaining description of the pre-change arrangement outside the
passages that deliberately explain the retirement of
script/verify-linter-pin.CI
successon73ea8a5(check / check (push), 1m23s);nextexactly one commit ahead of
mainand zero behind,merge-treereports no conflicts; subject carries
(closes #46);make fmtleaves the tree clean; inclusive terminology clean; no attribution
trailers or vendor references in the diff, tree or commit message;
no scope creep.
Not run, stated rather than implied: a cold-cache
make docker, sinceno build step changed. Out of scope per instruction and untouched here:
sneak/prompts#40,
#43,
#48.
73ea8a536ftod4eaf5fed2Re-review finding at
#47 (comment) accepted
in full — including the scope call. The sweep covered the tree and
stopped at the tree; the commit message is the copy that cannot be
corrected after merge, and it still asserted the claim the rework
existed to remove. Calling it blocking was right.
New SHA:
d4eaf5f(was73ea8a5, before that215539c, originallybeb865a).Commit message only — the tree did not change
git rev-parse ^{tree}is85fa3ae0483d1cb79046f7f759bd31bd6b59f477on both
73ea8a5andd4eaf5f, andgit diff 73ea8a5 d4eaf5fisempty. Subject byte-identical, still
Run all linting in Docker via Dockerfile.lint (closes #46)(#46). Branch is still exactly
one commit ahead of
main.The corrected paragraph
Was:
Now:
Both defects addressed: the false conclusion is replaced with the same
scoping already landed in
README.md,script/lintandDockerfile.lint, and the stale "The README states that" clause — whichdescribed a README this rework had itself replaced — is dropped rather
than reworded. The true premise (
config verifyvalidates from anembedded schema, measured under
--network none) is kept.Body tightened
Applied alongside, per a standing instruction from the owner dated
today: commit and PR prose should not be verbose — keep what a reader
needs to avoid a trap, cut the reasoning, history and self-justification.
Narration removed ("outright rather than being hardened", "and today's
make checkgets it", "Its replacement is one new", and similar).Nothing else went: every trap, the
REPO_POLICIES.mdordering-edgeprovenance, and the whole
Verified.paragraph with its timings andnegative controls are intact. Net 83 body lines to 78, with the
accuracy fix adding about three, so roughly seven lines of narration
came out.
Not re-verified, deliberately
No
make check, nomake docker. The tree is bit-identical to the onethe re-review exercised, so re-running would measure the same tree twice
and prove nothing new. CI runs
script/cibuildond4eaf5f.git fetch originimmediately before pushing confirmedorigin/nextwas still at
73ea8a5; the force-push used--force-with-leasepinnedto that SHA.
One thing left alone and flagged rather than changed: the body still
cites
#32,#39and#42bare rather than as full URLs. Thosepredate this rework, two reviews have passed over them, and Gitea
autolinks them in the commit view — but the authorised scope here was
the network sentence plus narration cuts, so expanding them would have
been a change nobody asked for. Say the word and they become full URLs
in a fourth amend.
Re-review of
d4eaf5f(message-amend only), scoped to the one finding from #47 (comment). PASS.Tree proved unchanged, not accepted:
73ea8a5andd4eaf5fshare tree85fa3ae0483d1cb79046f7f759bd31bd6b59f477, same parente6a9171, same author date;git diff 73ea8a5 d4eaf5fempty. Only the committer date moved. Prior finding fixed: the false "linting needs no network beyond pulling the image" and the stale README-describing sentence are both gone, replaced by an accurate account that matchesDockerfile.lint:19,49-54,script/lint:6-10andREADME.md:478-492. Every other claim in the body checks out against the tree. Narration cut is -83 words and removed no trap, no disclosure and no evidence line; the cold-cache network caveat is a net-new disclosure.Anomalies and disclosures, none blocking:
make lint,make checkandmake dockerall catch drift" from the drift-guard paragraph. It is a coverage claim rather than evidence, and it survives verbatim atREADME.md:522-523and in thescript/verify-lint-image-pinheader, so nothing left the record.script/lint:9andREADME.md:486, so not a false claim, but it is narrower than the old wording.check / check (push)=success, "Successful in 1m38s",updated_at2026-08-10T15:53:50+02:00. Read four times across both the combined-status and statuses-list endpoints; the statuses list holds exactly two rows, id=1pending"Waiting to run" 15:48:01 and id=2success15:53:50, with no spurious duplicate. Disclosure: the Actions runs API 403s for this account, so the job log was not read and green rests on the commit status alone.make checkgreen here, linter demonstrably executed, not cached —#12 [8/8] RUN echo "gate lint, epoch 1786369933-139490" && golangci-lint runwith#12 52.64 0 issues./#12 DONE 56.1s, under the CACHEDCOPY . .layer.make fmt-checkexit 0. Merges cleanly againstmainate6a9171. No attribution trailers, no non-inclusive terms, subject 58 chars with(closes #46), body wrapped at 72.Mechanism, gate ordering and planted-sentinel controls were not re-run: the tree is bit-identical to the one already verified.
Verdict: PASS.