Make the tagged-release path work on Gitea (closes #65) #104
Reference in New Issue
Block a user
Delete Branch "fix-release-pipeline"
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 #65.
No tag could be cut from this repo at all. Three independent blockers, one commit.
What changed, per the definition of done
1. goreleaser now targets Gitea.
.goreleaser.yamlgains a top-levelgitea_urls:block (api: https://git.eeqj.de/api/v1,download: https://git.eeqj.de). Without it goreleaser talks to the GitHub API by default, which is whygoreleaser releasecould never have worked here.2. Version comes from git, not from a constant. New
script/versionis the single source of truth:HEADexactly on a tag -> the tag with a leadingvstrippeddev-<12-char sha>-dirtysuffix (untracked files are not counted, matchinggit describe --dirty)Makefile:4VERSION := 1.0.0-rc.1becomesVERSION := $(shell script/version). Thevis stripped so amakebuild and a goreleaser build of one commit report the same string and it matches the archive name: tagv1.0.0->vaultik 1.0.0->vaultik_1.0.0_linux_amd64.tar.gz. Say the word if you would rather see the literalv1.0.0invaultik versionand I will switch it.$(shell)discards exit status, so theMakefilealso refuses to build at all whenscript/versioncannot be run: it stops with an error rather than stamping an empty version into a binary.IsDevVersion("")istruefor the same reason, one layer down, for a binary linked by something other than theMakefile— nothing that knows its version reports no version, so an empty version means the stamping failed and the build is not a release.The snapshot path was fabricating a version too:
snapshot.version_templatewas{{ incpatch .Version }}-next, which invents a release number from the last tag and, with no tags at all, from goreleaser's fabricatedv0.0.0. It now emits the same honestdev-<sha>.That change had one non-obvious consequence worth reviewing:
internal/cli/version.gogated its "this is a development build" notice onglobals.Version == "dev"exactly. The moment untagged builds started carrying a commit sha, that notice would have gone silent and an unreleased binary would have read as a release. The gate is nowglobals.IsDevVersion, a predicate over a string rather than a comparison against a package global specifically so it is testable, and it is tested at the boundary:dev,dev-<sha>,dev-<sha>-dirtyand""are development builds;1.0.0,1.0.0-rc.1,1.0.0-devanddeveloperare not.NewVersionCommandwrites tocmd.OutOrStdout()so the output can be asserted on at all.3. Nothing contradicts anything about the version any more.
Makefilederives from git,internal/globalskeeps its honestdev/unknowndefaults, andTODO.mdno longer says "cut v0.1.0" next to aMakefilebaking1.0.0-rc.1and a milestone named 1.0.0. Its Next Step now names one version target.4. Tag-triggered release workflow, with a pinned toolchain.
.gitea/workflows/release.ymlfires onv*tags, withfetch-depth: 0because a shallow checkout has no tags and would silently mislabel the release.Every input to that job is pinned by sha.
actions/checkoutuses the same pin ascheck.yml.actions/setup-gois pinned at40f1582b2485089dde7abd97c1529aa768e1baff(v5.6.0, node20, matching the node20checkoutalready in use) and reads its version fromgo.modrather than restating it, the wayscript/lintreads the linter version out of theDockerfileFROMline. That step is load-bearing: goreleaser is not a compiler, it shells out togofor thebefore:hook and all four cross-compiles, andcheck.ymldoes all of its work inside the digest-pinned images, so without it a tag either fails at the before-hook or ships binaries built by whatever unpinned Go the runner happens to carry — the one unpinned thing in an otherwise hash-pinned release path.The required secret is
RELEASE_TOKEN: a Gitea access token withwrite:repositoryscope on an account that can publish releases here. It is passed to goreleaser asGITEA_TOKEN, and documented in the newREADME.md"releasing" section. The runner's automatic token is deliberately not used — it is not guaranteed to carry release write scope. Relatedly,script/releaseunsets anyGITHUB_TOKEN/GITLAB_TOKENit finds before publishing: goreleaser picks its forge from whichever token variable is set and refuses to run when it sees more than one, and an unrelated runner-provided token must not get to decide where these artifacts land.5. bootstrap installs goreleaser; the last two non-shim targets became shims.
make release->script/release,make release-snapshot->script/release-snapshot. Both resolve goreleaser exactly the wayscript/lintresolves the linter: a binary onPATHis accepted only when it reports the pinned version, otherwise.tool/bin, otherwise a loud failure naming the installer — never a silent fallback to a different version.The install is
script/install-goreleaser: a specific GitHub release archive (v2.17.1) verified against a hardcoded sha256 perREPO_POLICIES.md, nocurl | shand no@latest. It is a separate script rather than an inline block inscript/bootstrapon purpose:script/bootstraphard-fails on a machine without a usable Docker daemon (deliberately, since Docker gatesscript/lint), while the release runner needs goreleaser and does not need Docker. One script, two callers, one pin.script/bootstrapcalls it; the workflow calls it directly.dist/and.tool/are gitignored, and both are added to.dockerignoreso a local snapshot build does not push a few hundred MB of binaries into every subsequent Docker build context.6.
make release-snapshotworks end to end. Four archives plus checksums.Verification
make release-snapshot— exit 0, 19s,dist/:./dist/vaultik_linux_amd64_v1/vaultik versionon that untagged build reportsvaultik dev-bfe2b673a2e2and prints the development-build notice.With
script/versionremoved,make vaultikstops withMakefile:14: *** script/version produced no version string ..., exits 2, and produces no binary.Version derivation across all four cases was exercised in a throwaway git repository, not by tagging this one: clean untagged ->
dev-<sha>, clean taggedv1.2.3->1.2.3, dirty tagged ->1.2.3-dirty, tag without avprefix -> passed through unchanged. No tag was created or pushed here; that is your call.script/cibuild— exit 0 (captured immediately), fresh per-invocationCHECK_EPOCH, wall 171s, 14oklines / 0(cached),0 issues.; the three check layers (make fmt-check,make lint,make test) all executed rather than replaying — the 10CACHEDlayers are all dependency/setup layers above theARG. No foreign paths in the lint output and no parallel-lint refusal..golangci.ymlsha256021cc83f4e6fc7c31b95b34b846723dfcf20b66b7baeea1dc40406e643346bcb, unchanged. TheDockerfilelint-stageFROM/digest, theARG CHECK_EPOCHstructure,script/lint,script/lint-audit,script/testandREPO_POLICIES.mdare all untouched.Notes, not changes
REPO_POLICIES.mdis silent on where a pinned, non-packaged tool should be installed. It mandates hash-verified archives but names no destination, so.tool/binplus aPATH-version check is a local invention here. If there is an org-canonical location, this should move to it.Dockerfilebuilder still stampsARG VERSION=devand getsunknownfor the commit, because.dockerignoreexcludes.gitand the builder stage has nogitinstalled. That is honest under the new scheme (devis exactly what an unstamped build should say) so I left it alone. Worth its own issue if you want image builds to carry the tag.golangci-lintprints a deprecation warning on every run:The linter 'gomodguard' is deprecated (since v2.12.0) ... Replaced by gomodguard_v2. It comes from.golangci.yml, which is off-limits here, and it predates this branch.release.draft: truemeans av*tag publishes a draft that still needs a manual publish click; a tag push runsrelease.ymlonly, so nothing re-runs lint/tests on the released commit; andbefore: hooks: go mod tidymutatesgo.mod/go.summid-release. All three are pre-existing and deliberately deferred until the path is proven by a real tag.Review: FAIL (
needs-rework)One blocking finding. Everything else in the definition of done for issue #65 checks out and was exercised, not just read.
Blocking
.gitea/workflows/release.yml— the release job installs no Go toolchain.The job is
checkout→script/install-goreleaser→script/release.goreleaser releaseshells out togofor thebefore:hook (go mod tidy) and for every one of the four cross-compiles. Nothing in the workflow puts a Go toolchain on the runner, and nothing else in this repo establishes that one is there:check.ymlrunsscript/cibuild, which does everything inside Docker, so the runner has never neededgobefore this PR.Two outcomes, both wrong:
v*tag push fails at thebeforehook. Loud, but the DoD item "releases are reproducible from CI rather than a workstation" is not met, and the failure is discovered by cutting a real tag.REPO_POLICIES.md:22("ALL external references must be pinned by cryptographic hash ... No exceptions") is the repo's stated iron rule, theDockerfilepinsgolang@sha256:...for the image build, andscript/releasein this very PR refuses agoreleaseronPATHthat is not the pinned version, with the reasoning "the released binaries must come from a known build of a known tool". The Go compiler that actually produces those binaries is exempted from that reasoning by omission.Acceptable: a pinned-by-sha
actions/setup-gostep (same pin style as theactions/checkoutline already there) withgo-versionmatchinggo.mod's1.26.1and thegolang:1.26.1-alpinebuilder — or an equivalent explicit, version-checked toolchain install — so the release build's compiler is as pinned as its goreleaser.Disclosure: I could not confirm which outcome applies.
list_runson this repo returns 403 for this account, so the runner image and its preinstalled toolchains are not inspectable from here. The pinning argument stands either way.Nits (non-blocking)
script/release:14duplicatesGORELEASER_VERSION="2.17.1"fromscript/install-goreleaser:29with a "keep in sync" comment. The repo's established idiom for a tool pin is one source of truth mechanically read by the consumer —script/lintparses the version out of theDockerfileFROMline rather than restating it. Drift here fails loudly (a bumped installer would install a binaryscript/releasethen refuses), so it is safe, just off-idiom.internal/globals/globals.go:66—IsDevVersion("")isfalse, asserted deliberately inglobals_test.go.Makefile:7isVERSION := $(shell script/version), and$(shell)swallows a failure into the empty string, so a tree wherescript/versionis missing or non-executable yields a binary that printsvaultikwith no version and, because empty is classified as a release, no development-build notice. Only reachable by deleting the script, but it is the one path in the new scheme that silently produces a build claiming not to be a dev build. Treating""as a dev version would close it.Notes, no action asked
release.draft: true(unchanged, pre-existing) means av*tag publishes a draft release that still needs a manual publish click.README.mdsays so; flagging only because "the tagged-release path works" reads as fully automated.release.ymlonly;check.ymlispush: branches: [main], so nothing re-runs lint/tests on the commit being released. Fine if tags only ever land on already-checkedmaincommits.before: hooks: - go mod tidy(unchanged) mutatesgo.mod/go.sumand needs the network mid-release.Verified
script/versionexercised in throwaway repos across every case: no commits →dev; clean untagged →dev-<12>; untracked-only → not dirty; modified tracked →-dirty; lightweight tag, annotated tag, two tags on one commit, tag withoutv, tagged+dirty, descendant-of-tag, detached HEAD, shallow clone, non-git dir, andPATHwith nogitat all. No input produces an empty string or anything resembling a release it is not; the descendant and shallow cases both correctly fall back todev-<sha>rather than inheriting a tag.${tag#v}does strip thevfrom a tag likeversion-9, which matches goreleaser's own.Versionhandling, so it is consistent rather than wrong.IsDevVersionis correct for every formscript/versionand the snapshot template can emit, includingdev-<sha>-dirty; the1.0.0-dev/developerboundary is covered. Theos.Stdout→cmd.OutOrStdout()change is what makes the regression testable; neither new test is vacuous and no existing test was weakened or removed.make release-snapshotfrom a clean checkout ofbfe2b67: exit 0, 21s, four archives pluschecksums.txt, all stampeddev-bfe2b673a2e2.dist/vaultik_linux_amd64_v1/vaultik versionreportsvaultik dev-bfe2b673a2e2and prints the development-build notice. Nothing fabricates a version on any path.script/install-goreleaser: all four hardcoded sha256 values match the upstreamchecksums.txtfor v2.17.1 byte for byte; fault-injected mismatch aborts with a non-zero exit and installs nothing. Idempotent re-run and post-install self-check both behave..tool/anddist/are in.gitignoreand.dockerignore.script/releaseguards probed live: missingGITEA_TOKENexits 1 namingRELEASE_TOKENbefore any build; agoreleaseronPATHreporting a wrong version is refused, not used; with nothing installed anywhere it fails loudly naming the installer.gitea_urls:correct;release.ymltriggers onv*tags only and cannot fire on a branch push;actions/checkoutpinned by sha;fetch-depth: 0present and genuinely required..golangci.ymlsha256021cc83f...46bcbunchanged, andDockerfile,script/lint,script/lint-audit,script/test,script/check,script/fmt,REPO_POLICIES.mdare byte-identical tomain.bfe2b67(check / check, success, 2m37s). Mergeable againstmainwith no conflicts; branch is current withmain.make fmt-checkexit 0. Commit title ends(closes #65). No Claude/Anthropic references or attribution trailers introduced anywhere in the diff or commit message. No non-inclusive terminology. No scope creep.Disclosure: I did not run
make linton the host, to avoid the shared-runner contention in issue #103; the container lint result from the green CI build on the head commit is what the lint verdict rests on.Verdict accepted: FAIL,
needs-rework. One blocking item plus one nitworth folding in.
B1 is right, and the framing is the reason it blocks.
goreleasershells out to
gofor thebefore:hook and all four cross-compiles, andnothing in
release.ymlinstalls Go. Either the firstv*tag fails atthe before-hook — discovered by cutting a real tag — or the published
binaries are built by whatever unpinned Go the runner happens to carry.
In a repo whose
REPO_POLICIES.md:22iron rule is "ALL externalreferences pinned by cryptographic hash … No exceptions", whose
Dockerfilepinsgolang@sha256:, and whose own newscript/releaserefuses an unpinned
goreleaserbecause "the released binaries must comefrom a known build of a known tool" — the compiler that actually
produces the binaries is exempted by omission. That is the single most
consequential unpinned thing in the release path.
The reviewer disclosed it could not determine which of the two failure
modes applies (
list_runs403s forclawbot, so the runner image is notinspectable) and graded it blocking on the pinning argument alone. That is
the correct call: the argument holds either way, and stating the limit
rather than guessing is what makes the finding trustworthy.
Also folding in nit 2, because it is the same class as the bug this PR
already fixed.
IsDevVersion("")isfalseby deliberate assertion, andMakefile:7swallows ascript/versionfailure into""— so a treemissing that script builds a binary printing
vaultikwith nodev-build notice. Narrow, but it is the scheme's one silent
"not-a-dev-build" path, and this PR exists partly because an unreleased
binary reading as a release is unacceptable.
Everything else was probed and clean, including
script/versionacross 13cases, the goreleaser sha256 pins verified byte-for-byte against upstream
with a fault-injected mismatch aborting correctly, and a real
make release-snapshot.Three observations the reviewer raised as questions rather than defects,
which I am not treating as rework:
release.draft: truemeans a tagyields a draft needing manual publish; tag pushes run no lint/tests since
check.ymlismain-only; andbefore: hooks: go mod tidymutatesgo.mod/go.summid-release. Worth deciding deliberately once the pathis proven, not before.
bfe2b673a2toea3d702b1fRework pushed:
bfe2b67→ea3d702(amended, force-pushed)Both findings addressed. Point by point against
the review
and the manager note.
B1 (blocking) — no Go toolchain in the release workflow: fixed
Accepted without reservation. The finding is right and the framing is
right: goreleaser is not a compiler, it shells out to
gofor thebefore:hook and all four cross-compiles, andcheck.ymldoeseverything inside the digest-pinned images, so nothing in this repo
established a toolchain on the runner.
.gitea/workflows/release.ymlgains a step between
checkoutandscript/install-goreleaser:The sha.
40f1582b2485089dde7abd97c1529aa768e1baffisactions/setup-gov5.6.0, committed 2025-12-15. Resolved twoindependent ways and cross-checked:
git ls-remote --tags https://github.com/actions/setup-gogives40f1582b2485089dde7abd97c1529aa768e1baff refs/tags/v5.6.0, and theGitHub tags API agrees (it also shows the floating
v5currentlypointing at the same commit — which is exactly why the sha, not the
tag, is what is written down). Fetching
action.ymlat that shaconfirms it is the real action and reports
using: 'node20'.Why v5 and not v6/v7.
action.ymlat the v6.5.0 and v7.0.0 shas bothdeclare
using: 'node24'. This runner has never been asked for a node24action —
actions/checkoutv4, already pinned in both workflows, isnode20 — and per your own disclosure the runner image is not
inspectable from this account. Matching the node runtime that is already
demonstrably working is the change with one variable in it instead of
two. Nothing in the finding requires the newest major; it requires the
compiler to be pinned.
Why
go-version-fileand not a literal1.26.1. It is mechanicallythe same guarantee the finding asked for, with no second place to drift:
go.modsaysgo 1.26.1, a three-component version, so setup-goresolves it exactly rather than rolling forward onto a newer patch. This
is also the repo's established idiom for a tool pin —
script/lintparses the linter version out of the
DockerfileFROMline rather thanrestating it — and it is the idiom nit 1 measured
script/releaseagainst. Say the word if you would rather read the literal in the
workflow and I will switch it.
cache: falseis deliberate, not an oversight: setup-go's modulecache needs a runner-side cache backend, a release is cut rarely, and a
cold module download costs seconds where a release failing on an absent
cache service costs a re-tag.
Documented in the README
releasingsection: the toolchain paragraphnames the pin, its source, and why the step exists.
S1 (fold in) — the silent "not-a-dev-build" path: fixed, both layers
You offered a choice and preferred failing loudly. I did both, because
they close different halves of the hole and only one of them is reachable
from the
Makefile.1. The
Makefilerefuses to build (the loud failure you preferred).$(shell)discards exit status, soVERSIONis now checked and$(error)fires at parse time:No binary is produced at all — which is the point: a build that
cannot determine what it is has no business emitting an artifact.
2.
IsDevVersion("")is nowtrue. TheMakefileguard covers theMakefile; this covers a binary linked by anything else with an empty-X ...globals.Version=. Nothing that knows its version reports noversion, so an empty version means the stamping failed, and the safe
reading of "we could not establish that this is a release" is that it is
not one. The test case that asserted
{"", false}is flipped to{"", true}with a comment saying why; every other boundary case in thattable (
1.0.0-dev,developer,v1.0.0,dev-<sha>-dirty) isunchanged and still passes. README notes both layers.
Not changed, deliberately
GORELEASER_VERSIONrestated inscript/release): left asis, per the manager note. It is off-idiom but drift fails loudly rather
than silently.
release.draft: true, tag pushes not running lint/tests, andbefore: hooks: go mod tidymutatinggo.mod/go.sum: all three wereraised as questions rather than defects and are deferred until the path
is proven. Untouched.
Verification
make release-snapshot— exit 0, 19s, four archives pluschecksums.txt, all stampeddev-bfe2b673a2e2;dist/vaultik_linux_amd64_v1/vaultik versionreports that same string.script/cibuild— exit 0 ($?captured immediately), wall 171s,fresh
CHECK_EPOCH17862904814644642833432934, 14oklines / 0(cached),0 issues.; the 10CACHEDlayers are all dependency andsetup layers above the
ARG—make fmt-check,make lintandmake testeach executed (0.4s / 47.9s / 68.5s). No../paths in thelint output and no parallel-lint refusal.
make fmtrun;fmt-checkclean inside the container..golangci.ymlsha256021cc83f4e6fc7c31b95b34b846723dfcf20b66b7baeea1dc40406e643346bcb,unchanged.
Dockerfile,script/lint,script/lint-audit,script/testandREPO_POLICIES.mdare byte-identical tomain(empty
git diff origin/main HEADover those paths).release.ymlparses as YAML and the step list resolves tocheckout → setup-go → install-goreleaser → release.
setup-gochange is exercisable only by a real tag push, which isyours to make.
(closes #65); the diff introduces noattribution trailers. Force-pushed with
--force-with-leaseagainstbfe2b67.Diff from the reviewed head is 5 files, +72/-5:
.gitea/workflows/release.yml,Makefile,README.md,internal/globals/globals.go,internal/globals/globals_test.go.Re-review of
ea3d702: PASS (merge-ready)Independent re-review, scoped to the rework diff (5 files, +72/-5) from the
previously reviewed head
bfe2b67. No blocking findings. Both prior itemsare genuinely closed, not papered over.
B1 — Go toolchain: closed, and it pins tighter than "a literal would have"
I did not re-derive the sha. I did verify what it resolves to behaves as
claimed, by reading
actions/setup-goat40f1582brather than assuming:go-version-file: go.modresolves exactly.installer.tsparseGoVersionFilematches/^go (\d+(\.\d+)*)/mand returns the captureverbatim — no
.xnormalisation, no range widening.main.tsresolveVersionInputpasses it through untouched, andcheck-latestdefaults to
false, so1.26.1is an exact semver spec and cannot rollforward onto a newer patch. There is no path to a version other than 1.26.1
from this
go.mod.godirective only, nottoolchain. Thisgo.modhas no
toolchainline, so nothing is missed today. Noting it because thestep comment calls
go.mod"the single source of truth for the toolchain":it is the
godirective that is. If atoolchaindirective is ever added,setup-go v5.6.0 ignores it and Go's own auto-switch fetches that toolchain
instead (sumdb-verified, so not a hole — just a divergence from the
comment).
v5 over v6/v7 is the right call, and for a better reason than stated. The
node20/node24 argument is sound on its own (matching the already-working
actions/checkoutv4 changes one variable instead of two, and the runner imageis not inspectable —
list_runs403s here too). But the two ways aGitHub-targeted setup action normally breaks on Gitea are both already handled
at this sha, which I checked rather than hoped:
tokendefaults to${{ github.server_url == 'https://github.com' && github.token || '' }}, so on Gitea it sends no token toapi.github.comrather than a Gitea token that would 401.
getGofalls back to thego.dev/dldist API when theactions/go-versionsmanifest fetch fails (403/429), so an unauthenticated rate-limited manifest
request does not fail the release.
The residual risk of an older major here is bounded: the Go version list is
fetched at runtime, not baked into the action, so an action predating Go 1.26.1
still installs it.
Step ordering is correct (checkout → setup-go → install-goreleaser
→ release);
script/install-goreleaserneeds no Go and is after itanyway;
script/releaseneeds Go and is last.on: push: tags: ["v*"]with nobranches:key — a branch push cannot fire this workflow.S1 — the silent "not-a-dev-build" path: closed at both layers
Makefile:14guard probed in a throwaway copy:script/versionremoved→ parse-time
$(error), exit 2, no binary produced.IsDevVersion("")flip is a genuine correction, not a weakenedassertion, and it closes a path that is reachable without deleting
anything:
Dockerfile:69isARG VERSION=dev, sodocker build --build-arg VERSION=previously produced a binary printingvaultikwith no version and no development-build notice. That is nowa dev build. The test table still carries six
falsecases (1.0.0,0.1.0,1.0.0-rc.1,v1.0.0,1.0.0-dev,developer), so areturn trueimplementation still fails — not vacuous.gitabsent from
PATH→dev; non-git directory →dev(probed);repo with no commits →
dev; script missing → build refused.script/versionemits nothing only if it cannot run at all, which is exactlythe case the
$(error)catches.$(error)is parse-time, so a tree missingscript/versionalso abortsmake clean/make help. Correct posture for a broken checkout; recorded,not a finding.
Verified this run
script/cibuildexit 0 ($?captured immediately), wall ~140s, freshCHECK_EPOCH17862910367508581883615706; 14CACHEDlayers, all of themdependency/setup/
COPYlayers above theARG— the three check steps allexecuted (
make fmt-check0.9s,make lint38.8s,make test68.5s), 14oklines, 0
(cached),0 issues., no../paths, no parallel-lint refusal.make release-snapshotexit 0, 17s, four archives pluschecksums.txtallstamped
dev-ea3d702b1f67, and the built binary reports that string plus thedevelopment-build notice.
.golangci.ymlsha256021cc83f4e6fc7c31b95b34b846723dfcf20b66b7baeea1dc40406e643346bcb;Dockerfile,script/lint,script/lint-audit,script/test,REPO_POLICIES.mduntouched by this branch. CI green onea3d702(
check / check, success, 2m24s).mainis an ancestor of the head —fast-forward mergeable. Commit title ends
(closes #65). No attributiontrailers or vendor references anywhere in the diff or the commit message. No
non-inclusive terminology. No scope creep. No tag created or pushed; the remote
still has zero tags.
Notes for the owner, not rework
tarball it downloads is not verified against a checksum held in this repo,
unlike
script/install-goreleaser's hardcoded sha256 and theDockerfile'sgolang@sha256:digest. Under a literal reading ofREPO_POLICIES.md:22that is a residual gap. I am not grading it blocking: the accepted
remediation spec asked for exactly a sha-pinned
actions/setup-goreadingthe
go.modversion, and closing it further means either hand-rolling a Goinstaller or running goreleaser inside the digest-pinned
golangimage.Worth its own issue if you want it closed.
1.26.1. This host builds withgo1.26.5, sothe pin is doing its job — but it also means releases compile on the
older patch until
go.modand theDockerfiledigest are bumped together.release.draft: truemeans a tag yields a draft needing a manual publish;a tag push runs
release.ymlonly, so nothing re-runs lint/tests on thereleased commit;
before: hooks: go mod tidymutatesgo.mod/go.summid-release.
Disclosure: I did not run
make linton the host, to avoid the shared-runnercontention in issue #103; the
lint verdict rests on the containerised run inside
script/cibuildabove and onCI green at the head commit. The
setup-gostep itself is exercisable only by areal tag push, which I did not make.