Skip to content

spec(t27f): live_graph_export -- the gx_ graph-data plane (per-node/per-edge accessors, name region, gx_apply_event, gx_replay) - #12148

Merged
gHashTag merged 2 commits into
masterfrom
lane/live-gx-exports
Oct 25, 2026
Merged

gHashTag merged 2 commits into
masterfrom
lane/live-gx-exports

Conversation

@gHashTag

Copy link
Copy Markdown
Collaborator

What

specs/t27f/live_graph_export.t27 -- the GRAPH-DATA plane of the gx_ buffer exports, companion to the #12005 contract (live_struct_glue.t27, unmodified) and the constant plane (live_glue_layout.t27, #12040, unmodified). The page's per-frame reads become small: a count, a kind, a NAME -- and the two ways events enter apply through work_graph.t27's law verbatim.

  • gx_graph_ptr()/gx_graph_len() -- the image region, routed through this card's own IMAGE_OFF/IMAGE_LEN (the layout plane's transcription untouched)
  • gx_node_count/gx_node_kind/gx_node_name_ptr/gx_node_name_len -- per-node; a past-count index answers each type's own one-past-the-end (NK_COUNT, the empty name pair), never a byte of state
  • gx_edge_count/gx_edge_kind/gx_edge_from/gx_edge_to -- per-edge; past-count endpoints answer NODE_CAP, the law's own impossible node
  • gx_apply_event(kind, tick, task) -- the live path: refreshes the live triple into staged row 0, applies through the law, returns the law's code verbatim
  • gx_replay(ptr, len) -- the load path: walks whole spec(t27f): live_struct_glue -- the struct-path export glue v2 contract as law (Refs #10262, #11781) #12005 records, first law refusal stops the walk with the law's own code (the contract's mid-batch semantics)
  • Regions tile the room above the contract's MEM_CAP: NAME at MEM_CAP (32 B x 2048 rows), LOG above it (64 records)
  • Refusal band 36 -- the one live_struct_glue left empty for a future card; E_GX_LEN/E_GX_PTR here, E_STATE/E_LAYOUT stay the contract's by import

Two measured findings (both filed under the #5980 emitter-gap family, stated in the card's QUESTION)

  1. str consts are not runtime-readable in the current gen -- an [8]str const indexed at runtime answers wrong bytes cross-module AND through importers (probed 2026-10-25). The spec paths therefore live as a 196-byte local u8 table (machine-copied from the fixture's own SPEC_NAMES), pinned by a transcription test: offsets, per-path lens, shared head/tail, whole-table byte sum 17882.
  2. The wasm32 emitter still cannot lower stateful exports (memory/data/locals/loops/calls) -- this card is law-before-emitter, exactly like the contract card was; nothing here emits wasm.

Test report (quoted, t27c test-report specs/t27f/live_graph_export.t27)

--- test report: specs/t27f/live_graph_export.t27 ---

  tests       7
  pass        7
  FAIL        0
  invariants  3   proved -- comptime, so compiling IS the check
  rate    100.0%

  runtime asserts executed, per test (#6509; a pass with 0 is vacuous, T730):
      16  fresh module: empty graph, refused actions, regions in bounds
   69694  the contract's two-node round-trip through gx_apply_event
     287  the 24-event fixture log replays to the graph the direct law builds
     198  names: every spec node carries its own fixture path, every pair stays in bounds
      23  refusals: this card's band, the law's codes verbatim, and the decode's all-or-nothing
     397  reads are idempotent: no action between them, every pair answers twice the same
      60  the path table is the fixture's own eight paths
  vacuous passes  0 of 7  (passed with 0 runtime asserts executed)

Mutation-proven: flipping name_len_of's tagged width to TAGGED_LEN + 1 fails 2 of the 7; restored and re-verified 7/7.

Local gates (all exit 0)

  • dupe_scan.py -- no new duplicate body, no known group grew
  • check_assertionless_spec_tests.py -- baseline held
  • tri types ratchet -- ledger 140, observed 140, CLEAN
  • check_duplicate_agreement.py, check_seal_coverage.py, check_seal_currency.py, check_ring_spec_drift.py
  • check_verilog_widths.py --require -- OK: 16 known, 0 new (the spec's 1,097,855-bit struct ledgered in the same lane, with exact arithmetic; the spec(t27f): live_struct_glue -- the struct-path export glue v2 contract as law (Refs #10262, #11781) #12005 lesson applied one PR earlier)

0 foreign lines: one new .t27 spec + one data ledger line in tools/verilog_width_baseline.txt.

Refs #12005, advances #10262. The trinity-side page PR (reading these exports) follows once this lands.

…er exports: per-node and per-edge accessors (gx_node_count/gx_node_kind/gx_node_name_ptr/gx_node_name_len, gx_edge_count/gx_edge_kind/gx_edge_from/gx_edge_to), the node NAME region tiled above the contract's MEM_CAP (one 32-byte row per node slot; a SPEC node inside the feed's join carries the fixture's own path from a 196-byte local table -- str consts are not runtime-readable in the current gen, measured, the #5980 family -- every other node the 11-byte tagged form), the LOG region of 64 whole #12005 records, gx_apply_event(kind, tick, task) refreshing the live triple into staged row 0 and applying through work_graph's law verbatim, gx_replay(ptr, len) walking whole records with the law's own partial semantics, and refusal band 36 (the one live_struct_glue left empty) with E_GX_LEN/E_GX_PTR while E_STATE/E_LAYOUT stay the contract's by import; 7/7 tests green (70,675 runtime asserts, 0 vacuous, mutation-proven), 3 invariants proved; the two-node round-trip pins the full 69,640-byte image equality with the direct law, and the 24-event fixture replays to the same digest. Refs #12005, advances #10262.
…ruct is the graph-data plane of the wasm memory image (the law's own WorkGraph 557,120 bits + the 2,048-byte log region with its commit + the 65,536-byte name region with its live extent), 1,097,855 bits measured by the checker; ledgered in the same lane as the spec, the #12005 lesson applied one PR earlier. Refs #12005, advances #10262.
@github-actions

Copy link
Copy Markdown
Contributor

PR Dashboard

Generated at: 2026-10-25 01:11:18 UTC

Summary

Status Count
Total Open PRs 43
PRs with Failing Checks 41
PRs with All Checks Green 2
READY 1
FAILING 41
PENDING 0
NO CHECKS YET 0

These columns do not partition: 1 + 41 + 0 + 0 = 42, and there are 43 open PRs. A PR is being counted twice or not at all.

Seal Status

  • ⚠️ STALE -- sha256(compiler.rs)=55c0f1b1b32d != manifest seal=87e5cbd3ad94.
    The committed NMSE numbers were certified against an older compiler.rs.
    Run scripts/reseal-check.sh locally for the two-step reseal command (advisory; not a merge gate).

@tri-claw

tri-claw Bot commented Oct 25, 2026

Copy link
Copy Markdown

Summary by t27-bees

Checked by the t27 compiler

t27c parse, parse-complete and typecheck on each changed .t27 file at 714b712. Nothing was generated, built or run. A pass means the file is well formed, not that it is right.

  • specs/t27f/live_graph_export.t27: parses and typechecks.

Read by the model

This spec adds the GRAPH-DATA plane (live_graph_export.t27) for the gx_ buffer exports: per-node/per-edge accessors, a NAME region, and two event-entry paths (gx_apply_event for live, gx_replay for load). It reads the single WorkGraph through the law's own pub fns, tiles two regions above the contract's MEM_CAP, and claims the refusal band (36..63) the contract reserved. Tests pass (7/7), invariants hold (3), and a transcription test pins the local SPEC_PATH_BYTES copy (needed because str consts aren't runtime-readable in the current gen).

Worth a look

  • specs/t27f/live_graph_export.t27:150-158 (log_get_u32) — The little-endian reconstruction relies on << binding tighter than + with no parentheses: v = v + (gx.log[(off + 2) as usize] as u32) << 16;. It is correct today, but a minimal edit (e.g., adding a cast or splitting the expression) could silently change the order of operations and corrupt every u32 read from the log.

  • specs/t27f/live_graph_export.t27:230-231 (name_row_write) — KIND_TAG[(kind * 2) as usize] and KIND_TAG[(kind * 2 + 1) as usize] assume kind < 5 for every live node. If node_kind_at_pub ever returns NK_COUNT (5) for a live node (a law bug), this reads past the 10-byte KIND_TAG table.

  • specs/t27f/live_graph_export.t27:320-324 (gx_replay) — The function requires ptr == LOG_OFF and len == gx.log_len exactly. The host cannot replay a prefix of the committed log or use a different base pointer; any mismatch returns E_GX_PTR/E_GX_LEN before applying anything. This is intentional per the spec, but it means the loader must know the exact region address and committed length.

  • specs/t27f/live_graph_export.t27:250-256 (remat_names) — gx.names_len accumulates the sum of variable-length name bytes across rows, while gx_node_name_ptr uses a fixed 32-byte stride (NAME_OFF + i * NAME_ROW). The two views are consistent (the page uses the stride for addressing, names_len for total live content), but the semantic difference is subtle and undocumented in the getter comments.

  • specs/t27f/live_graph_export.t27:196 (SPEC_PATH_BYTES) — The 196-byte table is a hand-copied duplicate of work_graph_fixture::SPEC_NAMES because str consts aren't runtime-readable. The transcription test (offsets, per-path lengths, shared "specs/"/".t27", byte-sum 17882) will catch drift, but any fixture rename that updates SPEC_NAMES without updating this table will fail the test — a manual sync step until the str floor lands (epic: self-host t27c -- rewrite the compiler from Rust into t27 #5980).


Read: 2 files, +819 -0, the whole diff. Head 714b712. Model: openai-compatible/nvidia/nemotron-3-ultra-550b-a55b. Commands: @t27-bees review | summary | help | pause | resume. Free for every repository.

@gHashTag
gHashTag merged commit 7273ad3 into master Oct 25, 2026
37 checks passed
gHashTag added a commit that referenced this pull request Oct 25, 2026
The wasm32 law grows its linear memory: SEC_MEMORY 5 / SEC_DATA 11, the
min-form limits (mem_limits_min), pages_for_bytes (MEM_CAP 143,692 -> 3
pages, the gx card's end 211,276 -> 4), the MVP active data entry (flags
0x00, i32.const offset, end, uleb length, bytes), the memory export entry
(export_entry_kind, kind 0x02), and compose_module_mem -- compose_module's
five sections with the memory section between function and export and the
data section after code (ids stay ascending 1,3,5,7,10,11). The stateless
compose_module is untouched: floor 7's 117-byte trio capstone still pins
its bytes, and the new capstone proves the A/B/A in one test -- the same
descriptor set composes 39 bytes stateless (export at 19, no memory
section) and 61 bytes with one page and 'abc' at offset 4 (export at 24,
the memory section's five bytes between). 71/71 tests, 0 vacuous, mutation
control kills (n==61 -> 62 fails, restored). gen-rust regenerated with
stdout redirect; the wasm seal re-saved through tri seals changed (drift 0,
coverage OK, currency 0 stale). Passive segments and the DataCount section
are NOT CLAIMED. Refs #12148, advances #10262, #5980.
gHashTag added a commit that referenced this pull request Oct 25, 2026
The stateful law's second floor: the global section (SEC_GLOBAL 6) with
global_type (valtype + mutability byte), global_entry_i32 (type + i32.const
init + end) and global_body (pre-joined entries behind a vector count); the
memory-backed slot instructions the wide state lowers through -- i32.load
0x28 and i32.store 0x36 with their memarg (uleb alignment, uleb static
offset; uleb(143692) = CC E2 08 pinned), the layout plane's own cap -- and
global.get 0x23 / global.set 0x24. compose_module_mem grows the globals
parameter and splices the global section between memory (5) and export
(7); the capstone pins the 69-byte module (floor 12's 61 + the global
section's eight bytes) with every section boundary and the memory export
entry five bytes right. QUESTION 2 answered and measured: the card's
regions are rows, so they are memory-backed slots; the narrow live scalars
are the globals; mutable globals are NOT EXPORTED (the memory is the one
exported window). 74/74 tests, 0 vacuous; mutation control kills
(n==69 -> 70 fails, restored). gen-rust regen (stdout redirect), seal
re-saved (drift 0, coverage OK, currency 0 stale). Refs #12148, advances
#10262, #5980.
gHashTag added a commit that referenced this pull request Oct 25, 2026
… records the landing and QUESTION 4 (the walk floors and the emitter-mount budget stop)

data_section now routes vec_prefix through the uleb cursor (no unused
binding -- the generated Rust adds no warning over master's one pre-existing
code_section warning); 74/74 tests, 0 vacuous, seal re-saved. The plan block
now states what landed (floors 12-13, with their capstones and mutation
controls) and QUESTION 4 names the measured stop: floors 14-16 are
spec-side walk work (relax REF_LOCAL_VARIABLE, FACT_ROW reserved word 11
for the local count, While-to-loop lowering, call-index mapping), but the
emitter mount needs a ~200-line adapter in cli/t27b/src/wasm32_emitter.rs
that the foreign budget (40/file, 80/PR, shrink-only) does not admit -- the
two lawful paths are named and neither is assumed. Refs #12148, advances
#10262, #5980.
@gHashTag

Copy link
Copy Markdown
Collaborator Author

STATUS: stateful floor law -- floors 12-13 landed on lane/wasm-stateful, floors 14-16 stopped at the emitter-mount budget (findings inside)

Branch: lane/wasm-stateful (4 commits, off origin/master 035df5c, not branched from #12044's lane). No PR to master -- the acceptance bar (the gx_ smoke test in Node) is not reachable yet, so per the lane rules the branch is the artifact.

Landed, each green with capstone + mutation control (74/74 tests, 0 vacuous; seal re-saved, drift 0, coverage OK, currency 0 stale; gen-rust regenerated with stdout redirect; cargo check -p t27b clean, no new warnings over master):

  • Floor 12 -- memory + data segments. SEC_MEMORY/SEC_DATA, min-form limits, pages_for_bytes (MEM_CAP 143,692 -> 3 pages; the gx card's end 211,276 -> 4), the MVP active data_entry, the memory export (export_entry_kind, kind 0x02 -- the page must READ the memory), and compose_module_mem (sections stay ascending 1,3,5,7,10,11). The A/B/A is pinned inside one test: the same descriptor set composes 39 bytes stateless (export at 19) and 61 bytes with one page + data (export at 24, exactly the memory section's five bytes between); floor 7's untouched 117-byte trio capstone keeps its bytes by construction.
  • Floor 13 -- globals + memory-backed state. The global section (id 6, between memory and export), global_type/global_entry_i32/global_body, i32.load 0x28/i32.store 0x36 with memargs (uleb(143692) = CC E2 08 pinned), global.get 0x23/global.set 0x24. QUESTION 2 answered and measured against this card: the NAME/LOG regions are rows -> memory-backed slots; the narrow live scalars are the globals; mutable globals are NOT exported (the memory is the one window). Capstone: the 69-byte module, every section boundary pinned.

The stop, measured (QUESTION 4, written into the spec's plan block):

  • Floors 14-16 (locals, loops, calls) are walk work, not byte-law work: floor 14 relaxes floor 8's REF_LOCAL_VARIABLE (refused_local_variable reads var_idx against nparams) and carries each fn's local count on FACT_ROW's reserved word 11; floor 15 lowers While to loop/br_if through payload nodes (floor 6 already owns the opcodes); floor 16 maps call sites to function indices the fact table does not carry yet. All three are spec-side and lawful -- they were not reached inside this lane's time-box.
  • The emitter mount is the blocked half: cli/t27b/src/wasm32_emitter.rs must flatten the stateful IR shapes into the fact table, and the foreign budget (40 lines/file, 80/PR, shrink-only, owner rules 2026-10-07/09; wasm32_emitter.rs is NOT in foreign-exceptions.txt) does not admit the ~200-line adapter that needs. The M-C PR feat(t27b): wasm32 M-C -- comparisons, eqz lower (t27os#3) #12048 (+369/-164) predates the 2026-10-24 budget gate -- that path is closed now. Two lawful paths are named in the spec: (a) the owner's owner-approved-foreign label with a same-PR deletion offset, or (b) the walk owning more so the adapter shrinks. Neither assumed; the decision is the owner's.

Lessons paid on this lane (for the next agent):

  • t27c gen-rust writes to stdout -- redirect or the idempotence check hashes nothing (paid twice before, avoided here).
  • Asserting any UNWRITTEN byte of an undefined buffer is the queen_chats differential: t27c fills 0xAA (cost one debugging round here; assert only written ranges).
  • Cross-module consumers compile against the SEALED wasm.t27 snapshot -- re-seal (tri seals changed, with target/release on PATH for the embedded t27c seal calls) before an importer can see new law fns.
  • uleb math by hand failed twice (143692 is CC E2 08, and the wide instruction is 5 bytes not 4) -- pin from the law's own vectors, then recompute by chunk before writing the assert.

Next actor: floor 14 per QUESTION 3/4, then the emitter-mount decision. Refs #12148, advances #10262, #5980.

gHashTag added a commit that referenced this pull request Oct 25, 2026
…he memory export, compose_module_mem (min+max limits) and floor 13 globals/load/store survive on #12152's vocabulary; 77/77, mutation-checked, resealed

Master's #12152 landed this lane's floor-12 section law under its own
names (pages_for, memtype, data_segment_active, memory_section,
data_section). The rebase drops this branch's parallel bodies
(pages_for_bytes, mem_limits_min, memory_body, data_entry and the
colliding memory_section/data_section) and keeps what master lacks:
EXPORT_KIND_FUNC/MEM, export_entry_kind, export_body_mem, the stateful
composer compose_module_mem (now min+max limits, fail-closed on
min>max and max>65536), and floor 13 in full (SEC_GLOBAL, global_type,
global_entry_i32, global_body, i32.load/i32.store memargs,
global.get/set). The 62-byte stateful leaf and the 70-byte global
module pin every section boundary; the code-section body length is
pinned 6 against master's own whole-module vector (one hand-pin was
caught red by the suite). The A/B/A holds inside the capstone: the
stateless composer still emits the 39-byte answer module beside the
stateful one. Mutations killed: n==62 and the memory-export kind byte.

Refs #12148, advances #10262, #5980.
@gHashTag

Copy link
Copy Markdown
Collaborator Author

STATUS: the page-side consumer of this law card has landed as a dormant branch of /t27f/live/ -- Trinity-S-AI-DNA/trinity#1744 (t27f_live.js feature-detects the gx_ surface; gx_replay feeds the whole re-encoded log, gx_apply_event walks it per event on the tick grid; nodes/names/edges are read from linear memory, kind colors SPEC amber / AGENT blue / TASK violet / ACTION grey / ARTIFACT green, PRODUCES edges in the particle color). Verified in Node against a mock module answering the gx_ surface with work_graph.t27's own semantics: load path, live path and all silent fallbacks green; today's trio-only wasm leaves the page exactly as it was. The day the stateful-floor lane serves a module with these exports, the graph switches to the law's own topology with zero further PRs on the page.

gHashTag added a commit that referenced this pull request Oct 25, 2026
…he windowed refusal, one i32 group per body); 81/81, mutation-checked, resealed

Floor 8 refused every var at or beyond nparams; floor 14 opens the local
window without moving one byte under it. FACT_ROW's reserved word 11
becomes the fn's i32 local count (fact_put_locals writes it, fact_put
still writes 0, fact_locals_n reads it); refused_local_variable_w and
expr_refusal_w read var_idx against nparams + locals_n (floor 8's fn is
the locals_n 0 case, construct and detail byte for byte);
fn_body_bytes_locals grows ONE i32 local group ahead of the floor-9
body (skipped at 0); walk_prog_locals writes each admitted fn's count as
a u32le row beside its descriptor and lowers through the windowed body;
code_entries_locals and compose_module_mem_l carry the group into the
code entry. Capstones: the third(a,b) local leaf pins the group bytes
01 7F ahead of local.get 2 and the seven-byte code entry, with the A/B/A
in-test (the same table with the window closed rederives the floor-10
bytes); the 64-byte stateful leaf carries the group beside the 62-byte
one. Two hand-pins were caught red by the suite before landing (the
code body length 6, the 62-byte leaf's const offset 48). Mutation
killed: the group valtype 0x7F -> 0x7E. The 117-byte trio is untouched:
walk_prog_locals over a locals-free table is walk_prog.

Refs #12148, advances #10262, #5980.
gHashTag added a commit that referenced this pull request Oct 25, 2026
…t kinds, BS_STMTS_RETURN, the fn gate admits it); 85/85, mutation-checked, resealed

gx_replay's While lowers as a node sequence the walk owns: EK_WHILE_OPEN
(loop 03 40), EK_COND_BR (br_if, var_idx the depth), EK_LOOP_END (end),
EK_LOAD32/EK_STORE32 (floor 13's memargs packed in node_v: align low 32,
offset high 32), EK_LOCAL_SET (floor 14's window's write side). One new
body shape BS_STMTS_RETURN lowers a payload of statement and expression
nodes then the trailing return; the four expression kinds delegate to
lower_expr_node byte for byte. stmt_refusal_w admits the statement kinds
(the window and operator checks stay through the delegation); fn_refusal
admits the shape with body_r's verdict -- BS_OTHER still refuses and the
zero-param const gate is untouched. Capstones: the count-down while pins
all 17 body bytes (03 40 | 20 00 | 0D 00 | 20 00 41 01 6B 21 00 | 0B |
20 00 | 0B) with the A/B/A in-test (the same fn as floor 10's shape is
the three-byte leaf); the whole-prog walk pins the 19-byte code entry.
Mutation killed: the trailing end byte. Shapes 0-2 and the 117-byte trio
are byte-identical -- fn_body_bytes grew a branch, not a byte.

Refs #12148, advances #10262, #5980.
gHashTag added a commit that referenced this pull request Oct 25, 2026
…dx, call_index_of maps an export name to its admitted index); 87/87, mutation-checked, resealed -- the stateful floor set is complete

The gx_ exports call the law's own helpers: EK_CALL joins the statement
kinds (call 0x10 + uleb, the arguments on the stack under it), and
call_index_of maps an export NAME to its index among the ADMITTED fns in
table order -- the same order walk_prog_locals numbers descriptor rows,
so the map's answer is the composed module's own function index. A name
no admitted fn carries maps to 0xFFFFFFFF: the adapter refuses the call
site, it does not guess. Capstones: the call leaf pins 20 00 10 00 0B
with the map's own answer as the callee index (helper 0 before wrap 1);
the map skips test rows and private rows. Mutation killed: gx_two's
index pin. Two test-buffer slips were caught red by the suite before
landing (an 18-byte name blob in a 16-byte slice, a name offset taken
from put_bytes' end instead of its start).

Refs #12148, advances #10262, #5980.
@gHashTag

Copy link
Copy Markdown
Collaborator Author

STATUS (lane/wasm-stateful, PR #12178): the stateful floor set 12-16 is COMPLETE as law -- reconciled onto #12152's section vocabulary (the branch's parallel floor-12 bodies dropped, the memory export + compose_module_mem + the global section kept), floor 14 the LOCAL WINDOW (FACT_ROW word 11 = locals_n, the windowed refusal, one i32 group per body), floor 15 LOOPS through the payload nodes (six statement kinds, BS_STMTS_RETURN, the fn gate admits it), floor 16 CALLS (EK_CALL, call_index_of). 87/87 tests, 0 vacuous, every floor mutation-checked, gen regenerated, sealed after each step. The 117-byte trio is byte-identical by construction and by the in-test A/B/A capstones.

THE MOUNT STOPPED AT A LAWFUL WALL, measured before any foreign line was written: every gx_ export takes gx: *GraphExport, and floor 8's admission law refuses non-i32-carrier params (REF_FN_PARAMETERS). The u8 returns ride carrier's default i32 arm; the pointer parameter is the one wall. The owner's owner-approved-foreign approval (2026-10-25, quoted in the PR) covers the adapter work, but the next agent's FIRST commit is the lawful decision: either the carrier-param law (does *T ride the i32 carrier in wasm32?) with its own pins, or wasm-shape wrappers on the card (i32-offset signatures). t27b build --target wasm32 already exists (cmd_build_wasm), so once admission opens, the mount is adapter-only work under the label, then the Node acceptance (gx_replay of the 24-event fixture, a real memory read, the trio byte-identical beside it).

gHashTag added a commit that referenced this pull request Oct 25, 2026
…12152, locals/loops/calls as law; the mount's measured stop at the pointer-param wall (#12178)

* spec(wasm): reconcile the stateful floors onto master's floor 12 -- the memory export, compose_module_mem (min+max limits) and floor 13 globals/load/store survive on #12152's vocabulary; 77/77, mutation-checked, resealed

Master's #12152 landed this lane's floor-12 section law under its own
names (pages_for, memtype, data_segment_active, memory_section,
data_section). The rebase drops this branch's parallel bodies
(pages_for_bytes, mem_limits_min, memory_body, data_entry and the
colliding memory_section/data_section) and keeps what master lacks:
EXPORT_KIND_FUNC/MEM, export_entry_kind, export_body_mem, the stateful
composer compose_module_mem (now min+max limits, fail-closed on
min>max and max>65536), and floor 13 in full (SEC_GLOBAL, global_type,
global_entry_i32, global_body, i32.load/i32.store memargs,
global.get/set). The 62-byte stateful leaf and the 70-byte global
module pin every section boundary; the code-section body length is
pinned 6 against master's own whole-module vector (one hand-pin was
caught red by the suite). The A/B/A holds inside the capstone: the
stateless composer still emits the 39-byte answer module beside the
stateful one. Mutations killed: n==62 and the memory-export kind byte.

Refs #12148, advances #10262, #5980.

* spec(wasm): floor 14 -- the LOCAL WINDOW (word 11 carries locals_n, the windowed refusal, one i32 group per body); 81/81, mutation-checked, resealed

Floor 8 refused every var at or beyond nparams; floor 14 opens the local
window without moving one byte under it. FACT_ROW's reserved word 11
becomes the fn's i32 local count (fact_put_locals writes it, fact_put
still writes 0, fact_locals_n reads it); refused_local_variable_w and
expr_refusal_w read var_idx against nparams + locals_n (floor 8's fn is
the locals_n 0 case, construct and detail byte for byte);
fn_body_bytes_locals grows ONE i32 local group ahead of the floor-9
body (skipped at 0); walk_prog_locals writes each admitted fn's count as
a u32le row beside its descriptor and lowers through the windowed body;
code_entries_locals and compose_module_mem_l carry the group into the
code entry. Capstones: the third(a,b) local leaf pins the group bytes
01 7F ahead of local.get 2 and the seven-byte code entry, with the A/B/A
in-test (the same table with the window closed rederives the floor-10
bytes); the 64-byte stateful leaf carries the group beside the 62-byte
one. Two hand-pins were caught red by the suite before landing (the
code body length 6, the 62-byte leaf's const offset 48). Mutation
killed: the group valtype 0x7F -> 0x7E. The 117-byte trio is untouched:
walk_prog_locals over a locals-free table is walk_prog.

Refs #12148, advances #10262, #5980.

* spec(wasm): floor 15 -- LOOPS through the payload nodes (six statement kinds, BS_STMTS_RETURN, the fn gate admits it); 85/85, mutation-checked, resealed

gx_replay's While lowers as a node sequence the walk owns: EK_WHILE_OPEN
(loop 03 40), EK_COND_BR (br_if, var_idx the depth), EK_LOOP_END (end),
EK_LOAD32/EK_STORE32 (floor 13's memargs packed in node_v: align low 32,
offset high 32), EK_LOCAL_SET (floor 14's window's write side). One new
body shape BS_STMTS_RETURN lowers a payload of statement and expression
nodes then the trailing return; the four expression kinds delegate to
lower_expr_node byte for byte. stmt_refusal_w admits the statement kinds
(the window and operator checks stay through the delegation); fn_refusal
admits the shape with body_r's verdict -- BS_OTHER still refuses and the
zero-param const gate is untouched. Capstones: the count-down while pins
all 17 body bytes (03 40 | 20 00 | 0D 00 | 20 00 41 01 6B 21 00 | 0B |
20 00 | 0B) with the A/B/A in-test (the same fn as floor 10's shape is
the three-byte leaf); the whole-prog walk pins the 19-byte code entry.
Mutation killed: the trailing end byte. Shapes 0-2 and the 117-byte trio
are byte-identical -- fn_body_bytes grew a branch, not a byte.

Refs #12148, advances #10262, #5980.

* spec(wasm): floor 16 -- CALLS (EK_CALL with the callee index in var_idx, call_index_of maps an export name to its admitted index); 87/87, mutation-checked, resealed -- the stateful floor set is complete

The gx_ exports call the law's own helpers: EK_CALL joins the statement
kinds (call 0x10 + uleb, the arguments on the stack under it), and
call_index_of maps an export NAME to its index among the ADMITTED fns in
table order -- the same order walk_prog_locals numbers descriptor rows,
so the map's answer is the composed module's own function index. A name
no admitted fn carries maps to 0xFFFFFFFF: the adapter refuses the call
site, it does not guess. Capstones: the call leaf pins 20 00 10 00 0B
with the map's own answer as the callee index (helper 0 before wrap 1);
the map skips test rows and private rows. Mutation killed: gx_two's
index pin. Two test-buffer slips were caught red by the suite before
landing (an 18-byte name blob in a 16-byte slice, a name offset taken
from put_bytes' end instead of its start).

Refs #12148, advances #10262, #5980.
gHashTag added a commit that referenced this pull request Oct 25, 2026
…12148, #5980)

Floor 14 opened the local window as law (#12178), but the deleted emitter's
mount fed it nothing: ir::wasm_facts projected no statement shape and wrote
fact word 11 as 0, so every fn with an intermediate still refused as fn body.
Floor 17 is the adapter floor -- the three pieces the mount calls, nothing
else:

- stmts_refusal_w: the windowed verdict of a BS_STMTS_RETURN payload.
  Statements are opcode spellings, the window rides the EK_VAR reads through
  expr_refusal_w, the value stack closes on exactly one return value, and a
  store REFUSES when the value it stores refused -- the verdict never rides
  under a local.set, or the lowering would emit a body missing those bytes.
  The loop/call/load/store kinds fail closed (their mounts carry the label
  depth, callee arity and memarg facts; the law already spells their bytes).
- bodies_cap_l: bodies_cap plus one local group's six bytes per
  locals-carrying row -- walk_prog_locals' own reserve.
- compose_module_with_image_l: the emit_with_image composer carrying the
  locals rows; an empty image over all-zero rows delegates to
  compose_module_with_image, so the scalar and mounted cuts cannot move.

Five tests: the window walk, fail-closed (unbalanced, mountless kinds, the
adapter's 255 marker, a partial fact row), the capacity +6, and the two
composers -- the delegation pinned by byte equality in both cuts, the
49-byte locals leaf (inc: t = a + 1; return t * 2;) pinned byte for byte
through walk_prog_locals, its 66-byte image twin in section id order
1, 3, 5, 7, 10, 11. 103/103 via t27b test and t27c test-report; wasm32.rs
12/12 and wasm32_equality 1/1 stay green; resealed.

The mount itself (ir.rs projection + lib.rs adapter) lands in the next
commit, paid by shrink.
gHashTag added a commit that referenced this pull request Oct 25, 2026
…1 feeds the walk (floor 17's mount; Refs #11840, #12148, #5980)

Floor 17's law landed in the previous commit; this is the mount it serves.
The diff shrinks: +10 hand-written foreign lines, -61 (the counted files
+1/-32; one duplicated test folded, see below).

- ir.rs wasm_facts: a straight-line body (assignments then the trailing
  return, all-i32-carried locals) projects as BS_STMTS_RETURN -- each
  assignment lowers postfix(value) then EK_LOCAL_SET, the return walks
  last -- and fact word 11 carries the local count through
  fact_put_locals. The body verdict is the law's own windowed fold
  (stmts_refusal_w), so a var read past nparams + locals_n still refuses
  with floor 8's construct and detail byte for byte. Any other statement
  (If, While, Call, Store) keeps BS_OTHER's refusal; a zero-param fn still
  owes the const shape.
- lib.rs emit_with_image: walk_prog_locals + bodies_cap_l +
  compose_module_with_image_l. All-zero locals rows delegate byte for byte
  to the old composer, so the 43-byte leaf, the 117-byte trio and the
  60-byte mounted cut cannot move.
- tests/wasm32.rs: the mount's end-to-end gate -- the inc leaf (the law's
  49 bytes, group 01 7F inside the entry) instantiates in Node's
  WebAssembly and answers inc(7) = 16. The M-B byte-pin test folds: the
  law pins the identical two-fn framing byte for byte (the 87-byte duo and
  117-byte trio, Node-verified per their law tests) and the same
  latency/frame source keeps its runtime gate in the neighboring Node
  test; stale narration prose trimmed beside it.

A/B on the same spec: pre-mount the wasm32 build refused "unsupported
construct fn body at line 4 (fn inc: ...)"; post-mount it emits the
49-byte module and Node answers inc(7)=16, inc(0)=2, byte-identical
across rebuilds. wasm32.rs 12/12, wasm32_equality 1/1, wasm.t27 103/103.
gHashTag added a commit that referenced this pull request Oct 26, 2026
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