behavior-contracts 0.7.3 → 0.8.0

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 (63) hide show
  1. package/README.md +0 -1
  2. package/dist/authoring.d.ts +215 -3
  3. package/dist/authoring.d.ts.map +1 -1
  4. package/dist/authoring.js +269 -8
  5. package/dist/authoring.js.map +1 -1
  6. package/dist/behavior.d.ts +22 -1
  7. package/dist/behavior.d.ts.map +1 -1
  8. package/dist/behavior.js +23 -1
  9. package/dist/behavior.js.map +1 -1
  10. package/dist/bind.d.ts +64 -0
  11. package/dist/bind.d.ts.map +1 -0
  12. package/dist/bind.js +131 -0
  13. package/dist/bind.js.map +1 -0
  14. package/dist/builder.d.ts +30 -88
  15. package/dist/builder.d.ts.map +1 -1
  16. package/dist/builder.js +48 -56
  17. package/dist/builder.js.map +1 -1
  18. package/dist/expr-operator-types.d.ts +89 -0
  19. package/dist/expr-operator-types.d.ts.map +1 -0
  20. package/dist/expr-operator-types.js +134 -0
  21. package/dist/expr-operator-types.js.map +1 -0
  22. package/dist/generator/async-plan.d.ts.map +1 -1
  23. package/dist/generator/async-plan.js +4 -0
  24. package/dist/generator/async-plan.js.map +1 -1
  25. package/dist/generator/core.d.ts +15 -0
  26. package/dist/generator/core.d.ts.map +1 -1
  27. package/dist/generator/core.js +2 -0
  28. package/dist/generator/core.js.map +1 -1
  29. package/dist/generator/emit-php.d.ts.map +1 -1
  30. package/dist/generator/emit-php.js +1 -0
  31. package/dist/generator/emit-php.js.map +1 -1
  32. package/dist/generator/emit-python.d.ts.map +1 -1
  33. package/dist/generator/emit-python.js +1 -0
  34. package/dist/generator/emit-python.js.map +1 -1
  35. package/dist/generator/emit-straightline-typed-typescript.d.ts.map +1 -1
  36. package/dist/generator/emit-straightline-typed-typescript.js +1 -0
  37. package/dist/generator/emit-straightline-typed-typescript.js.map +1 -1
  38. package/dist/generator/emit-typed-native-go.d.ts.map +1 -1
  39. package/dist/generator/emit-typed-native-go.js +83 -12
  40. package/dist/generator/emit-typed-native-go.js.map +1 -1
  41. package/dist/generator/emit-typed-native-rust.d.ts.map +1 -1
  42. package/dist/generator/emit-typed-native-rust.js +83 -20
  43. package/dist/generator/emit-typed-native-rust.js.map +1 -1
  44. package/dist/generator/emit-typescript.d.ts.map +1 -1
  45. package/dist/generator/emit-typescript.js +24 -8
  46. package/dist/generator/emit-typescript.js.map +1 -1
  47. package/dist/generator/index.d.ts +1 -1
  48. package/dist/generator/index.d.ts.map +1 -1
  49. package/dist/generator/index.js +7 -5
  50. package/dist/generator/index.js.map +1 -1
  51. package/dist/index.d.ts +41 -8
  52. package/dist/index.d.ts.map +1 -1
  53. package/dist/index.js +27 -6
  54. package/dist/index.js.map +1 -1
  55. package/dist/provenance.d.ts +102 -0
  56. package/dist/provenance.d.ts.map +1 -0
  57. package/dist/provenance.js +128 -0
  58. package/dist/provenance.js.map +1 -0
  59. package/dist/type-gate.d.ts +63 -0
  60. package/dist/type-gate.d.ts.map +1 -0
  61. package/dist/type-gate.js +295 -0
  62. package/dist/type-gate.js.map +1 -0
  63. package/package.json +4 -3
@@ -178,13 +178,17 @@ function staticStringArrElems(node) {
178
178
  * Value-typed input) FAILS CLOSED — the covered read plane admits NO boxed escape (there is no `Value`
179
179
  * fallback on the native module). `where` names the emission site for the error.
180
180
  */
181
- function portFieldRustType(node, inputPorts, where = "port", asBinding, plan) {
181
+ function portFieldRustType(node, inputPorts, where = "port", asBinding, plan, typedNodes) {
182
182
  if (staticStringArrElems(node) !== null)
183
183
  return "Vec<&'static str>";
184
184
  // #110: a componentRef port bound to a runtime array input port (with a resolvable elemType) → Vec<ElemT>.
185
185
  const arrElem = portArrayElemRef(node, inputPorts, plan);
186
186
  if (arrElem !== null)
187
187
  return `Vec<${renderTypeRef(arrElem)}>`;
188
+ // #129: a leaf port that is a ref into a PRIOR NODE's typed arr outType (node→leaf array dataflow) → Vec<ElemT>.
189
+ const priorArr = priorNodeArrElemInfo(node, typedNodes, plan);
190
+ if (priorArr !== null)
191
+ return `Vec<${renderTypeRef(priorArr.elemRef)}>`;
188
192
  const num = numLiteralRustExpr(node);
189
193
  if (num !== null)
190
194
  return typeof node === "number" && Number.isInteger(node) ? "i64" : "f64";
@@ -201,13 +205,13 @@ function portFieldRustType(node, inputPorts, where = "port", asBinding, plan) {
201
205
  * reads `ports.f_<field>` DIRECTLY. The struct derives Clone (a bc#87 parallel stage clones it into the
202
206
  * per-worker dispatch). Every field is a NATIVE Rust type (bc#90 / runtime-free) — NO boxed `Value`.
203
207
  */
204
- function emitPortsStruct(comp, node, asBinding, plan) {
208
+ function emitPortsStruct(comp, node, asBinding, plan, typedNodes) {
205
209
  const name = portsStructName(comp.name, node.id);
206
210
  const portNames = Object.keys(node.ports);
207
211
  if (portNames.length === 0) {
208
212
  return `// ${name} — native ports for node '${node.id}' (${node.component}); no ports.\n#[derive(Clone)]\npub struct ${name};`;
209
213
  }
210
- const types = portNames.map((p) => portFieldRustType(node.ports[p], comp.inputPorts, `port '${p}' of node '${node.id}'`, asBinding, plan));
214
+ const types = portNames.map((p) => portFieldRustType(node.ports[p], comp.inputPorts, `port '${p}' of node '${node.id}'`, asBinding, plan, typedNodes));
211
215
  const fields = portNames
212
216
  .map((p, i) => ` pub ${portFieldName(p)}: ${types[i]}, // ${JSON.stringify(p)}`)
213
217
  .join("\n");
@@ -681,6 +685,44 @@ function mapOverElemInfo(comp, node, typedNodes, plan) {
681
685
  function mapOverElemType(comp, node, typedNodes, plan) {
682
686
  return renderTypeRef(mapOverElemInfo(comp, node, typedNodes, plan).elemRef);
683
687
  }
688
+ /**
689
+ * priorNodeArrElemInfo (#129, rust twin) — resolve a leaf port that is a `ref` into a PRIOR BODY NODE
690
+ * whose annotated `outType` is (or reaches, through a field path) a native array, to its element TypeRef
691
+ * + the OWNED Rust Vec expression that yields it. GENERALIZES the `map…over` prior-node-arr dataflow
692
+ * (mapOverElemInfo's first branch) to ANY leaf input port: a fold/dedup leaf whose `items` port is
693
+ * `{ref:["<priorNode>"]}` (or `.items` of a prior connection node) now lowers to `Vec<ElemT>` cloned
694
+ * out of the prior node's typed cell — ZERO boxed Value on the read hot path.
695
+ *
696
+ * The element type comes STRICTLY from the prior node's EXPLICIT `outType` annotation walked through
697
+ * typedFieldAccess (BC does NOT infer — consumer-interface C3 / [[graphddb-typed-native-static-link]]):
698
+ * if the head is not a prior body node, or its outType (through the field path) is not an `arr`, this
699
+ * returns null and the caller keeps its existing fail-closed behaviour (never a silent box). A
700
+ * single-segment INPUT-array port (elemType) is a DIFFERENT covered shape (portArrayElemRef) — handled
701
+ * ahead of this. Requires the typedNodes map (prior-node outType TypeRefs) and a TypePlan.
702
+ */
703
+ function priorNodeArrElemInfo(node, typedNodes, plan) {
704
+ if (typedNodes === undefined || plan === undefined)
705
+ return null;
706
+ if (staticArrElems(node) !== null)
707
+ return null; // a static array literal — not a prior-node ref.
708
+ const e = classifyExpr(node);
709
+ if (e.kind !== "ref" || e.opt || e.path.length < 1)
710
+ return null;
711
+ if (!typedNodes.has(e.path[0]))
712
+ return null; // head is not a prior body node → not this shape.
713
+ const baseRef = typedNodes.get(e.path[0]);
714
+ let acc;
715
+ try {
716
+ acc = typedFieldAccess(`${typedCell(e.path[0])}.borrow()`, baseRef, e.path.slice(1), plan);
717
+ }
718
+ catch {
719
+ return null; // the field path does not resolve on the prior node's outType → fail-closed at caller.
720
+ }
721
+ if (acc.ref.kind !== "arr")
722
+ return null; // the prior-node result (through the path) is not an array.
723
+ // OWNED: clone the borrowed slice out of the RefCell so the ports struct owns its Vec<ElemT>.
724
+ return { elemRef: acc.ref.elem, overExpr: `${acc.expr}.clone()` };
725
+ }
684
726
  // ── eligibility ─────────────────────────────────────────────────────────────────────
685
727
  function reconstructR(e) {
686
728
  switch (e.kind) {
@@ -1069,7 +1111,7 @@ function emitNativeScalar(node, want, resolveRef) {
1069
1111
  * a boxed escape would re-introduce the bc-runtime coupling this module removes). `fieldRustType` is the
1070
1112
  * ports-struct field type (from portFieldRustType); `resolveRef` resolves a ref head for the scalar path.
1071
1113
  */
1072
- function emitPortInit(pn, expr, fieldRustType, resolveRef, inputPorts, plan) {
1114
+ function emitPortInit(pn, expr, fieldRustType, resolveRef, inputPorts, plan, typedNodes) {
1073
1115
  const field = portFieldName(pn);
1074
1116
  // #110: a componentRef port bound to a runtime array input port (with elemType) → the native Vec<ElemT>,
1075
1117
  // cloned out of the concrete input struct (`in_.<field>.clone()`) — ZERO boxed Value on the read hot path.
@@ -1079,6 +1121,12 @@ function emitPortInit(pn, expr, fieldRustType, resolveRef, inputPorts, plan) {
1079
1121
  return `${field}: in_.${rustFieldName(e.path[0])}.clone()`;
1080
1122
  }
1081
1123
  }
1124
+ // #129: a leaf port that is a ref into a PRIOR NODE's typed arr outType (node→leaf array dataflow) →
1125
+ // the native Vec<ElemT> cloned off the prior node's typed cell (`t_<node>.borrow().field….clone()`) —
1126
+ // ZERO boxed Value on the read hot path (a fold/dedup leaf whose `items` = a prior node's array result).
1127
+ const priorArr = priorNodeArrElemInfo(expr, typedNodes, plan);
1128
+ if (priorArr !== null)
1129
+ return `${field}: ${priorArr.overExpr}`;
1082
1130
  // change #2: a static string array (projection) → a native `vec![...]` of &'static str.
1083
1131
  const strArr = staticStringArrElems(expr);
1084
1132
  if (strArr !== null) {
@@ -1323,8 +1371,8 @@ function emitNativeRawRunner(comp, plan, ap) {
1323
1371
  const inits = [];
1324
1372
  for (const pn of portNames) {
1325
1373
  const expr = node.ports[pn];
1326
- const ty = portFieldRustType(expr, comp.inputPorts, `port '${pn}' of node '${node.id}'`, undefined, plan);
1327
- inits.push(emitPortInit(pn, expr, ty, resolveRef, comp.inputPorts, plan));
1374
+ const ty = portFieldRustType(expr, comp.inputPorts, `port '${pn}' of node '${node.id}'`, undefined, plan, typedNodes);
1375
+ inits.push(emitPortInit(pn, expr, ty, resolveRef, comp.inputPorts, plan, typedNodes));
1328
1376
  }
1329
1377
  const boundArg = node.bindField !== undefined ? `bound_${s}` : "None";
1330
1378
  // bc#97: derived per-node await — this point-read's future is awaited HERE (its result-consumption
@@ -1429,8 +1477,8 @@ function emitParallelStageArm(comp, stage, atPos, concurrency, metas, typedNodes
1429
1477
  const inits = [];
1430
1478
  for (const pn of Object.keys(node.ports)) {
1431
1479
  const expr = node.ports[pn];
1432
- const ty = portFieldRustType(expr, comp.inputPorts, `port '${pn}' of node '${node.id}'`, undefined, plan);
1433
- inits.push(emitPortInit(pn, expr, ty, resolveRef, comp.inputPorts, plan));
1480
+ const ty = portFieldRustType(expr, comp.inputPorts, `port '${pn}' of node '${node.id}'`, undefined, plan, typedNodes);
1481
+ inits.push(emitPortInit(pn, expr, ty, resolveRef, comp.inputPorts, plan, typedNodes));
1434
1482
  }
1435
1483
  if (gated) {
1436
1484
  lines.push(`${ind}ports_${s} = Some(${structName} { ${inits.join(", ")} });`);
@@ -2041,7 +2089,7 @@ ${emitStructSurface([])}
2041
2089
  // #108: a cond node has NO ports struct (pure Expression — no handler dispatch).
2042
2090
  }
2043
2091
  else
2044
- structs.push(emitPortsStruct(c, n, undefined, plan));
2092
+ structs.push(emitPortsStruct(c, n, undefined, plan, typedNodes)); // #129: typedNodes for prior-node arr leaf ports.
2045
2093
  }
2046
2094
  rowStructs.push(emitRawRowStructs(c, typedNodes, plan));
2047
2095
  handlerTraits.push(emitHandlerTrait(c, typedNodes, plan, asyncPlans.get(c.name)));
@@ -2149,7 +2197,7 @@ export function rustTypedNativeObserve(ir, ioModel = "sync") {
2149
2197
  const elemDecode = emitDecodeRow(c.name, n.id, elemT, elemRowRef, plan, emittedDecode, decodeFns, "Elem");
2150
2198
  const portsT = `${portsStructName(c.name, n.id)}Batch`;
2151
2199
  methods.push(` ${fnKw} ${nodeMethodName(n.id)}(&self, _ports: &${portsT}, _bound: ${bt}) -> Option<${batchT}> {
2152
- let (is_err, err, ok) = self.next(${rustStrLit(f.component)})?;
2200
+ let (is_err, err, ok, _echo) = self.next(${rustStrLit(f.component)})?;
2153
2201
  if is_err {
2154
2202
  return Some(${batchT} { is_error: true, err, ..Default::default() });
2155
2203
  }
@@ -2179,7 +2227,7 @@ export function rustTypedNativeObserve(ir, ioModel = "sync") {
2179
2227
  const retT = batched ? batchT : elemT;
2180
2228
  if (batched) {
2181
2229
  methods.push(` ${fnKw} ${nodeMethodName(n.id)}(&self, _ports: &${portsT}, _bound: ${bt}) -> Option<${batchT}> {
2182
- let (is_err, err, ok) = self.next(${rustStrLit(m.component)})?;
2230
+ let (is_err, err, ok, _echo) = self.next(${rustStrLit(m.component)})?;
2183
2231
  if is_err {
2184
2232
  return Some(${batchT} { is_error: true, err, ..Default::default() });
2185
2233
  }
@@ -2190,7 +2238,7 @@ export function rustTypedNativeObserve(ir, ioModel = "sync") {
2190
2238
  }
2191
2239
  else {
2192
2240
  methods.push(` ${fnKw} ${nodeMethodName(n.id)}(&self, _ports: &${portsT}, _bound: ${bt}) -> Option<${elemT}> {
2193
- let (is_err, err, ok) = self.next(${rustStrLit(m.component)})?;
2241
+ let (is_err, err, ok, _echo) = self.next(${rustStrLit(m.component)})?;
2194
2242
  if is_err {
2195
2243
  return Some(${elemT} { is_error: true, err, ..Default::default() });
2196
2244
  }
@@ -2204,11 +2252,22 @@ export function rustTypedNativeObserve(ir, ioModel = "sync") {
2204
2252
  const node = n;
2205
2253
  const rowT = rawRowStructName(c.name, node.id);
2206
2254
  const decode = emitDecodeRow(c.name, node.id, rowT, ref, plan, emittedDecode, decodeFns, "");
2207
- methods.push(` ${fnKw} ${nodeMethodName(node.id)}(&self, _ports: &${portsStructName(c.name, node.id)}, _bound: ${bt}) -> Option<${rowT}> {
2208
- let (is_err, err, ok) = self.next(${rustStrLit(node.component)})?;
2255
+ // #129 (TEST glue): if this node has an `items` port and its outType is a bare arr, support the
2256
+ // reference scriptedHandlers' `echo === "items"` — the scripted handler ECHOES the NATIVE `f_items`
2257
+ // port back as its result. This makes the node→leaf array dataflow test non-vacuous: the leaf's
2258
+ // observed result is exactly the native array that flowed into its ports struct, so a dropped or
2259
+ // boxed native array would diverge from run_behavior. `ports` is bound only on the echo path.
2260
+ const canEcho = ref.kind === "arr" && Object.prototype.hasOwnProperty.call(node.ports, "items");
2261
+ const portsParam = canEcho ? "ports" : "_ports";
2262
+ const echoBranch = canEcho
2263
+ ? `\n if echo == "items" {\n return Some(${rowT} { val: ${portsParam}.${portFieldName("items")}.clone(), ..Default::default() });\n }`
2264
+ : "";
2265
+ const okBind = canEcho ? "(is_err, err, ok, echo)" : "(is_err, err, ok, _echo)";
2266
+ methods.push(` ${fnKw} ${nodeMethodName(node.id)}(&self, ${portsParam}: &${portsStructName(c.name, node.id)}, _bound: ${bt}) -> Option<${rowT}> {
2267
+ let ${okBind} = self.next(${rustStrLit(node.component)})?;
2209
2268
  if is_err {
2210
2269
  return Some(${rowT} { is_error: true, err, ..Default::default() });
2211
- }
2270
+ }${echoBranch}
2212
2271
  let v = ok.unwrap_or(Value::Null);
2213
2272
  Some(${decode}(&v))
2214
2273
  }`);
@@ -2263,19 +2322,22 @@ impl ScriptedNativeRaw {
2263
2322
 
2264
2323
  // drain one scripted outcome for a component (last entry repeats — mirrors the reference scripted
2265
2324
  // handlers); None when the component has no scripted queue (fail-closed). Returns (is_error, err,
2266
- // ok_value). The bc#87 parallel-stage runner calls node_* from multiple threads, so the drain is
2267
- // Mutex-guarded.
2268
- fn next(&self, component: &str) -> Option<(bool, String, Option<Value>)> {
2325
+ // ok_value, echo). The bc#87 parallel-stage runner calls node_* from multiple threads, so the drain
2326
+ // is Mutex-guarded. #129: \`echo\` carries the reference scriptedHandlers' \`echo\` directive (e.g.
2327
+ // "items") so a scripted node can ECHO a named input PORT back — used to make the node→leaf array
2328
+ // dataflow test non-vacuous (the leaf returns its \`items\` port; a dropped/boxed array diverges).
2329
+ fn next(&self, component: &str) -> Option<(bool, String, Option<Value>, String)> {
2269
2330
  self.seen.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
2270
2331
  let mut queues = self.queues.lock().unwrap();
2271
2332
  let (queue, single) = queues.get_mut(component)?;
2272
2333
  let raw = if *single || queue.len() <= 1 { queue[0].clone() } else { queue.remove(0) };
2334
+ let echo = raw.get("echo").and_then(|e| e.as_str()).unwrap_or("").to_string();
2273
2335
  if let Some(okv) = raw.get("ok") {
2274
2336
  let v = behavior_contracts::decode_value(okv).expect("decode ok");
2275
- Some((false, String::new(), Some(v)))
2337
+ Some((false, String::new(), Some(v), echo))
2276
2338
  } else {
2277
2339
  let e = raw.get("error").and_then(|e| e.as_str()).unwrap_or("").to_string();
2278
- Some((true, e, None))
2340
+ Some((true, e, None, echo))
2279
2341
  }
2280
2342
  }
2281
2343
  }`;
@@ -2442,6 +2504,7 @@ function emitDecodeRow(compName, nodeId, rowT, ref, plan, emitted, out, suffix)
2442
2504
  }
2443
2505
  export const rustTypedNativeEmitter = {
2444
2506
  language: "rust-typed-native",
2507
+ classification: "codegen", // native: 型付きフラットコード生成・IR/dict 非在(#128/A6)
2445
2508
  fileExtension: ".rs",
2446
2509
  defaultRuntimeImport: "behavior_contracts",
2447
2510
  filenameHint: "behaviors_typed_native_generated.rs",