From 8aab2bc58bc179c6174b0ea2bf7f948e1aa0fe96 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 4 Aug 2026 18:38:47 +0000 Subject: [PATCH 1/4] docs: record W1 as done, resolve D3, and REVERSE D4's recommendation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit W1's gate is green in blockly-rs (4d39590) and the three follow-ups the plan listed as prerequisites have landed (31e18fb, e4b5e7e). Recorded here rather than left to the commit log, per the append-only rule. D4's recommendation is REVERSED, in place rather than rewritten. The Inventory host conflates two codebooks: Inventory indexes functions, which are shared by definition; constants are per-function data, which are owned by definition, so a pool there is a shared-mutable sink with N writers. Shipped instead is a sibling pool node with per-facet classids naming each constant's type — an f64 and a UTF-8 string are different readings of 12 bytes, and a discriminant byte inside the payload would be a second schema under one classid. Two further options are recorded as killed by named constraints so they are not re-proposed: literal-as-call-run breaks the W1 one-write gate (call count would track literal width), and stealing content slots makes the call budget per-function so a new string can overflow the calls. The pool remains gated on an operator mint and is opt-in in code: lower_script still refuses a wide literal, so a placeholder classid cannot reach stored data before the concepts exist. The proposed ids are labelled a proposal, and the cheap alternative (one concept plus a type-tag byte) is refused on the record. D3 resolved with two anchors, because the obvious one was insufficient: the census compared against SYMBOLS, so a renumber upstream would have left it passing while every stored program changed meaning. W2's producer half is recorded with the reasoning for the ordinal choice and the injection that proved its falsifier non-vacuous. --- docs/BLOCK-EDITOR-PLAN.md | 112 +++++++++++++++++++++++++++++++++++++- 1 file changed, 110 insertions(+), 2 deletions(-) diff --git a/docs/BLOCK-EDITOR-PLAN.md b/docs/BLOCK-EDITOR-PLAN.md index 6859a33..3e446e9 100644 --- a/docs/BLOCK-EDITOR-PLAN.md +++ b/docs/BLOCK-EDITOR-PLAN.md @@ -78,7 +78,7 @@ never propagates into OGAR, lance-graph, or ndarray. #234, #235, #236 merged. Nothing below may re-open these rulings without a new operator ruling and a ledger correction. -### W1 — the POC cast (`blockly-rs`) — **NEXT** +### W1 — the POC cast (`blockly-rs`) — **DONE** The operator's original brief, unchanged: *reuse the existing; materialize the ABI-shaped SoA the way a JSON would, but without serialization, by casting into @@ -103,6 +103,18 @@ No bytecode, no ISA — the encoding question was settled in W0. If that fails, the projection thesis is wrong and everything above W1 is decoration. It is cheap to run and must run first. +**Gate result: GREEN**, `blockly-rs` `4d39590`. Both halves route through +`Workspace::apply`, so a handler that let a drag reach the record fails +the test — verified by injecting exactly that leak and confirming two +tests fail. Without the apply step "a drag changes nothing" would have +been `f(x) == f(x)`, which is how the first version of this falsifier +was written and why it was rewritten. + +**Also landed in W1** (`31e18fb`, `e4b5e7e`): the ValueParam byte +encoding, both D3 drift anchors, the D4 constant pool (opt-in, pending +mint), and the Klickwege address producer — see § Open decisions for +each, including D4's reversal. + **Also in W1:** app-prefix mint for `blockly-rs` in `ogar-vocab::ports` (small, same review path as #234). @@ -155,9 +167,31 @@ tree today, unchanged, via `child_links: (parent_key, slot) → child_key`. | multi-facet body ingest | **absent** — `a2ui-wasm` implements ONE facet (`FACET_LEN = 12`, the V3 payload); a body is 30 | none; unimplemented capacity, not a defect | | drag/connect semantics | **absent** | **T2 pressure** — local drag state is fine, but the RESULT must travel as an address-carried write | +**Producer side landed** (`blockly-rs` `e4b5e7e`, `blockly_abi::klickweg`). +Three of `KlickwegEdge`'s five fields are a property of the program and are +produced there: `class_id` (canon-high under the app prefix), `ordinal`, and +`predicate`. `from_key` and `seq` belong to the session, not to the cast, so +they stay a2ui-side. Neither repo imports the other. + +**The ordinal is the CALL INDEX** — where `lower_script` put that block's +`Call` — so `raise_calls(body)[ordinal]` IS the clicked block's call, by +construction. That makes an address checkable against the ABI rather than +merely self-consistent, which is what its falsifier does. A block id is editor +state and a position is presentation; neither survives the trip, and Blockly's +tree order is not the program's order. Verified non-vacuous by injection: a +pre-order walk still yields unique, dense, plausible ordinals — and fails two +tests, because they index the real lowered body. + +T2 holds structurally rather than by assertion: nothing in that module returns +anything invocable, so an `onClick` lambda cannot be expressed through it even +by accident. The predicate carries a Selector code (`math_arithmetic[ADD]`) and +never a ValueParam code, so two clicks on one action cannot read as two +different actions. + **W2 gates:** 1. One block placed in a2ui-rs produces exactly one `KlickwegEdge` that `lower_action_fire` turns into a valid `ActionInvocation` — no new predicate. + *(Producer half done; the a2ui-side consumption is what remains.)* 2. `resolve_nested` walks `canvas → script → block` on real rows. 3. A `Skin::Grid` renders placed tiles from `position` addresses, and hit-test → ordinal → `ActionInvoke` fires by address (never an inline handler). @@ -198,9 +232,83 @@ becomes relevant, not before. | **D1** | POC drives JS Blockly via a thin WASM shim in `blockly-rs`, or over the a2ui-server wire? | **shim** — reaches the W1 falsifier fastest; the wire is W2's job | W1 | | **D2** | Does "place tile at slot N" travel as `ActionInvoke{ordinal: PLACE, args: [N, fn]}`, or does it need a third `FrameKind`? | **`ActionInvoke` args** — `args` is explicitly ClassView/ActionDef-carved, so this is an address-carried write, which is what T2 mandates. A third kind widens a deliberately closed vocabulary | W2 | | **D3** | Palette drift detection against upstream Apache-2.0 sources | **hand-curation + drift test** (~half a day). ruff has NO JS/TS parser; a `ruff_ts_spo` is multi-week for a 228-definition harvest. `ruff_spo_address`'s mint half is confirmed language-agnostic if that changes | any | -| **D4** | Constant pool home for wide literals (`WAIT:1.5`, strings) | Inventory SoA is the natural host — it is already the label codebook | W1 (only when a literal exceeds a byte) | +| **D4** | Constant pool home for wide literals (`WAIT:1.5`, strings) | ~~Inventory SoA is the natural host — it is already the label codebook~~ **REVERSED, see below** | W1 (only when a literal exceeds a byte) | | **D5** | `scratch-rs` licensing: whole-repo GPL-3.0, or GPL leaf crate + permissive siblings? | **leaf crate** — maximum optionality at zero cost | W4 | +### D3 — RESOLVED (`blockly-rs` `e4b5e7e`) + +Two drift anchors shipped in `blockly-abi`, because the obvious one was +not sufficient: + +- `the_palette_byte_values_are_pinned` pins the **bytes**. The + pre-existing census compared against `FnIndex::LT` — the *symbol* — so + an upstream renumber would have left every symbolic assertion passing + while every stored program changed meaning. The pins are also asserted + mutually distinct, so a collapsed palette cannot be matched by a + collapsed expectation table. +- `the_value_param_option_sets_are_pinned` pins the 17 argument-dropdown + option sets in source order, harvested from the Apache-2.0 + definitions. + +### D4 — RESOLVED, and the recommendation above is REVERSED + +The Inventory recommendation is **withdrawn**. It conflates two +codebooks: Inventory indexes *functions*, which are shared by +definition; constants are *per-function data*, which are owned by +definition. Putting a per-function pool in the one table every function +shares makes a shared-mutable sink with N writers. + +**Shipped instead: a sibling pool node** — same 30 content slots, same +16-byte stride, identity inherited from the owning function, and a +**per-facet classid naming the constant's type**, because an `f64` and a +UTF-8 string are different readings of 12 bytes and "your classid +defines the schema, period" forbids a discriminant byte inside the +payload. Index arithmetic `(idx-1)/30` and `(idx-1)%30`; `0` reserved as +the zero-fallback so a zeroed value byte reads as *no constant* rather +than *constant zero*. + +Two further options were considered and killed by specific constraints, +recorded so they are not re-proposed: + +- **Literal as a run of calls** (no pool) — killed by the W1 falsifier: + the call count would track the literal's width, so editing `255` to + `1000000` would shift every later call and rewrite the tail of the + body. An operand edit must produce ONE write. +- **Steal content slots from the body node** — legal under the substrate + (per-facet classids make a mixed node schema-honest), but it makes the + call budget per-function, so "add one string" can make a program that + fit stop fitting, with the overflow blaming the calls. + +Capacity, explicit: 255 usable indices, 30 per node, 9 nodes. The 256th +returns `PoolFull` and the remedy is a **function split**, never a wider +index. `PoolFull` is reachable only under `Quads` (270 addressable value +bytes > 255); `Pairs` (180) and `Triples` (240) cannot exhaust it. + +**Still gated on an operator mint.** The pool is opt-in: +`lower_script` still refuses a wide literal, and only +`lower_script_with_pool` interns — under caller-supplied classids, with +deliberately invalid placeholders in the interim, so a placeholder +cannot reach stored data before the concepts exist. Proposed mints +(`ConstantPool` / `ConstF64` / `ConstUtf8Inline` at `0x1703..0x1705`) +are a **proposal**, not an assumption. The explicitly rejected cheap +alternative — one concept with a type-tag byte in the payload — is +named here so it is refused on the record rather than rediscovered. + +### ValueParam encoding — RESOLVED (`blockly-rs` `31e18fb`) + +A `ValueParam` dropdown code encodes as its **ordinal in the codebook's +own pinned table**, keyed on `(block type, field name)`. Deliberately +not Blockly's live array order: reordering an options array is cosmetic +upstream and would silently reinterpret every stored program here, so +anchoring the ordinal in the codebook converts that hazard into a loud +drift-test failure. Keyed on the field and not the type alone because +`text_getSubstring`'s `WHERE1`/`WHERE2` differ only in their third +entry. + +Widths measured against the Apache-2.0 definitions: largest set is 8 +(`math_on_list`), so the byte is not a squeeze. `math_on_list[RANDOM]` +is absent from its table by construction — it is one of the three gaps. + ## Falsifiers (each must be able to FAIL) 1. **W1** — drag = 0 writes, operand change = 1 write. From 99d25ee5047798c4f4df5dab292eea9a900abb8a Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 4 Aug 2026 19:04:12 +0000 Subject: [PATCH 2/4] docs: W2-W5 all shipped; record the audit corrections and the honest W4 gap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Marks every wave DONE and records what the work found, including two places the plan's own audit was wrong and one place it missed a defect. W2's audit was corrected on two points by reading the source: Form and Flow do not map the position address at all (they place by iteration order), so Grid is the FIRST address-driven skin rather than a symmetric addition; and FACET_LEN=12 is a byte WIDTH, not a facet count — the framing that a prior session's withdrawn truncation claim died on. It also missed the real defect: an out-of-range mask position was silently dropped while apply returned Ok. W4 is marked done with an explicit gap rather than a claim: the lowering is proven end-to-end, but the gate as written names a .sb3 round-trip and no .sb3 is imported yet. Saying so is cheaper than letting a future session discover it. D1/D2/D5 resolved as recommended; D3 and D4 already recorded above, D4 reversed. --- docs/BLOCK-EDITOR-PLAN.md | 124 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 120 insertions(+), 4 deletions(-) diff --git a/docs/BLOCK-EDITOR-PLAN.md b/docs/BLOCK-EDITOR-PLAN.md index 3e446e9..630bbf6 100644 --- a/docs/BLOCK-EDITOR-PLAN.md +++ b/docs/BLOCK-EDITOR-PLAN.md @@ -118,7 +118,7 @@ each, including D4's reversal. **Also in W1:** app-prefix mint for `blockly-rs` in `ogar-vocab::ports` (small, same review path as #234). -### W2 — Klickwege wiring into a2ui-rs +### W2 — Klickwege wiring into a2ui-rs — **DONE** **This is the wave the goal names, and the key finding is that most of it already exists.** @@ -196,7 +196,43 @@ different actions. 3. A `Skin::Grid` renders placed tiles from `position` addresses, and hit-test → ordinal → `ActionInvoke` fires by address (never an inline handler). -### W3 — the second skin (PowerAutomate-shaped) +**Consumer side landed** (a2ui-rs #18). All three named gaps closed, and the +audit above was **corrected on two points** by reading the source: + +- Gap 2's "symmetric with Form/Flow" was generous. Form and Flow do not map + the address at all — they place by ITERATION ORDER and copy `position` + through only so hit-test can round-trip it. `Grid` is the FIRST skin where + `position` is genuinely a coordinate, which makes it more novel than + "symmetric" and is why its falsifier uses a position GAP (0, 1, 3): with + contiguous positions the two placement rules coincide and the test would + pass with `place_flow` substituted. +- Gap 3's framing was wrong. `FACET_LEN = 12` is not a facet COUNT — it is the + byte width of one V3 facet. (The prior withdrawn "truncation defect" died on + exactly this confusion; it must not be resurrected under a new name.) The + capacity deferral is real and already documented upstream as fail-safe. + +**But the audit MISSED a real defect, and it is the more important find.** +`apply_node_delta`'s loop was `if pos < FACET_LEN` with no `else`: an +out-of-range mask position consumed no value byte and raised no error, so a +field the sender declared vanished while `apply` returned `Ok`. The same loop +was already loud on the UNDER-supply side (`ValueUnderrun`) and silent on the +over-range side — and the wire is wide-native (`a2ui-core` round-trips +positions 40 and 64), so such frames are well-formed and only unrenderable at +the client. Now `PositionOutOfRange`. + +**The >255 ceiling does not bind this arc.** `FieldView.position` is `u8`; a +lowered body is at most 180 calls (`Pairs`) or 90 (`Quads`). No upstream OGAR +widening is needed — recorded so it is not re-derived. + +The palette is `(parent, slot) → Vec`, the structural twin of +`child_links`; the pair is the whole palette/canvas distinction with no third +concept (**available = offered but not linked; placed = linked**). Captions +resolve through `ClientClass.title`, so it stores addresses and reads names +rather than carrying names of its own — which is why it cannot mint a second +vocabulary. A pick is an ordinary `ActionInvoke` through the unmodified +up-wire path: no new `Frame` variant, no wire change. + +### W3 — the second skin (PowerAutomate-shaped) — **DONE** Operator-set: the same ABI, a low-code editor surface, **also** Mario-editor ergonomics over `ClassView : WideFieldMask` projections. Two skins, one ABI — @@ -206,7 +242,20 @@ applied at editor scale. Gate: the identical rows render under both skins with no ABI change and no second vocabulary. -### W4 — execution (`scratch-rs`, GPL leaf) +**GREEN, and it needed no new type** (a2ui-rs #18). `Grid { cols: 1 }` IS the +PowerAutomate-shaped vertical step list; `Grid { cols: 4 }` is the block +canvas. Same variant, different column count — the strongest available form of +the T1 discipline, since a second surface did not even require a second skin. + +The gate asserts one `NodeDelta` → one resolved surface → four skins with +identical row sets, field ADDRESSES, and action ordinals, and byte-identical +ABI afterwards (rendering is a read). Its anti-vacuity half is what makes it +worth having: the four skins must actually DIFFER in geometry, checked +pairwise — four skins producing identical rects would satisfy everything else +while "one surface, many skins" quietly collapsed to "one skin". Verified by +injection. + +### W4 — execution (`scratch-rs`, GPL leaf) — **DONE** `.sb3` import via `rash_loader_sb3`; SoA calls → `rash_vm::ScratchBlock` → Cranelift. Audit finding: **nothing in rash's IR forbids this** — `Ptr` is a @@ -217,12 +266,73 @@ Reciprocity: contribute the rash TODO gaps we need anyway (`Wait`, `Wait Until`, `Stop all`, `Ceiling`, `ASin/ACos/ATan`, `Ln/Log`, Lists, Broadcasts) upstream, where GPL is already the license. -### W5 — grammar projection +**SHIPPED** (`scratch-rs`, two crates). **D5's leaf-crate ruling holds and is +now mechanically checkable**: `cargo tree -p scratch-abi | grep -c rash` is 0. +The licence is not a choice — rash's workspace carries a GPLv3 repo-root +LICENSE and **no `license` field in any of its six `Cargo.toml` files**, so no +per-crate override exists to appeal to and every crate linking `rash_vm` is +GPL. Exactly one does. + +- `scratch-abi` (MIT) — `nest` / `nest_all` / `flatten`, the exact inverse of + the cast's post-order emission. Arity is read from blockly-abi's table, + deliberately not a second copy: two tables that disagreed would produce two + different trees from one program, silently. +- `scratch-jit` (GPL-3.0-only) — `to_scratch_block`. A **rename, not a + translation**: rash's `Input` is `Obj(literal) | Block(nested)`, exactly the + leaf/branch distinction a `CallTree` node makes, so no evaluation order is + chosen and no operand reordered. + +**The refusal is the load-bearing part.** Where the palette has no +`ScratchBlock` counterpart the mapping refuses rather than composing one from +several rash ops — `NEQ` as `not(cmp)` would be the adapter acquiring +semantics of its own, which is the parallel-object-model anti-pattern the +Core-First doctrine names. rash's operator set is Scratch's, so the gaps are +the Blockly operations Scratch lacks (`LN`/`LOG10`/`EXP`, inverse trig, +`NEQ`/`LTE`/`GTE`, `CEIL`, `POW`); `unmapped_functions()` enumerates them and +each is asserted to really refuse, so the list cannot rot into documentation +nothing checks. + +The comparison fold carries both halves on purpose: `LT`/`GT`/`EQ` fold into +rash's single `OpCmp`-with-`Ordering`, AND `NEQ`/`LTE`/`GTE` stay refused — +without the second half the first could quietly become "every comparison maps". + +**Falsifier state, honestly:** the gate as written ("a `.sb3` project produces +identical output through rash-native loading and through the SoA path") is +**not** met — no `.sb3` is imported yet. What IS proven end-to-end is the +lowering: Blockly record → ABI body → operand tree → rash IR, through three +crates' public surfaces, asserted structurally. The `.sb3` arm needs +`rash_loader_sb3::ProjectLoader` and a fixture project, and is the remaining +W4 work. + +### W5 — grammar projection — **DONE** `A = B + C` rendered from and parsed back into the call stream. Storage unchanged (the W0 ruling). This is where lance-graph's grammar machinery becomes relevant, not before. +**SHIPPED** (`blockly-abi::projection`). The load-bearing claim is proven +rather than argued: `5 + 3` typed as text and `5 + 3` built as blocks produce +**byte-identical** bodies, with a different program asserted not to match so +the equality is not free. + +It needs arity for the same reason `scratch-abi` does, and refuses for the +same reason. Statement-level functions (`IF`, `REPEAT`, `PROC_DEF`) are +deliberately outside the table — they nest by reference, which is the +*statement* projection, a separate surface. + +**Both halves of the gate, and the asymmetry that makes the second real:** +the call stream round-trips byte for byte in the direction that matters +(`body → text → body`; parsing is many-to-one, so `text → body → text` would +be the weaker claim). Whitespace, redundant parens, and every trace of +geometry explicitly do NOT survive — five spacings of `1 + 2 * 3` must yield +identical bytes, and the render is canonical rather than reproducing what was +typed. If spacing ever survived, the ABI would be carrying a layout decision. +Two-sided: parens that are NOT redundant must survive, or "spacing does not +matter" would be indistinguishable from "parens are ignored". + +Precedence and associativity are pinned by stack ORDER, not by round-trip — a +parser that ignored precedence would still round-trip its own output. + --- ## Open decisions @@ -235,6 +345,12 @@ becomes relevant, not before. | **D4** | Constant pool home for wide literals (`WAIT:1.5`, strings) | ~~Inventory SoA is the natural host — it is already the label codebook~~ **REVERSED, see below** | W1 (only when a literal exceeds a byte) | | **D5** | `scratch-rs` licensing: whole-repo GPL-3.0, or GPL leaf crate + permissive siblings? | **leaf crate** — maximum optionality at zero cost | W4 | +**D1 · D2 · D5 — all RESOLVED as recommended**, no reversals. D1: the W1 cast +is a library the shim drives, and the falsifier was reached without the wire. +D2: a pick travels as an ordinary `ActionInvoke` at an ordinal — no third +`FrameKind` was needed, and the palette added zero `Frame` variants. D5: the +leaf crate is shipped and its containment is `cargo tree`-checkable. + ### D3 — RESOLVED (`blockly-rs` `e4b5e7e`) Two drift anchors shipped in `blockly-abi`, because the obvious one was From 7077e3a7027b36717a35b5b39377d68b0e63b2c0 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 4 Aug 2026 19:11:35 +0000 Subject: [PATCH 3/4] =?UTF-8?q?docs:=20W4's=20gate=20is=20GREEN=20?= =?UTF-8?q?=E2=80=94=20the=20.sb3=20round-trip=20is=20closed?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous entry marked W4 done with an explicit gap: the gate names a .sb3 round-trip and none was imported. That gap is now closed, and the entry records how rather than merely that. The arms meet at execution rather than at the IR — not a compromise. An IR diff is not available (load_block takes a CompileContext with private fields and no public constructor), but it would also be the weaker check: a structural diff is satisfied by two trees that are equal and both wrong. Records both injections, because 'identical output' is the easiest assertion in the world to satisfy with an inert harness. --- docs/BLOCK-EDITOR-PLAN.md | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/docs/BLOCK-EDITOR-PLAN.md b/docs/BLOCK-EDITOR-PLAN.md index 630bbf6..2ad9966 100644 --- a/docs/BLOCK-EDITOR-PLAN.md +++ b/docs/BLOCK-EDITOR-PLAN.md @@ -296,13 +296,29 @@ The comparison fold carries both halves on purpose: `LT`/`GT`/`EQ` fold into rash's single `OpCmp`-with-`Ordering`, AND `NEQ`/`LTE`/`GTE` stay refused — without the second half the first could quietly become "every comparison maps". -**Falsifier state, honestly:** the gate as written ("a `.sb3` project produces -identical output through rash-native loading and through the SoA path") is -**not** met — no `.sb3` is imported yet. What IS proven end-to-end is the -lowering: Blockly record → ABI body → operand tree → rash IR, through three -crates' public surfaces, asserted structurally. The `.sb3` arm needs -`rash_loader_sb3::ProjectLoader` and a fixture project, and is the remaining -W4 work. +**Gate: GREEN** (`scratch-rs` `tests/sb3_gate.rs`). A real `.sb3` — a genuine +zip carrying `project.json` AND a costume asset, since rash resolves +`currentCostume` and reads the asset's bytes — whose green-flag script computes +`(3 + 4) * 2`. The native arm is `ProjectLoader::new(sb3).build()`: rash unzips, +parses with its own serde types, lowers with its own `load_block`, compiles with +Cranelift, and nothing of ours runs. The SoA arm is the same program as a call +stream (`3, 4, ADD, 2, MUL`) through `to_scratch_block` into the same +`Script`/`SpriteBuilder`/`ProjectBuilder` entry points rash's own loader uses. +Both run; the values are compared. + +**The arms meet at EXECUTION, not at the IR, and that is the stronger check.** +An IR diff is not available (`load_block` is public but takes a +`CompileContext` with private fields and no public constructor) — but it would +also be weaker: a structural diff is satisfied by two trees that are equal and +both wrong. A run proves the same VALUE comes out of rash's own compiler either +way. + +Both injections were run, because "identical output" is the easiest assertion +in the world to satisfy with an inert harness: changing the fixture to +`(3 + 4) * 9` makes the native arm read **63**, proving rash really parses and +runs OUR fixture rather than returning a cached or default value; lowering +`MUL` to `OpAdd` makes it **native 14 vs SoA 9**, proving the SoA arm really +feeds the comparison. ### W5 — grammar projection — **DONE** From cd21d85528d15b60e982be79846aa09a98b17ab9 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 4 Aug 2026 19:13:54 +0000 Subject: [PATCH 4/4] =?UTF-8?q?docs:=20address=20codex=20P2=20=E2=80=94=20?= =?UTF-8?q?W1's=20mint=20is=20outstanding,=20and=20the=20risk=20it=20named?= =?UTF-8?q?=20was=20real?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Codex flagged W1 as marked DONE while its own section still named an unfinished app-prefix mint, warning that someone could hardcode a colliding value. Correct, and the hypothetical was already instantiated: blockly-abi's Klickwege tests used 0x1000, which ogar-vocab::ports reserves for the V3-adoption monitor marker with a test asserting it must never be a port APP_PREFIX. Fixed in blockly-rs e53aefe. W1 is now 'DONE (one mint outstanding)' and the mint moved into a new Remaining section that collects all three open operator decisions in one place, with the allocation state and the reserved value recorded so the next reader does not re-derive them. No value is proposed for any of the three — a mint is an operator decision by standing rule. Codex's other P2 (W4 marked DONE while its gate was open) was already resolved in 7077e3a, one commit after the 99d25ee it reviewed: the .sb3 gate is green. --- docs/BLOCK-EDITOR-PLAN.md | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/docs/BLOCK-EDITOR-PLAN.md b/docs/BLOCK-EDITOR-PLAN.md index 2ad9966..ec9a451 100644 --- a/docs/BLOCK-EDITOR-PLAN.md +++ b/docs/BLOCK-EDITOR-PLAN.md @@ -78,7 +78,7 @@ never propagates into OGAR, lance-graph, or ndarray. #234, #235, #236 merged. Nothing below may re-open these rulings without a new operator ruling and a ledger correction. -### W1 — the POC cast (`blockly-rs`) — **DONE** +### W1 — the POC cast (`blockly-rs`) — **DONE (one mint outstanding)** The operator's original brief, unchanged: *reuse the existing; materialize the ABI-shaped SoA the way a JSON would, but without serialization, by casting into @@ -115,8 +115,19 @@ encoding, both D3 drift anchors, the D4 constant pool (opt-in, pending mint), and the Klickwege address producer — see § Open decisions for each, including D4's reversal. -**Also in W1:** app-prefix mint for `blockly-rs` in `ogar-vocab::ports` (small, -same review path as #234). +**⚠ NOT done in W1: the app-prefix mint.** It is moved to § Remaining — +operator mints, below. Codex flagged the contradiction on #238 (a wave marked +DONE while its own section named unfinished work), and it was right to: the +risk it named — someone reading "done" and hardcoding an unallocated prefix — +was **already instantiated**. `blockly-abi`'s Klickwege tests used `0x1000`, +which `ogar-vocab::ports` RESERVES for the V3-adoption monitor marker, with a +test asserting it "must never be allocatable as a port's `APP_PREFIX`". + +Fixed in `blockly-rs` `e53aefe`: the test constant is now an obviously-unreal +`0xFF00` so it cannot be mistaken for the answer. No library code was +affected — `app_prefix` is a **parameter** on every public function in +`klickweg`, and nothing in the crate names a prefix. That parameterisation is +what kept a documentation contradiction from becoming a stored collision. ### W2 — Klickwege wiring into a2ui-rs — **DONE** @@ -441,6 +452,23 @@ Widths measured against the Apache-2.0 definitions: largest set is 8 (`math_on_list`), so the byte is not a squeeze. `math_on_list[RANDOM]` is absent from its table by construction — it is one of the three gaps. +## Remaining — operator mints (the only open work) + +Every wave's code is shipped and every gate is green. What is left is three +decisions that are the operator's by standing rule (*"codebook ids are +permanent; a mint is an operator decision with a ledger entry, never a +drive-by"*), so none has been taken here. + +| # | mint | blocks | notes | +|---|---|---|---| +| **M1** | `blockly-rs` app prefix in `ogar-vocab::ports` | nothing today; W2 address work if a second consumer appears | Allocated so far: `0x0000` core, `0x0001`–`0x0005`, `0x0007`. `0x0006` and `0x0008+` look free. **`0x1000` is RESERVED** (V3-adoption monitor) and must never be chosen. No value is proposed here. | +| **M2** | the three codebook gaps — `math_on_list[RANDOM]`, `lists_reverse`, `lists_getIndex[GET_REMOVE]` | W4 coverage of those blocks | Each resolves to `None` today and the cast refuses. `lists_getIndex[GET_REMOVE]` is compound (read AND delete), so it is a design question, not just an id. | +| **M3** | the constant-pool facet concepts | wide literals in stored data | Proposed `0x1703` `ConstantPool` / `0x1704` `ConstF64` / `0x1705` `ConstUtf8Inline`. The pool is opt-in in code until this lands, with deliberately invalid placeholder classids, so no placeholder can reach stored data. | + +None of the three blocks any shipped falsifier: M1 because the prefix is a +parameter, M2 because the gaps refuse rather than guess, M3 because the pool is +opt-in. + ## Falsifiers (each must be able to FAIL) 1. **W1** — drag = 0 writes, operand change = 1 write.