Skip to content

Report the version the toolchain stamped rather than none at all - #212

Merged
iderex merged 1 commit into
mainfrom
release/report-the-version-the-toolchain-stamped
Aug 27, 2026
Merged

iderex merged 1 commit into
mainfrom
release/report-the-version-the-toolchain-stamped

Conversation

@iderex

@iderex iderex commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Refs #44 and #36. It closes neither, and what each of them still waits on is at
the bottom of this body.

What was wrong

The runner had no version output of any kind. Asking for one reached the
unknown-verb branch and returned the code record 0011 gives a broken
invocation:

git show origin/main:cmd/lab/main.go | grep -c 'case "version"'
0

So an operator holding a binary could not say which build they were holding, and
a report from one named nothing a reader could resolve. #44 opens with the
defect that makes this worse than an absence: a hand-maintained version constant
disagrees with the tag eventually and disagrees silently.

What this changes

lab version prints the version the toolchain stamped into the binary from
version control at build time, with the commit and the build moment beside it.

At the head of this branch, 45bfe62, built rather than run, because the
toolchain stamps version control into a build and not into go run:

go build -o lab ./cmd/lab && ./lab version
lab v0.0.0-20260827065511-e61e46250659+dirty
built from commit e61e46250659dd030c8b43c0288109ac2b09b6be, 2026-08-27T06:55:11Z
The tree this was built from carried changes version control did not hold,
so neither the version nor the commit above describes every byte in this binary.

The version is what the toolchain stamped from version control when this binary
was built rather than a constant written into the source, so it is one of three
things. A tag, which is what a release carries. A version of the shape
v0.0.0-<timestamp>-<commit>, which the toolchain derives from a commit no tag
names, and which is what building from an ordinary checkout produces. Or
(devel), the placeholder it writes when it was told nothing at all, which is
what running the source without building it produces.

NOTICE.md says what this program is for, LICENSE carries the terms it is under,
and docs/privacy.md says what stays on the host. Reading them is on you; this
text only says where they are.

The value it reads is the one this repository already treats as the answer.
internal/bom refuses a published artefact whose main component carries no
release version, and it refuses it against exactly this field, so reading it
here is one authority rather than a second:

git show origin/main:internal/bom/bom.go | grep -n 'isReleaseVersion(build.Main.Version)'
231:	if !isReleaseVersion(build.Main.Version) {

Nothing here decides whether the string is a release version. That judgement
lives in that package, and a copy of its pattern in the runner would be one rule
answered in two places.

The paragraph naming the three documents is now one string that both the usage
text and the version output print, rather than a copy in each. An operator
reaches for one route or the other and not reliably for both, and a second copy
of the text would drift against the first - which is the failure that paragraph
is written against, one level up.

The build information is an edge

It is a field of edges the way the clock already is, and for the same reason
that one is. A test asserting what the verb prints cannot build a tagged binary
to assert against, and one that read the real stamp would assert whatever the
machine running the suite happened to produce: a different string on a checkout,
at a tag and on a modified tree.

The proofs

Each guard was proved by breaking it and watching the suite go red, and each was
restored before the next.

sed -i '300d' cmd/lab/main.go        # the documents paragraph, out of the version output
go test -count=1 -run 'TestVersionNamesTheDocuments' ./cmd/lab
FAIL	github.com/Flowfin/lab/cmd/lab	0.680s

sed -i 's|version := strings.TrimSpace(info.Main.Version)|version := "v1.2.3"|' cmd/lab/main.go
go test -count=1 -run 'TestVersionReportsWhatTheToolchainStamped' ./cmd/lab
FAIL	github.com/Flowfin/lab/cmd/lab	0.475s

sed -i 's|if settings\["vcs.modified"\] == "true" {|if true {|' cmd/lab/main.go
go test -count=1 -run 'TestVersionSaysWhenTheTreeWasModified' ./cmd/lab
FAIL	github.com/Flowfin/lab/cmd/lab	0.522s

# the missing-build-information branch returning the clean code instead
go test -count=1 -run 'TestVersionWithoutBuildInformationCannot' ./cmd/lab
--- FAIL: TestVersionWithoutBuildInformationCannot (0.00s)
    main_test.go:377: exit code 0, want 2

The third one is the proof worth naming, because its negative leg is what the
test is for. A clean build must not print the modified-tree disclosure, and a
warning that appears on every run is one nobody reads.

The means

Go, in cmd/lab, which is where the runner already is. The alternative
considered and rejected was a version constant injected with -ldflags -X from
the release workflow. It costs a second place that decides what a version is,
it is a value the release route writes and every other build leaves empty, and
the field the toolchain already stamps is the one internal/bom already judges.
Nothing outside this repository forces either choice.

The gate

Run at 45bfe62:

go build ./cmd/... ./internal/...
go vet ./cmd/... ./internal/...
gofmt -l cmd internal
(no output)
go test -count=1 ./cmd/... ./internal/...
ok  	github.com/Flowfin/lab/cmd/bom	12.166s
ok  	github.com/Flowfin/lab/cmd/contexts	0.535s
ok  	github.com/Flowfin/lab/cmd/lab	3.365s
ok  	github.com/Flowfin/lab/cmd/notices	13.130s
ok  	github.com/Flowfin/lab/cmd/pullrequest	0.531s
ok  	github.com/Flowfin/lab/internal/bom	0.806s
ok  	github.com/Flowfin/lab/internal/check	1.082s
ok  	github.com/Flowfin/lab/internal/contexts	0.765s
ok  	github.com/Flowfin/lab/internal/hardware	0.767s
ok  	github.com/Flowfin/lab/internal/invariants	1.163s
ok  	github.com/Flowfin/lab/internal/notices	0.765s
ok  	github.com/Flowfin/lab/internal/prose	0.804s
ok  	github.com/Flowfin/lab/internal/pullrequest	0.771s

go run ./cmd/lab check .
examined .
1 experiment directory walked, 1 record read
26 decision records read
the time this run read is 2026-08-27T09:07:38Z
0 refused

What each issue still waits on

#44 keeps its second clause. CHANGELOG.md with an entry for the first
release needs a first release, and there is none:

gh api repos/Flowfin/lab/releases --jq 'length'
0

gh api repos/Flowfin/lab/tags --jq 'length'
0

#36 keeps the spelling its first clause is written in. The done-condition
names lab --help, the runner takes verbs and no flags, and whether it grows
one or the done-condition names lab help is a question about the command
interface. This change does not answer it: the paragraph it adds is reached
through lab version, which works whichever way that goes.

No second reader

This change has had no reader other than whoever wrote it. The proofs above
stand in place of one: each guard was deleted, the failure it produced is
pasted, and the near-miss chosen for the disclosure is the negative leg rather
than the positive one.

The runner had no version output of any kind. Asking for one returned the code
record 0011 gives a broken invocation, so an operator holding a binary had no
way to say which build they were holding and no way to name it in a report.

`lab version` prints what the toolchain stamped into the binary from version
control at build time, with the commit and the build moment beside it. It is not
a constant in a source file, and that is the point rather than a detail: a
hand-maintained version disagrees with the tag eventually and it disagrees
silently, which makes every report from that build misleading rather than wrong
in a way somebody notices. The same stamp is already what internal/bom refuses a
published artefact for being unable to resolve, so this reads the value that
package already treats as the answer instead of inventing a second one.

Three things a reader needs and would otherwise have to infer. The version is
one of three shapes - a tag, a version derived from a commit no tag names, or
the placeholder the toolchain writes when it was told nothing - and the output
says which three rather than leaving the reader to decode a string. A build from
a tree carrying changes version control did not hold is described by neither the
tag nor the commit, so it says so, and only then. And a binary carrying no build
information at all gets a message naming what is missing and the exit code for a
runner that could not do its job, rather than an empty version somebody would
take for a real one.

The output ends with the paragraph naming NOTICE.md, LICENSE and docs/privacy.md
that the usage text already carried, and that paragraph is now one string used by
both rather than a copy in each. An operator reaches for one route or the other
and not reliably for both, so a pointer only one of them prints reaches half of
them; a second copy of the text would drift against the first, which is the
failure the paragraph itself is written against one level up.

The build information is an edge the way the clock already is. A test asserting
what the verb prints cannot build a tagged binary to assert against, and one
reading the real stamp would assert whatever the machine running the suite
happened to produce - a different string on a checkout, at a tag and on a
modified tree.

Each guard was proved by breaking it and watching the suite go red: the
documents paragraph deleted from the version output, the version hard-coded
instead of read from the stamp, the modified-tree disclosure printed
unconditionally, and the missing-build-information branch returning the clean
code. The negative leg on the disclosure is the one worth naming - a warning
that appears on every run is one nobody reads.

Refs #44, refs #36

Signed-off-by: Nils Lehnen <30603423+iderex@users.noreply.github.com>
@iderex
iderex merged commit 326b489 into main Aug 27, 2026
25 checks passed
@iderex
iderex deleted the release/report-the-version-the-toolchain-stamped branch August 27, 2026 09:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant