Skip to content

tables: the C++ codec for unbounded arrays (#531) - #562

Merged
gafferongames merged 7 commits into
mainfrom
impl-list-codec
Sep 5, 2026
Merged

gafferongames merged 7 commits into
mainfrom
impl-list-codec

Conversation

@gafferongames

@gafferongames gafferongames commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

The second half of #531, after #548 landed the front end, the IR, the tool's wire and text halves and every backend's refusal by name. The C++ reference now carries the codec, held by the tables/lists corpus, and the page's §2.9 status line is gone.

What lands

  • The TableList runtime: the sixteen-byte slot (a self-relative reference and an int32 count), segments carved from the builder's arena in bulk, the dead bit and the live count, Add answering NULL at the arena or at the int32 cap, Erase by the element's own pointer, Each in index order over live elements, and a const form whose size(), operator[] and iteration are members of the storage type. operator[] is bounds-checked in every build, NDEBUG included, on §2.4's rule.
  • Measure, write and read for the five element classes: scalar, enum or flags, union, table by value, and *T. The four writing walks run in index order and skip dead elements. measure == save holds on every instance.
  • The node extent and pack: a holder's storage is its record plus the extent its lists and maps command, in declaration order, pre-order. An unreached non-empty list slot is refused by Cook and by Lock.
  • The cook write side and schema cook-check's element-array clause (§7.4): containment, alignment, fit and no overlap against the holder's own extent, then the elements' own slots, companions and tags. The gate writes cooks from the runtime, checks them with the tool, and refuses a forgery whose list slot points past its holder.
  • The JSON walk both ways: a list as a JSON array in index order, read back element by element, one list half emitted only in a unit that declares one, byte-identical across the corpus, absent from a list-free unit.
  • The §8.1 descriptor migration, with the map's emission moved to the same columns in this PR as the page says: kind is the element kind, is_array set, array_bound = 0 the tell for an out-of-line array, counted with count_offset naming the count, element_size the pitch, and one function column, place, serving both constructs.
  • LoadMeasure's -1 with a reason: TableRefuseReason as a native C++ enum carried as a trailing out-parameter (TableRefuseReason * reason_out = NULL), never an exception, with the two values this PR needs, count_over_length and count_over_extent_cap, emitted only by a unit that declares a map or a list. The other three values are owed as schema#523 and the page says so.
  • The two overflow outcomes per path: the region load refuses with a reason and no report, the builder load counts malformed.
  • The corpus tables/lists: list_empty, list_scalars, list_tables, list_mixed, list_shared, list_before_pointer, list_erased, list_of_maps, list_nested and the migration golden list_migrates (one content, two declarations, one pinned wire, both read into equal values), each crossing the wire, the text and the cook in test/tables/lists_main.cpp, wire goldens pinned under testdata/wire/tables/, header goldens under testdata/golden/tables/lists/, the two cooks pinned beside the wires.
  • make tables-list-measure-refusals: four synthetic wires (a count above the int32 cap, a count the field's L cannot carry, both at depth inside an element's own list) each answering -1 with its own reason, a clean wire beside them that measures, and no report counter moved.
  • The clamp control at 100,000 elements, past 2^16, decoded count checked.
  • Rulings on tool: unbounded arrays in the front end, the IR and the tool's wire and text (#531) #548 applied: bar attributes on a []T qualify the element as on a [..N]T (§2.9 and §11 restated), a list renders kind=14 in the cook projection where a bounded array renders its element's kind (§20.2), []?T unreachable stands.

What this PR changed on the way

  • schema cook-check refuses a MAP SLOT by name where its scan meets one, instead of refusing every cook from a unit that declares a map anywhere. The unit-level refusal made the whole tables/lists corpus uncheckable because one holder's element carries a map. The scan's refusal names Squad.roster, the reference that reads it, and schema#380 as the PR that lands the map's clause (§7.4 item 4), which stays owed. A cook of a map-free root in the same unit checks as any other does. internal/tablecook holds the test, and the gate asserts the map-holding cook is refused on exactly that clause.
  • Two Makefile defects in the work in progress: a blank line inside the aggregate controls target's continuation had ended its dependency list, and the element-kind control's sed script carried commas and an unbalanced parenthesis that $(call) split and cut. Both fixed, the aggregate target now runs all twelve.

Gates

lists: all checks passed (docs/SPEC-TABLES.md §2.9)            [build/schema_test_lists]
lists: all checks passed (docs/SPEC-TABLES.md §2.9)            [build/schema_test_lists_asan]
list gate: cook-check reads two cooks the runtime wrote, refuses the forged list slot, and refuses the map-holding cook by name
list measure refusals: four -1s with their reasons, one clean measure, no counter moved (docs/SPEC-TABLES.md §2.9, §6.5)
tables list-walk gate: one list half, byte-identical in 5 list-bearing .cpp files, and none in a list-free one

Every negative control the page names, red on a CHECK:

negative control: allocation turns the LIST GATE red
negative control: order turns the LIST GATE red: 76 failures
negative control: dead turns the LIST GATE red: 4 failures
negative control: preorder turns the LIST GATE red: 4 failures
negative control: walkorder turns the LIST GATE red: 1 failures
negative control: shared turns the LIST GATE red: 9 failures
negative control: clamp turns the LIST GATE red: 4 failures
negative control: elemkind turns the LIST GATE red: 6 failures
negative control: depth turns the LIST GATE red: 28 failures
negative control: fit turns the LIST GATE red: 6 failures
negative control: unreached turns the LIST GATE red: 3 failures
negative control: dropping cook-check's element-array clause turns its test RED

The checker's refusals (type-body, []Self, the claimed names) and the build-version rule are held in compiler and internal/check tests. go test ./... green. The committed generated/ tree regenerated as CI does and current: the one moved file is generated/bench/tables/cpp/BenchTableTable.cpp, the list-free bench unit gaining the JSON walk's list adapter stubs (TableJsonIsList answering false) exactly as it carries the map's.

The zero-cost law (#546)

The table read and write benchmarks over the existing corpus (bench/corpus/BenchTable.schema, corpus_id b51387f36d9b59c4, 2147 wire bytes per record), before at d46eea1e (origin/main) and after at this branch, the same bench/tables/cpp/table_main.cpp against each tree's generated/bench/tables/cpp/BenchTableTable.h. The two leg binaries are byte-identical (cmp clean, 106408 bytes each): the one moved file in the bench unit is BenchTableTable.cpp's JSON walk, which the leg does not compile, and the header is unchanged. The sitting is therefore a noise reading of one machine code, and it reads as one.

MacBook Air M2, 2026-09-05 08:31, five interleaved rounds (§2.4), --round K each, one-minute load 1.95 when the sitting started and 2.04 to 2.09 with the bench itself running (macOS, no core pinning, Spotlight had just finished indexing a worktree).

path before, median of 5 (msg/s) after, median of 5 (msg/s) before's own spread after's own spread
write 759271 759190 754694 to 761728 (0.9%) 741292 to 761748 (2.7%)
round_trip 260774 256508 256606 to 262569 (2.3%) 255580 to 261912 (2.5%)

Unchanged within noise: write 0.01% apart, round_trip 1.6% apart with each side's own spread wider than the gap. No cost measured, none landed.

Owed, named

  • The tool's COOK and UNCOOK halves for a []T, beside the map's (§15).
  • schema cook-check's map-slot clause, §7.4 item 4, schema#380.
  • The three remaining TableRefuseReason values, §7's check order and §19.2's block clauses, schema#523.
  • The ports, one per language, on the page's terms (§15).

#366

The C++ cell for unbounded arrays moves to ✅. Every other column stays ❌, refusing by name.

🤖 Generated with Claude Code

gafferongames and others added 7 commits September 5, 2026 08:17
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The unit-level map refusal at cook-check refused every cook from a unit
that declares a map anywhere, which is the whole tables/lists corpus. The
scan now refuses the slot it cannot bound, naming the field, the reference
that reads it and schema#380 as the PR that lands the clause, and a cook of
a map-free root in the same unit checks as any other does.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…trol's script survives make

A blank line inside the aggregate target's continuation ended its
dependency list, and the element-kind control's sed script carried commas
and an unbalanced parenthesis that make's call split and cut.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…§8.1 columns

Every list-free unit gains the JSON walk's three list adapter stubs, as it
carries the map's, and the map units take the descriptor columns §8.1 now
names, the TableRefuseReason enum and the extent carve.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The update-goldens recipe's for-loop is whole again: the blank line
after its first continuation is gone, and GNU Make 3.81 runs it through.

The preorder negative control sabotages both writers of a list whose
element holds a map, the pack's extent walk and the cook's extent writer,
and runs schema cook-check on the cook the sabotaged gate wrote. Both of
the instruments the page names go red: the pinned list_of_maps_cook byte
compare, and the containment clause, the array leaves the node.

The page states the cook-check stopgap under 7.4 item 4: the tool refuses
a map slot by name where its scan meets one until schema#380 lands the
clause, and the #380 entry in 15 names that clause as the piece owed.

The map's wire extent tests the int32 cap before the body's L, so a map
count above the cap answers count_over_extent_cap exactly as a list's
does, one rule for both constructs. The measure-refusals battery builds a
synthetic Army wire with the map's count as its knob and asserts both
reasons, with a clean map-holding wire beside them. The eight Table.h
goldens the emission moves are re-pinned.

The em dash in TestToolRefusesMapsByName's comment is a period.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@gafferongames
gafferongames merged commit dc6ce20 into main Sep 5, 2026
20 checks passed
@gafferongames
gafferongames deleted the impl-list-codec branch September 5, 2026 16:01
gafferongames added a commit that referenced this pull request Sep 5, 2026
…cap check gets its control (#564)

Main dc6ce20 is red at tables-maps-fit-negative-control on every make test
leg. PR #562 rewrote TableMapWireExtent: the L bound line now assigns the
reason count_over_length, and an int32 cap test sits before it. The fit
control's sed pattern still named the old line, so the sabotage patched
nothing. Repointing it was not enough: the map gate had no row asserting
count_over_length by name, and the hostile count the reader row used sits
past INT32_MAX, so the cap test refused it before the L check was reached and
the gate stayed green under the sabotage.

The map gate gains test_measure_refusals in the list gate's shape: a Fleet
wire whose map count is 100000 against a short body answers -1 with
count_over_length, a count of 0x80000000 answers count_over_extent_cap, and a
clean wire measures and loads. It prints one summary line under
`schema_test_maps measure-refusals`, wired into make test as
tables-maps-measure-refusals beside the list one.

The fit control targets the new line, keeping its shape, and
tables-maps-cap-negative-control sabotages the INT32_MAX line beside it. Two
more map controls had patterns #562 moved out from under them: textorder
named a map_at call the JSON walk no longer makes, and unreached named
TableMapUnreachedEmpty, which extent.go's TableExtentUnreachedEmpty replaced.
Both point at the emitted lines as they stand, and every one of the eleven
map controls turns the gate red on a CHECK.

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
rowan-claude pushed a commit that referenced this pull request Sep 5, 2026
…e batch, and schema#571 (#530)

Merges origin/main (#562, #564, #573) and lands the page's second round
(#575) on both engines: a ranged base encoded by its kind's signedness, the
quantized f32 row as min, max and res in float32 with the step count and the
width derived by SPEC.md §4.3's rule, quantization in float32 with two
roundings on each side, a refused first announcement terminal, and the
Envelope over a union of the three messages as the batch vector (244 bytes,
the singles at 52, 148 and 43, the announcement at 361).

Unbounded arrays ride the C++ message wire with the count the data decides
and the elements carved from the node's extent.

schema#571's six findings are fixed with a vector and a control each: surplus
elements decode into scratch (M1), a ranged 128-bit value is one arithmetic
for measure, write and read (M2), a width above the kind's domain is refused
in both shape parsers and the bit reader (M3), the Go engine writes the count
as its offset from the minimum (M4), one quantization rule (M5), and the C++
reader clamps while wide and narrows after (M6). The bases unit
(test/tables/Bases.schema) holds the vectors in both engines.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
rowan-claude added a commit that referenced this pull request Sep 7, 2026
Twice on 2026-09-05 main's certification went red on a merge whose
pull-request run was 20 of 20 green, and one of the two was a stale sabotage
pattern: #562 rewrote the line the maps fit control aimed at, the sabotage
patched nothing, and the control refused where only certification could see
it. The controls were doing their job; nobody was reading them until after
the merge.

The leg is two jobs. negative-controls-matrix runs the enumeration gate and
prints the plan as a matrix. negative-controls fans out over it, builds
bin/schema and the generated tree once, and runs its group's controls in one
`make -k` invocation so a control that refuses does not hide the ones behind
it. Red comes from two places: make's exit status, and a scan of the log for
the controls' own refusal lines, which puts the sentence a reader needs into
the run's annotations instead of leaving it a thousand compiler lines deep.
The scan was held against the 159 real control logs and matches no passing
one.

The per-port groups install their SDK exactly as the conformance jobs above
do, keyed on the row's field rather than on a language name, so a group is a
row in make/negative-controls.json and no edit here.

Also a prerequisite this leg found by running a control on its own for the
first time: conformance-negative-control-absent's second half runs the
harness over a substituted registry naming the Go driver, which execs
build/conformance-go, and the target did not name it. Inside `make test` the
Go leg is already built, which is why the omission stayed invisible.

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

* ci: enumerate every negative control from the Makefile (#563)

A negative control proves a gate is watching by breaking what the gate
watches and requiring the gate to go red. Each one in this tree already
refuses when its sabotage patches nothing, so a control whose pattern has
drifted off the line it aims at says so. Until now the only reader of that
refusal was `make test`, which runs after a merge.

This is the half that makes "every control" mean what it says.
tools/negativecontrols reads the Makefile and every file the Makefile
includes, collects each explicit target whose name carries
`negative-control`, and holds that set against make/negative-controls.json,
the plan the pull-request leg's matrix comes from. The test fails on a
difference in either direction: a control the makefiles define and the plan
does not carry, and a control the plan names and no makefile defines. An
exclusion needs a non-empty reason, so nothing leaves the leg silently.

The reader follows the tree's own include lines rather than a glob typed
here, and it reads the rule heads a makefile actually writes: several
targets on one head, a head continued over a backslash, `.PHONY` lines, and
`define` blocks and `:=` assignments that only look like rules. Both
readings of this tree agree at 159 controls.

The plan groups those 159 by what a runner has to install. Eight groups are
toolchain families; two more, wire-fuzz and message-form, are base-toolchain
families split out on measured cost.

Closes #563

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

* ci: every negative control runs on pull requests (#563)

Twice on 2026-09-05 main's certification went red on a merge whose
pull-request run was 20 of 20 green, and one of the two was a stale sabotage
pattern: #562 rewrote the line the maps fit control aimed at, the sabotage
patched nothing, and the control refused where only certification could see
it. The controls were doing their job; nobody was reading them until after
the merge.

The leg is two jobs. negative-controls-matrix runs the enumeration gate and
prints the plan as a matrix. negative-controls fans out over it, builds
bin/schema and the generated tree once, and runs its group's controls in one
`make -k` invocation so a control that refuses does not hide the ones behind
it. Red comes from two places: make's exit status, and a scan of the log for
the controls' own refusal lines, which puts the sentence a reader needs into
the run's annotations instead of leaving it a thousand compiler lines deep.
The scan was held against the 159 real control logs and matches no passing
one.

The per-port groups install their SDK exactly as the conformance jobs above
do, keyed on the row's field rather than on a language name, so a group is a
row in make/negative-controls.json and no edit here.

Also a prerequisite this leg found by running a control on its own for the
first time: conformance-negative-control-absent's second half runs the
harness over a substituted registry naming the Go driver, which execs
build/conformance-go, and the target did not name it. Inside `make test` the
Go leg is already built, which is why the omission stayed invisible.

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

* ci: the first stale control the leg found, and lint on the reader (#563)

tables-block-layout-model-negative-control refused on the leg's first run
with "NEGATIVE CONTROL FAILED: C# went red, but not on the layout check",
and it was right: the C# build had failed on `CS0246: The type or namespace
name 'Blockhome' could not be found`, not on the layout check the control
exists to watch. The C# half overrides BlockGeneratedDir alone, so the
project's BlockHomeGeneratedDir keeps its default and the blockhome sources
have to be on disk, and the target named only bin/schema. The padding and
pitch controls beside it already name build/tables-generated-cs/.stamp; this
one now does too. Inside `make test` an earlier leg had generated those
sources already, which is why nothing said so until every control ran on its
own.

The reader's four golangci-lint findings, all mechanical: SplitSeq and
FieldsSeq for the range loops, CutSuffix for the line continuation.

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

* ci: every negative-control job fits the owner's two-minute rule (#563)

The rule (Glenn, 2026-09-07): "if we have any CI that runs per-commit it
needs to finish 1 minute, 2 minutes max. Anything longer than that needs to
be nightly or manually triggered as needed." It is a rule about a JOB, and a
matrix row is a job, so every row of this leg is cut to fit it rather than
the leg as a whole being cut to fit it.

MEASURED on this branch's own pull-request run (34102397965), before this
change: base 8:57, wire-fuzz 7:13, cs 2:46, message-form 2:36, conformance
2:31, block-fuzz 1:59. Five of the eleven rows were over the rule and a
sixth was one second under it.

WHAT MOVED. The plan now carries 34 pull-request groups instead of 11, cut
where the measurement cuts them and not where the family names do:

  - base splits ten ways. The map gate is 17 controls at 4 s each on the
    author's machine, so it runs as base-maps and base-maps-keys; the
    conformance driver's two sabotage rebuilds are 45 s and 47 s of runner
    time each, so they are base-conformance and base-conformance-absence;
    the rest are the packet families, the list gate, the arm gate, the
    cook, the keyed controls and what is left.
  - wire-fuzz splits eleven ways, one control per job for ten of them.
    Each rebuilds the compiler under a source overlay and then fuzzes the
    sabotaged wire, 55 s to 75 s on the runner, so no two of them fit
    together; the four pinned-vector oracles are cheap and share the
    eleventh.
  - cs splits four ways. The C# layout check is the instrument for three
    block controls at 39 s, 31 s and 29 s.
  - block-fuzz splits in two, at 48 s and 33 s over two SDK installs.
  - conformance is gone as a group. Its one control had the bare
    `conformance` target as its prerequisite, which builds and runs all
    nine legs and needed all seven SDKs on one runner. The prerequisite is
    now `conformance-rust`, the rust leg and the derived manifest the
    control actually reads, and the control rides one SDK in rust-alloc.
    The soak and the audit keep `conformance`, deliberately: a NUMBER
    measured over a corpus whose matrix is red is a number about a defect,
    and this control is not a number.

ONE CONTROL DOES NOT FIT AT ANY GROUP SIZE, so it does not run on the pull
request at all: tables-message-form-negative-control drives 49 sabotage
rows one submake each and takes 124 s where the rule is 120. It is the
`nightly` group, and certify.yml runs that tier on the schedule it already
carries. Its four blades stay on the pull request in `message-form`.

So a group now names its tier. `when` is `pull-request` or `nightly`,
`matrix` renders one tier, and the enumerator REFUSES a group that names
neither: a control cannot leave the pull request without landing on the
nightly, and the package's test reads both workflows to prove each runs the
plan rather than a typed list.

The 35 controls main gained since this branch last merged it are in the
plan too, toolchain-negative-control from #660 among them, and the three
family umbrellas that would re-run a split family's other half join the
exclusion list with the reason. 194 controls, all of them in a group or in
an explained exclusion.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* ci: the two controls main gained while this branch was measuring (#563)

tables-block-const-negative-control and tables-json-list-walk-negative-controls
landed on main mid-run, and the enumerator did what it is for: the leg went red
naming both rather than running 194 of 196. Both are cheap, 1.6 s and 3.7 s on
the author's machine, and both belong beside the block and JSON refusers
already in `base`, which measures 24 s as a group with them in it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* ci: cut again where the first cut was inside the rule by luck (#563)

MEASURED on run 34115675206, the first run of the split leg: 52 of the 54
jobs were inside the owner's two-minute rule, and the two that were not are
tables-wire-fuzz-retain-negative-control at 140 s of control time and
`big-endian`, which is not this leg's job and is named in this file's header.

The retain control is over the rule ON ITS OWN, so grouping cannot save it:
it joins tables-message-form-negative-control in the `nightly` group, which
certify.yml runs. Its two siblings, the retain-class control and the retain
oracle, are inside the rule and stay on the pull request, so what moves is
that one pass.

Eight groups came in between 100 s and 111 s, which is a rule met by luck
rather than by design: `big-endian` was 1:49 when this file's header
measured it and is 4:19 today, on a job nobody changed. So base-maps,
base-maps-keys, base-arms, base-keyed and the oracle half of wire-fuzz are
each two groups now, cut where the measurement puts the weight rather than
where the family name does. 38 pull-request groups, 196 controls.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* ci: three controls the naming convention hid, and an umbrella off the nightly (#563)

The marker `tools/negativecontrols` reads is `negative-control`, and three
controls the Makefile introduces as "Its NEGATIVE CONTROL" spell themselves
`-negative`: tables-big-endian-negative, tables-hostile-negative and
tables-pack-negative. The enumerator cannot see a name it does not match, so
"every negative control runs on every pull request" passed over three of them,
and two ran only inside `make test`. They are renamed into the convention and
placed.

tables-hostile-negative-control and tables-pack-negative-control go in a new
base-pack group: one pack corpus, one JSON engine and a `go build -overlay`
each, 19 s over the two in one invocation on a cold Go build cache with
bin/schema and the generated tree already built.

tables-big-endian-negative-control goes in the exclusion list. It is the body
of ci.yml's big-endian job, which runs it on every pull request already, and
issue #684 owns moving that job; a second s390x cross-compile on this leg
proves nothing the first does not.

tables-wire-fuzz-retain-negative-control leaves the nightly group for the same
list. Makefile:4428 gives it no recipe: it is an umbrella over the retain
oracle and retain class leaves, which wire-fuzz-message-oracle and
wire-fuzz-retain-class already run. The nightly tier is one control now, and
stays a group, because it is where the next control that outgrows the rule
lands.

Also: the negative-control leg's timeout drops from 25 minutes to 5, which is
the rule enforcing itself on a leg whose rows are cut to 120 s; ci.yml no
longer says certify.yml runs on every push to main (#669); and the past-tense
narration on this branch's own lines is present tense.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* ci: the negative-control tests hold three joins a substring scan cannot (#563)

The leg's target list is only the plan while the leg EXPANDS the plan.
TestTheLegRunsTheManifestAndNotATypedList greps three substrings, and a leg
that typed its own include list and left the old expression in a comment
satisfies every one of them. So the workflows are parsed. tools/negativecontrols
carries a reader for the block-YAML subset these files are written in, and the
new test asserts what each leg actually expands: `strategy.matrix` IS
`${{ fromJSON(needs.<matrix-job>.outputs.matrix) }}`, the leg `needs:` that
job, and that job's matrix output comes from the step that runs this tool.
Both tiers, ci.yml and certify.yml.

Proven red first. With ci.yml's matrix expression replaced by a hand-typed
three-row include list, TestTheLegRunsTheManifestAndNotATypedList still
passes, and the new test says:

  .github/workflows/ci.yml: the negative-controls job expands
  map[string]interface {}{"include":[]interface {}{...}} as its matrix, want
  "${{ fromJSON(needs.negative-controls-matrix.outputs.matrix) }}": a matrix
  written any other way is a target list this package did not enumerate

A marked rule head spelled through a variable or as a pattern rule is refused
by name rather than dropped. A dropped head is a control in no group, in no
exclusion and in no job, with every test in the package green, which is the
one outcome the package exists to prevent.

And the plan's toolchain versions are held against test/conformance/*/ci.json.
node 20, dart 3.13.2, java 21, otp 29.0.5 and elixir 1.20.4 are written in
both files and nothing joined them; a bump in one alone runs a negative
control against a runtime its own conformance leg no longer uses. dotnet is
exempt by construction: the SDK version lives in .github/dotnet-version and
the test refuses a group that writes one here.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* ci: the four controls main gained while this branch was reading (#563)

The enumeration gate on the merge ref named all four rather than running 199
of 203, which is the gate doing its job. Measured off one warm build on the
author's machine and placed in the group whose corpus each already reads:

  tables-float-nan-negative-control            1.6 s  base
  tables-maps-cook-check-negative-control      1.6 s  base-maps
  tables-lists-tool-cook-negative-control      0.3 s  base-lists
  tables-message-form-retain-negative-control  2.4 s  message-form

No group changes tier and none goes near the rule: base 28 s over 24,
base-maps 20 s over 5, base-lists 35 s over 14, message-form 16 s over 5.

base-pack's own line is restated in the same convention the rest of the file
uses, off one warm build rather than off a cold Go build cache: 20 s over the
two, most of it the sanitized build of the hostile-value driver.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* ci: cut base-pack, the tightest job the last run measured (#563)

On run 34120798793 the base-pack row was 81 s of control time in a 113 s job,
which is the band this branch already cut once: a rule met by luck rather than
by design. The two controls share no compile that matters. tables-pack's floor
is a `go build -overlay` and a byte comparison; tables-hostile's floor is the
SANITIZED build of the hostile-value driver, which nothing else in the leg
builds and which is most of the 81 s.

So they run as two rows, base-pack at 7 s and base-hostile at 14 s off one
warm build. The map gate's count in docs/CONTRIBUTING.md is four groups, not
two, which it has been since the second cut.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* ci: the message form has five blades on the leg, not four (#563)

tables-message-form-retain-negative-control joined the message-form group when
main landed it, so the two places that count the blades say "other blades"
rather than a number that moves whenever the family grows.

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

1 participant