Skip to content

tables: the block opens a const region and reads const rows (#455) - #673

Merged
rowan-claude merged 3 commits into
mainfrom
contract-cook-bool-const-block
Sep 7, 2026
Merged

tables: the block opens a const region and reads const rows (#455)#673
rowan-claude merged 3 commits into
mainfrom
contract-cook-bool-const-block

Conversation

@rowan-claude

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

Copy link
Copy Markdown
Collaborator

The C++ column's CONTRACTION pass, two gaps in the table runtime. One of the
two landed. The other is refused by the page, and this body says why rather
than leaving a reader to find out.

Closes #455

#455: the block opens a const region and reads const rows

RenderFrameShips( block ) handed back mutable rows whether the block came
from the producer's Begin or from BlockOpen over bytes another language
handed over, and BlockOpen itself took void * base, so a consumer holding
an mmap'd read-only file had to const_cast to open it at all. C# already
splits that surface (ref readonly iteration, ReadOnlySpan<Row>) and Rust
returns &[Row]; C++ alone gave a reader write access.

Red first. test/tables/block_const_main.cpp and its two Makefile targets
landed in 7e7b4c6, one commit before the fix, and that commit is red:

error: no type named 'Const' in 'blockdemo::RenderFrameBlock'
error: no template named 'TableBlockConstRows'
error: no template named 'TableBlockConstSpan'

The fix, in internal/codegen/cpptable/block.go:

  • TableBlockConstRows<T> and TableBlockConstSpan<T> in the shared block
    runtime: the same two views the producer has, with const on every pointer
    they hand out. They are runtime names, so no per-table spelling is claimed
    (SPEC-TABLES §11).
  • <Name>Block::Const, a MEMBER TYPE of the handle that already exists, so it
    claims no file-scope name of its own. Same three facts, const on both
    pointers.
  • bool <Name>BlockOpen( <Name>Block::Const &, const void *, int64_t, TableRefuseReason * = NULL ), and the row accessors overloaded on the const
    handle, so a call site written against §19.2 reads the same either way.

The check is now ONE BODY per table, in an anonymous namespace in the
.cpp, answering the used extent beside the verdict. Both overloads run the
same clauses in the same order and name the same reason on the same refusal,
so the producer's path and the consumer's cannot drift. Begin, the fill
accessors and the typed base a worker indexes are untouched, and the fill
refuser still passes over the same markers.

The controls. The gate runs plain and sanitized; its negative control is a
COMPILE control, because a green run can never tell a read-only view from a
view in name only. The same program, one assignment through the const view
added by -DBLOCK_CONST_WRITE, must fail to compile:

error: cannot assign to return value because function 'operator[]'
returns a const value
  174 |     RenderFrameShipsSpan( block )[0].object_id = 1;
note: function 'operator[]' which returns const-qualified type
'const blockdemo::RenderShip &' declared here

The page. SPEC-TABLES §19.2 states the overload, <Name>Block::Const and
the two const views, and §19.5 states the gate and its control.

Goldens moved, and only by the added surface. No wire pin moved:
testdata/wire/ is byte identical, because a C++ overload changes no layout
fact the BUILD VERSION is taken over (§20.1).

file change
generated/bench/tables/cpp/BenchTableBlock.h +88
generated/bench/tables/cpp/BenchTableBlock.cpp +94 / -8
testdata/golden/wide/cpp/CaptionBlock.h +65
testdata/golden/wide/cpp/CaptionBlock.cpp +47 / -4

Each is the two view templates in the shared runtime, one Const member type
and one BlockOpen declaration per block-formed table, and in the .cpp the
open body moved into an anonymous namespace with the two overloads over it.

#271: NOT DONE, and the page is why

The issue asks that a cooked bool byte of 129 be refused by Open, with the
check placed in <X>OpenWalk. Every part of that premise has moved since the
issue was filed on 2026-09-02, and implementing it would knowingly turn three
standing gates red. It is left open, unclaimed by this PR, for a ruling.

  1. There is no OpenWalk. §11 records it as the one name ever removed
    from the claimed set: it named wire v1's validating walk, §7's Open is a
    header match with no walk in it, and a test now asserts <X>OpenWalk is
    LEGAL for a user schema. Re-claiming it takes that name back.

  2. Open reads no byte of the data part, by design. §7: "There is NO
    PER-NODE VALIDATION AT LOAD, ever, and that is not a cost saved but the
    design: a per-node pass over a catalog-scale file is the parse the whole
    form exists to delete." §7.4: "Nothing else is read. Not a scalar."

  3. An owner ruling one day AFTER the issue settles it. §13.4, dated
    2026-09-03: "OK to be clear, cooked data is generally trusted and will be
    loaded from disk"
    , and, on the question the issue actually raises, "If we
    have concerns, then we should sign it."
    / "We just have to load the
    data."
    Integrity is one verification over the whole file before Open,
    never per-node checks in the loader.

  4. Three gates go red. tables-cook-open-walk-negative-control exists to
    prove that a walk in Open is a defect and prints "a walk in Open turns the
    open-cost gate RED"; make/cs.mk carries its twin. The cook forgery
    fuzzer's oracle is "a mutation inside the DATA part must not change Open's
    answer AT ALL, which is the O(1) promise stated as a property a fuzzer can
    falsify", and a bool byte is in the data part. The open-cost gate itself
    measured 14.4 ns at 1.5 MB and 14.4 ns at 157 MB on this branch, ratio
    1.000.

  5. A refusal here is not the C++ lane's to make alone. The forgery corpus
    is a CROSS-LANGUAGE registry: testdata/conformance/tables/MANIFEST.txt
    says a forgery row is "one damaged fixture and the verdict every
    implementation owes it", and §7.5's cross-implementation lock has the C++,
    C# and every other Open pointing at the same bytes. A cook_bool_129 row
    with the verdict refuse obligates all nine backends; adding it to the C++
    reference alone would break the lock rather than close the gap.

The gap the issue names is real: a bool holding 129 is a trap
representation, and a caller reading a cook whose provenance it doubts cannot
check the region itself. The in-design home for that check is the TOOL, not
the loader: schema cook-check's pass two already walks every node and §7.4
enumerates what it reads, so the clause would go there and §7.4 would gain a
line. That is a design decision and a nine-backend conformance question, so it
is left to Glenn rather than taken here.

What ran

Rebased onto 59cd186, so #674's list-walk fix and #660's toolchain gate are
both under it.

  • make test SCHEMA_SKIP_LEGS=cs,dart,elixir,java,js exits 0, the whole
    chain. Five of the nine pinned toolchains are absent on this machine, and
    make test refuses a missing pinned toolchain by name, and names a skip that is asked for (#599) #660's gate refuses by name rather than skipping quietly, so the skips are
    named on purpose. tables-block runs inside that chain and carries the new
    gate: two OK lines (plain and sanitized) and one negative control that turns
    the compile red.
  • make tables-cook-open, and all three of
    tables-cook-open-lengths-negative-control,
    tables-cook-open-root-negative-control and
    tables-cook-open-walk-negative-control. The open-cost gate reported
    cook open is O(1) in the file's size: Scene at 1572832 bytes opens in 14.4 ns, at 157286368 bytes in 14.4 ns, ratio 1.000.
  • go test ./... clean.
  • Goldens re-pinned through go test ./internal/goldens -update and the bench
    table regeneration. The full make update-goldens target's per-language
    legs (GOLDENS_LEGS) need dotnet, which is absent here; nothing they pin is
    touched by a C++ block header, and CI's generated and cpp-lock jobs pass.

@rowan-claude rowan-claude reopened this Sep 7, 2026
rowan-claude and others added 2 commits September 7, 2026 06:46
test/tables/block_const_main.cpp loads the pinned block image, holds it as
a `const uint8_t *` from that moment and opens it with no cast, then walks
its rows through a read-only view and compares them against the same bytes
opened writable. Makefile gains `tables-block-const`, its sanitized twin and
`tables-block-const-negative-control`, and `tables-block` runs all three.

It is RED on this commit, which is the point of landing it first:

    error: no type named 'Const' in 'blockdemo::RenderFrameBlock'
    error: no template named 'TableBlockConstRows'
    error: no template named 'TableBlockConstSpan'

`BlockOpen` takes `void * base` and the row views hand back mutable rows, so
a consumer of foreign bytes gets write access and a const region cannot be
opened at all (docs/SPEC-TABLES.md §19.2).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
internal/codegen/cpptable/block.go gains three things, and the producer's
path gains nothing:

  * TableBlockConstRows<T> and TableBlockConstSpan<T> in the shared runtime,
    the same two views with const on every pointer they hand out. Both are
    runtime names, so no per-table spelling is claimed (SPEC-TABLES §11).
  * <Name>Block::Const, a member type of the handle that already exists, so
    it claims no name of its own. It carries the same three facts with const
    on both pointers.
  * bool <Name>BlockOpen( <Name>Block::Const &, const void *, int64_t,
    TableRefuseReason * = NULL ), and the row accessors overloaded on the
    const handle.

The CHECK is now one body per table, in an anonymous namespace in the .cpp,
answering the used extent beside the verdict: both overloads run the same
clauses in the same order and name the same reason on the same refusal, so
the producer's path and the consumer's cannot drift. Begin, the fill
accessors and the typed base a worker indexes are untouched.

docs/SPEC-TABLES.md §19.2 states the overload, the const handle and the two
const views, and §19.5 states the gate and its control.

Goldens moved, and only by the added surface:

  generated/bench/tables/cpp/BenchTableBlock.h   +88
  generated/bench/tables/cpp/BenchTableBlock.cpp +94 -8
  testdata/golden/wide/cpp/CaptionBlock.h        +65
  testdata/golden/wide/cpp/CaptionBlock.cpp      +47 -4

No wire pin moved: testdata/wire/ is byte-identical, because a C++ overload
changes no layout fact the BUILD VERSION is taken over (SPEC-TABLES §20.1).

The gate is green and its control is red on the write:

  OK: a const block region opens with no cast, and its rows read back
  read-only (docs/SPEC-TABLES.md §19.2)

  error: cannot assign to return value because function 'operator[]'
  returns a const value
      RenderFrameShipsSpan( block )[0].object_id = 1;
  note: function 'operator[]' which returns const-qualified type
  'const blockdemo::RenderShip &' declared here

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@rowan-claude
rowan-claude force-pushed the contract-cook-bool-const-block branch from 7101254 to 74fd04c Compare September 7, 2026 10:47
…ase and iterator too (#455)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@rowan-claude
rowan-claude merged commit 2196787 into main Sep 7, 2026
20 checks passed
@rowan-claude
rowan-claude deleted the contract-cook-bool-const-block branch September 7, 2026 11:09
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.

C++ block accessors are mutable-only, so a consumer of foreign bytes gets write access and a read-only mapping needs a const_cast (#447 F-18)

1 participant