Skip to content

guard: a window binary with no window in it says so, fails, and stays off stdout - #53

Merged
donislawdev merged 2 commits into
mainfrom
guard/no-window-build
Sep 5, 2026
Merged

guard: a window binary with no window in it says so, fails, and stays off stdout#53
donislawdev merged 2 commits into
mainfrom
guard/no-window-build

Conversation

@donislawdev

Copy link
Copy Markdown
Owner

internal/gui/gui.go was the only production file in the tree that no guard executed at all - measured with a coverage map, after subtracting the blocks init() lights up on its own.

The gap was not in gui.go. It was next door: internal/gui/run_nocgo.go carries a four part D6 message and an exit code, and nothing checked that a binary ever produced either. The catalogue guard knows that sentence exists as a text entry, which is a different fact.

Why it cannot be an ordinary test. The guard binary is compiled with cgo, so run resolves to run_cgo.go, and calling gui.Run from a test would try to open a real window. So the guard builds the binary the way somebody without a C compiler builds it - CGO_ENABLED=0 - and runs it. Measured at 1.7 s, because disabling cgo excludes the whole toolkit instead of compiling it.

Three contracts, and the third had no guard anywhere:

  • the message reaches standard error, out of internal/gui/text rather than written twice,
  • the exit code is 1,
  • standard output stays empty. That rule is on the regression surface for the command line, and the window binary is the other half of it - a script reading a report from stdout cannot tell an empty answer from a broken one.

Proof. Two mutations, both caught: return 1 becomes return 0, and the message goes to io.Discard.

⚠️ What this does not do. The guard runs the binary as a subprocess, so it lights no coverage - gui.go still reads as unexecuted in the map. That is a limit of the map, not a gap in the guard, and it is written down.

Full suite and preflight --quick green locally, all 12 checks.

🤖 Generated with Claude Code

donislawdev and others added 2 commits September 4, 2026 02:32
Somebody reports a bug, the fix lands on a branch, and they want to try it
before there is a release. Clicking Run workflow builds that branch and leaves
the binaries on the run page for fourteen days.

Three owner decisions shape it. A choice input, defaulting to the command line
binaries only: those cross compile to five platforms on one runner in about two
minutes, while the window needs three real runners and CGO. No test gate,
because the whole point is speed and the branch has its own CI on its own pull
request. And fourteen days rather than the default ninety, because an unsigned
binary should not sit for a quarter of a year behind a link somebody can pass
on as if it were official.

It is deliberately not shaped like a release, so an archive from here cannot be
mistaken for one. The name carries the COMMIT rather than the version, and that
is not a style choice: internal/version is a const and cannot be stamped at
link time, so a build from a fix branch reports whatever version that branch
inherited. The file name is the only place that can tell the truth about which
code this is. Every archive also carries UNOFFICIAL-BUILD.txt saying the same
in words, for whoever unpacks it a month later with no memory of where it came
from - not signed, no attestation, and what the version string inside does and
does not mean. The note is a script rather than a heredoc because two jobs on
four runners write it, and a note that says one thing in one archive and
something else in the other is worse than no note.

Three guards, five mutations, all caught.

The platform list is the one fact this shares with the release, so it is read
out of both workflows and compared. A workflow that quietly built four of five
platforms would leave somebody's machine unserved, and nothing would say so,
because a missing platform looks like a build that did not run.

The other two are about the failure mode that matters here, which is a person
trusting a file they should not: nothing in this workflow may be granted write,
no step may mention a way to publish, and both packaging jobs must write the
note - counted rather than found, because one job losing its call would leave
the other one proving nothing about it.

Measured rather than assumed: the command line loop was run locally against
this tree and packaged all five targets in 26 seconds, and the archive holds
the binary, the licence, the notices, the readme and the note.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… off stdout

internal/gui/gui.go was the only production file in the tree that no guard
executed - measured with a coverage map, after subtracting the blocks init()
lights up on its own. The gap was not in gui.go: run_nocgo.go carries a four
part D6 message and an exit code, and nothing checked that a binary ever
produced either. The catalogue guard knows the sentence exists as a text
entry, which is a different fact.

It cannot be an ordinary test. The guard binary is compiled with cgo, so run
resolves to run_cgo.go and calling gui.Run here would try to open a real
window. So it builds the binary the way somebody without a C compiler builds
it, with CGO_ENABLED=0, and runs it. Measured at 1.7 s, because disabling cgo
excludes the toolkit rather than compiling it.

Three contracts, and the third had no guard anywhere: the message reaches
standard error out of internal/gui/text, the exit code is 1, and standard
output stays empty. That last one is on the regression surface for the command
line and the window binary is its other half.

Two mutations, both caught.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@donislawdev
donislawdev merged commit b0e8873 into main Sep 5, 2026
18 checks passed
@donislawdev
donislawdev deleted the guard/no-window-build branch September 5, 2026 15:31
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