Skip to content

tables: map values of array, enum-extent, unbounded and blob kinds in the C++ reference (#628) - #662

Merged
rowan-claude merged 8 commits into
mainfrom
fix-628-map-value-kinds
Sep 7, 2026
Merged

tables: map values of array, enum-extent, unbounded and blob kinds in the C++ reference (#628)#662
rowan-claude merged 8 commits into
mainfrom
fix-628-map-value-kinds

Conversation

@rowan-claude

@rowan-claude rowan-claude commented Sep 7, 2026

Copy link
Copy Markdown
Collaborator

map[K][N]T, map[K][..N]T, map[K][E]T, map[K][]T, map[K]*string and
map[K]*bytes now emit C++ that compiles, and the corpus, the goldens, the
oracle and the controls say so.

Closes #628.

What lands

The emitter. internal/codegen/cpptable/maps.go. The entry's own storage
was right in all six: §2.8 makes the entry a real table whose value is an
ordinary field, so int32_t value[3], the element array beside value_count,
TableKeyed<int32_t, Slot>, TableList<Item> and the eight-byte TableRef
were all already emitted. What had no spelling was the HANDLE, because
mapValueStorageType asked cppFieldType for one type name and that function
answers a field's ELEMENT type, not its array, its keyed slot, its list slot or
its blob.

The rule the fix states is one sentence: the handle follows the storage.

value storage handle
[N]T T value[N], one member <Entry>Value *, a pointer to the ARRAY
[..N]T T value[N] + int32 value_count, a PAIR the ENTRY
[E]T TableKeyed<T, E>, one member TableKeyed<T, E> *
[]T TableList<T>, one member TableList<T> *
*string, *bytes TableRef, one member the SLOT on the builder, const TableBlob * on the const Find

mapValueIsText becomes mapValueIsPair and now covers [..N]T as well as
the three text kinds, which is #626's rule applied to the second storage shape
that has two members. [N]T gains one alias per entry, <Entry>Value, because
a return type cannot spell T (*)[N] without wrapping the declarator around
the function name. A blob takes its own arm in the pointer branch because it
has no declared type name, which is exactly what the two At( errors below
were.

The corpus. Six units under tables/maps, landed on their own in 588af57
before any emitter change, each a map and a field past it so a stop is visible:

table Cells  { rows   map[string(8)][3]int32   after int32 }
table Runs   { spans  map[uint16][..4]Item     after int32 }
table Slots  { seats  map[int32][Slot]int32    after int32 }
table Spans  { tracks map[uint8][]Item         after int32 }
table Docs   { pages  map[string(8)]*string    after int32 }
table Chunks { blobs  map[int32]*bytes         after int32 }

Red first

Against 1f62d47, the merge with origin/main at bb5f3bd and before any
emitter change. clang at the repo's own table flags, over a translation unit
that includes one generated header and nothing else:

CellsTable.h:6144: cannot initialize return object of type 'const int32_t *'
  with an rvalue of type 'const int32_t (*)[3]'
RunsTable.h:6134: cannot initialize return object of type 'const Item *'
  with an rvalue of type 'const Item (*)[4]'
SlotsTable.h:6204: cannot initialize return object of type 'const int32_t *'
  with an rvalue of type 'const TableKeyed<int32_t, Slot> *'
SpansTable.h:6141: cannot initialize return object of type 'const Item *'
  with an rvalue of type 'const TableList<Item> *'
DocsTable.h:6154: a type specifier is required for all declarations
  inline const  * TableEntryFound( const DocsPagesEntry * entry )
ChunksTable.h:6145: use of undeclared identifier 'At'

The pointer types in the first four are the whole diagnosis: the storage was
already the array, the keyed slot and the list slot, and only the return type
was unspellable.

The second defect, found by the corpus

internal/tablewire's visitEdges had no case for a MAP field, so the tool's
node-table numbering never reached the pointer slots inside an entry. The old
corpus hid it: Fleet.by_id's two keys name the node Fleet.flagship names,
so every node reachable through a map was already numbered through a pointer
field beside it. Docs.pages and Chunks.blobs are the first slots to name a
node NOTHING ELSE names, and the engine read the reference's bytes clean and
then re-encoded short:

map_docs: the engine re-encoded 64 bytes, the reference wrote 123
map_chunks: the engine re-encoded 61 bytes, the reference wrote 107

Fixed in fcf707f with the page's own sentence: a map is a by-value edge of the
one declaration-order walk, reached at its field's position, entries in
ascending key order, each descended before the next (§2.8, §3.1).
MapEntryOrder is that order, lifted out of encodeMap so the numbering and
the writer take it from one place.

Both engines

test/tables/maps_main.cpp gains six rows, one per unit, each pinning its wire,
holding measure == save, loading the region, re-saving it byte for byte, and
reading the text form back to the same wire.
test/conformance/harness/maps_test.go gains the six names, so the compiler's
own engine reads the reference's bytes, writes them back byte for byte, renders
the text and reads that text to the same bytes. The two engines' TEXTS agree:

{ "rows":   { "alpha": [ 10, 11, 12 ], "beta": [ 0, 1, 2 ] } }
{ "spans":  { "4": [ { "count": 40 }, { "count": 41 } ], "9": [ { "count": 90 } ] } }
{ "seats":  { "-2": { "Alpha": 2, "Beta": 101 }, "6": { "Alpha": 1, "Beta": 100 } } }
{ "tracks": { "1": [ ..., 3 items ], "8": [ ..., 2 items ] } }
{ "pages":  { "alpha": "first page", "beta": "second page" } }
{ "blobs":  { "-3": "3q2+7w==", "2": "AAGA/w==" } }

Every map is built OUT OF KEY ORDER, so the pinned wire says the four writing
walks sorted. Spans carries three elements under key 1 and two under key 8,
the SECOND-inserted key holding the longer list, so a walk that laid the
entries' lists in insertion order would read one list's elements as another's.
Slots and Chunks use signed keys, so -3 and -2 sort first.

The controls

New: tables-maps-entry-node-negative-control. It short-circuits the
per-entry Number call in the map's by-value edge, so a node named ONLY by a
map entry takes no index. Docs and Chunks are what meet it, and the answer
is the -1 §7.6 gives an unreached pointer rather than a wrong wire:

FAIL table wire golden map_docs: -1 bytes written, 123 pinned
FAIL test/tables/maps_main.cpp:1873: need > 0
FAIL test/tables/maps_main.cpp:1874: need = -1, past the 268435456 byte measure ceiling
FAIL table wire golden map_chunks: -1 bytes written, 107 pinned
FAIL test/tables/maps_main.cpp:1959: chunk_need > 0
FAIL test/tables/maps_main.cpp:1960: chunk_need = -1, past the 268435456 byte measure ceiling

Fleet.by_id cannot meet it, because Fleet.flagship names the same node.
Its sabotage carries an unbalanced parenthesis a $(call) argument cannot, so
the recipe is spelled out as the keylength one is.

tables-maps-value-reset-negative-control goes 4 to 10 failures, one per
value kind's duplicate row, which is the runtime behaviour each kind adds:

FAIL test/tables/maps_main.cpp:1571: repeat != NULL && ( *repeat )[0] == 0                    # [3]int32 reset whole
FAIL test/tables/maps_main.cpp:1655: repeat != NULL && repeat->value_count == 0               # [..4]Item, the count
FAIL test/tables/maps_main.cpp:1656: repeat != NULL && repeat->value[0].count == 0            # and the elements
FAIL test/tables/maps_main.cpp:1739: repeat != NULL && ( *repeat )[Slot::Alpha] == 0          # every keyed slot
FAIL test/tables/maps_main.cpp:1843: repeat != NULL && repeat->count == 0                     # the list slot, back to empty
FAIL test/tables/maps_main.cpp:1927: repeat != NULL && repeat->value == 0                     # the blob slot, back to null

tables-maps-depth-negative-control now names the Spans rows. An entry's
list elements are a term of the map's extent at the entry's depth, so a measure
summed at one depth only leaves them unplaced:

FAIL test/tables/maps_main.cpp:1791: one != NULL && one->count == 3
FAIL test/tables/maps_main.cpp:1797: eight != NULL && eight->count == 2
FAIL test/tables/maps_main.cpp:1804: SpansSave( loaded, again, sizeof( again ) ) = 55, want 121

The compile-time half has NO runtime control, by construction: a sabotage of
the storage line produces no binary to run. Its evidence is the red-first quote
above.

Seventeen map controls, all red on purpose:
sort 78, dead 6, ascending 4, duplicate 2, keykind 3, clamp 2, fit 2, cap 1, depth 38, textorder 6, keylength 4, keyidentity 2, keydomain 3, placefail 1, valuereset 10, entrynode 6, unreached 2.

Two rows moved so the instrument survives the sabotages beside it, and each
was a real hole in the gate rather than a convenience. The Chunks half left
the tail of test_blob_values for its own function, because a sabotage that
refuses one unit's save must leave the other unit reporting. And the Spans
element reads sit under their count, because a list whose elements were never
placed has a NULL element pointer: under the depth sabotage that read
faulted, and a fault takes the buffered output of every row with it, which is
what "the gate went red, but not on a CHECK" was saying.

The refusal

map[K]*wstring is the one value kind §2.8 lists that is refused BY NAME, and
internal/check/tables_test.go gains the case. The entry is where the refusal
has to reach a map's value, because the value is a field of a table nobody
wrote:

field value: *wstring is specified ahead of its implementation and no backend
emits it (docs/SPEC-TABLES.md §2.5)

No kind is refused under a map KEY that is not already refused there: the key
refusals are unchanged and their fourteen diagnostics cases stand.

The page

§2.8 gains two paragraphs. THE VALUE'S STORAGE IS THE ROW ITS KIND TAKES AT
A FIELD, kind by kind. THE HANDLE FOLLOWS THE STORAGE: a pointer to the value
member where the storage is one member, the ENTRY where it is two, the ARRAY
rather than its first element for a [N]T, and for a pointer value the SLOT on
the builder against the RESOLVED node on the const Find.

§4.2 gains one: a map's value is a FIELD POSITION, so the array element
kind, the kind 17 node index and the kind 12 and kind 33 payloads the mutator
list already names all land inside an entry without a strategy of their own,
and what stays the map's is the KEY.

§15 gains a named follow-on, below.

The goldens

NEW: testdata/golden/tables/maps/{Cells,Runs,Slots,Spans,Docs,Chunks}Table.{h,cpp}
and six wires, testdata/wire/tables/map_{cells,runs,slots,spans,docs,chunks}.bin.

MOVED, and every one has a named reason.

The unit's SHARED VOCABULARY (§3.3) grew, because mapdemo gained twelve
tables: the six holders and the six entries their maps generate. The vocabulary
is emitted identically into every header of a unit, which is why three
unrelated headers move and why no per-type codec does:

before after
TableIds::kCapacity 50 67
kTableRetainKnownIds 50 67
kTableMessageEntriesHere 52 66
kTableNodeTableFieldSlot 40 48
kTableAnnounceBytes 615 786
BuildVersion 0x28abc7f5927a7539 0xe4ae2b700e9c3e19

plus every w.put( <slot>, kTableMessageRefBitsHere ) and node.type_slot,
because the vocabulary is sorted and new types shift the slots after them. The
two message wires follow from the same fact: map_conn IS the announcement,
615 to 786 bytes, and map_full_message is 172 to 179 bytes and repacks,
because a body's type reference is an index into that vocabulary.

And the four <Base>Table.cpp files gained the JSON LIST WALK, which is the
one movement mapdemo has never had before: Spans.tracks's []Item value is
the unit's first unbounded array, so TableJsonIsList, TableJsonWriteList and
TableJsonReadList stop being the three unreachable stubs a list-free unit
emits and become the walk. It is the unit's shared vocabulary too, emitted into
every map-bearing .cpp of the unit, which is why all four move identically.
make tables-json-map-walk still reports one map half, byte-identical in ten
map-bearing .cpp files and in no map-free one.

tables/maps/tables.baseline is ADDITIVE, regenerated with its reason through
schema tables-baseline --update over the merge rather than hand-merged. The
value lines record kind=14 elem=4 array=fixed bound=3,
kind=14 elem=13 type=Item array=bounded bound=4,
kind=16 elem=4 array=keyed bound=2 key=Slot,
kind=14 elem=13 type=Item array=unbounded, kind=17 type=string and
kind=17 type=bytes.

Page silences decided, for the owner rather than quietly

  1. What a [N]T value's HANDLE is. §2.8 said nothing. Decided: the ARRAY,
    <Entry>Value *, which keeps the extent. The alternative, the decayed
    T *, loses the bound a caller would need to write the last slot, and the
    entry, tables: text map values, string(N), wstring(N) and bytes(N), in the C++ reference (#619) #626's answer for a pair, is not warranted here because the storage
    is one member and reaching it needs no second name. The cost, stated: one
    typedef per such entry in the header.
  2. What a BLOB value's const Find answers. §2.8 says a *T value's
    Find answers the resolved pointer. A blob has no declared type and no
    <T>At. Decided: const TableBlob *, through TableBlobAt, which is what
    the same slot answers at a field (§2.5, §6.3), with TableStringViewOf and
    TableBytesViewOf beside it as at a field. The builder handle stays the
    SLOT, which is what TableStringEmplace and TableBytesEmplace fill.
  3. Whether an entry's LIST elements ride in the holder's node extent.
    §2.8's memory layout states the pre-order placement for a map inside an
    entry's value and says nothing about a list there. Decided: the same rule,
    because §2.9 makes a list's elements the holder's node extent and an entry
    is by value inside that extent. Both engines and LoadMeasure agree, and
    tables-maps-depth-negative-control is red on it, so it is pinned either
    way and worth a ruling if it should read otherwise.

Named follow-on, found by probing every value kind §2.8 lists

An optional map value's presence companion has no handle (§15, new row).
?T and ?[N]T store the value beside a bool presence companion, which is
two members, so §2.8's handle rule answers the ENTRY. The reference answers the
value member instead, so a caller can fill a map[K]?T's value and cannot set
its presence, and the value is elided on every wire. ?[..N]T is the exception
by accident: its count companion already makes it a pair. Same root cause, one
predicate away, and left for its own issue rather than widened into this one.

Everything else §2.8 lists compiles as the ordinary field it is, each confirmed
by generating a unit and compiling the header and its walk alone: a scalar, an
enum, a flags mask, a declared type, a table by value, *T, a map, and a
union.

Merged, not rebased

origin/main was at bb5f3bd (#626) when this branch resumed with one commit
on it. Integrated with git merge origin/main; the one conflict was
tables/maps/tables.baseline, resolved by taking main's file and regenerating
with schema tables-baseline --update --reason. No golden was hand-merged.

Test

make tables-maps, make tables-json-map-walk and
make tables-maps-negative-controls green. go test ./... green across 30
packages. make test whole; the run is still going as this opens and its result,
with the absent-toolchain failures classified apart, lands in this body.

🤖 Generated with Claude Code

Correction from the cold read

The sentence above claiming everything else §2.8 lists compiles was wrong for one set: arrays of pointers under a map key, [N]*T, [..N]*T and []*T, take the map[K]*T arm through mapValueIsPointer (maps.go:87 tests Type.Pointer alone) and emit ItemAt on an array; red on main too, so inherited rather than introduced here. Owed as #666, the last of #628's class; this PR lands the six kinds it names.

rowan-claude and others added 7 commits September 6, 2026 20:52
)

Six units under tables/maps, one per value kind §2.8 lists that the C++
reference cannot spell today, each a map and a field past it so a stop is
visible: Cells map[string(8)][3]int32, Runs map[uint16][..4]Item, Slots
map[int32][Slot]int32, Spans map[uint8][]Item, Docs map[string(8)]*string
and Chunks map[int32]*bytes.

RED FIRST. Against this commit's emitter each generated header is refused
alone, clang at the repo's own table flags over a translation unit that
includes it and nothing else:

    CellsTable.h:6138: cannot initialize return object of type 'const int32_t *'
      with an rvalue of type 'const int32_t (*)[3]'
    RunsTable.h:6128: cannot initialize return object of type 'const Item *'
      with an rvalue of type 'const Item (*)[4]'
    SlotsTable.h:6198: cannot initialize return object of type 'const int32_t *'
      with an rvalue of type 'const TableKeyed<int32_t, Slot> *'
    SpansTable.h:6135: cannot initialize return object of type 'const Item *'
      with an rvalue of type 'const TableList<Item> *'
    DocsTable.h:6148: a type specifier is required for all declarations
      inline const  * TableEntryFound( const DocsPagesEntry * entry )
    ChunksTable.h:6139: use of undeclared identifier 'At'

The entry's own storage is right in every one of the six: §2.8 makes the
entry a real table whose `value` is an ordinary field, so the array, the
count companion, the keyed slot, the list slot and the buffer reference are
all already emitted. What has no spelling is the HANDLE.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`internal/tablewire`'s `visitEdges` had no case for a MAP field, so the
node-table numbering never reached the pointer slots inside an entry. The
corpus hid it: `Fleet.by_id`'s two keys name the node `Fleet.flagship`
names, so every node reachable through a map was already numbered through
a pointer field beside it.

`Docs.pages` is `map[string(8)]*string` and `Chunks.blobs` is
`map[int32]*bytes`, and each is the first slot to name a node NOTHING
ELSE names. Against this commit's parent the engine read the reference's
bytes clean and then re-encoded 64 bytes where the reference wrote 123,
dropping both blob records and both pointer fields:

    map_docs: the engine re-encoded 64 bytes, the reference wrote 123
    map_chunks: the engine re-encoded 61 bytes, the reference wrote 107

The fix is the page's own sentence: a map is a by-value edge of the one
declaration-order walk, reached at its field's position, its entries
visited in ascending key order, each entry descended before the next
(docs/SPEC-TABLES.md §2.8, §3.1). `MapEntryOrder` is that order, lifted
out of `encodeMap` so the numbering and the writer take it from one
place and a wire cannot disagree with its own node table.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
)

The handle every map surface hands back now follows the STORAGE the page
gives a field of the value's kind, so the six kinds #628 names emit C++
that compiles (docs/SPEC-TABLES.md §2.8, §4.2).

The entry's own storage was right in all six: §2.8 makes the entry a real
table whose `value` is an ordinary field, so `int32_t value[3]`, the
element array beside `value_count`, `TableKeyed<int32_t, Slot>`,
`TableList<Item>` and the eight-byte `TableRef` were all already emitted.
What had no spelling was the handle, because `mapValueStorageType` asked
`cppFieldType` for one type name and that function answers a field's
ELEMENT type, not its array, its keyed slot, its list slot or its blob.

- `[N]T` is ONE member, so the handle is a pointer to the ARRAY and keeps
  the extent. A return type cannot spell `T (*)[N]` without wrapping the
  declarator around the function name, so the entry gains one alias,
  `<Entry>Value`, and the handle is `<Entry>Value *`.
- `[..N]T` is a PAIR, the elements beside `value_count`, so the handle is
  the ENTRY, which is #626's rule for a text value applied to the second
  storage shape that has two members. `mapValueIsText` becomes
  `mapValueIsPair` and covers both.
- `[E]T` is `TableKeyed<T, E>` and `[]T` is `TableList<T>`, one member
  each, so the handle is that member.
- `*string` and `*bytes` name a BLOB NODE (§2.5), which has no declared
  type name, so the const Find resolves through `TableBlobAt` and answers
  `const TableBlob *` where a `map[K]*T` answers `<T>At`'s pointer. The
  builder's handle stays the slot, which is what an Emplace fills.

RED FIRST, against this commit's parent. clang at the repo's own table
flags, over a translation unit that includes one generated header and
nothing else:

    CellsTable.h:6144: cannot initialize return object of type 'const int32_t *'
      with an rvalue of type 'const int32_t (*)[3]'
    RunsTable.h:6134: cannot initialize return object of type 'const Item *'
      with an rvalue of type 'const Item (*)[4]'
    SlotsTable.h:6204: cannot initialize return object of type 'const int32_t *'
      with an rvalue of type 'const TableKeyed<int32_t, Slot> *'
    SpansTable.h:6141: cannot initialize return object of type 'const Item *'
      with an rvalue of type 'const TableList<Item> *'
    DocsTable.h:6154: a type specifier is required for all declarations
      inline const  * TableEntryFound( const DocsPagesEntry * entry )
    ChunksTable.h:6145: use of undeclared identifier 'At'

BOTH ENGINES. `test/tables/maps_main.cpp` gains five rows, one per unit,
each pinning its wire, holding `measure == save`, loading the region,
re-saving it byte for byte, and reading the text form back to the same
wire. `test/conformance/harness/maps_test.go` gains the six names, so the
compiler's own engine reads the reference's bytes, writes them back byte
for byte, renders the text and reads that text to the same bytes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`tables-maps-entry-node-negative-control` short-circuits the per-entry
`Number` call in the map's by-value edge, so a node named ONLY by a map
entry takes no index. The `Docs` and `Chunks` rows are what meet it:
their `*string` and `*bytes` values are the only slots in this corpus
that name a node nothing else names, and `Fleet.by_id` cannot meet it
because `Fleet.flagship` names the same node.

RED under the sabotage, all six failures in the new rows, and the answer
is the -1 §7.6 gives an unreached pointer rather than a wrong wire:

    FAIL table wire golden map_docs: -1 bytes written, 123 pinned
    FAIL test/tables/maps_main.cpp:1873: need > 0
    FAIL test/tables/maps_main.cpp:1874: need = -1, past the 268435456 byte measure ceiling
    FAIL table wire golden map_chunks: -1 bytes written, 107 pinned
    FAIL test/tables/maps_main.cpp:1959: chunk_need > 0
    FAIL test/tables/maps_main.cpp:1960: chunk_need = -1, past the 268435456 byte measure ceiling

Its sabotage carries an unbalanced parenthesis a `$(call)` argument
cannot, so the recipe is spelled out as the keylength one is.

Two rows moved so the instrument survives the sabotages beside it. The
`Chunks` half left the tail of `test_blob_values` for its own function,
because a sabotage that refuses one unit's save must leave the other unit
reporting. And the `Spans` element reads sit under their count, because a
list whose elements were never placed has a NULL element pointer: under
the `depth` sabotage that read faulted, and a fault takes the buffered
output of every row with it, which is what "the gate went red, but not on
a CHECK" was saying.

The controls now red on the new rows by name, and each is the sabotage
that owns that kind's runtime behaviour:

- `valuereset` 4 to 10 failures, one per value kind's duplicate row: the
  fixed array reset whole, the counted array's elements AND its count,
  every keyed slot, the list slot back to empty, the blob slot to null.
- `depth` names the `Spans` rows: an entry's list elements are a term of
  the map's extent at the entry's depth, so a measure summed at one depth
  only leaves them unplaced.
- The compile-time half of this PR has NO runtime control by
  construction: a sabotage of the storage line produces no binary to run,
  and its evidence is the red-first quote in 4a2d885.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
§2.8 gains two paragraphs. THE VALUE'S STORAGE IS THE ROW ITS KIND TAKES
AT A FIELD, kind by kind, because the entry is a real table and `value` is
an ordinary field of it. THE HANDLE FOLLOWS THE STORAGE: a pointer to the
`value` member where the storage is one member, the ENTRY where it is two,
the ARRAY rather than its first element for a `[N]T`, and for a pointer
value the SLOT on the builder against the RESOLVED node on the const
`Find`.

§4.2 gains one: a map's value is a FIELD POSITION, so the array element
kind, the kind 17 node index and the kind 12 and kind 33 payloads the
mutator list already names all land inside an entry without a strategy of
their own, and what stays the map's is the KEY.

§15 gains a named follow-on. `?T` and `?[N]T` store the value beside a
`bool` presence companion, which is two members, so §2.8's rule answers
the entry; the C++ reference answers the value member, so a
`map[K]?T`'s presence cannot be set and the value is elided on every wire.
`?[..N]T` is the exception by accident, its count companion already making
it a pair. Found by probing every value kind §2.8 lists; left for its own
issue rather than widened into this one.

The one value kind refused BY NAME gains a diagnostics case:
`map[uint32]*wstring`. The entry is where the refusal has to reach a map's
value, because the value is a field of a table nobody wrote. Every other
kind §2.8 lists compiles as the ordinary field it is, each confirmed by
generating a unit and compiling the header alone: a scalar, an enum, a
`flags` mask, a declared `type`, `?T`, `?[N]T`, `?[..N]T` and a union.

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

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@rowan-claude
rowan-claude merged commit e123f1b into main Sep 7, 2026
20 checks passed
@rowan-claude
rowan-claude deleted the fix-628-map-value-kinds branch September 7, 2026 09:34
rowan-claude added a commit that referenced this pull request Sep 7, 2026
The handle every map surface hands back now follows the ARRAY FORM and not
the element, so `[N]*T`, `[..N]*T` and `[]*T` under a map key emit C++ that
compiles (docs/SPEC-TABLES.md §2.1, §2.8, §4.2).

The entry's own storage was right in all three, and so was its value
reset: §2.8 makes the entry a real table whose `value` is an ordinary
field, so `TableRef value[2]`, that array beside `value_count`, and
`TableList<Item *>` were all already emitted, and the codec is the field's
own on every walk. What had no spelling was the HANDLE, because
`mapValueIsPointer` tested `Type.Pointer` alone and sent all three to the
`map[K]*T` arm, which spells `<T>At` on an array.

- the predicate is now pointer AND no array AND no enum key, so it reports
  the ONE slot it was written for;
- `[N]*T` takes the fixed-array arm and its element is a `TableRef`, so
  the handle is `<Entry>Value *` over `TableRef[N]`;
- `[..N]*T` takes the pair arm, because a counted array is a pair whatever
  its element is: the count beside the slots is the second member, so the
  handle is the ENTRY;
- `[]*T` takes the list arm, whose storage already answered
  `TableList<T *>` through the list emitter's own type argument.

`[E]*T`, `[N]*string` and `[]*bytes` are refused by name at the entry's
value (§2.4, §15), so this is the whole set.

RED FIRST, against this commit's parent, in 5bfd348's message: clang at
the repo's own table flags refused each of the three generated headers
alone, on `no matching function for call to 'ItemAt'` and on a `TableRef *`
that cannot be initialized from `TableRef (*)[2]` or `TableList<Item *> *`.

BOTH ENGINES. `test/tables/maps_main.cpp` gains three rows, one per unit,
each pinning its wire, holding `measure == save`, loading the region,
re-saving it byte for byte, and reading the text form back to the same
wire. Each instance carries a SHARED node and a slot that names NONE, so
the `&node` label and the `null` row of §16.7 both ride through a map
value. `test/conformance/harness/maps_test.go` gains the three names, so
the compiler's own engine reads the reference's bytes, writes them back
byte for byte, renders the text and reads that text to the same bytes; the
engine needed no change, because #662's by-value map edge already descends
an entry and the entry's value is an ordinary pointer-array field.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
rowan-claude added a commit that referenced this pull request Sep 7, 2026
… main left stale (#632)

FOUR LINES ARE THIS BRANCH'S, one per emitted message reader whose root's
numbering reaches a *string blob: blobs/AssetsTable.h twice, for Asset and for
Catalog; maps/DocsTable.h once, for a map whose VALUE is a text buffer; and
arms/GateTable.h once, for a blob reached only through a union arm. Each is the
same line, the record's bytes handed to TableUtf8Valid. NO WIRE GOLDEN MOVED:
the write side is untouched, and a read that refuses damage changes no byte a
writer produces.

THE REST WAS ALREADY STALE ON MAIN and `make tables-block-zero-cost` was red
before this branch: e77093c (#658) added the retain walk's `case 15: case 30:`
and e123f1b (#662) moved the map entry readers' text path, and neither re-pinned
tables/maps. maps/ChunksTable.h, RunsTable.h, SlotsTable.h and SpansTable.h
carry nothing of this branch's at all, and CellsTable.h carries none of its four
lines either. The gate now compares 117 Table sources byte-identical to their
pins.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
rowan-claude added a commit that referenced this pull request Sep 7, 2026
…666) (#668)

* tables: the map corpus for the three pointer-array value kinds (#666)

Three units under tables/maps, one per value kind whose element is a
POINTER, each a map and a field past it so a stop is visible: Pairs
map[uint32][2]*Item, Crews map[uint32][..2]*Item and Trails
map[uint32][]*Item. `[E]*T`, `[N]*string` and `[]*bytes` are refused by
name (§11), so this is the whole remaining set of #628's class.

RED FIRST. Against this commit's emitter each generated header is refused
alone, clang at the repo's own table flags over a translation unit that
includes it and nothing else:

    PairsTable.h:6168: no matching function for call to 'ItemAt'
      candidate not viable: no known conversion from 'const TableRef[2]'
      to 'const TableRef' for 1st argument
    PairsTable.h:6169: cannot initialize return object of type 'TableRef *'
      with an rvalue of type 'TableRef (*)[2]'
    CrewsTable.h:6170: no matching function for call to 'ItemAt'
      candidate not viable: no known conversion from 'const TableRef[2]'
      to 'const TableRef' for 1st argument
    CrewsTable.h:6171: cannot initialize return object of type 'TableRef *'
      with an rvalue of type 'TableRef (*)[2]'
    TrailsTable.h:6171: no matching function for call to 'ItemAt'
      candidate not viable: no known conversion from 'const TableList<Item *>'
      to 'const TableRef' for 1st argument
    TrailsTable.h:6172: cannot initialize return object of type 'TableRef *'
      with an rvalue of type 'TableList<Item *> *'

The entry's own storage is right in all three: §2.8 makes the entry a real
table whose `value` is an ordinary field, so `TableRef value[2]`, that
array beside `value_count`, and `TableList<Item *>` are all already
emitted, and so is the value reset that clears every slot. What has no
spelling is the HANDLE: `mapValueIsPointer` tests `Type.Pointer` alone, so
an array of pointers takes the `map[K]*T` arm and spells `<T>At` on an
array.

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

* tables: map values that are arrays of pointers (#666)

The handle every map surface hands back now follows the ARRAY FORM and not
the element, so `[N]*T`, `[..N]*T` and `[]*T` under a map key emit C++ that
compiles (docs/SPEC-TABLES.md §2.1, §2.8, §4.2).

The entry's own storage was right in all three, and so was its value
reset: §2.8 makes the entry a real table whose `value` is an ordinary
field, so `TableRef value[2]`, that array beside `value_count`, and
`TableList<Item *>` were all already emitted, and the codec is the field's
own on every walk. What had no spelling was the HANDLE, because
`mapValueIsPointer` tested `Type.Pointer` alone and sent all three to the
`map[K]*T` arm, which spells `<T>At` on an array.

- the predicate is now pointer AND no array AND no enum key, so it reports
  the ONE slot it was written for;
- `[N]*T` takes the fixed-array arm and its element is a `TableRef`, so
  the handle is `<Entry>Value *` over `TableRef[N]`;
- `[..N]*T` takes the pair arm, because a counted array is a pair whatever
  its element is: the count beside the slots is the second member, so the
  handle is the ENTRY;
- `[]*T` takes the list arm, whose storage already answered
  `TableList<T *>` through the list emitter's own type argument.

`[E]*T`, `[N]*string` and `[]*bytes` are refused by name at the entry's
value (§2.4, §15), so this is the whole set.

RED FIRST, against this commit's parent, in 5bfd348's message: clang at
the repo's own table flags refused each of the three generated headers
alone, on `no matching function for call to 'ItemAt'` and on a `TableRef *`
that cannot be initialized from `TableRef (*)[2]` or `TableList<Item *> *`.

BOTH ENGINES. `test/tables/maps_main.cpp` gains three rows, one per unit,
each pinning its wire, holding `measure == save`, loading the region,
re-saving it byte for byte, and reading the text form back to the same
wire. Each instance carries a SHARED node and a slot that names NONE, so
the `&node` label and the `null` row of §16.7 both ride through a map
value. `test/conformance/harness/maps_test.go` gains the three names, so
the compiler's own engine reads the reference's bytes, writes them back
byte for byte, renders the text and reads that text to the same bytes; the
engine needed no change, because #662's by-value map edge already descends
an entry and the entry's value is an ordinary pointer-array field.

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

* docs: an array of pointers under a map key, and its handle (#666)

§2.8 gains one paragraph between the storage list and the handle rule. AN
ARRAY OF POINTERS IS ITS ARRAY FORM'S ROW OVER A REFERENCE ELEMENT: the
element is the eight-byte reference every pointer field has, and the array
form decides the rest, so `[N]*T` is `N` references and one member,
`[..N]*T` is `N` references beside its `int32` used count and two members,
and `[]*T` is the sixteen-byte list slot over reference elements and one
member. Each slot names a node of the walk below, so two slots may name
one node and a slot may name none. `[E]*T`, `[N]*string` and `[]*bytes`
are refused by name, and the refusal reaches a map's value at the entry.

The handle rule gains the sentence that follows from it: THE ARRAY FORM
DECIDES AN ARRAY OF POINTERS' HANDLE, NEVER THE ELEMENT, which is the one
the C++ reference had backwards.

§4.2's map-value paragraph names the element kind the wire fuzzer's array
strategies meet at these three: kind `17`, a node index, so a `[N]*T`, a
`[..N]*T` and a `[]*T` are the array strategies over node indices rather
than a strategy of their own.

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

* tables: regenerate the maps unit's goldens over the three new roots (#666)

`make update-goldens`. Every moved file is the maps unit and nothing else,
and each movement is the three new roots joining that unit:

- SIX NEW GOLDENS, `PairsTable.{h,cpp}`, `CrewsTable.{h,cpp}` and
  `TrailsTable.{h,cpp}`: the three new units' own emission.
- `tables/maps/tables.baseline`: the projection gains the three roots, the
  three anonymous entries keyed by the holder's wire id and the map
  field's, and the three map fields. Each entry's value renders
  `kind=14 elem=17`, a kind 14 array over kind 17 NODE INDEX elements,
  which is §4.2's row for an array of pointers and the evidence the
  element is a reference and not the pointee.
- THE TEN EXISTING `*Table.h` GOLDENS, on FOUR NAMED CONSTANTS and the
  announcement blob they describe. The unit's vocabulary is one set shared
  by every header of the unit (§3.3), so three roots grow it for all of
  them: `kTableMessageEntriesHere` 66 to 75 and `kTableAnnounceBytes` 786
  to 901, the projection half accounting for six of the nine new entries,
  which moves `kTableNodeTableFieldSlot` 48 to 54, and the tail for three.
  `kTableRetainKnownIds` 67 to 76 is the same count at the retain-unknown
  known-id table (§6.6). No existing `*Table.cpp` moved: the text form's
  translation unit carries no vocabulary constant.
- `FleetTable.h` moves twice more. `Item` is a POINTER TARGET of the unit
  now, so the `ItemAt`/`ItemEmplace` surface is emitted where `Item` is
  declared, which is Fleet's file, and `ShipConfig`'s node slot moves 61
  to 69 with the vocabulary. `BuildVersion` moves with the projection, as
  it does on every projection edit.
- `testdata/wire/tables/map_conn.bin` 786 to 901 bytes: it IS the
  announcement, so it carries the nine new entries.
- `testdata/wire/tables/map_full_message.bin`, 179 bytes both ways: the
  message body references the vocabulary by slot, and `ShipConfig`'s slot
  moved 61 to 69 inside a field of unchanged width.

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

* tables: the predicate's comment carries no em dash (#666)

House rule, and the refusal citation is §2.4 and §15, which is what the
diagnostic itself names.

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

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
rowan-claude added a commit that referenced this pull request Sep 7, 2026
`make conformance-pin update-goldens` on a clean checkout of main rewrites
six files and nothing else: testdata/golden/tables/maps/{Cells,Chunks,Docs,
Runs,Slots,Spans}Table.h. The pins carry neither the retain walk's arm and
enum-reference case (kind 15 and kind 30 resolving as framed content) nor the
map entry loader's present clamp, both of which the C++ table emitter writes
today.

This is the shape #461 describes. The pins were written on the branch that
became #662, which was cut before #658's retain-unknown walk landed and
merged after it; the two diffs do not touch the same lines, so the merge is
clean and nothing in CI reads these files.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
rowan-claude added a commit that referenced this pull request Sep 7, 2026
… (#671)

* tables: a *string blob on a message body carries no content rule, red first (#632)

docs/SPEC-TABLES.md §3.1 states kinds 12 and 33's content rule MET AT A NODE:
"A TEXT blob's CONTENT is refused on the same terms", so a *string blob whose
bytes are not well-formed UTF-8, or which carries a zero byte, is damage. §3.3
says a form-2 body's content rules are §3's, unchanged in what they reject,
and that what differs is only the recovery, which a bit stream does not have.
Neither engine carries it at the message site, where both carry it at the file
site (decodenodes.go and pointers.go).

The two gates are the page's rows over blobdemo's Catalog, whose numbering
reaches a *string blob through note and a *bytes blob through thumb.
test/tables/message_blob_main.cpp is the C++ reference's and
TestAStringBlobRecordOnAMessageBodyCarriesTheContentRule is the oracle's.
Both are RED at this commit: the truncated sequence, the zero byte, the
overlong encoding and the lead byte 0xFF all load with a silent report.

The last row of each is a *bytes blob carrying the same bytes, which must stay
silent: a *bytes blob is bytes and never text, so the rule is the string id's
alone.

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

* tables: a *string blob record on a message body carries the content rule (#632)

Both engines read a form-2 blob record's bytes through the SAME function the
FILE form reads one with, so there is one rule and no second copy.

The C++ emitter: messagevariable.go's PASS TWO, where the record's bytes are
already in hand. The align a blob record spends before its bytes leaves the
span on a byte boundary, so the same pointer goes to the runtime's
TableUtf8Valid and to the memcpy below it, and the line is emitted under
rootReachesStringBlob, the same guard pointers.go emits the file form's under.
The check does NOT ride in MessageRecordScan, which is the framing walk
LoadMeasure shares: ill-formed content is sizeable, so a measure still answers
the region the framing commands and only the decode refuses.

The Go oracle: messagedecode.go's placement loop calls textValid on the
record's bytes under the TString arm alone, which is decodenodes.go's line for
the file form.

What differs from the file form is only the recovery, which a bit stream does
not have: where a file counts the record malformed and reads on with every slot
naming it null, a batch ends there. One malformed counts, the bodies before it
stand, and nothing after is read (§3.3).

A *bytes blob keeps no rule, because it is bytes and never text (§3.1), and
both gates carry the row that says so.

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

* tables: re-pin the zero-cost goldens the blob rule moves, and the six main left stale (#632)

FOUR LINES ARE THIS BRANCH'S, one per emitted message reader whose root's
numbering reaches a *string blob: blobs/AssetsTable.h twice, for Asset and for
Catalog; maps/DocsTable.h once, for a map whose VALUE is a text buffer; and
arms/GateTable.h once, for a blob reached only through a union arm. Each is the
same line, the record's bytes handed to TableUtf8Valid. NO WIRE GOLDEN MOVED:
the write side is untouched, and a read that refuses damage changes no byte a
writer produces.

THE REST WAS ALREADY STALE ON MAIN and `make tables-block-zero-cost` was red
before this branch: e77093c (#658) added the retain walk's `case 15: case 30:`
and e123f1b (#662) moved the map entry readers' text path, and neither re-pinned
tables/maps. maps/ChunksTable.h, RunsTable.h, SlotsTable.h and SpansTable.h
carry nothing of this branch's at all, and CellsTable.h carries none of its four
lines either. The gate now compares 117 Table sources byte-identical to their
pins.

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

* tables: two blades and two pinned vectors under the blob content rule (#632)

ONE BLADE AN ENGINE, each removing exactly one line and each naming what it
turns red. message-blob-accepts-ill-formed drops textValid from the oracle's
message placement loop; message-emitter-blob-accepts-ill-formed drops the
emitted TableUtf8Valid call from messagevariable.go's PASS TWO.

Each blade is drawn twice. Against the PAGE: the oracle's rides in
MESSAGE_FORM_CONTROLS against
TestAStringBlobRecordOnAMessageBodyCarriesTheContentRule, and the emitter's in
the new tables-message-form-blob-negative-control, which builds
test/tables/message_blob_main.cpp against a regenerated tables/blobs. The true
run of that program is the target's own first step, so the gate is green before
the blade is drawn, and the sabotage reddens four rows and leaves the two
silent-report rows standing.

Against EACH OTHER: message_blob_ill_formed_text is blob_str8 re-encoded as a
batch of one with the fourth byte of note's eight byte payload replaced by
0xFF, and message_blob_zero_byte is the same wire with 0x00 there instead, so
the two separate the two halves of one check. Nothing else moves, so what the
two readers answer differently is the content rule and nothing else. The
*bytes record beside it carries bytes no UTF-8 rule would accept and is never
checked, which is the vector's own control.

tables-wire-fuzz-message-blob-oracle-negative-control and its leg twin replay
both vectors alone and require red on each, with the reports mirrored:

  the leg says 0,0,0,0,0,true,read, the oracle says 0,0,0,0,0,false,read
  the leg says 0,0,0,0,0,false,read, the oracle says 0,0,0,0,0,true,read

AND THE LEG GREW THE ROOT THE VECTORS NEED. blobdemo's Catalog is the only root
on the fuzzer's roster whose numbering can PLACE a text blob record, so it is
the only one whose wire can carry §3.1's rule at a node. Its AssetsTable.cpp
was already in CONFORMANCE_SOURCES, so the leg costs one include and one
MESSAGE_VARIABLE row, and the corpus pass now runs 45 roots rather than 44.

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

* docs: §3.3's test ledger names the content rule it now holds at a node (#632)

No sentence of §3 or §3.1 moves and none is added: §3.1 already says a text
blob's CONTENT is refused on the same terms as a kind 12 payload, and §3.3
already says a form-2 body's content rules are §3's. What was stale is HELD BY
TEST, which carries one bullet per rule with its red clause and carried none
for this one. The bullet names the six rows the two engines' gates run, the
*bytes row among them, and the three ways a leg goes red.

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

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
rowan-claude added a commit that referenced this pull request Sep 7, 2026
…#674)

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>
rowan-claude added a commit that referenced this pull request Sep 7, 2026
…e, six baselines committed (#574, #461, #484) (#677)

* CLAUDE.md: the horizon section states the present tree (#574)

The table layer is in the language: `table` declares a data type on the
evolution-tolerant table wire and docs/SPEC-TABLES.md is its normative page
beside docs/SPEC.md, so the source-of-truth bullet names both. The parser
carries `table` as a declaration (internal/parser/parser.go, scanner.KwTable);
the file said it was refused by name.

The protocol layer stays out, as the tree has it: `message` and `object` are
reserved words the parser refuses by name and `contexts` is refused at file
scope. The frozen projection tokens are stated for what they do today, which
is holding every existing unit's id stable.

Glenn's scope sentence of 2026-08-25 is kept verbatim, in both places it
appears.

Closes #574

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

* docs, Makefile: the pages state what the tree carries

docs/VERSIONING.md's owed list, in the form its #522 and #525 rows already
use. #435 and #434: the C++ reference and internal/tablewire carry the
id-table form, the enum kind and the escape kind, and the eight ports are
what is owed (#511 to #518). #523's message form: the C++ reference carries
it and internal/tablenames claims §11's names, so what is owed is the eight
ports and the form's LoadRetain. #523's unbounded array: the C++ reference
carries the construct and testdata/wire/tables/list_migrates.bin is pinned.
The #523 doc-comment row leaves the list: the language carries the `///`
comment and a tag at every line kind, and the doc and tags descriptor columns
are done in all nine ports on ROADMAP.md.

docs/SPEC-TABLES.md §6.1's block-identity backend status: schema#301 is
closed and no open issue carries the row, so the page says so and states why
the identity is taken over the block's own text and never over a compiler
version.

docs/SPEC-TABLES.md §6.6: TableRetain and the three verbs are IN §11's
claimed set. internal/tablenames/cpp.go:13 registers TableRetain,
internal/check/check.go:3646 carries LoadRetain, MeasureRetain and SaveRetain
in tableGeneratedVerbs, and :3692 carries Dart's three member spellings. What
still lands with the Dart port is TableRetain in that backend's library-scope
registry.

docs/SPEC-TABLES.md §6.1's backend-status paragraph gains the form the eight
ports actually write, which is the one that preceded §3.

docs/SPEC-TABLES.md §15's map and list rows state the tool's honest cook
status: compiler/cook.go refuses a map or a []T at Cook and Uncook by name,
and cook-check refuses a map slot by name while carrying §7.4's element-array
clause.

Makefile: the zero-cost gate's parenthetical no longer cites ROUND-LOG.md,
which no longer exists in the tree.

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

* testdata: six stale map goldens re-pinned from the reference (#461)

`make conformance-pin update-goldens` on a clean checkout of main rewrites
six files and nothing else: testdata/golden/tables/maps/{Cells,Chunks,Docs,
Runs,Slots,Spans}Table.h. The pins carry neither the retain walk's arm and
enum-reference case (kind 15 and kind 30 resolving as framed content) nor the
map entry loader's present clamp, both of which the C++ table emitter writes
today.

This is the shape #461 describes. The pins were written on the branch that
became #662, which was cut before #658's retain-unknown walk landed and
merged after it; the two diffs do not touch the same lines, so the merge is
clean and nothing in CI reads these files.

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

* ci: a freshness gate for the pins under testdata/ (#461)

ci.yml checks freshness for generated/ and for nothing under testdata/, so a
conformance pin or a table golden regenerated against an older main
auto-merges clean and every job stays green against a pin nobody wrote.

The new `pins` job is the `generated` job's shape one door over: check out,
clone ../serialize at the shared SERIALIZE_TAG, run the tree's own re-pinning
targets (`conformance-pin` and `update-goldens`) in place, and fail on a dirty
testdata/, tracked or untracked. Naming the targets rather than the files
means a rule that starts writing a new pin is covered the moment it joins one
of them.

Its first run is the negative control the issue asks for, taken from live
material rather than a planted byte: on main the job goes red and names the
six map goldens the previous commit re-pins. With those committed it is green.

Closes #461

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

* tables: a baseline for the six corpora that hold none (#484)

`for d in tables/*/; do ./bin/schema check $d; done` names six units with no
tables.baseline: blobdemo, blockdemo, blockhome, scalardemo, streamdemo and
vocab9demo. Each is one unit in one directory, which is what the notice's
advice needs, so each gets the file the tool writes:

  ./bin/schema tables-baseline --update --reason "first baseline" tables/<d>

The files are generated and never hand-written. Every one carries the history
entry the reason opens, so the corpus itself carries the coverage clock
(docs/SPEC-TABLES.md §18.4). The loop now prints the notice zero times.

What still prints it is the multi-unit case #484 carves out: test/tables holds
seven units of different packages in one directory, where "commit one in this
directory" is advice a caller cannot take, and bench/corpus holds one more.
Neither is a baseline this commit can write.

Closes #484

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

* ci: record the pins job's measured wall (#461)

1m44s on run 34113070433, against 4m27s for big-endian in the same run: the
job is inside this file's two-minute rule and is not the binding constraint.
The comment names where its budget goes, so the next reader who needs the
time back knows the first place to look.

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

---------

Co-authored-by: Claude Opus 5 <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.

Map values of array, counted-array, enum-extent, unbounded and blob kinds emit C++ that does not compile in the reference (the class beside #619)

1 participant