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.
- package/README.md +0 -1
- package/dist/authoring.d.ts +215 -3
- package/dist/authoring.d.ts.map +1 -1
- package/dist/authoring.js +269 -8
- package/dist/authoring.js.map +1 -1
- package/dist/behavior.d.ts +22 -1
- package/dist/behavior.d.ts.map +1 -1
- package/dist/behavior.js +23 -1
- package/dist/behavior.js.map +1 -1
- package/dist/bind.d.ts +64 -0
- package/dist/bind.d.ts.map +1 -0
- package/dist/bind.js +131 -0
- package/dist/bind.js.map +1 -0
- package/dist/builder.d.ts +30 -88
- package/dist/builder.d.ts.map +1 -1
- package/dist/builder.js +48 -56
- package/dist/builder.js.map +1 -1
- package/dist/expr-operator-types.d.ts +89 -0
- package/dist/expr-operator-types.d.ts.map +1 -0
- package/dist/expr-operator-types.js +134 -0
- package/dist/expr-operator-types.js.map +1 -0
- package/dist/generator/async-plan.d.ts.map +1 -1
- package/dist/generator/async-plan.js +4 -0
- package/dist/generator/async-plan.js.map +1 -1
- package/dist/generator/core.d.ts +15 -0
- package/dist/generator/core.d.ts.map +1 -1
- package/dist/generator/core.js +2 -0
- package/dist/generator/core.js.map +1 -1
- package/dist/generator/emit-php.d.ts.map +1 -1
- package/dist/generator/emit-php.js +1 -0
- package/dist/generator/emit-php.js.map +1 -1
- package/dist/generator/emit-python.d.ts.map +1 -1
- package/dist/generator/emit-python.js +1 -0
- package/dist/generator/emit-python.js.map +1 -1
- package/dist/generator/emit-straightline-typed-typescript.d.ts.map +1 -1
- package/dist/generator/emit-straightline-typed-typescript.js +1 -0
- package/dist/generator/emit-straightline-typed-typescript.js.map +1 -1
- package/dist/generator/emit-typed-native-go.d.ts.map +1 -1
- package/dist/generator/emit-typed-native-go.js +83 -12
- package/dist/generator/emit-typed-native-go.js.map +1 -1
- package/dist/generator/emit-typed-native-rust.d.ts.map +1 -1
- package/dist/generator/emit-typed-native-rust.js +83 -20
- package/dist/generator/emit-typed-native-rust.js.map +1 -1
- package/dist/generator/emit-typescript.d.ts.map +1 -1
- package/dist/generator/emit-typescript.js +24 -8
- package/dist/generator/emit-typescript.js.map +1 -1
- package/dist/generator/index.d.ts +1 -1
- package/dist/generator/index.d.ts.map +1 -1
- package/dist/generator/index.js +7 -5
- package/dist/generator/index.js.map +1 -1
- package/dist/index.d.ts +41 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +27 -6
- package/dist/index.js.map +1 -1
- package/dist/provenance.d.ts +102 -0
- package/dist/provenance.d.ts.map +1 -0
- package/dist/provenance.js +128 -0
- package/dist/provenance.js.map +1 -0
- package/dist/type-gate.d.ts +63 -0
- package/dist/type-gate.d.ts.map +1 -0
- package/dist/type-gate.js +295 -0
- package/dist/type-gate.js.map +1 -0
- 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
|
-
|
|
2208
|
-
|
|
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
|
|
2267
|
-
// Mutex-guarded.
|
|
2268
|
-
|
|
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",
|