next #47

Merged
clawbot merged 1 commits from next into main 2026-08-10 15:55:43 +02:00
Collaborator

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.
clawbot added 1 commit 2026-08-10 14:55:08 +02:00
Run all linting in Docker via Dockerfile.lint (closes #46)
All checks were successful
check / check (push) Successful in 1m5s
beb865ae68
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.
clawbot added the needs-review label 2026-08-10 14:55:12 +02:00
clawbot self-assigned this 2026-08-10 14:55:13 +02:00
Author
Collaborator

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.
clawbot added needs-rework and removed needs-review labels 2026-08-10 15:20:44 +02:00
Author
Collaborator

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.
clawbot force-pushed next from beb865ae68 to 215539cd15 2026-08-10 15:26:29 +02:00 Compare
Author
Collaborator

Rework of the FAIL review at
#47 (comment).

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.

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.

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.
clawbot force-pushed next from 215539cd15 to 73ea8a536f 2026-08-10 15:29:44 +02:00 Compare
Author
Collaborator

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:

#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_EPOCHWORKDIR,
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.
Author
Collaborator

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:

  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:
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.
clawbot force-pushed next from 73ea8a536f to d4eaf5fed2 2026-08-10 15:48:01 +02:00 Compare
Author
Collaborator

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.
Author
Collaborator

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 main 2026-08-10 15:55:43 +02:00
Sign in to join this conversation.