From e72182003a4a153f63f928e0a6bf987bc0f30f92 Mon Sep 17 00:00:00 2001 From: 18628386721 <18628386721@163.com> Date: Mon, 27 Jul 2026 04:31:23 +0800 Subject: [PATCH] =?UTF-8?q?M5/M6=20=E4=BF=AE=E5=A4=8D=E4=B8=B2:=E5=88=A4?= =?UTF-8?q?=E6=8D=AE=20=E2=91=A2=20=E5=85=A8=E9=93=BE=E8=B7=AF=E8=B7=91?= =?UTF-8?q?=E9=80=9A=20=E2=80=94=E2=80=94=20=E5=85=AD=E5=A4=84=E3=80=8C?= =?UTF-8?q?=E5=A3=B0=E6=98=8E=E4=B8=8E=E5=BC=95=E6=93=8E=E5=AF=B9=E4=B8=8D?= =?UTF-8?q?=E4=B8=8A=E3=80=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 判据 ③ 现在从入站到 released 一路走通:步序推进 → LLM 步(密钥是占位符, 按声明降级为人兜底)→ 台账草稿层 → D1 条件跳步 → S4 工具 → S5 DMN 三分类 (LM358DR → Type A · 直接快答)→ 验收放行 → 草稿合并正式版 2 行 → 输出口 intake 发射 → 终态 released。 沿路修掉的六处,每一处都是「声明是这么写的,引擎不这么读」: 1. 人兜底卡只问一个笼统「结果」框 → 改问**这一步声明的出参字段**。 出参契约属于步、不属于执行者:同一步谁做完,落进作用域的形状都得一样。 2. 人事件的载荷是回传协议壳 `{mode,workOrderId,values}`, 引擎把整个壳当出参写 → 取 `values`。 3. 引擎作用域是 `{'域.对象': 行数组}`,而所有活动的步声明写的都是裸字段名 → 任务视图三层铺开:起单入参在底、各对象最新行的字段铺顶层、地址式整表保留。 4. 起单入参从不落库、也进不了作用域 → task 存 `input_data`,断点续跑后仍在。 5. 步出参无法声明**写哪一行** → 出参加 `key`(主键列 → 作用域路径); 六个活动包的每处出参补上键。台账几乎都有主键,不声明键就只能写无主键行。 6. `tick` 不处理 `finished`:谱走完仍按 running 一拍拍推,把预算烧到 exhausted, 把「没人裁」误报成「跑爆了」→ 放行通过则落地收敛后 released;未放行则挂起等人。 `flow.advance` 据末步验收裁决报 released —— **完成只出自放行通过**。 另两处装配缺口: · `release.land()` 实现完整却无人调用 —— 放行后从不落地。经 LandingGateway 接上。 · `process.onTaskTerminal()` 实现完整却无人调用 —— 任务终态报不到流程层, 流就停在那个节点上,既不流转也不走失败走向。task 在 L6 不得反向依赖 L7, 这一跳由 app 缝合(延后绑定,同 container 里 TriggerMatcher 的手法)。 还有一处版本化缺陷:资产播种恒用 v1,`shouldSeedAsset` 一比就跳过 —— 只有**第一个**包版本的资产此生有效,之后装什么都跑旧的。改为播下一版; 进化管线放行出的更高版仍不被盖过;同版重复安装不再播种。 gates: tsc / eslint / depcruise(317 模块)/ api-report / 393 测试 全绿 Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01KeDBx2PHLavbURXB8LJJ7m --- .../attachments/inbound/rfq-005@acme.com.eml | 12 + .../attachments/inbound/rfq-006@acme.com.eml | 12 + .../attachments/inbound/rfq-007@acme.com.eml | 12 + .../attachments/inbound/rfq-008@acme.com.eml | 12 + .../attachments/inbound/rfq-009@acme.com.eml | 12 + .../attachments/inbound/rfq-010@acme.com.eml | 12 + .../attachments/inbound/rfq-011@acme.com.eml | 12 + .../inbound/rfq-2026-07-27-002@acme.com.eml | 12 + .../inbound/rfq-2026-07-27-003@acme.com.eml | 12 + .../inbound/rfq-2026-07-27-004@acme.com.eml | 12 + activities/quote-archive/src/manifest.ts | 23 +- activities/quote-intake/src/manifest.ts | 36 +- activities/quote-negotiate/src/manifest.ts | 9 +- activities/quote-pricing/src/manifest.ts | 13 +- .../src/modules/activity-registry/port.d.ts | 3 +- .../apps/api/src/modules/task/contract.d.ts | 2 + .../apps/api/src/modules/task/port.d.ts | 27 +- .../packages/contracts/src/manifest.d.ts | 20 + apps/api/src/app/wiring/engines.ts | 98 +- .../__tests__/versioning.test.ts | 30 +- .../internal/adapter/registry-store.ts | 7 +- .../internal/usecase/registry-service.ts | 38 +- .../api/src/modules/activity-registry/port.ts | 3 +- .../__tests__/step-sequence.test.ts | 105 + .../internal/domain/step-sequence.ts | 24 + .../internal/usecase/flow-runner.ts | 41 +- apps/api/src/modules/task/contract.ts | 2 + .../task/internal/adapter/task-schema.ts | 2 + .../task/internal/adapter/task-store.ts | 2 + .../task/internal/usecase/task-service.ts | 36 + apps/api/src/modules/task/port.ts | 23 +- db/migrations/0009_cute_bromley.sql | 1 + db/migrations/meta/0009_snapshot.json | 3267 +++++++++++++++++ db/migrations/meta/_journal.json | 7 + packages/contracts/src/manifest.ts | 15 +- 35 files changed, 3901 insertions(+), 53 deletions(-) create mode 100644 .data/attachments/inbound/rfq-005@acme.com.eml create mode 100644 .data/attachments/inbound/rfq-006@acme.com.eml create mode 100644 .data/attachments/inbound/rfq-007@acme.com.eml create mode 100644 .data/attachments/inbound/rfq-008@acme.com.eml create mode 100644 .data/attachments/inbound/rfq-009@acme.com.eml create mode 100644 .data/attachments/inbound/rfq-010@acme.com.eml create mode 100644 .data/attachments/inbound/rfq-011@acme.com.eml create mode 100644 .data/attachments/inbound/rfq-2026-07-27-002@acme.com.eml create mode 100644 .data/attachments/inbound/rfq-2026-07-27-003@acme.com.eml create mode 100644 .data/attachments/inbound/rfq-2026-07-27-004@acme.com.eml create mode 100644 apps/api/src/modules/flow-engine/__tests__/step-sequence.test.ts create mode 100644 db/migrations/0009_cute_bromley.sql create mode 100644 db/migrations/meta/0009_snapshot.json diff --git a/.data/attachments/inbound/rfq-005@acme.com.eml b/.data/attachments/inbound/rfq-005@acme.com.eml new file mode 100644 index 0000000..f16726c --- /dev/null +++ b/.data/attachments/inbound/rfq-005@acme.com.eml @@ -0,0 +1,12 @@ +{ + "messageId": "rfq-005@acme.com", + "from": "buyer@acme.com", + "to": [ + "rfq@we.com" + ], + "cc": [], + "subject": "【询价】客户 C001 RFQ-20260727C 求报价", + "body": "客户编码 C001,询价单号 RFQ-20260727C。\nLM358DR TI 年用量 12000 目标价 8.20 交期 10w", + "receivedAt": "2026-07-27T04:00:00.000Z", + "attachments": [] +} \ No newline at end of file diff --git a/.data/attachments/inbound/rfq-006@acme.com.eml b/.data/attachments/inbound/rfq-006@acme.com.eml new file mode 100644 index 0000000..e8a143d --- /dev/null +++ b/.data/attachments/inbound/rfq-006@acme.com.eml @@ -0,0 +1,12 @@ +{ + "messageId": "rfq-006@acme.com", + "from": "buyer@acme.com", + "to": [ + "rfq@we.com" + ], + "cc": [], + "subject": "【询价】客户 C001 RFQ-20260727D 求报价", + "body": "客户编码 C001,询价单号 RFQ-20260727D。\nLM358DR TI 年用量 12000 目标价 8.20 交期 10w", + "receivedAt": "2026-07-27T05:00:00.000Z", + "attachments": [] +} \ No newline at end of file diff --git a/.data/attachments/inbound/rfq-007@acme.com.eml b/.data/attachments/inbound/rfq-007@acme.com.eml new file mode 100644 index 0000000..2d697e4 --- /dev/null +++ b/.data/attachments/inbound/rfq-007@acme.com.eml @@ -0,0 +1,12 @@ +{ + "messageId": "rfq-007@acme.com", + "from": "buyer@acme.com", + "to": [ + "rfq@we.com" + ], + "cc": [], + "subject": "【询价】客户 C001 RFQ-20260727E 求报价", + "body": "客户编码 C001,询价单号 RFQ-20260727E。\nLM358DR TI 年用量 12000 目标价 8.20 交期 10w", + "receivedAt": "2026-07-27T06:00:00.000Z", + "attachments": [] +} \ No newline at end of file diff --git a/.data/attachments/inbound/rfq-008@acme.com.eml b/.data/attachments/inbound/rfq-008@acme.com.eml new file mode 100644 index 0000000..395f151 --- /dev/null +++ b/.data/attachments/inbound/rfq-008@acme.com.eml @@ -0,0 +1,12 @@ +{ + "messageId": "rfq-008@acme.com", + "from": "buyer@acme.com", + "to": [ + "rfq@we.com" + ], + "cc": [], + "subject": "【询价】客户 C001 RFQ-20260727F 求报价", + "body": "客户编码 C001,询价单号 RFQ-20260727F。\nLM358DR TI 年用量 12000 目标价 8.20 交期 10w", + "receivedAt": "2026-07-27T07:00:00.000Z", + "attachments": [] +} \ No newline at end of file diff --git a/.data/attachments/inbound/rfq-009@acme.com.eml b/.data/attachments/inbound/rfq-009@acme.com.eml new file mode 100644 index 0000000..5c63325 --- /dev/null +++ b/.data/attachments/inbound/rfq-009@acme.com.eml @@ -0,0 +1,12 @@ +{ + "messageId": "rfq-009@acme.com", + "from": "buyer@acme.com", + "to": [ + "rfq@we.com" + ], + "cc": [], + "subject": "【询价】客户 C001 RFQ-20260727G 求报价", + "body": "客户编码 C001,询价单号 RFQ-20260727G。\nLM358DR TI 年用量 12000 目标价 8.20 交期 10w", + "receivedAt": "2026-07-27T08:00:00.000Z", + "attachments": [] +} \ No newline at end of file diff --git a/.data/attachments/inbound/rfq-010@acme.com.eml b/.data/attachments/inbound/rfq-010@acme.com.eml new file mode 100644 index 0000000..bde79f8 --- /dev/null +++ b/.data/attachments/inbound/rfq-010@acme.com.eml @@ -0,0 +1,12 @@ +{ + "messageId": "rfq-010@acme.com", + "from": "buyer@acme.com", + "to": [ + "rfq@we.com" + ], + "cc": [], + "subject": "【询价】客户 C001 RFQ-20260727H 求报价", + "body": "客户编码 C001,询价单号 RFQ-20260727H。\nLM358DR TI 年用量 12000 目标价 8.20 交期 10w", + "receivedAt": "2026-07-27T09:00:00.000Z", + "attachments": [] +} \ No newline at end of file diff --git a/.data/attachments/inbound/rfq-011@acme.com.eml b/.data/attachments/inbound/rfq-011@acme.com.eml new file mode 100644 index 0000000..fe99af1 --- /dev/null +++ b/.data/attachments/inbound/rfq-011@acme.com.eml @@ -0,0 +1,12 @@ +{ + "messageId": "rfq-011@acme.com", + "from": "buyer@acme.com", + "to": [ + "rfq@we.com" + ], + "cc": [], + "subject": "【询价】客户 C001 RFQ-20260727J 求报价", + "body": "客户编码 C001,询价单号 RFQ-20260727J。\nLM358DR TI 年用量 12000 目标价 8.20 交期 10w", + "receivedAt": "2026-07-27T10:00:00.000Z", + "attachments": [] +} \ No newline at end of file diff --git a/.data/attachments/inbound/rfq-2026-07-27-002@acme.com.eml b/.data/attachments/inbound/rfq-2026-07-27-002@acme.com.eml new file mode 100644 index 0000000..603b5e4 --- /dev/null +++ b/.data/attachments/inbound/rfq-2026-07-27-002@acme.com.eml @@ -0,0 +1,12 @@ +{ + "messageId": "rfq-2026-07-27-002@acme.com", + "from": "buyer@acme.com", + "to": [ + "rfq@we.com" + ], + "cc": [], + "subject": "【询价】ACME PN-9001 / PN-9002 求报价 RFQ-2026-0727", + "body": "您好,\n我司需求如下:\nPN-9001 TI 年用量 12000 目标价 8.20 交期 10w\nPN-9002 ADI 年用量 3000 目标价 21.00 交期 12w\nRFQ 编号 RFQ-2026-0727,请于本周内回复。\n谢谢!", + "receivedAt": "2026-07-27T02:00:00.000Z", + "attachments": [] +} \ No newline at end of file diff --git a/.data/attachments/inbound/rfq-2026-07-27-003@acme.com.eml b/.data/attachments/inbound/rfq-2026-07-27-003@acme.com.eml new file mode 100644 index 0000000..3d61f4d --- /dev/null +++ b/.data/attachments/inbound/rfq-2026-07-27-003@acme.com.eml @@ -0,0 +1,12 @@ +{ + "messageId": "rfq-2026-07-27-003@acme.com", + "from": "buyer@acme.com", + "to": [ + "rfq@we.com" + ], + "cc": [], + "subject": "【询价】客户 C001 RFQ-20260727A 求报价", + "body": "您好,\n客户编码 C001,询价单号 RFQ-20260727A。\n需求如下:\nPN-9001 TI 年用量 12000 目标价 8.20 交期 10w\n请于本周内回复。谢谢!", + "receivedAt": "2026-07-27T02:10:00.000Z", + "attachments": [] +} \ No newline at end of file diff --git a/.data/attachments/inbound/rfq-2026-07-27-004@acme.com.eml b/.data/attachments/inbound/rfq-2026-07-27-004@acme.com.eml new file mode 100644 index 0000000..baa10cd --- /dev/null +++ b/.data/attachments/inbound/rfq-2026-07-27-004@acme.com.eml @@ -0,0 +1,12 @@ +{ + "messageId": "rfq-2026-07-27-004@acme.com", + "from": "buyer@acme.com", + "to": [ + "rfq@we.com" + ], + "cc": [], + "subject": "【询价】客户 C001 RFQ-20260727B 求报价", + "body": "客户编码 C001,询价单号 RFQ-20260727B。\nPN-9001 TI 年用量 12000 目标价 8.20 交期 10w", + "receivedAt": "2026-07-27T03:00:00.000Z", + "attachments": [] +} \ No newline at end of file diff --git a/activities/quote-archive/src/manifest.ts b/activities/quote-archive/src/manifest.ts index 81944be..e8da110 100644 --- a/activities/quote-archive/src/manifest.ts +++ b/activities/quote-archive/src/manifest.ts @@ -13,7 +13,7 @@ export const manifest: ActivityManifest = { identity: { activityId: ACTIVITY_ID, name: 'A7 · 情报归档', - packageVersion: '0.1.0', + packageVersion: '0.1.1', category: '统一报价', paradigm: 'flow', sdkVersion: '0.1.0-m5', @@ -118,7 +118,12 @@ export const manifest: ActivityManifest = { name: 'S1_log_result', label: 'S1 结果登记(拒绝原因 / 竞对价位)', input: { reason: 'reason', quoteNo: 'quoteNo' }, - output: { domain: 'shared', object: 'history_price', fields: { lost_reason: 'lostReason' } }, + output: { + domain: 'shared', + object: 'history_price', + key: { pn: 'pn', quoted_at: 'quotedAt' }, + fields: { lost_reason: 'lostReason' }, + }, model: 'quote-extractor', template: '从跟进反馈里抽出拒绝原因与竞对价位。只输出 JSON。反馈:{{reason}}', outputSchema: { @@ -132,7 +137,12 @@ export const manifest: ActivityManifest = { name: 'S2_profile', label: 'S2 画像更新(供 P-01 的客户等级因子取用)', input: { customerCode: 'customerCode', reason: 'reason' }, - output: { domain: 'shared', object: 'customer', fields: { profile_tags: 'tags' } }, + output: { + domain: 'shared', + object: 'customer', + key: { customer_code: 'customerCode' }, + fields: { profile_tags: 'tags' }, + }, tool: 'quote-archive.profile', failure: { retry: { times: 1, backoff: 'PT10S' }, fallback: { kind: 'blocked' } }, }, @@ -141,7 +151,12 @@ export const manifest: ActivityManifest = { name: 'S3_landing_rate', label: 'S3 落地率统计', input: {}, - output: { domain: 'shared', object: 'landing_stats', fields: { landing_rate: 'landingRate' } }, + output: { + domain: 'shared', + object: 'landing_stats', + key: { period: 'period' }, + fields: { landing_rate: 'landingRate' }, + }, tool: 'quote-archive.landing-rate', failure: { retry: { times: 1, backoff: 'PT10S' }, fallback: { kind: 'blocked' } }, }, diff --git a/activities/quote-intake/src/manifest.ts b/activities/quote-intake/src/manifest.ts index 6bcfd3b..d3daeda 100644 --- a/activities/quote-intake/src/manifest.ts +++ b/activities/quote-intake/src/manifest.ts @@ -40,7 +40,7 @@ export const manifest: ActivityManifest = { identity: { activityId: ACTIVITY_ID, name: 'A1 · RFQ 需求分析', - packageVersion: '0.1.0', + packageVersion: '0.1.2', category: '统一报价', paradigm: 'flow', sdkVersion: '0.1.0-m5', @@ -135,6 +135,10 @@ export const manifest: ActivityManifest = { { name: 'type_tag', type: 'text', primaryKey: false, masked: false }, { name: 'basis', type: 'text', primaryKey: false, masked: false, label: '分级依据' }, { name: 'history_hit', type: 'boolean', primaryKey: false, masked: false, label: '历史价命中' }, + // 打标的三个判据必须落表:S5 的 DMN 从这里读,不落就无米下锅 + { name: 'history_days', type: 'integer', primaryKey: false, masked: false, label: '距上次报价天数' }, + { name: 'market_stable', type: 'boolean', primaryKey: false, masked: false, label: '行情稳定' }, + { name: 'lifecycle', type: 'text', primaryKey: false, masked: false, label: '生命周期' }, ], }, ], @@ -168,7 +172,12 @@ export const manifest: ActivityManifest = { name: 'S3_normalize', label: 'S3 逐条解析归一', input: { rfqRef: 'rfqRef', customerCode: 'customerCode' }, - output: { domain: INTAKE_DOMAIN, object: 'record', fields: { pn: 'pn', brand: 'brand', eau: 'eau' } }, + output: { + domain: INTAKE_DOMAIN, + object: 'record', + key: { customer_code: 'customerCode', pn: 'pn' }, + fields: { pn: 'pn', brand: 'brand', eau: 'eau' }, + }, model: 'quote-extractor', template: '按《询价受理规范》把这份 RFQ 拆解为逐条需求,抄录 PN/数量/EAU/厂牌/目标价/交期。' + @@ -213,7 +222,18 @@ export const manifest: ActivityManifest = { name: 'S4_history', label: 'S4 查历史价', input: { pn: 'pn' }, - output: { domain: INTAKE_DOMAIN, object: 'type_table', fields: { history_hit: 'hit' } }, + // 情报四件全部落表:S5 打标要的就是这三件,只落 hit 会让下一步无米下锅 + output: { + domain: INTAKE_DOMAIN, + object: 'type_table', + key: { pn: 'pn' }, + fields: { + history_hit: 'hit', + history_days: 'historyDays', + market_stable: 'marketStable', + lifecycle: 'lifecycle', + }, + }, tool: 'crm.history-price-read', failure: { retry: { times: 2, backoff: 'PT1M' }, fallback: { kind: 'human', workOrder: 'execution' } }, }, @@ -221,8 +241,14 @@ export const manifest: ActivityManifest = { kind: 'pure-tool', name: 'S5_classify', label: 'S5 逐条打标(DMN · 三分类规则 P-03)', - input: { historyDays: 'historyDays', marketStable: 'marketStable', lifecycle: 'lifecycle' }, - output: { domain: INTAKE_DOMAIN, object: 'type_table', fields: { type_tag: 'type', basis: 'play' } }, + // 入参名 ← 作用域路径:作用域里是 S4 落表后的字段名 + input: { historyDays: 'history_days', marketStable: 'market_stable', lifecycle: 'lifecycle' }, + output: { + domain: INTAKE_DOMAIN, + object: 'type_table', + key: { pn: 'pn' }, + fields: { type_tag: 'type', basis: 'play' }, + }, tool: 'quote-intake.classify', failure: { retry: { times: 1, backoff: 'PT10S' }, fallback: { kind: 'blocked' } }, }, diff --git a/activities/quote-negotiate/src/manifest.ts b/activities/quote-negotiate/src/manifest.ts index 06a7829..21f23d0 100644 --- a/activities/quote-negotiate/src/manifest.ts +++ b/activities/quote-negotiate/src/manifest.ts @@ -15,7 +15,7 @@ export const manifest: ActivityManifest = { identity: { activityId: ACTIVITY_ID, name: 'A6 · 报价议价', - packageVersion: '0.1.0', + packageVersion: '0.1.1', category: '统一报价', paradigm: 'flow', sdkVersion: '0.1.0-m5', @@ -135,7 +135,12 @@ export const manifest: ActivityManifest = { name: 'S1_compose', label: 'S1 生成正式报价单', input: { items: 'items', customerCode: 'customerCode' }, - output: { domain: DOMAIN, object: 'formal_quote', fields: { unit_price: 'unitPrice', lead_time: 'leadTime' } }, + output: { + domain: DOMAIN, + object: 'formal_quote', + key: { quote_no: 'quoteNo', pn: 'pn' }, + fields: { unit_price: 'unitPrice', lead_time: 'leadTime' }, + }, model: 'quote-extractor', template: '把已批准的报价草案排版为正式报价单条目(单价CNY/交期/SPQ/MOQ/贸易条款),' + diff --git a/activities/quote-pricing/src/manifest.ts b/activities/quote-pricing/src/manifest.ts index 56865ce..5a2d7db 100644 --- a/activities/quote-pricing/src/manifest.ts +++ b/activities/quote-pricing/src/manifest.ts @@ -59,7 +59,7 @@ export const manifest: ActivityManifest = { identity: { activityId: ACTIVITY_ID, name: 'A4 · 成本定价', - packageVersion: '0.1.0', + packageVersion: '0.1.1', category: '统一报价', paradigm: 'flow', sdkVersion: '0.1.0-m5', @@ -167,7 +167,7 @@ export const manifest: ActivityManifest = { label: 'S1 取价基准', // Type A 取历史价近期价;B/C 取供应商底价表(C 类参考替代对标表) input: { pn: 'pn' }, - output: { domain: DOMAIN, object: 'draft', fields: { deviation: 'deviation' } }, + output: { domain: DOMAIN, object: 'draft', key: { pn: 'pn' }, fields: { deviation: 'deviation' } }, tool: 'crm.history-price-read', failure: { retry: { times: 2, backoff: 'PT1M' }, fallback: { kind: 'human', workOrder: 'execution' } }, }, @@ -176,7 +176,12 @@ export const manifest: ActivityManifest = { name: 'S2_price', label: 'S2 逐条定价(DMN · 利润模型 P-01)', input: { customerGrade: 'customerGrade', materialType: 'materialType', volumeTier: 'volumeTier' }, - output: { domain: DOMAIN, object: 'draft', fields: { margin: 'marginLow', quoted_price: 'quotedPrice' } }, + output: { + domain: DOMAIN, + object: 'draft', + key: { pn: 'pn' }, + fields: { margin: 'marginLow', quoted_price: 'quotedPrice' }, + }, tool: 'quote-pricing.margin', failure: { retry: { times: 1, backoff: 'PT10S' }, fallback: { kind: 'blocked' } }, }, @@ -185,7 +190,7 @@ export const manifest: ActivityManifest = { name: 'S3_floor_gate', label: 'S3 底价自检(DMN · 底价门禁 P-02)', input: { deviation: 'deviation' }, - output: { domain: DOMAIN, object: 'draft', fields: { disposition: 'disposition' } }, + output: { domain: DOMAIN, object: 'draft', key: { pn: 'pn' }, fields: { disposition: 'disposition' } }, tool: 'quote-pricing.floor-gate', failure: { retry: { times: 1, backoff: 'PT10S' }, fallback: { kind: 'blocked' } }, }, diff --git a/api-report/apps/api/src/modules/activity-registry/port.d.ts b/api-report/apps/api/src/modules/activity-registry/port.d.ts index 4433c11..30a6e0a 100644 --- a/api-report/apps/api/src/modules/activity-registry/port.d.ts +++ b/api-report/apps/api/src/modules/activity-registry/port.d.ts @@ -10,7 +10,8 @@ export type IdGenerator = { next(): string; }; export type RegistryStore = { - savePackage(manifest: ActivityManifest, artifact: string): Promise; + /** 返回是否新增了这一包版本(同版重复安装返回 false) */ + savePackage(manifest: ActivityManifest, artifact: string): Promise; listPackages(activityId?: string): Promise; manifest(activityId: string, packageVersion?: string): Promise; artifact(activityId: string, packageVersion: string): Promise; diff --git a/api-report/apps/api/src/modules/task/contract.d.ts b/api-report/apps/api/src/modules/task/contract.d.ts index 607f225..57e2831 100644 --- a/api-report/apps/api/src/modules/task/contract.d.ts +++ b/api-report/apps/api/src/modules/task/contract.d.ts @@ -32,6 +32,8 @@ export type Task = { readonly reason?: string; /** 装载信封:包版本 + 各资产生效版 —— 行为可归因 */ readonly envelope: Readonly>; + /** 起单入参:契约的 data —— 引擎求值的作用域底料 */ + readonly data: Readonly>; readonly caps: BudgetCaps; readonly used: Consumption; readonly startedAt: string; diff --git a/api-report/apps/api/src/modules/task/port.d.ts b/api-report/apps/api/src/modules/task/port.d.ts index 889204c..db1c296 100644 --- a/api-report/apps/api/src/modules/task/port.d.ts +++ b/api-report/apps/api/src/modules/task/port.d.ts @@ -40,17 +40,32 @@ export type SchemaValidator = { /** 范式引擎 —— loop / flow 在此汇流;对上层不可区分(§9 双层边界) */ export type ParadigmEngine = { load(taskId: TaskId, model: unknown): Promise; - advance(taskId: TaskId): Promise<{ - status: 'running' | 'awaiting' | 'submitted' | 'finished' | 'blocked'; - reason?: string; - }>; + advance(taskId: TaskId): Promise; onEvent(taskId: TaskId, event: { kind: 'data' | 'time' | 'human' | 'control'; ref: string; payload: Record; + }): Promise; +}; +export type EngineOutcome = { + readonly status: 'running' | 'awaiting' | 'submitted' | 'finished' | 'blocked'; + readonly reason?: string; + /** 谱走完时:放行是否通过 —— **完成只出自放行通过**,引擎不自行宣布完成 */ + readonly released?: boolean; +}; +/** + * 落地收敛 —— app 注入 `release`(L4)。 + * 放行通过的那一刻才落地:草稿合并入正式版、动作意向执行、收敛后才发射输出口。 + */ +export type LandingGateway = { + land(input: { + taskId: TaskId; + ownerId: StaffId; + humanCallCap: number; }): Promise<{ - status: 'running' | 'awaiting' | 'submitted' | 'finished' | 'blocked'; - reason?: string; + converged: boolean; + emittedPorts: readonly string[]; + ledgerRowsMerged: number; }>; }; export type MemoryRecorder = { diff --git a/api-report/packages/contracts/src/manifest.d.ts b/api-report/packages/contracts/src/manifest.d.ts index 21f8257..3c95339 100644 --- a/api-report/packages/contracts/src/manifest.d.ts +++ b/api-report/packages/contracts/src/manifest.d.ts @@ -146,6 +146,7 @@ export declare const flowStepSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{ output: z.ZodOptional>; fields: z.ZodRecord; }, z.core.$strip>>; when: z.ZodOptional; @@ -175,6 +176,7 @@ export declare const flowStepSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{ output: z.ZodOptional>; fields: z.ZodRecord; }, z.core.$strip>>; when: z.ZodOptional; @@ -207,6 +209,7 @@ export declare const flowStepSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{ output: z.ZodOptional>; fields: z.ZodRecord; }, z.core.$strip>>; when: z.ZodOptional; @@ -234,6 +237,7 @@ export declare const flowStepSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{ output: z.ZodOptional>; fields: z.ZodRecord; }, z.core.$strip>>; when: z.ZodOptional; @@ -265,6 +269,7 @@ export declare const flowModelSchema: z.ZodObject<{ output: z.ZodOptional>; fields: z.ZodRecord; }, z.core.$strip>>; when: z.ZodOptional; @@ -294,6 +299,7 @@ export declare const flowModelSchema: z.ZodObject<{ output: z.ZodOptional>; fields: z.ZodRecord; }, z.core.$strip>>; when: z.ZodOptional; @@ -326,6 +332,7 @@ export declare const flowModelSchema: z.ZodObject<{ output: z.ZodOptional>; fields: z.ZodRecord; }, z.core.$strip>>; when: z.ZodOptional; @@ -353,6 +360,7 @@ export declare const flowModelSchema: z.ZodObject<{ output: z.ZodOptional>; fields: z.ZodRecord; }, z.core.$strip>>; when: z.ZodOptional; @@ -514,6 +522,7 @@ export declare const businessModelSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{ output: z.ZodOptional>; fields: z.ZodRecord; }, z.core.$strip>>; when: z.ZodOptional; @@ -543,6 +552,7 @@ export declare const businessModelSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{ output: z.ZodOptional>; fields: z.ZodRecord; }, z.core.$strip>>; when: z.ZodOptional; @@ -575,6 +585,7 @@ export declare const businessModelSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{ output: z.ZodOptional>; fields: z.ZodRecord; }, z.core.$strip>>; when: z.ZodOptional; @@ -602,6 +613,7 @@ export declare const businessModelSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{ output: z.ZodOptional>; fields: z.ZodRecord; }, z.core.$strip>>; when: z.ZodOptional; @@ -686,6 +698,7 @@ export declare const manifestAssetsSchema: z.ZodObject<{ output: z.ZodOptional>; fields: z.ZodRecord; }, z.core.$strip>>; when: z.ZodOptional; @@ -715,6 +728,7 @@ export declare const manifestAssetsSchema: z.ZodObject<{ output: z.ZodOptional>; fields: z.ZodRecord; }, z.core.$strip>>; when: z.ZodOptional; @@ -747,6 +761,7 @@ export declare const manifestAssetsSchema: z.ZodObject<{ output: z.ZodOptional>; fields: z.ZodRecord; }, z.core.$strip>>; when: z.ZodOptional; @@ -774,6 +789,7 @@ export declare const manifestAssetsSchema: z.ZodObject<{ output: z.ZodOptional>; fields: z.ZodRecord; }, z.core.$strip>>; when: z.ZodOptional; @@ -1034,6 +1050,7 @@ export declare const activityManifestSchema: z.ZodObject<{ output: z.ZodOptional>; fields: z.ZodRecord; }, z.core.$strip>>; when: z.ZodOptional; @@ -1063,6 +1080,7 @@ export declare const activityManifestSchema: z.ZodObject<{ output: z.ZodOptional>; fields: z.ZodRecord; }, z.core.$strip>>; when: z.ZodOptional; @@ -1095,6 +1113,7 @@ export declare const activityManifestSchema: z.ZodObject<{ output: z.ZodOptional>; fields: z.ZodRecord; }, z.core.$strip>>; when: z.ZodOptional; @@ -1122,6 +1141,7 @@ export declare const activityManifestSchema: z.ZodObject<{ output: z.ZodOptional>; fields: z.ZodRecord; }, z.core.$strip>>; when: z.ZodOptional; diff --git a/apps/api/src/app/wiring/engines.ts b/apps/api/src/app/wiring/engines.ts index cfd3612..5500a5c 100644 --- a/apps/api/src/app/wiring/engines.ts +++ b/apps/api/src/app/wiring/engines.ts @@ -86,6 +86,20 @@ const sfeel = { }, }; +/** 延后绑定的流程层引用 —— 只用到「任务终态上报」这一件 */ +function createDeferredProcess(): { + onTaskTerminal: ProcessService['onTaskTerminal']; + bind(real: ProcessService): void; +} { + let real: ProcessService | undefined; + return { + onTaskTerminal: async (taskId, terminal, ports) => real?.onTaskTerminal(taskId, terminal, ports), + bind(next) { + real = next; + }, + }; +} + export function wireEngines(deps: EngineWiringDeps): EngineWiring { const { db, clock, ids } = deps; @@ -101,10 +115,22 @@ export function wireEngines(deps: EngineWiringDeps): EngineWiring { /* ── 任务数据:引擎读**任务视图** = 正式版 + 本任务草稿层 ───────── */ const taskData = { + /** + * 任务视图 = 正式版 + 本任务草稿层(AM 4.2),再叠上起单入参。 + * + * 作用域的**形状要对得上声明的写法**:步声明里写的是裸字段名 + * (`input: { pn: 'pn' }`),不是 `域.对象.行号.字段`。所以这里三层铺开: + * ① 起单入参在底 —— 契约的 data 是这单的出发事实; + * ② 各对象**最新一行**的字段名铺到顶层 —— 过程工作区的数据形态即草稿层; + * ③ `域.对象` 仍保留整表,要整表的照样取得到。 + * 顶层同名以「后写的赢」为准:草稿层是这一单最新的过程事实,盖过起单入参。 + */ async view(taskId: TaskId) { - // 引擎对任务视图求值,不依赖事件(AM 4.2) + const task = await taskStore.get(taskId); + if (task === undefined) throw new Error(`任务不存在:${taskId}`); const objects = await deps.ledger.listObjects(); - const scope: Record = {}; + const scope: Record = { ...task.data }; + for (const address of objects) { const page = await deps.ledger.taskView(taskId, { address, @@ -114,6 +140,12 @@ export function wireEngines(deps: EngineWiringDeps): EngineWiring { offset: 0, }); scope[`${address.domain}.${address.object}`] = page.rows; + const latest = page.rows[page.rows.length - 1]; + if (latest === undefined) continue; + for (const [field, value] of Object.entries(latest)) { + // 空值不盖掉已有事实:没写进去的字段不该把起单入参抹成 undefined + if (value !== null && value !== undefined) scope[field] = value; + } } return scope; }, @@ -295,6 +327,29 @@ export function wireEngines(deps: EngineWiringDeps): EngineWiring { clock, }); + /** + * process 晚于 task 构造 —— 延后绑定(与 container 里 `TriggerMatcher` 同一手法)。 + * 未绑定时静默不报:装配尚未完成的那一瞬没有任务会终态。 + */ + const processRef = createDeferredProcess(); + + /** 任务的输出口载荷:活动声明的必交付输出口 → 该任务视图上的字段 */ + async function outputPortsOf(taskId: TaskId): Promise>> { + const task = await taskStore.get(taskId); + if (task === undefined) return {}; + const manifest = await deps.registry.manifestOf(task.activityId); + const scope = await taskData.view(taskId); + const ports: Record> = {}; + for (const port of manifest.contract.output.mandatory) { + const payload = port.payload as { properties?: Record }; + const fields = Object.keys(payload.properties ?? {}); + ports[port.name] = Object.fromEntries( + fields.filter((f) => scope[f] !== undefined).map((f) => [f, scope[f]]), + ); + } + return ports; + } + /* ── task(L6):范式在此汇流,对上层不可区分 ──────────────────── */ const task = createTaskService({ store: taskStore, @@ -330,7 +385,12 @@ export function wireEngines(deps: EngineWiringDeps): EngineWiring { load: (taskId, model) => flow.load(taskId, model as never).then(() => undefined), advance: async (taskId) => { const outcome = await flow.advance(taskId); - return { status: outcome.status, ...(outcome.blockedReason === undefined ? {} : { reason: outcome.blockedReason }) }; + return { + status: outcome.status, + ...(outcome.blockedReason === undefined ? {} : { reason: outcome.blockedReason }), + // 谱走完时把放行裁决带上来 —— 完成只出自放行通过 + ...(outcome.released === undefined ? {} : { released: outcome.released }), + }; }, onEvent: async (taskId, event) => { const outcome = await flow.resume(taskId, { @@ -342,6 +402,22 @@ export function wireEngines(deps: EngineWiringDeps): EngineWiring { }, }, }, + landing: { + /** 放行通过即刻落地:草稿合并 + 动作执行(附幂等键)→ 收敛才发射输出口(OS 7.3) */ + async land(input) { + const outcome = await deps.release.land({ + taskId: input.taskId, + ownerId: input.ownerId, + humanCallCap: input.humanCallCap, + selectedPorts: {}, + }); + return { + converged: outcome.verdict.kind === 'converged', + emittedPorts: outcome.emittedPorts, + ledgerRowsMerged: outcome.ledgerRowsMerged, + }; + }, + }, memory: { append: memoryRecorder.append as never, seal: (taskId) => deps.memory.seal(taskId), @@ -375,6 +451,17 @@ export function wireEngines(deps: EngineWiringDeps): EngineWiring { occurredAt: clock.now().toISOString(), source: { kind: 'engine', detail: 'task' }, }); + + /* + * 任务终态到达 → 通知流程层(L7)。 + * + * task 在 L6、process 在 L7,task 不得反向依赖 process —— 这一跳由 app 这层缝合。 + * 「工作流状态是聚合投影」的前提是节点实例的终态**报得上去**:不报, + * 流就停在那个节点上,既不流转也不走失败走向。 + * 用延后绑定是因为 process 在本函数里晚于 task 构造。 + */ + const ports = await outputPortsOf(taskId); + await processRef.onTaskTerminal(taskId, terminal, ports); }, }, notifier: { @@ -395,7 +482,7 @@ export function wireEngines(deps: EngineWiringDeps): EngineWiring { }); /* ── process(L7) ─────────────────────────────────────────────── */ - const process = createProcessService({ + const process: ProcessService = createProcessService({ store: createProcessStore(db), activities: { async faces() { @@ -556,5 +643,8 @@ export function wireEngines(deps: EngineWiringDeps): EngineWiring { ids, }); + // 延后绑定落定:此后任务终态才报得到流程层 + processRef.bind(process); + return { task, process, trigger }; } diff --git a/apps/api/src/modules/activity-registry/__tests__/versioning.test.ts b/apps/api/src/modules/activity-registry/__tests__/versioning.test.ts index 9d83da2..4a9af40 100644 --- a/apps/api/src/modules/activity-registry/__tests__/versioning.test.ts +++ b/apps/api/src/modules/activity-registry/__tests__/versioning.test.ts @@ -9,7 +9,7 @@ import { mayTakeEffect, shouldSeedAsset, } from '../internal/domain/versioning.ts'; -import type { AssetVersion, PackageVersion, ReplayCase } from '../internal/domain/versioning.ts'; +import type { AssetKind, AssetVersion, PackageVersion, ReplayCase } from '../internal/domain/versioning.ts'; import { scanArtifact } from '../internal/domain/static-audit.ts'; const pkg: PackageVersion = { @@ -150,3 +150,31 @@ describe('scanArtifact —— 活动包零环境能力(ADR-004)', () => { expect(scanArtifact('import fs from "node:fs";').capabilities).toContain('node:fs'); }); }); + +describe('包新版携带的资产:播下一版,不恒播 v1', () => { + const asset = (kind: AssetKind, version: number): AssetVersion => ({ + activityId: 'a1', + kind, + version, + effective: version === 1, + source: 'package-seed', + createdAt: '2026-07-27T00:00:00.000Z', + }); + + it('库里已有 v1 时,新包的资产应播 v2 —— 否则只有首版资产此生有效', () => { + const existing = [asset('business-model', 1)]; + expect(shouldSeedAsset(existing, 'business-model', 2)).toBe(true); + // 恒播 v1 就会被这条挡掉,新包的业务模型永远进不来 + expect(shouldSeedAsset(existing, 'business-model', 1)).toBe(false); + }); + + it('进化管线放行出的更高版不被播种盖过', () => { + const existing = [asset('business-model', 1), asset('business-model', 5)]; + expect(shouldSeedAsset(existing, 'business-model', 2)).toBe(false); + expect(shouldSeedAsset(existing, 'business-model', 6)).toBe(true); + }); + + it('库里没有该类资产时从 v1 起播', () => { + expect(shouldSeedAsset([], 'dmn', 1)).toBe(true); + }); +}); diff --git a/apps/api/src/modules/activity-registry/internal/adapter/registry-store.ts b/apps/api/src/modules/activity-registry/internal/adapter/registry-store.ts index 8c6c216..77972f7 100644 --- a/apps/api/src/modules/activity-registry/internal/adapter/registry-store.ts +++ b/apps/api/src/modules/activity-registry/internal/adapter/registry-store.ts @@ -12,7 +12,7 @@ import { activityAssetTable, activityPackageTable, assetIncrementTable } from '. export function createRegistryStore(db: Db): RegistryStore { return { async savePackage(manifest: ActivityManifest, artifact: string) { - await db + const inserted = await db .insert(activityPackageTable) .values({ activityId: manifest.identity.activityId, @@ -25,7 +25,10 @@ export function createRegistryStore(db: Db): RegistryStore { state: 'active', enabled: true, }) - .onConflictDoNothing(); + .onConflictDoNothing() + .returning({ version: activityPackageTable.version }); + // 回报是否**真的新增了这一版**:重复安装同版不该再播一次资产 + return inserted.length > 0; }, async listPackages(activityId) { diff --git a/apps/api/src/modules/activity-registry/internal/usecase/registry-service.ts b/apps/api/src/modules/activity-registry/internal/usecase/registry-service.ts index c4db8f8..362df11 100644 --- a/apps/api/src/modules/activity-registry/internal/usecase/registry-service.ts +++ b/apps/api/src/modules/activity-registry/internal/usecase/registry-service.ts @@ -89,22 +89,34 @@ export function createRegistryService(deps: RegistryServiceDeps): ActivityRegist if (!verdict.accepted) throw new InstallRejectedError(verdict); // 安装恒为新增、版本并存;在途任务按其启动时版本跑完 - await deps.store.savePackage(manifest, artifact); + const isNewPackageVersion = await deps.store.savePackage(manifest, artifact); - // 资产初始版随包播种 —— **不覆盖已有更高的资产版** + /* + * 资产初始版随包播种。 + * + * 播的是**下一个**资产版,不是恒定的 v1:包版本与资产版本是两条序列, + * 但新包携带的初始资产同样过了安装两审,理应可用 —— 若恒播 v1, + * 就只有第一个包版本的资产此生有效,之后再装什么都跑旧的。 + * 「不覆盖已有更高的资产版」仍然成立:进化管线放行出来的版本号更高,播种不会盖过它。 + * 同版重复安装不再播种;在途任务按启动时版本跑完,不受影响。 + */ const existing = await deps.store.listAssets(manifest.identity.activityId); const now = deps.clock.now().toISOString(); - for (const kind of SEED_KINDS) { - if (!shouldSeedAsset(existing, kind, 1)) continue; - const asset: AssetVersion = { - activityId: manifest.identity.activityId, - kind, - version: 1, - effective: true, - source: 'package-seed', - createdAt: now, - }; - await deps.store.saveAsset(asset, serializeAsset(manifest, kind)); + if (isNewPackageVersion) { + for (const kind of SEED_KINDS) { + const highest = existing.filter((a) => a.kind === kind).reduce((max, a) => Math.max(max, a.version), 0); + const seedVersion = highest + 1; + if (!shouldSeedAsset(existing, kind, seedVersion)) continue; + const asset: AssetVersion = { + activityId: manifest.identity.activityId, + kind, + version: seedVersion, + effective: true, + source: 'package-seed', + createdAt: now, + }; + await deps.store.saveAsset(asset, serializeAsset(manifest, kind)); + } } return verdict; diff --git a/apps/api/src/modules/activity-registry/port.ts b/apps/api/src/modules/activity-registry/port.ts index a532b76..1bd4497 100644 --- a/apps/api/src/modules/activity-registry/port.ts +++ b/apps/api/src/modules/activity-registry/port.ts @@ -8,7 +8,8 @@ export type Clock = { now(): Date }; export type IdGenerator = { next(): string }; export type RegistryStore = { - savePackage(manifest: ActivityManifest, artifact: string): Promise; + /** 返回是否新增了这一包版本(同版重复安装返回 false) */ + savePackage(manifest: ActivityManifest, artifact: string): Promise; listPackages(activityId?: string): Promise; manifest(activityId: string, packageVersion?: string): Promise; artifact(activityId: string, packageVersion: string): Promise; diff --git a/apps/api/src/modules/flow-engine/__tests__/step-sequence.test.ts b/apps/api/src/modules/flow-engine/__tests__/step-sequence.test.ts new file mode 100644 index 0000000..2ca9e76 --- /dev/null +++ b/apps/api/src/modules/flow-engine/__tests__/step-sequence.test.ts @@ -0,0 +1,105 @@ +/** flow 步序:声明校验 / 失败走向 / 兜底出参 —— domain 测试零 mock(AM 12.3) */ +import { describe, expect, it } from 'vitest'; +import { flowStepSchema } from '@contracts'; +import type { FlowStep } from '@contracts'; +import { + checkDeclaration, + fallbackFields, + failureAction, + initialPosition, + lastStepIsAcceptance, + markStep, + parallelGroupComplete, +} from '../internal/domain/step-sequence.ts'; + +const llmStep = (over: Record = {}): FlowStep => + flowStepSchema.parse({ + kind: 'llm', + name: 'S3', + label: 'S3 逐条解析归一', + input: { rfqRef: 'rfqRef' }, + output: { domain: 'quote_intake', object: 'record', fields: { pn: 'pn', brand: 'brand', eau: 'eau' } }, + model: 'quote-extractor', + template: '解析 {{rfqRef}}', + outputSchema: { type: 'object' }, + failure: { retry: { times: 2, backoff: 'PT30S' }, fallback: { kind: 'human', workOrder: 'execution' } }, + ...over, + }); + +describe('人兜底:出参契约属于步,不属于执行者', () => { + it('兜底卡问的字段 = 这一步声明的出参 —— 人填完能落进作用域', () => { + // 若只给一个笼统的「结果」框,下游按 output.fields 取值会取空,在下一步崩 + expect(fallbackFields(llmStep())).toEqual(['pn', 'brand', 'eau']); + }); + + it('无出参声明的步不凭空造字段', () => { + const step = flowStepSchema.parse({ + kind: 'llm', + name: 'S9', + label: '只说不写的一步', + input: {}, + model: 'm', + template: 't', + outputSchema: { type: 'object' }, + failure: { retry: { times: 0, backoff: 'PT1S' }, fallback: { kind: 'blocked' } }, + }); + expect(fallbackFields(step)).toEqual([]); + }); +}); + +describe('失败走向声明给定,不临场发明', () => { + it('重试次数未用尽 → retry', () => { + expect(failureAction(llmStep(), 1).kind).toBe('retry'); + }); + + it('重试用尽 → 按声明的 fallback 转人', () => { + const action = failureAction(llmStep(), 2); + expect(action.kind).toBe('human'); + }); + + it('声明 blocked 的步不转人 —— 走向是声明的,不是猜的', () => { + const step = llmStep({ failure: { retry: { times: 0, backoff: 'PT1S' }, fallback: { kind: 'blocked' } } }); + expect(failureAction(step, 0).kind).toBe('blocked'); + }); +}); + +describe('步序位置', () => { + it('初始位置每步待跑', () => { + const position = initialPosition([llmStep(), llmStep({ name: 'S4' })]); + expect(position.index).toBe(0); + expect(position.steps.map((s) => s.state)).toEqual(['pending', 'pending']); + }); + + it('markStep 只改那一步,其余原样', () => { + const position = initialPosition([llmStep(), llmStep({ name: 'S4' })]); + const next = markStep(position, 1, { state: 'done' }); + expect(next.steps.map((s) => s.state)).toEqual(['pending', 'done']); + }); + + it('并行组要全完成才算完成', () => { + const position = markStep(initialPosition([llmStep(), llmStep({ name: 'S4' })]), 0, { state: 'done' }); + expect(parallelGroupComplete(position, ['S3', 'S4'])).toBe(false); + expect(parallelGroupComplete(markStep(position, 1, { state: 'done' }), ['S3', 'S4'])).toBe(true); + }); +}); + +describe('声明校验', () => { + it('步名重复即拒', () => { + expect(checkDeclaration([llmStep(), llmStep()]).length).toBeGreaterThan(0); + }); + + it('末步是验收工单可辨', () => { + const acceptance: FlowStep = flowStepSchema.parse({ + kind: 'human', + name: 'S6', + label: '验收', + input: {}, + workOrder: 'acceptance', + card: {}, + deadline: 'P1D', + failure: { retry: { times: 0, backoff: 'PT1S' }, fallback: { kind: 'blocked' } }, + }); + expect(lastStepIsAcceptance([llmStep(), acceptance])).toBe(true); + expect(lastStepIsAcceptance([llmStep()])).toBe(false); + }); +}); diff --git a/apps/api/src/modules/flow-engine/internal/domain/step-sequence.ts b/apps/api/src/modules/flow-engine/internal/domain/step-sequence.ts index 80d7a97..bef86ae 100644 --- a/apps/api/src/modules/flow-engine/internal/domain/step-sequence.ts +++ b/apps/api/src/modules/flow-engine/internal/domain/step-sequence.ts @@ -17,6 +17,8 @@ export type StepRuntime = { readonly attempt: number; readonly output?: Record; readonly error?: string; + /** 验收工单的放行裁决:pass / 驳回类别 —— 末步据此定终局 */ + readonly verdict?: string; }; export type FlowPosition = { @@ -121,3 +123,25 @@ export function parallelGroupComplete(position: FlowPosition, members: readonly return runtime?.state === 'done' || runtime?.state === 'skipped'; }); } + +/** + * 人兜底时要回填的字段 —— 取自这一步**声明的出参**。 + * + * 出参契约属于**步**,不属于执行者:同一步无论由 LLM、工具还是人做完, + * 落进作用域的形状必须一致。若兜底卡只给一个笼统的「结果」文本框, + * 人填了也落不进去 —— 下游按 `output.fields` 取值,取不到就在下一步崩。 + * + * 返回键名数组,与 human 步的 `card.fields` 同形:卡的渲染只有一套。 + */ +export function fallbackFields(step: FlowStep): readonly string[] { + return step.output === undefined ? [] : Object.values(step.output.fields); +} + +/** + * 末步的放行裁决是否「通过」—— **完成只出自放行通过**(OS 05)。 + * 驳回(重做 / 迭代)不是完成:谱虽走完,任务不得进 released。 + */ +export function lastVerdictPassed(position: FlowPosition): boolean { + const last = position.steps[position.steps.length - 1]; + return last?.verdict === 'pass'; +} diff --git a/apps/api/src/modules/flow-engine/internal/usecase/flow-runner.ts b/apps/api/src/modules/flow-engine/internal/usecase/flow-runner.ts index 4cf2f1f..6610196 100644 --- a/apps/api/src/modules/flow-engine/internal/usecase/flow-runner.ts +++ b/apps/api/src/modules/flow-engine/internal/usecase/flow-runner.ts @@ -19,6 +19,9 @@ import type { import type { FlowPosition } from '../domain/step-sequence.ts'; import { checkDeclaration, + fallbackFields, + lastStepIsAcceptance, + lastVerdictPassed, failureAction, initialPosition, markStep, @@ -52,15 +55,29 @@ export function createFlowRunner(deps: FlowRunnerDeps): FlowEngineService { for (const [field, path] of Object.entries(step.output.fields)) { patch[field] = output[path] ?? output[field]; } + // 写哪一行:主键取自出参,取不到再回作用域(起单入参往往就带着主键) + const scope = await deps.data.view(taskId); + const key: Record = {}; + for (const [column, path] of Object.entries(step.output.key)) { + const value = output[path] ?? scope[path]; + // 主键值只能是标量;取不到就不填,让台账的非空约束在合并时如实报出来 + if (typeof value === 'string' || typeof value === 'number') key[column] = value; + } await deps.data.draftWrite({ taskId, domain: step.output.domain, object: step.output.object, - key: {}, + key, patch, }); } + /** 回传协议 → 出参:取 `values`;非回传形状的人事件按原样当出参 */ + function replyValuesOf(payload: Record): Record { + const values = payload['values']; + return typeof values === 'object' && values !== null ? (values as Record) : payload; + } + function inputOf(step: FlowStep, scope: Record): Record { const out: Record = {}; for (const [key, path] of Object.entries(step.input)) { @@ -188,7 +205,13 @@ export function createFlowRunner(deps: FlowRunnerDeps): FlowEngineService { ownerId: context.ownerId, humanCallCap: context.humanCallCap, workOrder: action.workOrder, - card: { title: `步「${step.label}」重试仍败,转人兜底`, reason: error }, + card: { + title: `步「${step.label}」重试仍败,转人兜底`, + reason: error, + // 兜底的人要交的东西,和这一步**本来该产出的东西**同形: + // 出参契约是步的契约,不因换谁来做而变(统一执行契约)。 + fields: fallbackFields(step), + }, }); await deps.listeners.register({ taskId, @@ -231,8 +254,10 @@ export function createFlowRunner(deps: FlowRunnerDeps): FlowEngineService { switch (action.kind) { case 'finished': { - // 末步是验收工单时,终态由其裁决给出;此处只报「谱走完」 - return { position, status: 'finished', released: false }; + // 末步是验收工单时,终态由其**裁决**给出 —— 完成只出自放行通过。 + // 裁决记在末步的 runtime 上(resume 时落笔),谱走完在这里读出来。 + const released = lastStepIsAcceptance(model.steps) && lastVerdictPassed(position); + return { position, status: 'finished', released }; } case 'skip': { await deps.memory.append({ @@ -268,10 +293,14 @@ export function createFlowRunner(deps: FlowRunnerDeps): FlowEngineService { title: `事件到达,步 ${index + 1} 完成条件满足(${event.kind})`, detail: { ref: event.ref }, }); + // 放行裁决落笔在这一步的运行时上:谱走完时据此定终局 + const verdict = event.kind === 'human' ? String(event.payload['verdict'] ?? '') : ''; if (step !== undefined && event.kind === 'human') { - await writeOutput(taskId, step, event.payload); + // 人事件的载荷是**回传协议**的形状(`{mode, workOrderId, values}`), + // 出参在 `values` 里;直接把整个载荷当出参写,落进去的会是协议壳而不是答案。 + await writeOutput(taskId, step, replyValuesOf(event.payload)); } - const next = markStep(position, index, { state: 'done' }); + const next = markStep(position, index, verdict === '' ? { state: 'done' } : { state: 'done', verdict }); await deps.state.save(taskId, model, next); return { position: next, status: 'running' }; }, diff --git a/apps/api/src/modules/task/contract.ts b/apps/api/src/modules/task/contract.ts index 3a2a462..4c83579 100644 --- a/apps/api/src/modules/task/contract.ts +++ b/apps/api/src/modules/task/contract.ts @@ -55,6 +55,8 @@ export type Task = { readonly reason?: string; /** 装载信封:包版本 + 各资产生效版 —— 行为可归因 */ readonly envelope: Readonly>; + /** 起单入参:契约的 data —— 引擎求值的作用域底料 */ + readonly data: Readonly>; readonly caps: BudgetCaps; readonly used: Consumption; readonly startedAt: string; diff --git a/apps/api/src/modules/task/internal/adapter/task-schema.ts b/apps/api/src/modules/task/internal/adapter/task-schema.ts index b34b268..d15179c 100644 --- a/apps/api/src/modules/task/internal/adapter/task-schema.ts +++ b/apps/api/src/modules/task/internal/adapter/task-schema.ts @@ -21,6 +21,8 @@ export const taskTable = sys.table( reason: text('reason'), /** 装载信封:包版本 + 各资产生效版 —— 行为可归因 */ envelope: jsonb('envelope').notNull(), + /** 起单入参 —— 步序声明按字段名读它;不存下来,断点续跑后作用域就空了 */ + inputData: jsonb('input_data').notNull().default({}), caps: jsonb('caps').notNull(), usedSteps: integer('used_steps').notNull().default(0), usedTokens: integer('used_tokens').notNull().default(0), diff --git a/apps/api/src/modules/task/internal/adapter/task-store.ts b/apps/api/src/modules/task/internal/adapter/task-store.ts index a323c9e..a7c022c 100644 --- a/apps/api/src/modules/task/internal/adapter/task-store.ts +++ b/apps/api/src/modules/task/internal/adapter/task-store.ts @@ -24,6 +24,7 @@ function toTask(row: Row): Task { ...(row.terminal === null ? {} : { terminal: row.terminal as TerminalState }), ...(row.reason === null ? {} : { reason: row.reason }), envelope: row.envelope as Record, + data: row.inputData as Record, caps: row.caps as BudgetCaps, used: { steps: row.usedSteps, @@ -49,6 +50,7 @@ export function createTaskStore(db: Db): TaskStore { title: task.title, runtime: task.runtime, envelope: task.envelope, + inputData: task.data, caps: task.caps, startedAt: new Date(task.startedAt), }); diff --git a/apps/api/src/modules/task/internal/usecase/task-service.ts b/apps/api/src/modules/task/internal/usecase/task-service.ts index ecf961e..3ba1d62 100644 --- a/apps/api/src/modules/task/internal/usecase/task-service.ts +++ b/apps/api/src/modules/task/internal/usecase/task-service.ts @@ -12,6 +12,7 @@ import type { Clock, IdGenerator, InteractionPhaseSetter, + LandingGateway, ListenerProbe, MemoryRecorder, ParadigmEngine, @@ -31,6 +32,8 @@ export type TaskServiceDeps = { /** 范式引擎按 paradigm 分派 —— 对上层不可区分 */ readonly engines: Readonly>; readonly memory: MemoryRecorder; + /** 落地收敛 —— 放行通过的那一刻才碰事实 */ + readonly landing: LandingGateway; readonly listeners: ListenerProbe; readonly persistence: PersistenceProbe; readonly events: TaskEventPublisher; @@ -80,6 +83,7 @@ export function createTaskService(deps: TaskServiceDeps): TaskService { ownerId: input.ownerId, title: input.title, runtime: 'running', + data: input.data, envelope: loaded.envelope, caps: loaded.caps, used: ZERO_CONSUMPTION, @@ -147,6 +151,38 @@ export function createTaskService(deps: TaskServiceDeps): TaskService { const finished = await this.finish(taskId, { kind: 'blocked', reason: outcome.reason ?? '无法推进' }); return { task: finished, verdict, progressed: true }; } + + /* + * 谱走完了。**完成只出自放行通过**(OS 05): + * · 放行通过 → 落地收敛(草稿合并 + 动作执行 → 收敛才发射输出口)→ released; + * · 未放行(还没裁 / 驳回)→ 不是完成,也**不再空推** —— 挂起等人, + * 否则推进器会一拍一拍把预算烧到 exhausted,把「没人裁」误报成「跑爆了」。 + */ + if (outcome.status === 'finished') { + if (outcome.released !== true) { + const waiting = await deps.store.update(taskId, { runtime: 'awaiting-human' }); + await deps.interaction.setPhase(taskId, 'awaiting-event'); + return { task: waiting, verdict, progressed: false }; + } + const landed = await deps.landing.land({ + taskId, + ownerId: task.ownerId, + humanCallCap: task.caps.humanCalls, + }); + if (!landed.converged) { + // 落地未收敛:输出口不得发射,任务受阻待人(落地失败卡已发) + const blocked = await this.finish(taskId, { kind: 'blocked', reason: '落地未收敛,输出口未发射' }); + return { task: blocked, verdict, progressed: true }; + } + await deps.memory.append({ + taskId, + kind: 'engine-transition', + title: `落地收敛:合并 ${landed.ledgerRowsMerged} 行,发射输出口 ${landed.emittedPorts.join('、') || '—'}`, + }); + const finished = await this.finish(taskId, { kind: 'released' }); + return { task: finished, verdict, progressed: true }; + } + return { task: updated, verdict, progressed: true }; }, diff --git a/apps/api/src/modules/task/port.ts b/apps/api/src/modules/task/port.ts index a4f8798..4c46242 100644 --- a/apps/api/src/modules/task/port.ts +++ b/apps/api/src/modules/task/port.ts @@ -38,11 +38,30 @@ export type SchemaValidator = { /** 范式引擎 —— loop / flow 在此汇流;对上层不可区分(§9 双层边界) */ export type ParadigmEngine = { load(taskId: TaskId, model: unknown): Promise; - advance(taskId: TaskId): Promise<{ status: 'running' | 'awaiting' | 'submitted' | 'finished' | 'blocked'; reason?: string }>; + advance(taskId: TaskId): Promise; onEvent( taskId: TaskId, event: { kind: 'data' | 'time' | 'human' | 'control'; ref: string; payload: Record }, - ): Promise<{ status: 'running' | 'awaiting' | 'submitted' | 'finished' | 'blocked'; reason?: string }>; + ): Promise; +}; + +export type EngineOutcome = { + readonly status: 'running' | 'awaiting' | 'submitted' | 'finished' | 'blocked'; + readonly reason?: string; + /** 谱走完时:放行是否通过 —— **完成只出自放行通过**,引擎不自行宣布完成 */ + readonly released?: boolean; +}; + +/** + * 落地收敛 —— app 注入 `release`(L4)。 + * 放行通过的那一刻才落地:草稿合并入正式版、动作意向执行、收敛后才发射输出口。 + */ +export type LandingGateway = { + land(input: { taskId: TaskId; ownerId: StaffId; humanCallCap: number }): Promise<{ + converged: boolean; + emittedPorts: readonly string[]; + ledgerRowsMerged: number; + }>; }; export type MemoryRecorder = { diff --git a/db/migrations/0009_cute_bromley.sql b/db/migrations/0009_cute_bromley.sql new file mode 100644 index 0000000..55bb79c --- /dev/null +++ b/db/migrations/0009_cute_bromley.sql @@ -0,0 +1 @@ +ALTER TABLE "sys"."task" ADD COLUMN "input_data" jsonb DEFAULT '{}'::jsonb NOT NULL; \ No newline at end of file diff --git a/db/migrations/meta/0009_snapshot.json b/db/migrations/meta/0009_snapshot.json new file mode 100644 index 0000000..7271795 --- /dev/null +++ b/db/migrations/meta/0009_snapshot.json @@ -0,0 +1,3267 @@ +{ + "id": "2e04c7a8-2318-4186-b651-35528ef150b0", + "prevId": "c7da678d-52d1-42f1-8682-1a7dfac74945", + "version": "7", + "dialect": "postgresql", + "tables": { + "sys.activity_asset": { + "name": "activity_asset", + "schema": "sys", + "columns": { + "activity_id": { + "name": "activity_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "effective": { + "name": "effective", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "activity_asset_effective_key": { + "name": "activity_asset_effective_key", + "columns": [ + { + "expression": "activity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"sys\".\"activity_asset\".\"effective\"", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "activity_asset_activity_id_kind_version_pk": { + "name": "activity_asset_activity_id_kind_version_pk", + "columns": [ + "activity_id", + "kind", + "version" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "sys.activity_package": { + "name": "activity_package", + "schema": "sys", + "columns": { + "activity_id": { + "name": "activity_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "paradigm": { + "name": "paradigm", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "manifest": { + "name": "manifest", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "artifact": { + "name": "artifact", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "installed_at": { + "name": "installed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "activity_package_category_idx": { + "name": "activity_package_category_idx", + "columns": [ + { + "expression": "category", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "paradigm", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "activity_package_activity_id_version_pk": { + "name": "activity_package_activity_id_version_pk", + "columns": [ + "activity_id", + "version" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "sys.asset_increment": { + "name": "asset_increment", + "schema": "sys", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "activity_id": { + "name": "activity_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "diff": { + "name": "diff", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "evidence_entry_ids": { + "name": "evidence_entry_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'candidate'" + }, + "replay_report": { + "name": "replay_report", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "released_by": { + "name": "released_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "asset_increment_activity_idx": { + "name": "asset_increment_activity_idx", + "columns": [ + { + "expression": "activity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "sys.channel_instance": { + "name": "channel_instance", + "schema": "sys", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "address": { + "name": "address", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "extract": { + "name": "extract", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "mapping": { + "name": "mapping", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "fallback_position": { + "name": "fallback_position", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "address_window_ms": { + "name": "address_window_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 604800000 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "sys.outbound_mail": { + "name": "outbound_mail", + "schema": "sys", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "thread_id": { + "name": "thread_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "from_addr": { + "name": "from_addr", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "to_addrs": { + "name": "to_addrs", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "cc_addrs": { + "name": "cc_addrs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "attachments": { + "name": "attachments", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "sent_at": { + "name": "sent_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "sys.event_consumption": { + "name": "event_consumption", + "schema": "sys", + "columns": { + "event_id": { + "name": "event_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "consumer": { + "name": "consumer", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "consumed_at": { + "name": "consumed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "ok": { + "name": "ok", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + } + }, + "indexes": { + "consumption_key": { + "name": "consumption_key", + "columns": [ + { + "expression": "event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "consumer", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "event_consumption_event_id_consumer_pk": { + "name": "event_consumption_event_id_consumer_pk", + "columns": [ + "event_id", + "consumer" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "sys.event_delivery": { + "name": "event_delivery", + "schema": "sys", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "event_id": { + "name": "event_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_kind": { + "name": "target_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "attempt": { + "name": "attempt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "next_attempt_at": { + "name": "next_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "delivery_pending_idx": { + "name": "delivery_pending_idx", + "columns": [ + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_attempt_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "delivery_target_idx": { + "name": "delivery_target_idx", + "columns": [ + { + "expression": "target_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "event_delivery_event_id_event_id_fk": { + "name": "event_delivery_event_id_event_id_fk", + "tableFrom": "event_delivery", + "tableTo": "event", + "schemaTo": "sys", + "columnsFrom": [ + "event_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "sys.event": { + "name": "event", + "schema": "sys", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "correlation": { + "name": "correlation", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "occurred_at": { + "name": "occurred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "trace": { + "name": "trace", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "target_kind": { + "name": "target_kind", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "event_target_idx": { + "name": "event_target_idx", + "columns": [ + { + "expression": "target_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "event_occurred_idx": { + "name": "event_occurred_idx", + "columns": [ + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "sys.listener": { + "name": "listener", + "schema": "sys", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "task_id": { + "name": "task_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "origin": { + "name": "origin", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key_filter": { + "name": "key_filter", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "condition": { + "name": "condition", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "semantic": { + "name": "semantic", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deadline": { + "name": "deadline", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "escalation": { + "name": "escalation", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "registered_at": { + "name": "registered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'armed'" + } + }, + "indexes": { + "listener_armed_idx": { + "name": "listener_armed_idx", + "columns": [ + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "listener_task_idx": { + "name": "listener_task_idx", + "columns": [ + { + "expression": "task_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "sys.outbound_ref": { + "name": "outbound_ref", + "schema": "sys", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "task_id": { + "name": "task_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "thread_id": { + "name": "thread_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "address": { + "name": "address", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sent_at": { + "name": "sent_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "outbound_thread_idx": { + "name": "outbound_thread_idx", + "columns": [ + { + "expression": "thread_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "outbound_addr_idx": { + "name": "outbound_addr_idx", + "columns": [ + { + "expression": "address", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sent_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "sys.flow_state": { + "name": "flow_state", + "schema": "sys", + "columns": { + "task_id": { + "name": "task_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "model": { + "name": "model", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "position": { + "name": "position", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "sys.chat_card": { + "name": "chat_card", + "schema": "sys", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "task_id": { + "name": "task_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "recipient_id": { + "name": "recipient_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "at": { + "name": "at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "superseded_by": { + "name": "superseded_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rejected_reason": { + "name": "rejected_reason", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "chat_card_task_idx": { + "name": "chat_card_task_idx", + "columns": [ + { + "expression": "task_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_card_recipient_idx": { + "name": "chat_card_recipient_idx", + "columns": [ + { + "expression": "recipient_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "sys.work_order": { + "name": "work_order", + "schema": "sys", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "task_id": { + "name": "task_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "recipient_id": { + "name": "recipient_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "at": { + "name": "at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "done": { + "name": "done", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "work_order_task_idx": { + "name": "work_order_task_idx", + "columns": [ + { + "expression": "task_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "work_order_recipient_idx": { + "name": "work_order_recipient_idx", + "columns": [ + { + "expression": "recipient_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "sys.chat_message": { + "name": "chat_message", + "schema": "sys", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "task_id": { + "name": "task_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "at": { + "name": "at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "chat_message_task_idx": { + "name": "chat_message_task_idx", + "columns": [ + { + "expression": "task_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "sys.task_view": { + "name": "task_view", + "schema": "sys", + "columns": { + "task_id": { + "name": "task_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "blocks": { + "name": "blocks", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "html": { + "name": "html", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "phase": { + "name": "phase", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'activating'" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "sys.llm_instance": { + "name": "llm_instance", + "schema": "sys", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "base_url": { + "name": "base_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "api_key_cipher": { + "name": "api_key_cipher", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "system_use": { + "name": "system_use", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "llm_instance_name_key": { + "name": "llm_instance_name_key", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "sys.replay_model": { + "name": "replay_model", + "schema": "sys", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "call": { + "name": "call", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "result": { + "name": "result", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "recorded_at": { + "name": "recorded_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "replay_model_recorded_idx": { + "name": "replay_model_recorded_idx", + "columns": [ + { + "expression": "recorded_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "sys.loop_state": { + "name": "loop_state", + "schema": "sys", + "columns": { + "task_id": { + "name": "task_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "model": { + "name": "model", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "parsed": { + "name": "parsed", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "runtime": { + "name": "runtime", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "sys.activity_memory": { + "name": "activity_memory", + "schema": "sys", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "activity_id": { + "name": "activity_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "layer": { + "name": "layer", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "statement": { + "name": "statement", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "evidence": { + "name": "evidence", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "increment": { + "name": "increment", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "pruned_at": { + "name": "pruned_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "active": { + "name": "active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + } + }, + "indexes": { + "activity_memory_layer_idx": { + "name": "activity_memory_layer_idx", + "columns": [ + { + "expression": "activity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "layer", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "sys.task_memory_seal": { + "name": "task_memory_seal", + "schema": "sys", + "columns": { + "task_id": { + "name": "task_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "sealed_at": { + "name": "sealed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "sys.task_memory": { + "name": "task_memory", + "schema": "sys", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "task_id": { + "name": "task_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "seq": { + "name": "seq", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "at": { + "name": "at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "side": { + "name": "side", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "detail": { + "name": "detail", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "stamp": { + "name": "stamp", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "governance": { + "name": "governance", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "links_to": { + "name": "links_to", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "event_ref": { + "name": "event_ref", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "task_memory_seq_key": { + "name": "task_memory_seq_key", + "columns": [ + { + "expression": "task_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "seq", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "task_memory_task_idx": { + "name": "task_memory_task_idx", + "columns": [ + { + "expression": "task_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "sys.system_message": { + "name": "system_message", + "schema": "sys", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "staff_id": { + "name": "staff_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "position_id": { + "name": "position_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "at": { + "name": "at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "read_at": { + "name": "read_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "card": { + "name": "card", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "answer": { + "name": "answer", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "event_ref": { + "name": "event_ref", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "message_staff_idx": { + "name": "message_staff_idx", + "columns": [ + { + "expression": "staff_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "message_unread_idx": { + "name": "message_unread_idx", + "columns": [ + { + "expression": "staff_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "read_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "sys.minute_resolution": { + "name": "minute_resolution", + "schema": "sys", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "minute_id": { + "name": "minute_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "text": { + "name": "text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "registered_by_id": { + "name": "registered_by_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "registered_at": { + "name": "registered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "minute_resolution_minute_idx": { + "name": "minute_resolution_minute_idx", + "columns": [ + { + "expression": "minute_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "sys.minute": { + "name": "minute", + "schema": "sys", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "author_id": { + "name": "author_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "participants": { + "name": "participants", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "occurred_on": { + "name": "occurred_on", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "minute_occurred_idx": { + "name": "minute_occurred_idx", + "columns": [ + { + "expression": "occurred_on", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "sys.position_grant": { + "name": "position_grant", + "schema": "sys", + "columns": { + "position_id": { + "name": "position_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "object_key": { + "name": "object_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "object": { + "name": "object", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + } + }, + "indexes": {}, + "foreignKeys": { + "position_grant_position_id_position_id_fk": { + "name": "position_grant_position_id_position_id_fk", + "tableFrom": "position_grant", + "tableTo": "position", + "schemaTo": "sys", + "columnsFrom": [ + "position_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "position_grant_position_id_object_key_action_pk": { + "name": "position_grant_position_id_object_key_action_pk", + "columns": [ + "position_id", + "object_key", + "action" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "sys.position": { + "name": "position", + "schema": "sys", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "note": { + "name": "note", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "position_code_key": { + "name": "position_code_key", + "columns": [ + { + "expression": "code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "sys.staff": { + "name": "staff", + "schema": "sys", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "account": { + "name": "account", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "position_id": { + "name": "position_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "staff_account_key": { + "name": "staff_account_key", + "columns": [ + { + "expression": "account", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "staff_position_idx": { + "name": "staff_position_idx", + "columns": [ + { + "expression": "position_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "staff_position_id_position_id_fk": { + "name": "staff_position_id_position_id_fk", + "tableFrom": "staff", + "tableTo": "position", + "schemaTo": "sys", + "columnsFrom": [ + "position_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "sys.node_instance": { + "name": "node_instance", + "schema": "sys", + "columns": { + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "node_id": { + "name": "node_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "instance": { + "name": "instance", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "running": { + "name": "running", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "waiting": { + "name": "waiting", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "activated": { + "name": "activated", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "terminal": { + "name": "terminal", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "failure_digested": { + "name": "failure_digested", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "task_id": { + "name": "task_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "approval_id": { + "name": "approval_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "node_instance_task_idx": { + "name": "node_instance_task_idx", + "columns": [ + { + "expression": "task_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "node_instance_workflow_id_node_id_instance_pk": { + "name": "node_instance_workflow_id_node_id_instance_pk", + "columns": [ + "workflow_id", + "node_id", + "instance" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "sys.process_package": { + "name": "process_package", + "schema": "sys", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "definition": { + "name": "definition", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "published_at": { + "name": "published_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "process_package_state_idx": { + "name": "process_package_state_idx", + "columns": [ + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "process_package_id_version_pk": { + "name": "process_package_id_version_pk", + "columns": [ + "id", + "version" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "sys.workflow": { + "name": "workflow", + "schema": "sys", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "package_id": { + "name": "package_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "package_version": { + "name": "package_version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "source": { + "name": "source", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "initiation": { + "name": "initiation", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "ports": { + "name": "ports", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "vars": { + "name": "vars", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "workflow_state_idx": { + "name": "workflow_state_idx", + "columns": [ + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "sys.action_intent": { + "name": "action_intent", + "schema": "sys", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "task_id": { + "name": "task_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool": { + "name": "tool", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "input": { + "name": "input", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'drafted'" + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "detail": { + "name": "detail", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "action_intent_task_idx": { + "name": "action_intent_task_idx", + "columns": [ + { + "expression": "task_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "sys.report": { + "name": "report", + "schema": "sys", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "staff_id": { + "name": "staff_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "period": { + "name": "period", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "window_from": { + "name": "window_from", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "window_to": { + "name": "window_to", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "draft": { + "name": "draft", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "note": { + "name": "note", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "submitted_at": { + "name": "submitted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "generated_at": { + "name": "generated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "report_staff_period_idx": { + "name": "report_staff_period_idx", + "columns": [ + { + "expression": "staff_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "period", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_from", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "sys.task": { + "name": "task", + "schema": "sys", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "activity_id": { + "name": "activity_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "paradigm": { + "name": "paradigm", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "node_id": { + "name": "node_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "runtime": { + "name": "runtime", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "terminal": { + "name": "terminal", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "envelope": { + "name": "envelope", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "input_data": { + "name": "input_data", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "caps": { + "name": "caps", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "used_steps": { + "name": "used_steps", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "used_tokens": { + "name": "used_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "used_active_ms": { + "name": "used_active_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "used_human_calls": { + "name": "used_human_calls", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_progress_at": { + "name": "last_progress_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "task_owner_idx": { + "name": "task_owner_idx", + "columns": [ + { + "expression": "owner_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "runtime", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "task_workflow_idx": { + "name": "task_workflow_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "task_terminal_idx": { + "name": "task_terminal_idx", + "columns": [ + { + "expression": "terminal", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "sys.replay_tool": { + "name": "replay_tool", + "schema": "sys", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "call": { + "name": "call", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "result": { + "name": "result", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "recorded_at": { + "name": "recorded_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "sys.tool_allowlist": { + "name": "tool_allowlist", + "schema": "sys", + "columns": { + "activity_id": { + "name": "activity_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool": { + "name": "tool", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "tool_allowlist_activity_id_tool_pk": { + "name": "tool_allowlist_activity_id_tool_pk", + "columns": [ + "activity_id", + "tool" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "sys.tool": { + "name": "tool", + "schema": "sys", + "columns": { + "name": { + "name": "name", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "input": { + "name": "input", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "output": { + "name": "output", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "effect": { + "name": "effect", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "errors": { + "name": "errors", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "idempotent": { + "name": "idempotent", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "timeout_ms": { + "name": "timeout_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 30000 + }, + "retries": { + "name": "retries", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "connection_keys": { + "name": "connection_keys", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "capabilities": { + "name": "capabilities", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "pure": { + "name": "pure", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "owner_activity_id": { + "name": "owner_activity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "connection_cipher": { + "name": "connection_cipher", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "registered_at": { + "name": "registered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_effect_idx": { + "name": "tool_effect_idx", + "columns": [ + { + "expression": "effect", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_owner_idx": { + "name": "tool_owner_idx", + "columns": [ + { + "expression": "owner_activity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "sys.trigger_claim": { + "name": "trigger_claim", + "schema": "sys", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "sys.trigger": { + "name": "trigger", + "schema": "sys", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "process_package_id": { + "name": "process_package_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mapping": { + "name": "mapping", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "spec": { + "name": "spec", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "last_fired_at": { + "name": "last_fired_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "trigger_enabled_idx": { + "name": "trigger_enabled_idx", + "columns": [ + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/db/migrations/meta/_journal.json b/db/migrations/meta/_journal.json index 46cd90b..a96aeca 100644 --- a/db/migrations/meta/_journal.json +++ b/db/migrations/meta/_journal.json @@ -64,6 +64,13 @@ "when": 1785094481010, "tag": "0008_strange_quasar", "breakpoints": true + }, + { + "idx": 9, + "version": "7", + "when": 1785096293279, + "tag": "0009_cute_bromley", + "breakpoints": true } ] } \ No newline at end of file diff --git a/packages/contracts/src/manifest.ts b/packages/contracts/src/manifest.ts index f383062..d9cba18 100644 --- a/packages/contracts/src/manifest.ts +++ b/packages/contracts/src/manifest.ts @@ -93,8 +93,19 @@ const stepBase = { label: z.string().min(1), /** 入参映射:取自任务数据 / 上步出参 */ input: z.record(z.string(), z.string()).default({}), - /** 出参写入:落任务数据(草稿态) */ - output: z.object({ domain: z.string(), object: z.string(), fields: z.record(z.string(), z.string()) }).optional(), + /** + * 出参写入:落任务数据(草稿态)。 + * `key` 指明**写哪一行**(主键列 → 作用域路径)—— 台账对象几乎都有主键, + * 不声明键就只能写出无主键的行,合并进正式版时必被非空约束挡下。 + */ + output: z + .object({ + domain: z.string(), + object: z.string(), + key: z.record(z.string(), z.string()).default({}), + fields: z.record(z.string(), z.string()), + }) + .optional(), /** 条件跳步:任务数据上的确定性谓词(S-FEEL),零 LLM */ when: z.string().optional(), failure: stepFailurePolicySchema,