Isolate the lint cache and context-gate the native path (closes #99)
All checks were successful
check / check (push) Successful in 2m23s
All checks were successful
check / check (push) Successful in 2m23s
Closes #80. script/lint pointed GOLANGCI_LINT_CACHE at a path shared by every worktree of this repo. Two worktrees have identical Go file contents, so their cache keys collided and one tree's stored findings replayed for another, paths included - observed as 231 findings all citing another session's worktree, with no parallel-runner message to signal it. The failure is symmetric and only one direction is loud: a clean tree failed by a dirty sibling gets investigated, a dirty tree passed by a clean sibling does not. The cache is now keyed per worktree on a digest of $ROOT, and remains persistent. Independently of that, script/lint-audit inspects every run's output and fails the run if any finding cites a path outside the tree being linted. That guard is the load-bearing part: it converts a silent unearned green into a hard error regardless of how the cache is keyed. It is deliberately built so it can never certify a pass, only reject, so it cannot itself become a gate that reports green. The native path was gated on version equality alone, which admitted a locally installed matching binary and bypassed the digest pin. It now requires VAULTIK_LINT_IN_CONTAINER=1, set only by the Dockerfile lint stage, in addition to version equality. /.dockerenv was rejected as the signal because dockerd creates it for `docker run` but not reliably during a BuildKit `docker build`, which is the case the exception exists for. A version mismatch inside the container is now a hard error rather than a fall-through. This mattered more than the issue supposed: on this host a matching golangci-lint exists on PATH, so script/lint was taking the native path and linting against the global cache without ever running the pinned image. That is the likely root of the observed contamination, and it is closed here rather than mitigated. The parallel-runner error is retried rather than reported. It is not a lint result, and surfacing it as a non-zero exit is indistinguishable to a caller from real findings; exhausted retries fail saying the tree was never analysed. Note that a private cache alone does not remove lock contention - measured with two concurrent runs using separate cache directories. script/bootstrap no longer reports success on a machine that cannot run the gate: docker is now required by lint, check and precommit, so a missing binary or unreachable daemon is a hard failure naming what will not work.
This commit was merged in pull request #102.
This commit is contained in:
44
TODO.md
44
TODO.md
@@ -18,6 +18,43 @@ Define remaining scope for a first tagged release and cut v0.1.0.
|
||||
|
||||
# Completed Steps
|
||||
|
||||
- 2026-08-09: Isolated the lint cache per worktree and context-gated the
|
||||
native lint path (issues #99, #80). One defect seen twice:
|
||||
`script/lint` decided whether it could skip the pinned image by asking
|
||||
what version was on `PATH` rather than where it was running, and cache
|
||||
isolation is part of that same question. The cache was one directory
|
||||
per repo, shared by every worktree on the host, so two checkouts with
|
||||
identical Go file contents collided and golangci-lint replayed the
|
||||
stored analysis — paths and all. The loud direction of that failure
|
||||
(a clean tree failed by a dirty sibling) is the harmless one; the
|
||||
silent direction, a dirty tree **passed** by a clean sibling, is a
|
||||
sixth way for a gate here to report a green it did not earn. The cache
|
||||
is now keyed on a digest of the worktree path, and every run is
|
||||
audited by the new `script/lint-audit`, which rejects output citing any
|
||||
file that is not in the tree being linted — a backstop that runs on
|
||||
clean output too, because that is the case nobody investigates. Caches
|
||||
record the worktree they belong to and are collected when it
|
||||
disappears, so throwaway worktrees do not accumulate them; the whole
|
||||
tree lives under `XDG_CACHE_HOME` and is disposable. The
|
||||
`parallel golangci-lint is running` refusal is now a bounded retry
|
||||
rather than a verdict: it is not a lint result, and exiting non-zero
|
||||
on it is indistinguishable to a caller from real findings (#88 showed
|
||||
a private cache does not remove that contention). The native path now
|
||||
requires `VAULTIK_LINT_IN_CONTAINER=1`, set only by the `Dockerfile`
|
||||
lint stage, in addition to matching the pin, so a developer's locally
|
||||
installed 2.12.2 no longer bypasses the digest pin; `/.dockerenv` was
|
||||
rejected as the signal because `dockerd` creates it for `docker run`
|
||||
and it is not reliably present during a BuildKit `docker build`, which
|
||||
is the case the exception exists for. Version detection uses
|
||||
`golangci-lint version --short` with the old banner scrape kept only
|
||||
as a fallback. `script/bootstrap` no longer prints `bootstrap
|
||||
complete` on a machine that cannot run the gate: a missing docker, or
|
||||
one whose daemon is unreachable, is a hard failure naming exactly what
|
||||
breaks. Verification was by reproduction rather than inspection — two
|
||||
concurrent lints from two worktrees of differing cleanliness, a real
|
||||
run made to report an outside path, a matching linter shimmed onto
|
||||
`PATH`, and a `PATH` with docker removed — and is recorded on the pull
|
||||
request.
|
||||
- 2026-08-09: Closed the fifth false-green mechanism (issues #93, #69).
|
||||
`script/test` omitted `-count=1`, so Go's test result cache could
|
||||
satisfy the gate outright: a second back-to-back `make test` printed
|
||||
@@ -198,8 +235,11 @@ Define remaining scope for a first tagged release and cut v0.1.0.
|
||||
exactly the pinned one (which is how the lint stage runs it inside the
|
||||
container); anything else goes through Docker, and a missing or
|
||||
unreachable Docker daemon is a hard error rather than a silent
|
||||
fallback. `make check` is therefore now as trustworthy as
|
||||
`script/cibuild`.
|
||||
fallback. Only the **lint** leg of `make check` became equivalent to
|
||||
`script/cibuild`; its tests and `gofmt` still run on the host against
|
||||
the host toolchain, as `README.md` states. An earlier version of this
|
||||
entry claimed `make check` was "as trustworthy as `script/cibuild`"
|
||||
outright, which overstated it; corrected under issue #80.
|
||||
- 2026-08-09: Finished the lint remediation under the canonical
|
||||
`.golangci.yml` (issue #61, which also unblocks issue #59). The
|
||||
remaining findings were fixed behavior-preservingly: `wsl_v5`
|
||||
|
||||
Reference in New Issue
Block a user