behavior-contracts 0.8.4 → 0.8.5

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.
@@ -49,162 +49,70 @@ function portsStructName(compName, nodeId) {
49
49
  // type to implement the per-component Handler_<comp> interface's concrete Node_* method signatures.
50
50
  return `PortsNR_${sanitize(compName)}_${nodeId.replace(/[^A-Za-z0-9_]/g, "_")}`;
51
51
  }
52
- function inferPortType(node, inputPorts, asBinding) {
53
- if (staticArrElems(node) !== null)
54
- return "Value";
55
- const e = classifyExpr(node);
56
- switch (e.kind) {
57
- case "str":
58
- case "concat":
59
- return "string";
60
- case "bool":
61
- return "bool";
62
- case "ref": {
63
- // #108: a `$as`-headed ref (map element binding) — resolve the element field's scalar type.
64
- if (asBinding !== undefined && e.path[0] === asBinding.name) {
65
- try {
66
- const { ref } = goTypedInternals.typedFieldAccess(asBinding.name, asBinding.ref, e.path.slice(1), asBinding.plan);
67
- if (ref.kind === "scalar" && ref.scalar !== "null")
68
- return goScalarPortField(ref.scalar);
69
- }
70
- catch {
71
- return "Value";
72
- }
73
- return "Value";
74
- }
75
- // a ref to a REQUIRED scalar input port → that scalar. An OPTIONAL port has no required-scalar
76
- // kind (its native type is *T) and is lowered by the opt lane ahead of this — reaching here with
77
- // one means the shape is uncovered, and "Value" routes it to the caller's loud fail-closed.
78
- if (e.path.length === 1) {
79
- const sc = inputScalarKind(inputPorts[e.path[0]]);
80
- if (sc !== undefined)
81
- return sc;
82
- }
83
- return "Value";
84
- }
85
- default:
86
- return "Value";
87
- }
88
- }
89
- /** scalar TypeRef → PortFieldType (native go scalar kind). */
90
- function goScalarPortField(scalar) {
91
- return scalar === "string" ? "string" : scalar === "int" ? "int64" : scalar === "float" ? "float64" : "bool";
92
- }
93
- /** A static string-array port (every element a static string literal) — the graphddb `projection`
94
- * shape. Returns the list of literal strings, or null when the node is not that shape. */
95
- function staticStringArrElems(node) {
96
- const arr = staticArrElems(node);
97
- if (arr === null)
98
- return null;
99
- const out = [];
100
- for (const el of arr) {
101
- const e = classifyExpr(el);
102
- if (e.kind !== "str")
103
- return null;
104
- out.push(e.value);
105
- }
106
- return out;
107
- }
108
52
  /**
109
- * portFieldGoType — the NATIVE Go type for a covered port field (bc#90 / runtime-free). The covered
110
- * ports struct carries NO `dslcontracts.Value` field: every port lowers to a concrete Go type.
111
- * - a static string-array (projection) `[]string` (change #2);
112
- * - a bare int/float number literal (e.g. Query `limit: 20`) `int64`/`float64`;
113
- * - a string/bool/scalar-input port the scalar Go type (inferPortType).
114
- * A port that would otherwise infer to the boxed `Value` (a dynamic operator, a non-static array, a
115
- * Value-typed input) FAILS CLOSED — the covered read plane admits NO boxed escape (there is no
116
- * `dslcontracts.Value` fallback on the native module). `where` names the emission site for the error.
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.
117
58
  */
118
- function portFieldGoType(node, inputPorts, where = "port", asBinding, plan, typedNodes) {
119
- // A port reading an OPTIONAL input port lowers through the shared native-expr compiler (the *T
120
- // pass-through / the coalesce default). It runs FIRST so an optional input can never fall into the
121
- // required-scalar inference below (which has no way to say "absent").
122
- const optPort = optPortCompileG(node, inputPorts, where, plan);
123
- if (optPort !== null)
124
- return renderTypeRef(optPort.ref);
125
- if (staticStringArrElems(node) !== null)
126
- return "[]string";
127
- // #110: a componentRef port bound to a runtime array input port (with a resolvable elemType) → []ElemT.
128
- const arrElem = portArrayElemRef(node, inputPorts, plan);
129
- if (arrElem !== null)
130
- return `[]${renderTypeRef(arrElem)}`;
131
- // #129: a leaf port that is a ref into a PRIOR NODE's typed arr outType (node→leaf array dataflow) → []ElemT.
132
- const priorArr = priorNodeArrElemInfo(node, typedNodes, plan);
133
- if (priorArr !== null)
134
- return `[]${renderTypeRef(priorArr.elemRef)}`;
135
- const num = numLiteralGoExpr(node);
136
- if (num !== null)
137
- return Number.isInteger(node) ? "int64" : "float64";
138
- // a ref to a `$as` element field (or an input map port) whose type is a MAP or NAMED struct (NOT a bare
139
- // scalar) → the native map/struct type (#108-map: a `{map:V}` write port lowers to map[string]V).
140
- const composite = portCompositeRef(node, inputPorts, asBinding, plan);
141
- if (composite !== null)
142
- return renderTypeRef(composite);
143
- const ft = inferPortType(node, inputPorts, asBinding);
144
- if (ft === "Value") {
145
- throw new GeneratorFailure("UNSUPPORTED_NODE_STRAIGHTLINE", `go combined read (bc#90 / runtime-free): ${where} '${JSON.stringify(node)}' does not lower to a native Go type (would need a boxed dslcontracts.Value). The covered read plane is runtime-free and admits NO boxed escape. ${GO_STATIC_PORT_FORMS} Regenerate on the boxed straight-line path if this port is genuinely dynamic.`);
146
- }
147
- return ft;
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);
148
62
  }
149
- /** portCompositeRef (go twin) — the full TypeRef of a port that is a `{ref:[...]}` into a `$as` element
150
- * MAP or NAMED field (or an input MAP port with elemType). Returns null otherwise (caller falls through
151
- * to scalar/array inference). #108-map: enables a `{map:V}` write port to lower to map[string]V. */
152
- function portCompositeRef(node, inputPorts, asBinding, plan) {
153
- const e = classifyExpr(node);
154
- if (e.kind !== "ref" || plan === undefined)
155
- return null;
156
- let ref = null;
157
- if (asBinding !== undefined && e.path[0] === asBinding.name) {
158
- try {
159
- ref = goTypedInternals.typedFieldAccess(asBinding.name, asBinding.ref, e.path.slice(1), asBinding.plan).ref;
160
- }
161
- catch {
162
- return null;
63
+ /**
64
+ * 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.
67
+ */
68
+ function goNativeResolveHead(comp, plan, typedNodes, isPrior, opts) {
69
+ return (head) => {
70
+ if (opts?.asBinding !== undefined && opts.asBase !== undefined && head === opts.asBinding.name) {
71
+ return { expr: opts.asBase, ref: opts.asBinding.ref };
163
72
  }
73
+ if (isPrior(head))
74
+ return { expr: typedLocal(head), ref: typedNodes.get(head) };
75
+ return goInputHeadRef(head, comp, plan);
76
+ };
77
+ }
78
+ /**
79
+ * compilePortNode — lower a port expression to its native ports-struct field via the runtime-free
80
+ * 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.
87
+ */
88
+ function compilePortNode(node, comp, plan, typedNodes, isPrior, where, opts) {
89
+ const resolveHead = goNativeResolveHead(comp, plan, typedNodes, isPrior, opts);
90
+ 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
+ return new NativeExprCompiler(makeGoExprBackend("", resolveHead, plan)).compilePort(node);
164
94
  }
165
- else if (e.path.length === 1) {
166
- const s = inputPorts[e.path[0]];
167
- if (s !== undefined && s.type === "map") {
168
- const et = s.elemType;
169
- if (et !== undefined)
170
- return { kind: "map", value: deriveTypeRef(et, plan) };
171
- }
172
- return null;
95
+ catch (e) {
96
+ if (!(e instanceof GeneratorFailure))
97
+ throw e;
98
+ 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}`);
173
99
  }
174
- if (ref === null)
175
- return null;
176
- if (ref.kind === "map" || ref.kind === "named")
177
- return ref;
178
- return null;
179
100
  }
180
- /** emitCompositePortValue (go twin) — the OWNED native value expr for a `{map:V}`/named port ref: a typed
181
- * field access `<elemBase>.<field>` (native map/struct, ZERO boxed Value). `elemBaseExpr` is the map arm's
182
- * element base for a `$as`-headed ref (omitted at top level — only input map-port refs resolve there). */
183
- function emitCompositePortValue(node, inputPorts, asBinding, plan, elemBaseExpr) {
184
- const e = classifyExpr(node);
185
- if (e.kind !== "ref")
186
- return null;
187
- if (asBinding !== undefined && elemBaseExpr !== undefined && e.path[0] === asBinding.name) {
188
- let acc;
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. */
103
+ function buildTypedNodes(comp, plan) {
104
+ const typedNodes = new Map();
105
+ for (const n of comp.body) {
106
+ if (isControlGate(n))
107
+ continue;
189
108
  try {
190
- acc = goTypedInternals.typedFieldAccess(elemBaseExpr, asBinding.ref, e.path.slice(1), plan);
109
+ typedNodes.set(n.id, "map" in n ? mapNodeArrRef(n, plan) : deriveTypeRef(n.outType, plan));
191
110
  }
192
111
  catch {
193
- return null;
194
- }
195
- if (acc.ref.kind !== "map" && acc.ref.kind !== "named")
196
- return null;
197
- return acc.expr;
198
- }
199
- // a WHOLE-port ref to an input MAP port (with elemType) — valid at top level OR inside a map node (a
200
- // ref head that is NOT the `$as` element binding). Reads the native map[string]V off the input struct.
201
- if ((asBinding === undefined || e.path[0] !== asBinding.name) && e.path.length === 1) {
202
- const s = inputPorts[e.path[0]];
203
- if (s !== undefined && s.type === "map" && s.elemType !== undefined) {
204
- return `in.${goFieldName(e.path[0])}`;
112
+ /* untyped node — omit */
205
113
  }
206
114
  }
207
- return null;
115
+ return typedNodes;
208
116
  }
209
117
  function staticArrElems(node) {
210
118
  if (node === null || typeof node !== "object" || Array.isArray(node))
@@ -215,96 +123,16 @@ function staticArrElems(node) {
215
123
  const arg = node.arr;
216
124
  return Array.isArray(arg) ? arg : null;
217
125
  }
218
- /**
219
- * portArrayElemRef (#110) resolve a componentRef input port that binds a RUNTIME ARRAY (an IN-list /
220
- * array-bound WHERE head) to its native element TypeRef, or null when the port is not a covered array.
221
- * The covered shape is a SINGLE-SEGMENT, non-opt `ref` into an INPUT ARRAY port that carries a resolvable
222
- * `elemType` (scp-ir-architecture.md §5.2 / PortSchema.elemType — BC does NOT infer, consumer-interface
223
- * C3). The port then lowers to a native `[]ElemT` field fed straight from `in.<Field>` ZERO boxed Value
224
- * on the read hot path. This REUSES the bc#108 `elemType` element lowering (deriveTypeRef over the input
225
- * port's elemType), extended from the `map…over` element-source path to a plain componentRef port.
226
- *
227
- * A static string array is a DIFFERENT covered shape ([]string, staticStringArrElems) — handled ahead of
228
- * this. An array port with NO resolvable elemType, an opt/multi-segment ref, or a non-array input schema
229
- * resolves to null here → the caller keeps its existing fail-closed behaviour (never a silent box).
230
- * Requires a TypePlan (an obj elemType maps to a named decl); without one it stays null (fail-closed).
231
- */
232
- function portArrayElemRef(node, inputPorts, plan) {
233
- if (plan === undefined)
234
- return null;
235
- if (staticStringArrElems(node) !== null)
236
- return null; // the static-string-array port ([]string) — not this shape.
237
- const e = classifyExpr(node);
238
- if (e.kind !== "ref" || e.opt || e.path.length !== 1)
239
- return null;
240
- const schema = inputPorts?.[e.path[0]];
241
- if (schema === undefined || (schema.type !== "array" && schema.type !== "arr"))
242
- return null;
243
- const et = schema.elemType;
244
- if (et === undefined)
245
- return null;
246
- return deriveTypeRef(et, plan);
247
- }
248
- /**
249
- * isNativeRawComponent — a component covered by the combined read emitter. Strictly sequential
250
- * (no real concurrency), body is ALL componentRef (no map/cond), default 'fail'/'continue'
251
- * policy, relationKind is single/connection or absent, and EVERY node carries outType + the
252
- * component carries outputType. relationKind:single / bindField ARE the point — inline exec makes
253
- * the child-present decision from the real parent result. Ports AND the output are evaluated via
254
- * the primitive SSoT over a Value-space portScope (so a dynamic operator in a port or an
255
- * obj-assembly output is fine — the SSoT handles fail-closed semantics; only the RESULT plane +
256
- * PORT container go native). A covered SHAPE that lacks outType is a fail-closed error raised at
257
- * emit (see assertTypedCoverage) — never a silent re-box.
258
- */
259
- /**
260
- * numLiteralGoExpr — a BARE numeric port literal (JSON number, e.g. the `limit: 20` Query port).
261
- * `classifyExpr` deliberately leaves a bare number `dynamic` (its checked int/float range rules are
262
- * the evaluate SSoT, expr.go evalNumberLiteral), so a plain number never reaches the str/bool/null
263
- * static branch. But a number literal IS statically resolvable to a native Go Value: the SAME
264
- * range check the runtime applies is performed HERE at generation time (integral literal in the
265
- * safe 2^53-1 window → int64; a fractional/exponent literal → the checked float64). Returns the Go
266
- * expression for the literal's Value, or null when the node is not a bare number OR the integral
267
- * literal is out of the safe window (that out-of-range case keeps the runtime INVALID_LITERAL
268
- * semantics — the port is NOT covered natively, so the whole component falls through, never a
269
- * silently-wrong inline). Mirrors coderuntime.evalNumberLiteral exactly.
270
- */
271
- const NUM_SAFE_INT = 9007199254740991; // 2^53 - 1 (expr.go safeInt)
272
- function numLiteralGoExpr(node) {
273
- if (typeof node !== "number" || !Number.isFinite(node))
274
- return null;
275
- if (Number.isInteger(node)) {
276
- if (node < -NUM_SAFE_INT || node > NUM_SAFE_INT)
277
- return null; // out of safe window → not covered (runtime INVALID_LITERAL)
278
- return `${PKG}.Value(int64(${node}))`;
279
- }
280
- // fractional / exponent literal → checked float64 (finite, already guaranteed above).
281
- return `${PKG}.Value(float64(${node}))`;
282
- }
283
- /** A port node is static (literal / ref / concat of those / static arr / bare number literal / an
284
- * optional-input read) — resolvable to a native Go value with no NewObj / no evaluate interpreter. This
285
- * is the SHAPE gate that decides native ELIGIBILITY; the lowering itself (portFieldGoType / emitPortInit)
286
- * is what proves each shape, and fails closed loudly on one it cannot lower. */
287
- function portIsStatic(node, inputPorts) {
288
- const arr = staticArrElems(node);
289
- if (arr !== null)
290
- return arr.every((el) => portIsStatic(el, inputPorts));
291
- if (numLiteralGoExpr(node) !== null)
292
- return true; // bare number literal (e.g. Query `limit`)
293
- // An optional-input read (`opt($.x)` / `coalesce(opt($.x), 20)` / `ne(opt($.x), null)`) is a
294
- // statically-resolvable port — the opt lane lowers it (or fails closed with the specific reason).
295
- if (inputPorts !== undefined && exprReadsOptionalInput(node, inputPorts))
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. */
129
+ function portIsStatic(node, comp, plan, typedNodes, asBinding) {
130
+ try {
131
+ compilePortNode(node, comp, plan, typedNodes, (h) => typedNodes.has(h), "port", asBinding !== undefined ? { asBinding, asBase: asBinding.name } : undefined);
296
132
  return true;
297
- const e = classifyExpr(node);
298
- switch (e.kind) {
299
- case "str":
300
- case "bool":
301
- case "null":
302
- case "ref":
303
- return true;
304
- case "concat":
305
- return e.parts.every((p) => portIsStatic(reconstructG(p), inputPorts));
306
- default:
307
- return false;
133
+ }
134
+ catch {
135
+ return false; // an eligibility probe: any lowering failure ⇒ not native-covered here.
308
136
  }
309
137
  }
310
138
  /** The output must lower to a typed struct/value with no Value-scope read: a ref whose head is a
@@ -352,8 +180,8 @@ function isFullyTyped(comp) {
352
180
  }
353
181
  return true;
354
182
  }
355
- function isNativeRaw(comp) {
356
- return isSequentialComponentRefRead(comp) && isFullyTyped(comp);
183
+ function isNativeRaw(comp, plan) {
184
+ return isSequentialComponentRefRead(comp, plan) && isFullyTyped(comp);
357
185
  }
358
186
  /**
359
187
  * coveredMapNode — the #86 part 2 covered map shape (nestedBatchGet). A BATCHED `map` node whose:
@@ -368,7 +196,7 @@ function isNativeRaw(comp) {
368
196
  * element struct by copying the over element's typed fields + materializing the into field from the
369
197
  * aligned batch raw — NO boxed Value on any plane.
370
198
  */
371
- function coveredMapNode(n, bodyIds, comp) {
199
+ function coveredMapNode(n, bodyIds, comp, plan, typedNodes) {
372
200
  if (n === null || typeof n !== "object" || !("map" in n))
373
201
  return false;
374
202
  const m = n.map;
@@ -405,11 +233,22 @@ function coveredMapNode(n, bodyIds, comp) {
405
233
  return false;
406
234
  }
407
235
  const ports = m.ports;
236
+ const asBinding = mapAsBinding(comp, n, typedNodes, plan);
408
237
  for (const p of Object.keys(ports))
409
- if (!portIsStatic(ports[p], comp.inputPorts))
238
+ if (!portIsStatic(ports[p], comp, plan, typedNodes, asBinding))
410
239
  return false;
411
240
  return true;
412
241
  }
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
+ }
413
252
  /**
414
253
  * coveredFanoutNode (v3) — a FANOUT node (first-class `@refs` id-list fan-out) is covered when:
415
254
  * - over is a static ref to a PRIOR BODY NODE's typed arr field OR an INPUT array port with elemType,
@@ -418,7 +257,7 @@ function coveredMapNode(n, bodyIds, comp) {
418
257
  * The fanout de-boxes to: one deduped batched dispatch → first-seen dedupe (by the elem's dedupeKey
419
258
  * field) + dangling drop + implicitSource strip + connection wrap {items, cursor:nil} — NO boxed Value.
420
259
  */
421
- function coveredFanoutNode(n, bodyIds, comp) {
260
+ function coveredFanoutNode(n, bodyIds, comp, plan, typedNodes) {
422
261
  if (n === null || typeof n !== "object" || !("fanout" in n))
423
262
  return false;
424
263
  const f = n.fanout;
@@ -438,8 +277,15 @@ function coveredFanoutNode(n, bodyIds, comp) {
438
277
  return false;
439
278
  }
440
279
  const ports = f.ports;
280
+ let asBinding;
281
+ try {
282
+ asBinding = { name: f.as, ref: fanoutOverElemInfo(comp, n, typedNodes, plan).elemRef, plan };
283
+ }
284
+ catch {
285
+ asBinding = undefined;
286
+ }
441
287
  for (const p of Object.keys(ports))
442
- if (!portIsStatic(ports[p], comp.inputPorts))
288
+ if (!portIsStatic(ports[p], comp, plan, typedNodes, asBinding))
443
289
  return false;
444
290
  return true;
445
291
  }
@@ -448,8 +294,8 @@ function coveredFanoutNode(n, bodyIds, comp) {
448
294
  * reads. A real-concurrency plan whose parallel members include a map / bindField-relation child is
449
295
  * NOT covered here (falls through) — the static-parallel orchestration covers the componentRef fan-out
450
296
  * (the GroupAccessRead shape); a parallel map/relation-child stage is a further shape (out of scope). */
451
- function isSequentialComponentRefRead(comp) {
452
- return coverageRejectReason(comp) === null;
297
+ function isSequentialComponentRefRead(comp, plan) {
298
+ return coverageRejectReason(comp, plan) === null;
453
299
  }
454
300
  /**
455
301
  * coverageRejectReason — the DIAGNOSTIC twin of isSequentialComponentRefRead (bc#86/#89). Returns
@@ -459,10 +305,11 @@ function isSequentialComponentRefRead(comp) {
459
305
  * never disagree. This is the message the fail-closed dispatch surfaces per non-native component — the
460
306
  * user's signal to add native coverage, fix a genuinely-dynamic spec, or use the --ir dynamic surface.
461
307
  */
462
- function coverageRejectReason(comp) {
308
+ function coverageRejectReason(comp, plan) {
463
309
  const ep = execPlan(comp);
464
310
  if (ep === null)
465
311
  return "component is not a straight-line/staged sequential exec plan (no static exec plan)";
312
+ const typedNodes = buildTypedNodes(comp, plan);
466
313
  // A parallel-stage member must be a componentRef node (no map) so the static parallel orchestration
467
314
  // is a clean fan-out — the bounded-concurrency error-precedence protocol is proven for that shape.
468
315
  // A map child inside a parallel stage still falls through (its per-element loop is a further shape).
@@ -507,7 +354,7 @@ function coverageRejectReason(comp) {
507
354
  if ("fanout" in n) {
508
355
  // v3: a first-class fanout node (deduped connection list-in fan-out) IS covered when its over is a
509
356
  // covered ref (prior-node arr / input array with elemType) and its element ports are static.
510
- if (!coveredFanoutNode(n, bodyIds, comp))
357
+ if (!coveredFanoutNode(n, bodyIds, comp, plan, typedNodes))
511
358
  return `node '${n.id}' is a non-covered fanout shape (over an input array without elemType, or a dynamic element port)`;
512
359
  continue;
513
360
  }
@@ -515,7 +362,7 @@ function coverageRejectReason(comp) {
515
362
  // #86 pt2 batched map...into / #93 shape 2/3 / #108 guarded map (no into) / map-over-input (with
516
363
  // elemType) ARE covered. A non-covered map shape (guard+into heterogeneous / over-input without
517
364
  // elemType) falls through to the loud fail-closed reject.
518
- if (!coveredMapNode(n, bodyIds, comp))
365
+ if (!coveredMapNode(n, bodyIds, comp, plan, typedNodes))
519
366
  return `node '${n.id}' is a non-covered map shape (guard+into heterogeneous, or over an input array without elemType)`;
520
367
  continue;
521
368
  }
@@ -546,7 +393,7 @@ function coverageRejectReason(comp) {
546
393
  if (cr.relationKind !== undefined && cr.relationKind !== "single" && cr.relationKind !== "connection")
547
394
  return `relationKind '${cr.relationKind}' not native-covered (node '${n.id}')`;
548
395
  for (const p of Object.keys(cr.ports))
549
- if (portIsStatic(cr.ports[p], comp.inputPorts) === false)
396
+ if (portIsStatic(cr.ports[p], comp, plan, typedNodes) === false)
550
397
  return `node '${n.id}' port '${p}' is not statically resolvable`;
551
398
  }
552
399
  if (!outputIsNativeLowerable(comp))
@@ -554,21 +401,21 @@ function coverageRejectReason(comp) {
554
401
  return null;
555
402
  }
556
403
  /** Fail closed if a covered read SHAPE lacks the typed annotations (no silent boxed re-box). */
557
- function assertTypedCoverage(comp) {
558
- if (!isSequentialComponentRefRead(comp))
404
+ function assertTypedCoverage(comp, plan) {
405
+ if (!isSequentialComponentRefRead(comp, plan))
559
406
  return;
560
407
  if (isFullyTyped(comp))
561
408
  return;
562
409
  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).`);
563
410
  }
564
411
  // ── native ports struct + PortReader ──────────────────────────────────────────────────
565
- function emitPortsStruct(comp, node, asBinding, plan, typedNodes) {
412
+ function emitPortsStruct(comp, node, plan, typedNodes, asBinding) {
566
413
  const structName = portsStructName(comp.name, node.id);
567
414
  const portNames = Object.keys(node.ports);
568
415
  if (portNames.length === 0) {
569
416
  return `// ${structName} — native ports for node '${node.id}' (${node.component}); no ports.\ntype ${structName} struct{}`;
570
417
  }
571
- const fieldTypes = portNames.map((p) => portFieldGoType(node.ports[p], comp.inputPorts, `port '${p}' of node '${node.id}'`, asBinding, plan, typedNodes));
418
+ 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));
572
419
  const fieldNames = portNames.map(goPortFieldName);
573
420
  const fields = portNames
574
421
  .map((p, i) => `\t${fieldNames[i]} ${fieldTypes[i]} // ${JSON.stringify(p)}`)
@@ -782,7 +629,7 @@ function emitMapPortsStructs(comp, node, typedNodes, plan) {
782
629
  // struct element). A pure-static/concat port ignores the binding.
783
630
  const { elemRef } = mapOverElemInfo(comp, node, typedNodes, plan);
784
631
  const asBinding = { name: m.as, ref: elemRef, plan };
785
- const elemStruct = emitPortsStruct(comp, { id: node.id, component: m.component, ports: m.ports }, asBinding, plan);
632
+ const elemStruct = emitPortsStruct(comp, { id: node.id, component: m.component, ports: m.ports }, plan, typedNodes, asBinding);
786
633
  const batchStruct = `${structName}_batch`;
787
634
  return `${elemStruct}
788
635
 
@@ -800,7 +647,7 @@ function emitFanoutPortsStructs(comp, node, typedNodes, plan) {
800
647
  const structName = portsStructName(comp.name, node.id);
801
648
  const { elemRef } = fanoutOverElemInfo(comp, node, typedNodes, plan);
802
649
  const asBinding = { name: f.as, ref: elemRef, plan };
803
- const elemStruct = emitPortsStruct(comp, { id: node.id, component: f.component, ports: f.ports }, asBinding, plan);
650
+ const elemStruct = emitPortsStruct(comp, { id: node.id, component: f.component, ports: f.ports }, plan, typedNodes, asBinding);
804
651
  const batchStruct = `${structName}_batch`;
805
652
  return `${elemStruct}
806
653
 
@@ -947,43 +794,6 @@ function mapOverElemInfo(comp, node, typedNodes, plan) {
947
794
  function mapOverElemType(comp, node, typedNodes, plan) {
948
795
  return renderTypeRef(mapOverElemInfo(comp, node, typedNodes, plan).elemRef);
949
796
  }
950
- /**
951
- * priorNodeArrElemInfo (#129) — resolve a leaf port that is a `ref` into a PRIOR BODY NODE whose
952
- * annotated `outType` is (or reaches, through a field path) a native array, to its element TypeRef +
953
- * the native Go slice expression that yields it. This GENERALIZES the `map…over` prior-node-arr
954
- * dataflow (mapOverElemInfo's first branch) to ANY leaf input port: a fold/dedup leaf whose `items`
955
- * port is `{ref:["<priorNode>"]}` (or `.items` of a prior connection node) now lowers to `[]ElemT`
956
- * fed straight from the prior node's typed local — ZERO boxed Value on the read hot path.
957
- *
958
- * The element type comes STRICTLY from the prior node's EXPLICIT `outType` annotation walked through
959
- * typedFieldAccess (BC does NOT infer — consumer-interface C3 / [[graphddb-typed-native-static-link]]):
960
- * if the head is not a prior body node, or its outType (through the field path) is not an `arr`, this
961
- * returns null and the caller keeps its existing fail-closed behaviour (never a silent box). A
962
- * single-segment INPUT-array port (elemType) is a DIFFERENT covered shape (portArrayElemRef) — handled
963
- * ahead of this. Requires the typedNodes map (prior-node outType TypeRefs) and a TypePlan.
964
- */
965
- function priorNodeArrElemInfo(node, typedNodes, plan) {
966
- if (typedNodes === undefined || plan === undefined)
967
- return null;
968
- if (staticArrElems(node) !== null)
969
- return null; // a static array literal — not a prior-node ref.
970
- const e = classifyExpr(node);
971
- if (e.kind !== "ref" || e.opt || e.path.length < 1)
972
- return null;
973
- if (!typedNodes.has(e.path[0]))
974
- return null; // head is not a prior body node → not this shape.
975
- const baseRef = typedNodes.get(e.path[0]);
976
- let acc;
977
- try {
978
- acc = goTypedInternals.typedFieldAccess(typedLocal(e.path[0]), baseRef, e.path.slice(1), plan);
979
- }
980
- catch {
981
- return null; // the field path does not resolve on the prior node's outType → fail-closed at caller.
982
- }
983
- if (acc.ref.kind !== "arr")
984
- return null; // the prior-node result (through the path) is not an array.
985
- return { elemRef: acc.ref.elem, overExpr: acc.expr };
986
- }
987
797
  function classifyPort(node) {
988
798
  if (staticArrElems(node) !== null)
989
799
  return "static";
@@ -1178,124 +988,18 @@ function emitInDecoder(comp, plan) {
1178
988
  lines.push(`}`);
1179
989
  return lines.join("\n");
1180
990
  }
1181
- /** The port forms the STATIC (non-optional) lane lowers — named in its reject message so the message
1182
- * describes what is actually covered rather than a stale corpus list. */
1183
- const GO_STATIC_PORT_FORMS = "This port does not read an optional input, so it lowers through the STATIC port forms: a string / bool / number literal, a static string-array (projection), a ref to a REQUIRED scalar input port, a ref to an input array/map port or a prior node's array (declared elemType), a ref to a `$as` element field, or a concat of native strings. A dynamic operator at port position is not covered on this lane. (A port that DOES read an optional input lowers through the full native expression compiler, which admits the whole closed operator set — so the covered operator set at port position currently depends on the lane, not on the operator.)";
1184
991
  /**
1185
- * emitNativeScalarlower a static port expr to a NATIVE Go expression of the concrete `want` scalar
1186
- * type, or null if it cannot be statically proven that type. Literals: a string literal is native for
1187
- * `want==="string"`, a bool literal for `want==="bool"`. A ref resolves via `resolveRef` (typed input
1188
- * field / prior-node scalar / element binding). A concat lowers ONLY into `string` and only when EVERY
1189
- * part is itself a native string (so the concat's strings-only TYPE_MISMATCH can never fire — the parts
1190
- * are statically strings), emitting `p0 + p1 + …`. Returns null (fall back to the Value path) otherwise.
992
+ * emitPortInitthe 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.
1191
997
  */
1192
- function emitNativeScalar(node, want, resolveRef) {
1193
- // a static arr is a Value port (never a scalar) — not native-lowerable here.
1194
- if (staticArrElems(node) !== null)
1195
- return null;
1196
- const e = classifyExpr(node);
1197
- switch (e.kind) {
1198
- case "str":
1199
- return want === "string" ? JSON.stringify(e.value) : null;
1200
- case "bool":
1201
- return want === "bool" ? (e.value ? "true" : "false") : null;
1202
- case "null":
1203
- return null; // a null literal is a Value(nil), never a required native scalar
1204
- case "ref": {
1205
- const sc = resolveRef(e.path[0], e.path.slice(1), e.opt);
1206
- if (sc === null || sc.scalar !== want)
1207
- return null;
1208
- return sc.expr;
1209
- }
1210
- case "concat": {
1211
- if (want !== "string")
1212
- return null;
1213
- const parts = [];
1214
- for (const p of e.parts) {
1215
- const sub = emitNativeScalar(reconstructG(p), "string", resolveRef);
1216
- if (sub === null)
1217
- return null;
1218
- // parenthesize a `+` sub-expression so string concatenation binds correctly (a ref/literal is
1219
- // atomic; a nested concat is already a single `a + b` — wrap to keep it one operand).
1220
- parts.push(sub.includes(" + ") ? `(${sub})` : sub);
1221
- }
1222
- return parts.join(" + ");
1223
- }
1224
- default:
1225
- return null;
1226
- }
1227
- }
1228
- /**
1229
- * emitPortInit — emit the port field build for one port as a FULLY NATIVE Go expression (bc#90 /
1230
- * runtime-free). Every covered port lowers to a concrete Go value assigned straight into the ports
1231
- * struct field — NO `dslcontracts.Value`, NO nvVE helper, NO `.(T)` type-assert:
1232
- * - a static string array (projection) → `[]string{"a", "b", ...}` (change #2);
1233
- * - a bare int/float number literal (e.g. Query `limit: 20`) → `int64(20)` / `float64(...)`;
1234
- * - a string/bool/scalar-input port → the native scalar expression (emitNativeScalar).
1235
- * A port that CANNOT lower natively FAILS CLOSED (there is no boxed Value fallback on the covered
1236
- * plane — a boxed escape would re-introduce the bc-runtime coupling this module removes). Returns the
1237
- * struct field initializer (`Field: <expr>`). `resolveRef` resolves a ref head for the scalar path.
1238
- */
1239
- function emitPortInit(pn, expr, fieldGoType, resolveRef, inputPorts, plan, typedNodes, asBinding, elemBaseExpr) {
1240
- const fieldName = goPortFieldName(pn);
1241
- // The OPTIONAL-input lane (*T pass-through / coalesce default) — same shared compile as
1242
- // portFieldGoType, so the field type and its initializer are derived from ONE lowering.
1243
- const optPort = optPortCompileG(expr, inputPorts ?? {}, `port '${pn}'`, plan);
1244
- if (optPort !== null)
1245
- return `${fieldName}: ${optPort.expr}`;
1246
- // #108-map: a port that is a ref to a `$as` element MAP/NAMED field (or an input map port) → the typed
1247
- // composite value directly off the element/input (native map[string]V / struct — ZERO boxed Value).
1248
- if (plan !== undefined) {
1249
- const compExpr = emitCompositePortValue(expr, inputPorts ?? {}, asBinding, plan, elemBaseExpr);
1250
- if (compExpr !== null)
1251
- return `${fieldName}: ${compExpr}`;
1252
- }
1253
- // #110: a componentRef port bound to a runtime array input port (with elemType) → the native []ElemT,
1254
- // read straight from the concrete input struct (`in.<Field>`) — ZERO boxed Value on the read hot path.
1255
- // The over-elem/element-source case (map…over) already resolves via mapOverElemInfo; this is the plain
1256
- // componentRef port (an IN-list / array-bound WHERE head).
1257
- if (inputPorts !== undefined && portArrayElemRef(expr, inputPorts, plan) !== null) {
1258
- const e = classifyExpr(expr);
1259
- if (e.kind === "ref" && e.path.length === 1) {
1260
- return `${fieldName}: in.${goFieldName(e.path[0])}`;
1261
- }
1262
- }
1263
- // #129: a leaf port that is a ref into a PRIOR NODE's typed arr outType (node→leaf array dataflow) →
1264
- // the native []ElemT read straight off the prior node's typed local (`t_<node>.Field…`) — ZERO boxed
1265
- // Value on the read hot path. The over-source case (map…over) resolves via mapOverElemInfo; this is
1266
- // the plain leaf array input port (a fold/dedup leaf whose `items` = a prior node's array result).
1267
- const priorArr = priorNodeArrElemInfo(expr, typedNodes, plan);
1268
- if (priorArr !== null)
1269
- return `${fieldName}: ${priorArr.overExpr}`;
1270
- // change #2: a static string array (projection) → a native `[]string{...}` literal.
1271
- const strArr = staticStringArrElems(expr);
1272
- if (strArr !== null) {
1273
- if (fieldGoType !== "[]string") {
1274
- throw new GeneratorFailure("UNSUPPORTED_NODE_STRAIGHTLINE", `go combined read (bc#90): port '${pn}' is a static string array but its field type is '${fieldGoType}' (expected []string).`);
1275
- }
1276
- return `${fieldName}: []string{${strArr.map((s) => JSON.stringify(s)).join(", ")}}`;
1277
- }
1278
- // a bare number literal (e.g. Query `limit: 20`): the SSoT range check ran in numLiteralGoExpr —
1279
- // emit the concrete native int64/float64 (unwrap the dslcontracts.Value(...) box: it was only there
1280
- // for the old boxed path; the range check is what matters, and it already passed).
1281
- const num = numLiteralGoExpr(expr);
1282
- if (num !== null) {
1283
- if (fieldGoType === "int64")
1284
- return `${fieldName}: int64(${expr})`;
1285
- if (fieldGoType === "float64")
1286
- return `${fieldName}: float64(${expr})`;
1287
- throw new GeneratorFailure("UNSUPPORTED_NODE_STRAIGHTLINE", `go combined read (bc#90): port '${pn}' is a number literal but its field type is '${fieldGoType}'.`);
1288
- }
1289
- // string / bool / scalar-input / concat → native scalar expression (no Value, no `.(T)`).
1290
- const scalarWant = fieldGoType;
1291
- const native = emitNativeScalar(expr, scalarWant, resolveRef);
1292
- if (native !== null) {
1293
- return `${fieldName}: ${native}`;
1294
- }
1295
- // FAIL CLOSED: the covered read plane is runtime-free — no boxed Value fallback (that would
1296
- // re-couple the covered module to bc-runtime). If a genuinely-dynamic port reaches here, the
1297
- // component is not native-eligible and must regenerate on the boxed straight-line path.
1298
- throw new GeneratorFailure("UNSUPPORTED_NODE_STRAIGHTLINE", `go combined read (bc#90 / runtime-free): port '${pn}' (${JSON.stringify(expr)}) does not lower to a native Go value of type '${fieldGoType}'. The covered read plane admits NO boxed dslcontracts.Value escape. ${GO_STATIC_PORT_FORMS} Regenerate on the boxed straight-line path if this port is genuinely dynamic.`);
998
+ function emitPortInit(pn, expr, comp, plan, typedNodes, isPrior, opts) {
999
+ const c = compilePortNode(expr, comp, plan, typedNodes, isPrior, `port '${pn}'`, opts);
1000
+ 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.`);
1002
+ return `${goPortFieldName(pn)}: ${c.expr}`;
1299
1003
  }
1300
1004
  /**
1301
1005
  * emitParallelStageArm (bc#87) — EXPLICIT static parallel orchestration for a real-concurrency stage.
@@ -1318,7 +1022,7 @@ function emitPortInit(pn, expr, fieldGoType, resolveRef, inputPorts, plan, typed
1318
1022
  *
1319
1023
  * NO RunPlan / []OpSpec / planSpec walk: the staging, the bound, and which ops are parallel are baked.
1320
1024
  */
1321
- function emitParallelStageArm(comp, stage, atPos, concurrency, metas, typedNodes, plan, priorNodeAt, zeroOut, flags, resolveRefAt) {
1025
+ function emitParallelStageArm(comp, stage, atPos, concurrency, metas, typedNodes, plan, priorNodeAt, zeroOut, flags) {
1322
1026
  const lines = [];
1323
1027
  const ids = stage.map((i) => comp.body[i].id);
1324
1028
  lines.push(`\t// ── bc#87 real-concurrency stage {${ids.map((x) => JSON.stringify(x)).join(", ")}} — static parallel`);
@@ -1407,11 +1111,8 @@ function emitParallelStageArm(comp, stage, atPos, concurrency, metas, typedNodes
1407
1111
  }
1408
1112
  }
1409
1113
  const inits = [];
1410
- const resolveRef = resolveRefAt(atPos);
1411
1114
  for (const pn of Object.keys(node.ports)) {
1412
- const expr = node.ports[pn];
1413
- const fieldGoType = portFieldGoType(expr, comp.inputPorts, `port '${pn}' of node '${node.id}'`, undefined, plan, typedNodes);
1414
- inits.push(emitPortInit(pn, expr, fieldGoType, resolveRef, comp.inputPorts, plan, typedNodes));
1115
+ inits.push(emitPortInit(pn, node.ports[pn], comp, plan, typedNodes, (h) => priorNodeAt(h, atPos)));
1415
1116
  }
1416
1117
  lines.push(`${ind}ps_${s} = ${structName}{${inits.join(", ")}}`);
1417
1118
  lines.push(`${ind}run_${s} = true`);
@@ -1518,35 +1219,6 @@ function emitNativeRawRunner(comp, plan, flags) {
1518
1219
  // assert on a row value — the runner reads row.<Field> directly and copies each into the outType local.
1519
1220
  lines.push(`func ${runnerName(comp.name)}[H ${handlerIfaceName(comp.name)}](handlers H, in ${inStructName(comp.name)}) (${outStructType}, error) {`);
1520
1221
  lines.push(`\t_ = in`);
1521
- // resolveNativeRef for this runner: a ref head is either a PRIOR NODE (read its typed struct field
1522
- // directly) or an INPUT PORT (read the typed `in.<Field>`). A REQUIRED scalar resolves to a native
1523
- // Go expression; an opt / non-scalar / refOpt resolves to null (falls back to the nvVE Value path).
1524
- const resolveRefAt = (atPos) => (head, restPath, opt) => {
1525
- if (opt)
1526
- return null; // a refOpt may be nil at runtime — keep the Value path (opt-nil semantics)
1527
- if (priorNodeAt(head, atPos)) {
1528
- const baseRef = typedNodes.get(head);
1529
- let acc;
1530
- let expr;
1531
- try {
1532
- const r = goTypedInternals.typedFieldAccess(typedLocal(head), baseRef, restPath, plan);
1533
- expr = r.expr;
1534
- acc = r.ref;
1535
- }
1536
- catch {
1537
- return null;
1538
- }
1539
- if (acc.kind !== "scalar" || acc.scalar === "null")
1540
- return null;
1541
- return { expr, scalar: goScalar(acc.scalar) };
1542
- }
1543
- if (restPath.length !== 0)
1544
- return null; // input.<field> path is a Value walk — not a native scalar
1545
- const scalar = inputScalarKind(comp.inputPorts?.[head]);
1546
- if (scalar === undefined)
1547
- return null;
1548
- return { expr: `in.${goFieldName(head)}`, scalar };
1549
- };
1550
1222
  for (const k of order.keys()) {
1551
1223
  const node = comp.body[order[k]];
1552
1224
  const id = node.id;
@@ -1569,7 +1241,7 @@ function emitNativeRawRunner(comp, plan, flags) {
1569
1241
  // ascending-sorted stages), so we emit the block at the first member and skip the rest.
1570
1242
  const stage = ep.parallelStageOf.get(i);
1571
1243
  if (stage !== undefined && stage[0] === i) {
1572
- lines.push(emitParallelStageArm(comp, stage, k, ep.concurrency, metas, typedNodes, plan, priorNodeAt, zeroOut, flags, resolveRefAt));
1244
+ lines.push(emitParallelStageArm(comp, stage, k, ep.concurrency, metas, typedNodes, plan, priorNodeAt, zeroOut, flags));
1573
1245
  k += stage.length - 1;
1574
1246
  continue;
1575
1247
  }
@@ -1641,11 +1313,8 @@ function emitNativeRawRunner(comp, plan, flags) {
1641
1313
  // dslcontracts.Value, ZERO nvVE helper, ZERO `.(T)`. A genuinely-dynamic port FAILS CLOSED.
1642
1314
  const inits = [];
1643
1315
  const boundArg = node.bindField !== undefined ? `bound_${s}` : "nil";
1644
- const resolveRef = resolveRefAt(k);
1645
1316
  for (const pn of portNames) {
1646
- const expr = node.ports[pn];
1647
- const fieldGoType = portFieldGoType(expr, comp.inputPorts, `port '${pn}' of node '${node.id}'`, undefined, plan, typedNodes);
1648
- inits.push(emitPortInit(pn, expr, fieldGoType, resolveRef, comp.inputPorts, plan, typedNodes));
1317
+ inits.push(emitPortInit(pn, node.ports[pn], comp, plan, typedNodes, (h) => priorNodeAt(h, k)));
1649
1318
  }
1650
1319
  lines.push(`${indent}ports_${s} := ${structName}{${inits.join(", ")}}`);
1651
1320
  const oc = `row_${s}`;
@@ -1738,67 +1407,15 @@ function emitMapArm(comp, node, atPos, typedNodes, plan, priorNodeAt, zeroOut, f
1738
1407
  const mkGuard = (elemVar) => {
1739
1408
  if (!hasGuard)
1740
1409
  return null;
1741
- const resolveHead = (head) => {
1742
- if (head === asName)
1743
- return { expr: elemVar, ref: overElemRef };
1744
- if (priorHere(head))
1745
- return { expr: typedLocal(head), ref: typedNodes.get(head) };
1746
- return goInputHeadRef(head, comp, plan);
1747
- };
1748
- const be = makeGoExprBackend(zeroOut, resolveHead, plan);
1749
- return new NativeExprCompiler(be).compileBool(m.when);
1410
+ 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);
1750
1412
  };
1751
1413
  // emit the per-element native ports struct build (shared; `il` is the indent inside the element
1752
1414
  // loop, `elemVar` the over element loop variable). Returns the lines building `ep_<s>`.
1753
1415
  const buildPorts = (il, elemVar) => {
1754
- const out = [];
1755
- const inits = [];
1756
- // resolveNativeRef for a map element port: a ref head is the element binding ($as → the over element
1757
- // typed struct field `elemVar.<Field>`), a PRIOR NODE (its typed struct field), or an INPUT PORT
1758
- // (`in.<Field>`). A REQUIRED scalar lowers to a native Go expr; opt / non-scalar → null (Value path).
1759
- const resolveRef = (head, restPath, opt) => {
1760
- if (opt)
1761
- return null;
1762
- let baseExpr;
1763
- let baseRef;
1764
- if (head === asName) {
1765
- baseExpr = elemVar;
1766
- baseRef = overElemRef;
1767
- }
1768
- else if (priorHere(head)) {
1769
- baseExpr = typedLocal(head);
1770
- baseRef = typedNodes.get(head);
1771
- }
1772
- else {
1773
- if (restPath.length !== 0)
1774
- return null;
1775
- const scalar = inputScalarKind(comp.inputPorts?.[head]);
1776
- if (scalar === undefined)
1777
- return null;
1778
- return { expr: `in.${goFieldName(head)}`, scalar };
1779
- }
1780
- let acc;
1781
- let expr;
1782
- try {
1783
- const r = goTypedInternals.typedFieldAccess(baseExpr, baseRef, restPath, plan);
1784
- expr = r.expr;
1785
- acc = r.ref;
1786
- }
1787
- catch {
1788
- return null;
1789
- }
1790
- if (acc.kind !== "scalar" || acc.scalar === "null")
1791
- return null;
1792
- return { expr, scalar: goScalar(acc.scalar) };
1793
- };
1794
1416
  const asBinding = { name: asName, ref: overElemRef, plan };
1795
- for (const pn of portNames) {
1796
- const fieldGoType = portFieldGoType(m.ports[pn], comp.inputPorts, `map port '${pn}' of node '${node.id}'`, asBinding, plan);
1797
- // #108-map: pass the element binding + base expr so a `$as` MAP/NAMED field port reads the typed composite.
1798
- inits.push(emitPortInit(pn, m.ports[pn], fieldGoType, resolveRef, comp.inputPorts, plan, undefined, asBinding, elemLocal(s)));
1799
- }
1800
- out.push(`${il}ep_${s} := ${structName}{${inits.join(", ")}}`);
1801
- return out;
1417
+ const inits = portNames.map((pn) => emitPortInit(pn, m.ports[pn], comp, plan, typedNodes, priorHere, { asBinding, asBase: elemVar }));
1418
+ return [`${il}ep_${s} := ${structName}{${inits.join(", ")}}`];
1802
1419
  };
1803
1420
  const lines = [];
1804
1421
  const closers = [];
@@ -1936,47 +1553,8 @@ function emitFanoutArm(comp, node, atPos, typedNodes, plan, priorNodeAt, zeroOut
1936
1553
  const dkGo = goFieldName(f.dedupeKey);
1937
1554
  // per-id native ports build (same as a batched map's per-element ports). $as → the over id value.
1938
1555
  const buildPorts = (il, elemVar) => {
1939
- const inits = [];
1940
- const resolveRef = (head, restPath, opt) => {
1941
- if (opt)
1942
- return null;
1943
- let baseExpr;
1944
- let baseRef;
1945
- if (head === asName) {
1946
- baseExpr = elemVar;
1947
- baseRef = overElemRef;
1948
- }
1949
- else if (priorHere(head)) {
1950
- baseExpr = typedLocal(head);
1951
- baseRef = typedNodes.get(head);
1952
- }
1953
- else {
1954
- if (restPath.length !== 0)
1955
- return null;
1956
- const scalar = inputScalarKind(comp.inputPorts?.[head]);
1957
- if (scalar === undefined)
1958
- return null;
1959
- return { expr: `in.${goFieldName(head)}`, scalar };
1960
- }
1961
- let acc;
1962
- let expr;
1963
- try {
1964
- const r = goTypedInternals.typedFieldAccess(baseExpr, baseRef, restPath, plan);
1965
- expr = r.expr;
1966
- acc = r.ref;
1967
- }
1968
- catch {
1969
- return null;
1970
- }
1971
- if (acc.kind !== "scalar" || acc.scalar === "null")
1972
- return null;
1973
- return { expr, scalar: goScalar(acc.scalar) };
1974
- };
1975
1556
  const asBinding = { name: asName, ref: overElemRef, plan };
1976
- for (const pn of portNames) {
1977
- const fieldGoType = portFieldGoType(f.ports[pn], comp.inputPorts, `fanout port '${pn}' of node '${node.id}'`, asBinding, plan);
1978
- inits.push(emitPortInit(pn, f.ports[pn], fieldGoType, resolveRef, comp.inputPorts, plan));
1979
- }
1557
+ const inits = portNames.map((pn) => emitPortInit(pn, f.ports[pn], comp, plan, typedNodes, priorHere, { asBinding, asBase: elemVar }));
1980
1558
  return [`${il}ep_${s} := ${structName}{${inits.join(", ")}}`];
1981
1559
  };
1982
1560
  const lines = [];
@@ -2045,59 +1623,6 @@ function goInputHeadRef(head, comp, plan) {
2045
1623
  return null;
2046
1624
  return { expr: `in.${goFieldName(head)}`, ref };
2047
1625
  }
2048
- /**
2049
- * optPortCompileG — the OPTIONAL-input port lane (go). The rust twin's mirror: a component may
2050
- * declare an input port `{opt:T}` (PortSchema `required:false`), and the only native representation of
2051
- * "the value is absent" is `*T`, so such a port never lowers through the required-scalar inference. The
2052
- * port expression compiles with the SHARED runtime-free native-expr compiler (native-expr.ts — the same
2053
- * traversal, type-inference and fail-closed discipline rust uses, so the two agree by construction):
2054
- *
2055
- * - `opt($.x)` / `$.x` at port position → `*T` (the leaf receives the absent value as null, exactly
2056
- * as run_behavior's `evalPorts` passes it).
2057
- * - `coalesce(opt($.x), <literal>)` → the native deref-or-default — a native `T`, no boxed Value.
2058
- * - `ne(opt($.x), null)` / `eq(…, null)` → the native presence test (`!= nil` / `== nil`).
2059
- *
2060
- * Returns null when the port expression reads NO optional input (the caller keeps its existing lowering).
2061
- * A port that DOES read one but does not compile — or that compiles to a fallible expression, which this
2062
- * position cannot hoist — FAILS CLOSED loudly rather than degrade to a required type or a zero value.
2063
- */
2064
- function optPortCompileG(node, inputPorts, where, plan) {
2065
- if (plan === undefined)
2066
- return null;
2067
- if (!exprReadsOptionalInput(node, inputPorts))
2068
- return null;
2069
- const resolveHead = (head) => goInputHeadRef(head, { inputPorts }, plan);
2070
- let c;
2071
- try {
2072
- // zeroOut is unreachable here: a port expression that needs an error return is rejected below
2073
- // (stmts.length > 0), so the backend never renders an early-return with it.
2074
- c = new NativeExprCompiler(makeGoExprBackend("", resolveHead, plan)).compilePort(node);
2075
- }
2076
- catch (e) {
2077
- if (!(e instanceof GeneratorFailure))
2078
- throw e;
2079
- throw new GeneratorFailure("UNSUPPORTED_NODE_STRAIGHTLINE", `go typed-native (runtime-free): ${where} ${JSON.stringify(node)} reads an OPTIONAL input port but does not lower to a native Go value: ${e.message}`);
2080
- }
2081
- if (c.stmts.length > 0)
2082
- throw new GeneratorFailure("UNSUPPORTED_NODE_STRAIGHTLINE", `go typed-native (runtime-free): ${where} ${JSON.stringify(node)} reads an OPTIONAL input port through 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.`);
2083
- return c;
2084
- }
2085
- /** exprReadsOptionalInput — does this port expression reference an OPTIONAL (`required:false`) input
2086
- * port? Language-neutral scan for `ref`/`refOpt` heads bound to an optional port. Decides ONLY which
2087
- * lane a port takes; the lane itself then proves the lowering (or fails closed). */
2088
- function exprReadsOptionalInput(node, inputPorts) {
2089
- if (node === null || typeof node !== "object")
2090
- return false;
2091
- if (Array.isArray(node))
2092
- return node.some((el) => exprReadsOptionalInput(el, inputPorts));
2093
- const rec = node;
2094
- const keys = Object.keys(rec);
2095
- if (keys.length === 1 && (keys[0] === "ref" || keys[0] === "refOpt")) {
2096
- const path = rec[keys[0]];
2097
- return Array.isArray(path) && typeof path[0] === "string" && inputPortIsOptional(inputPorts?.[path[0]]);
2098
- }
2099
- return keys.some((k) => exprReadsOptionalInput(rec[k], inputPorts));
2100
- }
2101
1626
  /**
2102
1627
  * emitCondArm — the struct-native exec of a covered cond node (#108). Mirrors run_behavior's cond
2103
1628
  * lower (behavior.ts:425-429): compile `if` to a native bool, materialize the TAKEN branch to the
@@ -2110,11 +1635,7 @@ function emitCondArm(comp, node, atPos, typedNodes, plan, priorNodeAt, zeroOut)
2110
1635
  const c = node.cond;
2111
1636
  const s = sanitize(node.id);
2112
1637
  const priorHere = (head) => priorNodeAt(head, atPos);
2113
- const resolveHead = (head) => {
2114
- if (priorHere(head))
2115
- return { expr: typedLocal(head), ref: typedNodes.get(head) };
2116
- return goInputHeadRef(head, comp, plan);
2117
- };
1638
+ const resolveHead = goNativeResolveHead(comp, plan, typedNodes, priorHere);
2118
1639
  const be = makeGoExprBackend(zeroOut, resolveHead, plan);
2119
1640
  const compiler = new NativeExprCompiler(be);
2120
1641
  const cond = compiler.compileBool(c.if);
@@ -2337,6 +1858,9 @@ function makeGoExprBackend(zeroOut, resolveHead, plan) {
2337
1858
  notOp: (a) => `(!${a})`,
2338
1859
  structLit: (name, inits) => `${name}{${inits.map((i) => `${goFieldName(i.field)}: ${i.expr}`).join(", ")}}`,
2339
1860
  arrLit: (elemTy, items) => `[]${elemTy}{${items.join(", ")}}`,
1861
+ // a projection port of static string literals — bare `[]string{"a", "b"}` (Go string literals are
1862
+ // already allocation-free; no divergence from the general arr path, so no golden churn).
1863
+ staticStrArr: (literals) => ({ expr: `[]string{${literals.map((s) => JSON.stringify(s)).join(", ")}}`, type: "[]string" }),
2340
1864
  // an opt (`*T`) NONE literal is `nil` (the inner type is not needed for the untyped nil in Go).
2341
1865
  optNone: (_innerTy) => "nil",
2342
1866
  // opt (`*T`) null-presence test: present = `(expr != nil)`, absent = `(expr == nil)`.
@@ -2539,12 +2063,14 @@ var ExpectedSpecVersions = map[string]int64{"behavior": ${sv.behavior}, "express
2539
2063
  }
2540
2064
  // ── module assembly ──────────────────────────────────────────────────────────────────
2541
2065
  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);
2542
2069
  // Fail closed if a covered read SHAPE lacks the typed annotations (no silent re-box).
2543
2070
  for (const c of ctx.ir.components)
2544
- assertTypedCoverage(c);
2545
- const native = ctx.ir.components.filter(isNativeRaw);
2546
- const nonNative = ctx.ir.components.filter((c) => !isNativeRaw(c));
2547
- const plan = buildTypePlan(ctx.ir);
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));
2548
2074
  const decls = emitTypeDecls(plan);
2549
2075
  // #108: reset the native-expr helper-usage accumulator (set by the cond/guard compiler when it emits
2550
2076
  // a fallible checked-arith helper call — determines whether the helper library + `math` import ship).
@@ -2559,11 +2085,7 @@ function emit(ctx) {
2559
2085
  const runnerCodes = [];
2560
2086
  for (const c of native) {
2561
2087
  inStructs.push(emitInStruct(c, plan));
2562
- const typedNodes = new Map();
2563
- // build the full typed-node map FIRST so a map's over-element resolution can see every node.
2564
- for (const n of c.body)
2565
- if (!isControlGate(n))
2566
- typedNodes.set(n.id, "map" in n ? mapNodeArrRef(n, plan) : deriveTypeRef(n.outType, plan)); // #125: control-gate typeless; map は produced-array ref に正規化
2088
+ const typedNodes = buildTypedNodes(c, plan);
2567
2089
  for (const n of c.body) {
2568
2090
  if ("fanout" in n) {
2569
2091
  // v3: a fanout node emits a per-id element ports struct + a batch struct (like a batched map).
@@ -2579,7 +2101,7 @@ function emit(ctx) {
2579
2101
  }
2580
2102
  else {
2581
2103
  // #129: pass typedNodes so a leaf array port fed by a prior node's arr outType types as []ElemT.
2582
- structs.push(emitPortsStruct(c, n, undefined, plan, typedNodes));
2104
+ structs.push(emitPortsStruct(c, n, plan, typedNodes));
2583
2105
  }
2584
2106
  }
2585
2107
  // CONCRETE per-node row structs + the per-component handler interface (the fully-concrete seam).
@@ -2608,7 +2130,7 @@ function emit(ctx) {
2608
2130
  // minimal, runtime-free header.
2609
2131
  if (nonNative.length > 0) {
2610
2132
  const rejects = nonNative
2611
- .map((c) => ` - component '${c.name}': ${coverageRejectReason(c) ?? "not a covered native read"}`)
2133
+ .map((c) => ` - component '${c.name}': ${coverageRejectReason(c, plan) ?? "not a covered native read"}`)
2612
2134
  .join("\n");
2613
2135
  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` +
2614
2136
  `The --typed-native (native codegen) surface is native-only and fail-closed: it does not silently ` +
@@ -2666,8 +2188,8 @@ function emit(ctx) {
2666
2188
  */
2667
2189
  export function goTypedNativeObserve(ir, runtimeImport) {
2668
2190
  const components = ir.components;
2669
- const native = components.filter(isNativeRaw);
2670
2191
  const plan = buildTypePlan(ir);
2192
+ const native = components.filter((c) => isNativeRaw(c, plan));
2671
2193
  const serializers = emitSerializers(plan);
2672
2194
  const marshallers = goTypedInternals.emitMarshallers(plan);
2673
2195
  const mustHelpers = emitMustHelpers(plan);
@@ -2802,8 +2324,13 @@ ${native.map((c) => `\t${handlerIfaceName(c.name)}`).join("\n")}
2802
2324
  // compiles clean / unused-param-free).
2803
2325
  const echoable = echoablePorts(c, node, ref, plan, typedNodes);
2804
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)}}`;
2805
2332
  const echoBranch = echoable
2806
- .map((ep) => `\tif src.echo == ${JSON.stringify(ep)} {\n\t\treturn ${rowT}{Val: ports.${goPortFieldName(ep)}}, true\n\t}\n`)
2333
+ .map((ep) => `\tif src.echo == ${JSON.stringify(ep)} {\n\t\treturn ${echoRow(ep)}, true\n\t}\n`)
2807
2334
  .join("");
2808
2335
  methodImpls.push(`func (s *ScriptedNativeRaw) ${nodeMethodName(c.name, node.id)}(${portsParam} ${portsStructName(c.name, node.id)}, _ ${nodeBoundT}) (${rowT}, bool) {
2809
2336
  src, ok := s.next(${JSON.stringify(node.component)})
@@ -2953,14 +2480,12 @@ ${arms}
2953
2480
  `;
2954
2481
  }
2955
2482
  /** echoablePorts (TEST glue) — the node's ports whose NATIVE type is exactly the node's outType, in
2956
- * declaration order. Those are the ports a scripted `echo: "<port>"` can return as the row's Val. A port
2957
- * with a different type is not echoable (the row could not hold it), and a NAMED outType has no Val at
2958
- * all (its row carries the struct's fields flattened see emitDecodeRow). */
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). */
2959
2486
  function echoablePorts(comp, node, ref, plan, typedNodes) {
2960
- if (ref.kind === "named")
2961
- return [];
2962
2487
  const want = renderTypeRef(ref);
2963
- return Object.keys(node.ports).filter((p) => portFieldGoType(node.ports[p], comp.inputPorts, `port '${p}'`, undefined, plan, typedNodes) === want);
2488
+ return Object.keys(node.ports).filter((p) => portFieldGoType(node.ports[p], comp, plan, typedNodes, `port '${p}'`) === want);
2964
2489
  }
2965
2490
  /** emitDecodeRow — emit (once, deduped) a decoder `decodeRow_<comp>_<node><suffix>(v Value) <rowT>`
2966
2491
  * that materializes the scripted ok Value into the concrete row struct (typed fields = the outType /