tables: the list-walk gate derives the list-free set from the schemas (red on main since #662) - #674
Merged
Merged
Conversation
The gate asserts §13.5's zero-cost promise for the text form: the JSON walker's list half is a body of non-template code, so it rides only in a unit that declares an unbounded array and a list-free unit pays nothing for it. The premise is right. The instrument named the list-free set by directory, which is a copy of a fact the schemas own, and #662 changed that fact: `Spans tracks map[uint8][]Item` makes the maps unit list-bearing, the emitter emits the half into it correctly, and the gate reports correct emission as a leak. LIST-WALK GATE FAILED: the list half reached the list-free unit build/tables-generated/maps/CellsTable.cpp internal/listwalk asks the compiler's own IR instead. A unit is list-free when no table in its closure carries an unbounded array, and a map's generated entry is a table of that closure (§2.8), so `map[K][]T` reaches the answer with no clause of its own. The corpus is read out of the Makefile's `tables_generate` define, which is the list that generates the tree the gate scans, so a unit added to the build is under the gate the same day. The derivation never asks the C++ emitter, whose own `unitHasList` the controls sabotage. The scan widened with the fix: 21 list-bearing .cpp files across five units where it held five in one directory, and none of the half in the 42 .cpp files of the 32 list-free units where it scanned three directories. arms, rt1 and rt2 were list-bearing and unheld. Three negative controls, each red by name. PLANTED puts the half into a list-free unit's emitted .cpp and holds the scan alone. UNGATED removes the emitter's `if anyList` so every unit carries the real half, which is what §13.5's ruling costs a list-free consumer the day the gating goes. DROPPED is the other half of that switch: a list-bearing unit left with the stub, which a gate that only refused leaks would pass. The map-walk gate keeps the same hand-kept shape and is narrow rather than red: its byte compare covers ten .cpp files of the seventeen that carry a map half, and its absence scan covers two directories. Owed separately. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This was referenced Sep 7, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
make testhas been red attables-json-list-walkon main since #662 merged(e123f1b). This is the instrument, not the emitter.
Red first, on main's tree
Clean clone of
origin/main(dcdd5cd),make bin/schema,make build/tables-generated/.stamp, then:The judgment
The gate's PREMISE is right. §13.5 put the JSON walk in
<Base>Table.cppbecause it is non-template, non-constant code every consumer would otherwise
re-parse, and the list half of it rides only where a list rides. A list-free
unit pays nothing for the text form's list surface. That is §2.2's zero-cost
property holding for the text form, and it did not move.
The gate's INSTRUMENT was wrong. It named the list-free set by directory:
for f in build/tables-generated/examples/*Table.cpp \ build/tables-generated/pointers/*Table.cpp \ build/tables-generated/maps/*Table.cpp; do ...That is a COPY of a fact the schemas own. #662 added
Spans { tracks map[uint8][]Item }totables/maps, which makes the wholeunit list-bearing; the emitter emitted the half into all ten of its
.cppfiles, correctly, and the copy standing in the recipe reported correct emission
as a leak. #662's own body records the movement ("the four
<Base>Table.cppfiles gained the JSON LIST WALK") and did not carry it into the recipe. A
copied fact rots on the day the thing it copies changes.
The instrument's new shape
internal/listwalkasks the compiler's own IR.unbounded array.
ir.ListFieldswalksir.TableClosureand names every[]T, and a map's generated entry is a real table of that closure (§2.8),so
map[K][]Treaches the answer with no clause of its own. No directoryname appears anywhere in the gate.
tables_generatedefine, whichis the list that GENERATES the tree the gate scans. One list, read by both
halves, on
internal/viewlisting's rule: a unit added to the build is underthe gate the same day, list-bearing or list-free.
.cppcarries the half; a list-free unit's carries none of it; the half is ONE
half, the same bytes wherever it rides.
internal/codegen/cpptablehasits own
unitHasList, and a gate that shared it would move with everysabotage of it and stay green. Two of the three controls below patch exactly
that gating.
Driven from the Makefile the way
tables-viewdrivesinternal/viewlisting:SCHEMA_LIST_WALK_DIRpoints at the generated tree,SCHEMA_LIST_WALK_UNITSnarrows a control's run, and without either the test still derives the answer
for every unit of the corpus, so
go test ./...keeps saying something aboutthe fact the gate is built on.
Green, and wider
The scan widened. It held five
.cppfiles in one directory and scanned threedirectories for the absence; it now holds all 63 generated
.cppfiles of thecorpus.
arms,rt1andrt2carry the list half and were held by neitherclause.
The controls
make tables-json-list-walk-negative-controls, new, and inmake testbesidethe gate.
PLANTED puts the half into a list-free unit's emitted
.cppin a throwawaycopy. Nothing is generated and no emitter is patched, so it holds the SCAN
alone:
UNGATED removes the emitter's own gating through a Go overlay
(
listAdapters := tableJsonNoListAdaptersbecomes the real half), so everyunit's
.cppcarries it. This is the control the gate exists for: it is what§13.5's ruling costs a list-free consumer the day the gating goes.
DROPPED is the other half of that switch: the list half is never emitted,
so a list-bearing unit is left with the stub. A gate that only refused leaks
would stay green here.
Each control narrows with
SCHEMA_LIST_WALK_UNITS, so its red is its own andnot thirty-five absent trees. A sabotage that patches nothing is itself a
failure, and a red that is not on the named clause is a failure too.
Named follow-on, not folded in
The map-walk gate keeps the same hand-kept shape. It is narrow rather than
red: its byte compare covers the ten
.cppfiles ofmaps/while seventeencarry a map half (
lists/'s five,rt1,rt2), and its absence scan namesexamples/andpointers/only. Nothing is wrong with what it asserts today;it is one corpus change away from the failure this PR repairs, and
internal/listwalkgeneralizes to it in a predicate. Left for its own issuerather than widened into a red-fixing PR.
Test
make tables-json-list-walkandmake tables-json-map-walkgreen.make tables-json-list-walk-negative-controlsred on all three controls, byname.
go test ./...green across 31 packages, includinginternal/ci'sTestEveryPackageTheBuildRunsIsCommitted, which is what makesthe new package's tracking a gate rather than a habit.
make testwhole to theabsent-toolchain stop: it reaches
build-conformance-csand stops ondotnet: command not found(Error 127), with the list-walk gate and its threecontrols green above it and no other failure in the log.
🤖 Generated with Claude Code