@mawaru/sdk 0.40.0 → 0.41.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/_schemas/graph.ts +0 -5
  2. package/_schemas/human-manifest.test.ts +39 -101
  3. package/_schemas/human-manifest.ts +25 -152
  4. package/_schemas/port-spec.test.ts +33 -92
  5. package/_schemas/port-spec.ts +35 -44
  6. package/dist/_cli/login.js +15 -1
  7. package/dist/_cli/previewApi.d.ts +1 -1
  8. package/dist/_cli/previewCommand.d.ts +0 -8
  9. package/dist/_cli/previewCommand.js +37 -95
  10. package/dist/_cli/previewFiles.d.ts +1 -2
  11. package/dist/_cli/previewFiles.js +18 -42
  12. package/dist/_schemas/graph.d.ts +0 -15
  13. package/dist/_schemas/graph.js +0 -5
  14. package/dist/_schemas/human-manifest.d.ts +6 -24
  15. package/dist/_schemas/human-manifest.js +24 -116
  16. package/dist/_schemas/port-spec.d.ts +1 -4
  17. package/dist/_schemas/port-spec.js +25 -34
  18. package/dist/cli.js +1 -2
  19. package/dist/typegen.d.ts +2 -2
  20. package/dist/typegen.js +21 -21
  21. package/dist/validate.js +9 -7
  22. package/dist/views/_humanProps.d.ts +10 -0
  23. package/dist/views/_humanProps.js +11 -0
  24. package/dist/views/{index.d.ts → components.d.ts} +0 -1
  25. package/dist/views/components.js +12 -0
  26. package/dist/views/host.d.ts +8 -3
  27. package/dist/views/host.js +5 -5
  28. package/dist/views/human.d.ts +49 -68
  29. package/dist/views/human.js +5 -6
  30. package/dist/views/protocol.d.ts +13 -3
  31. package/dist/views/protocol.js +2 -2
  32. package/dist/views/runtime.d.ts +2 -3
  33. package/dist/views/runtime.js +24 -24
  34. package/dist/views/sample.d.ts +22 -0
  35. package/dist/views/sample.js +38 -0
  36. package/docs/development.md +30 -31
  37. package/package.json +5 -1
  38. package/skills/create-loop/SKILL.md +3 -2
  39. package/templates/CLAUDE.md +2 -0
  40. package/views/_humanProps.ts +27 -0
  41. package/views/components.ts +16 -0
  42. package/views/host.ts +9 -5
  43. package/views/human.ts +71 -79
  44. package/views/protocol.ts +29 -6
  45. package/views/runtime.tsx +36 -45
  46. package/views/sample.tsx +94 -0
  47. package/dist/_cli/openBrowser.d.ts +0 -1
  48. package/dist/_cli/openBrowser.js +0 -15
  49. package/dist/views/_approval/ApprovalBar.d.ts +0 -20
  50. package/dist/views/_approval/ApprovalBar.js +0 -74
  51. package/dist/views/_approvalActions.d.ts +0 -18
  52. package/dist/views/_approvalActions.js +0 -10
  53. package/dist/views/index.js +0 -12
  54. package/views/_approval/ApprovalBar.tsx +0 -239
  55. package/views/_approvalActions.tsx +0 -31
  56. package/views/index.ts +0 -16
package/_schemas/graph.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  import { z } from "zod"
2
- import { humanDeclarationSchema } from "./human-manifest.js"
3
2
  import {
4
3
  graphPortSchema,
5
4
  jsonSchemaSchema,
@@ -170,10 +169,6 @@ export const humanConfigSchema = z.object({
170
169
  .optional(),
171
170
  // 読むブランチ(省略時はデフォルトブランチ)。publish でその先頭の commit に固定する
172
171
  ref: z.string().min(1).optional(),
173
- // 宣言の写し(in / approve / reject の型)。伝播はこれを読む。human_dir を選んだときに
174
- // 一覧 API(GET /tenants/:id/github/humans)の宣言を入れる。省略すると保存時に mawaru が repo の
175
- // config.json から読む(下書きに同じ human_dir の同じノードがあればその写しを保つ)
176
- declaration: humanDeclarationSchema.optional(),
177
172
  assignment: z
178
173
  .object({
179
174
  strategy: z.enum(["fixed", "round_robin"]).default("fixed"),
@@ -1,10 +1,5 @@
1
1
  import { describe, expect, it } from "vitest"
2
- import {
3
- humanDeclarationOfManifest,
4
- humanDeclarationSchema,
5
- humanManifestSchema,
6
- STANDARD_HUMAN_DECLARATION,
7
- } from "./human-manifest.js"
2
+ import { humanManifestSchema } from "./human-manifest.js"
8
3
  import { fileSchema, instructionSchema } from "./port-spec.js"
9
4
 
10
5
  const pr = {
@@ -12,118 +7,61 @@ const pr = {
12
7
  properties: {
13
8
  title: { type: "string", description: "PR のタイトル" },
14
9
  gifs: { type: "array", items: fileSchema() },
15
- count: { type: "integer" },
16
10
  },
17
11
  required: ["title"],
18
12
  }
19
13
 
20
- const issuesOf = (value: unknown) => {
21
- const parsed = humanDeclarationSchema.safeParse(value)
22
- return parsed.success
23
- ? []
24
- : parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`)
14
+ const messagesOf = (value: unknown) => {
15
+ const parsed = humanManifestSchema.safeParse(value)
16
+ return parsed.success ? [] : parsed.error.issues.map((i) => i.message)
25
17
  }
26
18
 
27
- describe("humanDeclarationSchema(宣言の検査)", () => {
28
- it("標準ビューの宣言と具体型の宣言を通す", () => {
29
- expect(issuesOf(STANDARD_HUMAN_DECLARATION)).toEqual([])
30
- expect(
31
- issuesOf({ in: pr, approve: pr, reject: instructionSchema() }),
32
- ).toEqual([])
33
- // 未宣言({})も宣言の1つ(スキーマ未定義)
34
- expect(issuesOf({ in: {}, approve: "T", reject: {} })).toEqual([])
35
- })
36
-
37
- it("type の配列(nullable)は場所つきで弾く", () => {
38
- expect(
39
- issuesOf({
40
- in: {
41
- type: "object",
42
- properties: { summary: { type: ["string", "null"] } },
43
- },
44
- approve: "T",
19
+ describe("humanManifestSchema(nodes/human/<dir>/config.json)", () => {
20
+ it("in・approve・reject を書いた宣言を通す(追加の出口も書ける)", () => {
21
+ const config = {
22
+ inputs: { in: { schema: pr } },
23
+ outputs: {
24
+ approve: { type: "array", items: pr },
45
25
  reject: instructionSchema(),
46
- }),
47
- ).toEqual([
48
- "in.properties.summary.type: 配列(nullable)は使えません。任意の値は required から外してください",
49
- ])
26
+ notion: pr,
27
+ },
28
+ }
29
+ // refs はエディタが転写する生成物なので、無ければ空になる
30
+ expect(humanManifestSchema.parse(config)).toEqual({ ...config, refs: {} })
50
31
  })
51
32
 
52
- it("知らない type・形の崩れた properties / required / items を弾く", () => {
53
- expect(
54
- issuesOf({
55
- in: { type: "strin" },
56
- approve: { type: "object", properties: [] },
57
- reject: {
58
- type: "object",
59
- required: [1],
60
- properties: { list: { type: "array", items: "x" } },
61
- },
62
- }),
63
- ).toEqual([
64
- "in.type: type は object / array / string / number / integer / boolean のどれかにしてください(strin)",
65
- "approve.properties: properties はオブジェクトで書いてください",
66
- "reject.properties.list.items: items はスキーマ(オブジェクト)で書いてください",
67
- "reject.required: required は文字列の配列で書いてください",
33
+ it("in・approve・reject のどれかが無ければ弾く(省略したときの決まりは無い)", () => {
34
+ const full = {
35
+ inputs: { in: { schema: pr } },
36
+ outputs: { approve: pr, reject: instructionSchema() },
37
+ }
38
+ const violation =
39
+ "human のポート構成は in(in) と out(approve / reject) が必須です(out は追加できます)"
40
+ expect(messagesOf({ ...full, inputs: {} })).toEqual([violation])
41
+ expect(messagesOf({ ...full, outputs: { approve: pr } })).toEqual([
42
+ violation,
68
43
  ])
69
- })
70
-
71
- it("type の無い構造(properties / items だけ)を弾く", () => {
72
44
  expect(
73
- issuesOf({
74
- in: { properties: { a: { type: "string" } } },
75
- approve: "T",
76
- reject: instructionSchema(),
77
- }),
78
- ).toEqual([
79
- "in.type: properties / items を書くときは type も書いてください",
80
- ])
45
+ messagesOf({ ...full, outputs: { reject: instructionSchema() } }),
46
+ ).toEqual([violation])
47
+ expect(humanManifestSchema.safeParse({}).success).toBe(false)
81
48
  })
82
49
 
83
- it('reject は上流のコピー("T")にできない', () => {
84
- expect(issuesOf({ in: "T", approve: "T", reject: "T" })).not.toEqual([])
85
- })
86
- })
87
-
88
- describe("humanManifestSchema(nodes/human/<dir>/config.json)", () => {
89
- it("省略した口は上流のコピー(in)・in と同型(approve)・標準の差し戻し(reject)になる", () => {
90
- const config = humanManifestSchema.parse({})
91
- expect(humanDeclarationOfManifest(config)).toEqual(
92
- STANDARD_HUMAN_DECLARATION,
93
- )
94
- })
95
-
96
- it("書いた口の型をそのまま宣言にする", () => {
97
- const config = humanManifestSchema.parse({
98
- inputs: { in: { schema: pr } },
99
- outputs: { approve: { type: "array", items: pr }, reject: pr },
100
- })
101
- expect(humanDeclarationOfManifest(config)).toEqual({
102
- in: pr,
103
- approve: { type: "array", items: pr },
104
- reject: pr,
105
- })
106
- })
107
-
108
- it("in / approve / reject 以外の口・知らないキーは弾く", () => {
109
- expect(
110
- humanManifestSchema.safeParse({ inputs: { instruction: {} } }).success,
111
- ).toBe(false)
50
+ it("in 以外の入口は弾く", () => {
112
51
  expect(
113
- humanManifestSchema.safeParse({ outputs: { notion: {} } }).success,
52
+ humanManifestSchema.safeParse({
53
+ inputs: { in: { schema: pr }, instruction: { schema: pr } },
54
+ outputs: { approve: pr, reject: instructionSchema() },
55
+ }).success,
114
56
  ).toBe(false)
115
- expect(humanManifestSchema.safeParse({ name: "x" }).success).toBe(false)
116
57
  })
117
58
 
118
- it("中の型も宣言の検査を通す", () => {
119
- const parsed = humanManifestSchema.safeParse({
120
- inputs: { in: { schema: { type: ["string", "null"] } } },
121
- })
122
- expect(parsed.success).toBe(false)
59
+ it('上流のコピー("T")は書けない(型は具体的に書く)', () => {
123
60
  expect(
124
- parsed.error?.issues.map((i) => `${i.path.join(".")}: ${i.message}`),
125
- ).toEqual([
126
- "inputs.in.schema.type: 配列(nullable)は使えません。任意の値は required から外してください",
127
- ])
61
+ humanManifestSchema.safeParse({
62
+ inputs: { in: { schema: "T" } },
63
+ outputs: { approve: "T", reject: instructionSchema() },
64
+ }).success,
65
+ ).toBe(false)
128
66
  })
129
67
  })
@@ -1,158 +1,31 @@
1
1
  import { z } from "zod"
2
- import { type JsonSchema, jsonSchemaSchema } from "./node.js"
3
- import {
4
- COPY_UPSTREAM_SCHEMA,
5
- type DeclaredPortSchema,
6
- instructionSchema,
7
- } from "./port-spec.js"
8
-
9
- // ---- human ノードの宣言(in / approve / reject の型) ----
10
- // 正は実装を持つ側(顧客 repo の nodes/human/<dir>/config.json。実装を指さないノードは標準の宣言)。
11
- // human ノードは実装を選んだ時点の写し(node_humans.declaration)を持ち、伝播はそれを読む
12
- // (docs/tasks/wip/承認ビューの顧客repo配置.md の「宣言(ポート型)」)。
13
- // "T"(COPY_UPSTREAM_SCHEMA)は「上流の out をそのまま受ける」(in)/「in と同型」(approve)。
14
- // reject は具体型だけ(上流のコピーは意味を持たない)
15
-
16
- export type HumanDeclaration = {
17
- in: DeclaredPortSchema
18
- approve: DeclaredPortSchema
19
- reject: JsonSchema
20
- }
21
-
22
- // 宣言に書ける type。file は x-mawaru-type マーカー付きの object
23
- const DECLARED_TYPES = [
24
- "object",
25
- "array",
26
- "string",
27
- "number",
28
- "integer",
29
- "boolean",
30
- ] as const
31
-
32
- const isPlainObject = (value: unknown): value is Record<string, unknown> =>
33
- typeof value === "object" && value !== null && !Array.isArray(value)
34
-
35
- // JSON Schema の形の崩れを場所つきで返す。接続の比較(schemaFit)が扱える形だけを通す:
36
- // 顧客 repo の宣言は mawaru のレビューを通らないので、壊れた型がノードに写る前に止める
37
- const schemaIssues = (
38
- schema: Record<string, unknown>,
39
- path: (string | number)[],
40
- ): { path: (string | number)[]; message: string }[] => {
41
- const issues: { path: (string | number)[]; message: string }[] = []
42
- const { type, properties, required, items } = schema
43
- if (Array.isArray(type)) {
44
- issues.push({
45
- path: [...path, "type"],
46
- message:
47
- "配列(nullable)は使えません。任意の値は required から外してください",
48
- })
49
- } else if (
50
- type !== undefined &&
51
- !DECLARED_TYPES.includes(type as (typeof DECLARED_TYPES)[number])
52
- ) {
53
- issues.push({
54
- path: [...path, "type"],
55
- message: `type は ${DECLARED_TYPES.join(" / ")} のどれかにしてください(${String(type)})`,
56
- })
57
- } else if (
58
- type === undefined &&
59
- (properties !== undefined || items !== undefined)
60
- ) {
61
- issues.push({
62
- path: [...path, "type"],
63
- message: "properties / items を書くときは type も書いてください",
64
- })
65
- }
66
- if (properties !== undefined) {
67
- if (!isPlainObject(properties)) {
68
- issues.push({
69
- path: [...path, "properties"],
70
- message: "properties はオブジェクトで書いてください",
71
- })
72
- } else {
73
- for (const [key, child] of Object.entries(properties)) {
74
- if (!isPlainObject(child)) {
75
- issues.push({
76
- path: [...path, "properties", key],
77
- message: "スキーマはオブジェクトで書いてください",
78
- })
79
- continue
80
- }
81
- issues.push(...schemaIssues(child, [...path, "properties", key]))
82
- }
83
- }
84
- }
85
- if (
86
- required !== undefined &&
87
- !(Array.isArray(required) && required.every((r) => typeof r === "string"))
88
- ) {
89
- issues.push({
90
- path: [...path, "required"],
91
- message: "required は文字列の配列で書いてください",
92
- })
93
- }
94
- if (items !== undefined) {
95
- if (!isPlainObject(items)) {
96
- issues.push({
97
- path: [...path, "items"],
98
- message: "items はスキーマ(オブジェクト)で書いてください",
99
- })
100
- } else {
101
- issues.push(...schemaIssues(items, [...path, "items"]))
102
- }
103
- }
104
- return issues
105
- }
106
-
107
- // 宣言に書く1つの型(検査つき)
108
- const checkedSchema = jsonSchemaSchema.superRefine((schema, ctx) => {
109
- for (const issue of schemaIssues(schema, []))
110
- ctx.addIssue({ code: "custom", ...issue })
111
- })
112
-
113
- const declaredPortSchema = z.union([
114
- z.literal(COPY_UPSTREAM_SCHEMA),
115
- checkedSchema,
116
- ])
117
-
118
- export const humanDeclarationSchema = z.object({
119
- in: declaredPortSchema,
120
- approve: declaredPortSchema,
121
- reject: checkedSchema,
122
- }) satisfies z.ZodType<HumanDeclaration>
123
-
124
- // 標準ビュー(common/standard)の宣言:上流の型をそのまま受けて出し、差し戻しは標準の指示フォーム
125
- export const STANDARD_HUMAN_DECLARATION: HumanDeclaration = {
126
- in: COPY_UPSTREAM_SCHEMA,
127
- approve: COPY_UPSTREAM_SCHEMA,
128
- reject: instructionSchema(),
129
- }
130
-
131
- // ---- 顧客 repo の nodes/human/<dir>/config.json ----
132
- // 形は AI の config.json に揃える(inputs の値は { schema }、outputs の値は素の JSON Schema)。
133
- // 口は in / approve / reject 固定。省略した口は STANDARD_HUMAN_DECLARATION と同じ扱い
2
+ import { jsonSchemaSchema, portKeySchema } from "./node.js"
3
+ import { kindPortsViolation } from "./port-spec.js"
4
+
5
+ // 顧客 repo の nodes/human/<dir>/config.json(human ノードの実装の入出力の宣言)。
6
+ // program / AI の config.json と同じく、エディタがディレクトリを選んだときに inputs / outputs から
7
+ // ports を起こし、ポートの編集は保存時にここへ書き戻される(実行時の正は ports テーブルのまま)。
8
+ // 形は AI に揃える(inputs の値は { schema }、outputs の値は素の JSON Schema)。
9
+ // 口の構成(in 1つ・approve / reject は必須・追加の出口は自由)は kind のポート構成の規則をそのまま使う
134
10
  export const humanManifestSchema = z
135
11
  .object({
136
- inputs: z
137
- .object({ in: z.object({ schema: checkedSchema }).strict().optional() })
138
- .strict()
139
- .optional(),
140
- outputs: z
141
- .object({
142
- approve: checkedSchema.optional(),
143
- reject: checkedSchema.optional(),
144
- })
145
- .strict()
146
- .optional(),
12
+ inputs: z.record(z.string(), z.object({ schema: jsonSchemaSchema })),
13
+ outputs: z.record(z.string(), jsonSchemaSchema),
14
+ // refs=ループ変数の定義の転写(program と同じ。エディタのグラフ保存が一方向に書く生成物)
15
+ refs: z.record(portKeySchema, jsonSchemaSchema).default({}),
16
+ })
17
+ .superRefine((manifest, ctx) => {
18
+ const violation = kindPortsViolation("human", [
19
+ ...Object.keys(manifest.inputs).map((key) => ({
20
+ io: "in" as const,
21
+ key,
22
+ })),
23
+ ...Object.keys(manifest.outputs).map((key) => ({
24
+ io: "out" as const,
25
+ key,
26
+ })),
27
+ ])
28
+ if (violation) ctx.addIssue({ code: "custom", message: violation })
147
29
  })
148
- .strict()
149
30
 
150
31
  export type HumanManifest = z.infer<typeof humanManifestSchema>
151
-
152
- export const humanDeclarationOfManifest = (
153
- config: HumanManifest,
154
- ): HumanDeclaration => ({
155
- in: config.inputs?.in?.schema ?? COPY_UPSTREAM_SCHEMA,
156
- approve: config.outputs?.approve ?? COPY_UPSTREAM_SCHEMA,
157
- reject: config.outputs?.reject ?? instructionSchema(),
158
- })
@@ -1,5 +1,4 @@
1
1
  import { describe, expect, it } from "vitest"
2
- import type { HumanDeclaration } from "./human-manifest.js"
3
2
  import type { GraphPort, JsonSchema } from "./node.js"
4
3
  import {
5
4
  arrayOf,
@@ -154,20 +153,17 @@ describe("kindPortsViolation(kind 別ポート構成)", () => {
154
153
  })
155
154
  })
156
155
 
157
- // human ノードに実装の宣言の写し(node_humans.declaration)を持たせる
158
- const withViewDeclaration = <N extends { kind: string }>(
159
- nodes: N[],
160
- declaration: HumanDeclaration,
161
- ) =>
156
+ // 実装(nodes/human/<dir>/)を指す human にする
157
+ const withHumanDir = <N extends { kind: string }>(nodes: N[]) =>
162
158
  nodes.map((node) =>
163
- node.kind === "human" ? { ...node, human: { declaration } } : node,
159
+ node.kind === "human" ? { ...node, human: { human_dir: "review" } } : node,
164
160
  )
165
161
 
166
162
  describe("型伝播(導出ポートの解決)", () => {
167
163
  it.each([
168
164
  "notion_task",
169
165
  "main",
170
- ])("Humanの追加出口 %s の宣言型は承認ビューの型で上書きしない", (key) => {
166
+ ])("Humanの追加出口 %s の宣言型は伝播で上書きしない", (key) => {
171
167
  const extra = port("out", key, {
172
168
  type: "object",
173
169
  properties: { title: { type: "string" } },
@@ -183,15 +179,10 @@ describe("型伝播(導出ポートの解決)", () => {
183
179
  extra,
184
180
  ],
185
181
  }
186
- const resolved = resolvePortSchemas(
187
- withViewDeclaration([human], {
188
- in: { type: "string" },
189
- approve: { type: "string" },
190
- reject: { type: "string" },
191
- }),
192
- [],
193
- )
194
- expect(resolved.get(extra.id)).toEqual(extra.schema)
182
+ for (const nodes of [[human], withHumanDir([human])]) {
183
+ const resolved = resolvePortSchemas(nodes, [])
184
+ expect(resolved.get(extra.id)).toEqual(extra.schema)
185
+ }
195
186
  })
196
187
 
197
188
  // 伝播テスト用の任意の宣言型(中身の形に意味はない)
@@ -201,10 +192,9 @@ describe("型伝播(導出ポートの解決)", () => {
201
192
  required: ["text"],
202
193
  }
203
194
 
204
- // human の in / approve / reject はビューの宣言をそのまま転写する(上流の kind は見ない)。
205
- // 宣言はノードの写し(declaration)を読む(sdk はカタログを知らない)
206
- // → docs/tasks/done/ポート型固定の撤去とセッション再開のポート属性化.md
207
- it("ai → human 直結(コピー宣言):in も approve も上流の out と同型、reject はビューの宣言型", () => {
195
+ // 実装を指さない human(標準の承認画面)は wait と同じ素通し。上流の kind は見ない
196
+ // → docs/tasks/wip/承認ビューの顧客repo配置.md の「ポート型」
197
+ it("実装を指さない human:in も approve も上流の out と同型、reject は標準の差し戻し", () => {
208
198
  const aiOut = port("out", "main", declaredOut)
209
199
  const ai = {
210
200
  id: uuid(),
@@ -220,11 +210,7 @@ describe("型伝播(導出ポートの解決)", () => {
220
210
  ports: [humanIn, approve, reject],
221
211
  }
222
212
  const resolved = resolvePortSchemas(
223
- withViewDeclaration([ai, human], {
224
- in: "T",
225
- approve: "T",
226
- reject: instructionSchema(),
227
- }),
213
+ [ai, human],
228
214
  [{ from_port_id: aiOut.id, to_port_id: humanIn.id }],
229
215
  )
230
216
  // 上流が AI でも中身を取り出さない:AI の出力形式がそのまま下流へ流れる
@@ -233,25 +219,17 @@ describe("型伝播(導出ポートの解決)", () => {
233
219
  expect(resolved.get(reject.id)).toEqual(instructionSchema())
234
220
  })
235
221
 
236
- describe("実装の宣言を human のポートへ転写する", () => {
222
+ describe("実装(nodes/human/<dir>/)を指す human は program / ai と同じ", () => {
237
223
  const viewIn = {
238
224
  type: "object",
239
- properties: {
240
- data: {
241
- type: "object",
242
- properties: { prs: { type: "array", items: { type: "object" } } },
243
- required: ["prs"],
244
- },
245
- description: { type: "string" },
246
- },
247
- required: ["data", "description"],
225
+ properties: { prs: { type: "array", items: { type: "object" } } },
226
+ required: ["prs"],
248
227
  }
249
228
  const viewOut = {
250
229
  type: "object",
251
- properties: { prs: { type: "array", items: { type: "object" } } },
252
- required: ["prs"],
230
+ properties: { merged: { type: "boolean" } },
231
+ required: ["merged"],
253
232
  }
254
- // reject もビューの宣言(具体型のみ。コピー指示は意味を持たない)
255
233
  const viewReject = {
256
234
  type: "object",
257
235
  properties: { reason: { type: "string" } },
@@ -264,91 +242,54 @@ describe("型伝播(導出ポートの解決)", () => {
264
242
  kind: upstreamKind,
265
243
  ports: [port("in", "in"), out],
266
244
  }
267
- const humanIn = port("in", "in")
268
- const approve = port("out", "approve")
269
- const reject = port("out", "reject")
245
+ const humanIn = port("in", "in", viewIn)
246
+ const approve = port("out", "approve", viewOut)
247
+ const reject = port("out", "reject", viewReject)
270
248
  const human = {
271
249
  id: uuid(),
272
250
  kind: "human",
251
+ human: { human_dir: "review" },
273
252
  ports: [humanIn, approve, reject],
274
253
  }
275
254
  return { upstream, human, out, humanIn, approve, reject }
276
255
  }
277
256
 
278
- it("宣言のあるビューは上流の kind に依らず同じ型になる", () => {
257
+ it("out(approve・reject)は宣言のまま(上流の kind に依らない)", () => {
279
258
  for (const kind of ["ai", "program"]) {
280
259
  const { upstream, human, out, humanIn, approve, reject } = build(
281
260
  kind,
282
261
  declaredOut,
283
262
  )
284
263
  const resolved = resolvePortSchemas(
285
- withViewDeclaration([upstream, human], {
286
- in: viewIn,
287
- approve: viewOut,
288
- reject: viewReject,
289
- }),
264
+ [upstream, human],
290
265
  [{ from_port_id: out.id, to_port_id: humanIn.id }],
291
266
  )
292
- expect(resolved.get(humanIn.id)).toEqual(viewIn)
293
267
  expect(resolved.get(approve.id)).toEqual(viewOut)
294
268
  expect(resolved.get(reject.id)).toEqual(viewReject)
295
269
  }
296
270
  })
297
271
 
298
- it("上流の out は書き換えない(逆伝播しない)", () => {
272
+ it("in は上流の具体型を写す(program / ai の in と同じ鏡映)", () => {
299
273
  const { upstream, human, out, humanIn } = build("ai", declaredOut)
300
274
  const resolved = resolvePortSchemas(
301
- withViewDeclaration([upstream, human], {
302
- in: viewIn,
303
- approve: viewOut,
304
- reject: viewReject,
305
- }),
306
- [{ from_port_id: out.id, to_port_id: humanIn.id }],
307
- )
308
- expect(resolved.get(out.id)).toEqual(declaredOut)
309
- })
310
-
311
- it("コピー宣言(標準ビュー)は上流の型をそのまま in と approve へ流す", () => {
312
- const { upstream, human, out, humanIn, approve } = build(
313
- "ai",
314
- declaredOut,
315
- )
316
- const resolved = resolvePortSchemas(
317
- withViewDeclaration([upstream, human], {
318
- in: "T",
319
- approve: "T",
320
- reject: viewReject,
321
- }),
275
+ [upstream, human],
322
276
  [{ from_port_id: out.id, to_port_id: humanIn.id }],
323
277
  )
324
278
  expect(resolved.get(humanIn.id)).toEqual(declaredOut)
325
- expect(resolved.get(approve.id)).toEqual(declaredOut)
279
+ // 上流の out は書き換えない(逆伝播しない)
280
+ expect(resolved.get(out.id)).toEqual(declaredOut)
326
281
  })
327
282
 
328
- it("未接続でも宣言で固定される", () => {
329
- const humanIn = port("in", "in")
330
- const approve = port("out", "approve")
331
- const reject = port("out", "reject")
332
- const human = {
333
- id: uuid(),
334
- kind: "human",
335
- ports: [humanIn, approve, reject],
336
- }
337
- const resolved = resolvePortSchemas(
338
- withViewDeclaration([human], {
339
- in: viewIn,
340
- approve: viewOut,
341
- reject: viewReject,
342
- }),
343
- [],
344
- )
283
+ it("未接続の in は宣言のまま", () => {
284
+ const { human, humanIn, approve, reject } = build("ai", declaredOut)
285
+ const resolved = resolvePortSchemas([human], [])
345
286
  expect(resolved.get(humanIn.id)).toEqual(viewIn)
346
287
  expect(resolved.get(approve.id)).toEqual(viewOut)
347
288
  expect(resolved.get(reject.id)).toEqual(viewReject)
348
289
  })
349
290
  })
350
291
 
351
- it("ai → wait → human:素通しで上流の宣言型が伝播し、reject はビュー宣言が無ければ未宣言", () => {
292
+ it("ai → wait → human:素通しで上流の宣言型が伝播し、reject は標準の差し戻し", () => {
352
293
  // ai の in は宣言型(nodes/ai/<dir>/config.json の inputs.in 由来。実行repoの構造見直し)
353
294
  const aiInSchema = {
354
295
  type: "object",
@@ -383,8 +324,8 @@ describe("型伝播(導出ポートの解決)", () => {
383
324
  expect(resolved.get(humanIn.id)).toEqual(declaredOut)
384
325
  // approve は in と同型(上流の kind による中身の取り出しはしない)
385
326
  expect(resolved.get(approve.id)).toEqual(declaredOut)
386
- // reject はビューの宣言でのみ決まる(sdk 単体=カタログ無しでは未宣言。型固定は廃止)
387
- expect(resolved.get(reject.id)).toEqual(UNDECLARED_SCHEMA)
327
+ // reject は標準の承認画面の差し戻し(指示+参考ファイル)
328
+ expect(resolved.get(reject.id)).toEqual(instructionSchema())
388
329
  // ai の in は宣言型がそのまま残る(const 扱いは廃止)
389
330
  expect(resolved.get(aiIn.id)).toEqual(aiInSchema)
390
331
  })