Commit Graph

7 Commits

Author SHA1 Message Date
54c43407d4 Make the test gate unfakeable and stop test-integration lying (closes #93)
All checks were successful
check / check (pull_request) Successful in 2m58s
script/test omitted -count=1, so Go's test result cache could satisfy
the gate outright. A cached package prints `ok <pkg> (cached)`, and
that is an `ok` line: two back-to-back `make test` runs on the parent
commit produced the same 14 `ok` lines, the second in 0.42s with every
line marked (cached), having executed no test at all. The evidence
signal this repo leans on was therefore forgeable. This sits one level
below the Docker layer cache #85 addressed: CHECK_EPOCH forces the
`RUN make test` step to re-execute, but a GOCACHE baked into an earlier
image layer would survive into the re-executed step, so the step can
re-run and still do no work.

Add -count=1 unconditionally rather than only in the containerised
path. The pre-commit hook runs this same script, and a gate that is
honest only in CI is dishonest exactly where people rely on it most.
test-coverage gets the same flag -- a coverage profile assembled from
cached results describes a run that did not happen -- and script/check
inherits it by calling script/test.

Delete make test-integration rather than making it real (option (b) of
issue #69). No file in the repo carries a build tag, so -tags=integration
selected nothing extra and the target was an exact duplicate of make
test, while internal/vaultik/integration_test.go ran unconditionally on
every make test -- the opposite of what a reader of the Makefile would
conclude. Tagging a subset was rejected because the entire suite runs in
well under a minute, so gating would save seconds in exchange for a
build-tag scheme and a second CI path that must be kept wired up; in a
repo that has now found five distinct ways for a gate to report an
unearned green, a mechanism whose failure mode is "some tests silently
stopped running" is a bad trade. Deleting it also means nothing can
drop out of CI coverage, since nothing is conditional. The Makefile and
README now say plainly that make test runs everything.

Raise -timeout from 30s to 120s, after measuring rather than assuming.
The standing claim that cold-cache compilation is charged against
-timeout is false: -timeout reaches the test binary as -test.timeout
and its clock starts inside testing.M.Run, after compilation and
linking. A containerised run with an empty GOCACHE spent 46s compiling
and still reported per-package durations within noise of a warm host
run. The real exposure was margin, not compilation: against the slowest
package's worst observed time, 10.2s for internal/database on a cold
run, 30s left only 2.9x, thin for a loaded CI runner, and each fresh
measurement of that package has come in above the last. -timeout is a
hang backstop, not a performance budget, so it should sit far above the
slowest legitimate runtime; 120s leaves about 12x while still bounding
a hung package, including the verbose rerun, to a few minutes.

120s is a deliberate divergence from REPO_POLICIES.md:192, which
mandates a 30-second timeout, and from that file's canonical Go recipe
at :212-214. REPO_POLICIES.md is org-canonical and not editable from
this repo, so the divergence and its reasoning are recorded in
script/test's comment and in TODO.md, and issue #101 proposes amending
the policy text upstream. That issue also carries the trade this
surfaces: the verbose rerun makes a hung package pay the timeout twice,
which at 120s puts a hang-case Docker build over the same policy's
five-minute build limit.

Both invocations in script/test now share one run_tests function so the
quiet run and the verbose rerun cannot drift apart in flags.

Measurements and the full verification are recorded once, on the pull
request, and deliberately not restated here or in TODO.md.
2026-08-09 14:15:44 +00:00
50816b7415 Make a missing CHECK_EPOCH fail the build (closes #91)
All checks were successful
check / check (push) Successful in 3m2s
PR #89 stopped script/cibuild replaying cached check layers, but left a
gap: a bare `docker build .` with no --build-arg still faked. An unset
ARG is an empty string, an empty string is a stable cache key, and the
check layers replay from it. That gap mattered because REPO_POLICIES.md
names `docker build .` verbatim as a command that must be green, so the
documented command was the one that lied.

Both check stages now carry `RUN [ -n "$CHECK_EPOCH" ] || exit 1`
immediately under their own ARG. Failed steps are never cached, so this
fails on every invocation rather than once - a bare build now stops with
a named error instead of reporting a green it did not earn. Each stage
needs its own guard because ARG scope is per-stage; a gate-carrying stage
without one is a silent hole if ordering ever changes.

The check RUNs now reference the value (`echo "check epoch: ${CHECK_EPOCH}"
&& make <target>`), so the cache miss is contractual rather than resting
on BuildKit's current treatment of unreferenced ARGs, and the epoch is
visible in the build log.

The epoch becomes "$(date +%s%N)$$" so concurrent invocations in the same
second cannot collide. busybox silently drops %N and exits 0, so $$ is
what makes it correct there. The bare-assignment form is retained
deliberately: inlining the substitution into --build-arg would, under
set -eu, yield an empty and therefore constant epoch without aborting.

script/docker gets the same treatment - it is not the gate, but two
entrypoints disagreeing about whether the tree is green is its own
hazard, and local builds are almost always warm.

Verified by negative control rather than inspection: a bare build fails
twice consecutively here and succeeds twice on the parent commit, so the
change is demonstrably not a no-op. The builder-stage guard was fired
directly with a targeted probe build, since the lint stage otherwise
fails first and would leave it unexercised.
2026-08-09 10:09:27 +02:00
c3bb3b5580 Make script/cibuild unable to report an unearned green (closes #85)
All checks were successful
check / check (push) Successful in 3m13s
script/cibuild was a bare `docker build .`. On an unchanged tree Docker
served the check RUN layers from cache, so make fmt-check, make lint and
make test never executed - and the build still exited 0. Measured at
221ms with zero ok lines and every check layer CACHED, against 162s for a
real run. CI showed the same signature: 6 second "successes" on main.

An ARG CHECK_EPOCH now sits immediately above the check RUNs in both
stages - each stage declares its own, since ARG scope is per-stage - and
script/cibuild passes a fresh value per invocation. Dependency and module
layers sit above the ARG and still cache, so this does not make every
build cold.

The epoch is assigned before the build rather than inlined into the
--build-arg. Under `set -eu` a command substitution that fails inside an
argument does not abort the script: CHECK_EPOCH would become an empty
string, an empty string is a constant, a constant CHECK_EPOCH restores
the cached false green, and the guard would silently disarm itself while
still exiting 0. As a bare assignment, set -e catches a failing date and
no build starts.

The README and Dockerfile state the guarantee conditionally. It holds per
build context and CHECK_EPOCH value, and depends on script/cibuild
passing a fresh one - a bare `docker build .` with no --build-arg still
replays the check layers from the second consecutive run onward. That
residual gap is tracked in #91 along with the remaining upstream
hardening.

Verification is recorded once, in the PR's verification comment, rather
than restated with differing numbers in three places.
2026-08-09 09:37:55 +02:00
af607e3597 Run the linter at the pinned version locally too (closes #78)
All checks were successful
check / check (push) Successful in 6s
script/lint ran bare golangci-lint from PATH while CI and the Dockerfile
pinned v2.12.2 by digest, so make lint and CI could disagree about
findings. That drift ran both directions: it produced two false green
claims during the lint remediation, and on an ambient 2.10.1 it also
reported four gosec findings on a tree CI linted clean.

script/lint now extracts the image reference - tag and digest - from the
Dockerfile lint stage FROM line and runs that exact image under docker.
The Dockerfile FROM line is the single source of truth for the linter
version; the duplicate pins in the Makefile deps target and in
script/bootstrap are removed rather than kept in sync.

A golangci-lint on PATH is used only when its version exactly equals the
pin, which is what makes the in-container lint stage work (the Dockerfile
runs make lint inside the pinned image, where there is no docker daemon).
Any other version, or none, goes through docker. When docker is
unavailable the script fails with an actionable message and never falls
back to a different linter version.

script/lint-fix delegates to script/lint --fix so autofixes come from the
pinned linter too. The container mounts persistent build and module
caches and runs as the invoking uid/gid.

Verified by reinstating the four historical nolint directives that 2.10.1
requires and 2.12.2 reports as unused: the old script passed on that tree
and the new one fails with four nolintlint findings.
2026-08-09 04:52:22 +02:00
04fce150bc Add script/lint-fix entrypoint and make lint-fix shim (refs #61) 2026-08-07 16:40:59 +00:00
c9c72ef29d script/bootstrap: install sqlite3, which the test suite shells out to 2026-08-07 16:29:45 +00:00
43346e62db Adopt scripts-to-rule-them-all: script/ entrypoints, Makefile shims 2026-07-07 01:53:18 +02:00