behavior-contracts 0.1.3 → 0.2.1

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 (106) hide show
  1. package/README.md +8 -2
  2. package/dist/authoring.d.ts +202 -0
  3. package/dist/authoring.d.ts.map +1 -0
  4. package/dist/authoring.js +787 -0
  5. package/dist/authoring.js.map +1 -0
  6. package/dist/behavior.d.ts +248 -0
  7. package/dist/behavior.d.ts.map +1 -0
  8. package/dist/behavior.js +373 -0
  9. package/dist/behavior.js.map +1 -0
  10. package/dist/builder.d.ts +154 -0
  11. package/dist/builder.d.ts.map +1 -0
  12. package/dist/builder.js +574 -0
  13. package/dist/builder.js.map +1 -0
  14. package/dist/generator/core.d.ts +89 -0
  15. package/dist/generator/core.d.ts.map +1 -0
  16. package/dist/generator/core.js +124 -0
  17. package/dist/generator/core.js.map +1 -0
  18. package/dist/generator/emit-go.d.ts +19 -0
  19. package/dist/generator/emit-go.d.ts.map +1 -0
  20. package/dist/generator/emit-go.js +79 -0
  21. package/dist/generator/emit-go.js.map +1 -0
  22. package/dist/generator/emit-php.d.ts +20 -0
  23. package/dist/generator/emit-php.d.ts.map +1 -0
  24. package/dist/generator/emit-php.js +76 -0
  25. package/dist/generator/emit-php.js.map +1 -0
  26. package/dist/generator/emit-python.d.ts +10 -0
  27. package/dist/generator/emit-python.d.ts.map +1 -0
  28. package/dist/generator/emit-python.js +75 -0
  29. package/dist/generator/emit-python.js.map +1 -0
  30. package/dist/generator/emit-rust.d.ts +25 -0
  31. package/dist/generator/emit-rust.d.ts.map +1 -0
  32. package/dist/generator/emit-rust.js +125 -0
  33. package/dist/generator/emit-rust.js.map +1 -0
  34. package/dist/generator/emit-straightline-go.d.ts +77 -0
  35. package/dist/generator/emit-straightline-go.d.ts.map +1 -0
  36. package/dist/generator/emit-straightline-go.js +592 -0
  37. package/dist/generator/emit-straightline-go.js.map +1 -0
  38. package/dist/generator/emit-straightline-php.d.ts +46 -0
  39. package/dist/generator/emit-straightline-php.d.ts.map +1 -0
  40. package/dist/generator/emit-straightline-php.js +501 -0
  41. package/dist/generator/emit-straightline-php.js.map +1 -0
  42. package/dist/generator/emit-straightline-python.d.ts +36 -0
  43. package/dist/generator/emit-straightline-python.d.ts.map +1 -0
  44. package/dist/generator/emit-straightline-python.js +441 -0
  45. package/dist/generator/emit-straightline-python.js.map +1 -0
  46. package/dist/generator/emit-straightline-rust.d.ts +78 -0
  47. package/dist/generator/emit-straightline-rust.d.ts.map +1 -0
  48. package/dist/generator/emit-straightline-rust.js +615 -0
  49. package/dist/generator/emit-straightline-rust.js.map +1 -0
  50. package/dist/generator/emit-straightline-typed-go.d.ts +39 -0
  51. package/dist/generator/emit-straightline-typed-go.d.ts.map +1 -0
  52. package/dist/generator/emit-straightline-typed-go.js +828 -0
  53. package/dist/generator/emit-straightline-typed-go.js.map +1 -0
  54. package/dist/generator/emit-straightline-typed-rust.d.ts +48 -0
  55. package/dist/generator/emit-straightline-typed-rust.d.ts.map +1 -0
  56. package/dist/generator/emit-straightline-typed-rust.js +823 -0
  57. package/dist/generator/emit-straightline-typed-rust.js.map +1 -0
  58. package/dist/generator/emit-straightline-typed-typescript.d.ts +49 -0
  59. package/dist/generator/emit-straightline-typed-typescript.d.ts.map +1 -0
  60. package/dist/generator/emit-straightline-typed-typescript.js +314 -0
  61. package/dist/generator/emit-straightline-typed-typescript.js.map +1 -0
  62. package/dist/generator/emit-straightline-typescript.d.ts +36 -0
  63. package/dist/generator/emit-straightline-typescript.d.ts.map +1 -0
  64. package/dist/generator/emit-straightline-typescript.js +397 -0
  65. package/dist/generator/emit-straightline-typescript.js.map +1 -0
  66. package/dist/generator/emit-typescript.d.ts +11 -0
  67. package/dist/generator/emit-typescript.d.ts.map +1 -0
  68. package/dist/generator/emit-typescript.js +68 -0
  69. package/dist/generator/emit-typescript.js.map +1 -0
  70. package/dist/generator/fingerprint.d.ts +14 -0
  71. package/dist/generator/fingerprint.d.ts.map +1 -0
  72. package/dist/generator/fingerprint.js +74 -0
  73. package/dist/generator/fingerprint.js.map +1 -0
  74. package/dist/generator/index.d.ts +22 -0
  75. package/dist/generator/index.d.ts.map +1 -0
  76. package/dist/generator/index.js +64 -0
  77. package/dist/generator/index.js.map +1 -0
  78. package/dist/generator/literal.d.ts +15 -0
  79. package/dist/generator/literal.d.ts.map +1 -0
  80. package/dist/generator/literal.js +232 -0
  81. package/dist/generator/literal.js.map +1 -0
  82. package/dist/generator/straightline.d.ts +147 -0
  83. package/dist/generator/straightline.d.ts.map +1 -0
  84. package/dist/generator/straightline.js +122 -0
  85. package/dist/generator/straightline.js.map +1 -0
  86. package/dist/generator/typed.d.ts +156 -0
  87. package/dist/generator/typed.d.ts.map +1 -0
  88. package/dist/generator/typed.js +108 -0
  89. package/dist/generator/typed.js.map +1 -0
  90. package/dist/guard.d.ts +36 -0
  91. package/dist/guard.d.ts.map +1 -1
  92. package/dist/guard.js +186 -0
  93. package/dist/guard.js.map +1 -1
  94. package/dist/index.d.ts +17 -3
  95. package/dist/index.d.ts.map +1 -1
  96. package/dist/index.js +23 -2
  97. package/dist/index.js.map +1 -1
  98. package/dist/plan.d.ts +36 -0
  99. package/dist/plan.d.ts.map +1 -1
  100. package/dist/plan.js +152 -14
  101. package/dist/plan.js.map +1 -1
  102. package/dist/primitives.d.ts +112 -0
  103. package/dist/primitives.d.ts.map +1 -0
  104. package/dist/primitives.js +206 -0
  105. package/dist/primitives.js.map +1 -0
  106. package/package.json +4 -1
@@ -0,0 +1,787 @@
1
+ import { SPEC_VERSIONS } from "./index.js";
2
+ import { assertPortableComponentGraph } from "./guard.js";
3
+ import { buildComponentCore, checkPortableEntry, checkPortNames, checkDeclaredPortTypes, collectNodeIdRefs, DEFAULT_PLAN_CONCURRENCY, } from "./builder.js";
4
+ export class AuthoringFailure extends Error {
5
+ code;
6
+ constructor(code, message) {
7
+ super(message);
8
+ this.code = code;
9
+ this.name = "AuthoringFailure";
10
+ }
11
+ }
12
+ function afail(code, message) {
13
+ throw new AuthoringFailure(code, message);
14
+ }
15
+ // ── root マーカー(クラス単位・effect 非依存)───────────────────────────────────
16
+ const BEHAVIOR_MARK = /* @__PURE__ */ Symbol.for("behavior-contracts.authoring.behavior");
17
+ /**
18
+ * `SemanticBehavior` — 共有 authoring の基底クラス(既定の root マーカー)。
19
+ * 継承したクラスの各 public メソッドが 1 root Behavior として publish される。
20
+ * マーカー以外の実装・状態は持たない(authoring は effect 非依存・宣言のみ)。
21
+ */
22
+ export class SemanticBehavior {
23
+ }
24
+ /**
25
+ * `@behavior` — クラスデコレータ形の root マーカー(`SemanticBehavior` 継承と**同義**。
26
+ * 同じクラス本体はどちらのマーカーでも同一 IR に lower される)。
27
+ * TC39 standard / legacy いずれのデコレータ位置でも、素の関数呼び出しでも使える。
28
+ */
29
+ export function behavior(cls, _context) {
30
+ Object.defineProperty(cls, BEHAVIOR_MARK, { value: true, configurable: true });
31
+ return cls;
32
+ }
33
+ function isBehaviorClass(cls) {
34
+ if (typeof cls !== "function")
35
+ return false;
36
+ if (cls[BEHAVIOR_MARK] === true)
37
+ return true;
38
+ return cls === SemanticBehavior || cls.prototype instanceof SemanticBehavior;
39
+ }
40
+ // ── recorder(式 Proxy)─────────────────────────────────────────────────────────
41
+ const EXPR_META = /* @__PURE__ */ Symbol("behavior-contracts.authoring.expr");
42
+ /** recorder 値なら ExprMeta を返す。 */
43
+ function exprMetaOf(v) {
44
+ if (v === null || (typeof v !== "object" && typeof v !== "function"))
45
+ return undefined;
46
+ return v[EXPR_META];
47
+ }
48
+ function makeExpr(meta) {
49
+ return new Proxy({}, {
50
+ get(_t, prop) {
51
+ if (prop === EXPR_META)
52
+ return meta;
53
+ if (typeof prop === "symbol") {
54
+ if (prop === Symbol.toPrimitive)
55
+ return () => notRecordable("implicit coercion of a recorded value (use concat/add/eq/... builders)");
56
+ return undefined;
57
+ }
58
+ // Promise-likeness / util.inspect の誤爆を避ける(record 対象でもない)。
59
+ if (prop === "then" || prop === "constructor" || prop === "inspect")
60
+ return undefined;
61
+ if (prop === "valueOf" || prop === "toString" || prop === "toJSON")
62
+ return () => notRecordable(`'${prop}' on a recorded value (implicit coercion is not statically interpretable)`);
63
+ if (prop === "map")
64
+ return (cb, opts) => recordMap(meta, cb, opts);
65
+ if (meta.refPath === null)
66
+ afail("COMPUTED_ACCESS", `property '.${prop}' on a computed expression cannot be lowered (bind the value through a component result or restructure the wiring)`);
67
+ if (meta.isInput && meta.refPath.length === 0)
68
+ recordInputPort(prop);
69
+ const path = [...meta.refPath, prop];
70
+ return makeExpr({ node: { [meta.refOp]: path }, refPath: path, refOp: meta.refOp, isInput: meta.isInput });
71
+ },
72
+ ownKeys() {
73
+ return notRecordable("spread / enumeration of a recorded value (Expression IR has no object-merge operator; wire explicit keys)");
74
+ },
75
+ getOwnPropertyDescriptor() {
76
+ return notRecordable("property enumeration of a recorded value");
77
+ },
78
+ set() {
79
+ return notRecordable("assignment into a recorded value (authoring is declarative)");
80
+ },
81
+ });
82
+ }
83
+ function notRecordable(what) {
84
+ afail("NOT_RECORDABLE", `not statically interpretable: ${what}`);
85
+ }
86
+ /** 演算結果(ref path を持たない)recorder。 */
87
+ function opExpr(node) {
88
+ return makeExpr({ node, refPath: null, refOp: "ref", isInput: false });
89
+ }
90
+ /** ノード結果 recorder(`{ref:[<nodeId>]}` root。プロパティアクセスで path 延長可能)。 */
91
+ function nodeHandle(id) {
92
+ return makeExpr({ node: { ref: [id] }, refPath: [id], refOp: "ref", isInput: false });
93
+ }
94
+ // ── 値 → Expression IR lowering ────────────────────────────────────────────────
95
+ /**
96
+ * authoring 本体に現れた値を Expression IR へ lower する。
97
+ * recorder はその IR ノードに、リテラルはそのまま(配列 → `{arr:[...]}`、オブジェクト →
98
+ * `{obj:{...}}`、bigint → `{int:"..."}`)に落ちる。関数 / Date / Promise / undefined は
99
+ * Portability 違反として loud に失敗する(fail-closed)。
100
+ */
101
+ function lowerValue(v, at) {
102
+ const meta = exprMetaOf(v);
103
+ if (meta) {
104
+ if (meta.refPath !== null && meta.refPath.length === 0)
105
+ afail("WHOLE_INPUT", `${at}: the whole '$' input cannot be wired; reference a field of it`);
106
+ return meta.node;
107
+ }
108
+ if (v === null)
109
+ return null;
110
+ switch (typeof v) {
111
+ case "boolean":
112
+ case "string":
113
+ return v;
114
+ case "number":
115
+ if (Number.isInteger(v) && !Number.isSafeInteger(v))
116
+ afail("BAD_PORT_VALUE", `${at}: integral literal ${v} exceeds the safe range (use a bigint literal)`);
117
+ if (!Number.isFinite(v))
118
+ afail("BAD_PORT_VALUE", `${at}: non-finite number literal`);
119
+ return v;
120
+ case "bigint":
121
+ return { int: v.toString() };
122
+ case "undefined":
123
+ afail("BAD_PORT_VALUE", `${at}: undefined cannot be wired`);
124
+ break;
125
+ case "function":
126
+ afail("BAD_PORT_VALUE", `${at}: functions cannot be placed in IR (Portability Guard)`);
127
+ break;
128
+ }
129
+ if (Array.isArray(v))
130
+ return { arr: v.map((e, i) => lowerValue(e, `${at}[${i}]`)) };
131
+ if (v instanceof Date || v instanceof Promise)
132
+ afail("BAD_PORT_VALUE", `${at}: ${v.constructor.name} cannot be placed in IR (Portability Guard)`);
133
+ if (typeof v === "object") {
134
+ const out = {};
135
+ for (const k of Object.keys(v)) {
136
+ if (k === "__proto__")
137
+ afail("BAD_PORT_VALUE", `${at}: object key "__proto__" is forbidden (fail-closed)`);
138
+ out[k] = lowerValue(v[k], `${at}.${k}`);
139
+ }
140
+ return { obj: out };
141
+ }
142
+ afail("BAD_PORT_VALUE", `${at}: value cannot be wired`);
143
+ }
144
+ // ── Expression builders(expression-ir.md §3 の閉集合と 1:1)──────────────────────
145
+ function binOp(op) {
146
+ return (a, b) => opExpr({ [op]: [lowerValue(a, op), lowerValue(b, op)] });
147
+ }
148
+ function unOp(op) {
149
+ return (a) => opExpr({ [op]: [lowerValue(a, op)] });
150
+ }
151
+ /** `+`(文字列連結)。n-ary(min 2)。 */
152
+ export function concat(...parts) {
153
+ if (parts.length < 2)
154
+ afail("BAD_PORT_VALUE", `concat expects >= 2 args, got ${parts.length}`);
155
+ return opExpr({ concat: parts.map((p, i) => lowerValue(p, `concat[${i}]`)) });
156
+ }
157
+ export const add = /* @__PURE__ */ binOp("add");
158
+ export const sub = /* @__PURE__ */ binOp("sub");
159
+ export const mul = /* @__PURE__ */ binOp("mul");
160
+ export const div = /* @__PURE__ */ binOp("div");
161
+ export const mod = /* @__PURE__ */ binOp("mod");
162
+ export const neg = /* @__PURE__ */ unOp("neg");
163
+ export const eq = /* @__PURE__ */ binOp("eq");
164
+ export const ne = /* @__PURE__ */ binOp("ne");
165
+ export const lt = /* @__PURE__ */ binOp("lt");
166
+ export const le = /* @__PURE__ */ binOp("le");
167
+ export const gt = /* @__PURE__ */ binOp("gt");
168
+ export const ge = /* @__PURE__ */ binOp("ge");
169
+ export const and = /* @__PURE__ */ binOp("and");
170
+ export const or = /* @__PURE__ */ binOp("or");
171
+ export const not = /* @__PURE__ */ unOp("not");
172
+ /** `??`。 */
173
+ export const coalesce = /* @__PURE__ */ binOp("coalesce");
174
+ export const len = /* @__PURE__ */ unOp("len");
175
+ /**
176
+ * `opt(x)` — `?.`(optional chaining)の record 形。ref recorder を refOpt に変換する
177
+ * (以降のプロパティアクセスは中間 null で null を返す — expression-ir.md `refOpt`)。
178
+ */
179
+ export function opt(x) {
180
+ const meta = exprMetaOf(x);
181
+ if (!meta || meta.refPath === null || meta.refPath.length === 0)
182
+ afail("BAD_PORT_VALUE", "opt(...) expects a reference (a recorded '$' field or node result)");
183
+ return makeExpr({ node: { refOpt: meta.refPath }, refPath: meta.refPath, refOp: "refOpt", isInput: meta.isInput });
184
+ }
185
+ let currentFrame = null;
186
+ function requireFrame(what) {
187
+ if (!currentFrame)
188
+ afail("NO_FRAME", `${what} was used outside compileBehaviors() (recorders only work during build-time evaluation)`);
189
+ return currentFrame;
190
+ }
191
+ function recordInputPort(name) {
192
+ const f = requireFrame(`$.${name}`);
193
+ if (!f.inputPorts.has(name))
194
+ f.inputPorts.set(name, { type: "unknown", required: true });
195
+ }
196
+ function nextId(f) {
197
+ const id = `n${f.seq++}`;
198
+ f.nodeIds.add(id);
199
+ return id;
200
+ }
201
+ /** 分岐スタックの条件を `and` 連鎖に合成する(gate の `if`)。 */
202
+ function composedCond(f) {
203
+ const conds = f.branchStack.map((b) => b.cond);
204
+ return conds.reduce((acc, c) => (acc === undefined ? c : { and: [acc, c] }), undefined);
205
+ }
206
+ /**
207
+ * 分岐内に Component が初めて現れた時点で gate(CondNode)を遅延生成する。
208
+ * gate は条件成立時 `{ok:true}` / 不成立時 `null` を返し、分岐内ノードは
209
+ * `parent=gate, bindField:"ok"` の null-binding skip(execution-plan.md)で実行選択される。
210
+ */
211
+ function ensureGate(f) {
212
+ const bf = f.branchStack[f.branchStack.length - 1];
213
+ if (!bf)
214
+ return null;
215
+ if (f.branchStack.length > 1)
216
+ afail("NESTED_BRANCH", `method '${f.method}': a when(...) branch containing components cannot nest inside another when(...) branch (authoring v1; flatten to sibling gates)`);
217
+ if (bf.gateId === null) {
218
+ const id = nextId(f);
219
+ const gate = { id, cond: { if: composedCond(f), then: { obj: { ok: true } }, else: null } };
220
+ f.nodes.push(gate);
221
+ bf.gateId = id;
222
+ }
223
+ return bf;
224
+ }
225
+ /** ports の deps から parent(Wire)を決める: in-branch データ親 > gate > 先頭データ親。 */
226
+ function chooseParent(f, deps, bf) {
227
+ if (bf) {
228
+ const inBranch = deps.find((d) => {
229
+ const idx = f.nodes.findIndex((n) => n.id === d);
230
+ return idx >= bf.startLen;
231
+ });
232
+ if (inBranch !== undefined)
233
+ return { parent: inBranch }; // gate skip は親経由で伝播する
234
+ return { parent: bf.gateId, bindField: "ok" };
235
+ }
236
+ if (deps.length > 0)
237
+ return { parent: deps[0] };
238
+ return {};
239
+ }
240
+ // ── Catalog → component 関数(leaf 呼び出しの record)──────────────────────────────
241
+ /**
242
+ * catalogComponents — consumer の Catalog から、authoring 本体で呼べる leaf コンポーネント
243
+ * 関数群を作る(C2: consumer が供給するのはこの Catalog だけ)。
244
+ *
245
+ * 各関数は `Component({ port: <expr | literal> })` の呼び出しを componentRef ノードとして
246
+ * record し、ノード結果の recorder(後続配線・`.map`・Output 参照に使える)を返す。
247
+ * Port は catalog の Port schema に対して build-time 検証される(未知 Port / required 欠落は
248
+ * fail-closed)。`portableToIR: false` のエントリは可搬 IR に載らないため拒否する。
249
+ */
250
+ export function catalogComponents(catalog) {
251
+ const out = {};
252
+ for (const name of Object.keys(catalog)) {
253
+ const entry = catalog[name];
254
+ out[name] = (ports) => recordComponentCall(name, entry, ports);
255
+ }
256
+ return out;
257
+ }
258
+ function recordComponentCall(name, entry, portsIn) {
259
+ const f = requireFrame(`component '${name}'`);
260
+ checkPortableEntry(name, entry, afail);
261
+ if (!portsIn || typeof portsIn !== "object" || Array.isArray(portsIn) || exprMetaOf(portsIn))
262
+ afail("BAD_PORT_VALUE", `component '${name}': ports must be a plain object literal`);
263
+ // Port schema 検証(build-time・fail-closed。builder.ts のデータ登録経路と共有 — B-3):
264
+ // 未宣言 Port(additionalPorts ポリシー)・required 欠落、lower 後に宣言 Port の
265
+ // スカラリテラル型。
266
+ checkPortNames(name, entry, Object.keys(portsIn), afail);
267
+ const ports = {};
268
+ for (const k of Object.keys(portsIn))
269
+ ports[k] = lowerValue(portsIn[k], `component '${name}' port '${k}'`);
270
+ checkDeclaredPortTypes(name, entry, ports, afail);
271
+ // map コールバック内 → MapNode の component/ports として capture(単一静的ノード)。
272
+ if (f.pendingMap) {
273
+ if (f.pendingMap.component !== null)
274
+ afail("MAP_MULTI_COMPONENT", `method '${f.method}': a .map callback must invoke exactly one component (got a second call: '${name}')`);
275
+ f.pendingMap.component = name;
276
+ f.pendingMap.ports = ports;
277
+ // callback 内でこの要素結果へさらに配線することはできない(record は構造のみ)。
278
+ return opExpr(null);
279
+ }
280
+ const bf = ensureGate(f);
281
+ const deps = collectNodeIdRefs(ports, f.nodeIds);
282
+ const id = nextId(f);
283
+ const wire = chooseParent(f, deps, bf);
284
+ const node = { id, component: name, ports };
285
+ if (wire.parent !== undefined)
286
+ node.parent = wire.parent;
287
+ if (wire.bindField !== undefined)
288
+ node.bindField = wire.bindField;
289
+ f.nodes.push(node);
290
+ return nodeHandle(id);
291
+ }
292
+ // ── structured control: Map(ネイティブ `.map` を lower)───────────────────────────
293
+ function recordMap(overMeta, cb, opts) {
294
+ const f = requireFrame(".map");
295
+ if (f.pendingMap)
296
+ afail("NESTED_MAP", `method '${f.method}': nested .map is not in the v2 vocabulary (chain a second map over the augmented list — scp-ir-architecture.md §5.1)`);
297
+ if (f.branchStack.length > 0)
298
+ afail("MAP_IN_BRANCH", `method '${f.method}': .map inside a when(...) branch is not expressible in the IR (guard the 'over' expression instead)`);
299
+ if (typeof cb !== "function")
300
+ afail("MAP_NOT_LOWERABLE", `method '${f.method}': .map expects an inline arrow callback`);
301
+ if (cb.length > 1)
302
+ afail("MAP_NOT_LOWERABLE", `method '${f.method}': .map callback takes exactly one element binding (v1)`);
303
+ if (overMeta.refPath !== null && overMeta.refPath.length === 0)
304
+ afail("WHOLE_INPUT", `method '${f.method}': the whole '$' input cannot be iterated; reference an array field`);
305
+ if (opts !== undefined) {
306
+ if (opts === null || typeof opts !== "object")
307
+ afail("MAP_NOT_LOWERABLE", ".map options must be an object");
308
+ if (opts.into !== undefined && typeof opts.into !== "string")
309
+ afail("MAP_NOT_LOWERABLE", ".map option 'into' must be a string key");
310
+ if (opts.batched !== undefined && typeof opts.batched !== "boolean")
311
+ afail("MAP_NOT_LOWERABLE", ".map option 'batched' must be a boolean");
312
+ }
313
+ const as = `$e${f.mapSeq++}`;
314
+ const binding = makeExpr({ node: { ref: [as] }, refPath: [as], refOp: "ref", isInput: false });
315
+ f.pendingMap = { as, component: null, ports: null, when: undefined };
316
+ let pm;
317
+ try {
318
+ cb(binding);
319
+ }
320
+ finally {
321
+ pm = f.pendingMap;
322
+ f.pendingMap = null;
323
+ }
324
+ if (pm.component === null)
325
+ afail("MAP_NOT_LOWERABLE", `method '${f.method}': a .map callback must invoke a catalog component (a pure-transform map has no operator in the portable Expression IR v2 vocabulary)`);
326
+ const over = overMeta.node;
327
+ const deps = collectNodeIdRefs(over, f.nodeIds);
328
+ collectNodeIdRefs(pm.ports, f.nodeIds, deps);
329
+ if (pm.when !== undefined)
330
+ collectNodeIdRefs(pm.when, f.nodeIds, deps);
331
+ const id = nextId(f);
332
+ const node = {
333
+ id,
334
+ map: { over, as, component: pm.component, ports: pm.ports },
335
+ };
336
+ if (pm.when !== undefined)
337
+ node.map.when = pm.when;
338
+ if (opts?.into !== undefined)
339
+ node.map.into = opts.into;
340
+ if (opts?.batched === true)
341
+ node.map.batched = true;
342
+ if (deps.length > 0)
343
+ node.map.parent = deps[0];
344
+ f.nodes.push(node);
345
+ return nodeHandle(id);
346
+ }
347
+ // ── structured control: Conditional / Guard(`?:` / `&&` を lower)──────────────────
348
+ /**
349
+ * when — Conditional(`?:`)/ Guard(`&&`)の record 形(semantic-contract-programming.md
350
+ * 「構造化制御構文」の Compiler 規則を評価モードで実装する):
351
+ *
352
+ * - 両分岐(else 省略時は then のみ)が**純式のみ** → Expression IR `{cond:[c,t,e]}` へ
353
+ * inline lower(body ノードは増えない。else 省略は `null`)。
354
+ * - 分岐に **Component 宣言を含む** → 分岐ごとに gate(CondNode + null-binding skip)を
355
+ * 立て、両分岐が値を持つ場合は Φ(CondNode)ノードで合流する(採用側のみ実行される)。
356
+ * Guard 形(else 無し)は then の値をそのまま返す — 条件不成立時は当該ノードが Skip され、
357
+ * 下流へは未生成 Port の Skip 伝播規則で連鎖する。
358
+ * - `.map` コールバック内では **per-element guard**(v2 `map.when`)として record される
359
+ * (guard 形のみ。false の要素は handler 未呼び出しで結果から除外)。
360
+ *
361
+ * 条件は recorder 式でなければならない(ネイティブ `===` / `&&` の結果は record できない —
362
+ * eq/and/... builder を使う)。
363
+ */
364
+ export function when(condition, then, otherwise) {
365
+ const f = requireFrame("when");
366
+ if (!exprMetaOf(condition))
367
+ afail("BAD_CONDITION", `method '${f.method}': when(...) condition must be a recorded expression (native ===/&&/?: over recorded values cannot be observed at build time — use eq/ne/and/... builders)`);
368
+ if (typeof then !== "function" || (otherwise !== undefined && typeof otherwise !== "function"))
369
+ afail("BAD_BRANCH", `method '${f.method}': when(...) branches must be thunks (() => ...)`);
370
+ const c = lowerValue(condition, "when condition");
371
+ // map コールバック内: per-element guard(v2 map.when)。
372
+ if (f.pendingMap) {
373
+ if (otherwise !== undefined)
374
+ afail("BAD_BRANCH", `method '${f.method}': when(...) inside a .map callback is guard-form only (no else — v2 'map.when' skips the element)`);
375
+ const before = f.pendingMap.component;
376
+ const result = then();
377
+ if (f.pendingMap.component === before) {
378
+ // 純式 guard → inline cond(else は null)。
379
+ return opExpr({ cond: [c, lowerValue(result, "when then"), null] });
380
+ }
381
+ if (f.pendingMap.when !== undefined)
382
+ afail("BAD_CONDITION", `method '${f.method}': a .map callback records at most one when(...) guard`);
383
+ f.pendingMap.when = c;
384
+ return result;
385
+ }
386
+ // then 分岐。
387
+ const thenFrame = { cond: c, gateId: null, startLen: f.nodes.length };
388
+ f.branchStack.push(thenFrame);
389
+ let thenVal;
390
+ try {
391
+ thenVal = then();
392
+ }
393
+ finally {
394
+ f.branchStack.pop();
395
+ }
396
+ const thenHasComponents = f.nodes.length > thenFrame.startLen;
397
+ // else 分岐。
398
+ let elseVal = null;
399
+ let elseHasComponents = false;
400
+ if (otherwise !== undefined) {
401
+ const elseFrame = { cond: { not: [c] }, gateId: null, startLen: f.nodes.length };
402
+ f.branchStack.push(elseFrame);
403
+ try {
404
+ elseVal = otherwise();
405
+ }
406
+ finally {
407
+ f.branchStack.pop();
408
+ }
409
+ elseHasComponents = f.nodes.length > elseFrame.startLen;
410
+ }
411
+ // 純式のみ → Expression IR へ inline lower(Compiler 規則)。
412
+ if (!thenHasComponents && !elseHasComponents)
413
+ return opExpr({ cond: [c, lowerValue(thenVal, "when then"), lowerValue(elseVal, "when else")] });
414
+ // Guard 形(else 無し): then の値をそのまま返す(不成立時は gate skip → 未生成 Port)。
415
+ if (otherwise === undefined)
416
+ return thenVal;
417
+ // Φ 合流ノード: 採用側の値を単一 Port にする(cond は採用側のみ評価する)。
418
+ const id = nextId(f);
419
+ const phi = {
420
+ id,
421
+ cond: { if: c, then: lowerValue(thenVal, "when then"), else: lowerValue(elseVal, "when else") },
422
+ };
423
+ f.nodes.push(phi);
424
+ return nodeHandle(id);
425
+ }
426
+ // ── source scan(NATIVE_CONTROL_SYNTAX — bc#24 audit fix)───────────────────────────
427
+ /**
428
+ * JS Proxy には ToBoolean / strict-equality のトラップが存在しないため、recorder 値上の
429
+ * ネイティブ制御構文は評価モードでは観測できず、黙って誤った DAG に lower され得る
430
+ * (`$.flag ? C.Get(..) : C.Put(..)` → 無条件 Get、`$.flag && C.Get(..)` → ガード消失、
431
+ * `$.limit ?? "d"` → default 消失、`!$.flag` → リテラル false、`if ($.flag) {..}` →
432
+ * then 側のみ、`switch ($.x)` → default 側のみ)。evaluation-mode recording に内在する
433
+ * 制約であり Proxy では塞げないため、**compileBehaviors は評価の前にソースを走査**して
434
+ * これらのトークンを一律 fail-closed で拒否する。
435
+ *
436
+ * 前処理(偽陽性の回避): 文字列リテラルの内容・テンプレートリテラルのテキスト部
437
+ * (`${...}` 内の式コードは保持)・コメント・正規表現リテラル本体を空白化してから走査する。
438
+ * 走査対象はソースの最初の `{` 以降(= 本体)。ランタイムの `Function.prototype.toString`
439
+ * は型消去後のソースを返すため、TS の型レベル `?`(optional param / conditional type)は
440
+ * 現れない。パラメータ既定値はビルド時に実行されるコードなので走査対象に含まれてよい。
441
+ *
442
+ * ヘルパの扱い: `_`-prefix ヘルパは prototype から列挙して個別に走査する。`#`-private
443
+ * メソッドは prototype に現れないため、**クラスソース全体(親クラス連鎖含む)も走査する**。
444
+ * record 中に実行され得るコードはすべて対象であり、未到達のヘルパも fail-closed で
445
+ * 拒否される(到達可能性はソースからは判定できない)。
446
+ *
447
+ * 検出できない残余はヘッダ docstring / semantic-contract-programming.md「残余リスク」を参照
448
+ * (クラス外の関数・eval / new Function・`for`/`while` のループ条件)。
449
+ */
450
+ const NATIVE_CONTROL_TOKEN = /\?\?=|&&=|\|\|=|\?\?|\?\.|&&|\|\||===|!==|==|!=|\bif\s*\(|\bswitch\s*\(|[?!]/;
451
+ /** 正規表現リテラルが直前トークンの直後に開始し得るか(`/` を除算と区別する軽量判定)。 */
452
+ const REGEX_PRECEDING_KEYWORDS = /* @__PURE__ */ new Set([
453
+ "return", "typeof", "instanceof", "in", "of", "case", "do", "else", "void", "delete", "throw", "new", "yield", "await",
454
+ ]);
455
+ const WORD_CHAR = /[A-Za-z0-9_$#]/;
456
+ function regexCanFollow(prev, word) {
457
+ if (prev === "")
458
+ return true;
459
+ if (WORD_CHAR.test(prev))
460
+ return REGEX_PRECEDING_KEYWORDS.has(word);
461
+ return !")]}\"'`.".includes(prev);
462
+ }
463
+ /**
464
+ * ソースから「コードでないテキスト」を空白化する(インデックス保存 — エラー位置抜粋用):
465
+ * 文字列リテラル内容 / テンプレートリテラルのテキスト部(`${...}` 内コードは保持)/
466
+ * 行・ブロックコメント / 正規表現リテラル本体。
467
+ */
468
+ function stripNonCode(src) {
469
+ const out = src.split("");
470
+ const blank = (idx) => {
471
+ if (out[idx] !== "\n")
472
+ out[idx] = " ";
473
+ };
474
+ // モードスタック: code(`${...}` ネスト用に {}-depth を持つ)と template。
475
+ const stack = [{ tpl: false, braces: 0 }];
476
+ let prev = ""; // 直前の有意(非空白)コード文字
477
+ let word = ""; // 直前の識別子/キーワード(regex 開始判定用)
478
+ let prevWasWord = false;
479
+ let i = 0;
480
+ while (i < src.length) {
481
+ const top = stack[stack.length - 1];
482
+ const ch = src[i];
483
+ if (top.tpl) {
484
+ if (ch === "\\") {
485
+ blank(i);
486
+ if (i + 1 < src.length)
487
+ blank(i + 1);
488
+ i += 2;
489
+ continue;
490
+ }
491
+ if (ch === "`") {
492
+ stack.pop();
493
+ i++;
494
+ prev = "`";
495
+ word = "";
496
+ prevWasWord = false;
497
+ continue;
498
+ }
499
+ if (ch === "$" && src[i + 1] === "{") {
500
+ stack.push({ tpl: false, braces: 0 });
501
+ i += 2;
502
+ prev = "{";
503
+ word = "";
504
+ prevWasWord = false;
505
+ continue;
506
+ }
507
+ blank(i);
508
+ i++;
509
+ continue;
510
+ }
511
+ const nx = src[i + 1];
512
+ if (ch === "/" && nx === "/") {
513
+ while (i < src.length && src[i] !== "\n")
514
+ blank(i++);
515
+ continue;
516
+ }
517
+ if (ch === "/" && nx === "*") {
518
+ blank(i);
519
+ blank(i + 1);
520
+ i += 2;
521
+ while (i < src.length && !(src[i] === "*" && src[i + 1] === "/"))
522
+ blank(i++);
523
+ if (i < src.length) {
524
+ blank(i);
525
+ blank(i + 1);
526
+ i += 2;
527
+ }
528
+ continue;
529
+ }
530
+ if (ch === '"' || ch === "'") {
531
+ i++;
532
+ while (i < src.length && src[i] !== ch && src[i] !== "\n") {
533
+ if (src[i] === "\\") {
534
+ blank(i);
535
+ i++;
536
+ if (i < src.length) {
537
+ blank(i);
538
+ i++;
539
+ }
540
+ continue;
541
+ }
542
+ blank(i);
543
+ i++;
544
+ }
545
+ if (i < src.length && src[i] === ch)
546
+ i++;
547
+ prev = ch;
548
+ word = "";
549
+ prevWasWord = false;
550
+ continue;
551
+ }
552
+ if (ch === "`") {
553
+ stack.push({ tpl: true, braces: 0 });
554
+ i++;
555
+ prev = "`";
556
+ word = "";
557
+ prevWasWord = false;
558
+ continue;
559
+ }
560
+ if (ch === "/" && regexCanFollow(prev, word)) {
561
+ blank(i);
562
+ i++;
563
+ let inClass = false;
564
+ while (i < src.length && (inClass || src[i] !== "/") && src[i] !== "\n") {
565
+ if (src[i] === "\\") {
566
+ blank(i);
567
+ i++;
568
+ if (i < src.length) {
569
+ blank(i);
570
+ i++;
571
+ }
572
+ continue;
573
+ }
574
+ if (src[i] === "[")
575
+ inClass = true;
576
+ else if (src[i] === "]")
577
+ inClass = false;
578
+ blank(i);
579
+ i++;
580
+ }
581
+ if (i < src.length && src[i] === "/") {
582
+ blank(i);
583
+ i++;
584
+ }
585
+ while (i < src.length && /[a-z]/i.test(src[i])) {
586
+ blank(i);
587
+ i++;
588
+ }
589
+ prev = "/";
590
+ word = "";
591
+ prevWasWord = false;
592
+ continue;
593
+ }
594
+ if (ch === "{")
595
+ top.braces++;
596
+ else if (ch === "}") {
597
+ if (top.braces === 0 && stack.length > 1) {
598
+ stack.pop();
599
+ i++;
600
+ prev = "}";
601
+ word = "";
602
+ prevWasWord = false;
603
+ continue;
604
+ }
605
+ top.braces--;
606
+ }
607
+ if (WORD_CHAR.test(ch)) {
608
+ word = prevWasWord ? word + ch : ch;
609
+ prevWasWord = true;
610
+ }
611
+ else {
612
+ prevWasWord = false;
613
+ if (!/\s/.test(ch))
614
+ word = "";
615
+ }
616
+ if (!/\s/.test(ch))
617
+ prev = ch;
618
+ i++;
619
+ }
620
+ return out.join("");
621
+ }
622
+ /** 検出トークン → 表示名と record-form の置き換え案内。 */
623
+ function nativeControlHint(token) {
624
+ if (token.startsWith("if"))
625
+ return { name: "if (...)", hint: "use when(cond, () => then, () => else)" };
626
+ if (token.startsWith("switch"))
627
+ return { name: "switch (...)", hint: "use when(...) chains with eq(...) conditions" };
628
+ switch (token) {
629
+ case "??=":
630
+ case "&&=":
631
+ case "||=":
632
+ return { name: token, hint: "authoring is declarative; bind a fresh const via coalesce/and/or builders" };
633
+ case "??":
634
+ return { name: "??", hint: "use coalesce(a, b)" };
635
+ case "?.":
636
+ return { name: "?.", hint: "use opt(x).field (refOpt)" };
637
+ case "&&":
638
+ return { name: "&&", hint: "use when(cond, () => x) for a guard, or and(a, b) for a boolean" };
639
+ case "||":
640
+ return { name: "||", hint: "use or(a, b) for a boolean, or coalesce(a, b) for a default" };
641
+ case "===":
642
+ case "==":
643
+ return { name: token, hint: "use eq(a, b)" };
644
+ case "!==":
645
+ case "!=":
646
+ return { name: token, hint: "use ne(a, b)" };
647
+ case "?":
648
+ return { name: "?:", hint: "use when(cond, () => then, () => else)" };
649
+ default:
650
+ return { name: "!", hint: "use not(x) or ne(a, b)" };
651
+ }
652
+ }
653
+ /** method / helper / class ソースを走査し、ネイティブ制御構文を fail-closed で拒否する。 */
654
+ function scanNativeControlSyntax(source, label) {
655
+ const stripped = stripNonCode(source);
656
+ const bodyStart = stripped.indexOf("{"); // 本体(最初の `{` 以降)のみ走査する
657
+ if (bodyStart < 0)
658
+ return;
659
+ const m = NATIVE_CONTROL_TOKEN.exec(stripped.slice(bodyStart + 1));
660
+ if (!m)
661
+ return;
662
+ const at = bodyStart + 1 + m.index;
663
+ const excerpt = source
664
+ .slice(Math.max(0, at - 30), Math.min(source.length, at + 30))
665
+ .replace(/\s+/g, " ")
666
+ .trim();
667
+ const { name, hint } = nativeControlHint(m[0]);
668
+ afail("NATIVE_CONTROL_SYNTAX", `${label}: native control syntax '${name}' cannot be observed over recorded values at build time (JS Proxy has no ToBoolean/equality trap) and would silently mislower the DAG — ${hint} (near \`${excerpt}\`)`);
669
+ }
670
+ /**
671
+ * compileBehaviors — `SemanticBehavior` 継承(または `@behavior` 付き)クラスを
672
+ * component-graph IR(scp-ir-architecture.md §5)へ lower する共有 Compiler。
673
+ *
674
+ * - **各 public メソッド = 1 root Behavior**(`components[]` の 1 エントリ。名前 = メソッド名)。
675
+ * `#`-private はランタイム不可視でそもそも列挙されない。TS の `private` 修飾子は型消去で
676
+ * ランタイムに残らないため、**評価ベースのヘルパ規約は `#` または `_`-prefix**(`_` 始まりは
677
+ * publish されない)。ヘルパはメソッド本体からの呼び出しで record にインライン展開される。
678
+ * - 評価の**前に**メソッド / ヘルパ / クラスソースを {@link scanNativeControlSyntax} で走査し、
679
+ * 評価では観測できないネイティブ制御構文(`?:` / `&&` / `||` / `??` / `?.` / `!` /
680
+ * `==`/`===`/`!=`/`!==` / `if` / `switch` / 複合論理代入)を `NATIVE_CONTROL_SYNTAX` で
681
+ * fail-closed に拒否する(bc#24 audit fix — 残余はヘッダ docstring 参照)。
682
+ * - メソッド本体は throwing-sentinel recorder で 1 回評価され、`$` 参照・component 呼び出し・
683
+ * structured control({@link when} / `.map`)が body ノード・Wire・plan に lower される。
684
+ * - 生成 IR は `assertPortableComponentGraph` を必ず通してから返す(fail-closed の自己検査)。
685
+ * 実行は既存の `runBehavior` がそのまま担う。
686
+ *
687
+ * effect(Query/Command)はここでは一切扱わない — {@link classifyBehaviorEffect} を参照。
688
+ */
689
+ export function compileBehaviors(cls, opts = {}) {
690
+ if (!isBehaviorClass(cls))
691
+ afail("NOT_A_BEHAVIOR_CLASS", `class '${cls?.name ?? String(cls)}' is not a behavior class (extend SemanticBehavior or mark it with @behavior)`);
692
+ const proto = cls.prototype;
693
+ const methodNames = Object.getOwnPropertyNames(proto).filter((name) => {
694
+ if (name === "constructor" || name.startsWith("_") || name.startsWith("#"))
695
+ return false;
696
+ const d = Object.getOwnPropertyDescriptor(proto, name);
697
+ return typeof d?.value === "function";
698
+ });
699
+ if (methodNames.length === 0)
700
+ afail("NO_PUBLIC_METHOD", `class '${cls.name}' declares no public method (each public method is one root Behavior)`);
701
+ // ── source scan(評価の前に fail loud — NATIVE_CONTROL_SYNTAX)─────────────────
702
+ // public メソッドと `_`-prefix ヘルパは個別に(正確なラベルで)、`#`-private ヘルパは
703
+ // prototype から列挙できないためクラスソース全体(親クラス連鎖含む)で走査する。
704
+ for (const method of methodNames)
705
+ scanNativeControlSyntax(proto[method].toString(), `method '${method}'`);
706
+ for (const name of Object.getOwnPropertyNames(proto)) {
707
+ if (name === "constructor" || methodNames.includes(name))
708
+ continue;
709
+ const d = Object.getOwnPropertyDescriptor(proto, name);
710
+ if (typeof d?.value === "function")
711
+ scanNativeControlSyntax(d.value.toString(), `helper '${name}'`);
712
+ }
713
+ for (let k = cls; typeof k === "function" && k !== SemanticBehavior && k !== Function.prototype; k = Object.getPrototypeOf(k)) {
714
+ const src = Function.prototype.toString.call(k);
715
+ if (!/\{\s*\[native code\]\s*\}/.test(src))
716
+ scanNativeControlSyntax(src, `class '${k.name || "<anonymous>"}'`);
717
+ }
718
+ const instance = new cls();
719
+ const components = [];
720
+ for (const method of methodNames) {
721
+ const frame = {
722
+ method,
723
+ nodes: [],
724
+ nodeIds: new Set(),
725
+ inputPorts: new Map(),
726
+ seq: 0,
727
+ mapSeq: 0,
728
+ branchStack: [],
729
+ pendingMap: null,
730
+ };
731
+ const dollar = makeExpr({ node: { ref: [] }, refPath: [], refOp: "ref", isInput: true });
732
+ currentFrame = frame;
733
+ let returned;
734
+ try {
735
+ returned = proto[method].call(instance, dollar);
736
+ }
737
+ finally {
738
+ currentFrame = null;
739
+ }
740
+ if (returned === undefined)
741
+ afail("BAD_OUTPUT", `method '${method}' must return its Output Port (got undefined)`);
742
+ if (returned instanceof Promise)
743
+ afail("BAD_OUTPUT", `method '${method}' must be synchronous and declarative (async/await is derived by the compiler, not written)`);
744
+ const output = lowerValue(returned, `method '${method}' output`);
745
+ const inputPorts = {};
746
+ for (const [k, s] of frame.inputPorts)
747
+ inputPorts[k] = opts.inputPorts?.[method]?.[k] ?? s;
748
+ // 上書き schema にしか現れない Port も契約として載せる(型抽出ツールの接続点)。
749
+ for (const [k, s] of Object.entries(opts.inputPorts?.[method] ?? {}))
750
+ if (!(k in inputPorts))
751
+ inputPorts[k] = s;
752
+ // lowering の末尾は builder.ts の共有 core(B-3): Component 組み立て・plan 導出
753
+ //(Kahn levels)・決定的正規化(キーソート)・Guard 自己検査は recorder 経路と
754
+ // データ登録経路(buildComponentDefinition)の単一実装。catalog port 照合は
755
+ // record 時(recordComponentCall)に同じ共有関数で済ませているため catalog は渡さない。
756
+ components.push(buildComponentCore({ name: method, inputPorts, body: frame.nodes, output }, { concurrency: opts.concurrency ?? DEFAULT_PLAN_CONCURRENCY }));
757
+ }
758
+ const ir = { irVersion: 1, exprVersion: opts.exprVersion ?? SPEC_VERSIONS.expression, components };
759
+ assertPortableComponentGraph(ir); // fail-closed 自己検査(P0-2 Guard を必ず通す)
760
+ return ir;
761
+ }
762
+ // ── effect 導出(consumer の CQRS 層のための共有ヘルパ — authoring は effect 非依存)────
763
+ /**
764
+ * referencedComponents — lowering 済み合成コンポーネントの body が参照する catalog 名
765
+ * (componentRef / map。重複除去・出現順)。
766
+ */
767
+ export function referencedComponents(component) {
768
+ const out = [];
769
+ for (const n of component.body) {
770
+ const name = "map" in n ? n.map.component : "cond" in n ? null : n.component;
771
+ if (name !== null && !out.includes(name))
772
+ out.push(name);
773
+ }
774
+ return out;
775
+ }
776
+ /**
777
+ * classifyBehaviorEffect — CQRS の Query/Command 分類を **component graph から導出**する
778
+ * consumer 向けヘルパ。authoring は effect 非依存(Query/Command の語彙を持たない)であり、
779
+ * 分類は「body が consumer の write-catalog(`PutItem`/`Insert`/… — consumer が指定)を
780
+ * 参照するか」という graph の性質にすぎない、という責務分離(bc#24 / litedbmodel v2 §2.4)の
781
+ * 実装点。
782
+ */
783
+ export function classifyBehaviorEffect(component, writeComponents) {
784
+ const writes = new Set(writeComponents);
785
+ return referencedComponents(component).some((c) => writes.has(c)) ? "writing" : "read-only";
786
+ }
787
+ //# sourceMappingURL=authoring.js.map