/** * behavior.ts — component-graph IR schema + `runBehavior` unified execution IF (COMMON). * * scp-ir-architecture.md §5–§7 の規範実装。P0-1/P0-2/P0-3 の behavior-contracts 側。 * * 「振る舞い=Behavior をコンポーネントとして契約化し、Port と Wire で合成する」SCP の * 可搬 IR は **component-graph**(`components[]{ name, inputPorts, body[], output, plan }`)で * 表現される。`body` ノード種は: * - `componentRef` — catalog 名(文字列参照のみ)+ port 式(Expression IR) * - `map` — 配列 Port 反復(`over`/`as`/component/ports、v2: `when`/`into`/`batched`) * - `cond` — Conditional(`if`/`then`/`else`、純 Expression。handler を呼ばない) * * 実行機構(§7.2)は既存 COMMON をそのまま使う: * - SCP汎用層(合成・plan)= runPlan(stage 実行・Skip 伝播・Policy Kind)+ evaluateExpression。 * - 専用コンポーネント実行 = runPlan の `exec` seam を、catalog名 → handlers[name] で解決して呼ぶ * (汎用層=COMMON、専用コンポーネント=CONSUMER, runtime-boundary.md の分類に一致)。 * * handler は境界注入(`IR + {effects,config,hooks}` 不変則, concept.md §4.4)。IR には catalog * 参照名+Port配線だけが載り、実装(副作用・SDK)は載らない(C4)。 */ import { type Value, type Scope } from "./expr-eval.js"; import { type ExecutionPlanSpec, type ExecOutcome, type PolicyKind, type RelationKind, type ElementPolicyKind, type ErrorDetail } from "./plan.js"; /** * 可搬型記法の閉集合(typed codegen の導出型・レジストリ不要)。Expression IR の operator * 閉集合(guard.ts の PORTABLE_EXPR_OPERATORS)と同じ精度で規範化する。これ以外は * `assertPortableComponentGraph` が fail-closed で reject する(`"any"` エスケープハッチ無し)。 * * - スカラ: `"string" | "int" | "float" | "bool" | "null"`(5 種の文字列リテラルのみ) * - `{opt: T}` — nullable * - `{arr: T}` — 同種配列(要素型 T) * - `{map: T}` — 動的キー(string キー)・同種値型 T のマップ(`Record` 相当。 * `{arr:T}` の map 版。canonical 直列化はキーを code point 昇順にソート) * - `{obj: {field: T, …}}` — レコード(field キーに `__proto__` 禁止) * * すべて additive・省略可(`outType` 無しの IR は従来と完全に同一に扱われる — 後方互換)。 */ export type PortableScalarType = "string" | "int" | "float" | "bool" | "null"; /** * 汎用不透明値型(bc#156)。DB ドライバ境界の**本質的に generic な**束縛値(`WireValue`)を表す * 宣言型で、`value` は単一の不透明値・`{arr: "value"}` は heterogeneous な束縛値列(`Vec` * / `[]WireValue`)を表す。出現できる位置は 2 つだけで、どちらもゲートが強制する(§D4b): * - **入力(leaf-input port)** — 束縛値は境界で正当に generic。 * - **ノードの出力ワイヤ透過** — `wirePassthrough:true` のノードの `outType` が `value` / * `{arr:"value"}`({@link wirePassthroughOutTypeOk} が shape の SSoT)。中間結果を de-box せずに * 下流の op 非依存 leaf へ渡す宣言で、鎖の de-box は具体型を宣言した終端 1 箇所だけになる。 * **`component.outputType` には現れない**: component は透過フラグを持たない = 呼び側に de-box 契約が * 無いので、不透明のまま終わる鎖は fail-closed(compile 経路は `outputContract`(lowering.ts)、生 IR の * adopt は Portability Guard が落とす)。native emitter は宣言点で BC 所有の `WireValue`(#154 の SSoT)へ * 落とし、transport の param 境界へ透過的に spread する(covered plane には box を残さない — 値は * transport param 境界にだけ存在)。 */ export type PortableValueType = "value"; export type PortableType = PortableScalarType | PortableValueType | { opt: PortableType; } | { arr: PortableType; } | { map: PortableType; } | { obj: Record; name?: string; }; /** * Portable Type Notation — PortableType の正準表記(scp-error.md)。 * * 全域・決定的・全言語同一。`ErrorDetail.expectedType` が載せる表記で、**静的に宣言された型の * レンダリング**である(型オブジェクトの直列化ではない)。codegen ターゲットはこれをリテラルとして * 焼き込む — 実行時に型を歩くものは無く、直列化構造が文字列に載ることも無い。 */ export declare function portableTypeNotation(t: PortableType): string; /** Port schema エントリ(IR 側が配線先を静的検証できる最小契約)。 */ export interface PortSchema { type: string; required?: boolean; /** * `type:"array"`(配列 input port)の **要素の確定型**(可搬型記法・§5.2)。additive・省略可 * (#108 / bc#101)。map が input 配列を `over` するとき、`$as` 要素の native 型はこの注記から * 解決する(BC は推論しない = consumer-interface.md C3。注記が無ければ runtime-free native 化は * fail-closed)。可搬型閉集合の検査は body `outType` 等と同一(guard.ts)。省略時は従来と同一。 */ elemType?: PortableType; } /** 未生成表現の種別(single=null / connection=空 / item / items)。 */ export interface CatalogOutput { shape: string; } /** * catalog エントリ(DSL固有・§6)。名前+Port契約+出力形まで。ハンドラ実装(④)とは分離。 * `portableToIR` が false のコンポーネント参照は可搬 IR に載せてはならない(ts-runtime 専用)。 */ export interface CatalogEntry { name: string; inputPorts: Record; output?: CatalogOutput; portableToIR: boolean; /** * このコンポーネントが返す要素の確定型(可搬型記法・B0 / bc#44・§5.2)。additive・省略可。 * typed codegen(Layer B)が消費する導出型で、B0 では schema/guard/fingerprint にのみ載る * (emitter 消費は B2)。省略時は従来と完全に同一。 */ elemType?: PortableType; /** * op-agnostic leaf transport シンボル名(#176・SPEC 固定)。`defineLeaf` の `meta.transport` 由来で、 * node へ `leafSymbol` として焼かれ native emitter が読む(consumer 実装関数の契約名)。手書き catalog * では省略され、CLI override / 既定名にフォールバックする。additive・省略可。 */ leafSymbol?: string; /** * この leaf のハンドラが async I/O か(#210)。宣言(`@leaf static async` / `Promise` 戻り型)から * `catalogEntryFromLeaf` が導く **宣言メタ**で、additive・省略可(省略 = sync = 従来と完全に同一)。 * * **可搬 IR には載らない**: async/sync は language-runtime concern であり、IR に入れると * 「1 IR → N emitter」不変(rust-async IR ≠ go IR)が壊れる(generator/async-plan.ts 冒頭)。よって * ここ(catalog = 宣言由来のメタ)に載せ、codegen 入口が per-terminal-handler の I/O モデルとして * 読み、既存の bottom-up 伝播(async-plan.ts)が runner の async 性を導く。rust は `async fn`/`.await`、 * go は await 概念が無く無視、ts/py は runtime 側(`bindAsync` / `run_behavior_async`)で解決する。 */ async?: boolean; } export type Catalog = Record; /** 専用コンポーネント参照(catalog名+Port配線=Expression IR)。 */ export interface ComponentRefNode { id: string; /** ②catalog への参照(名前のみ。実装は載らない — C4)。 */ component: string; /** Port 配線(各値は Expression IR ノード)。 */ ports: Record; /** 依存する親ノード id(Wire)。root は省略。 */ parent?: string; /** 親 result のどのフィールドを束縛キーに読むか(null/欠落なら Skip)。 */ bindField?: string; /** 未生成表現の種別。 */ relationKind?: RelationKind; /** Error Policy Kind(既定 fail)。 */ policy?: PolicyKind; /** * このノード結果の確定型(可搬型記法・B0 / bc#44・§5.2)。additive・省略可。typed codegen * (Layer B)が消費する導出型で、B0 では schema/guard/fingerprint にのみ載る(emitter 消費は * B2/#46-48、graphddb lowering での注記生成は B1/#45)。省略時は従来と完全に同一。 */ outType?: PortableType; /** * 出力ワイヤ透過フラグ(bc#164)。additive・省略可。`true` のとき、このノード結果は de-box されず * **不透明ワイヤのまま**(`WireValue` / `Vec`)typed cell に格納される(下流の op-agnostic * leaf が per-op の `typed→Value` 再box 無しで消費する)。set 時 `outType` は `value` または * `{arr:"value"}` に限る({@link wirePassthroughOutTypeOk} で強制)。unset 時は従来どおり `value` を * 出力型に持てない(入力専用)。 */ wirePassthrough?: boolean; /** * 参照先 leaf の **入力ポート型契約**(#173・irVersion 2 で必須)。`ports` の各キーに対応する * `PortSchema`(leaf catalog `inputPorts` 由来の宣言ポート型)。native emitter の port 境界が * この宣言型を honor し(`inputPortTypeRef` → 期待型 → * `compileTo` materialize)、要素 kind からの推論を廃する。`node.outType`(出力型契約)の入力側の * 双子で、機械導出(authoring が `entry.inputPorts` から焼く)— 手書き禁止。irVersion 1 では省略。 */ portSchemas?: Record; /** * 参照先 leaf の op-agnostic transport シンボル名(#176・SPEC 固定・additive)。native emitter の * `resolveLeafSymbol` が解決する(`node.leafSymbol` があれば第一候補 → CLI `--leaf-transport` * override → 既定 `Leaf_`)。`@leaf` 宣言は transport を固定しないので通常は省略される。 */ leafSymbol?: string; } /** SCP の Map(配列 Port 反復。単一静的ノード)。 */ export interface MapNode { id: string; map: { /** 反復する配列 Port(Expression IR)。 */ over: unknown; /** 各要素を束縛する名前(例 `"$t"`)。 */ as: string; /** * 各要素で呼ぶ専用コンポーネント(catalog 名)。**`transform` map には無い**(要素本体が純式の map は * Component を呼ばない — {@link mapTransformExpr} が形の唯一の判定点)。 */ component?: string; /** Port 配線(`as` 束縛を参照できる Expression IR)。`component` と対で、`transform` map には無い。 */ ports?: Record; /** * 要素本体が**純式**の map(`xs.map($e => ({x: $e.y}))`)の要素式(Expression IR・`as` 束縛入りスコープ * で評価)。反復は SCP 構造層の責務(expression-ir.md §3「Map belongs to the SCP structural layer」) * なので、Expression IR に反復演算子を足すのではなく map ノードのもう 1 つの形として持つ。 * `component`/`ports` と**排他**(両方 / どちらも無しは fail-closed)。 */ transform?: unknown; /** * per-element guard(Expression IR、`as` 束縛入りスコープで評価。behaviorVersion 2)。 * `{cond:[when,true,false]}` へ lower して評価する(cond と同じ strict-bool 規律 — * 非 bool は TYPE_MISMATCH で fail-closed)。false の要素は skip(handler 未呼び出し・ * 結果リストから除外。順序は保持)。 */ when?: unknown; /** * zip-attach キー(behaviorVersion 2)。指定時、この map ノードの結果は「`over` の各要素へ * `into` キーで対応する handler 結果を書き戻した augment 済みリスト」(over と同じ長さ・順序)。 * `when` で skip された要素は **無変更で pass through**(`into` キーは付かない)。 * 親ノードの results は変異しない(コピーして augment)。guard 通過要素が object でなければ * MAP_INTO_ELEMENT_NOT_OBJECT で fail-closed。 */ into?: string; /** * バッチ fan-out(behaviorVersion 2)。true なら guard 通過全要素の ports を先に評価し、 * handler を **1 回だけ** `handler({items:[…]}, ctx)` で呼ぶ。handler は items と * 同じ長さ・順序の結果リストを返す契約(違反は MAP_BATCH_RESULT_MISMATCH で fail-closed)。 * guard 通過要素が 0 件なら handler は呼ばれず結果は空リスト。dedupe/chunk/retry は * CONSUMER handler 内の責務。 */ batched?: boolean; parent?: string; relationKind?: RelationKind; policy?: PolicyKind; /** * Element Error Policy Kind(既定 `error`・scp-error.md)。要素の Failure を map の Component * Failure にするか(`error`)、その要素を結果リストから落として続行するか(`skip`・順序保持)。 * * `skip` は**要素ごとの Failure が存在する場合のみ**合法。`batched` map は handler を 1 回だけ * 呼び結果リストを 1 つ受け取る=要素ごとの outcome が無いため fail-closed * (`ELEMENT_POLICY_NOT_APPLICABLE`)。非 map ノードには本フィールド自体が無い(単一 * Component の Failure 耐性は `continue` の領分)。 */ elementPolicy?: ElementPolicyKind; /** * 参照先 leaf の入力ポート型契約(#173・irVersion 2 で必須)。`ports` の各キーの `PortSchema`。 * {@link ComponentRefNode.portSchemas} と同一意味論(機械導出・手書き禁止)。irVersion 1 では省略。 */ portSchemas?: Record; /** * 参照先 leaf の op-agnostic transport シンボル名(#176・SPEC 固定・additive)。 * {@link ComponentRefNode.leafSymbol} と同一意味論(batched map も単一 leaf の 1 transport を共有)。 */ leafSymbol?: string; }; /** * この map ノード結果の確定型(可搬型記法・B0 / bc#44・§5.2)。additive・省略可。 * 通常は `{arr: <要素型>}`(`into` 指定時は augment 済みリストの要素型)。省略時は従来と同一。 */ outType?: PortableType; /** * 出力ワイヤ透過フラグ(bc#164)。additive・省略可。map ノードの `outType` は**要素型**なので、 * `true` のとき要素型は不透明 `value`(=produced array は `Vec`)で、各要素が de-box されず * ワイヤのまま格納される。{@link ComponentRefNode.wirePassthrough} と同一意味論。 */ wirePassthrough?: boolean; } /** * SCP の Fanout(`@refs` id-list fan-out — behaviorVersion 3, first-class node kind)。 * * # なぜ batched map ではないのか * batched map は**無条件の行整列**を強制する(`MAP_BATCH_RESULT_MISMATCH`: handler 結果は * items と同じ長さ・順序でなければならない — 上の `checkBatchAligned`)。native(go/rust)の * batched-map handler は物理的に dedupe/drop できず、整列した生リストしか返せない。よって * 「id-list を dedupe(first-seen)し dangling(null body)を drop し connection を返す」 * `@refs` 読みは batched map では表現できない。graphddb は現状これを INTERPRETER の * 出力 fold(`_fold_read_output`)で糊付けしているが typed-native には等価物が無い。 * * # このノードが所有するもの(THE ONE dedup/drop definition) * 物理層は `over`(id-list)を **dedup した 1 回の BatchGet**(batched handler 1 コール)。 * その整列生リストに対して: * 1. **first-seen dedupe**: 各要素の `dedupeKey` port 値で重複を排除(初出のみ残す・順序保持)。 * 2. **dangling drop**(`drop:"dangling"`): null/欠落 body を落とす。 * 3. **implicitSource strip**: `implicitSource` フィールドを結果要素から除去。 * 4. **connection wrap**: `{items:[…], cursor:null}` に包む(整列リストでは**ない** → * `MAP_BATCH_RESULT_MISMATCH` の整列制約は適用されない)。 * dedupe/drop の規範は {@link FANOUT_DEDUP_DROP} 1 箇所に集約し、interpreter({@link runFanout})と * native codegen(go/rust emitter)が verbatim に共有する(発散した手コピー禁止)。 * outType は connection `{obj:{items:{arr:}, cursor:{opt:"string"}}}`。 */ export interface FanoutNode { id: string; fanout: { /** fan-out する id-list(Expression IR。connection の physical BatchGet の入力キー列)。 */ over: unknown; /** 各 id 要素を束縛する名前(例 `"$ref"`)。 */ as: string; /** dedup 済み id ごとに 1 回の batched BatchGet で呼ぶ専用コンポーネント(catalog 名)。 */ component: string; /** Port 配線(`as` 束縛を参照できる Expression IR。batched handler の各 item ports)。 */ ports: Record; /** first-seen dedupe の基準となる、結果要素の port(フィールド名)。 */ dedupeKey: string; /** dangling(null/欠落 body)の扱い: `"dangling"`=drop / `"none"`=保持。 */ drop: "dangling" | "none"; /** 結果要素から除去する暗黙ソースフィールド名(省略時は strip しない)。 */ implicitSource?: string; /** 未生成表現の種別(fanout は常に connection)。 */ relationKind: "connection"; parent?: string; policy?: PolicyKind; /** * 参照先 leaf の入力ポート型契約(#173・irVersion 2 で必須)。`ports` の各キーの `PortSchema`。 * {@link ComponentRefNode.portSchemas} と同一意味論(機械導出・手書き禁止)。irVersion 1 では省略。 */ portSchemas?: Record; /** * 参照先 leaf の op-agnostic transport シンボル名(#176・SPEC 固定・additive)。 * {@link ComponentRefNode.leafSymbol} と同一意味論(fanout の batched dispatch も 1 transport を共有)。 */ leafSymbol?: string; }; /** * この fanout ノード結果の確定型(可搬型記法・§5.2)。connection * `{obj:{items:{arr:}, cursor:{opt:"string"}}}`。additive・省略可(native de-box に必須)。 */ outType?: PortableType; } /** SCP の Conditional(純 Expression。handler は呼ばない)。 */ export interface CondNode { id: string; cond: { /** 条件(Expression IR、bool を返す)。 */ if: unknown; /** 採用側 Expression。 */ then: unknown; else: unknown; parent?: string; }; /** * この cond ノード結果の確定型(可搬型記法・B0 / bc#44・§5.2)。additive・省略可。 * 省略時は従来と完全に同一。 */ outType?: PortableType; } /** * isControlGate — when(...) guard の gate CondNode(`{if:…, then:{obj:{ok:true}}, else:null}`)か。 * これは制御フロー専用ノードで出力データ型を持たない(consumer が参照する出力ではない)。gate は * authoring.ts の `ensureGate` が生成する固定形で、成立時 `{ok:true}` / 不成立時 `null` を返し、 * 分岐内ノードは `parent=gate, bindField:"ok"` の null-binding skip で実行選択される(execution-plan.md)。 * * 単一定義(SoT): type-gate(UNTYPED_NODE 免除)と typed-native emitter(skip-control 被覆)の双方が * この 1 関数を共有する。分岐した写しを作らない(`fanoutDedupDrop` と同じ単一定義主義)。この固定形に * だけ outType 不要を許す — Φ 合流 CondNode(本物の data-join)は outType を要求する(consumer の出力値だから)。 */ export declare function isControlGate(n: unknown): boolean; /** * nodeWirePassthrough — body ノードが出力ワイヤ透過(bc#164)としてフラグされているか。単一定義(SoT): * guard(可搬性検証)・interpreter({@link conformResultToOutType})・typed-native emitter(rust/go の * 格納サイト)が **この 1 関数**を共有する(分岐した写しを作らない — {@link isControlGate} と同じ規律)。 */ export declare function nodeWirePassthrough(n: unknown): boolean; /** * mapTransformExpr — map ノードが**純式 map**(要素本体が Expression・Component 呼びなし)なら要素式を、 * Component map なら undefined を返す。**形の判定はこの 1 関数**(guard / builder / interpreter / 5 emitter が * 共有する SSoT。`component` の有無で各所が独自に分岐すると形が増えたとき追随漏れが出る)。 */ export declare function mapTransformExpr(m: MapNode["map"]): unknown | undefined; /** * wirePassthroughOutTypeOk — 出力ワイヤ透過ノードの `outType` が不透明位置を持つか(=透過フラグが * 意味を持つ形か)。 * * **de-box は宣言型が駆動する**(#221): `value` は宣言型の**どの位置にも**置ける(トップレベル / 配列の * 要素 / record field / map value / それらのネスト)。de-box は各位置を宣言型どおりに materialize し、 * `value` 位置だけを as-is で格納する — 具体型の位置は 1 回 de-box される。かつて在った「透過は出力 * まるごと/配列直下だけ」という shape whitelist は、フラグがノード単位の boolean だったことの帰結で * あって意味論上の制約ではなかった(型記法は最初から混在を表現できていた)。 * * よってこの述語は「宣言型に不透明位置が 1 つ以上あるか」であり、フラグとの等価(フラグ ⇔ 型が不透明位置を * 含む)を guard / interpreter / emitter が共有する(規則を二重定義しない)。 */ export declare function wirePassthroughOutTypeOk(outType: PortableType | undefined): boolean; /** * mapNodeElemIsOpaqueWire — map ノードの**要素が不透明ワイヤ**か(`outType:"value"` + 出力ワイヤ透過)。 * map の `outType` は要素型なので、透過は「各要素が de-box されずワイヤのまま produced array へ積まれる」 * ことを意味する({@link MapNode.wirePassthrough})。native emitter の covered-map 判定が言語ごとに * 分かれているため、**この 1 つの述語**を両方が読む(規則を 2 箇所に書かない)。 */ export declare function mapNodeElemIsOpaqueWire(n: unknown): boolean; /** * portableTypeHasValue — 可搬型のどこかに不透明値 `value` が現れるか(再帰・全域)。 * {@link wirePassthroughOutTypeOk} の**対**: 出力型が `value` を含むのに合法な透過形でない * (record field / map value / ネスト配列の中の `value`)ものを名指しで fail-close するために使う。 * 「不透明値は出力の**トップレベル**(または top-level `arr` の直下要素)だけ」という単一の * shape 規則を、判定(ok)と検出(has)の 2 面から支える。 */ export declare function portableTypeHasValue(t: PortableType | undefined): boolean; /** * body ノード種別の閉集合(**値の runtime-enumerable SoT**)。型 {@link BodyNode} はこの 4 種の * union で、`nodeKind` の返り値・網羅検査はこの配列から導出する(POLICY_KINDS / RELATION_KINDS と * 同型の SoT パターン — plan.ts)。新 body ノード種を足すときはここに 1 語追加するのが正であり、 * これを SoT にすることで「新ノード種が matrix 未カバーのまま緑」になる穴を塞ぐ(#132)。 */ export declare const BODY_NODE_KINDS: readonly ["componentRef", "map", "cond", "fanout"]; /** * body ノードが載せうる **注記フィールド**の閉集合(`id` / ノード種キー / `ports` のような構造そのもの * ではなく、実行・型・配線の**契約**を述べるキー)。{@link BODY_NODE_KINDS} と同型の値 SoT で、下の * 型レベル等式が interface との一致を強制する(interface にフィールドを足してここに足し忘れると * **コンパイルが落ちる**)。authoring 面がこの語彙をどこまで産出できるかは ast-equivalence.test.ts の * `[completeness]` が実コンパイルした IR で機械検査する(産出不能なものは理由付きで明示する)。 */ export declare const BODY_NODE_ANNOTATIONS: readonly ["parent", "bindField", "relationKind", "policy", "elementPolicy", "transform", "when", "into", "batched", "dedupeKey", "drop", "implicitSource", "outType", "wirePassthrough", "portSchemas", "leafSymbol"]; export type BodyNodeAnnotation = (typeof BODY_NODE_ANNOTATIONS)[number]; /** body ノード種別({@link BODY_NODE_KINDS} から導出)。 */ export type BodyNodeKind = (typeof BODY_NODE_KINDS)[number]; export type BodyNode = ComponentRefNode | MapNode | CondNode | FanoutNode; /** 合成コンポーネント定義(=公開メソッド, §5)。 */ export interface Component { name: string; inputPorts: Record; body: BodyNode[]; /** 合流(Φ): body ノード結果を参照して最終出力を組む Expression IR。 */ output: unknown; /** 依存から導出された plan(`{groups, concurrency}`)。省略時は body 順の逐次。 */ plan?: ExecutionPlanSpec; /** * `output` 合流式の確定型(可搬型記法・B0 / bc#44・§5.2)。additive・省略可。typed codegen * (Layer B)が消費する導出型で、B0 では schema/guard/fingerprint にのみ載る。省略時は従来と同一。 */ outputType?: PortableType; } /** * 宣言スキーマの**フィールド閉集合**(値の runtime-enumerable SoT)。{@link BODY_NODE_ANNOTATIONS} と * 同型で、下の型レベル等式が interface との一致を強制する(interface に足してここに足し忘れると * **コンパイルが落ちる**)。authoring 面がこれらを産出できるかは ast-equivalence.test.ts の * `[completeness]` が実コンパイルした IR / 宣言 catalog で機械検査する — `CatalogEntry.async` は * まさにこの位置に開いた穴(catalog に在るのに宣言から立たない・#210)だった。 */ export declare const COMPONENT_FIELDS: readonly ["name", "inputPorts", "body", "output", "plan", "outputType"]; export type ComponentField = (typeof COMPONENT_FIELDS)[number]; /** catalog エントリ(`@leaf` 宣言の射影先)のフィールド閉集合。 */ export declare const CATALOG_ENTRY_FIELDS: readonly ["name", "inputPorts", "output", "portableToIR", "elemType", "leafSymbol", "async"]; export type CatalogEntryField = (typeof CATALOG_ENTRY_FIELDS)[number]; /** Port schema のフィールド閉集合。 */ export declare const PORT_SCHEMA_FIELDS: readonly ["type", "required", "elemType"]; export type PortSchemaField = (typeof PORT_SCHEMA_FIELDS)[number]; /** 可搬 IR ルート(普遍形式・全 SCP ライブラリ共通, §5)。 */ export interface ComponentGraphIR { /** * IR envelope 版。**codegen 契約は irVersion 2**(#173): 全 componentRef/map/fanout ノードが * `portSchemas`(参照先 leaf の入力ポート型契約)を持ち、native emitter の port 境界が宣言型を * honor する(silent 推論なし)。runtime(`runBehavior`)は port 入力型を使わない(C4 — catalog は * IR に載らない)ため irVersion 1 の IR を従来どおり実行でき、guard も v1 では `portSchemas` を * 要求しない。codegen(`generateModule`)は irVersion 2 のみ受理する。 */ irVersion: 2 | 3; exprVersion: number; components: Component[]; } /** * handler ctx(behaviorVersion 2)。runBehavior が全 handler 呼び出しに node identity * (`nodeId` = body ノード id / `component` = catalog 名)を渡す。エラー文脈・トレース用。 * map(非 batched)ではさらに `bound` = `as` 束縛の要素値。追加のみ(既存 handler 互換)。 */ export interface HandlerCtx { nodeId: string; component: string; bound?: Value; } /** * 専用コンポーネント handler。評価済み Port({name: Value})と ctx(node identity + * map 要素の束縛値)を受け取り、ExecOutcome を返す。副作用・SDK・config・hooks はこの * handler が閉じ込める(IR には載らない — C4 / concept.md §4.4 の `IR + {effects,config,hooks}`)。 * `map.batched` の handler は ports として `{items:[<要素ごとの評価済み ports>…]}` を受け取り、 * items と同じ長さ・順序の結果リストを返す。 */ export type Handler = (ports: Record, ctx: HandlerCtx) => ExecOutcome; export type Handlers = Record; /** * 非同期 handler(runBehaviorAsync 用)。ExecOutcome を同期で返しても Promise で返してもよい。 * `plan.concurrency > 1` の component では、同一 stage 内の兄弟ノードに対して**並行に * 呼ばれ得る**(bc#23。並行呼び出しを許容しない consumer は `concurrency: 1` を出荷する)。 */ export type AsyncHandler = (ports: Record, ctx: HandlerCtx) => ExecOutcome | Promise; export type AsyncHandlers = Record; export type BehaviorFailureCode = "UNKNOWN_COMPONENT" | "UNKNOWN_NODE_KIND" | "MAP_OVER_NOT_ARRAY" | "MAP_INTO_ELEMENT_NOT_OBJECT" | "MAP_BATCH_RESULT_MISMATCH" | "FANOUT_OVER_NOT_ARRAY" | "FANOUT_BATCH_RESULT_MISMATCH" | "UNKNOWN_ELEMENT_POLICY" | "ELEMENT_POLICY_NOT_APPLICABLE" | "UNKNOWN_ENTRY"; export declare class BehaviorFailure extends Error { code: BehaviorFailureCode; /** 構造化された回復可能ペイロード(scp-error.md「The Error Value」)。 */ detail?: ErrorDetail; constructor(code: BehaviorFailureCode, message: string, detail?: ErrorDetail); } /** body ノードの依存親 id(Wire)を取り出す(root は undefined)。 */ export declare function nodeParent(n: BodyNode): string | undefined; /** componentRef の bindField(null-binding skip のキー)。map/cond/fanout は持たない。 */ export declare function nodeBindField(n: BodyNode): string | undefined; export declare function nodeRelationKind(n: BodyNode): RelationKind | undefined; /** * PORT_SCHEMA_TYPES — `PortSchema.type` の**閉集合**(可搬 IR の port 型語彙の SSoT)。 * * `type` は自由文字列だったので、閉集合の外(`"strng"` のような綴り間違い)は「解決できない型」として * **黙って**扱われ、その port の型契約が丸ごと無効になっていた(宣言はあるのに何も検査されない)。 * Portability Guard がこの集合で弾くので、宣言が黙って意味を失うことはもう無い。 * * 別名(`arr`=`array` / `list`=`array` / `number`=`float` / `literal`=`string`)は既存 IR が使っている * 綴りなのでここに列挙して**受理**する。列挙が 1 箇所にあることが要点で、読み手ごとに語彙が違う状態 * (型ゲートは `object`/`arr` を知らず、native emitter は `null` を知らない)が本当の欠陥だった。 */ export declare const PORT_SCHEMA_TYPES: ReadonlySet; /** `elemType`(要素型 / whole-struct 型の注記)に**読み手がいる** port type だけの部分集合。 */ export declare const PORT_SCHEMA_ELEM_TYPES: ReadonlySet; /** * portableTypeOfInputPort — input port schema から確定 PortableType を得る(確定しているときのみ)。 * * **port 型語彙の唯一の reader**。native emitter の `inputPortTypeRef` はこの結果を TypeRef へ写すだけで、 * 自前の switch を持たない — 以前は 2 つの switch が別々の語彙を持ち、型ゲートは `object`/`arr`/`value`/ * `literal`/`number` を知らず(=それらの port は型ゲートから見えず、port 契約の照合からも黙って外れた)、 * native 側は `null` を知らなかった。 * * `array`/`map` は `elemType`(A1/#108 の要素型)が付いているときだけ確定。`object` は #173 の * whole-struct port で、obj 型を `elemType` に載せる。`unknown` は「静的型を持たない」の明示綴りで、 * 確定しない(=output 直参照で `UNTYPED_NODE`)。 */ export declare function portableTypeOfInputPort(schema: PortSchema): PortableType | undefined; /** * descend — 確定型 `t` に `field` を 1 段降りた型を返す(解決不能なら undefined)。 * - `{obj:{…}}`: 当該 field の型。無い field は解決不能。 * - `{opt:U}`: 内側 U を降りて `{opt: …}` で包み直す(optional chain 相当)。 * scalar / arr / map へのフィールドアクセスは解決不能({obj} だけが静的キー field を持つ)。 */ export declare function descend(t: PortableType, field: string): PortableType | undefined; export declare function unproducibleNodeIds(comp: Component): ReadonlySet; /** * refPathKey — proven-non-null narrowing set における静的 ref PATH の正準キー。narrowing の**生産者** * ({@link nodeNarrowedPaths})と**消費者**(型層 type-gate と native emitter native-expr)が**この 1 関数** * で鍵付けするので、証明された ref とその使用位置が常に一致する。 */ export declare function refPathKey(path: readonly string[]): string; /** * narrowedNonNullPaths — gate/guard の条件が TRUE のとき NON-NULL を保証する ref PATH の集合。制御 gate * (`when` → gate 内ノードは `if` 成立時のみ実行)と map の要素 guard(`when` 成立要素のみ keep)が課す * 非 null 証明を、条件式から抽出する。認識するのは条件が保証する null-presence 連言だけ: * `{ne:[, null]}`(左右どちらの順でも)と、それらの `{and:[…]}`(`and` が真=各連言が真)。その他の形は * 何も寄与しない(保守的 — 未証明 ref は fail-closed のまま)。ref 抽出は {@link bareRefPath}、鍵は * {@link refPathKey}(2 つ目の綴りを作らない)。 */ export declare function narrowedNonNullPaths(cond: unknown): ReadonlySet; /** * nodeNarrowedPaths — THIS ノードの port を lower/型付けする scope で NON-NULL が**証明済み**の ref path 集合。 * 証明源は「port 評価の前に runtime が既に行う skip/keep」の 3 機構と 1:1(plan.ts preflightOp / behavior.ts * map keep): * * (1) **bindField** — `bindField: bf`・`parent: P` の componentRef 子は `P.value[bf]` が非 null のときだけ * 実行される(preflightOp の null-binding skip)。ゆえに `ref([P, bf])` はこの scope で非 null。これが * #263 で欠けていた源(型層は宣言 `{opt:…}` を見るが、実態はこの位置で非 null)。map/fanout の子は * bindField を持たない({@link nodeBindField})ので、この源からは narrow しない(gate 済み parent が * あっても、null marker でも実行され得る — 元 (a3) の除外規則を保つ)。 * (2) **control-gate parent** — さらに P が制御 gate なら、その `if` が成立して初めて子が走るので、`if` が * `ne(, null)` で検査した ref はすべて非 null((a3) の gated read)。 * (3) **map の `when` guard** — map は `when` 成立要素のみ keep する(behavior.ts map keep)ので、guard が * 検査した ref は、この map が port を lower する kept 要素で非 null((a2) の guarded element key)。 * * 未 gate/未 guard のノードは空集合(その opt は fail-closed のまま)。 */ export declare function nodeNarrowedPaths(comp: Component, node: BodyNode): ReadonlySet; /** * mapIntoHoldsUnproduced — `map.into` の**載せ先フィールド**の宣言型が、per-element Component の * **未生成値**を保持できるか(#232 の唯一の述語 — `unproducibleNodeIds` の要素粒度の双子)。 * * `into` は「別のエンティティを引いて要素へ zip-attach する」位置であり、その引きは未生成になり得る * (参照先の行が無い / 要素 guard skip / elementPolicy skip)。未生成値の綴りは relationKind が決める * (`builder.ts` の relationKind 検証がその SSoT: 「未生成の既定は null で、注記の**不在**がそれを意味する」): * - relationKind 無し → 未生成 = **null** ⇒ 載せ先は `{opt:…}` でなければ保持できない * - `relationKind:"connection"` → 未生成 = 空 connection `{items:[],cursor:null}` ⇒ 載せ先は obj * * 保持できない宣言(非 opt の非 connection)は「未生成が起きたら必ず落ちる」型を宣言している: * {@link runBehavior} の applyInto は skip 要素へ `into` を**書かない**ので出力 conformance が * MISSING_PROP、leaf が未生成値(null)を返した要素では TYPE_MISMATCH になる。宣言が契約である以上、 * これは実行時ではなく generate 時に落とすべき食い違い(#232 — #220 が出力位置に対して行った前倒しを * `into` の載せ先へ届かせる)。 * * 要素型が obj でない / `into` キーが宣言に無い場合も false(`into` は object 要素と、載せ先の宣言を * 要求する — applyInto の `MAP_INTO_ELEMENT_NOT_OBJECT` と同じ前提)。 */ export declare function mapIntoHoldsUnproduced(n: MapNode): boolean; /** * holdsUnproduced — 型 `t` は、relationKind `kind` のノードの**未生成値を `rest` で辿った値**を保持できるか * (#232 の述語)。未生成値そのものは {@link unproducedValue}(plan.ts・SSoT)から**取り出して辿る** — * `{items:[],cursor:null}` というキー名も値もここへ写さない(写した瞬間に 2 つ目の定義になる)。 * * 参照はノードを丸ごと読むとは限らないので、判定対象は「未生成値の**その位置**」である: * - relationKind 無し(未生成 = null): `rest` が空なら null ⇒ `{opt:…}` だけが保持できる。1 段でも * 降りるなら **null を辿ることになる** — `ref` は NULL_REF で落ちる(保持できる型は無い)が、 * `refOpt` は null を**返す**({@link refCore})ので opt スロットはそれを保持できる(`optional`)。 * - `relationKind:"connection"`(未生成 = 空 connection): `rest` 空なら obj が保持できる。`items` へ * 降りれば読む値は **`[]`** = 任意の要素型の配列が保持できる。`cursor` へ降りれば **null** = opt。 * 空 connection に無いキーへ降りる参照は `ref`/`refOpt` とも実行時に MISSING_PROP なので保持不能 * (`optional` は中間 null にしか効かない — {@link refCore} がそう綴っている)。 * * 「空 connection にどのキーがあるか」を決めるのは下の `Object.hasOwn` 走査**だけ**であり、キー名は * ここにも呼び側にも literal で現れない({@link unproducedValue} を取り出して辿る)。 * * 読み手は 3 つあり、**この 1 述語**を全員が読む(写しを作らない): `map.into` の載せ先({@link * mapIntoHoldsUnproduced})、未生成になり得るノードを読む output 位置の**導出**({@link * unproducedRefType})、そして typed-native の produced-aware 出力 lowering の floor * (generator/typed.ts `lowerProducedAwareRead` — 未生成読みを表現できないスロットで fail-closed)。 * output 位置の宣言検査(#239/#241)は独立した述語ではない — 導出した「実際に materialize される型」と * 宣言型を `sameType` で突き合わせる 1 検査(type-gate の #243)の 2 方向である。 * * @param optional 参照が `refOpt` か(中間 null を null 伝播できるか)。既定 `false` = `ref`。 */ export declare function holdsUnproduced(t: PortableType | undefined, kind: RelationKind | undefined, rest?: readonly string[], optional?: boolean): boolean; /** * unproducedRefType — 未生成になり得るノードを path `rest`(ノード id を除いた残り)で読んだときの型 * (#220 の導出・#241 で厳密化・#243 で path 全域へ)。 * * 値域は「生成された値の型 `t` ∪ 未生成値の**その位置**」であり、後者が既に `t` の住人なら参照は `t` の * まま — 広げる必要が無い。その判定が {@link holdsUnproduced} そのもの(2 つ目の綴りを作らない)。 * ランタイム({@link writeUnproduced})は skip したノードへ `unproducedValue(relationKind)` を書き戻し、 * 出力の参照はそれを読む。`{opt:t}` を余計に被せると「起こり得ない null」を宣言型に載せることになり、 * consumer は永久に来ない分岐を書かされる(実態より**広い**宣言 — #232/#239 の裏返し)。 */ export declare function unproducedRefType(t: PortableType, kind: RelationKind | undefined, rest?: readonly string[]): PortableType; /** * nodeResultType — body ノードの「結果参照が読む型」。map ノードの `outType` は**要素型**ゆえ結果配列は * `{arr: 要素型}`、fanout/cond/componentRef は `outType` そのもの(type-gate と emitter が共有する SSoT)。 * outType 注記が無いノード(control gate 等)は undefined。 */ export declare function nodeResultType(n: BodyNode): PortableType | undefined; /** * overIsDeclaredOptArr(#177 (A))— fanout/map の `over` の **宣言型が `{opt:{arr:E}}`** か。 * true のとき null の over は「absent な optional collection」= **空反復(0 要素)** として扱う * (faithful opt 意味論)。false(`required` arr 等)なら null は従来どおり `(FANOUT|MAP)_OVER_NOT_ARRAY`。 * * over が bare ref のときだけ解決する(計算された over は宣言 opt とみなさない = strict)。 * - prior-node ref: 参照先ノードの結果型({@link nodeResultType})を path で {@link descend} して opt-arr 判定。 * - input-port ref(単一 path): **optional な array port は要素型注記(native 専用)に関わらず宣言 opt-arr**。 * interpreter の opt-collection 意味論は要素型に依存しない(native 被覆は別途 elemType を要求する)。 * native(rust/go typed-native)の opt-over 被覆と同一集合で empty 反復するため byte-equal。 */ export declare function overIsDeclaredOptArr(over: unknown, body: BodyNode[], inputPorts: Record): boolean; /** * conformResultToOutType — ノード結果を宣言 `outType` に照らして検査し、正規化した値を返す公開 seam。 * * run_behavior({@link checkNodeOutTypeIn})と、生成された straight-line / typed TS モジュールが * **この 1 箇所を共有**して「node 結果 ≡ 宣言 outType」の正規化を行う(検査の発散も正規化の発散も * 作らない — 型消去の TS でも生成コードは runtime でこの SSoT を呼び、run_behavior と byte 一致する)。 * * map ノードの `outType` は**要素型**(scp-error.md「Map element type」)なので、結果型は * `{arr: 要素型}`。これは compiler がノード結果参照の型を解決するときの読み方(`type-gate` の * `"map" in n ? {arr: ot} : ot`)と同一。map 以外は結果型そのもの。 */ export declare function conformResultToOutType(nodeId: string, value: Value, outType: PortableType, isMapNode: boolean, opaque?: boolean): Value; /** * FANOUT_DEDUP_DROP — THE ONE dedup/drop definition(behaviorVersion 3)。 * * fanout の物理層(dedup 済み `over` に対する 1 回の batched handler)が返した「整列生リスト」 * `alignedBodies`(`items` と同じ長さ・順序)を、connection の最終 `items` へ変換する規範。 * **interpreter({@link runFanout})と native codegen(go/rust emitter)が verbatim に共有する * 単一実装**。手コピーで発散させてはならない(bc の owner 決定)。 * * 規則(この順序で適用): * 1. **first-seen dedupe**: 各 body の `dedupeKey` フィールド値で重複排除。初出のみ残す・順序保持。 * 同一 key の 2 個目以降は捨てる。key が読めない body(非 object / 欠落)は「dangling 扱い」。 * 2. **dangling drop**(`drop==="dangling"`): null / 非 object / `dedupeKey` 欠落の body を落とす。 * `drop==="none"` なら null body もそのまま残す(dedupe だけ適用)。 * 3. **implicitSource strip**: `implicitSource` が指定されていれば、残った各 object body から * そのフィールドを除去(shallow copy)。 * * @param alignedBodies dedup 済み over と整列した batched handler の生結果(各要素 = 1 body)。 * @param spec dedupeKey / drop / implicitSource。 * @returns connection の `items`(wrap は呼び出し側 = runFanout が `{items, cursor:null}` で行う)。 */ export interface FanoutDedupDropSpec { dedupeKey: string; drop: "dangling" | "none"; implicitSource?: string; } export declare function fanoutDedupDrop(alignedBodies: Value[], spec: FanoutDedupDropSpec): Value[]; export declare function runBehavior(ir: ComponentGraphIR, handlers: Handlers, input?: Scope, entry?: string): Value; /** * runBehaviorAsync — runBehavior の非同期版(bc#23)。handler が Promise を返せる。 * * @internal **低レベル IR 実行プリミティブ(consumer authoring surface ではない — #128/A6)。** * {@link runBehavior} と同じく consumer 向け実行契約は {@link bindBehaviors}(`bindAsync`)である。 * 本関数は生成 literal/typed モジュールと test harness が叩く BC 内部の規範 async IR 実行 runtime。 * * plan の stage 実行を {@link runPlanAsync} に委譲することで、同一 stage 内の兄弟ノードを * `plan.concurrency` を上限とした bounded 並列で実行する(旧 graphddb の sibling relation * query thread-overlap の回復)。map ノード**内部**の要素反復は宣言どおり逐次 * (stage 間/stage 内の並列単位は body ノード — §7)。 * * 観測等価: 結果・Skip/Policy 伝播・Failure(code/message)は {@link runBehavior} と * 完全一致(runBehavior が規範実装。等価性は conformance vectors の両経路 PASS で担保)。 * 相違は投機的 dispatch のみ(runPlanAsync の決定的 commit プロトコル参照)。 * * 並行呼び出し契約: `concurrency > 1` の plan を持つ component では、同一 stage 内の * 兄弟ノードの handler が並行に呼ばれ得る。並行呼び出しを許容しない consumer は * `concurrency: 1` の plan を出荷する(concurrency は plan 構造の一部 — execution-plan.md §3)。 */ export declare function runBehaviorAsync(ir: ComponentGraphIR, handlers: AsyncHandlers, input?: Scope, entry?: string): Promise; //# sourceMappingURL=behavior.d.ts.map