Re-vendor the canonical files from sneak/prompts at e3f79c4 (closes #95)
check / check (push) Waiting to run

Every vendored file, REPO_POLICIES.md and every model script is the
copy at sneak/prompts e3f79c4, with this repository's own entries kept
after the canonical content. Lint and test are phases of the Dockerfile
that write no image. Every build in script/ passes a new CACHEBUST
value, which every stage declares first, so every RUN step runs again;
the steps that compile keep Go's build cache, and both go test runs
pass -count=1. make test runs the suite under the race detector as
nobody. Dockerfile.lint, script/verify-lint-image-pin and
make test-race are gone. Prettier runs on the host.
.claude/settings.json is deleted.

Deviation: the set comes from e3f79c4 on next rather than dd4027b.
Judgement call: the test phase mounts the cache at nobody's
/tmp/.cache/go-build, owned by nobody.

Model: opus-5-5
This commit is contained in:
clawbot
2026-10-15 15:03:44 +00:00
parent 92c02c3bd1
commit 32e7fd095a
25 changed files with 995 additions and 700 deletions
+65 -95
View File
@@ -29,6 +29,20 @@
# Completed Steps
- re-vendor the canonical files and model scripts from `sneak/prompts` `next` at
`e3f79c4`: golangci-lint v2.14.0; lint and test are phases of the `Dockerfile`
that write no image, and `make test` runs the suite under the race detector,
so `Dockerfile.lint`, `script/verify-lint-image-pin` and `make test-race` are
gone; every `docker build` in `script/` passes a new `CACHEBUST` value, which
every stage of the `Dockerfile` declares first, so every `RUN` step runs on
every build, while the steps that compile keep Go's build cache and the tests
run with `-count=1`; prettier runs on the host, from the node and yarn
`script/bootstrap` installs, so the `prettier` and `markdown` stages are gone
and the build stage installs `git` and `make` itself; a new push cancels the
workflow's older run on the same branch, and a run stops after 20 minutes;
`.claude/settings.json` is deleted (2026-10-15,
https://git.eeqj.de/sneak/sfdupes/issues/95)
- with `-x`, an operand under another operand is walked on its own instead of
being dropped when a directory from it (from its directory, for a regular
file) up to but not including that operand is on a different filesystem than
@@ -60,19 +74,26 @@
https://git.eeqj.de/sneak/sfdupes/issues/12)
- cut the narration from `TODO.md` Completed Steps and from the comments in
`script/` and both Dockerfiles; §Workflow now branches from and merges to
`next` (2026-10-04, https://git.eeqj.de/sneak/sfdupes/issues/49)
`script/`, `Dockerfile` and `Dockerfile.lint` (gone since
https://git.eeqj.de/sneak/sfdupes/issues/95); §Workflow now branches from and
merges to `next` (2026-10-04, https://git.eeqj.de/sneak/sfdupes/issues/49)
- `make test-race` runs the test suite under the race detector in a cgo-enabled
- `make test-race` ran the test suite under the race detector in a cgo-enabled
container, outside `make check` (2026-10-04,
https://git.eeqj.de/sneak/sfdupes/issues/18)
https://git.eeqj.de/sneak/sfdupes/issues/18). Since
https://git.eeqj.de/sneak/sfdupes/issues/95 `make test` itself runs the suite
under the race detector, in the `Dockerfile`'s Debian-based `test` phase, and
`make test-race` is gone
- a bare `docker build .` fails with a message naming `script/cibuild` and
`script/docker` instead of serving the gates from cache (2026-10-04,
https://git.eeqj.de/sneak/sfdupes/issues/39)
- a bare `docker build .` failed, naming `script/cibuild` and `script/docker`,
rather than serve the gates from cache (2026-10-04,
https://git.eeqj.de/sneak/sfdupes/issues/39). Since
https://git.eeqj.de/sneak/sfdupes/issues/95 a bare build succeeds, as
`REPO_POLICIES.md` requires, and may serve the gates from cache; the builds in
`script/` pass a new `CACHEBUST` value, so theirs always run
- `make fmt` and `make fmt-check` run prettier over all Markdown, in Docker, and
CI checks it; all Markdown reformatted (2026-10-04,
- `make fmt` and `make fmt-check` run prettier over all Markdown, and CI checks
it; all Markdown reformatted (2026-10-04,
https://git.eeqj.de/sneak/sfdupes/issues/19)
- `script/lint` writes no image, so a run no longer leaves an untagged one
@@ -115,9 +136,12 @@
- README documents install, Docker, a daily cron scan and how to read and check
the reports (2026-10-04, https://git.eeqj.de/sneak/sfdupes/issues/54)
- the `Dockerfile` build stage keeps the Go module cache out of `builder`'s home
and copies the sources with `--chown`, so no `chown -R` walks them
(2026-10-04, https://git.eeqj.de/sneak/sfdupes/issues/43)
- the `Dockerfile` build stage kept the Go module cache out of `builder`'s home
and copied the sources with `--chown`, so no `chown -R` walked them
(2026-10-04, https://git.eeqj.de/sneak/sfdupes/issues/43). Since
https://git.eeqj.de/sneak/sfdupes/issues/95 there is no `builder` user and
nothing changes owner: the build stage only compiles, as root, and the tests
run as `nobody` in the `test` phase
- `--version` prints `sfdupes VERSION` to stdout; README documents it and
`--help` (2026-10-04, https://git.eeqj.de/sneak/sfdupes/issues/15)
@@ -181,97 +205,46 @@
- fix the lint-image pin comments and `FROM` form in `Dockerfile` and
`Dockerfile.lint` (2026-08-10, branch `next`, closes
https://git.eeqj.de/sneak/sfdupes/issues/25): both pins are now the policy
https://git.eeqj.de/sneak/sfdupes/issues/25): both pins became the policy
`# image:vX.Y.Z, YYYY-MM-DD` comment over a bare `FROM image@sha256:...`,
without the false `(Debian-based)` note or the tag; digest unchanged.
`script/verify-lint-image-pin` still matches the tagless form, and a tag on
one side only is caught as a plain mismatch.
without the false `(Debian-based)` note or the tag. Since
https://git.eeqj.de/sneak/sfdupes/issues/95 the `Dockerfile`'s `lint` phase
holds the only golangci-lint pin, in that form, so `Dockerfile.lint` and
`script/verify-lint-image-pin`, which compared the two pins, are gone.
- run all linting in Docker via `Dockerfile.lint` and `script/lint` (2026-08-10,
branch `next`, closes https://git.eeqj.de/sneak/sfdupes/issues/46): per the
owner ruling the linter is never installed on a host. `Dockerfile.lint` copies
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;
`script/lint` builds it. `script/bootstrap` no longer installs or pins the
linter, and warns rather than fails when `docker` is absent;
`ENV PATH=/home/builder/go/bin:$PATH` went with its `go install`.
`script/verify-linter-pin` is retired; `script/verify-lint-image-pin`, a gate
in both files, compares their two `FROM` lines and restates neither pin.
Traps: an unchanged tree lets a lint build pass in under a second having run
no linter, so every gate `RUN` references `ARG CHECK_EPOCH` (BuildKit hashes
the expanded command) and `script/lint` passes `"$(date +%s)-$$"`, the PID
because two runs land in the same second easily. Nothing inside an image build
may shell out to docker, so the `Dockerfile` lint stage calls `golangci-lint`
directly and the build stage runs `make test` and `make fmt-check` instead of
`make check`, through `make` because the Makefile's `export CGO_ENABLED = 0`
only reaches what it invokes. `COPY --from=lint /src/go.sum /dev/null`
replaces the copied linter binary as the only edge making the build stage wait
for lint; dropping it would end fail-fast linting under a still-green build.
`golangci-lint config verify`, included per the ruling, validates from an
embedded schema with no network call, but `go mod download` above the gates
still needs the network on a cold cache. Verified: `make lint` green with no
`golangci-lint` on `PATH`; two back-to-back `script/lint` runs on an untouched
tree both ran the linter (27.7s and 28.7s in the lint step, `COPY . .`
`CACHED` above); a planted unused variable failed `script/lint`, and failed
`make docker` at `[lint 9/9]` with the build stage stopped at
`[builder 3/12]`; the drift guard fails on a tag-only, a digest-only and an
unreadable reference, naming both sides; under `--network none` config verify
passes a valid config and rejects an invalid one; `make docker` green in 5m35s
with all six gates run under one epoch (lint 37.6s, test 25.2s reporting
`ok sneak.berlin/go/sfdupes 1.938s coverage: 88.5%`, not `(cached)`); in the
builder image with the Go test cache off, `--user 0:0` still fails
`TestScanHardlinkRunFailsTogether` where the unprivileged user passes. Noted
for follow-up, not fixed here: `golangci-lint` warns that `gomodguard` is
deprecated since v2.12.0 in favour of `gomodguard_v2`.
owner ruling the linter is never installed on a host, and
`golangci-lint config verify` runs before `golangci-lint run`.
`script/bootstrap` stopped installing or pinning the linter, and
`script/verify-linter-pin` was retired. Since
https://git.eeqj.de/sneak/sfdupes/issues/95 both commands run in the
`Dockerfile`'s `lint` phase, which `script/lint` builds alone and the build
stage depends on through `COPY --from=lint /src/go.sum /dev/null`;
`Dockerfile.lint` and `script/verify-lint-image-pin` are gone. Nothing inside
an image build may run docker, so the phase calls `golangci-lint` directly.
- install the Docker build stage's prerequisites by running `script/bootstrap`
instead of `apk add --no-cache make` inline (2026-08-09, branch
`dockerfile-bootstrap`, closes https://git.eeqj.de/sneak/sfdupes/issues/42):
the stage copies `script/` plus `go.mod`/`go.sum` and runs `script/bootstrap`,
which ends in `go mod download`, so the separate call to it is gone.
`COPY --from=lint /usr/bin/golangci-lint` stays and moves above the bootstrap
layer: it is the only edge making this stage depend on the lint stage, so
deleting it would end fail-fast linting silently. A new
`script/verify-linter-pin`, run in the build stage before bootstrap, fails the
build naming both versions unless that copied binary is the version
`script/bootstrap` pins; a pin it cannot read is a hard failure, not a skip.
`$GOPATH/bin` joins `PATH`, where bootstrap's `go install` lands. Everything
added sits above `ARG CHECK_EPOCH`, and the `chown` and `USER builder` still
precede `make check`. Verified: the guard fails the build with both versions
named when the lint stage's linter is faked to another version, and passes an
unmodified build; bootstrap runs clean under Alpine's `sh` and `apk`, finding
the copied linter already at the pin; a second build served the bootstrap and
dependency layers `CACHED` while both gates ran with a fresh epoch; a planted
`unused` finding failed the build at the lint gate in 48.9s with the build
stage's `make check` never starting; and the suite run in the image as
`--user 0:0` fails `TestScanHardlinkRunFailsTogether`, so the drop to the
unprivileged user is still needed. That last check needs the Go test cache
off: as root it first reported `ok ... (cached)`, reusing the build-time
result. Build times on a noisy shared host: 2m13s on an unchanged tree, 2m17s
and 4m29s after a source change, 5m14s cold, which breaches the policy
ceiling; `chown -R builder:builder /src /home/builder` walks the module cache
and alone varied from 77s to 210s across those builds, and `main` measured
5m03s cold with a 209s `chown`. Filed as
https://git.eeqj.de/sneak/sfdupes/issues/43
`dockerfile-bootstrap`, closes https://git.eeqj.de/sneak/sfdupes/issues/42),
with `script/verify-linter-pin` failing the build unless the linter copied
from the lint stage was the version `script/bootstrap` pinned. Builds then
took up to 5m14s cold, mostly in a `chown -R` of the module cache, filed as
https://git.eeqj.de/sneak/sfdupes/issues/43. Since
https://git.eeqj.de/sneak/sfdupes/issues/95 the build stage installs `git` and
`make` with `apk add --no-cache` and only compiles; the `lint` and `test`
phases are the gates
- bust the Docker layer cache for the gate steps, so `script/cibuild` and
`script/docker` cannot report a green they did not earn (2026-08-09, branch
`cibuild-cache-bust`, closes https://git.eeqj.de/sneak/sfdupes/issues/32): the
`Dockerfile` copies the tree before its gates, so on an unchanged tree Docker
served them from cache and the build exited 0 having run nothing. Both scripts
now pass `--build-arg CHECK_EPOCH="$(date +%s)"`. `ARG` is per stage and the
gates span two stages, so it is declared in both; BuildKit hashes the expanded
command, so each gate `RUN` echoes the epoch, which also logs it as evidence
the layer ran. It sits below the dependency layers so they stay cached.
Verified under `BUILDKIT_PROGRESS=plain`, each script run twice back to back
on an unchanged tree: all three gates ran on all four runs with a fresh epoch
(`script/cibuild` 78.8s then 61.1s; `script/docker` 61.1s then 53.4s), and
thirteen steps were still served `CACHED`. With a planted `unused` finding the
build failed at `make lint` in 36.1s and the build-stage `make check` never
started. Run as root, the same image fails `TestScanHardlinkRunFailsTogether`,
because root reads through the `chmod(0)` the test relies on, so the build
stage must drop to the unprivileged `builder` user. Local fix only;
propagating it to the canonical templates is
https://git.eeqj.de/sneak/prompts/issues/26
served them from cache and the build exited 0 having run nothing. The fix was
a `CHECK_EPOCH` build argument; since
https://git.eeqj.de/sneak/sfdupes/issues/95 every `docker build` in `script/`
passes a new `CACHEBUST` value instead, which every stage of the `Dockerfile`
declares first. Run as root, the tests fail
`TestScanHardlinkRunFailsTogether`, because root reads through the `chmod(0)`
the test relies on, so the `test` phase runs them as `nobody`
- check the installed golangci-lint version in `script/bootstrap` instead of
only its presence (2026-08-09, branch `bootstrap-version-check`, closes
https://git.eeqj.de/sneak/sfdupes/issues/24): the version lives only in
@@ -427,6 +400,3 @@ Accepted divergences (no action):
- flat single-package layout with `.go` files in the repo root — fine for a
small single-binary tool per the Go styleguide; the tracker audit agrees
- `make test` runs without `-race` — the repo mandates `CGO_ENABLED=0` (pure-Go
builds) and the race detector requires cgo, so the detector runs in a separate
cgo-enabled container, `make test-race`, which is not part of `make check`