Skip to content

Hold the three exit-code declarations to one contract (#125) - #126

Merged
iderex merged 1 commit into
mainfrom
scaffolding/one-contract-for-the-exit-codes
Aug 12, 2026
Merged

Hold the three exit-code declarations to one contract (#125)#126
iderex merged 1 commit into
mainfrom
scaffolding/one-contract-for-the-exit-codes

Conversation

@iderex

@iderex iderex commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Closes #125

What this changes

Adds an exit-code leg to internal/invariants. It reads every exit-code
constant out of the tree the scan already read and refuses two things: a code
declared with two different numbers, and one number declared under two
different codes. Both refuse under exit-code-declarations-disagree, because
the repair for either is the same repair.

Nothing moves. The three declarations stay where their producers are, which is
the reading internal/hardware/hardware.go already argued for and the reason
the fourth code is not written into the runner as a branch nothing can reach.
The reason each declaration is held to the others is written at each of the
three, because that is where somebody moving a number is looking, rather than
only at the leg or only in the issue.

The leg finds a declaration by the shape of its name rather than from a list of
files, so a fourth declaration somewhere nobody has thought of yet is held to
the same contract without the leg being edited.

What failure it prevents

Each of the three declarations was right about itself and every code each one
returns is reached by a test. What was unproved was that they are the same
numbers as each other. A number that moved in one of them compiled, passed the
tests of the package it moved in, and changed the meaning of anything keyed on
it without that reader's file being edited.

The second direction is what reaches the harness code. It is declared exactly
once, so there is no second copy for the first direction to compare against,
and what can go wrong there is a collision with a meaning the runner's three
already carry.

What was run

At b3ba466268feba5389f2c9047e65235966b2ad64.

$ go build ./... && go vet ./... && gofmt -l .
(no output from any of the three)

$ go test -count=1 ./...
ok  	github.com/Flowfin/lab/cmd/lab	6.236s
ok  	github.com/Flowfin/lab/cmd/pullrequest	1.230s
?   	github.com/Flowfin/lab/experiments/reading-a-tree-of-records	[no test files]
ok  	github.com/Flowfin/lab/internal/check	1.320s
ok  	github.com/Flowfin/lab/internal/hardware	1.115s
ok  	github.com/Flowfin/lab/internal/invariants	1.264s
ok  	github.com/Flowfin/lab/internal/prose	1.035s
ok  	github.com/Flowfin/lab/internal/pullrequest	1.562s

The leg was deleted from the scan and the suite went red on both cases, which
is what says the cases are proving the leg rather than passing beside it:

$ go test -count=1 ./internal/invariants/     # with the leg unwired from Scan
--- FAIL: TestCases (0.01s)
    --- FAIL: TestCases/an-exit-code-declared-with-two-numbers (0.00s)
        invariants_test.go:159: expected refusal not produced: exit-code-declarations-disagree
    --- FAIL: TestCases/one-number-declared-under-two-exit-codes (0.00s)
        invariants_test.go:159: expected refusal not produced: exit-code-declarations-disagree
FAIL

It also bites on this repository rather than only on a fixture. Moving one
number in one of the three declarations, and separately giving the harness code
a number the runner already uses:

$ go test -count=1 -run TestThisRepositorySatisfiesTheInvariants ./internal/invariants/
  # with exitRefused = 5 in cmd/pullrequest/main.go
--- FAIL: TestThisRepositorySatisfiesTheInvariants (0.11s)
    ..\..\cmd\pullrequest\main.go: it declares exitRefused as 5 at line 41, and ..\..\cmd\lab\main.go declares the same code as 1, so the contract record 0011 fixes has two numbers in this tree (exit-code-declarations-disagree)

  # with ExitAskedAndDeliveredNothing = 2 in internal/hardware/hardware.go
--- FAIL: TestThisRepositorySatisfiesTheInvariants (0.11s)
    ..\..\internal\hardware\hardware.go: it declares ExitAskedAndDeliveredNothing as 2 at line 45, and ..\..\cmd\lab\main.go declares exitCannot as the same number, so one code in record 0011's contract answers to two names (exit-code-declarations-disagree)

Both edits were reverted and the full suite above was run afterwards.

The report says what the leg covered:

  the exit codes: 41 examined
0 refused

The means is Go, in the package that already holds the rules that are properties
of this repository's own tracked text, using go/parser from the standard
library. It carries the three rules: the property is refusable through the same
Refusal the package already has, the cases prove it bites, and the numbers
above come from commands. It adds no language, runtime or dependency this tree
does not already carry, and it is testable by the harness that exists rather
than needing a parallel one.

This change has no second reader. Nobody but me has read it, and the evidence
above stands in place of that rather than beside it.

What this does not do

It does not make the record the authority in code. Nothing here reads
docs/decisions/0011-the-exit-codes.md, so the three declarations moving
together, in the same change, to numbers the record does not name would stay
green. What is refused is disagreement between declarations, not disagreement
with the document.

It cannot see a code whose name does not follow the convention the three follow.
That is stated at the leg and at each declaration rather than left to be found.

Both directions refuse under one property, so a case comparing property sets
cannot tell them apart and a change that loses one of the two stays green on the
other's case. That is the same bound this package already carries for the notice
leg, and each direction has its own case anyway.

Record 0011 fixes four codes and their meanings, and this tree declares
them in three places because a code lives next to the thing that can
return it. Each declaration was right about itself and nothing was right
about all of them together, so a number that moved in one of them
compiled, passed the tests of the package it moved in, and changed the
meaning of every reader keyed on it without their files being edited.

The exit-code leg in internal/invariants reads every exit-code constant
out of the tree and refuses a code declared with two numbers, or a number
declared under two codes. The second direction is the one that reaches
the harness code: it is declared exactly once, so there is no second copy
of it for the first direction to compare against, and what can go wrong
is a collision with a meaning the runner's three already carry.

The reason is written at each of the three declarations rather than only
at the leg, because that is where somebody moving a number is looking.

Both directions have a case that trips exactly this property and no
other, and both name the same near neighbour, which is that case with
the one character or the one identifier repaired.

Signed-off-by: Nils Lehnen <30603423+iderex@users.noreply.github.com>
@iderex
iderex merged commit eb88aad into main Aug 12, 2026
24 checks passed
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.

Hold the three declarations of the exit codes to one contract

1 participant