The check workflow ran `script/cibuild`, a plain `docker build .`. With a
warm layer cache the lint and builder stages replayed instead of running,
so a commit could report "Successful in 4s" without being formatted,
linted, tested, or built. Squash-merging an already-built branch onto
`next` hits exactly that path, so no merge was actually validated.
The workflow now writes `.ci-fingerprint` into the build context: the
hash of the last commit that touched the context. Any commit that
changes code gets a new value, invalidates the `COPY . .` layer of both
check stages, and really runs `make fmt-check`, `make lint`, `make test`
and `make build`. A docs-only commit leaves it unchanged and still
replays from cache in seconds, as `.dockerignore` already intends. The
module download layer sits above `COPY . .` and stays cached either way,
so this is cheaper than a scoped `--no-cache-filter`. `script/cibuild`
is a model script shared across repos and is left untouched.
The second half of the problem was that Gitea cancels the in-flight run
when a newer commit lands on the same branch and records the
cancellation as `failure`, marking commits red that were never tested.
That cancellation is unconditional server-side for push events, so the
superseding run now rewrites the exact `Has been cancelled` status to
`skipped`. Genuine failures are never touched.