Skip to content

every row, every leg: the gate that keeps the edge cases held - #970

Draft
gafferongames wants to merge 3 commits into
fixed-table-formfrom
rowan/every-row-every-leg
Draft

every row, every leg: the gate that keeps the edge cases held#970
gafferongames wants to merge 3 commits into
fixed-table-formfrom
rowan/every-row-every-leg

Conversation

@gafferongames

Copy link
Copy Markdown
Contributor

The silence

The rows of docs/FIXED-FORM-VERSIONING-TESTS.md are the fixed form's versioning edge cases — one definition change per row, each read two ways — and each leg's harness probes them by row name: {row: "field_append"} in the Go-driven harnesses, field_append_case() in the C++ reference. CI's full lane runs all of them.

Nothing said a row is probed on every leg. A row could be written into the page, probed on four legs, and nobody would see the other five. Glenn, today:

"The edge cases must be probed, tested and known to hold, and stay held."
"Everything you decide, every edge case you fix, this creates new tests that enforce that decision moving forward across all languages."

The gate

compiler/everyrow_test.go, two tests and a ledger.

TestEveryRowEveryLeg parses the page's row tables — a table whose first header cell is row, which is the main table of definition changes and the divergence table, and not the tables about the rows — greps each of the nine legs' harnesses for each row name, and fails naming every (row, leg) pair nobody probes. The match is anchored on both sides and allows only the reference's _case / _hostile_case suffixes, so string_grow takes no credit from wstring_grow and fixed_I_grow none from fixed_I_grow_element. A leg whose harness file does not exist yet probes nothing, which is the true state of the Java leg.

A row whose NEW-READS-OLD column is is LOCK-only: no file exists for a leg to read, its proof is the lock's refusal in internal/lockfile, and the nine read harnesses do not owe it. That is read off the page rather than listed in Go, so a row that gains a read column starts being owed on nine legs without anyone editing this file.

docs/FIXED-FORM-OWED-ROWS.md is the exception list, and it is a ledger rather than an escape — row | leg | owed by | since. The gate also fails when a line is for a pair that is probed, for a row the page does not have, for a leg outside the nine, or for a row the page marks LOCK-only. The list can only shrink. A row added to the page is owed on nine legs the minute it is written; a row that lands on a leg is one line deleted.

TestEveryRowHasCorpusBytes is the second gate: a row with a read column needs bytes, and the dump writes one row= per row into build/fixedform-corpus/manifest.txt. The corpus is a build product, so the test skips when it is absent and fails under SCHEMA_REQUIRE_CORPUS. Its exceptions are the same ledger, under the target corpus.

The counts, seeded with today's true state

rows in the page's row tables 39 (35 definition changes + 4 divergence rows)
LOCK-only (NEW-READS-OLD is ) 10field_undeprecate field_modify array_shape text_kind range_added default_change keyword_change closure_plain_table closure_variable_kind closure_self
rows owed a read on each leg 29
pairs (29 × 9 legs) 261
probed today 192
owed today 69 leg pairs, plus 5 rows the dump writes no bytes for
of those, unassigned 50 (45 leg pairs + 5 corpus rows)

The 69 are three groups:

Red first

By hand, all five refusals, each observed red and then green again: a deleted ledger line (field_append on java named), a ledger line for a pair that is probed, a row that does not exist, a leg outside the nine, a LOCK-only row listed as owed; and the corpus half skipping without SCHEMA_REQUIRE_CORPUS and failing with it. TestEveryRowProbeNameIsAnchored pins the near-misses as a unit test.

Where it runs

ci.yml's go-test, as a named step after make tables-go-versioning — that step is what builds the corpus, so the corpus half runs for real instead of skipping. It rides the plain go test ./... too, where the corpus half skips by design. Seconds: two file reads and a regexp. #966's fast lane has not merged; when it does, this belongs in lint-fast.

gofmt -l . clean; go test ./internal/ci/ green; go test ./compiler/ -run 'EveryRow|Owed' green with the seeded ledger and the corpus built.

🤖 Generated with Claude Code

gafferongames and others added 3 commits September 11, 2026 08:48
The rows of docs/FIXED-FORM-VERSIONING-TESTS.md are the fixed form's versioning
edge cases, and each leg's harness probes them BY ROW NAME. Nothing said a row
is probed on EVERY leg: a row could be written into the page, probed on four
legs, and nobody would see the other five. Glenn today: "The edge cases must be
probed, tested and known to hold, and stay held."

compiler/everyrow_test.go is that sentence as a test. It reads the page's row
tables (the main table and the divergence table — a table whose first header
cell is `row`), takes the nine legs' harnesses, and fails naming every
(row, leg) pair nobody probes. A row whose NEW-READS-OLD column is `—` is
LOCK-only and owed to internal/lockfile rather than to the nine, read off the
page so a row that GAINS a read column starts being owed without editing Go.

docs/FIXED-FORM-OWED-ROWS.md is the ledger of the pairs that are owed today,
each with the PR or card that owes it. It can only shrink: the gate fails on a
line whose pair IS probed, a row the page does not have, a leg outside the nine,
or a row the page marks LOCK-only. A row added to the page is owed on nine legs
the same minute it is written.

The second half, TestEveryRowHasCorpusBytes, wants a `row=` in
build/fixedform-corpus/manifest.txt for every row with a read column: a skip
when the corpus is absent, a failure under SCHEMA_REQUIRE_CORPUS. Its exceptions
are the same ledger under the target `corpus`.

Seeded with today's true state: 39 rows, 10 LOCK-only, 29 × 9 = 261 pairs, 192
probed, 69 owed — the 24 rows #920 brings to the Java leg, `fixed_I_grow_element`
on all nine, and the four divergence rows (§5.8's 4, 9, 11, 12) on all nine —
plus 5 rows the dump writes no bytes for. 50 of those lines are unassigned.

Red first, by hand, on all five refusals: a deleted line, a line for a probed
pair, an unknown row, a leg outside the nine, a LOCK-only row; and the corpus
half skipping without SCHEMA_REQUIRE_CORPUS and failing with it.
TestEveryRowProbeNameIsAnchored is the gate's own red, so `string_grow` never
takes credit from `wstring_grow`.

Wired into ci.yml's go-test after `make tables-go-versioning`, which is the step
that builds the corpus, so the corpus half runs for real. #966's fast lane has
not merged yet.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
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