version command, product version in the handshake
No sapedb binary can say which build it is. Embed the version at build time, add a
sapedb version command that prints it, and have the daemon state its product
version during the handshake so a connected client can record it.
When something breaks, the first question anybody asks is which version is running, and today nobody can answer — not the operator, not a bug report, not a log line. It also blocks four other tickets: a compatibility policy nobody can check is not a policy, a release workflow cannot stamp what does not exist, and the upgrade rehearsal in SAPE-6 needs two builds that can tell each other apart.
Measured 2026-09-20.Welcomecarries a version field, but it is the protocol version, and that is the constant1ininternal/protocol/frame.go:23. Two builds six months apart introduce themselves identically. There is noversioncommand incmd/sapedbat all.
sapedb version prints the tag it
was built from, and the two answers differ.internal/build holds Version as a variable, not a constant
(internal/build/build.go:21), stamped by the linker at build.Path =
"github.com/sapedb/sapedb/internal/build.Version" and never assigned from inside the program —
the package doc says why: "a build that decides its own version at run time is a build that cannot be
identified from the outside".TestAStampedBuildSaysWhatItWasStampedWith (version_live_test.go:60) builds
the real sapedb binary twice, from two hand-written stamps that are not derived from each
other or from build.Version — "1.4.0-first-of-two" and
"2.9.1-second-of-two" — and asserts sapedb version prints each one back,
distinctly. TestAnUnstampedBuildDoesNotClaimAVersion (version_live_test.go:79)
is the negative case: a build with no -X flag says dev, a word no release
will ever be tagged.internal/cli/cli.go:325-337 registers version, refuses
arguments (checkNoArguments("version")), and writes
fmt.Fprintf(out, "sapedb %s\n", build.Version).ProductVersion is a field of Welcome
(internal/server/server.go:544, json:"productVersion"), filled from
build.Version when the server is not told otherwise
(server.go:149-150), and asserted onto the wire itself —
TestTheWelcomeCarriesTheProductVersion (internal/server/product_version_test.go:50)
checks both the decoded struct field and the raw frame payload for the literal substring
"productVersion":"…".
TestAConnectedClientReadsTheDaemonsProductVersion (version_live_test.go:126)
is the same fact from a live client's side of a real socket.TestAClientThatPredatesTheProductVersionStillReadsTheWelcome
(internal/server/product_version_test.go:126) decodes today's welcome into the struct
shape that existed before this field, so adding it is measured as additive rather than assumed to
be.