@yss123/cage-approval 0.2.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +77 -9
  2. package/dist/ai.d.ts +55 -0
  3. package/dist/ai.js +51 -0
  4. package/dist/ai.js.map +1 -0
  5. package/dist/bridge.d.ts +7 -1
  6. package/dist/bridge.js +29 -8
  7. package/dist/bridge.js.map +1 -1
  8. package/dist/deadlines.d.ts +29 -0
  9. package/dist/deadlines.js +72 -0
  10. package/dist/deadlines.js.map +1 -0
  11. package/dist/engine.d.ts +138 -4
  12. package/dist/engine.js +306 -99
  13. package/dist/engine.js.map +1 -1
  14. package/dist/execution.d.ts +41 -0
  15. package/dist/execution.js +85 -0
  16. package/dist/execution.js.map +1 -0
  17. package/dist/http.d.ts +6 -0
  18. package/dist/http.js +83 -12
  19. package/dist/http.js.map +1 -1
  20. package/dist/index.d.ts +11 -1
  21. package/dist/index.js +9 -1
  22. package/dist/index.js.map +1 -1
  23. package/dist/openapi.d.ts +17 -0
  24. package/dist/openapi.js +71 -0
  25. package/dist/openapi.js.map +1 -0
  26. package/dist/operations-schema.d.ts +20 -0
  27. package/dist/operations-schema.js +20 -0
  28. package/dist/operations-schema.js.map +1 -0
  29. package/dist/operations.d.ts +118 -0
  30. package/dist/operations.js +349 -0
  31. package/dist/operations.js.map +1 -0
  32. package/dist/route.d.ts +85 -7
  33. package/dist/route.js +100 -23
  34. package/dist/route.js.map +1 -1
  35. package/dist/selectors.d.ts +8 -0
  36. package/dist/selectors.js +53 -0
  37. package/dist/selectors.js.map +1 -0
  38. package/dist/task-actions.d.ts +94 -0
  39. package/dist/task-actions.js +198 -0
  40. package/dist/task-actions.js.map +1 -0
  41. package/dist/task-stages.d.ts +17 -0
  42. package/dist/task-stages.js +47 -0
  43. package/dist/task-stages.js.map +1 -0
  44. package/dist/types.d.ts +6 -2
  45. package/package.json +2 -2
package/README.md CHANGED
@@ -1,19 +1,87 @@
1
1
  # @yss123/cage-approval
2
2
 
3
- 审批适配接口、桥接与代码内封闭路由(可选)
3
+ Cage 的审批路由与执行包。用代码声明顺序步骤、条件分支、角色/部门负责人选人和会签,提供待办接口、审批历史与结论桥接,也支持 AI 审批步骤。通过、驳回和退回最终调用领域命令,审批引擎不直接修改业务表。
4
4
 
5
- Cage 是通过 TypeScript 声明业务、由框架统一执行权限和数据规则的中后台框架。此包是框架的一部分。
5
+ ## 安装
6
6
 
7
- ## 使用
7
+ 需要 Node.js 24+、PostgreSQL,以及同版本 Cage 框架。在应用 domain 包中安装:
8
8
 
9
9
  ```powershell
10
- pnpm add @yss123/cage-approval
10
+ pnpm add @yss123/cage-approval @yss123/cage-kernel
11
11
  ```
12
12
 
13
- 首次使用建议通过脚手架创建完整项目,所需依赖由模板统一配置。需要 Node.js 24 或更新版本。
13
+ 标准 `@yss123/cage-http` 装配审批接口,worker 消费审批启动/取消事件。只启动 API 不会完成后台事件消费。
14
14
 
15
- - [完整使用说明](https://github.com/shunshunyue/cage/blob/main/docs/使用说明.md)
16
- - [框架源码与范例](https://github.com/shunshunyue/cage)
17
- - [问题反馈](https://github.com/shunshunyue/cage/issues)
15
+ ## 将已有业务接到审批
18
16
 
19
- 许可证:[MIT](https://github.com/shunshunyue/cage/blob/main/LICENSE)。
17
+ 以下是采购模板可用的组合片段。`PurchaseOrder`、事件和结论命令来自业务文件;结论命令须声明正确的状态迁移、process 调用身份及业务硬约束。
18
+
19
+ ```ts
20
+ /** 将领域事件关联到审批生命周期;路由与绑定的 process 必须相同。 */
21
+ import { approval } from '@yss123/cage-kernel';
22
+ import { approver, gt, route, step, when } from '@yss123/cage-approval';
23
+ import {
24
+ PurchaseOrder, OrderSubmitted, OrderCancelled, approve, reject,
25
+ } from './purchase-order.js';
26
+
27
+ /** 提交启动、取消撤回;只允许金额和部门字段用于流程路由。 */
28
+ export const poApproval = approval(PurchaseOrder, {
29
+ process: 'po-approval',
30
+ start: OrderSubmitted,
31
+ cancelOn: [OrderCancelled],
32
+ routable: ['amount', 'deptId'],
33
+ onApproved: approve,
34
+ onRejected: reject,
35
+ });
36
+
37
+ /** 先部门负责人;金额严格大于 50000 元时,增加财务角色的一人审批。 */
38
+ export const poRoute = route('po-approval', [
39
+ step('部门经理审批', approver.deptLeader('deptId')),
40
+ when(
41
+ gt('amount', 50000),
42
+ step('财务审批', approver.role('finance'), { mode: 'any' }),
43
+ ),
44
+ ]);
45
+ ```
46
+
47
+ 将这两个声明经模块入口从 domain 根入口导出,运行 `pnpm exec cage check` 和 `pnpm gen`,审查迁移后执行 `pnpm migrate`。启动 API/worker,在系统管理中配置真实部门负责人、角色与命令权限;通过提交命令触发业务事件后,从 `/admin/` 的审批待办处理。
48
+
49
+ `routable` 是允许路由读取的字段清单,不是权限列表。被流程选为审批人也必须具备实际结论命令能力、数据范围及额度,并满足职责分离;路由选人不会自动授予这些权限。
50
+
51
+ ## 主要入口
52
+
53
+ | 入口 | 用途 |
54
+ | --- | --- |
55
+ | `route`、`step`、`when` | 声明步骤和条件;顺序执行,条件只读取允许路由的字段 |
56
+ | `approver` | 角色、部门负责人等选人规则 |
57
+ | `gt/gte/lt/lte/eq/ne/isIn` | 可被分析的条件表达式 |
58
+ | `InCodeEngine`、`TaskView` | 内置流程执行器及待办视图,通常由 HTTP/worker 装配 |
59
+ | `bindApproval`、`applyOutcome` | 适配器把审批结论桥接回领域命令 |
60
+ | `ApprovalAdapter` 等适配类型 | 对接外部审批实现时遵守的生命周期协议 |
61
+ | `approvalRoutes` | 受认证保护的 HTTP 审批接口装配 |
62
+ | `AiStepOptions`、`approvalRoleContext` | AI 节点配置与自动执行角色上下文 |
63
+ | `TaskActionPolicy`、`TaskActionInput`、`InCodeEngine.act` | 人工节点显式开放转办、委派归还、当前节点并行必签;版本与操作记录共同提交 |
64
+ | `InCodeEngine.taskHistory` | 当前单据授权读者按游标读取人工操作记录 |
65
+ | `approvalOperations()` | 显式启用框架持久站内提醒、申请人催办与已读,复用现有 worker |
66
+
67
+ 领域绑定原语 `approval()` 从 `@yss123/cage-kernel` 导入;不要与本包的路由原语混淆。
68
+
69
+ AI 审批使用同一个 `step` 的 `kind: 'ai'` 选项,配置模型、材料范围、策略、handoff 和预算;结果为 `pass/return/manual`。需要 domain 启用 `@yss123/cage-ai`,退回需声明 `onReturned`,自动结论需显式配置 `automatedBy` 对应角色权限。模型失败不会被当成自动批准。
70
+
71
+ ## 文档
72
+
73
+ 人工节点可设置 `actions: { transfer: true, delegate: true, addSign: true }`,默认关闭并随流程实例保存。
74
+ 委派未归还不能批准;前、后及并行加签在 any/all 模式下都必须全部通过。候选任务先领取再决定,任务操作及启用候选/流转节点的决定须携带 revision。
75
+ 不能把任务管理理解为授予角色能力。升级需执行追加系统迁移,旧实例保持原政策。
76
+
77
+ 通用时效通过 `export const approvalOps = approvalOperations()` 启用。人工 `step` 配置 `sla: { durationMinutes: 1440, remindBeforeMinutes: 60 }`,
78
+ `route` 第三参配置 `reminders: { requesterField: 'createdBy', cooldownMinutes: 15 }`,申请人字段必须进入 `approval.routable`。
79
+ 框架负责持久提醒、去重、当前接收人授权、冷却和已读;Admin 提供“我的申请”和“流程提醒”,业务应用无需自行实现通知表或定时器。
80
+ 节点时限采用自然分钟,加签、转办和领取不重置;尚无工作日历、自动升级、外部通知、并行网关或流程设计器。
81
+
82
+ - [人工审批任务流转、SDK 与边界](https://github.com/shunshunyue/cage/blob/main/docs/ai/approvals.md)
83
+ - [标准采购审批链路](https://github.com/shunshunyue/cage/blob/main/docs/ai/purchase-example.md)
84
+ - [AI 审批三态、转人工和授权](https://github.com/shunshunyue/cage/blob/main/docs/ai/intelligence.md)
85
+ - [采购审批完整源码](https://github.com/shunshunyue/cage/blob/main/examples/purchase-admin/packages/domain/src/modules/purchase/approvals.ts)
86
+
87
+ 源码与反馈:[GitHub](https://github.com/shunshunyue/cage)。许可证:[MIT](https://github.com/shunshunyue/cage/blob/main/LICENSE)。
package/dist/ai.d.ts ADDED
@@ -0,0 +1,55 @@
1
+ /**
2
+ * AI 审批的声明协议、受限角色上下文与前向迁移。模型循环在 cage-ai 中执行,
3
+ * 此模块不直接调用模型、不创建另一套流程;AI 结果仍进入 InCodeEngine 的当前步骤。
4
+ */
5
+ import { type Ctx, type Kernel } from '@yss123/cage-kernel';
6
+ import type { Approver } from './route.js';
7
+ /** AI 是步骤执行类型,handoff 仅指定本步骤的接管人,不嵌入第二条流程。 */
8
+ export interface AiStepOptions {
9
+ kind: 'ai';
10
+ /** 应用 AI 配置中的模型名,不在业务声明中保存供应商密钥。 */
11
+ model: string;
12
+ /** 节点审核政策;表达判定要求,不赋予超出自动角色的工具或数据权限。 */
13
+ instructions: string;
14
+ /** 允许取证的字段与已注册只读查询,字段须公开且实际访问仍受角色授权约束。 */
15
+ context: {
16
+ fields: readonly string[];
17
+ queries?: readonly string[];
18
+ };
19
+ /** 当前步骤转人工时的选人策略;不是另起一条无关审批流程。 */
20
+ handoff: Approver;
21
+ /** 单次执行预算:步骤数、工具调用数、token 数和毫秒超时,均应为正整数。 */
22
+ limits?: {
23
+ maxSteps?: number;
24
+ maxToolCalls?: number;
25
+ tokenBudget?: number;
26
+ timeoutMs?: number;
27
+ };
28
+ /** 模型/工具失败后的处理政策;故障不等同于正常通过。 */
29
+ onFailure?: 'manual' | 'failed';
30
+ }
31
+ export interface AiStep extends AiStepOptions {
32
+ title: string;
33
+ }
34
+ /** 三态审核结果;pass 只通过本节点,return 走领域退回命令,manual 生成本节点人工待办。 */
35
+ export interface AiDecision {
36
+ decision: 'pass' | 'return' | 'manual';
37
+ reason: string;
38
+ suggestions: string[];
39
+ evidence: {
40
+ source: string;
41
+ detail: string;
42
+ }[];
43
+ }
44
+ /** 角色授权只赋予流程服务主体;不借用用户身份,也不继承 systemCtx 的全部范围。 */
45
+ /**
46
+ * 每次从当前租户的 active 角色解析能力/范围/明文权限,不缓存旧授权、不借用真人身份。
47
+ * 角色缺失或未配置立即 FORBIDDEN;因为没有部门上下文,范围政策不能假定自动主体属于某个部门。
48
+ */
49
+ export declare function approvalRoleContext(kernel: Kernel, process: string, tenantId: string): Promise<Ctx>;
50
+ /** 追加迁移,保留原 FLOW_DDL 的已发布内容。 */
51
+ /** 节点记录保存针对的提交版本、run 归属、判定及可见性证据;同实例/步骤唯一,防重复占位。 */
52
+ export declare const APPROVAL_AI_MIGRATION: {
53
+ name: string;
54
+ sql: string;
55
+ };
package/dist/ai.js ADDED
@@ -0,0 +1,51 @@
1
+ /**
2
+ * AI 审批的声明协议、受限角色上下文与前向迁移。模型循环在 cage-ai 中执行,
3
+ * 此模块不直接调用模型、不创建另一套流程;AI 结果仍进入 InCodeEngine 的当前步骤。
4
+ */
5
+ import { cageError, mergeCaps, mergeScopes, unmaskFrom } from '@yss123/cage-kernel';
6
+ /** 角色授权只赋予流程服务主体;不借用用户身份,也不继承 systemCtx 的全部范围。 */
7
+ /**
8
+ * 每次从当前租户的 active 角色解析能力/范围/明文权限,不缓存旧授权、不借用真人身份。
9
+ * 角色缺失或未配置立即 FORBIDDEN;因为没有部门上下文,范围政策不能假定自动主体属于某个部门。
10
+ */
11
+ export async function approvalRoleContext(kernel, process, tenantId) {
12
+ const binding = kernel.registry.approvalByProcess(process)?.a;
13
+ if (!binding?.automatedBy)
14
+ throw cageError('FORBIDDEN', { reason: '审批未配置自动执行角色' });
15
+ const role = await kernel.app.selectFrom('sys.role').select(['capabilities', 'scopes']).where('tenant_id', '=', tenantId)
16
+ .where('code', '=', binding.automatedBy).where('status', '=', 'active').executeTakeFirst();
17
+ if (!role)
18
+ throw cageError('FORBIDDEN', { reason: '自动执行角色不存在或已禁用' });
19
+ const capabilities = mergeCaps([role]);
20
+ return { actor: { kind: 'process', id: `process:${process}:role:${binding.automatedBy}` }, userId: null, tenantId,
21
+ deptId: null, deptPath: null, capabilities, scopes: mergeScopes([role]), unmask: unmaskFrom(capabilities), source: 'process' };
22
+ }
23
+ /** 追加迁移,保留原 FLOW_DDL 的已发布内容。 */
24
+ /** 节点记录保存针对的提交版本、run 归属、判定及可见性证据;同实例/步骤唯一,防重复占位。 */
25
+ export const APPROVAL_AI_MIGRATION = {
26
+ name: 'cage:approval_ai_001',
27
+ sql: `
28
+ ALTER TABLE flow.approval_instance ADD COLUMN subject_version integer;
29
+ ALTER TABLE flow.approval_instance ADD COLUMN start_event_id bigint;
30
+ ALTER TABLE flow.approval_instance ADD COLUMN last_actor jsonb;
31
+ CREATE TABLE flow.approval_step (
32
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
33
+ tenant_id uuid NOT NULL,
34
+ instance_id uuid NOT NULL REFERENCES flow.approval_instance(id),
35
+ step integer NOT NULL,
36
+ kind text NOT NULL CHECK (kind IN ('human','ai')),
37
+ status text NOT NULL,
38
+ expected_version integer,
39
+ run_id uuid,
40
+ decision jsonb,
41
+ decision_visibility jsonb,
42
+ actor jsonb,
43
+ created_at timestamptz NOT NULL DEFAULT now(),
44
+ updated_at timestamptz NOT NULL DEFAULT now(),
45
+ UNIQUE(instance_id, step)
46
+ );
47
+ CREATE INDEX approval_step_queue_idx ON flow.approval_step(status, created_at) WHERE kind = 'ai';
48
+ GRANT SELECT, INSERT, UPDATE ON flow.approval_step TO cage_app;
49
+ `,
50
+ };
51
+ //# sourceMappingURL=ai.js.map
package/dist/ai.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai.js","sourceRoot":"","sources":["../src/ai.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,WAAW,EAAE,UAAU,EAAyB,MAAM,qBAAqB,CAAC;AA4B3G,kDAAkD;AAClD;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,MAAc,EAAE,OAAe,EAAE,QAAgB;IACzF,MAAM,OAAO,GAAG,MAAM,CAAC,QAAQ,CAAC,iBAAiB,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAC9D,IAAI,CAAC,OAAO,EAAE,WAAW;QAAE,MAAM,SAAS,CAAC,WAAW,EAAE,EAAE,MAAM,EAAE,aAAa,EAAE,CAAC,CAAC;IACnF,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC,cAAc,EAAE,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,WAAW,EAAE,GAAG,EAAE,QAAQ,CAAC;SACtH,KAAK,CAAC,MAAM,EAAE,GAAG,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC,KAAK,CAAC,QAAQ,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAC,gBAAgB,EAAE,CAAC;IAC7F,IAAI,CAAC,IAAI;QAAE,MAAM,SAAS,CAAC,WAAW,EAAE,EAAE,MAAM,EAAE,eAAe,EAAE,CAAC,CAAC;IACrE,MAAM,YAAY,GAAG,SAAS,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IACvC,OAAO,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,WAAW,OAAO,SAAS,OAAO,CAAC,WAAW,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ;QAC/G,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,WAAW,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE,MAAM,EAAE,UAAU,CAAC,YAAY,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;AACnI,CAAC;AAED,gCAAgC;AAChC,oDAAoD;AACpD,MAAM,CAAC,MAAM,qBAAqB,GAAG;IACnC,IAAI,EAAE,sBAAsB;IAC5B,GAAG,EAAE;;;;;;;;;;;;;;;;;;;;;;CAsBN;CACA,CAAC"}
package/dist/bridge.d.ts CHANGED
@@ -1,6 +1,12 @@
1
- import type { Kernel } from '@yss123/cage-kernel';
1
+ import { type Kernel } from '@yss123/cage-kernel';
2
2
  import type { Transaction } from 'kysely';
3
3
  import type { ApprovalAdapter, ApprovalOutcome } from './types.js';
4
+ /**
5
+ * 从流程绑定选择通过/驳回/退回命令,再为结论构造可信 process 上下文。
6
+ * 人工结论实时核对最后审批人的命令能力与单据可见性;自动结论使用受限角色,不能继承系统全部范围。
7
+ * 传 tx 时与待办决定共同回滚,未传时命令自行开事务;expectedVersion 阻止迟到结论覆盖新版本。
8
+ */
4
9
  export declare function applyOutcome(k: Kernel, r: ApprovalOutcome, tx?: Transaction<any>): Promise<Record<string, any>>;
5
10
  /** 外部引擎(例如独立部署的 Warm-Flow、Flowable 服务):它的结论通过回调进入这里 */
11
+ /** 只登记结束回调,不创建外部流程或授予回调来源信任;适配器须自行完成其协议认证和投递可靠性。 */
6
12
  export declare function bindApproval(adapter: ApprovalAdapter, k: Kernel): void;
package/dist/bridge.js CHANGED
@@ -1,17 +1,38 @@
1
+ // 桥接:引擎的结论只能转成命令,以流程身份调用,走完整 12 关。引擎侧没有任何业务表的数据库权限。
2
+ import { cageError, loadUserGrants, userCtx } from '@yss123/cage-kernel';
3
+ import { approvalRoleContext } from './ai.js';
4
+ /**
5
+ * 从流程绑定选择通过/驳回/退回命令,再为结论构造可信 process 上下文。
6
+ * 人工结论实时核对最后审批人的命令能力与单据可见性;自动结论使用受限角色,不能继承系统全部范围。
7
+ * 传 tx 时与待办决定共同回滚,未传时命令自行开事务;expectedVersion 阻止迟到结论覆盖新版本。
8
+ */
1
9
  export async function applyOutcome(k, r, tx) {
2
10
  const binding = k.registry.approvalByProcess(r.process);
3
11
  if (!binding)
4
12
  throw new Error(`[CAGE-APPR-004] 没有流程 ${r.process} 的审批绑定`);
5
- const cmd = r.outcome === 'approved' ? binding.a.onApproved : binding.a.onRejected;
6
- const ctx = k.processCtx({
7
- process: r.process,
8
- tenantId: r.tenantId,
9
- onBehalfOf: r.lastApprover,
10
- capabilities: [k.registry.capabilityOf(binding.a.onApproved), k.registry.capabilityOf(binding.a.onRejected)],
11
- });
12
- return tx ? k.executeIn(tx, cmd, { id: r.businessKey }, ctx, 0) : k.execute(cmd, { id: r.businessKey }, ctx);
13
+ const cmd = r.outcome === 'approved' ? binding.a.onApproved : r.outcome === 'returned' ? binding.a.onReturned : binding.a.onRejected;
14
+ if (!cmd)
15
+ throw new Error('[CAGE-APPR-006] 审批未绑定退回修改命令');
16
+ let ctx;
17
+ if (r.lastApprover) {
18
+ // 待办决定已持有事务连接时复用它;同实例并发排队不能耗尽池后让锁持有者再等新连接。
19
+ const grants = await loadUserGrants(tx ?? k.app, r.lastApprover, r.tenantId);
20
+ const user = grants ? userCtx(grants) : null;
21
+ if (!user?.capabilities.has(k.registry.capabilityOf(cmd)) || !user.capabilities.has(`${binding.a.agg.name}.read`))
22
+ throw cageError('FORBIDDEN');
23
+ // 先核验真人对目标单据的权限;命令内声明的后台读依赖仍按已有流程身份完成业务校验。
24
+ const visible = await k.read.tx(user, read => read.selectFrom(`safe.${binding.a.agg.table.name}`).select('id').where('id', '=', r.businessKey).executeTakeFirst());
25
+ if (!visible)
26
+ throw cageError('NOT_FOUND');
27
+ ctx = k.processCtx({ process: r.process, tenantId: r.tenantId, onBehalfOf: r.lastApprover, capabilities: [k.registry.capabilityOf(cmd)] });
28
+ }
29
+ else
30
+ ctx = await approvalRoleContext(k, r.process, r.tenantId);
31
+ const input = { id: r.businessKey, expectedVersion: r.expectedVersion };
32
+ return tx ? k.executeIn(tx, cmd, input, ctx, 0) : k.execute(cmd, input, ctx);
13
33
  }
14
34
  /** 外部引擎(例如独立部署的 Warm-Flow、Flowable 服务):它的结论通过回调进入这里 */
35
+ /** 只登记结束回调,不创建外部流程或授予回调来源信任;适配器须自行完成其协议认证和投递可靠性。 */
15
36
  export function bindApproval(adapter, k) {
16
37
  adapter.onCompleted(async (r) => {
17
38
  await applyOutcome(k, r);
@@ -1 +1 @@
1
- {"version":3,"file":"bridge.js","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"AAKA,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,CAAS,EAAE,CAAkB,EAAE,EAAqB;IACrF,MAAM,OAAO,GAAG,CAAC,CAAC,QAAQ,CAAC,iBAAiB,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;IACxD,IAAI,CAAC,OAAO;QAAE,MAAM,IAAI,KAAK,CAAC,wBAAwB,CAAC,CAAC,OAAO,QAAQ,CAAC,CAAC;IACzE,MAAM,GAAG,GAAG,CAAC,CAAC,OAAO,KAAK,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC;IACnF,MAAM,GAAG,GAAG,CAAC,CAAC,UAAU,CAAC;QACvB,OAAO,EAAE,CAAC,CAAC,OAAO;QAClB,QAAQ,EAAE,CAAC,CAAC,QAAQ;QACpB,UAAU,EAAE,CAAC,CAAC,YAAY;QAC1B,YAAY,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;KAC7G,CAAC,CAAC;IACH,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,WAAW,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,WAAW,EAAE,EAAE,GAAG,CAAC,CAAC;AAC/G,CAAC;AAED,uDAAuD;AACvD,MAAM,UAAU,YAAY,CAAC,OAAwB,EAAE,CAAS;IAC9D,OAAO,CAAC,WAAW,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE;QAC9B,MAAM,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAC3B,CAAC,CAAC,CAAC;AACL,CAAC"}
1
+ {"version":3,"file":"bridge.js","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ import { type Transaction } from 'kysely';
2
+ /** 自然分钟时限;允许小数,精度至少一毫秒,最长一年,不包含工作日历或暂停扣时。 */
3
+ export interface ApprovalSla {
4
+ /** 从进入节点起计算的总办理分钟数,前后加签共用这一截止时间。 */
5
+ durationMinutes: number;
6
+ /** 在截止前多少分钟产生临期提醒;省略只提醒超时,必须小于总时限。 */
7
+ remindBeforeMinutes?: number;
8
+ }
9
+ /** 申请人催办政策随实例冻结;字段只标识申请人,不授予其审批结论能力。 */
10
+ export interface ApprovalReminderPolicy {
11
+ /** routable 快照中的单个用户 UUID 字段,运行时还需当前用户有效且可见业务单据。 */
12
+ requesterField: string;
13
+ /** 两次成功催办间隔的自然分钟数,同实例各节点共用冷却;无收件人不占用冷却。 */
14
+ cooldownMinutes: number;
15
+ }
16
+ /** 校验并冻结节点政策;未知键和不合理提前量明确报声明错误,不把配置失误静默当作关闭提醒。 */
17
+ export declare function approvalSla(value: ApprovalSla): Readonly<ApprovalSla>;
18
+ /** 校验申请人字段名与正数冷却;真正的字段存在性、routable 和公开性在注册表装配后再检查。 */
19
+ export declare function approvalReminderPolicy(value: ApprovalReminderPolicy): Readonly<ApprovalReminderPolicy>;
20
+ /**
21
+ * 在进入人工节点的同一事务登记固定截止时间;使用数据库时间,服务重启不会丢失或重新计时。
22
+ * next_notice_scan_at 是有界扫描的持久轮转位置,后续收件人变化可补投,不让已提醒的旧步骤长期占满扫描批次。
23
+ */
24
+ export declare function initializeDeadline(tx: Transaction<any>, instanceId: string, step: number, policy: ApprovalSla): Promise<void>;
25
+ /** 前向迁移:时间只新增到节点,通知以来源/任务/接收人唯一;已读不删除历史,禁止修改已发布迁移。 */
26
+ export declare const APPROVAL_DEADLINES_MIGRATION: {
27
+ name: string;
28
+ sql: string;
29
+ };
@@ -0,0 +1,72 @@
1
+ /**
2
+ * 人工节点的自然时间办理政策及持久存储,由 route 校验声明、engine 在进入节点时写入。
3
+ * 时限属于整个节点:领取、转办、委派和前后加签均不重置;这里只计时,不替代领域结论或自动批准。
4
+ * 迁移在已发布审批表之上追加,旧实例没有时限或催办政策,升级不会自动启用新行为。
5
+ */
6
+ import { declError } from '@yss123/cage-kernel';
7
+ import { sql } from 'kysely';
8
+ import { z } from 'zod';
9
+ const minutes = z.number().finite().min(1 / 60_000).max(525_600);
10
+ const slaSchema = z.strictObject({ durationMinutes: minutes, remindBeforeMinutes: minutes.optional() })
11
+ .refine(value => value.remindBeforeMinutes === undefined || Math.round(value.remindBeforeMinutes * 60_000) < Math.round(value.durationMinutes * 60_000));
12
+ const reminderSchema = z.strictObject({ requesterField: z.string().regex(/^[A-Za-z][A-Za-z0-9]*$/), cooldownMinutes: minutes });
13
+ /** 校验并冻结节点政策;未知键和不合理提前量明确报声明错误,不把配置失误静默当作关闭提醒。 */
14
+ export function approvalSla(value) {
15
+ const parsed = slaSchema.safeParse(value);
16
+ if (!parsed.success)
17
+ throw declError('CAGE-APPR-006', 'sla 须为有效自然分钟时限(至少一毫秒,最长一年),提前提醒必须小于总时限');
18
+ return Object.freeze(parsed.data);
19
+ }
20
+ /** 校验申请人字段名与正数冷却;真正的字段存在性、routable 和公开性在注册表装配后再检查。 */
21
+ export function approvalReminderPolicy(value) {
22
+ const parsed = reminderSchema.safeParse(value);
23
+ if (!parsed.success)
24
+ throw declError('CAGE-APPR-006', 'reminders 需要有效 requesterField 和自然分钟 cooldownMinutes(至少一毫秒,最长一年)');
25
+ return Object.freeze(parsed.data);
26
+ }
27
+ /**
28
+ * 在进入人工节点的同一事务登记固定截止时间;使用数据库时间,服务重启不会丢失或重新计时。
29
+ * next_notice_scan_at 是有界扫描的持久轮转位置,后续收件人变化可补投,不让已提醒的旧步骤长期占满扫描批次。
30
+ */
31
+ export async function initializeDeadline(tx, instanceId, step, policy) {
32
+ const duration = Math.round(policy.durationMinutes * 60_000);
33
+ const before = policy.remindBeforeMinutes === undefined ? null : Math.round(policy.remindBeforeMinutes * 60_000);
34
+ await tx.updateTable('flow.approval_step').set({
35
+ due_at: sql `now() + ${duration} * interval '1 millisecond'`,
36
+ remind_at: before === null ? null : sql `now() + ${duration - before} * interval '1 millisecond'`,
37
+ next_notice_scan_at: sql `now() + ${duration - (before ?? 0)} * interval '1 millisecond'`,
38
+ }).where('instance_id', '=', instanceId).where('step', '=', step).execute();
39
+ }
40
+ /** 前向迁移:时间只新增到节点,通知以来源/任务/接收人唯一;已读不删除历史,禁止修改已发布迁移。 */
41
+ export const APPROVAL_DEADLINES_MIGRATION = {
42
+ name: 'cage:approval_deadlines_003',
43
+ sql: `
44
+ ALTER TABLE flow.approval_instance ADD COLUMN reminder_policy jsonb;
45
+ ALTER TABLE flow.approval_instance ADD COLUMN next_urge_at timestamptz;
46
+ ALTER TABLE flow.approval_step ADD COLUMN due_at timestamptz;
47
+ ALTER TABLE flow.approval_step ADD COLUMN remind_at timestamptz;
48
+ ALTER TABLE flow.approval_step ADD COLUMN next_notice_scan_at timestamptz;
49
+ ALTER TABLE flow.approval_step ADD CONSTRAINT approval_step_deadline_order CHECK (remind_at IS NULL OR (due_at IS NOT NULL AND remind_at < due_at));
50
+ CREATE INDEX approval_step_notice_scan_idx ON flow.approval_step(next_notice_scan_at, instance_id) WHERE next_notice_scan_at IS NOT NULL;
51
+ CREATE TABLE flow.approval_notice (
52
+ id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
53
+ tenant_id uuid NOT NULL,
54
+ instance_id uuid NOT NULL REFERENCES flow.approval_instance(id),
55
+ task_id uuid NOT NULL REFERENCES flow.approval_task(id),
56
+ step integer NOT NULL,
57
+ recipient_id uuid NOT NULL,
58
+ source text NOT NULL,
59
+ kind text NOT NULL CHECK (kind IN ('due_soon','overdue','urge')),
60
+ actor_id uuid,
61
+ reason text,
62
+ created_at timestamptz NOT NULL DEFAULT now(),
63
+ read_at timestamptz,
64
+ UNIQUE(instance_id, step, source, task_id, recipient_id)
65
+ );
66
+ CREATE INDEX approval_notice_recipient_idx ON flow.approval_notice(tenant_id, recipient_id, id);
67
+ CREATE INDEX approval_notice_unread_idx ON flow.approval_notice(tenant_id, recipient_id, id) WHERE read_at IS NULL;
68
+ GRANT SELECT, INSERT, UPDATE ON flow.approval_notice TO cage_app;
69
+ GRANT USAGE, SELECT ON SEQUENCE flow.approval_notice_id_seq TO cage_app;
70
+ `,
71
+ };
72
+ //# sourceMappingURL=deadlines.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deadlines.js","sourceRoot":"","sources":["../src/deadlines.ts"],"names":[],"mappings":"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"}
package/dist/engine.d.ts CHANGED
@@ -1,7 +1,16 @@
1
- import { type Kernel } from '@yss123/cage-kernel';
1
+ /**
2
+ * 原生结构化审批执行器:实例、AND 分支、候选/直派任务及签署阶段存在 flow 模式,结论经桥接调用领域命令。
3
+ * HTTP、worker 和 AI 运行时共用本入口;按实例锁串行决定,前后签激活与最后结论共同事务提交。
4
+ * waiting 不是完成;任务选人不授予能力,最后命令拒绝(如超额度)会回滚本次决定及附带变化。
5
+ */
6
+ import { type Kernel, type Ctx } from '@yss123/cage-kernel';
2
7
  import { type ApprovalEngine } from '@yss123/cage-kernel/worker';
3
8
  import { type Transaction } from 'kysely';
4
9
  import { type Route } from './route.js';
10
+ import { type BranchLocation } from './execution.js';
11
+ import { type AiDecision } from './ai.js';
12
+ import { type TaskActionInput, type TaskActionPolicy } from './task-actions.js';
13
+ /** 对外待办摘要;step 为 0 基索引,vars 仅来自 routable 白名单,不包含整份领域对象。 */
5
14
  export interface TaskView {
6
15
  id: string;
7
16
  instanceId: string;
@@ -10,36 +19,161 @@ export interface TaskView {
10
19
  businessKey: string;
11
20
  step: number;
12
21
  title: string;
22
+ /** 所属并行组/分支路径;普通主线或旧顺序实例为空数组,不把索引大小当作实际执行先后。 */
23
+ branchPath: BranchLocation[];
13
24
  status: string;
14
25
  vars: Record<string, unknown>;
15
26
  createdAt: string;
27
+ /** 节点总办理截止 UTC 时间;无 SLA 为 null,waiting 或转办不重置,不代表自动批准时间。 */
28
+ dueAt: string | null;
29
+ /** 待办版本,启用任务流转的节点提交决定时也必须携带。 */
30
+ revision: number;
31
+ /** 当前持有人;候选池待领取时为空。 */
32
+ assigneeId: string | null;
33
+ /** direct 直接分配,candidate 允许当前候选领取/持有人释放。 */
34
+ assignment: 'direct' | 'candidate';
35
+ /** 加签来源任务;旧并行加签和原始任务可为空。 */
36
+ parentTaskId: string | null;
37
+ /** 相对父任务的签署顺序;null 表示原票或旧版未记录签序。 */
38
+ signPosition: 'parallel' | 'before' | 'after' | null;
39
+ /** 实例允许的新加签位置;未开放为 [],第一批旧快照仅 parallel,不表示本任务可以再次加签。 */
40
+ signPositions: readonly ('parallel' | 'before' | 'after')[];
41
+ /** 委派原负责人;未委派或已转办时为空。 */
42
+ ownerId: string | null;
43
+ /** pending 表示受托处理中,只能归还;resolved 表示已归还原负责人。 */
44
+ delegation: 'pending' | 'resolved' | null;
45
+ /** original 是路由选人,additional 是节点前后/并行追加必签。 */
46
+ taskKind: 'original' | 'additional';
47
+ /** 实例快照中的动作政策,仅供界面显示;服务端仍会实时授权。 */
48
+ actions: Readonly<TaskActionPolicy>;
16
49
  }
50
+ /**
51
+ * 有限无环路由的持久执行器:实例冻结展开步骤与依赖,人工任务和 AI 节点共享同一推进/结束边界。
52
+ * 以实例行锁串行化决定,最后结论与领域命令共同事务提交;命令失败时本次审批也不能算完成。
53
+ */
17
54
  export declare class InCodeEngine implements ApprovalEngine {
18
55
  private readonly k;
19
56
  readonly route: Route;
20
57
  constructor(k: Kernel, route: Route);
21
- private approversOf;
58
+ /**
59
+ * 执行候选领取/释放或本人待办转办/委派/归还/前后加签;必需任务版本与原因,失败不会部分更新。
60
+ * 分配或动作政策须显式启用,接收人须已有批准和读取权限;返回版本及新任务 ID,不推进领域状态。
61
+ */
62
+ act(taskId: string, userId: string, tenantId: string, input: TaskActionInput): Promise<import("./task-actions.js").TaskActionResult>;
63
+ /** 登记当前节点与提交版本;AI 节点排队等待运行时领取,人工节点立即解析候选并创建待办。 */
22
64
  private openStep;
65
+ /** direct 逐人建立票,candidate 只建一项未领取任务;空候选明确报错,不跳过或默认通过。 */
66
+ private openTasks;
67
+ /**
68
+ * 在消费事务中创建新轮次:先取消旧执行者,再保存展开步骤和触发事件/提交版本。
69
+ * 事件版本已落后于当前对象时标 superseded 而不创建新待办,防止迟到提交事件审核过期内容。
70
+ */
23
71
  start(input: {
24
72
  process: string;
25
73
  businessKey: string;
26
74
  tenantId: string;
27
75
  vars: Record<string, unknown>;
28
76
  tx: Transaction<any>;
77
+ subjectVersion?: number;
78
+ eventId?: number;
29
79
  }): Promise<{
30
80
  instanceId: string;
31
81
  }>;
82
+ /** 关闭同租户/流程/对象的 running 轮次、未决待办和可运行 AI 节点,已完成历史保持可追溯。 */
32
83
  cancel(input: {
33
84
  process: string;
34
85
  businessKey: string;
35
86
  tenantId: string;
36
87
  tx: Transaction<any>;
37
88
  }): Promise<void>;
38
- /** 我的待办 */
89
+ /**
90
+ * 从本人最近 200 条任务候选中按当前单据 read/范围过滤,撤权或停用后不继续披露任务摘要。
91
+ * vars 是历史 routable 摘要,始终去隐藏字段并掩码;需要明文时另经带审计的业务读接口获取。
92
+ */
39
93
  tasksOf(userId: string, tenantId: string, status?: string): Promise<TaskView[]>;
94
+ /**
95
+ * 查询最近 200 项未领取候选中的可见任务;当前候选身份与批准/read 权限均需成立。
96
+ * 列表不是占用承诺,领取必须再次锁定并校验;待领取任务不会混入 tasksOf/AI 的可决定清单。
97
+ */
98
+ candidateTasks(userId: string, tenantId: string): Promise<TaskView[]>;
99
+ /** 共用安全摘要投影;候选规则在读取时解析,组织变化即时影响候选清单,历史变量仍始终掩码。 */
100
+ private listTasks;
101
+ /** 以业务对象读权限查看审批轮次;步骤标题取提交时的快照,隐藏内部 policy、提示词、工具历史和运行凭据。 */
102
+ history(aggregate: string, businessKey: string, ctx: Ctx): Promise<{
103
+ id: any;
104
+ process: string;
105
+ status: any;
106
+ currentStep: any;
107
+ currentSteps: any[];
108
+ definitionVersion: string | null;
109
+ createdAt: string;
110
+ updatedAt: string;
111
+ steps: {
112
+ index: any;
113
+ title: string;
114
+ branchPath: BranchLocation[];
115
+ dependsOn: number[];
116
+ kind: any;
117
+ status: any;
118
+ decision: any;
119
+ detailRestricted: boolean;
120
+ actor: any;
121
+ updatedAt: string;
122
+ dueAt: string | null;
123
+ }[];
124
+ }[]>;
125
+ /**
126
+ * 按业务对象当前 read/范围读取本流程的人工操作记录,跨轮次以递增 ID 游标分页。
127
+ * after 是服务端返回的十进制字符串,避免 bigint 转为 JS number 丢失精度;默认 50、最多 200 条。
128
+ * 历史意见仅供同一对象的授权读者共享,不返回任务变量或 AI 提示词;旧迁移前的动作不会伪造补录。
129
+ */
130
+ taskHistory(aggregate: string, businessKey: string, ctx: Ctx, options?: {
131
+ after?: string;
132
+ limit?: number;
133
+ }): Promise<{
134
+ items: {
135
+ id: string;
136
+ instanceId: any;
137
+ taskId: any;
138
+ actorId: any;
139
+ action: any;
140
+ reason: any;
141
+ details: any;
142
+ createdAt: string;
143
+ }[];
144
+ nextCursor: string | null;
145
+ }>;
40
146
  /** 审批人做出决定;流程结束时在同一事务里执行结论命令 */
41
- decide(taskId: string, userId: string, tenantId: string, outcome: 'approved' | 'rejected', comment?: string): Promise<{
147
+ decide(taskId: string, userId: string, tenantId: string, outcome: 'approved' | 'rejected' | 'returned', comment?: string, expectedRevision?: number): Promise<{
148
+ finished: boolean;
149
+ outcome?: string;
150
+ }>;
151
+ /** AI 助理的动作回执可与人工待办决定共用该事务,重试不重复审批。 */
152
+ /**
153
+ * 先按租户/受托人定位任务,再锁实例和任务并实时验证能力/单据可见性;过期待办不允许再次决定。
154
+ * 委派未归还或 waiting 时拒绝决定;候选/启用流转的节点要求 revision,避免责任或阶段变化后旧决定仍生效。
155
+ * 驳回/退回直接结束;all 等待全部,any 等任一原票及全部追加票;批准后激活已满足依赖的前后签。
156
+ * 外层拥有事务提交权,可与 AI 助理动作回执共同提交;finished:false 仅表示流程还需后续步骤。
157
+ */
158
+ decideIn(tx: Transaction<any>, taskId: string, userId: string, tenantId: string, outcome: 'approved' | 'rejected' | 'returned', comment?: string, expectedRevision?: number): Promise<{
42
159
  finished: boolean;
43
160
  outcome?: string;
44
161
  }>;
162
+ /** 先执行带版本的领域结论命令,成功后才关闭剩余待办并写流程终态,保证两边不会各自成功。 */
163
+ private finish;
164
+ /**
165
+ * 在实例锁内完成一项节点,再开启所有前驱已通过且尚无持久记录的节点;AND 汇合等待每条分支末端。
166
+ * 创建节点和最终领域结论都在当前事务内,失败回滚本次签署;重启只依赖数据库状态,唯一键阻止重复节点。
167
+ * current_step 仅保留最小活跃索引给旧读者,决定、AI 与提醒均以节点自身状态为准。
168
+ */
169
+ private advance;
170
+ /** 只有仍然占据该节点的 run 能提交;调用方需在同一事务核验 AI run 租约。 */
171
+ /**
172
+ * 锁定实例、节点和业务对象,校验 run 仍占据当前节点且对象版本未变;旧轮次/旧 run 回执一律冲突。
173
+ * 保存判定时同时保存敏感明文/查询范围证据,history 据此决定后来读者能否查看理由。
174
+ * manual 仅开人工接管待办;return/ pass 分别进入共同 finish/advance,保持领域命令约束不变。
175
+ */
176
+ completeAi(tx: Transaction<any>, stepId: string, runId: string, decision: AiDecision, actor?: Ctx): Promise<{
177
+ finished: boolean;
178
+ }>;
45
179
  }