Skip to content

Smoke what was published rather than what can be rebuilt - #210

Merged
iderex merged 1 commit into
mainfrom
release/smoke-the-published-artefact
Aug 27, 2026
Merged

iderex merged 1 commit into
mainfrom
release/smoke-the-published-artefact

Conversation

@iderex

@iderex iderex commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Refs #43

What this changes

.github/workflows/smoke.yml, which runs on a published release, on a weekly schedule and on demand, and never on a pull request or a push.

Two jobs. verify the published artefacts downloads the whole release, checks every published digest against the checksum file, and verifies the signature over that file against the public signing keys the platform publishes for the account. smoke (<platform>) downloads one platform's binary, checks its digest, clones the default branch fresh, runs the downloaded file against that clone and asserts on the output as well as on the exit code.

internal/contexts/contexts.go gains the four new check names as permanent deliberate absences, under a reason of their own rather than the one the pending entries share.

What failure it prevents

The class the issue names: a defect that exists only in what was published. A build flag that applies to one platform, a release missing a document, a binary that needs a library the build machine happened to have. Nothing else in this repository reads a published file at all.

It never rebuilds. A rebuild would test the build a second time and say nothing about the thing an operator downloads.

It asserts on the output rather than only on the exit code, which is the half of the issue that earns the job. Exit zero having printed nothing is what a binary that never found the tree also produces, and so is a report whose every count is nought.

It checks the signature by running the two commands the release notes hand a reader, rather than an equivalent of them, so published instructions that stop working redden here instead of being discovered by whoever follows them.

On a schedule, it catches an artefact that stopped being fetchable weeks after it was published, which is a real failure nothing else here would notice.

The means

A workflow file, forced: this has to run where the release is and on a schedule the platform keeps. What is in shell is downloading, hashing and asserting on text. Nothing new is added to the tree, no action is used that this repository does not already use, and no dependency arrives.

What was run

There is no published release on this board, so the two jobs cannot be run as they will run:

$ gh api repos/Flowfin/lab/releases --jq 'length'
0
$ gh release list --repo Flowfin/lab --exclude-drafts --limit 1 --json tagName --jq '.[0].tagName'

$ echo "exit=$?"
exit=0

That last pair is the measurement the empty-release branch is written against rather than a guess: gh prints an empty line and exits zero, so the job reads an empty tag and says it examined nothing, instead of failing on a non-zero code that never comes.

What could be run was the assertion block, against a binary built here standing in for a published one and a genuinely fresh clone of the default branch.

$ git clone --depth 1 https://github.com/Flowfin/lab.git fresh-clone
cloned e6d37906cfc6edaf26d644f6d78136e1fb35e670

Against that clone:

-- the fresh clone:
  returned 0
  VERDICT: green - read 26 decision record(s) and said what it examined

Against an empty directory, which is the release defect the assertions exist to catch:

-- an empty directory:
  returned 0
  VERDICT: red - it reported: no experiments directory in this tree

Against a directory holding the decision records and no experiments, which is the same defect one step less obvious:

-- a directory holding docs/decisions and no experiments:
  returned 0
  VERDICT: red - it reported: no experiments directory in this tree

The exit code is 0 in all three. That is the whole argument for asserting on the output: a job keyed on the code alone would have called the second and third of those a passing release.

The phrases the assertions read were taken out of a real run rather than written from memory:

$ 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-27T06:33:04Z
0 refused

The gate at this head:

$ go build ./cmd/... ./internal/... && go vet ./cmd/... ./internal/...
$ gofmt -l cmd internal
$ go test -count=1 ./cmd/... ./internal/...
ok  	github.com/Flowfin/lab/cmd/bom	11.938s
ok  	github.com/Flowfin/lab/cmd/contexts	0.568s
ok  	github.com/Flowfin/lab/cmd/lab	3.334s
ok  	github.com/Flowfin/lab/cmd/notices	12.061s
ok  	github.com/Flowfin/lab/cmd/pullrequest	0.568s
ok  	github.com/Flowfin/lab/internal/bom	0.598s
ok  	github.com/Flowfin/lab/internal/check	0.974s
ok  	github.com/Flowfin/lab/internal/contexts	0.536s
ok  	github.com/Flowfin/lab/internal/hardware	0.542s
ok  	github.com/Flowfin/lab/internal/invariants	1.207s
ok  	github.com/Flowfin/lab/internal/notices	0.528s
ok  	github.com/Flowfin/lab/internal/prose	0.883s
ok  	github.com/Flowfin/lab/internal/pullrequest	0.564s
$ go run ./cmd/lab check .
0 refused
$ go run ./cmd/contexts
  declared by the workflows: 30
  written down as deliberately absent: 30
0 refusal(s), 3 note(s)

What this does not do

It does not finish #43. The last line of that done-when is that the job has passed against a real release, and there is none. The job exists, it downloads and verifies, it runs against a fresh clone and it asserts on both output and exit code; the last leg is met by the first release rather than by anything anybody can do here. #45 is where that act is flagged.

Nothing in the two jobs has run on the platform, on any of the four check names. What is above is the assertion logic exercised on this machine against a real clone of the default branch, which is a different thing from the job having run, and the two are not written as one.

It runs the binary on three of the six platforms and checks the digest of all six. Record 0012 gives three of its entries a runner and three of them none, and this is the same division the build workflow already carries. The three that are verified and never executed are linux/arm64, darwin/amd64 and windows/arm64.

It asserts nothing about the number of refusals or the number of experiments. The first is a statement about this board rather than about the release, and the exit code already carries it; the second moves whenever somebody starts an experiment, and an assertion on it would redden a release smoke test for an ordinary commit.

Where there is no published release it passes having examined nothing, and says so with a warning annotation and a zero in every count. A red tick standing for weeks on a board that has not cut its first release teaches readers that red is survivable, which this repository's own decision record about the platform list argues against at length. The residual is real and is stated rather than softened: a green tick over a run that examined nothing is exactly the reading this repository dislikes, and what is put against it is that every step prints the count it examined.

It removes no path from the tree.

This board has no second reader tonight. What stands in place of one is the evidence above, including the three-case rehearsal in which the exit code was zero every time and only the output assertions told the cases apart.

Closes #43.

Everything else here tests the runner as source. An operator runs a downloaded
file, and the distance between those two is where release defects live: a build
flag that only applies to one platform, a release missing a document, a binary
that needs a library the build machine happened to have.

Two jobs. One downloads the whole release and checks every published digest
against the checksum file and the signature against the public keys the platform
publishes for the account, which is the same pair of commands the release notes
hand a reader, so instructions that stop working redden here instead of being
discovered by whoever follows them. The other downloads this platform's binary,
clones the default branch fresh, runs the downloaded file against it and asserts
on the output as well as on the exit code.

It never rebuilds. A rebuild tests the build a second time and says nothing
about what was published, which is what an operator will actually download.

Six digests are checked and three binaries are executed, because record 0012
gives three of its six platforms a runner and three of them none. The run says
both counts rather than leaving six green ticks to be read as six binaries
executed.

The output assertions are what earn this job, and they are read out of a real
run rather than written from memory. Exit zero with no output is what a binary
that never found the tree also produces; so is a report whose every count is
nought. Both are refused, and the count of decision records has to be above
zero. What is deliberately not asserted is the number of refusals or the number
of experiments: the first is a statement about this board rather than about the
release and the exit code already carries it, and the second moves whenever
somebody starts an experiment.

Where there is no published release the run says so and examines nothing, rather
than standing red for weeks on a board that has not cut its first one, which
teaches readers that red is survivable. Every step reports the count it
examined, so a run that examined nothing prints zero.

The digest is computed and compared rather than handed to a --check flag,
because macOS carries no sha256sum and the two spellings disagree about what
--strict and --ignore-missing mean.

internal/contexts carries the four new check names as permanent deliberate
absences under their own reason. These jobs read a tag or a published release,
which a pull request has neither of, so requiring one would hold every merge
open for a tick that is not coming.

    go run ./cmd/contexts
      declared by the workflows: 30
      written down as deliberately absent: 30
    0 refusal(s), 3 note(s)

Signed-off-by: Nils Lehnen <30603423+iderex@users.noreply.github.com>
@iderex iderex added the ci label Aug 27, 2026
@iderex iderex self-assigned this Aug 27, 2026
@iderex iderex added the tests label Aug 27, 2026
@iderex
iderex merged commit e61e462 into main Aug 27, 2026
25 checks passed
@iderex
iderex deleted the release/smoke-the-published-artefact branch August 27, 2026 06:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant