@mawaru/sdk 0.13.0 → 0.14.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.
- package/_schemas/ai-manifest.test.ts +54 -59
- package/_schemas/ai-manifest.ts +38 -23
- package/_schemas/extract-component.test.ts +51 -2
- package/_schemas/extract-component.ts +4 -2
- package/_schemas/graph.test.ts +87 -66
- package/_schemas/graph.ts +14 -29
- package/_schemas/node.ts +4 -0
- package/_schemas/port-spec.test.ts +120 -129
- package/_schemas/port-spec.ts +70 -106
- package/dist/_schemas/ai-manifest.d.ts +12 -1
- package/dist/_schemas/ai-manifest.js +35 -20
- package/dist/_schemas/extract-component.js +4 -2
- package/dist/_schemas/graph.d.ts +5 -0
- package/dist/_schemas/graph.js +15 -26
- package/dist/_schemas/node.d.ts +1 -0
- package/dist/_schemas/node.js +4 -0
- package/dist/_schemas/port-spec.d.ts +2 -5
- package/dist/_schemas/port-spec.js +57 -89
- package/dist/typegen.js +6 -6
- package/docs/development.md +5 -1
- package/package.json +2 -3
- package/skills/create-loop/SKILL.md +1 -1
- package/templates/CLAUDE.md +1 -1
- package/templates/echo/main.ts +1 -1
- package/templates/mawaru-runner.yml +1 -1
package/_schemas/graph.ts
CHANGED
|
@@ -6,9 +6,7 @@ import {
|
|
|
6
6
|
portKeySchema,
|
|
7
7
|
} from "./node.js"
|
|
8
8
|
import {
|
|
9
|
-
AI_INSTRUCTION_PORT_KEY,
|
|
10
9
|
hasInboundConflict,
|
|
11
|
-
isAiEnvelopeSchema,
|
|
12
10
|
isSchemaSubset,
|
|
13
11
|
kindPortsViolation,
|
|
14
12
|
resolvePortSchemas,
|
|
@@ -203,6 +201,10 @@ export type HumanConfig = z.infer<typeof humanConfigSchema>
|
|
|
203
201
|
// (docs/tasks/wip/コンポーネント(ループの部品化と再利用).md)
|
|
204
202
|
export const componentConfigSchema = z.object({
|
|
205
203
|
component_loop_id: z.uuid(),
|
|
204
|
+
// 配置の設定値:子 loop の refs 定義(default/required)への上書き。
|
|
205
|
+
// 子 run 起動時の起動時指定として渡る(省略=上書きなし。検証は保存 API と
|
|
206
|
+
// 起動時の mergeRefs が行う。コンポーネントの設定値(refs)と1ノードのコンポーネント化.md)
|
|
207
|
+
refs: z.record(z.string(), z.unknown()).optional(),
|
|
206
208
|
})
|
|
207
209
|
export type ComponentConfig = z.infer<typeof componentConfigSchema>
|
|
208
210
|
|
|
@@ -316,19 +318,18 @@ export const saveGraphBodySchema = z
|
|
|
316
318
|
else outCount++
|
|
317
319
|
})
|
|
318
320
|
// end は「in は1つ以上」(複数ルートを別々の in で集約する)。
|
|
319
|
-
// ai
|
|
321
|
+
// ai も1つ以上(in は自由ポート:追加・削除・key 自由)。他 kind は1つ固定。
|
|
320
322
|
// key の妥当性は kindPortsViolation が別途見る
|
|
321
|
-
const expectIn = node.kind === "ai" ? 2 : 1
|
|
322
323
|
const inCountOk =
|
|
323
|
-
node.kind === "end" ? inCount >= 1 : inCount ===
|
|
324
|
+
node.kind === "end" || node.kind === "ai" ? inCount >= 1 : inCount === 1
|
|
324
325
|
if (!inCountOk) {
|
|
325
326
|
ctx.addIssue({
|
|
326
327
|
code: "custom",
|
|
327
328
|
path: ["nodes", i, "ports"],
|
|
328
329
|
message:
|
|
329
|
-
node.kind === "end"
|
|
330
|
+
node.kind === "end" || node.kind === "ai"
|
|
330
331
|
? "in ポートが1つ以上必要です"
|
|
331
|
-
:
|
|
332
|
+
: "in ポートはちょうど1つ必要です",
|
|
332
333
|
})
|
|
333
334
|
}
|
|
334
335
|
if (outCount < 1) {
|
|
@@ -371,7 +372,8 @@ export const saveGraphBodySchema = z
|
|
|
371
372
|
})
|
|
372
373
|
}
|
|
373
374
|
|
|
374
|
-
// kind 別のポート構成(program 以外は数・key・io 固定。ポートのkind別仕様.md
|
|
375
|
+
// kind 別のポート構成(program 以外は数・key・io 固定。ポートのkind別仕様.md)。
|
|
376
|
+
// ポートのスキーマ自体は kind によらず任意の JSON Schema(形の強制はしない)
|
|
375
377
|
const violation = kindPortsViolation(node.kind, node.ports)
|
|
376
378
|
if (violation) {
|
|
377
379
|
ctx.addIssue({
|
|
@@ -380,21 +382,6 @@ export const saveGraphBodySchema = z
|
|
|
380
382
|
message: violation,
|
|
381
383
|
})
|
|
382
384
|
}
|
|
383
|
-
|
|
384
|
-
// AI の出力宣言は封筒型 { data: T, description: string }(T は任意の JSON Schema)。
|
|
385
|
-
// 複数出口でも全出口に強制する(どの出口も human に接続できる不変条件)
|
|
386
|
-
if (node.kind === "ai") {
|
|
387
|
-
node.ports.forEach((port, j) => {
|
|
388
|
-
if (port.io === "out" && !isAiEnvelopeSchema(port.schema)) {
|
|
389
|
-
ctx.addIssue({
|
|
390
|
-
code: "custom",
|
|
391
|
-
path: ["nodes", i, "ports", j],
|
|
392
|
-
message:
|
|
393
|
-
"AI の出力は封筒型 { data, description } で宣言してください",
|
|
394
|
-
})
|
|
395
|
-
}
|
|
396
|
-
})
|
|
397
|
-
}
|
|
398
385
|
})
|
|
399
386
|
|
|
400
387
|
// 接続:両端がグラフ内の port を指し、from=out / to=in。
|
|
@@ -480,17 +467,15 @@ export const saveGraphBodySchema = z
|
|
|
480
467
|
})
|
|
481
468
|
}
|
|
482
469
|
|
|
483
|
-
// 差し戻し(reject)と instruction
|
|
484
|
-
//
|
|
485
|
-
//
|
|
486
|
-
// ({ prompt, files } を受ける program へも繋げる)
|
|
470
|
+
// 差し戻し(reject)と instruction の組み合わせに kind の縛りも型固定も無い:
|
|
471
|
+
// reject の型はビューの宣言、instruction は ai の in の1つ(接続鏡映)でしかない
|
|
472
|
+
// → docs/tasks/wip/ポート型固定の撤去とセッション再開のポート属性化.md
|
|
487
473
|
|
|
488
474
|
// 型検証は kind によらず常に 伝播型(out) ⊆ 伝播型(in)。
|
|
489
475
|
// 未宣言は unknown=判断できないので通す(作りかけのグラフを保存できなくしない)。
|
|
490
476
|
// 接続鏡映する口(program / ai の in)とコピー宣言のビューは接続元の型を採用する
|
|
491
477
|
// ので自明に通り、実質の検証対象は宣言で固定された口
|
|
492
|
-
// (component
|
|
493
|
-
// → docs/tasks/wip/Humanの入出力を承認ビューの宣言に一本化する.md
|
|
478
|
+
// (component の契約・宣言のあるビューの human.in)になる
|
|
494
479
|
const fromSchema = resolved.get(conn.from_port_id) ?? from.schema
|
|
495
480
|
const toSchema = resolved.get(conn.to_port_id) ?? to.schema
|
|
496
481
|
if (!isSchemaSubset(fromSchema, toSchema)) {
|
package/_schemas/node.ts
CHANGED
|
@@ -47,6 +47,10 @@ export const graphPortSchema = z
|
|
|
47
47
|
key: portKeySchema,
|
|
48
48
|
name: z.string().min(1),
|
|
49
49
|
schema: jsonSchemaSchema,
|
|
50
|
+
// in ポートの実行属性:true ならこの口から届いた実行は前回セッションを復元して
|
|
51
|
+
// 再開する(AI ノードで意味を持つ。key ではなくこの属性が再開を決める。
|
|
52
|
+
// ポート型固定の撤去とセッション再開のポート属性化.md)
|
|
53
|
+
resume: z.boolean().optional(),
|
|
50
54
|
side: portSideSchema.optional(),
|
|
51
55
|
offset: z.number().min(0).max(1).optional(),
|
|
52
56
|
})
|
|
@@ -3,12 +3,9 @@ import type { GraphPort, JsonSchema } from "./node.js"
|
|
|
3
3
|
import {
|
|
4
4
|
dataFormatOf,
|
|
5
5
|
dataSchemaOf,
|
|
6
|
-
envelopeDataSchemaOf,
|
|
7
|
-
envelopeSchemaOf,
|
|
8
6
|
fileSchema,
|
|
9
7
|
hasInboundConflict,
|
|
10
8
|
instructionSchema,
|
|
11
|
-
isAiEnvelopeSchema,
|
|
12
9
|
isFileSchema,
|
|
13
10
|
isSchemaSubset,
|
|
14
11
|
kindPortsViolation,
|
|
@@ -77,102 +74,19 @@ describe("データフォーマットカタログ(S)", () => {
|
|
|
77
74
|
})
|
|
78
75
|
})
|
|
79
76
|
|
|
80
|
-
describe("
|
|
81
|
-
it("
|
|
82
|
-
const text = envelopeSchemaOf({ type: "string" })
|
|
83
|
-
expect(text).toEqual({
|
|
84
|
-
type: "object",
|
|
85
|
-
properties: {
|
|
86
|
-
data: { type: "string" },
|
|
87
|
-
description: { type: "string" },
|
|
88
|
-
},
|
|
89
|
-
required: ["data", "description"],
|
|
90
|
-
})
|
|
91
|
-
// カタログ外の任意型も包める
|
|
92
|
-
const custom = {
|
|
93
|
-
type: "object",
|
|
94
|
-
properties: { title: { type: "string" }, count: { type: "number" } },
|
|
95
|
-
required: ["title"],
|
|
96
|
-
}
|
|
97
|
-
expect(envelopeSchemaOf(custom).properties).toMatchObject({ data: custom })
|
|
98
|
-
})
|
|
99
|
-
|
|
100
|
-
it("envelopeDataSchemaOf:封筒型なら data のサブスキーマ、そうでなければ null", () => {
|
|
101
|
-
const dataSchema = { type: "array", items: { type: "string" } }
|
|
102
|
-
expect(envelopeDataSchemaOf(envelopeSchemaOf(dataSchema))).toEqual(
|
|
103
|
-
dataSchema,
|
|
104
|
-
)
|
|
105
|
-
// data キーが無い / optional / object でない
|
|
106
|
-
expect(envelopeDataSchemaOf({ type: "object", properties: {} })).toBeNull()
|
|
77
|
+
describe("kindPortsViolation(kind 別ポート構成)", () => {
|
|
78
|
+
it("ai は in / out とも key 自由(構成の固定なし。口数は graph 側が見る)", () => {
|
|
107
79
|
expect(
|
|
108
|
-
|
|
109
|
-
type: "object",
|
|
110
|
-
properties: { data: { type: "string" } },
|
|
111
|
-
}),
|
|
80
|
+
kindPortsViolation("ai", [port("in", "in"), port("out", "main")]),
|
|
112
81
|
).toBeNull()
|
|
113
|
-
expect(envelopeDataSchemaOf({ type: "string" })).toBeNull()
|
|
114
|
-
expect(envelopeDataSchemaOf(UNDECLARED_SCHEMA)).toBeNull()
|
|
115
|
-
})
|
|
116
|
-
|
|
117
|
-
it("isAiEnvelopeSchema:data と description: string の両方が必須のときだけ true", () => {
|
|
118
|
-
expect(isAiEnvelopeSchema(envelopeSchemaOf({ type: "string" }))).toBe(true)
|
|
119
|
-
expect(isAiEnvelopeSchema(envelopeSchemaOf({ type: "object" }))).toBe(true)
|
|
120
|
-
// description が無い / optional / string でない
|
|
121
|
-
expect(
|
|
122
|
-
isAiEnvelopeSchema({
|
|
123
|
-
type: "object",
|
|
124
|
-
properties: { data: { type: "string" } },
|
|
125
|
-
required: ["data"],
|
|
126
|
-
}),
|
|
127
|
-
).toBe(false)
|
|
128
|
-
expect(
|
|
129
|
-
isAiEnvelopeSchema({
|
|
130
|
-
type: "object",
|
|
131
|
-
properties: {
|
|
132
|
-
data: { type: "string" },
|
|
133
|
-
description: { type: "string" },
|
|
134
|
-
},
|
|
135
|
-
required: ["data"],
|
|
136
|
-
}),
|
|
137
|
-
).toBe(false)
|
|
138
|
-
expect(
|
|
139
|
-
isAiEnvelopeSchema({
|
|
140
|
-
type: "object",
|
|
141
|
-
properties: {
|
|
142
|
-
data: { type: "string" },
|
|
143
|
-
description: { type: "number" },
|
|
144
|
-
},
|
|
145
|
-
required: ["data", "description"],
|
|
146
|
-
}),
|
|
147
|
-
).toBe(false)
|
|
148
|
-
expect(isAiEnvelopeSchema(UNDECLARED_SCHEMA)).toBe(false)
|
|
149
|
-
})
|
|
150
|
-
})
|
|
151
|
-
|
|
152
|
-
describe("kindPortsViolation(kind 別ポート構成)", () => {
|
|
153
|
-
it("ai は out の key 自由・複数可。in は in / instruction の2口固定(AI の複数出口)", () => {
|
|
154
|
-
const ins = [port("in", "in"), port("in", "instruction")]
|
|
155
|
-
expect(kindPortsViolation("ai", [...ins, port("out", "main")])).toBeNull()
|
|
156
|
-
expect(kindPortsViolation("ai", [...ins, port("out", "reply")])).toBeNull()
|
|
157
82
|
expect(
|
|
158
83
|
kindPortsViolation("ai", [
|
|
159
|
-
|
|
84
|
+
port("in", "task"),
|
|
85
|
+
port("in", "feedback"),
|
|
160
86
|
port("out", "spam"),
|
|
161
87
|
port("out", "sales"),
|
|
162
|
-
port("out", "support"),
|
|
163
88
|
]),
|
|
164
89
|
).toBeNull()
|
|
165
|
-
// instruction が無い・key が違うのは違反
|
|
166
|
-
expect(
|
|
167
|
-
kindPortsViolation("ai", [port("in", "in"), port("out", "main")]),
|
|
168
|
-
).toMatch(/instruction/)
|
|
169
|
-
expect(
|
|
170
|
-
kindPortsViolation("ai", [
|
|
171
|
-
port("in", "input"),
|
|
172
|
-
port("in", "instruction"),
|
|
173
|
-
port("out", "main"),
|
|
174
|
-
]),
|
|
175
|
-
).toMatch(/ai/)
|
|
176
90
|
})
|
|
177
91
|
|
|
178
92
|
it("human は in / approve / reject 固定", () => {
|
|
@@ -217,13 +131,18 @@ describe("kindPortsViolation(kind 別ポート構成)", () => {
|
|
|
217
131
|
})
|
|
218
132
|
|
|
219
133
|
describe("型伝播(導出ポートの解決)", () => {
|
|
220
|
-
|
|
134
|
+
// 伝播テスト用の任意の宣言型(中身の形に意味はない)
|
|
135
|
+
const declaredOut = {
|
|
136
|
+
type: "object",
|
|
137
|
+
properties: { text: { type: "string" } },
|
|
138
|
+
required: ["text"],
|
|
139
|
+
}
|
|
221
140
|
|
|
222
|
-
// human の in / approve はビューの宣言をそのまま転写する(上流の kind は見ない)。
|
|
141
|
+
// human の in / approve / reject はビューの宣言をそのまま転写する(上流の kind は見ない)。
|
|
223
142
|
// sdk はカタログを知らないので、呼び出し元が viewPortSchemasOf で解決して渡す
|
|
224
|
-
// → docs/tasks/wip
|
|
225
|
-
it("ai → human 直結(コピー宣言):in も approve も上流の out と同型、reject
|
|
226
|
-
const aiOut = port("out", "main",
|
|
143
|
+
// → docs/tasks/wip/ポート型固定の撤去とセッション再開のポート属性化.md
|
|
144
|
+
it("ai → human 直結(コピー宣言):in も approve も上流の out と同型、reject はビューの宣言型", () => {
|
|
145
|
+
const aiOut = port("out", "main", declaredOut)
|
|
227
146
|
const ai = {
|
|
228
147
|
id: uuid(),
|
|
229
148
|
kind: "ai",
|
|
@@ -240,11 +159,17 @@ describe("型伝播(導出ポートの解決)", () => {
|
|
|
240
159
|
const resolved = resolvePortSchemas(
|
|
241
160
|
[ai, human],
|
|
242
161
|
[{ from_port_id: aiOut.id, to_port_id: humanIn.id }],
|
|
243
|
-
{
|
|
162
|
+
{
|
|
163
|
+
viewPortSchemasOf: () => ({
|
|
164
|
+
in: "T",
|
|
165
|
+
approve: "T",
|
|
166
|
+
reject: instructionSchema(),
|
|
167
|
+
}),
|
|
168
|
+
},
|
|
244
169
|
)
|
|
245
170
|
// 上流が AI でも中身を取り出さない:AI の出力形式がそのまま下流へ流れる
|
|
246
|
-
expect(resolved.get(humanIn.id)).toEqual(
|
|
247
|
-
expect(resolved.get(approve.id)).toEqual(
|
|
171
|
+
expect(resolved.get(humanIn.id)).toEqual(declaredOut)
|
|
172
|
+
expect(resolved.get(approve.id)).toEqual(declaredOut)
|
|
248
173
|
expect(resolved.get(reject.id)).toEqual(instructionSchema())
|
|
249
174
|
})
|
|
250
175
|
|
|
@@ -266,6 +191,12 @@ describe("型伝播(導出ポートの解決)", () => {
|
|
|
266
191
|
properties: { prs: { type: "array", items: { type: "object" } } },
|
|
267
192
|
required: ["prs"],
|
|
268
193
|
}
|
|
194
|
+
// reject もビューの宣言(具体型のみ。コピー指示は意味を持たない)
|
|
195
|
+
const viewReject = {
|
|
196
|
+
type: "object",
|
|
197
|
+
properties: { reason: { type: "string" } },
|
|
198
|
+
required: ["reason"],
|
|
199
|
+
}
|
|
269
200
|
const build = (upstreamKind: string, upstreamOut: JsonSchema) => {
|
|
270
201
|
const out = port("out", "main", upstreamOut)
|
|
271
202
|
const upstream = {
|
|
@@ -275,65 +206,97 @@ describe("型伝播(導出ポートの解決)", () => {
|
|
|
275
206
|
}
|
|
276
207
|
const humanIn = port("in", "in")
|
|
277
208
|
const approve = port("out", "approve")
|
|
209
|
+
const reject = port("out", "reject")
|
|
278
210
|
const human = {
|
|
279
211
|
id: uuid(),
|
|
280
212
|
kind: "human",
|
|
281
|
-
ports: [humanIn, approve,
|
|
213
|
+
ports: [humanIn, approve, reject],
|
|
282
214
|
}
|
|
283
|
-
return { upstream, human, out, humanIn, approve }
|
|
215
|
+
return { upstream, human, out, humanIn, approve, reject }
|
|
284
216
|
}
|
|
285
217
|
|
|
286
218
|
it("宣言のあるビューは上流の kind に依らず同じ型になる", () => {
|
|
287
219
|
for (const kind of ["ai", "program"]) {
|
|
288
|
-
const { upstream, human, out, humanIn, approve } = build(
|
|
220
|
+
const { upstream, human, out, humanIn, approve, reject } = build(
|
|
221
|
+
kind,
|
|
222
|
+
declaredOut,
|
|
223
|
+
)
|
|
289
224
|
const resolved = resolvePortSchemas(
|
|
290
225
|
[upstream, human],
|
|
291
226
|
[{ from_port_id: out.id, to_port_id: humanIn.id }],
|
|
292
|
-
{
|
|
227
|
+
{
|
|
228
|
+
viewPortSchemasOf: () => ({
|
|
229
|
+
in: viewIn,
|
|
230
|
+
approve: viewOut,
|
|
231
|
+
reject: viewReject,
|
|
232
|
+
}),
|
|
233
|
+
},
|
|
293
234
|
)
|
|
294
235
|
expect(resolved.get(humanIn.id)).toEqual(viewIn)
|
|
295
236
|
expect(resolved.get(approve.id)).toEqual(viewOut)
|
|
237
|
+
expect(resolved.get(reject.id)).toEqual(viewReject)
|
|
296
238
|
}
|
|
297
239
|
})
|
|
298
240
|
|
|
299
241
|
it("上流の out は書き換えない(逆伝播しない)", () => {
|
|
300
|
-
const { upstream, human, out, humanIn } = build("ai",
|
|
242
|
+
const { upstream, human, out, humanIn } = build("ai", declaredOut)
|
|
301
243
|
const resolved = resolvePortSchemas(
|
|
302
244
|
[upstream, human],
|
|
303
245
|
[{ from_port_id: out.id, to_port_id: humanIn.id }],
|
|
304
|
-
{
|
|
246
|
+
{
|
|
247
|
+
viewPortSchemasOf: () => ({
|
|
248
|
+
in: viewIn,
|
|
249
|
+
approve: viewOut,
|
|
250
|
+
reject: viewReject,
|
|
251
|
+
}),
|
|
252
|
+
},
|
|
305
253
|
)
|
|
306
|
-
expect(resolved.get(out.id)).toEqual(
|
|
254
|
+
expect(resolved.get(out.id)).toEqual(declaredOut)
|
|
307
255
|
})
|
|
308
256
|
|
|
309
257
|
it("コピー宣言(標準ビュー)は上流の型をそのまま in と approve へ流す", () => {
|
|
310
|
-
const { upstream, human, out, humanIn, approve } = build(
|
|
258
|
+
const { upstream, human, out, humanIn, approve } = build(
|
|
259
|
+
"ai",
|
|
260
|
+
declaredOut,
|
|
261
|
+
)
|
|
311
262
|
const resolved = resolvePortSchemas(
|
|
312
263
|
[upstream, human],
|
|
313
264
|
[{ from_port_id: out.id, to_port_id: humanIn.id }],
|
|
314
|
-
{
|
|
265
|
+
{
|
|
266
|
+
viewPortSchemasOf: () => ({
|
|
267
|
+
in: "T",
|
|
268
|
+
approve: "T",
|
|
269
|
+
reject: viewReject,
|
|
270
|
+
}),
|
|
271
|
+
},
|
|
315
272
|
)
|
|
316
|
-
expect(resolved.get(humanIn.id)).toEqual(
|
|
317
|
-
expect(resolved.get(approve.id)).toEqual(
|
|
273
|
+
expect(resolved.get(humanIn.id)).toEqual(declaredOut)
|
|
274
|
+
expect(resolved.get(approve.id)).toEqual(declaredOut)
|
|
318
275
|
})
|
|
319
276
|
|
|
320
277
|
it("未接続でも宣言で固定される", () => {
|
|
321
278
|
const humanIn = port("in", "in")
|
|
322
279
|
const approve = port("out", "approve")
|
|
280
|
+
const reject = port("out", "reject")
|
|
323
281
|
const human = {
|
|
324
282
|
id: uuid(),
|
|
325
283
|
kind: "human",
|
|
326
|
-
ports: [humanIn, approve,
|
|
284
|
+
ports: [humanIn, approve, reject],
|
|
327
285
|
}
|
|
328
286
|
const resolved = resolvePortSchemas([human], [], {
|
|
329
|
-
viewPortSchemasOf: () => ({
|
|
287
|
+
viewPortSchemasOf: () => ({
|
|
288
|
+
in: viewIn,
|
|
289
|
+
approve: viewOut,
|
|
290
|
+
reject: viewReject,
|
|
291
|
+
}),
|
|
330
292
|
})
|
|
331
293
|
expect(resolved.get(humanIn.id)).toEqual(viewIn)
|
|
332
294
|
expect(resolved.get(approve.id)).toEqual(viewOut)
|
|
295
|
+
expect(resolved.get(reject.id)).toEqual(viewReject)
|
|
333
296
|
})
|
|
334
297
|
})
|
|
335
298
|
|
|
336
|
-
it("ai → wait → human
|
|
299
|
+
it("ai → wait → human:素通しで上流の宣言型が伝播し、reject はビュー宣言が無ければ未宣言", () => {
|
|
337
300
|
// ai の in は宣言型(nodes/ai/<dir>/config.json の inputs.in 由来。実行repoの構造見直し)
|
|
338
301
|
const aiInSchema = {
|
|
339
302
|
type: "object",
|
|
@@ -341,7 +304,7 @@ describe("型伝播(導出ポートの解決)", () => {
|
|
|
341
304
|
required: ["inquiry"],
|
|
342
305
|
}
|
|
343
306
|
const aiIn = port("in", "in", aiInSchema)
|
|
344
|
-
const aiOut = port("out", "main",
|
|
307
|
+
const aiOut = port("out", "main", declaredOut)
|
|
345
308
|
const waitIn = port("in", "in")
|
|
346
309
|
const waitOut = port("out", "main")
|
|
347
310
|
const humanIn = port("in", "in")
|
|
@@ -363,16 +326,41 @@ describe("型伝播(導出ポートの解決)", () => {
|
|
|
363
326
|
{ from_port_id: waitOut.id, to_port_id: humanIn.id },
|
|
364
327
|
]
|
|
365
328
|
const resolved = resolvePortSchemas([ai, wait, human], conns)
|
|
366
|
-
expect(resolved.get(waitIn.id)).toEqual(
|
|
367
|
-
expect(resolved.get(waitOut.id)).toEqual(
|
|
368
|
-
expect(resolved.get(humanIn.id)).toEqual(
|
|
369
|
-
//
|
|
370
|
-
expect(resolved.get(approve.id)).toEqual(
|
|
371
|
-
|
|
329
|
+
expect(resolved.get(waitIn.id)).toEqual(declaredOut)
|
|
330
|
+
expect(resolved.get(waitOut.id)).toEqual(declaredOut)
|
|
331
|
+
expect(resolved.get(humanIn.id)).toEqual(declaredOut)
|
|
332
|
+
// approve は in と同型(上流の kind による中身の取り出しはしない)
|
|
333
|
+
expect(resolved.get(approve.id)).toEqual(declaredOut)
|
|
334
|
+
// reject はビューの宣言でのみ決まる(sdk 単体=カタログ無しでは未宣言。型固定は廃止)
|
|
335
|
+
expect(resolved.get(reject.id)).toEqual(UNDECLARED_SCHEMA)
|
|
372
336
|
// ai の in は宣言型がそのまま残る(const 扱いは廃止)
|
|
373
337
|
expect(resolved.get(aiIn.id)).toEqual(aiInSchema)
|
|
374
338
|
})
|
|
375
339
|
|
|
340
|
+
it("ai の in はどの口も接続鏡映される(instruction の特別扱い=型固定は廃止)", () => {
|
|
341
|
+
const progOut = port("out", "main", declaredOut)
|
|
342
|
+
const program = {
|
|
343
|
+
id: uuid(),
|
|
344
|
+
kind: "program",
|
|
345
|
+
ports: [port("in", "in"), progOut],
|
|
346
|
+
}
|
|
347
|
+
// 宣言はデフォルトの指示型。接続すると接続元の型に同期される(他の in と同じ)
|
|
348
|
+
const instruction = port("in", "instruction", instructionSchema())
|
|
349
|
+
const ai = {
|
|
350
|
+
id: uuid(),
|
|
351
|
+
kind: "ai",
|
|
352
|
+
ports: [port("in", "in"), instruction, port("out", "main")],
|
|
353
|
+
}
|
|
354
|
+
const resolved = resolvePortSchemas(
|
|
355
|
+
[program, ai],
|
|
356
|
+
[{ from_port_id: progOut.id, to_port_id: instruction.id }],
|
|
357
|
+
)
|
|
358
|
+
expect(resolved.get(instruction.id)).toEqual(declaredOut)
|
|
359
|
+
// 未接続なら宣言(デフォルト型)を維持する
|
|
360
|
+
const kept = resolvePortSchemas([ai], [])
|
|
361
|
+
expect(kept.get(instruction.id)).toEqual(instructionSchema())
|
|
362
|
+
})
|
|
363
|
+
|
|
376
364
|
it("未接続の導出ポート・純パラメトリック閉路は any", () => {
|
|
377
365
|
const in1 = port("in", "in")
|
|
378
366
|
const out1 = port("out", "main")
|
|
@@ -398,7 +386,7 @@ describe("型伝播(導出ポートの解決)", () => {
|
|
|
398
386
|
properties: { inquiry: { type: "string" } },
|
|
399
387
|
required: ["inquiry"],
|
|
400
388
|
}
|
|
401
|
-
const aiOut = port("out", "main",
|
|
389
|
+
const aiOut = port("out", "main", declaredOut)
|
|
402
390
|
const ai = {
|
|
403
391
|
id: uuid(),
|
|
404
392
|
kind: "ai",
|
|
@@ -414,7 +402,7 @@ describe("型伝播(導出ポートの解決)", () => {
|
|
|
414
402
|
[ai, program],
|
|
415
403
|
[{ from_port_id: aiOut.id, to_port_id: progIn.id }],
|
|
416
404
|
)
|
|
417
|
-
expect(resolved.get(progIn.id)).toEqual(
|
|
405
|
+
expect(resolved.get(progIn.id)).toEqual(declaredOut)
|
|
418
406
|
})
|
|
419
407
|
|
|
420
408
|
it("ai の in も接続鏡映(sticky):具体型の接続元だけ上書きする", () => {
|
|
@@ -422,7 +410,7 @@ describe("型伝播(導出ポートの解決)", () => {
|
|
|
422
410
|
type: "object",
|
|
423
411
|
properties: { topic: { type: "string" } },
|
|
424
412
|
}
|
|
425
|
-
const progOut = port("out", "main",
|
|
413
|
+
const progOut = port("out", "main", declaredOut)
|
|
426
414
|
const program = {
|
|
427
415
|
id: uuid(),
|
|
428
416
|
kind: "program",
|
|
@@ -439,7 +427,7 @@ describe("型伝播(導出ポートの解決)", () => {
|
|
|
439
427
|
[program, ai],
|
|
440
428
|
[{ from_port_id: progOut.id, to_port_id: aiIn.id }],
|
|
441
429
|
)
|
|
442
|
-
expect(resolved.get(aiIn.id)).toEqual(
|
|
430
|
+
expect(resolved.get(aiIn.id)).toEqual(declaredOut)
|
|
443
431
|
// 接続元が any(源泉の無い wait)→ 宣言を維持
|
|
444
432
|
const waitOut = port("out", "main")
|
|
445
433
|
const wait = {
|
|
@@ -491,7 +479,7 @@ describe("型伝播(導出ポートの解決)", () => {
|
|
|
491
479
|
})
|
|
492
480
|
|
|
493
481
|
it("program の in:wait 越しでも具体型が鏡映される(固定点反復)", () => {
|
|
494
|
-
const aiOut = port("out", "main",
|
|
482
|
+
const aiOut = port("out", "main", declaredOut)
|
|
495
483
|
const ai = {
|
|
496
484
|
id: uuid(),
|
|
497
485
|
kind: "ai",
|
|
@@ -513,7 +501,7 @@ describe("型伝播(導出ポートの解決)", () => {
|
|
|
513
501
|
{ from_port_id: waitOut.id, to_port_id: progIn.id },
|
|
514
502
|
],
|
|
515
503
|
)
|
|
516
|
-
expect(resolved.get(progIn.id)).toEqual(
|
|
504
|
+
expect(resolved.get(progIn.id)).toEqual(declaredOut)
|
|
517
505
|
})
|
|
518
506
|
|
|
519
507
|
// start の run 入力契約は接続先から導出する
|
|
@@ -624,7 +612,7 @@ describe("型伝播(導出ポートの解決)", () => {
|
|
|
624
612
|
})
|
|
625
613
|
|
|
626
614
|
it("propagateDerivedPorts:schema を書き戻し、変化のないノードは同じ参照を返す", () => {
|
|
627
|
-
const progOut = port("out", "main",
|
|
615
|
+
const progOut = port("out", "main", declaredOut)
|
|
628
616
|
const program = {
|
|
629
617
|
id: uuid(),
|
|
630
618
|
kind: "program",
|
|
@@ -641,7 +629,10 @@ describe("型伝播(導出ポートの解決)", () => {
|
|
|
641
629
|
[{ from_port_id: progOut.id, to_port_id: waitIn.id }],
|
|
642
630
|
)
|
|
643
631
|
expect(next[0]).toBe(program) // 宣言のみ=無変更
|
|
644
|
-
expect(next[1]?.ports.map((p) => p.schema)).toEqual([
|
|
632
|
+
expect(next[1]?.ports.map((p) => p.schema)).toEqual([
|
|
633
|
+
declaredOut,
|
|
634
|
+
declaredOut,
|
|
635
|
+
])
|
|
645
636
|
// 接続を消すと any に戻る
|
|
646
637
|
const cleared = propagateDerivedPorts(next, [])
|
|
647
638
|
expect(cleared[1]?.ports.map((p) => p.schema)).toEqual([
|
|
@@ -652,7 +643,7 @@ describe("型伝播(導出ポートの解決)", () => {
|
|
|
652
643
|
|
|
653
644
|
it("propagateDerivedPorts:side / offset(表示位置)を落とさない", () => {
|
|
654
645
|
const progOut = {
|
|
655
|
-
...port("out", "main",
|
|
646
|
+
...port("out", "main", declaredOut),
|
|
656
647
|
side: "top" as const,
|
|
657
648
|
offset: 0.3,
|
|
658
649
|
}
|