Make the CI gate execute the checks it reports on (closes #119) #138
Reference in New Issue
Block a user
Delete Branch "issue-119-real-ci-gate"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #119.
What changed
Repo-local only:
.gitea/workflows/check.yml,Dockerfile,.dockerignore,.gitignore,README.md.script/cibuildis a model script shared across repos and is untouched — see "Layer decision" below.Cache replay. The workflow writes
.ci-fingerprintinto the build context before callingscript/cibuild. Its value is the hash of the last commit that touched the build context. Any commit that changes code — including a squash merge whose tree matches an already-built branch — gets a new value, which invalidates theCOPY . .layer of both thelintandbuilderstages, somake fmt-check,make lint,make testandmake buildreally execute. A docs-only commit leaves the value unchanged and the image replays from cache in seconds, which is what.dockerignoreexcluding*.mdalready intends. Thego mod downloadlayer sits aboveCOPY . .and stays cached either way, so this is cheaper than--no-cache-filter=lint,builder, which would also redo the module download.Note the review established a stronger property than this mechanism alone provides: because
.dockerignoreexcludes no Go file, Docker's ownCOPY . .content hash already invalidates on any code change independently of the fingerprint. Verified by pinning the fingerprint to a fully-cached value and adding a failing test — the build still went red. The fingerprint is belt-and-braces, not the sole guard.Cancelled runs recorded as failures. Gitea cancels the in-flight run when a newer commit lands on the same branch and maps
StatusCancelledtoCommitStatusFailure, so a commit that was never tested reads red. Both behaviours are server-side and unconditional for push events in 1.25.4, and no workflow key disables them —concurrencyis not consulted on this path. The superseding run therefore rewrites the exactfailure/Has been cancelledstatus on recent ancestor commits toskipped/ "Superseded by a newer commit; never tested". Only that exact pair is matched, so a genuine failure is never overwritten.skippedis Gitea's neutral state: non-blocking in the combined-status calculation, unlikepending, which would leave those commits blocked forever.Layer decision
Everything here is repo-local.
script/cibuildis untouched by this PR and still runs a plaindocker build .. Nothing in this PR is pending an org-wide decision.Correction to an earlier version of this description, which claimed
script/cibuild"stays byte-identical to the model script": it is NOT byte-identical today. Its header comment has pre-existing drift from the model, tracked at #147. The executable body IS identical, and this PR neither caused the drift nor changes it.Baking
--no-cache-filter=lint,builderinto the sharedscript/cibuildwould have been the org-wide alternative. It is not needed here, and it is strictly worse for this repo: it discards the module-download layer as well, and it removes the cheap docs-only path the issue asks to keep, since the script cannot tell a docs commit from a code commit.Acceptance check
Run on scratch branch
ci-gate-acceptance-119(since deleted), against this exact change set.A deliberately broken test makes CI go red. Commit
94ee42fadded a test whose body ist.Fatal: https://git.eeqj.de/sneak/webhooker/actions/runs/155 —failure, "Failing after 2m8s". The lint stage ran for real (59.5s) and the test stage caught the break:In the same log the
COPY . .layers areDONE, notCACHED, whileRUN go mod downloadisCACHED— the fingerprint invalidated exactly the intended layers.A real commit runs the checks instead of replaying them. https://git.eeqj.de/sneak/webhooker/actions/runs/142 —
success, "Successful in 3m57s". For comparison,nextbefore this PR reads "Successful in 4s" and "Successful in 5s".A docs-only commit stays cheap. https://git.eeqj.de/sneak/webhooker/actions/runs/147 —
success, "Successful in 9s", every stageCACHEDincludingRUN make lintandRUN make test.A superseded run no longer leaves a red commit. Commit
c35abbbwas superseded mid-run; Gitea recorded https://git.eeqj.de/sneak/webhooker/actions/runs/149 asfailure/ "Has been cancelled". The next run rewrote it, combined state nowsuccessrather thanfailure. The same step also cleared the three false reds this issue reported onnext(e50a79c,15a6117,d51cd0f) on its first run.Gate evidence
make checkon the rebased branch: exit 0 (0 issues., all tests pass).script/cibuildafter changing only.ci-fingerprint(no source change): 3m49s, withRUN make lint79.8s,RUN make test70.9s,RUN make build66.2s — noneCACHED.script/cibuildwith the fingerprint unchanged: 1.256s, 22 layersCACHED, exit 0 — the false green this issue is about, reproduced and now reachable only when the context genuinely did not change.No cache was pruned; invalidation was scoped to the context fingerprint throughout.
Notes
TODO.mdis untouched, per #112.PASS.
Gate verified independently on the PR head, cache warmed on a baseline fingerprint first:
COPY . .DONE (not CACHED) in both stages;make fmt-check4.4s,make lint75.1s,make test71.7s,make build55.9s all DONE;go mod downloadCACHED. 219s total.make lintran 63.2s,make testfailed). Docker's ownCOPY . .content hash invalidates independently of the fingerprint, so a stale fingerprint cannot manufacture a green.git logpathspec: squash merge (this repo's default style), 3-way merge with code on both sides, and a fresh branch each yield a new fingerprint; empty commits, docs-only commits, and a merge tree-identical to its branch reuse it — in every one of those the build context is byte-identical, so the replay is honest. Docs-only history returns empty and falls back to$GITHUB_SHA, i.e. a full build.Two items to correct or track, neither blocking:
The PR body states
script/cibuild"stays byte-identical to the model script". It is not. Against https://git.eeqj.de/sneak/prompts/raw/branch/main/script/cibuild only the header comment differs (script/cibuildlines 2-5 against the model's lines 2-3); the executable body is identical. The divergence is pre-existing and byte-for-byte the same onnext, so it is not introduced here and does not block this PR — but the claim as written is inaccurate, and the drift deserves its own issue..gitea/workflows/check.yml:32hardcodesctx='check / check (push)'. Rename the workflowname:or the job id and the match silently stops firing, restoring the false-red behaviour with no signal at all. Deriving it as"${GITHUB_WORKFLOW} / ${GITHUB_JOB} (${GITHUB_EVENT_NAME})"would keep it self-maintaining.Disclosures: I did not push a scratch branch — the broken-test acceptance check was reproduced locally through the same Dockerfile gate, not server-side, so the Gitea cancellation path itself is verified only from its recorded statuses.
clawbotgets 403 on the Actions API, so I could not read run 155's log and do not confirm it beyond the author's report. Via the commit-status API I did confirm head5ac43e6issuccess/ "Successful in 3m0s" (run 156 — a real run, not a 4s replay) and thate50a79c,15a6117,d51cd0fnow readskipped/ "Superseded by a newer commit; never tested". The rewrite step fails closed:set -euwithcurl -sfaborts the job if the API URL, token, or POST is bad. Theskipped-folds-to-successcaveat the author disclosed is confirmed and I concur with it overpending, which would block those commits permanently.Also clean: merges into current
next; basenext; single commit; title ends(closes #119); no attribution trailers or vendor references;make fmt-checkclean withgit statusclean (.ci-fingerprintcorrectly gitignored);TODO.mduntouched; all external references hash-pinned; lint stage andCOPY --from=lint /src/go.sum /dev/nullintact.