behavior-contracts 0.8.5 → 0.8.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/dist/authoring.d.ts.map +1 -1
  2. package/dist/authoring.js +25 -1
  3. package/dist/authoring.js.map +1 -1
  4. package/dist/behavior.d.ts +23 -3
  5. package/dist/behavior.d.ts.map +1 -1
  6. package/dist/behavior.js +209 -11
  7. package/dist/behavior.js.map +1 -1
  8. package/dist/builder.d.ts.map +1 -1
  9. package/dist/builder.js +12 -1
  10. package/dist/builder.js.map +1 -1
  11. package/dist/expr-eval.d.ts +7 -1
  12. package/dist/expr-eval.d.ts.map +1 -1
  13. package/dist/expr-eval.js +8 -1
  14. package/dist/expr-eval.js.map +1 -1
  15. package/dist/generator/core.d.ts +28 -0
  16. package/dist/generator/core.d.ts.map +1 -1
  17. package/dist/generator/core.js +1 -0
  18. package/dist/generator/core.js.map +1 -1
  19. package/dist/generator/emit-shared-debox.d.ts +108 -0
  20. package/dist/generator/emit-shared-debox.d.ts.map +1 -0
  21. package/dist/generator/emit-shared-debox.js +117 -0
  22. package/dist/generator/emit-shared-debox.js.map +1 -0
  23. package/dist/generator/emit-typed-native-go.d.ts.map +1 -1
  24. package/dist/generator/emit-typed-native-go.js +923 -494
  25. package/dist/generator/emit-typed-native-go.js.map +1 -1
  26. package/dist/generator/emit-typed-native-rust.d.ts +9 -6
  27. package/dist/generator/emit-typed-native-rust.d.ts.map +1 -1
  28. package/dist/generator/emit-typed-native-rust.js +950 -505
  29. package/dist/generator/emit-typed-native-rust.js.map +1 -1
  30. package/dist/guard.d.ts.map +1 -1
  31. package/dist/guard.js +12 -0
  32. package/dist/guard.js.map +1 -1
  33. package/dist/index.d.ts +4 -4
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +4 -4
  36. package/dist/index.js.map +1 -1
  37. package/dist/plan.d.ts +43 -2
  38. package/dist/plan.d.ts.map +1 -1
  39. package/dist/plan.js +19 -5
  40. package/dist/plan.js.map +1 -1
  41. package/package.json +2 -2
@@ -5,7 +5,20 @@ import { goTypedInternals } from "./emit-shared-go-typed.js";
5
5
  import { buildTypePlan, deriveTypeRef, findDecl, inputPortIsOptional, inputPortTypeRef } from "./typed.js";
6
6
  import { concurrencyPlan, execPlan, analyzeSequentialOps, classifyExpr, buildComponentPlan, } from "./straightline.js";
7
7
  import { NativeExprCompiler } from "./native-expr.js";
8
+ import { buildDeBoxPlan, notationOfRef } from "./emit-shared-debox.js";
8
9
  const PKG = "dslcontracts";
10
+ /** resolveGoWire — the CONCRETE consumer wire type names the de-box classifies a node result against
11
+ * (bc#141). Default (no option) → the in-package convention names, unqualified. A configured `import`
12
+ * qualifies the names with the imported package's selector so a consumer can keep its wire types in its
13
+ * own package. `value`/`row`/`list` name the WireValue / WireRow / WireList concrete types; the covered
14
+ * runner + the generated Probe result structs reference them by these names. */
15
+ function resolveGoWire(ctx) {
16
+ const w = ctx.goWire;
17
+ if (w === undefined)
18
+ return { value: "WireValue", row: "WireRow", list: "WireList" };
19
+ const sel = w.import !== undefined ? `${w.import.split("/").pop().replace(/[^A-Za-z0-9_]/g, "_")}.` : "";
20
+ return { value: `${sel}${w.value}`, row: `${sel}${w.row}`, list: `${sel}${w.list}`, import: w.import };
21
+ }
9
22
  /** goErr — build the LOCAL concrete error value for a behavior/plan failure on the covered read
10
23
  * plane (runtime-free). The covered module declares `type BehaviorError struct { Code, Message string }`
11
24
  * (see emitBehaviorErrorType); a `*BehaviorError` satisfies the runner's `error` return. This REPLACES
@@ -15,14 +28,34 @@ function goErr(code, msgExpr) {
15
28
  return `&BehaviorError{Code: ${JSON.stringify(code)}, Message: ${msgExpr}}`;
16
29
  }
17
30
  /** emitBehaviorErrorType — the LOCAL concrete error type baked into the covered module so a runner can
18
- * fail closed with ZERO bc-runtime import. `*BehaviorError` satisfies `error`. */
31
+ * fail closed with ZERO bc-runtime import. `*BehaviorError` satisfies `error` and carries the leaf's
32
+ * structured Error Value (scp-error.md "The Error Value"). */
19
33
  function emitBehaviorErrorType() {
20
- return `// BehaviorError — the LOCAL concrete failure type for the covered read plane (runtime-free): a
34
+ return `// ErrorDetail — the structured, recoverable payload a failure carries (scp-error.md "The Error
35
+ // Value"). The LEAF produces it at the wire boundary (the only party holding both the declared type
36
+ // and the raw wire datum); the runner transports it verbatim. Concrete fields — no boxed Value, no
37
+ // serialized blob in a string: ExpectedType is Portable Type Notation, a rendering of a STATICALLY
38
+ // DECLARED type, so nothing walks a type at runtime. Kind is the closed set (typeMismatch /
39
+ // missingField / overflow), empty when the failure is not about a datum.
40
+ type ErrorDetail struct {
41
+ Kind string
42
+ Model string
43
+ Field string
44
+ ExpectedType string
45
+ ActualWireType string
46
+ RawValue string
47
+ Context map[string]string
48
+ }
49
+
50
+ // BehaviorError — the LOCAL concrete failure type for the covered read plane (runtime-free): a
21
51
  // covered runner returns *BehaviorError (which satisfies error) instead of a bc-runtime failure, so
22
52
  // the fully-covered module imports ZERO bc runtime. Codes match run_behavior verbatim (byte-equal).
53
+ // Detail carries the leaf's structured Error Value across the seam so a caller can log / skip /
54
+ // repair / migrate rather than parse a message (nil when the failure is not about a datum).
23
55
  type BehaviorError struct {
24
56
  Code string
25
57
  Message string
58
+ Detail *ErrorDetail
26
59
  }
27
60
 
28
61
  func (e *BehaviorError) Error() string { return e.Code + ": " + e.Message }
@@ -30,7 +63,48 @@ func (e *BehaviorError) Error() string { return e.Code + ": " + e.Message }
30
63
  // FailureCode exposes the stable failure code (byte-equal to run_behavior) WITHOUT a bc-runtime type:
31
64
  // a caller that wants the code type-asserts the small local interface { FailureCode() string } rather
32
65
  // than a bc failure type — keeping this module runtime-free while a harness can still read the code.
33
- func (e *BehaviorError) FailureCode() string { return e.Code }`;
66
+ func (e *BehaviorError) FailureCode() string { return e.Code }
67
+
68
+ // unknownComponent — the fail-closed failure for a catalog name with no handler (byte-equal to
69
+ // run_behavior). In the STATIC codegen model a missing impl is normally a compile error; this covers
70
+ // the scripted test glue's dynamic queue miss.
71
+ func unknownComponent(component string) *BehaviorError {
72
+ return &BehaviorError{Code: "UNKNOWN_COMPONENT", Message: "component '" + component + "' has no handler (fail-closed)"}
73
+ }
74
+
75
+ // leafFailure — a failure the LEAF reports (test glue): the runner assigns the node's failure code
76
+ // from its Error Policy Kind, so what a leaf carries is its message and its structured Error Value.
77
+ func leafFailure(message string) *BehaviorError {
78
+ return &BehaviorError{Code: "LEAF_FAILURE", Message: message}
79
+ }
80
+
81
+ // opFailed wraps a leaf failure as the node's Failure under its Error Policy Kind, carrying the
82
+ // leaf's structured detail through unchanged (the runner transports the Error Value, it does not
83
+ // synthesize or re-interpret it).
84
+ func opFailed(node string, policy string, e error) *BehaviorError {
85
+ if be, ok := e.(*BehaviorError); ok {
86
+ return &BehaviorError{Code: "OP_FAILED", Message: "operation '" + node + "' failed under '" + policy + "' policy: " + be.Message, Detail: be.Detail}
87
+ }
88
+ return &BehaviorError{Code: "OP_FAILED", Message: "operation '" + node + "' failed under '" + policy + "' policy: " + e.Error()}
89
+ }
90
+
91
+ // deTypeMismatch / deMissingField / deOverflow — the de-box mismatch failures the generated decode
92
+ // produces (scp-error.md "The Error Value"). Codes are byte-equal to the runtime outType check
93
+ // (TYPE_MISMATCH / MISSING_PROP) so the covered read stays equivalent to run_behavior; detail.kind
94
+ // distinguishes typeMismatch / missingField / overflow. ErrorDetail is built by a POSITIONAL literal so
95
+ // the offending value is carried without naming the field (the covered module stays whole-file free of
96
+ // the boxed result-type token; its only permitted occurrence is the ErrorDetail field declaration).
97
+ func deTypeMismatch(model, field, expected, actualWire, raw string) *BehaviorError {
98
+ return &BehaviorError{Code: "TYPE_MISMATCH", Message: "node '" + model + "': " + field + ": expected " + expected + ", got " + actualWire, Detail: &ErrorDetail{"typeMismatch", model, field, expected, actualWire, raw, nil}}
99
+ }
100
+
101
+ func deMissingField(model, field, expected string) *BehaviorError {
102
+ return &BehaviorError{Code: "MISSING_PROP", Message: "node '" + model + "': " + field + ": required field is absent", Detail: &ErrorDetail{"missingField", model, field, expected, "", "", nil}}
103
+ }
104
+
105
+ func deOverflow(model, field, expected, actualWire, raw string) *BehaviorError {
106
+ return &BehaviorError{Code: "TYPE_MISMATCH", Message: "node '" + model + "': " + field + ": " + raw + " is outside the range of " + expected, Detail: &ErrorDetail{"overflow", model, field, expected, actualWire, raw, nil}}
107
+ }`;
34
108
  }
35
109
  const { sanitize, emitExpr } = goStraightlineInternals;
36
110
  const { goFieldName, renderTypeRef, goScalar, serializeTyped, emitTypeDecls, emitSerializers, emitTypedValue, emitMustHelpers, } = goTypedInternals;
@@ -39,6 +113,241 @@ const { goFieldName, renderTypeRef, goScalar, serializeTyped, emitTypeDecls, emi
39
113
  function typedLocal(nodeId) {
40
114
  return `t_${nodeId.replace(/[^A-Za-z0-9_]/g, "_")}`;
41
115
  }
116
+ // ── strict de-box: the ONE inline renderer over the SSoT plan ─────────────────────────────
117
+ //
118
+ // A covered node result de-box is compiled INLINE at its consumption point from the language-neutral
119
+ // SSoT walk (emit-shared-debox.ts `buildDeBoxPlan`). The handler returns the consumer's WIRE (a single
120
+ // concrete WireValue — the producer's datum); the generated de-box classifies it against the STATICALLY
121
+ // declared node type via the CONCRETE seam methods (WireValue.As* → the top; WireRow.Probe* → a record
122
+ // field / map value; WireList.Elem* → a list element). The consumer supplies variant classification (a
123
+ // probe reports the producer's own wire tag as a free string); the generated code owns strictness —
124
+ // required/optional, present/absent, error assembly, fail-closed. NO decodeWire_* helper, NO output-side
125
+ // recursion, NO interface, NO generics, NO boxed Value: a nested record expands to a nested struct
126
+ // build, a collection to a nested loop, and every method call resolves to a concrete consumer type.
127
+ /** goTypeOfPlan — the CONCRETE Go type of a de-box plan node (base kind + `optDepth` `*` wraps). Equal to
128
+ * renderTypeRef of the ORIGINAL ref, but read off the language-neutral plan so the inline renderer can
129
+ * declare its temps / list-element / map-value locals without re-deriving the TypeRef. */
130
+ function goTypeOfPlan(plan) {
131
+ let base;
132
+ switch (plan.k) {
133
+ case "scalar":
134
+ base = goScalar(plan.scalar);
135
+ break;
136
+ case "record":
137
+ base = plan.typeName;
138
+ break;
139
+ case "list":
140
+ base = `[]${goTypeOfPlan(plan.elem)}`;
141
+ break;
142
+ case "map":
143
+ base = `map[string]${goTypeOfPlan(plan.value)}`;
144
+ break;
145
+ }
146
+ return "*".repeat(plan.optDepth) + base;
147
+ }
148
+ /**
149
+ * renderGoDeBox — the ONE inline, recursive strict de-box renderer (Go). Folds a language-neutral
150
+ * {@link DeBoxPlan} (the SSoT walk) into FLAT Go STATEMENTS that decode the wire obtained by probing
151
+ * `srcExpr` per the node's access (top → `As*()`, field → `Probe*(key)`, elem → `Elem*(i)`, mapval →
152
+ * `Probe*(k)`) and assign the decoded native value into the lvalue `target`. The recursion runs HERE at
153
+ * generation time (walking the plan), so the emitted Go is fully unrolled: NO `decodeWire_*` helper, NO
154
+ * output-side recursion — a nested record expands to a nested struct build, a nested collection to a
155
+ * nested loop. A required-position mismatch runs `fail(...)` (default: `return <zero>, de_*(...)`); an
156
+ * optional position (optDepth>0) leaves the target pointer nil on absent/null. `ctr` hands out unique
157
+ * temp suffixes so nested blocks never collide; `idxExpr` is the element index / map-key argument the
158
+ * enclosing list / map loop supplies (empty for top / field). Mirrors the interpreter's outType check,
159
+ * but TOTAL over the grammar (opt(named)/arr(arr)/map(opt)…) and inlined — the type is baked all the way
160
+ * through by the TS compiler, and every seam call resolves to a CONCRETE consumer type (no dispatch).
161
+ */
162
+ function renderGoDeBox(plan, target, srcExpr, ctr, indent, fail, idxExpr = "") {
163
+ const I = indent;
164
+ const I1 = indent + "\t";
165
+ const I2 = indent + "\t\t";
166
+ const id = ctr.n++;
167
+ const ml = JSON.stringify(plan.model);
168
+ const fl = JSON.stringify(plan.field);
169
+ const nl = JSON.stringify(plan.expectedType);
170
+ const opt = plan.optDepth > 0;
171
+ const pv = `p${id}`;
172
+ const kindSuffix = plan.k === "list" ? "List" : plan.k === "scalar" ? (plan.scalar === "string" ? "String" : plan.scalar === "bool" ? "Bool" : "Number") : "Row";
173
+ // the probe call for this node's access (top → As*, field/mapval → Probe*(key), elem → Elem*(i)).
174
+ const probeCall = (() => {
175
+ switch (plan.access.from) {
176
+ case "top":
177
+ return `${srcExpr}.As${kindSuffix}()`;
178
+ case "field":
179
+ return `${srcExpr}.Probe${kindSuffix}(${JSON.stringify(plan.access.key)})`;
180
+ case "elem":
181
+ return `${srcExpr}.Elem${kindSuffix}(${idxExpr})`;
182
+ case "mapval":
183
+ return `${srcExpr}.Probe${kindSuffix}(${idxExpr})`;
184
+ }
185
+ })();
186
+ const mismatch = fail(`deTypeMismatch(${ml}, ${fl}, ${nl}, ${pv}.ActualWireType, ${pv}.Raw)`);
187
+ const missing = fail(`deMissingField(${ml}, ${fl}, ${nl})`);
188
+ // wrap a decoded BASE-typed temp in optDepth `&` layers into target (present ⇒ &^N; absent/null ⇒ nil).
189
+ const wrapAssign = (baseTemp) => {
190
+ if (plan.optDepth === 0)
191
+ return [`${I1}${target} = ${baseTemp}`];
192
+ const out = [];
193
+ let cur = baseTemp;
194
+ for (let i = 0; i < plan.optDepth; i++) {
195
+ if (i === plan.optDepth - 1)
196
+ out.push(`${I1}${target} = &${cur}`);
197
+ else {
198
+ const w = `w${id}_${i}`;
199
+ out.push(`${I1}${w} := &${cur}`);
200
+ cur = w;
201
+ }
202
+ }
203
+ return out;
204
+ };
205
+ const lines = [];
206
+ lines.push(`${I}${pv} := ${probeCall}`);
207
+ lines.push(`${I}if ${pv}.Kind == probeGot {`);
208
+ if (plan.k === "scalar") {
209
+ if (plan.scalar === "string" || plan.scalar === "bool") {
210
+ if (plan.optDepth === 0)
211
+ lines.push(`${I1}${target} = ${pv}.Got`);
212
+ else {
213
+ lines.push(`${I1}sv${id} := ${pv}.Got`);
214
+ for (const l of wrapAssign(`sv${id}`))
215
+ lines.push(l);
216
+ }
217
+ }
218
+ else {
219
+ // int/float: parse + range-check here (BC owns overflow) — no panic, a structured overflow error.
220
+ const parse = plan.scalar === "int" ? `strconv.ParseInt(${pv}.Got, 10, 64)` : `strconv.ParseFloat(${pv}.Got, 64)`;
221
+ lines.push(`${I1}n${id}, nErr${id} := ${parse}`);
222
+ lines.push(`${I1}if nErr${id} != nil {`);
223
+ lines.push(`${I2}${fail(`deOverflow(${ml}, ${fl}, ${nl}, ${pv}.ActualWireType, ${pv}.Got)`)}`);
224
+ lines.push(`${I1}}`);
225
+ for (const l of wrapAssign(`n${id}`))
226
+ lines.push(l);
227
+ }
228
+ }
229
+ else if (plan.k === "record") {
230
+ lines.push(`${I1}var rec${id} ${plan.typeName}`);
231
+ for (const f of plan.fields)
232
+ lines.push(renderGoDeBox(f.plan, `rec${id}.${goFieldName(f.name)}`, `${pv}.Got`, ctr, I1, fail));
233
+ for (const l of wrapAssign(`rec${id}`))
234
+ lines.push(l);
235
+ }
236
+ else if (plan.k === "list") {
237
+ const elemGo = goTypeOfPlan(plan.elem);
238
+ lines.push(`${I1}list${id} := make([]${elemGo}, 0, ${pv}.Got.Len())`);
239
+ lines.push(`${I1}for i${id} := 0; i${id} < ${pv}.Got.Len(); i${id}++ {`);
240
+ lines.push(`${I2}var el${id} ${elemGo}`);
241
+ lines.push(renderGoDeBox(plan.elem, `el${id}`, `${pv}.Got`, ctr, I2, fail, `i${id}`));
242
+ lines.push(`${I2}list${id} = append(list${id}, el${id})`);
243
+ lines.push(`${I1}}`);
244
+ for (const l of wrapAssign(`list${id}`))
245
+ lines.push(l);
246
+ }
247
+ else {
248
+ // map(V): the wire value is a producer map; enumerate its keys, decode each value as V.
249
+ const valGo = goTypeOfPlan(plan.value);
250
+ lines.push(`${I1}m${id} := make(map[string]${valGo})`);
251
+ lines.push(`${I1}for _, k${id} := range ${pv}.Got.Keys() {`);
252
+ lines.push(`${I2}var mv${id} ${valGo}`);
253
+ lines.push(renderGoDeBox(plan.value, `mv${id}`, `${pv}.Got`, ctr, I2, fail, `k${id}`));
254
+ lines.push(`${I2}m${id}[k${id}] = mv${id}`);
255
+ lines.push(`${I1}}`);
256
+ for (const l of wrapAssign(`m${id}`))
257
+ lines.push(l);
258
+ }
259
+ // WRONG → mismatch (required + optional). Required: NULL → mismatch, ABSENT → missing. Optional:
260
+ // NULL / ABSENT leave the target pointer nil (no statement — the zero value IS None).
261
+ lines.push(`${I}} else if ${pv}.Kind == probeWrong {`);
262
+ lines.push(`${I1}${mismatch}`);
263
+ if (!opt) {
264
+ lines.push(`${I}} else if ${pv}.Kind == probeNull {`);
265
+ lines.push(`${I1}${mismatch}`);
266
+ lines.push(`${I}} else {`);
267
+ lines.push(`${I1}${missing}`);
268
+ }
269
+ lines.push(`${I}}`);
270
+ return lines.join("\n");
271
+ }
272
+ /** returnZeroFail — the default de-box mismatch control flow: return the runner's zero output + the
273
+ * structured Error Value (fail/retry componentRef results + map/fanout element de-box, which fails closed
274
+ * regardless of elementPolicy — matching the interpreter's whole-node outType throw). */
275
+ const returnZeroFail = (zeroOut) => (errValueExpr) => `return ${zeroOut}, ${errValueExpr}`;
276
+ /** renderResultDeBox — inline strict de-box of a whole result WIRE (`srcExpr`, a WireValue) into the
277
+ * lvalue `target`, from the TOTAL SSoT plan for `ref` at the top position. `fail` selects the mismatch
278
+ * control flow. This is the ONE de-box call the runner arms share (componentRef result + map/fanout
279
+ * element) — no per-position predicate, no helper decode fn. `ctr` hands out unique temp suffixes: the
280
+ * sequential runner threads ONE counter across all its at-fn-scope nodes so siblings never collide;
281
+ * loop/if-scoped arms (map / fanout / parallel commit) may pass a fresh one. */
282
+ function renderResultDeBox(ref, target, srcExpr, nodeId, plan, indent, fail, ctr = { n: 0 }) {
283
+ return renderGoDeBox(buildDeBoxPlan(ref, { from: "top" }, { model: nodeId, field: nodeId, plan }), target, srcExpr, ctr, indent, fail);
284
+ }
285
+ /** emitProbeWireTypes — the once-per-module probe kind consts + probe RESULT structs. A NumberProbe's
286
+ * Got is the raw numeric text (the de-box parses + range-checks it, so overflow is BC's to detect); every
287
+ * probe carries ActualWireType even on a match so overflow can report the numeric wire tag. Raw is the
288
+ * offending value stringified (NOT named RawValue — that token is reserved for the ErrorDetail field
289
+ * decl). Concrete data structs — no boxed Value on the seam. bc does NOT declare the WireValue/WireRow/
290
+ * WireList seam types (that would own the wire format, and a Go interface is vtable dynamic dispatch); it
291
+ * REFERENCES the consumer's CONCRETE types by name (RowProbe.Got is a `${wire.row}`, ListProbe.Got a
292
+ * `${wire.list}`) and the runner calls their methods directly (zero dynamic dispatch, zero dictionary). */
293
+ function emitProbeWireTypes(wire) {
294
+ return `// Probe kinds — the outcome of classifying one wire attribute against a declared field type.
295
+ const (
296
+ probeGot uint8 = 0 // present; the wire variant matches the declared type
297
+ probeWrong uint8 = 1 // present but a different wire variant (carries ActualWireType + Raw)
298
+ probeAbsent uint8 = 2 // no attribute for this field
299
+ probeNull uint8 = 3 // present as the producer's null variant (opt → None; required → mismatch)
300
+ )
301
+
302
+ // StringProbe / NumberProbe / BoolProbe — a scalar probe result. Got holds the matched value (a
303
+ // NumberProbe's Got is the raw numeric text; the de-box parses + range-checks it). ActualWireType is the
304
+ // producer's own wire tag (a free string, e.g. a DynamoDB "S"/"N"/"BOOL"); Raw is the offending value
305
+ // stringified. Concrete structs — no boxed Value crosses the seam.
306
+ type StringProbe struct {
307
+ Kind uint8
308
+ Got string
309
+ ActualWireType string
310
+ Raw string
311
+ }
312
+
313
+ type NumberProbe struct {
314
+ Kind uint8
315
+ Got string
316
+ ActualWireType string
317
+ Raw string
318
+ }
319
+
320
+ type BoolProbe struct {
321
+ Kind uint8
322
+ Got bool
323
+ ActualWireType string
324
+ Raw string
325
+ }
326
+
327
+ // RowProbe / ListProbe — a composite probe result: a nested wire map (a DynamoDB "M") or wire array (an
328
+ // "L"). Got is the consumer's CONCRETE ${wire.row} / ${wire.list} (a value type — no boxed Value, no
329
+ // interface); the inline de-box probes it directly.
330
+ type RowProbe struct {
331
+ Kind uint8
332
+ Got ${wire.row}
333
+ ActualWireType string
334
+ Raw string
335
+ }
336
+
337
+ type ListProbe struct {
338
+ Kind uint8
339
+ Got ${wire.list}
340
+ ActualWireType string
341
+ Raw string
342
+ }`;
343
+ }
344
+ /** mapFanoutElemRef — the element ROW TypeRef a map/fanout node decodes (the per-element record). */
345
+ function mapFanoutElemRef(node, typedNodes, plan) {
346
+ const ref = typedNodes.get(node.id);
347
+ if ("fanout" in node)
348
+ return fanoutItemsElemRef(node, ref, plan);
349
+ return mapElemRowRef(node, ref, plan);
350
+ }
42
351
  function goPortFieldName(wire) {
43
352
  const safe = wire.replace(/[^A-Za-z0-9_]/g, "_");
44
353
  const head = safe.charAt(0);
@@ -49,21 +358,14 @@ function portsStructName(compName, nodeId) {
49
358
  // type to implement the per-component Handler_<comp> interface's concrete Node_* method signatures.
50
359
  return `PortsNR_${sanitize(compName)}_${nodeId.replace(/[^A-Za-z0-9_]/g, "_")}`;
51
360
  }
52
- /**
53
- * portFieldGoType — the NATIVE Go type for a covered port field (bc#90 / runtime-free), read off the ONE
54
- * port lowering (compilePortNode / native-expr). The covered ports struct carries NO `dslcontracts.Value`
55
- * field: every port lowers to a concrete Go type, and a port that cannot lower FAILS CLOSED. The field
56
- * type is the compiled expression's TypeRef, so it can never disagree with the initializer (emitPortInit),
57
- * which is the SAME lowering read for its value. `where` names the emission site for the error.
58
- */
59
- function portFieldGoType(node, comp, plan, typedNodes, where = "port", opts) {
60
- const c = compilePortNode(node, comp, plan, typedNodes, (h) => typedNodes.has(h), where, opts);
61
- return c.renderedType ?? renderTypeRef(c.ref);
361
+ /** scalar TypeRef → PortFieldType (native go scalar kind). */
362
+ function goScalarPortField(scalar) {
363
+ return scalar === "string" ? "string" : scalar === "int" ? "int64" : scalar === "float" ? "float64" : "bool";
62
364
  }
63
365
  /**
64
366
  * goNativeResolveHead — the ONE ref-head resolver the runtime-free native-expr compiler reads, for cond
65
- * `if`/branches, a map/fanout `when` guard, AND a port (go twin Go's value semantics need no ownership
66
- * ceremony). A head is a `$as` element binding, a prior body node's typed local, or an input port.
367
+ * `if`/branches, a map/fanout `when` guard, AND a port. A head is a `$as` element binding, a prior body
368
+ * node's typed local, or an input port.
67
369
  */
68
370
  function goNativeResolveHead(comp, plan, typedNodes, isPrior, opts) {
69
371
  return (head) => {
@@ -78,18 +380,14 @@ function goNativeResolveHead(comp, plan, typedNodes, isPrior, opts) {
78
380
  /**
79
381
  * compilePortNode — lower a port expression to its native ports-struct field via the runtime-free
80
382
  * native-expr compiler (native-expr.ts): the ONE port lowering. The port's field TYPE is the compiled
81
- * `ref`, its initializer the compiled `expr` — so the two are derived from a SINGLE lowering and can
82
- * never disagree. Any closed operator, a literal, a ref (input scalar/array/map, prior-node scalar/array,
83
- * `$as` element field), a concat, a static array, an optional-input read — all resolve here. A port that
84
- * cannot lower FAILS CLOSED loudly. A lowering that is FALLIBLE (hoists statements) is a covered SHAPE
85
- * returned here as-is; only the value site (emitPortInit) rejects it, since a ports-struct literal cannot
86
- * host statements — the coverage gate/type site must still see it as covered.
383
+ * `ref`, its initializer the compiled `expr` — derived from a SINGLE lowering, so they cannot disagree.
384
+ * Any closed operator, a literal, a ref (input scalar/array/map, prior-node scalar/array, `$as` element
385
+ * field), a concat, a static array, an optional-input read — all resolve here. A port that cannot lower
386
+ * FAILS CLOSED. A FALLIBLE lowering (hoists statements) is returned as-is; only emitPortInit rejects it.
87
387
  */
88
388
  function compilePortNode(node, comp, plan, typedNodes, isPrior, where, opts) {
89
389
  const resolveHead = goNativeResolveHead(comp, plan, typedNodes, isPrior, opts);
90
390
  try {
91
- // zeroOut is unreachable: a fallible port is rejected at emitPortInit (a ports-struct literal hosts no
92
- // statements), so the backend never renders an early-return with it.
93
391
  return new NativeExprCompiler(makeGoExprBackend("", resolveHead, plan)).compilePort(node);
94
392
  }
95
393
  catch (e) {
@@ -98,8 +396,73 @@ function compilePortNode(node, comp, plan, typedNodes, isPrior, where, opts) {
98
396
  throw new GeneratorFailure("UNSUPPORTED_NODE_STRAIGHTLINE", `go typed-native (runtime-free): ${where} ${JSON.stringify(node)} does not lower to a native Go value: ${e.message}`);
99
397
  }
100
398
  }
101
- /** the prior-node typed-local TypeRef map for a component (control-gate nodes are typeless; a map node
102
- * carries its produced-array ref). Shared by coverage, the struct surface, and the runner. */
399
+ /**
400
+ * portFieldGoType the NATIVE Go type for a covered port field (bc#90 / runtime-free). The covered
401
+ * ports struct carries NO `dslcontracts.Value` field: every port lowers to a concrete Go type.
402
+ * - a static string-array (projection) → `[]string` (change #2);
403
+ * - a bare int/float number literal (e.g. Query `limit: 20`) → `int64`/`float64`;
404
+ * - a string/bool/scalar-input port → the scalar Go type (inferPortType).
405
+ * A port that would otherwise infer to the boxed `Value` (a dynamic operator, a non-static array, a
406
+ * Value-typed input) FAILS CLOSED — the covered read plane admits NO boxed escape (there is no
407
+ * `dslcontracts.Value` fallback on the native module). `where` names the emission site for the error.
408
+ */
409
+ function portFieldGoType(node, comp, plan, typedNodes, where = "port", opts) {
410
+ const c = compilePortNode(node, comp, plan, typedNodes, (h) => typedNodes.has(h), where, opts);
411
+ return c.renderedType ?? renderTypeRef(c.ref);
412
+ }
413
+ function staticArrElems(node) {
414
+ if (node === null || typeof node !== "object" || Array.isArray(node))
415
+ return null;
416
+ const keys = Object.keys(node);
417
+ if (keys.length !== 1 || keys[0] !== "arr")
418
+ return null;
419
+ const arg = node.arr;
420
+ return Array.isArray(arg) ? arg : null;
421
+ }
422
+ /**
423
+ * isNativeRawComponent — a component covered by the combined read emitter. Strictly sequential
424
+ * (no real concurrency), body is ALL componentRef (no map/cond), default 'fail'/'continue'
425
+ * policy, relationKind is single/connection or absent, and EVERY node carries outType + the
426
+ * component carries outputType. relationKind:single / bindField ARE the point — inline exec makes
427
+ * the child-present decision from the real parent result. Ports AND the output are evaluated via
428
+ * the primitive SSoT over a Value-space portScope (so a dynamic operator in a port or an
429
+ * obj-assembly output is fine — the SSoT handles fail-closed semantics; only the RESULT plane +
430
+ * PORT container go native). A covered SHAPE that lacks outType is a fail-closed error raised at
431
+ * emit (see assertTypedCoverage) — never a silent re-box.
432
+ */
433
+ /**
434
+ * numLiteralGoExpr — a BARE numeric port literal (JSON number, e.g. the `limit: 20` Query port).
435
+ * `classifyExpr` deliberately leaves a bare number `dynamic` (its checked int/float range rules are
436
+ * the evaluate SSoT, expr.go evalNumberLiteral), so a plain number never reaches the str/bool/null
437
+ * static branch. But a number literal IS statically resolvable to a native Go Value: the SAME
438
+ * range check the runtime applies is performed HERE at generation time (integral literal in the
439
+ * safe 2^53-1 window → int64; a fractional/exponent literal → the checked float64). Returns the Go
440
+ * expression for the literal's Value, or null when the node is not a bare number OR the integral
441
+ * literal is out of the safe window (that out-of-range case keeps the runtime INVALID_LITERAL
442
+ * semantics — the port is NOT covered natively, so the whole component falls through, never a
443
+ * silently-wrong inline). Mirrors coderuntime.evalNumberLiteral exactly.
444
+ */
445
+ const NUM_SAFE_INT = 9007199254740991; // 2^53 - 1 (expr.go safeInt)
446
+ function numLiteralGoExpr(node) {
447
+ if (typeof node !== "number" || !Number.isFinite(node))
448
+ return null;
449
+ if (Number.isInteger(node)) {
450
+ if (node < -NUM_SAFE_INT || node > NUM_SAFE_INT)
451
+ return null; // out of safe window → not covered (runtime INVALID_LITERAL)
452
+ return `${PKG}.Value(int64(${node}))`;
453
+ }
454
+ // fractional / exponent literal → checked float64 (finite, already guaranteed above).
455
+ return `${PKG}.Value(float64(${node}))`;
456
+ }
457
+ /** A port node is static (literal / ref / concat of those / static arr / bare number literal) —
458
+ * resolvable to a native Go Value with no NewObj / no evaluate interpreter. */
459
+ /** planForComp — a TypePlan for a single component (its named types are component-scoped, so a
460
+ * one-component IR yields the same plan the full-IR build would for this component). */
461
+ function planForComp(comp) {
462
+ return buildTypePlan({ irVersion: 1, exprVersion: 2, components: [comp] });
463
+ }
464
+ /** buildTypedNodes — the prior-node typed-local TypeRef map for a component (control-gate nodes are
465
+ * typeless; a map node carries its produced-array ref). An untyped node is omitted (not an error here). */
103
466
  function buildTypedNodes(comp, plan) {
104
467
  const typedNodes = new Map();
105
468
  for (const n of comp.body) {
@@ -114,25 +477,26 @@ function buildTypedNodes(comp, plan) {
114
477
  }
115
478
  return typedNodes;
116
479
  }
117
- function staticArrElems(node) {
118
- if (node === null || typeof node !== "object" || Array.isArray(node))
119
- return null;
120
- const keys = Object.keys(node);
121
- if (keys.length !== 1 || keys[0] !== "arr")
122
- return null;
123
- const arg = node.arr;
124
- return Array.isArray(arg) ? arg : null;
480
+ /** mapAsBinding — the `$as` element binding for a map/fanout node's element ports (the over-element
481
+ * TypeRef), or undefined when the over source does not resolve (an uncovered map). */
482
+ function mapAsBinding(comp, node, typedNodes, plan) {
483
+ try {
484
+ return { name: node.map.as, ref: mapOverElemInfo(comp, node, typedNodes, plan).elemRef, plan };
485
+ }
486
+ catch {
487
+ return undefined;
488
+ }
125
489
  }
126
- /** Does a port lower to a native value? The ELIGIBILITY gate ASKS the one port lowering (compilePortNode
127
- * / native-expr) whether it can lower this port, rather than re-deriving the shape rules — so the gate
128
- * and the emitter can never disagree. A port that does not lower (or lowers fallibly) fails closed. */
490
+ /** portIsStatic the ELIGIBILITY gate ASKS the one port lowering (compilePortNode / native-expr)
491
+ * whether it can lower this port, rather than re-deriving the shape rules — so the gate and the emitter
492
+ * can never disagree. A port that does not lower (or lowers fallibly) fails closed. */
129
493
  function portIsStatic(node, comp, plan, typedNodes, asBinding) {
130
494
  try {
131
495
  compilePortNode(node, comp, plan, typedNodes, (h) => typedNodes.has(h), "port", asBinding !== undefined ? { asBinding, asBase: asBinding.name } : undefined);
132
496
  return true;
133
497
  }
134
498
  catch {
135
- return false; // an eligibility probe: any lowering failure ⇒ not native-covered here.
499
+ return false;
136
500
  }
137
501
  }
138
502
  /** The output must lower to a typed struct/value with no Value-scope read: a ref whose head is a
@@ -180,8 +544,8 @@ function isFullyTyped(comp) {
180
544
  }
181
545
  return true;
182
546
  }
183
- function isNativeRaw(comp, plan) {
184
- return isSequentialComponentRefRead(comp, plan) && isFullyTyped(comp);
547
+ function isNativeRaw(comp) {
548
+ return isSequentialComponentRefRead(comp) && isFullyTyped(comp);
185
549
  }
186
550
  /**
187
551
  * coveredMapNode — the #86 part 2 covered map shape (nestedBatchGet). A BATCHED `map` node whose:
@@ -196,7 +560,7 @@ function isNativeRaw(comp, plan) {
196
560
  * element struct by copying the over element's typed fields + materializing the into field from the
197
561
  * aligned batch raw — NO boxed Value on any plane.
198
562
  */
199
- function coveredMapNode(n, bodyIds, comp, plan, typedNodes) {
563
+ function coveredMapNode(n, bodyIds, comp) {
200
564
  if (n === null || typeof n !== "object" || !("map" in n))
201
565
  return false;
202
566
  const m = n.map;
@@ -213,7 +577,7 @@ function coveredMapNode(n, bodyIds, comp, plan, typedNodes) {
213
577
  // A map node must carry an outType (its ELEMENT type per the recorder SoT — authoring.ts recordMap
214
578
  // holds `node.outType = mapElem`, the PRE-array-wrap element; the emitter synthesizes the produced
215
579
  // `[]element`). A map WITHOUT outType is uncovered. Legacy hand-built IR that already carries the
216
- // `{arr:element}` produced form is ALSO accepted mapNodeArrRef normalizes both to the array ref.
580
+ // The map node's outType is the ELEMENT type; mapNodeArrRef wraps it to the produced array ref.
217
581
  const outT = n.outType;
218
582
  if (outT === undefined || typeof outT !== "object")
219
583
  return false;
@@ -233,22 +597,14 @@ function coveredMapNode(n, bodyIds, comp, plan, typedNodes) {
233
597
  return false;
234
598
  }
235
599
  const ports = m.ports;
600
+ const plan = planForComp(comp);
601
+ const typedNodes = buildTypedNodes(comp, plan);
236
602
  const asBinding = mapAsBinding(comp, n, typedNodes, plan);
237
603
  for (const p of Object.keys(ports))
238
604
  if (!portIsStatic(ports[p], comp, plan, typedNodes, asBinding))
239
605
  return false;
240
606
  return true;
241
607
  }
242
- /** the `$as` element binding for a covered map/fanout node's element ports (name + over-element TypeRef),
243
- * or undefined when the over element type does not resolve (the node is then uncovered upstream). */
244
- function mapAsBinding(comp, node, typedNodes, plan) {
245
- try {
246
- return { name: node.map.as, ref: mapOverElemInfo(comp, node, typedNodes, plan).elemRef, plan };
247
- }
248
- catch {
249
- return undefined;
250
- }
251
- }
252
608
  /**
253
609
  * coveredFanoutNode (v3) — a FANOUT node (first-class `@refs` id-list fan-out) is covered when:
254
610
  * - over is a static ref to a PRIOR BODY NODE's typed arr field OR an INPUT array port with elemType,
@@ -257,7 +613,7 @@ function mapAsBinding(comp, node, typedNodes, plan) {
257
613
  * The fanout de-boxes to: one deduped batched dispatch → first-seen dedupe (by the elem's dedupeKey
258
614
  * field) + dangling drop + implicitSource strip + connection wrap {items, cursor:nil} — NO boxed Value.
259
615
  */
260
- function coveredFanoutNode(n, bodyIds, comp, plan, typedNodes) {
616
+ function coveredFanoutNode(n, bodyIds, comp) {
261
617
  if (n === null || typeof n !== "object" || !("fanout" in n))
262
618
  return false;
263
619
  const f = n.fanout;
@@ -277,6 +633,8 @@ function coveredFanoutNode(n, bodyIds, comp, plan, typedNodes) {
277
633
  return false;
278
634
  }
279
635
  const ports = f.ports;
636
+ const plan = planForComp(comp);
637
+ const typedNodes = buildTypedNodes(comp, plan);
280
638
  let asBinding;
281
639
  try {
282
640
  asBinding = { name: f.as, ref: fanoutOverElemInfo(comp, n, typedNodes, plan).elemRef, plan };
@@ -294,8 +652,8 @@ function coveredFanoutNode(n, bodyIds, comp, plan, typedNodes) {
294
652
  * reads. A real-concurrency plan whose parallel members include a map / bindField-relation child is
295
653
  * NOT covered here (falls through) — the static-parallel orchestration covers the componentRef fan-out
296
654
  * (the GroupAccessRead shape); a parallel map/relation-child stage is a further shape (out of scope). */
297
- function isSequentialComponentRefRead(comp, plan) {
298
- return coverageRejectReason(comp, plan) === null;
655
+ function isSequentialComponentRefRead(comp) {
656
+ return coverageRejectReason(comp) === null;
299
657
  }
300
658
  /**
301
659
  * coverageRejectReason — the DIAGNOSTIC twin of isSequentialComponentRefRead (bc#86/#89). Returns
@@ -305,11 +663,12 @@ function isSequentialComponentRefRead(comp, plan) {
305
663
  * never disagree. This is the message the fail-closed dispatch surfaces per non-native component — the
306
664
  * user's signal to add native coverage, fix a genuinely-dynamic spec, or use the --ir dynamic surface.
307
665
  */
308
- function coverageRejectReason(comp, plan) {
666
+ function coverageRejectReason(comp) {
667
+ const crPlan = planForComp(comp);
668
+ const crTypedNodes = buildTypedNodes(comp, crPlan);
309
669
  const ep = execPlan(comp);
310
670
  if (ep === null)
311
671
  return "component is not a straight-line/staged sequential exec plan (no static exec plan)";
312
- const typedNodes = buildTypedNodes(comp, plan);
313
672
  // A parallel-stage member must be a componentRef node (no map) so the static parallel orchestration
314
673
  // is a clean fan-out — the bounded-concurrency error-precedence protocol is proven for that shape.
315
674
  // A map child inside a parallel stage still falls through (its per-element loop is a further shape).
@@ -354,7 +713,7 @@ function coverageRejectReason(comp, plan) {
354
713
  if ("fanout" in n) {
355
714
  // v3: a first-class fanout node (deduped connection list-in fan-out) IS covered when its over is a
356
715
  // covered ref (prior-node arr / input array with elemType) and its element ports are static.
357
- if (!coveredFanoutNode(n, bodyIds, comp, plan, typedNodes))
716
+ if (!coveredFanoutNode(n, bodyIds, comp))
358
717
  return `node '${n.id}' is a non-covered fanout shape (over an input array without elemType, or a dynamic element port)`;
359
718
  continue;
360
719
  }
@@ -362,7 +721,7 @@ function coverageRejectReason(comp, plan) {
362
721
  // #86 pt2 batched map...into / #93 shape 2/3 / #108 guarded map (no into) / map-over-input (with
363
722
  // elemType) ARE covered. A non-covered map shape (guard+into heterogeneous / over-input without
364
723
  // elemType) falls through to the loud fail-closed reject.
365
- if (!coveredMapNode(n, bodyIds, comp, plan, typedNodes))
724
+ if (!coveredMapNode(n, bodyIds, comp))
366
725
  return `node '${n.id}' is a non-covered map shape (guard+into heterogeneous, or over an input array without elemType)`;
367
726
  continue;
368
727
  }
@@ -393,7 +752,7 @@ function coverageRejectReason(comp, plan) {
393
752
  if (cr.relationKind !== undefined && cr.relationKind !== "single" && cr.relationKind !== "connection")
394
753
  return `relationKind '${cr.relationKind}' not native-covered (node '${n.id}')`;
395
754
  for (const p of Object.keys(cr.ports))
396
- if (portIsStatic(cr.ports[p], comp, plan, typedNodes) === false)
755
+ if (portIsStatic(cr.ports[p], comp, crPlan, crTypedNodes) === false)
397
756
  return `node '${n.id}' port '${p}' is not statically resolvable`;
398
757
  }
399
758
  if (!outputIsNativeLowerable(comp))
@@ -401,20 +760,25 @@ function coverageRejectReason(comp, plan) {
401
760
  return null;
402
761
  }
403
762
  /** Fail closed if a covered read SHAPE lacks the typed annotations (no silent boxed re-box). */
404
- function assertTypedCoverage(comp, plan) {
405
- if (!isSequentialComponentRefRead(comp, plan))
763
+ function assertTypedCoverage(comp) {
764
+ if (!isSequentialComponentRefRead(comp))
406
765
  return;
407
766
  if (isFullyTyped(comp))
408
767
  return;
409
768
  throw new GeneratorFailure("UNSUPPORTED_NODE_STRAIGHTLINE", `go combined read (bc#77): component '${comp.name}' is a sequential componentRef read but lacks outType/outputType on every node — the combined emitter de-boxes the RESULT into the node's outType struct, so a covered read MUST be fully typed (bc#45). Add outType annotations, or regenerate on go-straightline-native (boxed result).`);
410
769
  }
411
770
  // ── native ports struct + PortReader ──────────────────────────────────────────────────
412
- function emitPortsStruct(comp, node, plan, typedNodes, asBinding) {
771
+ function emitPortsStruct(comp, node, asBinding, plan, typedNodes) {
413
772
  const structName = portsStructName(comp.name, node.id);
414
773
  const portNames = Object.keys(node.ports);
415
774
  if (portNames.length === 0) {
416
775
  return `// ${structName} — native ports for node '${node.id}' (${node.component}); no ports.\ntype ${structName} struct{}`;
417
776
  }
777
+ // a node WITH ports always resolves through the one port lowering, which needs the type plan + the
778
+ // prior-node map (both are supplied by every caller — narrow the optional params for the fields below).
779
+ if (plan === undefined || typedNodes === undefined) {
780
+ throw new GeneratorFailure("UNSUPPORTED_NODE_STRAIGHTLINE", `go typed-native: ports struct for node '${node.id}' needs a type plan + prior-node map`);
781
+ }
418
782
  const fieldTypes = portNames.map((p) => portFieldGoType(node.ports[p], comp, plan, typedNodes, `port '${p}' of node '${node.id}'`, asBinding !== undefined ? { asBinding, asBase: asBinding.name } : undefined));
419
783
  const fieldNames = portNames.map(goPortFieldName);
420
784
  const fields = portNames
@@ -430,32 +794,11 @@ type ${structName} struct {
430
794
  ${fields}
431
795
  }`;
432
796
  }
433
- // ── CONCRETE per-node row structs + row→outType copiers (bc: full Go concreteness) ──────────
434
- //
435
- // The covered path no longer crosses a generic `RawValue`/`RawRow` on the RESULT plane. Each
436
- // covered node has a CONCRETE row struct `RawRow_<comp>_<node>` whose fields are the node's
437
- // projected outType fields (typed, flattened) plus a per-node error signal (IsError/Err). The
438
- // per-component `Handler_<comp>` interface has one method per node returning that concrete struct;
439
- // the runner reads `row.<Field>` DIRECTLY (no `.(RawRow)`, no `.(scalar)`, no `RawValue`) and copies
440
- // each field into the node's outType struct local. The `.(T)` type-assert seam that the generic
441
- // RawValue de-box carried is GONE on the covered path (the handler produces the typed fields).
442
- //
443
- // A scalar/arr/opt node (outType not a named struct) has a single `Val <typed>` payload field. A
444
- // named-struct node has one Go field per outType field (same names/types as the outType decl), so
445
- // `RawRow_<comp>_<node>` is structurally the outType struct + {IsError, Err}. The copier is then a
446
- // mechanical field-for-field assignment into the outType struct — NO dynamic value read anywhere.
447
- /** Row_<comp>_<node> — the concrete per-node handler-result row struct name. (Deliberately NOT
448
- * prefixed with the generic dynamic-accessor name — that accessor is what the concrete path removes.) */
449
- function rawRowStructName(compName, nodeId) {
450
- return `Row_${sanitize(compName)}_${nodeId.replace(/[^A-Za-z0-9_]/g, "_")}`;
451
- }
452
- /** RowElem_<comp>_<node> — the concrete per-ELEMENT row struct name for a covered map node (the
453
- * element/into row the map handler yields per over element). */
454
- function rawElemStructName(compName, nodeId) {
455
- return `RowElem_${sanitize(compName)}_${nodeId.replace(/[^A-Za-z0-9_]/g, "_")}`;
456
- }
457
- /** Handler_<comp> — the per-component concrete handler interface name (one Node_* method per node). */
458
- function handlerIfaceName(compName) {
797
+ /** handlerTypeName the per-component CONCRETE handler type name the runner takes by value (one Node_*
798
+ * method per node). It is NOT a bc-declared interface (a Go interface is vtable dynamic dispatch): the
799
+ * consumer supplies a concrete type of this name (default `Handler_<comp>`) and the runner calls
800
+ * `h.Node_*(...)` directly (zero dynamic dispatch, zero dictionary). */
801
+ function handlerTypeName(compName) {
459
802
  return `Handler_${sanitize(compName)}`;
460
803
  }
461
804
  /** RunNativeRawStruct_<comp> — the EXPORTED name of the per-component struct-returning combined runner
@@ -470,76 +813,6 @@ function runnerName(compName) {
470
813
  function nodeMethodName(compName, nodeId) {
471
814
  return `Node_${sanitize(compName)}_${nodeId.replace(/[^A-Za-z0-9_]/g, "_")}`;
472
815
  }
473
- /**
474
- * emitRawRowStructs — emit the concrete `RawRow_<comp>_<node>` struct per covered node (and, for a
475
- * covered map node, the per-element `RawElem_<comp>_<node>`). A named-struct outType flattens to one
476
- * Go field per outType field; a scalar/arr/opt node carries a single `Val` payload. Every row carries
477
- * the per-node error signal (IsError/Err) so the runner interprets policy without a boxed outcome.
478
- */
479
- function emitRawRowStructs(comp, typedNodes, plan) {
480
- const parts = [];
481
- for (const n of comp.body) {
482
- // #108: a cond node has NO handler-result row (pure Expression — no dispatch).
483
- if ("cond" in n)
484
- continue;
485
- const ref = typedNodes.get(n.id);
486
- if ("fanout" in n) {
487
- // a covered fanout node: RawElem_ is the connection items element (the per-body row the batched
488
- // handler yields, aligned to the deduped id list); the batch RawRow_ carries the aligned []RawElem_.
489
- const elemRowRef = fanoutItemsElemRef(n, ref, plan);
490
- parts.push(emitOneRowStruct(rawElemStructName(comp.name, n.id), elemRowRef, plan));
491
- parts.push(`// ${rawRowStructName(comp.name, n.id)} — the BATCH row for fanout '${n.id}': the aligned per-body rows.\n` +
492
- `type ${rawRowStructName(comp.name, n.id)} struct {\n\tIsError bool\n\tErr string\n\tRows []${rawElemStructName(comp.name, n.id)}\n}`);
493
- continue;
494
- }
495
- if ("map" in n) {
496
- // a covered map node: RawElem_<comp>_<node> is the HANDLER'S per-element return shape:
497
- // - into: the `into` field's type (the fetched item merged onto every over element);
498
- // - no-into (#93 shape 3): the element outType directly (connection-collect).
499
- // The batched Node_* returns RawRow_<comp>_<node> (aligned []RawElem_ + error); a per-element
500
- // dispatch Node_* returns RawElem_ directly (one physical call per element).
501
- const arrRef = ref;
502
- const elemRowRef = mapElemRowRef(n, arrRef, plan);
503
- parts.push(emitOneRowStruct(rawElemStructName(comp.name, n.id), elemRowRef, plan));
504
- parts.push(`// ${rawRowStructName(comp.name, n.id)} — the BATCH row for map '${n.id}': the aligned per-element rows.\n` +
505
- `type ${rawRowStructName(comp.name, n.id)} struct {\n\tIsError bool\n\tErr string\n\tRows []${rawElemStructName(comp.name, n.id)}\n}`);
506
- continue;
507
- }
508
- parts.push(emitOneRowStruct(rawRowStructName(comp.name, n.id), ref, plan));
509
- }
510
- return parts.join("\n\n");
511
- }
512
- /** Emit one concrete row struct: flatten a named outType to typed fields, else a single Val payload;
513
- * always carry IsError/Err. */
514
- function emitOneRowStruct(name, ref, plan) {
515
- const lines = [];
516
- lines.push(`// ${name} — CONCRETE handler-result row (typed fields = the node's outType; + error signal).`);
517
- lines.push(`type ${name} struct {`);
518
- lines.push(`\tIsError bool`);
519
- lines.push(`\tErr string`);
520
- if (ref.kind === "named") {
521
- const decl = findDecl(plan, ref.name);
522
- for (const f of decl.fields) {
523
- lines.push(`\t${goFieldName(f.name)} ${renderTypeRef(f.type)} // ${JSON.stringify(f.name)}`);
524
- }
525
- }
526
- else {
527
- lines.push(`\tVal ${renderTypeRef(ref)}`);
528
- }
529
- lines.push(`}`);
530
- return lines.join("\n");
531
- }
532
- /** emitRowCopy — the statements copying a concrete row's typed fields into the node's outType local
533
- * `target` (a value of Go type renderTypeRef(ref)). Named: field-for-field; scalar/arr/opt: `.Val`.
534
- * `rowExpr` is the concrete RawRow_/RawElem_ value; `indent` tabs. No dynamic read — pure assignment. */
535
- function emitRowCopy(target, rowExpr, ref, plan, indent) {
536
- const t = "\t".repeat(indent);
537
- if (ref.kind === "named") {
538
- const decl = findDecl(plan, ref.name);
539
- return decl.fields.map((f) => `${t}${target}.${goFieldName(f.name)} = ${rowExpr}.${goFieldName(f.name)}`).join("\n");
540
- }
541
- return `${t}${target} = ${rowExpr}.Val`;
542
- }
543
816
  /**
544
817
  * boundGoType (change #3 — typed `bound`) — the CONCRETE Go type of a covered node's `bound` handler
545
818
  * param (bc#90 / runtime-free). `bound` is the parent-produced key (the bindField value); it replaces
@@ -569,47 +842,6 @@ function boundGoType(node, comp, typedNodes, plan) {
569
842
  return `*${renderTypeRef(bfRef.inner)}`;
570
843
  return `*${renderTypeRef(bfRef)}`;
571
844
  }
572
- /**
573
- * emitHandlerInterface — the per-component `Handler_<comp>` interface: one concrete-typed `Node_*`
574
- * method per covered node. Each componentRef node method takes the node's native ports struct + the
575
- * typed `bound` pointer (change #3 — the produced-aware parent key; NO boxed dslcontracts.Value) and
576
- * returns its concrete row struct + a resolved bool. A covered map node's method takes the batch
577
- * ports struct (batched) or the element ports struct (per-element) and returns the batch / element
578
- * row. This is the fully-concrete, runtime-free replacement for the generic ExecNativeRaw seam.
579
- */
580
- function emitHandlerInterface(comp, typedNodes, plan) {
581
- const lines = [];
582
- lines.push(`// ${handlerIfaceName(comp.name)} — the CONCRETE per-component handler seam: one typed method`);
583
- lines.push(`// per covered node (native ports struct IN, concrete row struct OUT). No generic boxed-ports`);
584
- lines.push(`// / boxed-value / dynamic accessor crosses the covered boundary — the consumer implements each`);
585
- lines.push(`// Node_* with a decode of its own wire payload into the concrete row (see INTEGRATION.md §6).`);
586
- lines.push(`type ${handlerIfaceName(comp.name)} interface {`);
587
- for (const n of comp.body) {
588
- if ("cond" in n)
589
- continue; // #108: a cond node has no handler method (pure Expression).
590
- const method = nodeMethodName(comp.name, n.id);
591
- const bt = boundGoType(n, comp, typedNodes, plan);
592
- if ("fanout" in n) {
593
- // fanout (v3): one batched dispatch of the deduped id list. Ports = the batch struct; returns the
594
- // aligned batch row (per-body rows) + resolved. The runner applies dedupe/drop/wrap after.
595
- const portsT = `${portsStructName(comp.name, n.id)}_batch`;
596
- const retT = rawRowStructName(comp.name, n.id);
597
- lines.push(`\t${method}(ports ${portsT}, bound ${bt}) (${retT}, bool)`);
598
- continue;
599
- }
600
- if ("map" in n) {
601
- const m = n.map;
602
- const batched = m.batched === true;
603
- const portsT = batched ? `${portsStructName(comp.name, n.id)}_batch` : portsStructName(comp.name, n.id);
604
- const retT = batched ? rawRowStructName(comp.name, n.id) : rawElemStructName(comp.name, n.id);
605
- lines.push(`\t${method}(ports ${portsT}, bound ${bt}) (${retT}, bool)`);
606
- continue;
607
- }
608
- lines.push(`\t${method}(ports ${portsStructName(comp.name, n.id)}, bound ${bt}) (${rawRowStructName(comp.name, n.id)}, bool)`);
609
- }
610
- lines.push(`}`);
611
- return lines.join("\n");
612
- }
613
845
  /** mapElemMaterializerName — the augmented-element copier for a covered map...into node. */
614
846
  function mapElemMaterializerName(compName, nodeId) {
615
847
  return `materializeMapElemNR_${sanitize(compName)}_${nodeId.replace(/[^A-Za-z0-9_]/g, "_")}`;
@@ -629,7 +861,7 @@ function emitMapPortsStructs(comp, node, typedNodes, plan) {
629
861
  // struct element). A pure-static/concat port ignores the binding.
630
862
  const { elemRef } = mapOverElemInfo(comp, node, typedNodes, plan);
631
863
  const asBinding = { name: m.as, ref: elemRef, plan };
632
- const elemStruct = emitPortsStruct(comp, { id: node.id, component: m.component, ports: m.ports }, plan, typedNodes, asBinding);
864
+ const elemStruct = emitPortsStruct(comp, { id: node.id, component: m.component, ports: m.ports }, asBinding, plan, typedNodes);
633
865
  const batchStruct = `${structName}_batch`;
634
866
  return `${elemStruct}
635
867
 
@@ -647,7 +879,7 @@ function emitFanoutPortsStructs(comp, node, typedNodes, plan) {
647
879
  const structName = portsStructName(comp.name, node.id);
648
880
  const { elemRef } = fanoutOverElemInfo(comp, node, typedNodes, plan);
649
881
  const asBinding = { name: f.as, ref: elemRef, plan };
650
- const elemStruct = emitPortsStruct(comp, { id: node.id, component: f.component, ports: f.ports }, plan, typedNodes, asBinding);
882
+ const elemStruct = emitPortsStruct(comp, { id: node.id, component: f.component, ports: f.ports }, asBinding, plan, typedNodes);
651
883
  const batchStruct = `${structName}_batch`;
652
884
  return `${elemStruct}
653
885
 
@@ -693,18 +925,16 @@ function fanoutOverElemInfo(comp, node, typedNodes, plan) {
693
925
  }
694
926
  throw new GeneratorFailure("UNSUPPORTED_NODE_STRAIGHTLINE", `go fanout (v3): fanout '${node.id}' 'over' must be a ref to a prior node's typed arr field OR an input array port with a declared elemType (got ${JSON.stringify(f.over)}).`);
695
927
  }
696
- /** mapNodeArrRef — the map node's PRODUCED-ARRAY TypeRef from its `outType`, normalizing the two IR
697
- * conventions to the one array shape every downstream site assumes (`typedNodes.get(mapId)` is an arr):
698
- * - the RECORDER SoT (authoring.ts recordMap): `node.outType` is the ELEMENT (pre-array-wrap) wrap
699
- * to `{arr:element}` (the map produces `[]element`), matching type-gate's outputType wrap
700
- * (`"map" in n ? {arr:ot} : ot`). This is the declarative-authoring form (graphddb batchWrite/forEach).
701
- * - legacy hand-built IR: `node.outType` already the `{arr:element}` produced form take it as-is.
702
- * A map ELEMENT is never itself an `{arr:…}` in the covered corpus, so the discriminator is unambiguous.
703
- * Both converge to the SAME produced-array ref, so existing goldens stay byte-identical. */
928
+ /** mapNodeArrRef — the map node's PRODUCED-ARRAY TypeRef from its `outType`.
929
+ *
930
+ * A map node's `outType` is its ELEMENT type (scp-error.md "Map element type"), so the produced array
931
+ * is `{arr: element}`. This is the same reading the compiler applies when it resolves what a node
932
+ * result reference has (`type-gate`: `"map" in n ? {arr:ot} : ot`) the emitter follows that SSoT
933
+ * rather than inspecting the annotation's shape, so an element that is ITSELF an `arr` stays
934
+ * expressible (element `arr(T)` produces `arr(arr(T))`). */
704
935
  function mapNodeArrRef(node, plan) {
705
936
  const outT = node.outType;
706
- const isArr = outT !== null && typeof outT === "object" && "arr" in outT;
707
- return deriveTypeRef((isArr ? outT : { arr: outT }), plan);
937
+ return deriveTypeRef({ arr: outT }, plan);
708
938
  }
709
939
  /** mapElemRowRef — the handler's per-element RETURN shape for a covered map node: the `into` field's
710
940
  * type (into) or the element outType directly (no-into). This is the type of RawElem_<comp>_<node>. */
@@ -724,9 +954,8 @@ function mapElemRowRef(node, arrRef, plan) {
724
954
  }
725
955
  /**
726
956
  * emitMapElemMaterializers — for each covered map...into node, emit the AUGMENTED-element copier: copy
727
- * the over element's typed fields + assign the `into` field from the concrete per-element handler row
728
- * (RawElem_<comp>_<node>). Pure struct field assignment — NO dynamic value read (the handler already
729
- * produced the typed into row).
957
+ * the over element's typed fields + place the already-de-boxed `into` value into its field. Pure struct
958
+ * field assignment — NO dynamic value read (the inline de-box already produced the typed `into` value).
730
959
  */
731
960
  function emitMapElemMaterializers(comp, typedNodes, plan) {
732
961
  const parts = [];
@@ -748,16 +977,15 @@ function emitMapElemMaterializers(comp, typedNodes, plan) {
748
977
  const intoRowRef = mapElemRowRef(n, arrRef, plan);
749
978
  const fn = mapElemMaterializerName(comp.name, n.id);
750
979
  const overElemGo = mapOverElemType(comp, n, typedNodes, plan);
751
- const elemRowGo = rawElemStructName(comp.name, n.id);
752
980
  const lines = [];
753
981
  lines.push(`// ${fn} — assemble the augmented map element '${n.id}': copy the over element's typed`);
754
- lines.push(`// fields + set the '${intoKey}' (into) field from the concrete per-element handler row.`);
755
- lines.push(`func ${fn}(over ${overElemGo}, into ${elemRowGo}) ${elemRef.name} {`);
982
+ lines.push(`// fields + set the '${intoKey}' (into) field from the already-de-boxed into value.`);
983
+ lines.push(`func ${fn}(over ${overElemGo}, into ${renderTypeRef(intoRowRef)}) ${elemRef.name} {`);
756
984
  lines.push(`\tvar out ${elemRef.name}`);
757
985
  for (const f of overFields) {
758
986
  lines.push(`\tout.${goFieldName(f.name)} = over.${goFieldName(f.name)}`);
759
987
  }
760
- lines.push(emitRowCopy(`out.${goFieldName(intoKey)}`, "into", intoRowRef, plan, 1));
988
+ lines.push(`\tout.${goFieldName(intoKey)} = into`);
761
989
  lines.push(`\treturn out`);
762
990
  lines.push(`}`);
763
991
  parts.push(lines.join("\n"));
@@ -847,31 +1075,17 @@ function reconstructG(e) {
847
1075
  function inStructName(compName) {
848
1076
  return `In_${sanitize(compName)}`;
849
1077
  }
850
- /**
851
- * inputPortGoType — the native Go type for an input port's declared type. Scalars lower to the concrete
852
- * scalar; `array`/`map` with a declared elemType to `[]ElemT` / `map[string]ElemT`; an OPTIONAL port
853
- * (`{opt:T}` → `required:false`) to `*T` — the native representation of "the value is absent" (nil).
854
- * A declared type with no native lowering stays the boxed Value.
855
- *
856
- * An OPTIONAL port whose inner type has no native lowering FAILS CLOSED: a boxed `Value` cannot carry
857
- * the presence distinction on the covered plane, and emitting the bare inner type would silently turn
858
- * "absent" into a zero value. A port whose optionality cannot be represented is never silently coerced.
859
- */
1078
+ /** Go type for an input port's declared type (scalar → concrete; array+elemType → []ElemT; else Value). */
860
1079
  function inputPortGoType(schema, plan, where = "input port") {
861
1080
  const ref = inputPortTypeRef(schema, plan);
862
1081
  if (ref !== undefined)
863
1082
  return renderTypeRef(ref);
864
1083
  if (inputPortIsOptional(schema))
865
- throw new GeneratorFailure("UNSUPPORTED_NODE_STRAIGHTLINE", `go typed-native (runtime-free): ${where} is OPTIONAL (declared {opt:…}) but its inner type ${JSON.stringify(schema?.type)} has no native Go lowering, so "absent" has no native representation (a boxed Value would be an escape, and the bare inner type would silently read absent as a zero value). Declare an elemType for an array/map, or use a natively-lowerable scalar.`);
1084
+ throw new GeneratorFailure("UNSUPPORTED_NODE_STRAIGHTLINE", `go typed-native (runtime-free): ${where} is OPTIONAL (declared {opt:…}) but its inner type ${JSON.stringify(schema?.type)} has no native Go lowering, so "absent" has no native representation. Declare an elemType for an array/map, or use a natively-lowerable scalar.`);
866
1085
  return `${PKG}.Value`;
867
1086
  }
868
- /** The scalar kind a REQUIRED input port declares, or undefined when it is not a required native scalar.
869
- * An OPTIONAL port is deliberately NOT a scalar kind here: its native type is `*T`, and the callers of
870
- * this function build REQUIRED scalar slots (`in.<Field>` read straight into a non-opt port field).
871
- * Answering "int64" for an `{opt:"int"}` port is exactly how "absent" would silently become `0`. */
1087
+ /** The scalar kind an input port declares, or undefined if it is not a native scalar. */
872
1088
  function inputScalarKind(schema) {
873
- if (inputPortIsOptional(schema))
874
- return undefined;
875
1089
  switch (schema?.type) {
876
1090
  case "string":
877
1091
  case "literal": // a `"literal"` union is a constrained string (see inputPortGoType).
@@ -896,6 +1110,8 @@ function emitInStruct(comp, plan) {
896
1110
  return `// ${name} — the CONCRETE input for '${comp.name}' (no input ports).\ntype ${name} struct{}`;
897
1111
  }
898
1112
  const names = ports.map((p) => goFieldName(p));
1113
+ // #139: inputPortGoType reads the inputPortTypeRef SSoT — an OPTIONAL port is `*T` (absent → nil); an
1114
+ // optional whose inner type has no native lowering FAILS CLOSED (never a boxed Value escape).
899
1115
  const types = ports.map((p) => inputPortGoType(comp.inputPorts[p], plan, `input port '${p}' of component '${comp.name}'`));
900
1116
  const nameW = Math.max(0, ...names.map((n) => n.length));
901
1117
  const typeW = Math.max(0, ...types.map((t) => t.length));
@@ -937,15 +1153,6 @@ function emitInDecoder(comp, plan) {
937
1153
  const scalar = inputScalarKind(schema);
938
1154
  const et = schema.elemType;
939
1155
  lines.push(`\tif v, ok := input.Get(${JSON.stringify(p)}); ok {`);
940
- if (inputPortIsOptional(schema)) {
941
- // An OPTIONAL port decodes into its native *T. materializeExpr's opt de-box IS the wire rule:
942
- // a nil Value (null) → nil (absent), any other value → a pointer to the materialized inner. A key
943
- // that never appears leaves the struct's zero value (nil) — the same absent value. Checked FIRST:
944
- // optionality wraps every inner type (an optional array is *[]T, not []T).
945
- lines.push(`\t\tin.${field} = ${goTypedInternals.materializeExpr("v", inputPortTypeRef(schema, plan), plan)}`);
946
- lines.push(`\t}`);
947
- continue;
948
- }
949
1156
  if ((schema?.type === "array" || schema?.type === "arr") && et !== undefined) {
950
1157
  // #108: decode the boxed Value array into the native []ElemT (test glue — materialize each element
951
1158
  // via the typed materializer; a non-array or non-conforming element is a fail-closed decode error).
@@ -976,11 +1183,9 @@ function emitInDecoder(comp, plan) {
976
1183
  lines.push(`\t\tin.${field} = v`);
977
1184
  }
978
1185
  else {
979
- lines.push(`\t\tt, tok := v.(${scalar})`);
980
- lines.push(`\t\tif !tok {`);
981
- lines.push(`\t\t\treturn in, ${PKG}.NewExprFailure("TYPE_MISMATCH", "input ${p}: expected ${scalar}, got "+${PKG}.TypeName(v))`);
982
- lines.push(`\t\t}`);
983
- lines.push(`\t\tin.${field} = t`);
1186
+ // #139: materialize via the inputPortTypeRef SSoT so an OPTIONAL scalar reads as *T (present &v);
1187
+ // an absent optional never enters this block (Get !ok), leaving the field nil (not a required zero).
1188
+ lines.push(`\t\tin.${field} = ${goTypedInternals.materializeExpr("v", inputPortTypeRef(schema, plan), plan)}`);
984
1189
  }
985
1190
  lines.push(`\t}`);
986
1191
  }
@@ -988,17 +1193,25 @@ function emitInDecoder(comp, plan) {
988
1193
  lines.push(`}`);
989
1194
  return lines.join("\n");
990
1195
  }
1196
+ /** A ref resolver for native lowering: given a ref head + remaining field path, return a native scalar
1197
+ * Go expression (typed input field / prior-node typed struct scalar / element binding field), or null
1198
+ * when the ref does not resolve to a REQUIRED (non-opt) native scalar (then the caller falls back to
1199
+ * the nvVE Value path — which keeps the runtime null/opt/TYPE_MISMATCH semantics). */
991
1200
  /**
992
- * emitPortInit — the port field initializer, read off the ONE port lowering (compilePortNode / native-
993
- * expr). The field's declared TYPE (portFieldGoType) is the SAME lowering's TypeRef, so type and value
994
- * can never disagree. `isPrior` tests whether a ref head names a settled prior node at this position;
995
- * `opts.asBinding`/`opts.asBase` carry a map/fanout element binding. A FALLIBLE lowering (hoisted
996
- * statements) is rejected HERE a ports-struct literal cannot host statements.
1201
+ * emitPortInit — emit the port field build for one port as a FULLY NATIVE Go expression (bc#90 /
1202
+ * runtime-free). Every covered port lowers to a concrete Go value assigned straight into the ports
1203
+ * struct field NO `dslcontracts.Value`, NO nvVE helper, NO `.(T)` type-assert:
1204
+ * - a static string array (projection) `[]string{"a", "b", ...}` (change #2);
1205
+ * - a bare int/float number literal (e.g. Query `limit: 20`) `int64(20)` / `float64(...)`;
1206
+ * - a string/bool/scalar-input port → the native scalar expression (emitNativeScalar).
1207
+ * A port that CANNOT lower natively FAILS CLOSED (there is no boxed Value fallback on the covered
1208
+ * plane — a boxed escape would re-introduce the bc-runtime coupling this module removes). Returns the
1209
+ * struct field initializer (`Field: <expr>`). `resolveRef` resolves a ref head for the scalar path.
997
1210
  */
998
1211
  function emitPortInit(pn, expr, comp, plan, typedNodes, isPrior, opts) {
999
1212
  const c = compilePortNode(expr, comp, plan, typedNodes, isPrior, `port '${pn}'`, opts);
1000
1213
  if (c.stmts.length > 0)
1001
- throw new GeneratorFailure("UNSUPPORTED_NODE_STRAIGHTLINE", `go typed-native (runtime-free): port '${pn}' ${JSON.stringify(expr)} lowers to a FALLIBLE expression (one that can raise INT_OVERFLOW / NAN_OR_INF / MOD_ZERO / PRECISION_LOSS). Such an expression lowers to STATEMENTS carrying an early error-return, and a port is built inline in the ports-struct literal — a position that cannot host them. (A cond/guard position CAN host them, so the same expression is covered there — the boundary is the POSITION, not the operator.) Use a non-fallible port expression.`);
1214
+ throw new GeneratorFailure("UNSUPPORTED_NODE_STRAIGHTLINE", `go typed-native (runtime-free): port '${pn}' ${JSON.stringify(expr)} lowers to a FALLIBLE expression (one that can raise INT_OVERFLOW / NAN_OR_INF / MOD_ZERO / PRECISION_LOSS). Such an expression lowers to STATEMENTS carrying an early error-return, and a port is built inline in the ports-struct literal — a position that cannot host them. Use a non-fallible port expression.`);
1002
1215
  return `${goPortFieldName(pn)}: ${c.expr}`;
1003
1216
  }
1004
1217
  /**
@@ -1022,7 +1235,7 @@ function emitPortInit(pn, expr, comp, plan, typedNodes, isPrior, opts) {
1022
1235
  *
1023
1236
  * NO RunPlan / []OpSpec / planSpec walk: the staging, the bound, and which ops are parallel are baked.
1024
1237
  */
1025
- function emitParallelStageArm(comp, stage, atPos, concurrency, metas, typedNodes, plan, priorNodeAt, zeroOut, flags) {
1238
+ function emitParallelStageArm(comp, stage, atPos, concurrency, metas, typedNodes, plan, priorNodeAt, zeroOut, flags, wire) {
1026
1239
  const lines = [];
1027
1240
  const ids = stage.map((i) => comp.body[i].id);
1028
1241
  lines.push(`\t// ── bc#87 real-concurrency stage {${ids.map((x) => JSON.stringify(x)).join(", ")}} — static parallel`);
@@ -1059,9 +1272,10 @@ function emitParallelStageArm(comp, stage, atPos, concurrency, metas, typedNodes
1059
1272
  const node = comp.body[i];
1060
1273
  const s = sanitize(node.id);
1061
1274
  const structName = portsStructName(comp.name, comp.body[i].id);
1062
- const rowT = rawRowStructName(comp.name, comp.body[i].id);
1063
- lines.push(`\t\tvar row_${s} ${rowT}`);
1064
- lines.push(`\t\tvar row_${s}Resolved bool`);
1275
+ // every member's goroutine returns the result WIRE (a single WireValue); the ascending COMMIT phase
1276
+ // de-boxes it INLINE (so the wire — not the decode — is what the goroutine writes to its slot).
1277
+ lines.push(`\t\tvar wire_${s} ${wire.value}`);
1278
+ lines.push(`\t\tvar row_${s}Err error`);
1065
1279
  lines.push(`\t\trun_${s} := false`);
1066
1280
  lines.push(`\t\tvar ps_${s} ${structName}`);
1067
1281
  lines.push(`\t\t_ = ps_${s}`);
@@ -1133,32 +1347,43 @@ function emitParallelStageArm(comp, stage, atPos, concurrency, metas, typedNodes
1133
1347
  lines.push(`\t\t\t\tdefer wg.Done()`);
1134
1348
  lines.push(`\t\t\t\tdefer func() { <-sem }()`);
1135
1349
  const boundArg = node.bindField !== undefined ? `bound_${s}` : "nil";
1136
- lines.push(`\t\t\t\trow_${s}, row_${s}Resolved = handlers.${nodeMethodName(comp.name, node.id)}(ps_${s}, ${boundArg})`);
1350
+ lines.push(`\t\t\t\twire_${s}, row_${s}Err = handlers.${nodeMethodName(comp.name, node.id)}(ps_${s}, ${boundArg})`);
1137
1351
  lines.push(`\t\t\t}()`);
1138
1352
  lines.push(`\t\t}`);
1139
1353
  }
1140
1354
  lines.push(`\t\twg.Wait()`);
1141
- // 3) COMMIT (ascending index): interpret + materialize deterministically — lowest-index failure wins.
1355
+ // 3) COMMIT (ascending index): interpret + INLINE de-box deterministically — lowest-index failure wins.
1356
+ // Uniform seam: every member's slot holds a single result WIRE (WireValue); the inline de-box
1357
+ // (renderResultDeBox) classifies it against the STATICALLY declared result type and produces the
1358
+ // structured Error Value on a mismatch (byte-equal codes to run_behavior's outType check, so ≡ holds).
1142
1359
  for (const i of refMembers) {
1143
1360
  const node = comp.body[i];
1144
1361
  const meta = metas[i];
1145
1362
  const s = sanitize(node.id);
1146
1363
  const ref = typedNodes.get(node.id);
1147
- const oc = `row_${s}`;
1148
1364
  lines.push(`\t\tif run_${s} {`);
1149
- lines.push(`\t\t\tif !${oc}Resolved {`, `\t\t\t\treturn ${zeroOut}, ${goErr("UNKNOWN_COMPONENT", `"component '${node.component}' has no handler (fail-closed)"`)}`, `\t\t\t}`);
1150
1365
  if (meta.policy === "continue") {
1151
- lines.push(`\t\t\tif !${oc}.IsError {`);
1152
- lines.push(emitRowCopy(typedLocal(node.id), oc, ref, plan, 4));
1153
- lines.push(`\t\t\t\tproduced_${s} = true`);
1366
+ // continue: a failed member (leaf failure OR de-box mismatch) commits no Port (defensive — covered
1367
+ // componentRef members are always 'fail' policy today). A local skip, NOT a runner-wide propagate.
1368
+ const nodeGo = renderTypeRef(ref);
1369
+ const nodeZeroFail = (e) => `return ${goZero(ref)}, ${e}`;
1370
+ lines.push(`\t\t\tif row_${s}Err == nil {`);
1371
+ lines.push(`\t\t\t\tdec_${s}, dec_${s}DErr := func() (${nodeGo}, error) {`);
1372
+ lines.push(`\t\t\t\t\tvar out ${nodeGo}`);
1373
+ lines.push(renderResultDeBox(ref, "out", `wire_${s}`, node.id, plan, "\t\t\t\t\t", nodeZeroFail));
1374
+ lines.push(`\t\t\t\t\treturn out, nil`);
1375
+ lines.push(`\t\t\t\t}()`);
1376
+ lines.push(`\t\t\t\tif dec_${s}DErr == nil {`);
1377
+ lines.push(`\t\t\t\t\t${typedLocal(node.id)} = dec_${s}`);
1378
+ lines.push(`\t\t\t\t\tproduced_${s} = true`);
1379
+ lines.push(`\t\t\t\t}`);
1154
1380
  lines.push(`\t\t\t}`);
1155
1381
  }
1156
1382
  else {
1157
- const policy = meta.policy === "retry" ? "retry' policy (exhausted)" : "fail' policy";
1158
- lines.push(`\t\t\tif ${oc}.IsError {`);
1159
- lines.push(`\t\t\t\treturn ${zeroOut}, ${goErr("OP_FAILED", `"operation '${node.id}' failed under '${policy}: "+${oc}.Err`)}`);
1383
+ lines.push(`\t\t\tif row_${s}Err != nil {`);
1384
+ lines.push(`\t\t\t\treturn ${zeroOut}, opFailed(${JSON.stringify(node.id)}, ${JSON.stringify(meta.policy === "retry" ? "retry" : "fail")}, row_${s}Err)`);
1160
1385
  lines.push(`\t\t\t}`);
1161
- lines.push(emitRowCopy(typedLocal(node.id), oc, ref, plan, 3));
1386
+ lines.push(renderResultDeBox(ref, typedLocal(node.id), `wire_${s}`, node.id, plan, "\t\t\t", returnZeroFail(zeroOut)));
1162
1387
  lines.push(`\t\t\tproduced_${s} = true`);
1163
1388
  }
1164
1389
  lines.push(`\t\t}`);
@@ -1179,7 +1404,7 @@ function emitParallelStageArm(comp, stage, atPos, concurrency, metas, typedNodes
1179
1404
  * consumer (graphddb) drives it and keeps the struct native. A Value observation is produced ONLY
1180
1405
  * by the separate serialize wrapper (run_native_raw_<comp>, below), the single outer Value boundary.
1181
1406
  */
1182
- function emitNativeRawRunner(comp, plan, flags) {
1407
+ function emitNativeRawRunner(comp, plan, flags, wire) {
1183
1408
  const ep = execPlan(comp);
1184
1409
  const order = ep.order;
1185
1410
  const cplan = buildComponentPlan(comp, goStraightlineInternals.dialect, "scope");
@@ -1200,6 +1425,9 @@ function emitNativeRawRunner(comp, plan, flags) {
1200
1425
  const outRef = comp.outputType !== undefined ? deriveTypeRef(comp.outputType, plan) : undefined;
1201
1426
  const outStructType = outRef ? renderTypeRef(outRef) : `${PKG}.Value`;
1202
1427
  const zeroOut = outRef ? goZero(outRef) : "nil";
1428
+ // ONE de-box temp counter across all at-fn-scope sequential nodes so sibling reads never collide on a
1429
+ // temp name (a gated / loop-scoped arm has its own Go scope, so it may keep a fresh counter).
1430
+ const seqCtr = { n: 0 };
1203
1431
  const lines = [];
1204
1432
  lines.push(`// ${runnerName(comp.name)} — the STRUCT-RETURNING combined read (bc#77/#87): the fully`);
1205
1433
  lines.push(`// de-plumbed path. Ports are a native struct (direct field assignment); the handler`);
@@ -1213,11 +1441,12 @@ function emitNativeRawRunner(comp, plan, flags) {
1213
1441
  lines.push(`// index order so the observed value / op multiset / failure precedence byte-match run_behavior`);
1214
1442
  lines.push(`// (the goroutine spawn is the ONLY runtime element — the WHAT is baked). The output is a typed`);
1215
1443
  lines.push(`// struct/value assembled by struct literal + field access — the consumer keeps it native.`);
1216
- // FULLY CONCRETE: GENERIC over the per-component CONCRETE handler interface ${handlerIfaceName(comp.name)},
1217
- // whose Node_* methods return the concrete per-node row struct (typed fields = the node's outType).
1218
- // The covered result plane carries no boxed ports / boxed value / dynamic accessor / semantic type-
1219
- // assert on a row value — the runner reads row.<Field> directly and copies each into the outType local.
1220
- lines.push(`func ${runnerName(comp.name)}[H ${handlerIfaceName(comp.name)}](handlers H, in ${inStructName(comp.name)}) (${outStructType}, error) {`);
1444
+ // FULLY CONCRETE + NON-GENERIC: the runner takes the consumer's CONCRETE ${handlerTypeName(comp.name)}
1445
+ // handler by value and calls h.Node_*(...) DIRECTLY (a Go generic method call goes through a dictionary,
1446
+ // a Go interface through a vtable BOTH are dynamic dispatch, so the covered plane uses neither). Each
1447
+ // Node_* returns the node's result WIRE (a single ${wire.value}); the runner de-boxes it INLINE
1448
+ // (fully-unrolled probe/match no decode helper, no output recursion) into the node's outType struct.
1449
+ lines.push(`func ${runnerName(comp.name)}(handlers ${handlerTypeName(comp.name)}, in ${inStructName(comp.name)}) (${outStructType}, error) {`);
1221
1450
  lines.push(`\t_ = in`);
1222
1451
  for (const k of order.keys()) {
1223
1452
  const node = comp.body[order[k]];
@@ -1241,7 +1470,7 @@ function emitNativeRawRunner(comp, plan, flags) {
1241
1470
  // ascending-sorted stages), so we emit the block at the first member and skip the rest.
1242
1471
  const stage = ep.parallelStageOf.get(i);
1243
1472
  if (stage !== undefined && stage[0] === i) {
1244
- lines.push(emitParallelStageArm(comp, stage, k, ep.concurrency, metas, typedNodes, plan, priorNodeAt, zeroOut, flags));
1473
+ lines.push(emitParallelStageArm(comp, stage, k, ep.concurrency, metas, typedNodes, plan, priorNodeAt, zeroOut, flags, wire));
1245
1474
  k += stage.length - 1;
1246
1475
  continue;
1247
1476
  }
@@ -1317,22 +1546,35 @@ function emitNativeRawRunner(comp, plan, flags) {
1317
1546
  inits.push(emitPortInit(pn, node.ports[pn], comp, plan, typedNodes, (h) => priorNodeAt(h, k)));
1318
1547
  }
1319
1548
  lines.push(`${indent}ports_${s} := ${structName}{${inits.join(", ")}}`);
1320
- const oc = `row_${s}`;
1321
- lines.push(`${indent}${oc}, ${oc}Resolved := handlers.${nodeMethodName(comp.name, node.id)}(ports_${s}, ${boundArg})`);
1322
- lines.push(`${indent}if !${oc}Resolved {`, `${indent}\treturn ${zeroOut}, ${goErr("UNKNOWN_COMPONENT", `"component '${node.component}' has no handler (fail-closed)"`)}`, `${indent}}`);
1323
- const copy = (ind) => emitRowCopy(typedLocal(node.id), oc, ref, plan, ind.length);
1549
+ // uniform seam: the handler returns a single result WIRE (${wire.value}); the inline de-box
1550
+ // (renderResultDeBox) classifies it against the STATICALLY declared result type and produces the
1551
+ // structured Error Value on a mismatch (byte-equal codes to run_behavior's outType check, so holds).
1324
1552
  if (meta.policy === "continue") {
1325
- lines.push(`${indent}if !${oc}.IsError {`);
1326
- lines.push(copy(indent + "\t"));
1327
- lines.push(`${indent}\tproduced_${s} = true`);
1553
+ // continue: a failed node (leaf failure OR de-box mismatch) produces no Port — the downstream Skip
1554
+ // propagation is what `continue` means. The inline de-box runs in a local closure so a mismatch is a
1555
+ // LOCAL skip (not a runner-wide propagate); only a clean decode commits. (Covered componentRef nodes
1556
+ // are always 'fail' policy today — this arm is defensive.)
1557
+ const nodeGo = renderTypeRef(ref);
1558
+ const nodeZeroFail = (e) => `return ${goZero(ref)}, ${e}`;
1559
+ lines.push(`${indent}if wire_${s}, wire_${s}Err := handlers.${nodeMethodName(comp.name, node.id)}(ports_${s}, ${boundArg}); wire_${s}Err == nil {`);
1560
+ lines.push(`${indent}\tdec_${s}, dec_${s}DErr := func() (${nodeGo}, error) {`);
1561
+ lines.push(`${indent}\t\tvar out ${nodeGo}`);
1562
+ lines.push(renderResultDeBox(ref, "out", `wire_${s}`, node.id, plan, `${indent}\t\t`, nodeZeroFail, seqCtr));
1563
+ lines.push(`${indent}\t\treturn out, nil`);
1564
+ lines.push(`${indent}\t}()`);
1565
+ lines.push(`${indent}\tif dec_${s}DErr == nil {`);
1566
+ lines.push(`${indent}\t\t${typedLocal(node.id)} = dec_${s}`);
1567
+ lines.push(`${indent}\t\tproduced_${s} = true`);
1568
+ lines.push(`${indent}\t}`);
1328
1569
  lines.push(`${indent}}`);
1329
1570
  }
1330
1571
  else {
1331
- const policy = meta.policy === "retry" ? "retry' policy (exhausted)" : "fail' policy";
1332
- lines.push(`${indent}if ${oc}.IsError {`);
1333
- lines.push(`${indent}\treturn ${zeroOut}, ${goErr("OP_FAILED", `"operation '${node.id}' failed under '${policy}: "+${oc}.Err`)}`);
1572
+ const policyLit = JSON.stringify(meta.policy === "retry" ? "retry" : "fail");
1573
+ lines.push(`${indent}wire_${s}, wire_${s}Err := handlers.${nodeMethodName(comp.name, node.id)}(ports_${s}, ${boundArg})`);
1574
+ lines.push(`${indent}if wire_${s}Err != nil {`);
1575
+ lines.push(`${indent}\treturn ${zeroOut}, opFailed(${JSON.stringify(node.id)}, ${policyLit}, wire_${s}Err)`);
1334
1576
  lines.push(`${indent}}`);
1335
- lines.push(copy(indent));
1577
+ lines.push(renderResultDeBox(ref, typedLocal(node.id), `wire_${s}`, node.id, plan, indent, returnZeroFail(zeroOut), seqCtr));
1336
1578
  lines.push(`${indent}produced_${s} = true`);
1337
1579
  }
1338
1580
  for (const c of closers)
@@ -1408,7 +1650,8 @@ function emitMapArm(comp, node, atPos, typedNodes, plan, priorNodeAt, zeroOut, f
1408
1650
  if (!hasGuard)
1409
1651
  return null;
1410
1652
  const resolveHead = goNativeResolveHead(comp, plan, typedNodes, priorHere, { asBinding: { name: asName, ref: overElemRef, plan }, asBase: elemVar });
1411
- return new NativeExprCompiler(makeGoExprBackend(zeroOut, resolveHead, plan)).compileBool(m.when);
1653
+ const be = makeGoExprBackend(zeroOut, resolveHead, plan);
1654
+ return new NativeExprCompiler(be).compileBool(m.when);
1412
1655
  };
1413
1656
  // emit the per-element native ports struct build (shared; `il` is the indent inside the element
1414
1657
  // loop, `elemVar` the over element loop variable). Returns the lines building `ep_<s>`.
@@ -1430,7 +1673,13 @@ function emitMapArm(comp, node, atPos, typedNodes, plan, priorNodeAt, zeroOut, f
1430
1673
  // over collection (typed field access to the parent node's arr — no serialize).
1431
1674
  lines.push(`${indent}over_${s} := ${overExpr}`);
1432
1675
  lines.push(`${indent}${typedLocal(node.id)} = make(${elemGo}, 0, len(over_${s}))`);
1676
+ // each element is de-boxed strictly INLINE: the handler returns the per-element WIRE and the arm decodes
1677
+ // it against the element ref (the `into` field's type for map...into, else the element type). A de-box
1678
+ // mismatch fails closed regardless of elementPolicy (matching the interpreter's whole-node outType
1679
+ // throw); elementPolicy still governs the handler's own leaf failures only.
1433
1680
  const elemRowRef = mapElemRowRef(node, arrRef, plan);
1681
+ const elemRowGo = renderTypeRef(elemRowRef);
1682
+ const renderElemDeBox = (target, src, il) => renderResultDeBox(elemRowRef, target, src, node.id, plan, il, returnZeroFail(zeroOut));
1434
1683
  // emit the per-element guard keep-gate (#108). `il` = indent, `elemVar` = the over element loop var.
1435
1684
  // On skip: batched → `continue` (element excluded from items + keptOver); non-batched → `continue`.
1436
1685
  const emitGuard = (il, elemVar) => {
@@ -1447,9 +1696,9 @@ function emitMapArm(comp, node, atPos, typedNodes, plan, priorNodeAt, zeroOut, f
1447
1696
  };
1448
1697
  if (batched) {
1449
1698
  // BATCHED (#86 pt2 into / #93 shape 3 no-into / #108 guarded no-into): build kept-element ports,
1450
- // dispatch ONCE. The concrete Node_* returns RawRow{IsError,Err,Rows []RawElem} aligned to items.
1699
+ // dispatch ONCE. The concrete Node_* returns the aligned per-element WIRES ([]WireValue) or an error.
1451
1700
  // With a guard, `items` (and, for into, `keptOver`) hold only kept elements — matching run_behavior's
1452
- // keptIdx-aligned batch. `into` is never combined with a guard (rejected). no-into collects each row.
1701
+ // keptIdx-aligned batch. `into` is never combined with a guard (rejected). Each wire is de-boxed inline.
1453
1702
  lines.push(`${indent}items_${s} := make([]${structName}, 0, len(over_${s}))`);
1454
1703
  if (hasInto)
1455
1704
  lines.push(`${indent}keptOver_${s} := make([]${overElemGo}, 0, len(over_${s}))`);
@@ -1465,22 +1714,24 @@ function emitMapArm(comp, node, atPos, typedNodes, plan, priorNodeAt, zeroOut, f
1465
1714
  // empty items => empty result (handler not called), matching run_behavior.
1466
1715
  lines.push(`${indent}if len(items_${s}) > 0 {`);
1467
1716
  lines.push(`${indent}\tbp_${s} := ${batchStruct}{Items: items_${s}}`);
1468
- lines.push(`${indent}\tmo_${s}, mo_${s}Resolved := handlers.${nodeMethodName(comp.name, node.id)}(bp_${s}, nil)`);
1469
- lines.push(`${indent}\tif !mo_${s}Resolved {`, `${indent}\t\treturn ${zeroOut}, ${goErr("UNKNOWN_COMPONENT", `"component '${m.component}' has no handler (fail-closed)"`)}`, `${indent}\t}`);
1470
- lines.push(`${indent}\tif mo_${s}.IsError {`, `${indent}\t\treturn ${zeroOut}, ${goErr("OP_FAILED", `"operation '${node.id}' failed under 'fail' policy: "+mo_${s}.Err`)}`, `${indent}\t}`);
1471
- lines.push(`${indent}\tif len(mo_${s}.Rows) != len(items_${s}) {`, `${indent}\t\treturn ${zeroOut}, ${goErr("MAP_BATCH_RESULT_MISMATCH", `fmt.Sprintf("map '${node.id}': batched handler must return a list aligned to items (want %d)", len(items_${s}))`)}`, `${indent}\t}`);
1717
+ lines.push(`${indent}\tmo_${s}, mo_${s}Err := handlers.${nodeMethodName(comp.name, node.id)}(bp_${s}, nil)`);
1718
+ lines.push(`${indent}\tif mo_${s}Err != nil {`, `${indent}\t\treturn ${zeroOut}, opFailed(${JSON.stringify(node.id)}, "fail", mo_${s}Err)`, `${indent}\t}`);
1719
+ lines.push(`${indent}\tif len(mo_${s}) != len(items_${s}) {`, `${indent}\t\treturn ${zeroOut}, ${goErr("MAP_BATCH_RESULT_MISMATCH", `fmt.Sprintf("map '${node.id}': batched handler must return a list aligned to items (want %d)", len(items_${s}))`)}`, `${indent}\t}`);
1472
1720
  if (hasInto) {
1473
- // #86 pt2: zip-attach the into field onto each kept over element (augmented element copier).
1721
+ // #86 pt2: de-box each aligned element wire (into the `into` field's type), then zip-attach onto the
1722
+ // kept over element (augmented element copier).
1474
1723
  lines.push(`${indent}\tfor mk_${s} := range keptOver_${s} {`);
1475
- lines.push(`${indent}\t\tel_${s} := ${elemMat}(keptOver_${s}[mk_${s}], mo_${s}.Rows[mk_${s}])`);
1724
+ lines.push(`${indent}\t\tvar dec_${s} ${elemRowGo}`);
1725
+ lines.push(renderElemDeBox(`dec_${s}`, `mo_${s}[mk_${s}]`, `${indent}\t\t`));
1726
+ lines.push(`${indent}\t\tel_${s} := ${elemMat}(keptOver_${s}[mk_${s}], dec_${s})`);
1476
1727
  lines.push(`${indent}\t\t${typedLocal(node.id)} = append(${typedLocal(node.id)}, el_${s})`);
1477
1728
  lines.push(`${indent}\t}`);
1478
1729
  }
1479
1730
  else {
1480
- // #93 shape 3 / #108: collect each element DIRECTLY from the aligned per-element row (field copy).
1481
- lines.push(`${indent}\tfor mk_${s} := range mo_${s}.Rows {`);
1731
+ // #93 shape 3 / #108: de-box each aligned per-element wire DIRECTLY into the element (result = kept).
1732
+ lines.push(`${indent}\tfor mk_${s} := 0; mk_${s} < len(mo_${s}); mk_${s}++ {`);
1482
1733
  lines.push(`${indent}\t\tvar el_${s} ${renderTypeRef(arrRef.elem)}`);
1483
- lines.push(emitRowCopy(`el_${s}`, `mo_${s}.Rows[mk_${s}]`, elemRowRef, plan, indent.length + 2));
1734
+ lines.push(renderElemDeBox(`el_${s}`, `mo_${s}[mk_${s}]`, `${indent}\t\t`));
1484
1735
  lines.push(`${indent}\t\t${typedLocal(node.id)} = append(${typedLocal(node.id)}, el_${s})`);
1485
1736
  lines.push(`${indent}\t}`);
1486
1737
  }
@@ -1489,7 +1740,7 @@ function emitMapArm(comp, node, atPos, typedNodes, plan, priorNodeAt, zeroOut, f
1489
1740
  else {
1490
1741
  // #93 shape 2 / #108: NON-batched map — dispatch the child handler ONCE PER KEPT ELEMENT (N physical
1491
1742
  // requests, matching run_behavior's per-element loop). A guarded element that fails `when` is skipped
1492
- // (no dispatch, excluded from the result). The concrete Node_* returns ONE RawElem_.
1743
+ // (no dispatch, excluded from the result). The concrete Node_* returns ONE element WIRE (WireValue).
1493
1744
  lines.push(`${indent}for mk_${s} := range over_${s} {`);
1494
1745
  lines.push(`${indent}\t${elemLocal(s)} := over_${s}[mk_${s}]`);
1495
1746
  for (const l of emitGuard(indent + "\t", elemLocal(s)))
@@ -1499,15 +1750,27 @@ function emitMapArm(comp, node, atPos, typedNodes, plan, priorNodeAt, zeroOut, f
1499
1750
  // per-element dispatch (ONE physical request per element). run_behavior passes the over element
1500
1751
  // as the bound value; the element data is fully conveyed by the native ports struct here, so bound
1501
1752
  // stays nil. Byte-equivalence holds because the bound value only feeds handler-internal logic.
1502
- lines.push(`${indent}\tmo_${s}, mo_${s}Resolved := handlers.${nodeMethodName(comp.name, node.id)}(ep_${s}, nil)`);
1503
- lines.push(`${indent}\tif !mo_${s}Resolved {`, `${indent}\t\treturn ${zeroOut}, ${goErr("UNKNOWN_COMPONENT", `"component '${m.component}' has no handler (fail-closed)"`)}`, `${indent}\t}`);
1504
- lines.push(`${indent}\tif mo_${s}.IsError {`, `${indent}\t\treturn ${zeroOut}, ${goErr("OP_FAILED", `"operation '${node.id}' failed under 'fail' policy: "+mo_${s}.Err`)}`, `${indent}\t}`);
1753
+ // Element Error Policy (scp-error.md): `skip` drops the failing element and proceeds (order is
1754
+ // preserved; the leaf keeps the Error Value it produced). `error` (default) promotes the element
1755
+ // Failure to the map's Component Failure, carrying the leaf's detail through.
1756
+ lines.push(`${indent}\tmo_${s}, mo_${s}Err := handlers.${nodeMethodName(comp.name, node.id)}(ep_${s}, nil)`);
1757
+ if (m.elementPolicy === "skip") {
1758
+ lines.push(`${indent}\tif mo_${s}Err != nil {`, `${indent}\t\tcontinue`, `${indent}\t}`);
1759
+ }
1760
+ else {
1761
+ lines.push(`${indent}\tif mo_${s}Err != nil {`, `${indent}\t\treturn ${zeroOut}, opFailed(${JSON.stringify(node.id)}, "fail", mo_${s}Err)`, `${indent}\t}`);
1762
+ }
1763
+ // the element's wire is de-boxed strictly INLINE. A de-box mismatch fails closed regardless of
1764
+ // elementPolicy (≡ the interpreter's whole-node outType throw); elementPolicy governs the handler's
1765
+ // own leaf failures only (above).
1505
1766
  if (hasInto) {
1506
- lines.push(`${indent}\tel_${s} := ${elemMat}(${elemLocal(s)}, mo_${s})`);
1767
+ lines.push(`${indent}\tvar dec_${s} ${elemRowGo}`);
1768
+ lines.push(renderElemDeBox(`dec_${s}`, `mo_${s}`, `${indent}\t`));
1769
+ lines.push(`${indent}\tel_${s} := ${elemMat}(${elemLocal(s)}, dec_${s})`);
1507
1770
  }
1508
1771
  else {
1509
1772
  lines.push(`${indent}\tvar el_${s} ${renderTypeRef(arrRef.elem)}`);
1510
- lines.push(emitRowCopy(`el_${s}`, `mo_${s}`, elemRowRef, plan, indent.length + 1));
1773
+ lines.push(renderElemDeBox(`el_${s}`, `mo_${s}`, `${indent}\t`));
1511
1774
  }
1512
1775
  lines.push(`${indent}\t${typedLocal(node.id)} = append(${typedLocal(node.id)}, el_${s})`);
1513
1776
  lines.push(`${indent}}`);
@@ -1578,19 +1841,22 @@ function emitFanoutArm(comp, node, atPos, typedNodes, plan, priorNodeAt, zeroOut
1578
1841
  // empty id-list => empty connection (handler not called), matching run_behavior.
1579
1842
  lines.push(`${indent}if len(pi_${s}) > 0 {`);
1580
1843
  lines.push(`${indent}\tbp_${s} := ${batchStruct}{Items: pi_${s}}`);
1581
- lines.push(`${indent}\tfo_${s}, fo_${s}Resolved := handlers.${nodeMethodName(comp.name, node.id)}(bp_${s}, nil)`);
1582
- lines.push(`${indent}\tif !fo_${s}Resolved {`, `${indent}\t\treturn ${zeroOut}, ${goErr("UNKNOWN_COMPONENT", `"component '${f.component}' has no handler (fail-closed)"`)}`, `${indent}\t}`);
1583
- lines.push(`${indent}\tif fo_${s}.IsError {`, `${indent}\t\treturn ${zeroOut}, ${goErr("OP_FAILED", `"operation '${node.id}' failed under 'fail' policy: "+fo_${s}.Err`)}`, `${indent}\t}`);
1584
- lines.push(`${indent}\tif len(fo_${s}.Rows) != len(pi_${s}) {`, `${indent}\t\treturn ${zeroOut}, ${goErr("FANOUT_BATCH_RESULT_MISMATCH", `fmt.Sprintf("fanout '${node.id}': batched handler must return a list aligned to the deduped id list (want %d)", len(pi_${s}))`)}`, `${indent}\t}`);
1844
+ // the handler returns the aligned per-id element WIRES (one nilable WireValue per id); each present
1845
+ // body is de-boxed strictly INLINE (a de-box mismatch fails closed). A DANGLING id is a nil wire → the
1846
+ // zero element (empty dedupeKey), which the dangling drop below excludes.
1847
+ lines.push(`${indent}\tfo_${s}, fo_${s}Err := handlers.${nodeMethodName(comp.name, node.id)}(bp_${s}, nil)`);
1848
+ lines.push(`${indent}\tif fo_${s}Err != nil {`, `${indent}\t\treturn ${zeroOut}, opFailed(${JSON.stringify(node.id)}, "fail", fo_${s}Err)`, `${indent}\t}`);
1849
+ lines.push(`${indent}\tif len(fo_${s}) != len(pi_${s}) {`, `${indent}\t\treturn ${zeroOut}, ${goErr("FANOUT_BATCH_RESULT_MISMATCH", `fmt.Sprintf("fanout '${node.id}': batched handler must return a list aligned to the deduped id list (want %d)", len(pi_${s}))`)}`, `${indent}\t}`);
1585
1850
  // ── THE ONE dedupe/drop (mirrors fanoutDedupDrop verbatim): first-seen dedupe by dedupeKey field;
1586
1851
  // drop dangling (zero dedupeKey ≡ interpreter's null/absent-key body); implicitSource strip is
1587
1852
  // structural (the elem type already omits it). cursor is always nil (connection wrap).
1588
- lines.push(`${indent}\tseen_${s} := make(map[string]struct{}, len(fo_${s}.Rows))`);
1589
- lines.push(`${indent}\titems_${s} = make([]${elemGo}, 0, len(fo_${s}.Rows))`);
1590
- lines.push(`${indent}\tfor fi_${s} := range fo_${s}.Rows {`);
1591
- lines.push(`${indent}\t\tbody_${s} := fo_${s}.Rows[fi_${s}]`);
1853
+ lines.push(`${indent}\tseen_${s} := make(map[string]struct{}, len(fo_${s}))`);
1854
+ lines.push(`${indent}\titems_${s} = make([]${elemGo}, 0, len(fo_${s}))`);
1855
+ lines.push(`${indent}\tfor fi_${s} := range fo_${s} {`);
1592
1856
  lines.push(`${indent}\t\tvar el_${s} ${elemGo}`);
1593
- lines.push(emitRowCopy(`el_${s}`, `body_${s}`, elemRef, plan, indent.length + 2));
1857
+ lines.push(`${indent}\t\tif fo_${s}[fi_${s}] != nil {`);
1858
+ lines.push(renderResultDeBox(elemRef, `el_${s}`, `(*fo_${s}[fi_${s}])`, node.id, plan, `${indent}\t\t\t`, returnZeroFail(zeroOut)));
1859
+ lines.push(`${indent}\t\t}`);
1594
1860
  lines.push(`${indent}\t\tkey_${s} := el_${s}.${dkGo}`);
1595
1861
  if (f.drop === "dangling") {
1596
1862
  // dangling: a body whose dedupeKey is the zero value (empty string) is dropped.
@@ -1609,15 +1875,12 @@ function emitFanoutArm(comp, node, atPos, typedNodes, plan, priorNodeAt, zeroOut
1609
1875
  lines.push(c);
1610
1876
  return lines.join("\n");
1611
1877
  }
1612
- /**
1613
- * goInputHeadRef (#108) resolve an INPUT port head for a native-expr ref (cond `if` / map `when` /
1614
- * branches / an opt-lane port). The port's declared type comes from the shared `inputPortTypeRef` SSoT,
1615
- * so an OPTIONAL port resolves as `{opt:…}` (native `*T`) and the compiler can see its optionality: a
1616
- * null-compare becomes a real presence test and a coalesce a real deref-or-default, while a
1617
- * REQUIRED-scalar slot fed by an opt fails closed. Go values are read directly off the input struct (no
1618
- * ownership ceremony); a type with no native lowering → null (fail-closed).
1619
- */
1878
+ /** #108: resolve an INPUT port head for a native-expr ref (cond `if` / map `when` / branches). A scalar
1879
+ * port `in.<Field>` of the scalar type; an array port WITH elemType `in.<Field>` of `[]ElemT` (so
1880
+ * `len(in.<Field>)` works); anything else null (fail-closed). */
1620
1881
  function goInputHeadRef(head, comp, plan) {
1882
+ // #139: the port's declared type comes from the inputPortTypeRef SSoT, so an OPTIONAL port resolves as
1883
+ // {opt:…} (native *T) and the compiler sees its optionality; a type with no native lowering → null.
1621
1884
  const ref = inputPortTypeRef(comp.inputPorts?.[head], plan);
1622
1885
  if (ref === undefined)
1623
1886
  return null;
@@ -1635,7 +1898,11 @@ function emitCondArm(comp, node, atPos, typedNodes, plan, priorNodeAt, zeroOut)
1635
1898
  const c = node.cond;
1636
1899
  const s = sanitize(node.id);
1637
1900
  const priorHere = (head) => priorNodeAt(head, atPos);
1638
- const resolveHead = goNativeResolveHead(comp, plan, typedNodes, priorHere);
1901
+ const resolveHead = (head) => {
1902
+ if (priorHere(head))
1903
+ return { expr: typedLocal(head), ref: typedNodes.get(head) };
1904
+ return goInputHeadRef(head, comp, plan);
1905
+ };
1639
1906
  const be = makeGoExprBackend(zeroOut, resolveHead, plan);
1640
1907
  const compiler = new NativeExprCompiler(be);
1641
1908
  const cond = compiler.compileBool(c.if);
@@ -1866,14 +2133,11 @@ function makeGoExprBackend(zeroOut, resolveHead, plan) {
1866
2133
  // opt (`*T`) null-presence test: present = `(expr != nil)`, absent = `(expr == nil)`.
1867
2134
  optIsSome: (expr) => `(${expr} != nil)`,
1868
2135
  optIsNone: (expr) => `(${expr} == nil)`,
1869
- // opt (`*T`) defaulted to a PURE default. Go has no `unwrap_or`, so this is the same immediately-
1870
- // invoked func literal the `ternary` rendering uses (the established shape in this emitter): bind the
1871
- // pointer ONCE, deref it when present, else yield the default. The default sits in the else branch,
1872
- // so it is evaluated only when absent — run_behavior's lazy right side exactly.
2136
+ // opt (`*T`) defaulted to a PURE default an immediately-invoked func literal: bind the pointer ONCE,
2137
+ // deref when present, else yield the default (evaluated only when absent run_behavior's lazy right).
1873
2138
  optUnwrapOr: (optExpr, defaultExpr, innerTy) => `func() ${innerTy} { if v := ${optExpr}; v != nil { return *v }; return ${defaultExpr} }()`,
1874
- // opt defaulted to a FALLIBLE default — a statement if/else, NOT a func literal: the default's
1875
- // hoisted `return zeroOut, err` early returns must leave the ENCLOSING fn, which a func literal
1876
- // would swallow (it would return from the literal). Only the nil branch evaluates the default.
2139
+ // opt defaulted to a FALLIBLE default — a statement if/else (NOT a func literal): the default's hoisted
2140
+ // `return zeroOut, err` early returns must leave the ENCLOSING fn. Only the nil branch evaluates it.
1877
2141
  optCoalesceGuard: (temp, ty, optExpr, bindTemp, dStmts, dExpr) => [
1878
2142
  `var ${temp} ${ty}`,
1879
2143
  `if ${bindTemp} := ${optExpr}; ${bindTemp} != nil {`,
@@ -2037,17 +2301,23 @@ var ComponentNamesNativeRaw = []string{${native.map((c) => JSON.stringify(c.name
2037
2301
  * mismatch Sprintf, `strings` for a concat builder, `sync` for a parallel stage). ExpectedSpecVersions
2038
2302
  * stays as a local `var` for provenance, but the old `dslcontracts.SpecVersions` skew `init()` is GONE
2039
2303
  * (change #5 — there is no runtime to skew against once the module is runtime-free). */
2040
- function emitMinimalHeader(ctx, needStrings, needSync, needFmt, needMath) {
2304
+ function emitMinimalHeader(ctx, needStrings, needSync, needFmt, needMath, needStrconv = false, wireImport) {
2041
2305
  const sv = ctx.specVersions;
2042
2306
  const imports = [];
2043
2307
  if (needFmt)
2044
2308
  imports.push(`\t"fmt"`);
2045
2309
  if (needMath)
2046
2310
  imports.push(`\t"math"`);
2311
+ if (needStrconv)
2312
+ imports.push(`\t"strconv"`);
2047
2313
  if (needStrings)
2048
2314
  imports.push(`\t"strings"`);
2049
2315
  if (needSync)
2050
2316
  imports.push(`\t"sync"`);
2317
+ // a configured consumer wire package (goWire.import) — the covered module references its CONCRETE
2318
+ // WireValue/WireRow/WireList types (default: in-package, no import).
2319
+ if (wireImport !== undefined)
2320
+ imports.push(`\t${JSON.stringify(wireImport)}`);
2051
2321
  const importBlock = imports.length > 0 ? `\nimport (\n${imports.join("\n")}\n)\n` : "";
2052
2322
  return `// GENERATED by behavior-contracts (bc#90, go-typed-native — the RUNTIME-FREE covered read de-box) — DO NOT EDIT.
2053
2323
  // Input: portable component-graph IR only. Every component here is a COVERED READ (sequential typed
@@ -2063,29 +2333,33 @@ var ExpectedSpecVersions = map[string]int64{"behavior": ${sv.behavior}, "express
2063
2333
  }
2064
2334
  // ── module assembly ──────────────────────────────────────────────────────────────────
2065
2335
  function emit(ctx) {
2066
- // The type plan is the SSoT the coverage gate + the port lowering both read (the gate ASKS the port
2067
- // lowering whether a port lowers), so it is built up-front — before the coverage checks.
2068
- const plan = buildTypePlan(ctx.ir);
2069
2336
  // Fail closed if a covered read SHAPE lacks the typed annotations (no silent re-box).
2070
2337
  for (const c of ctx.ir.components)
2071
- assertTypedCoverage(c, plan);
2072
- const native = ctx.ir.components.filter((c) => isNativeRaw(c, plan));
2073
- const nonNative = ctx.ir.components.filter((c) => !isNativeRaw(c, plan));
2338
+ assertTypedCoverage(c);
2339
+ const native = ctx.ir.components.filter(isNativeRaw);
2340
+ const nonNative = ctx.ir.components.filter((c) => !isNativeRaw(c));
2341
+ const plan = buildTypePlan(ctx.ir);
2074
2342
  const decls = emitTypeDecls(plan);
2075
2343
  // #108: reset the native-expr helper-usage accumulator (set by the cond/guard compiler when it emits
2076
2344
  // a fallible checked-arith helper call — determines whether the helper library + `math` import ship).
2077
2345
  goExprUsed.helpers = false;
2346
+ const wire = resolveGoWire(ctx);
2078
2347
  // Emit the runners first so the nv* helper usage flags are known.
2079
2348
  const flags = { bind: false, field: false, cat: false, arr: false };
2080
2349
  const structs = [];
2081
2350
  const inStructs = [];
2082
- const rowStructs = [];
2083
- const handlerIfaces = [];
2084
2351
  const elemCopiers = [];
2085
2352
  const runnerCodes = [];
2353
+ // every covered node returns a WireValue the inline de-box classifies — the wire seam (probe kind
2354
+ // consts + probe RESULT structs) is emitted once whenever any handler node exists.
2355
+ const anyWire = native.some((c) => c.body.some((n) => !("cond" in n)));
2086
2356
  for (const c of native) {
2087
2357
  inStructs.push(emitInStruct(c, plan));
2088
- const typedNodes = buildTypedNodes(c, plan);
2358
+ const typedNodes = new Map();
2359
+ // build the full typed-node map FIRST so a map's over-element resolution can see every node.
2360
+ for (const n of c.body)
2361
+ if (!isControlGate(n))
2362
+ typedNodes.set(n.id, "map" in n ? mapNodeArrRef(n, plan) : deriveTypeRef(n.outType, plan)); // #125: control-gate typeless; map は produced-array ref に正規化
2089
2363
  for (const n of c.body) {
2090
2364
  if ("fanout" in n) {
2091
2365
  // v3: a fanout node emits a per-id element ports struct + a batch struct (like a batched map).
@@ -2101,17 +2375,14 @@ function emit(ctx) {
2101
2375
  }
2102
2376
  else {
2103
2377
  // #129: pass typedNodes so a leaf array port fed by a prior node's arr outType types as []ElemT.
2104
- structs.push(emitPortsStruct(c, n, plan, typedNodes));
2378
+ structs.push(emitPortsStruct(c, n, undefined, plan, typedNodes));
2105
2379
  }
2106
2380
  }
2107
- // CONCRETE per-node row structs + the per-component handler interface (the fully-concrete seam).
2108
- rowStructs.push(emitRawRowStructs(c, typedNodes, plan));
2109
- handlerIfaces.push(emitHandlerInterface(c, typedNodes, plan));
2110
- // augmented-element copiers for covered map...into nodes (over fields + concrete into row).
2381
+ // augmented-element copiers for covered map...into nodes (over fields + the de-boxed into value).
2111
2382
  const mm = emitMapElemMaterializers(c, typedNodes, plan);
2112
2383
  if (mm)
2113
2384
  elemCopiers.push(mm);
2114
- const r = emitNativeRawRunner(c, plan, flags);
2385
+ const r = emitNativeRawRunner(c, plan, flags, wire);
2115
2386
  runnerCodes.push(r.code);
2116
2387
  }
2117
2388
  const structSurface = emitStructSurface(native);
@@ -2130,7 +2401,7 @@ function emit(ctx) {
2130
2401
  // minimal, runtime-free header.
2131
2402
  if (nonNative.length > 0) {
2132
2403
  const rejects = nonNative
2133
- .map((c) => ` - component '${c.name}': ${coverageRejectReason(c, plan) ?? "not a covered native read"}`)
2404
+ .map((c) => ` - component '${c.name}': ${coverageRejectReason(c) ?? "not a covered native read"}`)
2134
2405
  .join("\n");
2135
2406
  throw new GeneratorFailure("UNSUPPORTED_NODE_STRAIGHTLINE", `go typed-native (native codegen) surface: ${nonNative.length} component(s) are NOT a covered native read:\n${rejects}\n\n` +
2136
2407
  `The --typed-native (native codegen) surface is native-only and fail-closed: it does not silently ` +
@@ -2140,30 +2411,32 @@ function emit(ctx) {
2140
2411
  // #108: `math` is imported iff the native-expr compiler emitted a fallible helper (bcExpr_modF uses
2141
2412
  // math.Mod). The helper library itself is baked into the module only when used (goExprUsed.helpers).
2142
2413
  const needMath = goExprUsed.helpers;
2143
- const header = emitMinimalHeader(ctx, false, needSync, needFmt, needMath);
2414
+ // `strconv` is imported iff a generated inline de-box parses a numeric field (int/float range check).
2415
+ const needStrconv = runnerCodes.some((c) => c.includes("strconv."));
2416
+ const header = emitMinimalHeader(ctx, false, needSync, needFmt, needMath, needStrconv, wire.import);
2144
2417
  const banner = `// COMBINED READ layer (bc#90 — the RUNTIME-FREE read de-box; STRUCT-ONLY, FULLY-NATIVE surface). A
2145
2418
  // covered read is a strictly-sequential typed componentRef chain (point reads + relationKind:single
2146
2419
  // children), emitted ONLY here: each node builds a native ports struct by direct field assignment (every
2147
2420
  // port is a CONCRETE Go value — string / bool / []string projection / int64 limit — NO boxed Value, NO
2148
- // PortReader accessor); the CONCRETE per-component handler interface (Handler_<comp>) takes a typed
2149
- // produced-aware 'bound' pointer and returns a CONCRETE per-node row struct (Row_<comp>_<node>, typed
2150
- // fields = the node's outType) whose fields the runner reads DIRECTLY and copies into the node's outType
2151
- // struct. The covered plane carries NO bc-runtime reference at all failures use the LOCAL
2152
- // *BehaviorError (same codes, byte-equal to run_behavior). Exec is INLINE sequential (no plan driver) so
2153
- // a relation child reads the parent's REAL struct result via direct field access and the child-present
2421
+ // PortReader accessor); the runner takes the consumer's CONCRETE Handler_<comp> handler by
2422
+ // value and calls h.Node_*(...) DIRECTLY (no interface, no generics zero dynamic dispatch). Each Node_*
2423
+ // takes a typed produced-aware 'bound' pointer and returns the node's result WIRE (a single ${wire.value});
2424
+ // the runner de-boxes it INLINE (fully-unrolled probe/match no decode helper, no output recursion) into
2425
+ // the node's outType struct. The covered plane carries NO bc-runtime reference at all — failures use the
2426
+ // LOCAL *BehaviorError (same codes, byte-equal to run_behavior). Exec is INLINE sequential (no plan driver)
2427
+ // so a relation child reads the parent's REAL struct result via direct field access and the child-present
2154
2428
  // decision is made from the real parent value — relationSingle / connection converge with run_behavior
2155
- // (fixes #323/#74). The exposed API is the STRUCT-returning RunNativeRawStruct_<comp> (the consumer
2156
- // keeps the model native — no serialization on the read hot path). This module is FULLY NATIVE: a naive
2157
- // grep for boxing primitives OR for the bc-runtime package finds nothing, by design (bc#90/runtime-free).`;
2429
+ // (fixes #323/#74). The exposed API is the STRUCT-returning RunNativeRawStruct_<comp> (the consumer keeps
2430
+ // the model native — no serialization on the read hot path). This module is FULLY NATIVE: a naive grep for
2431
+ // boxing primitives OR for the bc-runtime package finds nothing, by design (bc#90/runtime-free).`;
2158
2432
  const sections = [
2159
2433
  banner,
2160
2434
  `// Local concrete failure type (runtime-free) — a covered runner returns *BehaviorError (satisfies\n// error) instead of a bc-runtime failure, so the fully-covered module imports ZERO bc runtime.\n${behaviorErrorType}`,
2161
2435
  decls ? `// Typed struct declarations (outType-derived; hash-dedup — shared type plan).\n${decls}` : undefined,
2162
2436
  `// Per-component CONCRETE input structs (fields = inputPorts; the consumer builds them natively —\n// no generic *Obj crosses the covered read boundary).\n${inStructs.join("\n\n")}`,
2163
- rowStructs.some((r) => r.trim().length > 0)
2164
- ? `// CONCRETE per-node handler-result row structs (typed fields = the node's outType; + error signal).\n${rowStructs.filter((r) => r.trim().length > 0).join("\n\n")}`
2437
+ anyWire
2438
+ ? `// strict de-box wire seam — the consumer supplies CONCRETE ${wire.value} / ${wire.row} / ${wire.list}\n// types (variant classification) and the runner classifies each node result against the STATICALLY\n// declared type via their methods; the generated INLINE de-box owns strictness (required/optional,\n// present/absent, error assembly, fail-closed). bc emits only the probe RESULT structs — concrete data,\n// no boxed Value, no interface, no dictionary crosses the seam.\n${emitProbeWireTypes(wire)}`
2165
2439
  : undefined,
2166
- `// Per-component CONCRETE handler interfaces (one typed Node_* method per covered node).\n${handlerIfaces.join("\n\n")}`,
2167
2440
  elemCopiers.length
2168
2441
  ? `// Augmented map-element copiers (over element fields + the concrete into row — pure assignment).\n${elemCopiers.join("\n\n")}`
2169
2442
  : undefined,
@@ -2188,8 +2461,8 @@ function emit(ctx) {
2188
2461
  */
2189
2462
  export function goTypedNativeObserve(ir, runtimeImport) {
2190
2463
  const components = ir.components;
2464
+ const native = components.filter(isNativeRaw);
2191
2465
  const plan = buildTypePlan(ir);
2192
- const native = components.filter((c) => isNativeRaw(c, plan));
2193
2466
  const serializers = emitSerializers(plan);
2194
2467
  const marshallers = goTypedInternals.emitMarshallers(plan);
2195
2468
  const mustHelpers = emitMustHelpers(plan);
@@ -2203,27 +2476,215 @@ export function goTypedNativeObserve(ir, runtimeImport) {
2203
2476
  // decode the generic *Obj input into the CONCRETE In_<comp> struct (test glue — ONE decode at the
2204
2477
  // top, OUTSIDE the covered hot path; a REQUIRED declared input is present so an absent key is a
2205
2478
  // fail-closed decode error surfaced here, never on the native runner path).
2206
- return `\tif name == ${JSON.stringify(c.name)} {\n\t\tin, ierr := decode_${inStructName(c.name)}(input)\n\t\tif ierr != nil {\n\t\t\treturn nil, ierr\n\t\t}\n\t\ttv, err := ${runnerName(c.name)}(handlers, in)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn ${ser}, nil\n\t}`;
2479
+ return `\tif name == ${JSON.stringify(c.name)} {\n\t\tin, ierr := decode_${inStructName(c.name)}(input)\n\t\tif ierr != nil {\n\t\t\treturn nil, ierr\n\t\t}\n\t\ttv, err := ${runnerName(c.name)}(${handlerTypeName(c.name)}{handlers}, in)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn ${ser}, nil\n\t}`;
2207
2480
  })
2208
2481
  .join("\n");
2209
2482
  // input decoders (TEST glue): generic *Obj -> concrete In_<comp>. Scalar fields read the typed value
2210
2483
  // (a `.(T)` here is on the SDK/wire Value in the OBSERVE companion, never in the covered module).
2211
2484
  const inDecoders = native.map((c) => emitInDecoder(c, plan)).join("\n\n");
2212
- // The observe companion drives EVERY covered component, so its handler type param H must satisfy
2213
- // EVERY per-component concrete Handler_<comp> interface a combined constraint embeds them all.
2214
- const combinedConstraint = `allNativeRawHandlers`;
2215
- const constraintDecl = `// ${combinedConstraint} the combined constraint embedding every covered component's concrete
2216
- // handler interface, so one handler value drives all covered runners (test glue only).
2217
- type ${combinedConstraint} interface {
2218
- ${native.map((c) => `\t${handlerIfaceName(c.name)}`).join("\n")}
2219
- }`;
2220
- // ── concrete scripted adapter (TEST glue): implements every Node_* method by draining a per-
2221
- // COMPONENT scripted-outcome queue and DECODING the scripted ok Value into the CONCRETE row struct.
2222
- // This is where the generic scripted source (a Value queue) meets the concrete per-node seam: the
2223
- // decode is generated (it knows the concrete row type), so the runner stays fully concrete. ──
2224
- const decodeFns = [];
2485
+ // every covered node returns a WireValue the inline de-box classifies so the scripted wire adapter is
2486
+ // needed whenever any component has a handler node.
2487
+ const anyDeBox = native.some((c) => c.body.some((n) => !("cond" in n)));
2488
+ // scripted wire adapter (TEST glue): wraps a scripted bc Value as the CONCRETE WireValue / WireRow /
2489
+ // WireList (the in-package default seam types the covered module references) and classifies each datum's
2490
+ // native type into a DynamoDB-flavored wire tag (S/N/BOOL/L/M/NULL). A real consumer supplies its OWN
2491
+ // concrete WireValue over its AttributeValue payload; this exists only so the harness drives the
2492
+ // generated inline de-box from the same scripted vectors run_behavior uses — so actualWireType observed
2493
+ // in a mismatch is the PRODUCER tag (e.g. "N"), not the collapsed native type.
2494
+ const wireAdapter = anyDeBox
2495
+ ? `// ── scripted wire adapter (TEST glue) ──
2496
+ func scriptedWireTag(v ${PKG}.Value) string {
2497
+ switch v.(type) {
2498
+ case nil:
2499
+ return "NULL"
2500
+ case bool:
2501
+ return "BOOL"
2502
+ case int64, float64:
2503
+ return "N"
2504
+ case string:
2505
+ return "S"
2506
+ case []${PKG}.Value:
2507
+ return "L"
2508
+ case *${PKG}.Obj:
2509
+ return "M"
2510
+ default:
2511
+ return "UNKNOWN"
2512
+ }
2513
+ }
2514
+
2515
+ func scriptedWireRaw(v ${PKG}.Value) string {
2516
+ switch t := v.(type) {
2517
+ case nil:
2518
+ return "null"
2519
+ case bool:
2520
+ if t {
2521
+ return "true"
2522
+ }
2523
+ return "false"
2524
+ case int64:
2525
+ return strconv.FormatInt(t, 10)
2526
+ case float64:
2527
+ s, _ := ${PKG}.PyFloatRepr(t)
2528
+ return s
2529
+ case string:
2530
+ return t
2531
+ default:
2532
+ return ${PKG}.EncodeValue(v)
2533
+ }
2534
+ }
2535
+
2536
+ func probeStringVal(v ${PKG}.Value) StringProbe {
2537
+ if v == nil {
2538
+ return StringProbe{Kind: probeNull, ActualWireType: "NULL", Raw: "null"}
2539
+ }
2540
+ if s, ok := v.(string); ok {
2541
+ return StringProbe{Kind: probeGot, Got: s, ActualWireType: "S", Raw: s}
2542
+ }
2543
+ return StringProbe{Kind: probeWrong, ActualWireType: scriptedWireTag(v), Raw: scriptedWireRaw(v)}
2544
+ }
2545
+
2546
+ func probeNumberVal(v ${PKG}.Value) NumberProbe {
2547
+ if v == nil {
2548
+ return NumberProbe{Kind: probeNull, ActualWireType: "NULL", Raw: "null"}
2549
+ }
2550
+ switch v.(type) {
2551
+ case int64, float64:
2552
+ return NumberProbe{Kind: probeGot, Got: scriptedWireRaw(v), ActualWireType: "N", Raw: scriptedWireRaw(v)}
2553
+ }
2554
+ return NumberProbe{Kind: probeWrong, ActualWireType: scriptedWireTag(v), Raw: scriptedWireRaw(v)}
2555
+ }
2556
+
2557
+ func probeBoolVal(v ${PKG}.Value) BoolProbe {
2558
+ if v == nil {
2559
+ return BoolProbe{Kind: probeNull, ActualWireType: "NULL", Raw: "null"}
2560
+ }
2561
+ if b, ok := v.(bool); ok {
2562
+ return BoolProbe{Kind: probeGot, Got: b, ActualWireType: "BOOL", Raw: scriptedWireRaw(v)}
2563
+ }
2564
+ return BoolProbe{Kind: probeWrong, ActualWireType: scriptedWireTag(v), Raw: scriptedWireRaw(v)}
2565
+ }
2566
+
2567
+ func probeRowVal(v ${PKG}.Value) RowProbe {
2568
+ if v == nil {
2569
+ return RowProbe{Kind: probeNull, ActualWireType: "NULL", Raw: "null"}
2570
+ }
2571
+ if o, ok := v.(*${PKG}.Obj); ok {
2572
+ return RowProbe{Kind: probeGot, Got: WireRow{obj: o}, ActualWireType: "M"}
2573
+ }
2574
+ return RowProbe{Kind: probeWrong, ActualWireType: scriptedWireTag(v), Raw: scriptedWireRaw(v)}
2575
+ }
2576
+
2577
+ func probeListVal(v ${PKG}.Value) ListProbe {
2578
+ if v == nil {
2579
+ return ListProbe{Kind: probeNull, ActualWireType: "NULL", Raw: "null"}
2580
+ }
2581
+ if a, ok := v.([]${PKG}.Value); ok {
2582
+ return ListProbe{Kind: probeGot, Got: WireList{items: a}, ActualWireType: "L"}
2583
+ }
2584
+ return ListProbe{Kind: probeWrong, ActualWireType: scriptedWireTag(v), Raw: scriptedWireRaw(v)}
2585
+ }
2586
+
2587
+ // WireValue — the CONCRETE scripted node-result wire (the in-package default seam type the covered runner
2588
+ // classifies via As*). A top value is always present, so As* never observes Absent; a scripted null is a
2589
+ // nil Value → Null. A real consumer supplies its own concrete WireValue over its AttributeValue payload.
2590
+ type WireValue struct {
2591
+ v ${PKG}.Value
2592
+ }
2593
+
2594
+ func (w WireValue) AsString() StringProbe { return probeStringVal(w.v) }
2595
+ func (w WireValue) AsNumber() NumberProbe { return probeNumberVal(w.v) }
2596
+ func (w WireValue) AsBool() BoolProbe { return probeBoolVal(w.v) }
2597
+ func (w WireValue) AsRow() RowProbe { return probeRowVal(w.v) }
2598
+ func (w WireValue) AsList() ListProbe { return probeListVal(w.v) }
2599
+
2600
+ // WireRow / WireList — the CONCRETE scripted wire map / array (the in-package default seam types), probed
2601
+ // per declared field / element by the generated inline de-box (their methods return bc's probe structs).
2602
+ type WireRow struct {
2603
+ obj *${PKG}.Obj
2604
+ }
2605
+
2606
+ func (w WireRow) field(name string) (${PKG}.Value, bool) {
2607
+ if w.obj == nil {
2608
+ return nil, false
2609
+ }
2610
+ return w.obj.Get(name)
2611
+ }
2612
+
2613
+ func (w WireRow) Keys() []string {
2614
+ if w.obj == nil {
2615
+ return nil
2616
+ }
2617
+ return w.obj.Keys
2618
+ }
2619
+
2620
+ func (w WireRow) ProbeString(field string) StringProbe {
2621
+ if v, ok := w.field(field); ok {
2622
+ return probeStringVal(v)
2623
+ }
2624
+ return StringProbe{Kind: probeAbsent}
2625
+ }
2626
+
2627
+ func (w WireRow) ProbeNumber(field string) NumberProbe {
2628
+ if v, ok := w.field(field); ok {
2629
+ return probeNumberVal(v)
2630
+ }
2631
+ return NumberProbe{Kind: probeAbsent}
2632
+ }
2633
+
2634
+ func (w WireRow) ProbeBool(field string) BoolProbe {
2635
+ if v, ok := w.field(field); ok {
2636
+ return probeBoolVal(v)
2637
+ }
2638
+ return BoolProbe{Kind: probeAbsent}
2639
+ }
2640
+
2641
+ func (w WireRow) ProbeRow(field string) RowProbe {
2642
+ if v, ok := w.field(field); ok {
2643
+ return probeRowVal(v)
2644
+ }
2645
+ return RowProbe{Kind: probeAbsent}
2646
+ }
2647
+
2648
+ func (w WireRow) ProbeList(field string) ListProbe {
2649
+ if v, ok := w.field(field); ok {
2650
+ return probeListVal(v)
2651
+ }
2652
+ return ListProbe{Kind: probeAbsent}
2653
+ }
2654
+
2655
+ type WireList struct {
2656
+ items []${PKG}.Value
2657
+ }
2658
+
2659
+ func (l WireList) Len() int { return len(l.items) }
2660
+ func (l WireList) ElemString(i int) StringProbe { return probeStringVal(l.items[i]) }
2661
+ func (l WireList) ElemNumber(i int) NumberProbe { return probeNumberVal(l.items[i]) }
2662
+ func (l WireList) ElemBool(i int) BoolProbe { return probeBoolVal(l.items[i]) }
2663
+ func (l WireList) ElemRow(i int) RowProbe { return probeRowVal(l.items[i]) }
2664
+ func (l WireList) ElemList(i int) ListProbe { return probeListVal(l.items[i]) }
2665
+
2666
+ // FailureDetail exposes the structured Error Value fields WITHOUT naming the local ErrorDetail type, so
2667
+ // the harness can assert the de-box's kind / field / expectedType / actualWireType / rawValue (TEST
2668
+ // glue — same package as the covered module, so it may read the fields directly).
2669
+ func (e *BehaviorError) FailureDetail() (kind, model, field, expectedType, actualWireType, rawValue string, ok bool) {
2670
+ if e.Detail == nil {
2671
+ return "", "", "", "", "", "", false
2672
+ }
2673
+ d := e.Detail
2674
+ return d.Kind, d.Model, d.Field, d.ExpectedType, d.ActualWireType, d.RawValue, true
2675
+ }`
2676
+ : "";
2677
+ // Handler_<comp> wrapper types: the covered runner takes the CONCRETE per-component handler by value
2678
+ // (non-generic). One scripted queue backs every component, so each Handler_<comp> embeds the shared
2679
+ // *ScriptedNativeRaw — the Node_* methods (on *ScriptedNativeRaw) are promoted, so the runner's direct
2680
+ // h.Node_*(...) call resolves to a concrete method (no interface, no dictionary). Test glue only.
2681
+ const handlerWrappers = native
2682
+ .map((c) => `// ${handlerTypeName(c.name)} — the CONCRETE handler the '${c.name}' runner takes by value (embeds the\n// shared scripted queue; the Node_* methods are promoted).\ntype ${handlerTypeName(c.name)} struct {\n\t*ScriptedNativeRaw\n}`)
2683
+ .join("\n\n");
2684
+ // ── concrete scripted adapter (TEST glue): implements every Node_* method by draining a per-COMPONENT
2685
+ // scripted-outcome queue and returning the scripted ok Value wrapped as a WireValue (the generic
2686
+ // scripted source — a Value queue — meets the uniform CONCRETE wire seam). ──
2225
2687
  const methodImpls = [];
2226
- const emittedDecode = new Set();
2227
2688
  for (const c of native) {
2228
2689
  const typedNodes = new Map();
2229
2690
  for (const n of c.body)
@@ -2234,113 +2695,117 @@ ${native.map((c) => `\t${handlerIfaceName(c.name)}`).join("\n")}
2234
2695
  continue; // #108: cond has no handler method (pure Expression) — no scripted impl.
2235
2696
  const ref = typedNodes.get(n.id);
2236
2697
  if ("fanout" in n) {
2237
- // v3: scripted fanout impl drain one batched outcome and decode the aligned []Value into the
2238
- // per-body elem rows (a null array element materializes to the zero elem row = dangling).
2698
+ // fanout: return the aligned per-id element WIRES ([]*WireValue); a null aligned body is a
2699
+ // DANGLING id nil (the covered runner maps it to the zero element).
2239
2700
  const f = n.fanout;
2240
- const elemRowRef = fanoutItemsElemRef(n, ref, plan);
2241
- const elemT = rawElemStructName(c.name, n.id);
2242
- const batchT = rawRowStructName(c.name, n.id);
2243
- const elemDecode = emitDecodeRow(c.name, n.id, elemT, elemRowRef, plan, emittedDecode, decodeFns, "Elem");
2244
2701
  const portsT = `${portsStructName(c.name, n.id)}_batch`;
2245
2702
  const foBoundT = boundGoType(n, c, typedNodes, plan);
2246
- methodImpls.push(`func (s *ScriptedNativeRaw) ${nodeMethodName(c.name, n.id)}(_ ${portsT}, _ ${foBoundT}) (${batchT}, bool) {
2703
+ methodImpls.push(`func (s *ScriptedNativeRaw) ${nodeMethodName(c.name, n.id)}(_ ${portsT}, _ ${foBoundT}) ([]*WireValue, error) {
2247
2704
  src, ok := s.next(${JSON.stringify(f.component)})
2248
2705
  if !ok {
2249
- return ${batchT}{}, false
2706
+ return nil, unknownComponent(${JSON.stringify(f.component)})
2250
2707
  }
2251
2708
  if src.isErr {
2252
- return ${batchT}{IsError: true, Err: src.err}, true
2709
+ return nil, leafFailure(src.err)
2253
2710
  }
2254
2711
  arr, _ := src.ok.([]${PKG}.Value)
2255
- rows := make([]${elemT}, 0, len(arr))
2712
+ wires := make([]*WireValue, 0, len(arr))
2256
2713
  for _, ev := range arr {
2257
- // v3: a null aligned body is a DANGLING id — decode it to the zero elem row (empty dedupeKey
2258
- // field), so the runner's dangling-drop excludes it (byte-equal to fanoutDedupDrop).
2259
2714
  if ev == nil {
2260
- rows = append(rows, ${elemT}{})
2715
+ wires = append(wires, nil)
2261
2716
  continue
2262
2717
  }
2263
- rows = append(rows, ${elemDecode}(ev))
2718
+ wv := WireValue{v: ev}
2719
+ wires = append(wires, &wv)
2264
2720
  }
2265
- return ${batchT}{Rows: rows}, true
2721
+ return wires, nil
2266
2722
  }`);
2267
2723
  continue;
2268
2724
  }
2269
2725
  if ("map" in n) {
2726
+ // map: batched → one WIRE per element ([]WireValue); non-batched → one element WIRE (WireValue).
2270
2727
  const m = n.map;
2271
2728
  const batched = m.batched === true;
2272
- const arrRef = ref;
2273
- const elemRowRef = mapElemRowRef(n, arrRef, plan);
2274
- const elemT = rawElemStructName(c.name, n.id);
2275
- const batchT = rawRowStructName(c.name, n.id);
2276
- const elemDecode = emitDecodeRow(c.name, n.id, elemT, elemRowRef, plan, emittedDecode, decodeFns, "Elem");
2277
2729
  const portsT = batched ? `${portsStructName(c.name, n.id)}_batch` : portsStructName(c.name, n.id);
2278
- const retT = batched ? batchT : elemT;
2279
2730
  const mapBoundT = boundGoType(n, c, typedNodes, plan);
2280
2731
  if (batched) {
2281
- methodImpls.push(`func (s *ScriptedNativeRaw) ${nodeMethodName(c.name, n.id)}(_ ${portsT}, _ ${mapBoundT}) (${batchT}, bool) {
2732
+ methodImpls.push(`func (s *ScriptedNativeRaw) ${nodeMethodName(c.name, n.id)}(_ ${portsT}, _ ${mapBoundT}) ([]WireValue, error) {
2282
2733
  src, ok := s.next(${JSON.stringify(m.component)})
2283
2734
  if !ok {
2284
- return ${batchT}{}, false
2735
+ return nil, unknownComponent(${JSON.stringify(m.component)})
2285
2736
  }
2286
2737
  if src.isErr {
2287
- return ${batchT}{IsError: true, Err: src.err}, true
2738
+ return nil, leafFailure(src.err)
2288
2739
  }
2289
2740
  arr, _ := src.ok.([]${PKG}.Value)
2290
- rows := make([]${elemT}, 0, len(arr))
2741
+ wires := make([]WireValue, 0, len(arr))
2291
2742
  for _, ev := range arr {
2292
- rows = append(rows, ${elemDecode}(ev))
2743
+ wires = append(wires, WireValue{v: ev})
2293
2744
  }
2294
- return ${batchT}{Rows: rows}, true
2745
+ return wires, nil
2295
2746
  }`);
2296
2747
  }
2297
2748
  else {
2298
- methodImpls.push(`func (s *ScriptedNativeRaw) ${nodeMethodName(c.name, n.id)}(_ ${portsT}, _ ${mapBoundT}) (${elemT}, bool) {
2749
+ methodImpls.push(`func (s *ScriptedNativeRaw) ${nodeMethodName(c.name, n.id)}(_ ${portsT}, _ ${mapBoundT}) (WireValue, error) {
2299
2750
  src, ok := s.next(${JSON.stringify(m.component)})
2300
2751
  if !ok {
2301
- return ${elemT}{}, false
2752
+ return WireValue{}, unknownComponent(${JSON.stringify(m.component)})
2302
2753
  }
2303
2754
  if src.isErr {
2304
- return ${elemT}{IsError: true, Err: src.err}, true
2755
+ return WireValue{}, leafFailure(src.err)
2305
2756
  }
2306
- return ${elemDecode}(src.ok), true
2757
+ return WireValue{v: src.ok}, nil
2307
2758
  }`);
2308
2759
  }
2309
- void retT;
2310
2760
  continue;
2311
2761
  }
2762
+ // componentRef: return the whole result WIRE (one WireValue wrapping the scripted ok Value); the
2763
+ // covered runner's INLINE de-box classifies it. A real consumer returns its own wire payload.
2312
2764
  const node = n;
2313
- const rowT = rawRowStructName(c.name, node.id);
2314
- const decode = emitDecodeRow(c.name, node.id, rowT, ref, plan, emittedDecode, decodeFns, "");
2315
2765
  const nodeBoundT = boundGoType(node, c, typedNodes, plan);
2316
- // #129 (TEST glue): support the reference scriptedHandlers' echo forms (PROTOCOL.md §3.5) the
2317
- // adapter normalizes `{"echo":"port","port":"X"}` / `{"echo":"items"}` to the port NAME, and the
2318
- // scripted handler
2319
- // ECHOES a NATIVE port back as its result. This makes a port-plane pin non-vacuous: the node's
2320
- // observed result IS the native value that flowed into its ports struct, so a dropped / re-boxed /
2321
- // zero-defaulted port diverges from run_behavior instead of passing. A port is echoable when its
2322
- // native type IS the node's outType (that is exactly when the row's Val can hold it); `ports` is
2323
- // named only when at least one port qualifies (else the param stays `_` so a non-echo node
2324
- // compiles clean / unused-param-free).
2325
- const echoable = echoablePorts(c, node, ref, plan, typedNodes);
2326
- const portsParam = echoable.length > 0 ? "ports" : "_";
2327
- // a NAMED outType row is FLATTENED (no Val) — echo a named port by copying the struct's fields;
2328
- // a scalar/arr/opt outType row holds the port in its single Val field.
2329
- const echoRow = (ep) => ref.kind === "named"
2330
- ? `${rowT}{${findDecl(plan, ref.name).fields.map((f) => `${goFieldName(f.name)}: ports.${goPortFieldName(ep)}.${goFieldName(f.name)}`).join(", ")}}`
2331
- : `${rowT}{Val: ports.${goPortFieldName(ep)}}`;
2332
- const echoBranch = echoable
2333
- .map((ep) => `\tif src.echo == ${JSON.stringify(ep)} {\n\t\treturn ${echoRow(ep)}, true\n\t}\n`)
2334
- .join("");
2335
- methodImpls.push(`func (s *ScriptedNativeRaw) ${nodeMethodName(c.name, node.id)}(${portsParam} ${portsStructName(c.name, node.id)}, _ ${nodeBoundT}) (${rowT}, bool) {
2766
+ // echo (#129/#141 fixtures): the scripted leaf can ECHO an input PORT back as its result wire, so the
2767
+ // input-port value flows into the de-boxed result and the runtime non-vacuity asserts survive.
2768
+ const echoBranches = [];
2769
+ if (ref.kind === "named") {
2770
+ const decl = findDecl(plan, ref.name);
2771
+ const field = decl !== undefined && decl.fields.length === 1 ? decl.fields[0] : undefined;
2772
+ const fieldGo = field !== undefined ? renderTypeRef(field.type) : null;
2773
+ const recordGo = renderTypeRef(ref);
2774
+ for (const pn of Object.keys(node.ports)) {
2775
+ let portGo = null;
2776
+ try {
2777
+ portGo = portFieldGoType(node.ports[pn], c, plan, typedNodes, `echo port '${pn}'`);
2778
+ }
2779
+ catch {
2780
+ portGo = null;
2781
+ }
2782
+ if (portGo === recordGo) {
2783
+ // the port IS the whole record → echo it DIRECTLY (matching run_behavior's plain echo:port).
2784
+ echoBranches.push(`\tif src.echo == ${JSON.stringify(pn)} {\n\t\treturn WireValue{v: ${serializeTyped(`ports.${goPortFieldName(pn)}`, ref, plan)}}, nil\n\t}`);
2785
+ }
2786
+ else if (fieldGo !== null && field !== undefined && portGo === fieldGo) {
2787
+ // a scalar port wraps into the record's single field ({v: ports.X}).
2788
+ echoBranches.push(`\tif src.echo == ${JSON.stringify(pn)} {\n\t\treturn WireValue{v: ${serializeTyped(`${recordGo}{${goFieldName(field.name)}: ports.${goPortFieldName(pn)}}`, ref, plan)}}, nil\n\t}`);
2789
+ }
2790
+ }
2791
+ }
2792
+ else if (ref.kind === "arr" && Object.prototype.hasOwnProperty.call(node.ports, "items")) {
2793
+ // #129: an arr node with an `items` port echoes the NATIVE port array (serialized to a Value array)
2794
+ // so the node→leaf array dataflow test is non-vacuous.
2795
+ echoBranches.push(`\tif src.echo == "items" {\n\t\treturn WireValue{v: ${serializeTyped(`ports.${goPortFieldName("items")}`, ref, plan)}}, nil\n\t}`);
2796
+ }
2797
+ const canEcho = echoBranches.length > 0;
2798
+ const portsParam = canEcho ? "ports" : "_";
2799
+ const echoBlock = canEcho ? `${echoBranches.join("\n")}\n` : "";
2800
+ methodImpls.push(`func (s *ScriptedNativeRaw) ${nodeMethodName(c.name, node.id)}(${portsParam} ${portsStructName(c.name, node.id)}, _ ${nodeBoundT}) (WireValue, error) {
2336
2801
  src, ok := s.next(${JSON.stringify(node.component)})
2337
2802
  if !ok {
2338
- return ${rowT}{}, false
2803
+ return WireValue{}, unknownComponent(${JSON.stringify(node.component)})
2339
2804
  }
2340
2805
  if src.isErr {
2341
- return ${rowT}{IsError: true, Err: src.err}, true
2806
+ return WireValue{}, leafFailure(src.err)
2342
2807
  }
2343
- ${echoBranch} return ${decode}(src.ok), true
2808
+ ${echoBlock} return WireValue{v: src.ok}, nil
2344
2809
  }`);
2345
2810
  }
2346
2811
  }
@@ -2355,10 +2820,10 @@ type scriptSrc struct {
2355
2820
  ok ${PKG}.Value
2356
2821
  }
2357
2822
 
2358
- // ScriptedNativeRaw — the CONCRETE test handler: it satisfies every Handler_<comp> interface by
2359
- // draining a per-COMPONENT scripted queue and decoding the ok Value into the concrete row struct.
2360
- // The decode (decodeRow_*) is generated, so the covered runner drives fully-concrete Node_* methods.
2361
- // EXPORTED so the runner glue (a separate package) can name it for the generic instantiation.
2823
+ // ScriptedNativeRaw — the CONCRETE test handler: its Node_* methods drain a per-COMPONENT scripted queue
2824
+ // and return the ok Value wrapped as a WireValue (the covered runner de-boxes it inline). Each runner
2825
+ // takes a Handler_<comp> value that embeds this, so the Node_* calls stay fully concrete (no interface).
2826
+ // EXPORTED so the runner glue (a separate package) can name it.
2362
2827
  type ScriptedNativeRaw struct {
2363
2828
  mu sync.Mutex
2364
2829
  queues map[string][]scriptSrc
@@ -2383,15 +2848,13 @@ func NewScriptedNativeRaw(spec *${PKG}.JObj) (*ScriptedNativeRaw, error) {
2383
2848
  for _, e := range entries {
2384
2849
  eo, _ := e.(*${PKG}.JObj)
2385
2850
  // #129: an \`echo\` directive names an input port to echo back (mirrors the reference
2386
- // scriptedHandlers' PROTOCOL.md §3.5 echo forms). It is drained together with the ok
2387
- // payload. \`{"echo":"port","port":"X"}\` is normalized to the port NAME here, so the
2388
- // generated node method just compares \`src.echo\` against its echoable port names —
2389
- // \`{"echo":"items"}\` is already that shape.
2851
+ // scriptedHandlers' \`echo === "items"\`). It is drained together with the ok payload.
2390
2852
  echo := ""
2391
2853
  if echoNode, has := eo.Get("echo"); has {
2392
2854
  echo, _ = echoNode.(string)
2855
+ // normalize echo:port to the port NAME X so a de-box handler's echo comparison fires;
2856
+ // echo:items already names the port directly.
2393
2857
  if echo == "port" {
2394
- echo = ""
2395
2858
  if portNode, hasPort := eo.Get("port"); hasPort {
2396
2859
  echo, _ = portNode.(string)
2397
2860
  }
@@ -2434,86 +2897,52 @@ func (s *ScriptedNativeRaw) next(component string) (scriptSrc, bool) {
2434
2897
  }`;
2435
2898
  return `// GENERATED test-only observe companion (bc#77) — NOT part of the covered module. Serializes the
2436
2899
  // struct-native runner's result to a canonical Value for the equivalence pin, and carries the CONCRETE
2437
- // scripted test handler (scriptedNativeRaw) that satisfies every Handler_<comp> interface by decoding a
2438
- // scripted Value into the concrete per-node row. The consumer never uses this (it keeps the model
2439
- // native + implements Handler_<comp> over its own wire payload); it exists only so the test harness can
2440
- // byte-compare the STRUCT-only covered read against run_behavior. Same package as the covered module.
2900
+ // scripted test handler (ScriptedNativeRaw) + the CONCRETE WireValue/WireRow/WireList seam types +
2901
+ // per-component Handler_<comp> wrappers the runner takes. The consumer never uses this (it keeps the
2902
+ // model native + supplies its own concrete WireValue + Handler_<comp>); it exists only so the test
2903
+ // harness can byte-compare the STRUCT-only covered read against run_behavior. Same package as the module.
2441
2904
  package behaviors
2442
2905
 
2443
2906
  import (
2444
- "sync"
2907
+ ${anyDeBox ? '\t"strconv"\n\n' : ""} "sync"
2445
2908
 
2446
2909
  ${PKG} "${runtimeImport ?? "github.com/foo-ogawa/behavior-contracts/go"}"
2447
2910
  )
2448
2911
 
2912
+ // scripted wire adapter (TEST glue) — the CONCRETE WireValue/WireRow/WireList the covered module references.
2913
+ ${wireAdapter}
2914
+
2449
2915
  // typed -> Value serializers (TEST glue only — the covered module has none).
2450
2916
  ${serializers}
2451
2917
 
2452
- // Value -> typed struct marshallers (+ must* helpers) for the concrete-row decode (TEST glue only).
2918
+ // Value -> typed struct marshallers (+ must* helpers) for the input decoders (TEST glue only).
2453
2919
  ${marshallers}
2454
2920
 
2455
2921
  ${mustHelpers}
2456
2922
 
2457
2923
  ${arrOptHelpers}
2458
2924
 
2459
- // per-node concrete-row decoders (scripted Value -> RawRow_/RawElem_ struct).
2460
- ${decodeFns.join("\n\n")}
2461
-
2462
2925
  // input decoders (generic *Obj -> concrete In_<comp>; TEST glue, one decode at the observe boundary).
2463
2926
  ${inDecoders}
2464
2927
 
2465
2928
  ${adapter}
2466
2929
 
2467
- ${constraintDecl}
2930
+ // per-component CONCRETE handler wrappers (the runner takes one by value — embeds the shared queue).
2931
+ ${handlerWrappers}
2468
2932
 
2469
- // scriptedNativeRaw method impls (one per covered node — the concrete Node_* seam, test glue).
2933
+ // ScriptedNativeRaw method impls (one per covered node — the concrete Node_* seam returning WireValue).
2470
2934
  ${methodImpls.join("\n\n")}
2471
2935
 
2472
- // ObserveNativeRaw drives the struct-native runner and serializes to a canonical Value (test only).
2473
- // GENERIC over the CONCRETE combined handler type H so each enclosed RunNativeRawStruct_* call is
2474
- // instantiated at the concrete handler type (direct, devirtualized per-node method calls), not at an
2475
- // interface. Instantiate with your concrete handler type to preserve the covered path's concreteness.
2476
- func ObserveNativeRaw[H ${combinedConstraint}](name string, handlers H, input *${PKG}.Obj) (${PKG}.Value, error) {
2936
+ // ObserveNativeRaw drives the struct-native runner and serializes to a canonical Value (test only). It
2937
+ // takes the CONCRETE *ScriptedNativeRaw and wraps it per component in the runner's Handler_<comp> value —
2938
+ // each enclosed RunNativeRawStruct_* call is a NON-generic call over a concrete handler (direct,
2939
+ // devirtualized per-node method calls), never an interface or a generic dictionary.
2940
+ func ObserveNativeRaw(name string, handlers *ScriptedNativeRaw, input *${PKG}.Obj) (${PKG}.Value, error) {
2477
2941
  ${arms}
2478
2942
  return nil, ${PKG}.NewBehaviorFailure("UNKNOWN_ENTRY", "component '"+name+"' is not a covered read (fail-closed)")
2479
2943
  }
2480
2944
  `;
2481
2945
  }
2482
- /** echoablePorts (TEST glue) — the node's ports whose NATIVE type is exactly the node's outType, in
2483
- * declaration order. Those are the ports a scripted `echo: "<port>"` can return as the row (a scalar/arr/
2484
- * opt row via its Val; a NAMED row via its flattened fields copied from the struct port). A port with a
2485
- * different type is not echoable (the row could not hold it). */
2486
- function echoablePorts(comp, node, ref, plan, typedNodes) {
2487
- const want = renderTypeRef(ref);
2488
- return Object.keys(node.ports).filter((p) => portFieldGoType(node.ports[p], comp, plan, typedNodes, `port '${p}'`) === want);
2489
- }
2490
- /** emitDecodeRow — emit (once, deduped) a decoder `decodeRow_<comp>_<node><suffix>(v Value) <rowT>`
2491
- * that materializes the scripted ok Value into the concrete row struct (typed fields = the outType /
2492
- * elem-row type). Named: reuse the Value->struct marshaller + copy fields; scalar/arr/opt: materialize
2493
- * into .Val. TEST glue only (the production consumer decodes its own wire payload). */
2494
- function emitDecodeRow(compName, nodeId, rowT, ref, plan, emitted, out, suffix) {
2495
- const fn = `decodeRow_${sanitize(compName)}_${nodeId.replace(/[^A-Za-z0-9_]/g, "_")}${suffix}`;
2496
- if (emitted.has(fn))
2497
- return fn;
2498
- emitted.add(fn);
2499
- const lines = [];
2500
- lines.push(`func ${fn}(v ${PKG}.Value) ${rowT} {`);
2501
- lines.push(`\tvar out ${rowT}`);
2502
- if (ref.kind === "named") {
2503
- const decl = findDecl(plan, ref.name);
2504
- lines.push(`\tt := ${goTypedInternals.materializeExpr("v", ref, plan)}`);
2505
- for (const f of decl.fields) {
2506
- lines.push(`\tout.${goFieldName(f.name)} = t.${goFieldName(f.name)}`);
2507
- }
2508
- }
2509
- else {
2510
- lines.push(`\tout.Val = ${goTypedInternals.materializeExpr("v", ref, plan)}`);
2511
- }
2512
- lines.push(`\treturn out`);
2513
- lines.push(`}`);
2514
- out.push(lines.join("\n"));
2515
- return fn;
2516
- }
2517
2946
  /**
2518
2947
  * goTypedNativeEmitter — language id `go-typed-native` (bc#77). Emits the generic straight-line
2519
2948
  * body (for non-covered components / helpers) plus the COMBINED READ layer (native ports struct