openxiangda-skill-kit 2.3.81 → 2.3.90

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openxiangda-skill-kit",
3
- "version": "2.3.81",
3
+ "version": "2.3.90",
4
4
  "description": "OpenXiangda 2.0 中文 AI 技能的校验、分发与安装。",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -17,7 +17,7 @@
17
17
  "README.md"
18
18
  ],
19
19
  "dependencies": {
20
- "openxiangda-devkit-core": "2.48.2"
20
+ "openxiangda-devkit-core": "2.52.0"
21
21
  },
22
22
  "devDependencies": {
23
23
  "tsx": "4.23.12",
@@ -91,10 +91,43 @@ export function ResponsibilityMembers() {
91
91
 
92
92
  读取沿用原流程管理权限。仅获成员管理委托的用户可能可以维护角色,同时没有权限读取关联流程;组件单独呈现该拒绝。返回不含成员、实例标识或业务事实,每页最多 100 条;最多 1000 个版本组合、32 MiB 源 JSON、10000 节点/引用,超限明确拒绝。
93
93
 
94
+ ### 多职责成员合并
95
+
96
+ 审批或抄送的 `app_role` / `app_role_in_scope` 来源可用 `roleCodes` 声明 2–8 个不同职责,
97
+ 与单职责 `roleCode` 互斥。例如:
98
+
99
+ ```ts
100
+ jointReviewers: {
101
+ provider: 'app_role',
102
+ roleCodes: ['student-office', 'organization'],
103
+ }
104
+ ```
105
+
106
+ 节点进入时读取这些职责的当前有效成员;范围角色共用同一份代码计算的 `scope`。
107
+ 同一人员只占一个审批席位或抄送名额,解析解释保留各职责来源。
108
+ 职责顺序决定重叠人员席位使用哪个职责的代理规则:采用首个匹配职责,
109
+ 不会借用后续职责的代理。已有任务保持原人员快照。
110
+
111
+ 管理员仅在代码开放人员来源配置的节点中调整职责组合;单选仍保存 `roleCode`,
112
+ 多选保存 `roleCodes`。保存、版本激活均检查所有职责,任何职责失效或查询失败都不能
113
+ 变成部分名单。总候选最多 200 个成员身份;去重后的审批上限仍受原 binding 限制,
114
+ 自动抄送最多 20 人。声明自动协商 `workflow.role-union@1.0.0`,无需初始化开关。
115
+
94
116
  管理范围检索使用 `listRoleManagementScopeValues(dimensionCode, { keyword, limit, offset })`,返回 `NativeRoleManagementScopeValuePage` 的 `id/label` 与 `limit/offset`。它使用 `openxiangda.native-role-management-scope-value-page/v2`,与 Data 字段选择器的 `value/label/cursor` 协议分开。权限仍要求对成员的分配或更新能力;只读权限不因此扩张。
95
117
 
96
118
  ## 限时审批代理 {#workflow-delegations}
97
119
 
120
+ 限定流程后,可进一步选择该职责在某个审批节点的代理。共享维护页的节点选项来自本人有效
121
+ 职责下的真实审批引用,包含当前及在途定义;不同节点可分别委托不同人员。不选节点覆盖
122
+ 范围内全部适用节点,全节点代理与重叠的单节点代理不能同时生效。预览和原操作回执保留范围,
123
+ 旧回执缺少节点仍按原规则读取;创建、撤销或到期均不改派已创建的任务。
124
+
125
+ 自定义页面使用 `WorkflowDelegationMutationRequest` 的create操作,在非空 `workflowCode`
126
+ 下传可选 `nodeId`;候选查询也携带同样的流程与节点。`delegationCatalog().sources[].nodeScopes`
127
+ 提供可选节点,服务器重新核验职责和范围,不能从客户端指定审批人身份。
128
+ 按节点创建必须使用预览/execute/receipt协议;旧 `createDelegation` 入口拒绝 `nodeId`,
129
+ 避免与不支持节点限制的旧服务联调时意外扩大范围。平台自动支持,无新增默认关闭开关。
130
+
98
131
  平台自动提供 `workflow.delegation-management@1.0.0`,没有额外的默认关闭开关。本人只能委托自己的有效审批职责;代理人必须具有同角色、覆盖原范围且有效期覆盖整个代理窗口的成员身份。采用平台数据库时间与 `[开始, 结束)` 区间,禁止自己代理、重叠链和环。管理员可全应用查看和撤销,创建由原审批人本人完成;成员管理委托不会扩大后台或代理管理权。
99
132
 
100
133
  标准管理入口、应用后台工具与门户个人页可复用 `WorkflowDelegationManager`。组件读取当前挂载应用和环境;`initialAll` 只是筛选意图,平台仍核验超管权限。宿主把草稿回调交给已有导航保护,避免切页时丢失输入或未知操作:
@@ -4,6 +4,8 @@
4
4
 
5
5
  平台网关验证当前用户完整的应用角色并集,并把经平台重验的角色、capability 与可选 Perspective 交给 Nest SDK。业务 controller 使用生成的 operation 合同和 capability 装饰器;平台仍是身份与授权的唯一所有者。请求作用域 `OpenXiangdaDataApiService` 自动继承 Perspective 读取投影;绕过 Data API 的自定义读取才使用 `@CurrentPerspective()` 显式投影。应用代码不替换身份、不保存平台凭据,也不建立第二套用户或权限状态。
6
6
 
7
+ `OpenXiangdaBusinessDataApiService` 的跨模型读写使用固定 operation 的受信业务证明,不向 Native 转发用户读取 Perspective。原请求的视角保持,普通 Data API 读取仍收窄;业务动作继续按当前用户角色并集授权,并在平台事务内核验当前成员范围、状态和 revision。不要通过清除浏览器所有请求的 Perspective 来修复业务提交,也不要把业务 facade 当作任意读取接口。
8
+
7
9
  ```bash
8
10
  pnpm openxiangda dev
9
11
  pnpm openxiangda check
@@ -16,7 +18,11 @@ operation、事件消费者、人员提供器,然后运行 `pnpm openxiangda c
16
18
 
17
19
  依赖安装失败会保留新源码并报告 `OPENXIANGDA_BACKEND_INSTALL_FAILED`;重试相同命令
18
20
  即可继续。关闭 backend 不自动删除用户源码。仅删除已经确认不用的后端目录和其依赖。
19
- 本地 `/api` 经过 connected proxy 进入 Nest;发布态由同源应用网关转发。
21
+ 开发者本地 `/api` 经过 connected proxy 进入 Nest;普通浏览器身份经过平台实时授权,
22
+ 在支持 `application.development-backend-invocations` 的预发布开发配置上通过当前
23
+ CLI 反向连接到本地 Nest。仅开放已声明 operation,保持短 invocation、请求断言和
24
+ 原业务幂等键。有限缓冲 HTTP 的预算与关闭/版本失效规则见
25
+ [普通角色与应用登录联调](getting-started.md#普通角色与应用登录联调)。发布态由同源应用网关转发。
20
26
 
21
27
  | 需求 | 使用的 SDK | 权威边界 |
22
28
  | --- | --- | --- |
@@ -50,6 +56,41 @@ operationCode、idempotencyKey;SDK 绑定当前环境并核验返回关联。N
50
56
  标准 PC/移动发起页在响应未知时保留原操作定位信息,刷新可继续查询;同一页面、原身份和
51
57
  版本内可手动重试冻结的标准输入,绝不自动重发或悄悄换键。
52
58
 
59
+ ### 补正重提的业务操作 {#correction-business-command}
60
+
61
+ 使用固定 Workflow 的 resubmit handler 执行业务重校验;请求是生成操作契约的
62
+ `WorkflowBusinessCommandInvocation`,身份、环境与具名动作由网关和请求作用域 SDK 验证。
63
+ 禁止接受页面自报的申请人、资格快照或下游节点。
64
+
65
+ ```ts
66
+ // 必须在当前资料读取和业务校验之前恢复同一用户的原任务结果。
67
+ const original = await businessProcess.resolveOriginalTaskCommand(invocation);
68
+ if (original.status === 'succeeded') return original.result;
69
+ if (original.status === 'failed') throw new Error(original.errorCode || 'WORKFLOW_COMMAND_FAILED');
70
+
71
+ // 合并当前申请与 task form 的受限字段,重验业务规则并准备当前资料的 guards。
72
+ // form.expectedRevision 与 invocation.expectedRevision 保持一致,不给系统字段赋客户端值。
73
+ return businessProcess.commandWithData({
74
+ workflow: invocation,
75
+ subject: { fromOperation: 'request' },
76
+ data: {
77
+ guards,
78
+ operations: [{ key: 'request', kind: 'update', resourceCode: 'requests',
79
+ id: invocation.recordId, expectedRevision: invocation.expectedRevision,
80
+ data: { lastValidatedAt: platformResolvedAt } }],
81
+ },
82
+ expectedTransition: { kind: 'correction-replay' },
83
+ });
84
+ ```
85
+
86
+ `lastValidatedAt` 为本例应用声明的非路由审计字段。任务 form 由平台在同一事务先应用,
87
+ 平台自动衔接其产生的主体 revision;业务 mutation 不能改写固定事实映射。
88
+ 守卫、字段、资格、下游解析或流转失败时,Native 更新与任务、会话、事件一起回滚。
89
+ 原结果查询使用既有 `/workflow/tasks/:id/commands/original`,核对当前 operation、任务、
90
+ 原键、命令、流程、业务记录和原用户输入摘要,不新建恢复表或自动换键重试。
91
+ `not_observed` 仅表示尚未看到确定回执;保留原输入、原 token 和原键,按用户显式操作恢复。
92
+ 声明及限制见[工作流](workflow-events.md#correction-business-command)。
93
+
53
94
  ### 数据修改的原结果恢复 {#data-business-commands}
54
95
 
55
96
  仅修改业务资料的具名动作,在 `platformAccess` 声明
@@ -93,6 +134,36 @@ return committed.receipt;
93
134
  服务端只持久保存意图和公开键的摘要,复用原 Native 回执,不建立第二份状态表。
94
135
  结果仅返回可信应用后端,应用按已声明的 responseSchema 向用户投影。
95
136
 
137
+ ### 数据写入的流程阶段前置条件 {#workflow-native-stage-guard}
138
+
139
+ 证明生成等具名动作需要把当前流程阶段与 Native 写入放在同一事务中时,声明固定条件:
140
+
141
+ ```ts
142
+ platformAccess: {
143
+ dataCommands: { mode: 'recoverable-native' },
144
+ workflow: { codes: ['certificate'] },
145
+ workflowStage: {
146
+ workflowCode: 'certificate', resourceCode: 'requests', actor: 'initiator',
147
+ runningNodeIds: ['applicant-confirm'], allowedStatuses: ['approved'],
148
+ },
149
+ }
150
+ ```
151
+
152
+ 编译器核对固定流程、来源资源与节点,并要求 `workflow.native-stage-guard` 1.0.0。
153
+ 声明后每次 `commitCommand` 必须在 `data.workflowStage` 传入所观测的
154
+ `instanceId`、`subjectRecordId`、`expectedInstanceSequence`;同时传入该来源记录的
155
+ `record-match` 或 `record-assert` guard,包含 `revision eq 原版本`。
156
+ 这些值由动作代码从授权读取结果组装,不允许用户指定可用阶段或替换策略。
157
+ `runningNodeIds` 限定运行中的主节点;`allowedStatuses` 明确列出其他允许状态,
158
+ 包括需要保留的 returned,未列出的状态拒绝。
159
+ `actor: 'reader'` 复用 Kernel 的实例读取权限;动作能力与 Native 数据范围仍独立核对。
160
+
161
+ 平台先恢复已提交的原回执,再获取 Kernel 实例锁、重验当前身份和阶段,然后持锁
162
+ 执行来源版本守卫、数据修改及回执提交。阶段、来源或序号变化明确拒绝;锁冲突返回
163
+ 可重试错误,保留原业务意图和键。已提交结果可在后续阶段或同环境新 Head 下恢复。
164
+ 此条件不属于通用 Data API guard,也不能与 decimalReservation 合并使用。
165
+ 上传准备仍在事务外:失败后不得关联文件,未关联上传由既有托管文件清理机制处理。
166
+
96
167
  自定义表单页若先用 `createResourceFormDraftClient` 保存认证草稿,并由 Named Action
97
168
  提交业务记录和流程,则在同一次 `OpenXiangdaBusinessProcessService.commit` 中传入
98
169
  `formDraft: { resourceCode, id, expectedRevision, mode, recordId?, viewCode? }`。草稿必须
@@ -111,7 +182,14 @@ return committed.receipt;
111
182
  重、不自行加锁、不在重试时重新生成业务时间。
112
183
 
113
184
  只读前置条件使用 `record-exists` 或 `record-match`,它们不要求同记录 mutation,
114
- 但仍执行 read capability、字段权限与行级授权。需要与数据库当前时间比较时使用
185
+ 但仍执行 read capability、字段权限与行级授权。多行申请引用同一类主数据时使用
186
+ `record-set-match`:`resourceCode`、`errorCode`、`records: [{ id, expectedRevision }]`
187
+ 及可选公共 `where`。每组1–500个不同UUID,所有组累计最多500条,守卫总数仍最多20条。
188
+ 同一资源只能使用一条集合守卫,应合并选择集合以保持资源及记录的固定锁序。
189
+ 例如国家选择可用 `where: { field: 'enabled', operator: 'eq', value: true }`;
190
+ 平台先确认全部记录有read授权,再锁定集合并重读,任一记录缺失、失权、停用或版本
191
+ 改变均原子拒绝。它不授予update,也不替代increment所需的record-assert。原成功回执
192
+ 恢复优先于主数据重新核验。需要与数据库当前时间比较时使用
115
193
  `databaseNowAssertion('publishAt', 'lte')`;平台在守卫行锁及写入前校验完成后,
116
194
  用一次 PostgreSQL `clock_timestamp()` 完成所有动态断言,并把该接受时刻作为
117
195
  `evaluatedAt` 存入幂等回执。它不是最终提交时刻。相同幂等键重放不会重新
@@ -269,6 +347,36 @@ await businessData.transaction({
269
347
  分派已接受后撤销角色,不会自动撤销历史分派;后续处理动作必须重新验证当前权限,
270
348
  由管理员重新分派。此规则应写入 AppSpec,并实测撤销先发生和分派先发生两种顺序。
271
349
 
350
+ ## 在事务中核对当前办理人的操作和范围 {#actor-authority}
351
+
352
+ 管理动作需要同时具备操作权限和业务范围时,在具名动作声明
353
+ `platformAccess: { roleAssertions: { roleCodes: ['college-admin'], actorAuthority: true } }`。
354
+ 这会要求平台 `data.transaction-actor-authority@1.0.0`。提交同一业务事务时增加:
355
+
356
+ ```ts
357
+ { kind: 'actor-authority', errorCode: 'OPENXIANGDA_MANAGEMENT_SCOPE_DENIED',
358
+ anyOf: [{ roleCode: 'college-admin',
359
+ scope: { dimensionCode: 'college', value: authoritativeCollegeCode, operation: 'manage' } }],
360
+ allowAppSuperAdmin: false }
361
+ ```
362
+
363
+ `anyOf` 为 1–20 个不同的角色/精确范围分支;角色必须在动作声明和当前授权定义内,
364
+ 范围维度必须在当前定义内。省略 scope 的分支仍需角色与本动作 capability 属于同一成员。
365
+ 当前办理人和 capability 仅从已验证动作取得,守卫不接收 userId、成员 ID 或能力输入。
366
+ 平台在同一事务锁定账号、相关有效 package 成员和授权投影,锁后以数据库时间重新解释;
367
+ 不能把不同成员的能力和范围拼接,也不使用账号部门、前端权限缓存或选人候选替代管理授权。
368
+ scope operations 省略、null、空数组或包含 `*` 表示允许全部,否则必须包含指定操作。
369
+
370
+ `allowAppSuperAdmin` 默认 false;只有明确设为 true 且当前应用管理员授权被锁定并验证时
371
+ 才能通过这个分支,它不代表审批参与人。单事务最多核对 400 条相关成员;超限返回
372
+ `OPENXIANGDA_ACTOR_AUTHORITY_BUSY`。锁冲突沿用 `OPENXIANGDA_ROLE_ASSERTION_CONFLICT`,
373
+ 锁等待最多 1 秒、单条 SQL 最多 10 秒(保留更紧的已有上限)。已先完成撤权则新提交拒绝;
374
+ 已先锁定并接受的事务结束后才能撤权。守卫失败返回应用声明的失败码及 `/guards/<index>`,
375
+ 业务写入、事件和回执一起回滚。结果未知继续查询原请求;已有成功回执重放不重复写入。
376
+ 普通 Data SDK、应用凭据、事件和队列上下文不能使用;未声明返回
377
+ `OPENXIANGDA_ACTOR_AUTHORITY_NOT_DECLARED`。仪器归属等业务事实仍由应用在同一事务
378
+ 使用 Native revision/CAS/记录守卫冻结;prepare 不授予后续提交权限。
379
+
272
380
  ## AI 能力目录与 MCP Facade {#ai-catalog}
273
381
 
274
382
  编译器为每个应用生成不可变的 AI 能力目录:资源的标准 CRUD 面(query/get/create/update/delete,
@@ -412,6 +520,13 @@ import {
412
520
  用户动作 send 的 schemaVersion 使用 OPENXIANGDA_NOTIFICATION_BUSINESS_SEND_V2;事件处理 sendFromEvent 使用 OPENXIANGDA_NOTIFICATION_EVENT_SEND_V2。二者的调用上下文和收件人来源不同,不能混用。
413
521
  ## 外部处理受控文件
414
522
 
523
+ 证明等业务动作需要核对真实审批状态时,可调用
524
+ `workflow.recordHistory(resourceCode, recordId, { instanceId?, limit: 1 })`(注入
525
+ `OpenXiangdaWorkflowService`)。它沿当前用户的 Native 读取和流程历史权限,返回实际
526
+ `recordRevision` 与 `instance.status`;不能用流程命令的执行成功推断审批已批准。
527
+ 记录修订不一致应重新核对;403/404/409 直接拒绝,不回退业务字段或应用缓存。
528
+ 此读取不授予任务办理或服务身份权限,offset 上限500、limit 上限100。
529
+
415
530
  需要水印或归档处理时,Nest DataApi 可签发短期对象下载地址。不要把要求登录态的 content URL 或用户 Cookie 交给外部服务。
416
531
 
417
532
  ```ts
@@ -28,6 +28,36 @@ pnpm openxiangda check
28
28
 
29
29
  角色成员、维度授权和平台管理员由平台管理面维护,不属于应用开发 CLI。
30
30
 
31
+ ### 登录用户的公共字段与管理范围
32
+
33
+ 同一资源需要保留公共目录、同时按学院或负责人读取私有字段时,在只读策略声明
34
+ `publicRead: { fields: ['name', 'category'] }`。这仅适用于已认证用户,基线角色由
35
+ `authenticatedUserRoleCode` 确定,不传入角色、身份或范围。`operations` 必须恰好是
36
+ `['read']`,不能搭配 `readExpression` 或 `writeBoundary`。
37
+
38
+ 公共字段是 1–1000 个唯一的真实业务字段,必须允许基线角色读取;不能包含平台元数据
39
+ 或子表。所有未列出的业务字段都必须通过 `access.read` 拒绝基线角色,有私有字段时
40
+ `audit.read` 也必须拒绝基线角色。缺失字段策略、把私有能力授给基线、遗漏历史限制都会
41
+ 在两侧编译器校验失败。平台基础元数据保持原读取协议,不计入业务公共白名单。
42
+
43
+ 源声明仍禁止把基线角色直接放入 `unrestrictedRoleCodes`。编译器只在上述证明通过后
44
+ 物化公共读取,并保留公共字段意图;需要 `data.authenticated-public-projection@1.0.0`。
45
+ 每次列表、筛选、排序、聚合或导出使用整组请求字段筛选同一个有效成员,再执行其行策略。
46
+ 因此管理私有字段不能借用公共成员的全行范围,不同成员的字段能力和范围也不能拼接。
47
+ 管理 Perspective 可以进一步收窄读取;关键写入仍独立验证当前角色并集和事务条件。
48
+
49
+ ```ts
50
+ {
51
+ code: 'catalogue-read', name: '公共目录与管理读取', resourceCode: 'catalogue',
52
+ operations: ['read'], publicRead: { fields: ['name', 'category'] },
53
+ unrestrictedRoleCodes: ['school-admin'], matchMode: 'OR',
54
+ rules: [{ dimensionCode: 'college', field: 'college', valuePath: 'value',
55
+ operation: 'manage', roleCodes: ['college-admin'] }],
56
+ }
57
+ ```
58
+
59
+ 这不是匿名公开数据接口;对外无账号读取仍使用独立的 `frontend.publicAccess` 合同。
60
+
31
61
  ### 条件唯一键
32
62
 
33
63
  需要“同一编号只能有一条有效主档”时,在模型声明 `uniqueKeys`。平台在环境
@@ -30,7 +30,7 @@
30
30
  | `labelField` 必须指向目标资源的 `text.short` / `text.long` 字段 | 不要用流水号/选项字段当 label |
31
31
  | 列表可排序列用视图级 `sortableFields` 表达(`defaultSort.field` 隐式可排序);平台审计列(如 `created_at`)同样合法 | `list: { sortableFields: ['capacity'], defaultSort: { field: 'name', order: 'asc' } }`;`defaultSort: { field: 'created_at', order: 'desc' }` |
32
32
  | 每个字段都必须带中文/业务 `label`(含子表外键与排序字段) | `{ code: 'requestId', type: 'uuid', label: '所属申请', required: true }` |
33
- | 子表 `subtable` 的外键是子资源的 **uuid** 字段,排序字段是**可写 number.integer** | 子资源:`{ code: 'requestId', type: 'uuid', required: true }` + `{ code: 'sortOrder', type: 'number.integer', required: true }`;父表:`subtable: { resourceCode: 'repair-items', foreignKey: 'requestId', orderField: 'sortOrder', maxRows: 20 }` |
33
+ | 子表 `subtable` 的外键是子资源的 **uuid** 字段,排序字段是**可写 number.integer** | 子资源:`{ code: 'requestId', type: 'uuid', required: true }` + `{ code: 'sortOrder', type: 'number.integer', required: true }`;父表:`subtable: { resourceCode: 'repair-items', foreignKey: 'requestId', orderField: 'sortOrder', minRows: 1, maxRows: 20 }`;minRows默认0、不能超过maxRows |
34
34
  | 图片/附件的 `file` 限定数量与大小 | `file: { maxCount: 3, maxSizeMb: 10, accept: ['image/png', 'image/jpeg'] }` |
35
35
 
36
36
  ## 权限声明
@@ -39,6 +39,7 @@
39
39
  | --- | --- |
40
40
  | 数据策略是**白名单**语义:规则 `roleCodes` 之外的角色若不在 `unrestrictedRoleCodes` 中会被 RLS 全拒(报错只有 FIELD_ROW_FORBIDDEN) | `unrestrictedRoleCodes: ['admin']` 必须列出所有"不受限"角色 |
41
41
  | 基线角色(`authenticatedUserRoleCode`)进 `unrestrictedRoleCodes` = 策略对所有人失效(角色并集必含基线角色),编译器直接报错 | 把基线角色移出 unrestrictedRoleCodes,为其单独声明 rules |
42
+ | 登录公共字段与管理私有范围共存不能直接放开基线角色 | 只读策略显式 `publicRead: { fields: [...] }`,全部非公开字段及变更历史须拒绝基线,详见 data-authz;不支持公共子表 |
42
43
  | 匿名公开策略的 `ownRecordFields` 必须是 `fields` 的子集;`create` 必须配套 `draft`;`requiredFields` ⊆ `fields` | 先定 fields,再从中选 required/own |
43
44
  | workflow definition 必须显式 `launch`(编译器强制) | `definitions: [{ version: 1, definition, launch: { mode: 'standalone' } }]` |
44
45
  | option/user/department/resource-ref/cascade 字段投影进工作流事实是 { label, value } 对象,不能声明为标量;条件比较用 `<fact>.value` | `inputSchema.properties.urgency = { type: 'object', ... }` + `path: 'urgency.value'` |
@@ -52,4 +52,11 @@
52
52
 
53
53
  开发使用 `pnpm openxiangda dev`,过程中运行必要的聚焦测试。交接前按[检查与验收](testing.md)验证;授权发布后按[交付](delivery.md)部署。失败保留错误码、位置和原始候选,依据平台恢复指令继续。
54
54
 
55
+ 多流程迁移可一次选择 3–5 条代表业务,先集中核对字段、审批人、操作、分支和
56
+ 副作用,再按公共能力主题修复。开发期间使用本地源码及 connected development,
57
+ 完整开发配置的覆盖与限制见[连接开发](getting-started.md#connected-development)。
58
+ 小改动执行相关检查,复用原申请和成功证据;一批能力完成后再统一冻结工具链、
59
+ 构建和正式测试激活,集中验收普通角色的 PC 与手机任务。源码检查、配置同步、
60
+ 正式激活和业务运行证据分别记录,不用通过的 fixture 替代真实业务验收。
61
+
55
62
  [AppSpec](appspec.md)保存业务意图、设计与交付记录,不复制字段 Schema、生成契约和部署状态。新应用维护具体设计和评审,复杂度随业务展开;既有小变更沿用有效设计,仅修订受影响记录,无行为变化可引用已有记录。每轮先读取当前规则、澄清业务、评估架构、权限和性能,随后实施与验证,发布后更新当前规格及交接。测试部署前需要设计和验收计划,生产晋级前需要绑定测试运行及包摘要的实际验收报告。
@@ -130,6 +130,13 @@ import { AttachmentFileList } from 'openxiangda/field-kit';
130
130
  平台按来源资源的字段权限和 PostgreSQL RLS 查询并返回完整资源快照。来源记录改名或删除后,
131
131
  已经保存的 `{label,value,resourceCode,snapshot}` 仍可直接展示,不需要再次查询。
132
132
 
133
+ 过滤条件绑定其他表单字段时,可显式设置 `source.clearOnBindingChange: true`。
134
+ 用户改变绑定值后,标准表单、任务补填和同一子表行会清除对应旧选择:单选清空、
135
+ 多选变为空数组,依赖链中的可写关联字段也会清除。只比较引用的真实 `value`;
136
+ 同值重选、显示标签刷新、无关字段或其他子表行的修改不清除当前选择。
137
+ 默认缺省或 `false` 保留选择;程序预填、草稿与资料恢复不会触发这项用户事件联动。
138
+ 这项设置只管理当前输入,提交是否合法仍由平台事务与字段权限判断。
139
+
133
140
  成员和部门同样保存完整显示快照:
134
141
 
135
142
  ```ts
@@ -160,11 +167,24 @@ import { AttachmentFileList } from 'openxiangda/field-kit';
160
167
  ```
161
168
 
162
169
  职责和范围维度必须在同包声明。`pageSize` 为1–50,默认20;范围使用代码常量 `value`,
163
- 或同资源的 `text.short`、`uuid`、`option.single` 字段 `field`,两者互斥。
170
+ 或同资源的 `text.short`、`uuid`、`option.single`、`resource-ref.single` 字段 `field`,两者互斥。
164
171
  单选范围取稳定的 `.value`,不按显示标签决定身份。记录范围必须来自已授权、已保存的记录或当前任务,
165
172
  首版新建不接受非空的记录范围选人。修改范围须清空相依选择并保存,再按新范围重选。
166
173
  候选显示失败不能当作空名单;旧选择失效须保留显示快照并提示重选。
167
174
 
175
+ 具名流程原子创建申请时,可显式声明 `scope: { dimensionCode, operation, field,
176
+ creation: 'prospective' }`。编译器自动要求 `data.user-candidate-launch-scope@1.0.0`。
177
+ 维度必须使用uuid的Native资源来源;资源引用范围字段指向同一来源。发起候选查询
178
+ 使用`scopeValue`,平台先核验具名发起绑定及范围记录的read权限、RLS和enabled。
179
+ 查询值仅为搜索意图;服务端业务操作从可信资料重新取得实际范围,并在原写事务
180
+ 重新核验职责成员、规范化姓名。普通Native创建和无平台业务证明的Application
181
+ 写入不能消费此模式。已有字段不加`creation`时保持原已保存范围要求。
182
+
183
+ 标准`WorkflowSubmissionPage`从正在填写的字段取得范围;范围来自可信准备资料、
184
+ 未绑定为可提交输入时,使用`formOptions.candidateScopeValues`提供。该参数不进入
185
+ 提交或草稿。自定义Field Kit通过`renderers.candidateScopeValues`提供相同搜索上下文。
186
+ 更新和任务候选仍从平台已保存资料取范围,不能传`scopeValue`覆盖。
187
+
168
188
  流程使用 `{ provider: 'form_field_users', inputPath: 'leaders', candidateField: 'unitLeaders' }`,
169
189
  其中 `subject.factProjection.leaders` 必须精确指向 `unitLeaders`,范围字段也须有唯一事实投影。
170
190
  不能另写职责、范围或路由覆盖来源,不能用嵌套输入路径绕过候选字段。
@@ -189,8 +209,16 @@ import { AttachmentFileList } from 'openxiangda/field-kit';
189
209
  ```ts
190
210
  { code: 'reminderMinute', type: 'time', label: '提醒时间', timePrecision: 'minute' }
191
211
  { code: 'checkpointSecond', type: 'time', label: '检查时间', timePrecision: 'second' }
212
+ { code: 'meetingStart', type: 'datetime', label: '会议开始', timePrecision: 'minute' }
213
+ { code: 'usageTimes', type: 'datetime-range', label: '使用时间', rangeBoundary: 'closed', timePrecision: 'minute' }
192
214
  ```
193
215
 
216
+ `datetime`和`datetime-range`也可声明`timePrecision`。分钟声明同时驱动PC/手机
217
+ 输入、筛选和只读显示;保存仍为ISO instant,秒及小数秒非零时服务端拒绝,
218
+ 不会自动截断原值。区间的精度应用于两端,闭/半开边界分别保留。直接组件已有
219
+ `minuteStep`时继续使用更严格步长;无需在每个页面重复设置步长。未声明或声明
220
+ `second`沿用既有日期时间保存行为。`date`与`date-range`不使用时间精度。
221
+
194
222
  定位只支持钉钉定位或浏览器 Geolocation 采集 WGS84 经纬度。组件没有地址输入、
195
223
  手工定位或地图选点;服务商返回的地址/POI 只能作为该坐标的只读显示快照。
196
224
 
@@ -303,3 +331,18 @@ ISO instant)与 `minuteStep`(1 到 60 且整除 60)。设置步长后只
303
331
 
304
332
  `rating` 需要支持该控件的编译器、前端包与服务端 Surface 校验组合;存储、查询和校验仍为
305
333
  整数。它不会修改已有 `number.integer` 字段的默认数值输入控件。
334
+
335
+ ### 子表提交行数
336
+
337
+ 单个子表 `maxRows` 最多500。同一模型下所有子表的声明上限合计默认最多500;
338
+ 历史快照加行程、多种材料等场景可在模型上显式声明 `ownedRowLimit: 550` 或
339
+ `ownedRowLimit: 1000`,最多1000。此限额随模型编译到Native资源,不由提交请求指定。
340
+ 扩展声明需要平台支持 `data.aggregate-owned-subtable-capacity`;旧平台在激活前拒绝。
341
+ 标准表单、任务补填、具名初建和级联删除共用该限额与原子事务。完整替换最多2016个
342
+ 操作,但一次事务/草稿/任务值仍限2MiB;超额或最后行版本冲突整笔失败,不自动裁剪或分批写。
343
+
344
+ `subtable.minRows`为可选整数,默认0,不能超过`maxRows`(默认20);恰好一行可用
345
+ `minRows: 1, maxRows: 1`。它约束父表提交的owned计划,独立子表写权限仍应关闭。
346
+ 普通新建即使省略表也检查;编辑只检查本次提交的表。任务草稿/保存允许不足,
347
+ 完成时对可见可写表检查最终行数,删除标记不算行。旧固定声明不隐式补造数据。
348
+ 标准PC/移动表单显示下限并保留字段校验;应用可提供初始空行,平台不生成可信资料。
@@ -121,6 +121,36 @@ AppSpec 随开发持续维护:测试发布前补齐总纲、关联变更与验
121
121
 
122
122
  前端启动使用根目录 `dev:web`。声明了后端的应用同时运行配置的 `backend.root` 包内的 `dev`,无需增加根目录 `dev:server`;纯前端应用只启动 Web 和代理。已有前端测试版本后,可以启用本地 Nest 联调,不必先构建或发布后端镜像。平台返回的活动应用版本和开发会话绑定本次联调,环境版本变化导致会话不一致时,停止后重新运行 `dev`。
123
123
 
124
+ 在已有测试环境的应用中,`dev` 先将当前完整资源、权限、具名操作和固定流程配置
125
+ 提交给平台。平台使用同源编译器校验配置与契约,保存真实配置版本和同步回执,
126
+ 选择该应用 test 的配置 Head,然后 CLI 重新读取环境并创建开发会话。这个过程
127
+ 不运行应用 `build`、打包或容器部署;代码继续在本机运行。相同配置与契约再次启动
128
+ 时复用当前版本;旧 Head 的开发会话失效,需要重新启动。新增模型和流程可以在
129
+ 同一批开发中联调,不需要每次冻结 SDK 和正式部署应用。
130
+
131
+ 完整开发配置要求平台提供 `application.development-configuration` 1.0.0,并且
132
+ 应用已有 test Native Head;当前尚不支持无测试基线的新应用直接同步。同步会改变
133
+ test 的配置选择,历史正式版本、数据和原运行保留。开发配置不能用于生产晋级、
134
+ 正式部署或回滚候选。
135
+
136
+ 声明后端的应用还要求 `application.development-backend-events` 1.0.0。CLI自动连接
137
+ 当前test开发版本,把平台已签名的后台事件和流程业务步骤投递到本机Nest源码,
138
+ 并复用平台的持久执行回执与失败恢复。应用运行凭据只进入本地Nest环境,不传入Web、
139
+ 页面或终端状态。开发连接关闭或失联时,事件保留在平台队列,不回退到旧后端制品;
140
+ 同一测试环境同时只能连接一个开发会话,环境版本变化后重新运行dev。
141
+ 临时运输故障会在20秒内恢复原连接;已执行处理器的回复只重发原响应,不重复执行业务。
142
+ 授权、环境版本或连接归属变化会立即停止;超过恢复窗口则保留平台原执行记录,重新运行dev继续。
143
+
144
+ 开发连接会启用当前后台事件订阅,保留手工暂停;正常关闭时暂停本连接开启的订阅。
145
+ Timer和DateTrigger继续暂停,日期提醒仍须确认测试副作用意图并完成专门验收。
146
+ 当前投递范围是标准后台事件与流程业务步骤;自定义审批人provider、managed-command
147
+ planner等调用仍须正式后端或后续能力。不要把本地开发者联调视为普通角色验收。
148
+
149
+ 同步失败保留原配置运行 ID;相同输入重试复用原回执与版本,并由平台锁和 Head
150
+ CAS 判断是否允许继续。并发请求返回进行中;服务重启释放锁后允许恢复原运行。
151
+ 不要修改环境 ID、直接写配置表或换随机键绕过错误。平台缺能力时更新配套平台,
152
+ 不通过重复打包应用规避开发能力缺口。
153
+
124
154
  纯 CRUD 修改优先使用标准模型、字段和页面;跨模型事务或外部副作用再选择后端。角色、行和字段权限在平台执行。详见[开发流程](development.md)、[模型与标准 CRUD](application-foundation.md)和[按需后端](backend.md)。
125
155
 
126
156
  ## 应用源码
@@ -227,6 +257,29 @@ openxiangda link rebind --base-url https://platform-b.example.com # 显式换
227
257
  幂等初始化处暴露,随时可以再次 rebind 修正。详细决策记录见仓库
228
258
  `docs/architecture-decisions/workspace-platform-rebind.md`。
229
259
 
260
+ ### 普通角色与应用登录联调
261
+
262
+ `pnpm openxiangda dev --identity browser` 使用应用自身的登录入口验证普通用户;
263
+ 新模板已接入开发模式挂载插件。既有 Vite 应用在 `vite.config.ts` 中从
264
+ `openxiangda/config` 导入 `createConnectedDevelopmentVitePlugin`,加入 `plugins`。
265
+ 插件只在 CLI 的普通身份连接开发模式下提供标准运行时挂载信息;正式构建及
266
+ 默认开发者模式不受影响,登录、Cookie、CSRF 与实时角色仍由平台管理。
267
+ 退出后仍保持应用登录模式。默认的 `developer` 模式方便开发者联调,但已携带应用
268
+ 浏览器 cookie 或不同显式 Bearer 的请求保留原身份。失效 cookie 由平台拒绝,不会
269
+ 回退开发者身份。登录、退出和 CSRF 均由平台处理。
270
+
271
+ 普通用户的 Data API 和业务操作经过平台授权。预发布开发配置已激活、平台支持
272
+ `application.development-backend-invocations` 且当前 CLI 连接存活时,已声明的业务
273
+ operation 通过平台网关进入本地 Nest 源码;浏览器身份、CSRF 和原幂等键保持不变。
274
+ 平台在派发和 Nest 验证时重查身份、角色与当前版本,浏览器不会取得开发者凭据。
275
+
276
+ 这条联调路径支持有限缓冲 HTTP:请求最多 256 KiB,响应最多 1 MiB,每环境事件与
277
+ 业务调用共用 8 个运输槽,普通调用最多等待 30 秒;SSE 明确拒绝,托管文件使用平台
278
+ 文件接口。关闭或撤销连接、停止环境、切换 Head 都使旧调用失效。回执丢失沿原
279
+ 意图查询或恢复,不生成第二次业务提交。正式部署仍走已激活的正式后端。
280
+ 旧平台与事件专用连接不支持此路径;启用后端的 `--identity browser` 会提示配套升级。
281
+ 源码测试与批次正式构建、激活、普通角色验收分别记录。
282
+
230
283
  ## 检查与交付 {#delivery}
231
284
 
232
285
  只检查时运行 `pnpm openxiangda check`。需要部署测试环境时直接运行 `pnpm openxiangda deploy`,它已包含检查、测试和构建;无需再连续重复运行全部脚本。
@@ -49,9 +49,9 @@ export async function readOffer(
49
49
 
50
50
  将读取接入应用已有的异步查询状态:初次加载显示局部骨架,空数组显示无可展示内容,失败提供局部重试。组件卸载或资源变化时中止旧请求,迟到响应不能覆盖新资源。同页多个观察者可复用应用已有查询层,查询键包含 `client.scope`、读取 code 和规范化参数,身份变化时清除旧主体视图;当前没有内置的 `useManagedRead` hook。
51
51
 
52
- ### 只读预算繁忙恢复
52
+ ### 只读繁忙与断连恢复
53
53
 
54
- `read`、`mine`、`result`、`allocation` 共用有界恢复:单次调用默认含 HTTP 和等待的总预算 120 秒,最多 12 次总请求。应用可以通过 `budgetMs` 显式延长;超过 120 秒时最多 120 次总请求,预算上限为 30 分钟,超出上限按 30 分钟处理。只重试 HTTP 429 的 `CONCURRENCY_API_BUSY`、`CONCURRENCY_RESULT_BUSY`、`CONCURRENCY_RATE_LIMITED`、`CONCURRENCY_SOURCE_BUSY`;兼容旧平台的 HTTP 503 仅限前两个已知预算错误。明确 `retryable: false`、未知 429、权限拒绝、Redis/数据库/授权依赖失败和网络失败直接返回,不用繁忙重试掩盖。
54
+ `read`、`mine`、`result`、`allocation` 共用有界恢复:单次调用默认含 HTTP 和等待的总预算 120 秒,最多 12 次总请求。应用可以通过 `budgetMs` 显式延长;超过 120 秒时最多 120 次总请求,预算上限为 30 分钟,超出上限按 30 分钟处理。可恢复的预算响应为 HTTP 429 的 `CONCURRENCY_API_BUSY`、`CONCURRENCY_RESULT_BUSY`、`CONCURRENCY_RATE_LIMITED`、`CONCURRENCY_SOURCE_BUSY`;兼容旧平台的 HTTP 503 仅限前两个已知预算错误。认证传输层标记为 HTTP 503、`PLATFORM_TRANSPORT_UNAVAILABLE` 的连接中断也按原预算恢复。明确 `retryable: false`、未标记的网络错误、未知 429、权限拒绝、Redis/数据库/授权依赖失败直接返回;连接中断不证明请求已受理或业务失败。
55
55
 
56
56
  第一次失败后的基础等待为 2 秒,之后指数增加到最多 30 秒;取其与有效服务端提示的较大值,再加 0% 至 25% 随机抖动。提示优先使用合法 `Retry-After`(秒或 HTTP 日期),缺失时使用 `data.retryAfterMs`。若等待达到剩余预算,不提前查询,直接返回最后一次繁忙错误;请求次数用完也保留最后繁忙响应。正在进行的请求超过总预算则中止并返回 `CONCURRENCY_READ_RECOVERY_EXHAUSTED`,不以之前的繁忙响应掩盖悬挂请求。
57
57
 
@@ -236,10 +236,16 @@ hook 从 `openxiangda/react` 和 `openxiangda/mobile` 导出。state、initializ
236
236
 
237
237
  已受理申请的自动观察最多持续到首次明确提交后的 30 分钟,默认受理恢复仍为 120 秒,两者分别计算。自动观察的每次结果读取同时受单次 120 秒和原提交剩余时间限制;刷新和 resume 不重新获得观察时间。跨设备没有本地首次时间时,用原回执 acceptedAt 计算观察窗口,不能以页面挂载时间重新计时。达到窗口后保留原意图,状态为 recovering、isObserving 为 false,提示稍后核对;明确 refresh 仍可用有界初查预算查询迟到终态,但不重新启动已经到期的自动观察,也不自动提交。
238
238
 
239
- 正常待处理结果每次至少间隔 5 秒,遵守更长的服务端 retryAfterMs,再加随机抖动。单次只读恢复耗尽且仍是已知预算繁忙时,外层可在原观察窗口内指数退避继续。SDK 自身明确标记为 status=504、retryable=true 的 CONCURRENCY_READ_RECOVERY_EXHAUSTED 也只在已受理原结果观察中按退避恢复;它不代表服务端忙,不延长原三十分钟截止,也不触发再次提交。权限、依赖、普通网络错误、其他 504 或未标记可恢复的错误立即停止自动观察,显示 error 并保留原回执和请求键。终态停止。受理恢复中核对原结果同样只允许已知预算繁忙继续,读取依赖失败不能被外层重试隐藏。一个业务区域只挂载一个观察者。离开或关闭页面不撤销已受理请求,平台自动继续;新设备通过 mine 找到本人的原请求。position 为空时显示「已受理,稍后可查看」,不要显示虚假的精确人数或预计秒数。
239
+ 正常待处理结果每次至少间隔 5 秒,遵守更长的服务端 retryAfterMs,再加随机抖动。单次只读恢复耗尽且仍是已知预算繁忙或认证传输层标记的 `PLATFORM_TRANSPORT_UNAVAILABLE`/503 时,外层可在原观察窗口内指数退避继续。SDK 自身明确标记为 status=504、retryable=true 的 CONCURRENCY_READ_RECOVERY_EXHAUSTED 也只在已受理原结果观察中按退避恢复;它不代表服务端忙,不延长原三十分钟截止,也不触发再次提交。权限、依赖、未标记的网络错误、其他 504 或未标记可恢复的错误立即停止自动观察,显示 error 并保留原回执和请求键。终态停止。受理恢复中核对原结果的连接中断由同一有界只读层恢复,内层耗尽不触发 enqueue 重放,读取依赖失败不能被外层重试隐藏。一个业务区域只挂载一个观察者。离开或关闭页面不撤销已受理请求,平台自动继续;新设备通过 mine 找到本人的原请求。position 为空时显示「已受理,稍后可查看」,不要显示虚假的精确人数或预计秒数。
240
240
 
241
- refresh 有本地原键时先薄查询原 key;找到已受理非终态后不再 mine。没有本地意图或原申请已有终态时,mine 优先恢复新的进行中周期,避免本地历史 succeeded 遮蔽另一个设备的新申请。原键明确 404 后保留一次本人列表兜底;未确认的本地意图只能采用同原键回执。列表中只有别的活跃周期时显示 recovering / CONCURRENCY_ORIGINAL_REQUEST_REQUIRED,并保留原 key/input;没有匹配时显示 CONCURRENCY_ACCEPTANCE_UNCONFIRMED,提供「核对原申请」和「恢复原申请」动作。不能把无匹配解释为业务失败,也不丢弃可能迟到受理的原意图。已知读取繁忙耗尽显示 recovering,真实依赖、网络、权限或未知 400 显示 error;两种状态均保留原键、输入和已受理回执,读取失败不自动提交。
241
+ refresh 有本地原键时先薄查询原 key;找到已受理非终态后不再 mine。没有本地意图或原申请已有终态时,mine 优先恢复新的进行中周期,避免本地历史 succeeded 遮蔽另一个设备的新申请。原键明确 404 后保留一次本人列表兜底;未确认的本地意图只能采用同原键回执。列表中只有别的活跃周期时显示 recovering / CONCURRENCY_ORIGINAL_REQUEST_REQUIRED,并保留原 key/input;没有匹配时显示 CONCURRENCY_ACCEPTANCE_UNCONFIRMED,提供「核对原申请」和「恢复原申请」动作。不能把无匹配解释为业务失败,也不丢弃可能迟到受理的原意图。已知读取繁忙耗尽显示 recovering,认证传输层标记的断连先在原预算内恢复;真实依赖、未标记的网络错误、权限或未知 400 显示 error。两种状态均保留原键、输入和已受理回执,读取失败不自动提交。
242
242
 
243
243
  不要在 mount 发现历史 succeeded 时自动跳成功页或永久禁用提交。它可能已经被管理员取消,需结合当前业务记录展示。只有用户明确再次点击 submit,且原请求已有终态,SDK 才创建新的 requestKey;活跃请求或未知应答始终恢复原 key。平台明确返回未受理的参数错误(400 + CONCURRENCY_INPUT_INVALID 等约定错误)时,SDK 才清除被拒输入,允许修正后再提交;未知 400、409、429、5xx 和网络错误仍保留原意图。成功提示以 receipt.state==='succeeded' 和 receipt.result 为准,accepted/executing 只显示「已登记,处理中」。
244
244
 
245
245
  permit 的 ManagedCommandGate 仍只用于 admitted 短确认,不用于 durable。durable 不能套任意前端 onSubmit 冒充后台事务,真正业务必须由已声明的 backend-plan handler 返回受管计划。
246
+
247
+ ### 入口等待与身份加载连续性
248
+
249
+ 平台启用入口排队时,SDK 的当前用户读取只对明确的入口繁忙响应接续等待:HTTP 429、`CONCURRENCY_BOOTSTRAP_BUSY`、可重试以及有效的等待状态与 `remainingMs`。第一次有效回执固定等待截止,后续回执只能缩短,最长不超过首次读取开始后的三十分钟。查询至少间隔 2 秒,尊重更长的服务端提示,再加最多 20% 抖动;例如 60 秒提示实际等待 60–72 秒,不提前压成 15 秒。若提示达到剩余等待预算,保留最后响应并停止,不能提前查询或刷新截止。不会因普通繁忙或网络错误无限延长。普通身份读取仍使用五分钟恢复预算;权限拒绝、版本变化、入口过期和满额终止恢复。单次读取、投影和网络失败的限制保持独立。
250
+
251
+ 该行为只读取平台身份,不替应用发起或重放报名,也不改变已经受理命令的原始请求键与结果截止。上线容量需在使用该 SDK 的实际应用制品上重新验证。
@@ -94,6 +94,60 @@ events: {
94
94
  时长,十年内,如 `-PT1H` 表示提前一小时)到达时触发。适合到期提醒、超期升级等场景;
95
95
  同一条记录的字段更新后按新值重算。`code` 唯一,`eventType` 同样引用已声明应用事件。
96
96
 
97
+ 日期事件自动带上 `triggerCode`、`resourceCode`、`recordId`、`recordRevision`、`field`
98
+ 和 `dueAt`;`payload` 显式写普通对象(无自定义内容时写 `{}`),不能覆盖这些字段或
99
+ `projection`。需要申请人、提醒正文等记录资料时,复用订阅的 `payload.fields`:平台
100
+ 取同一事件修订中、事件类型及资源匹配的字段并集,和日期、记录修订一起读取真实
101
+ Native 值,写入事件 `projection`。每个消费者收到的投影再按自身字段声明裁剪。
102
+
103
+ 例如,应用代码为 `reference-app`、资源 `bookings` 包含 `owner`(user.single)、
104
+ `reminderText`(text.long)、`expiresAt`(datetime)时,可以声明:
105
+
106
+ ```ts
107
+ events: {
108
+ schemas: [{
109
+ eventType: 'reference-app.booking.expiry.v1', dataSchemaVersion: '1.0.0',
110
+ jsonSchema: {
111
+ type: 'object', additionalProperties: false,
112
+ required: ['triggerCode', 'resourceCode', 'recordId', 'recordRevision', 'field', 'dueAt', 'projection'],
113
+ properties: {
114
+ triggerCode: { const: 'booking-expiry' }, resourceCode: { const: 'bookings' },
115
+ recordId: { type: 'string', format: 'uuid' }, recordRevision: { type: 'integer', minimum: 1 },
116
+ field: { const: 'expiresAt' }, dueAt: { type: 'string', format: 'date-time' },
117
+ projection: {
118
+ type: 'object', additionalProperties: false, required: ['owner', 'reminderText'],
119
+ properties: {
120
+ owner: { type: 'object', additionalProperties: false, required: ['value', 'label'],
121
+ properties: { value: { type: 'string', format: 'uuid' }, label: { type: 'string' } } },
122
+ reminderText: { type: 'string', minLength: 1, maxLength: 2000 },
123
+ },
124
+ },
125
+ },
126
+ },
127
+ }],
128
+ dateTriggers: [{ code: 'booking-expiry', resourceCode: 'bookings', field: 'expiresAt',
129
+ offset: '-PT1H', eventType: 'reference-app.booking.expiry.v1', payload: {} }],
130
+ subscriptions: [{ code: 'booking-expiry-notice', eventTypes: ['reference-app.booking.expiry.v1'],
131
+ filter: { resourceCodes: ['bookings'] }, payload: { includeChanges: false, fields: ['owner', 'reminderText'] },
132
+ platformAccess: { notification: { mode: 'business-standard' } },
133
+ }],
134
+ },
135
+ ```
136
+
137
+ 订阅处理器照常实现。通知的 `sendFromEvent` 使用 `recipientPaths: ['projection.owner.value']`
138
+ 等路径,由平台从已认证事件取得收件人;应用不在发送请求中复制人员 ID。
139
+
140
+ 投影只接受资源声明的顶层字段,禁止子表、路径/SQL表达式及 `mask: 'omit'` 的敏感字段。
141
+ 单订阅最多32字段,同事件投影并集最多64字段,扫描最多100订阅,完整事件不超过
142
+ 64 KiB。投影 JSON Schema 必须是关闭额外属性的 object、声明全部已选字段,不能
143
+ 要求未投影字段。编译阶段检查固定日期封套和结构,运行时才校验真实资料的类型、
144
+ 必填、长度等约束;不生成虚构人员让编译检查通过。资料不满足 schema、来源版本
145
+ 漂移或字段不合法时,该调度意图取消并保留错误,不发送不完整提醒。
146
+
147
+ 有资料投影的日期声明要求平台能力 `events.date-source-projection:1.0.0`;连接旧平台
148
+ 会在能力协商时拒绝。无字段投影的既有日期事件保持原形态及原能力要求。日期事件
149
+ 不自动筛选批准状态;要限定业务状态,应显式设计条件,不能从审批名称推断。
150
+
97
151
  两类触发器各最多 100 条。事件 Schema 用 `events.schemas` 声明
98
152
  (`{ eventType, dataSchemaVersion, jsonSchema, sensitiveFields? }`),`eventType` 遵循
99
153
  `xxx.yyy.v1` 版本后缀模式;触发器只发事件,不直接写数据或调用流程。
@@ -106,6 +160,13 @@ events: {
106
160
 
107
161
  标准任务和实例详情的“返回”由应用 Router 使用同一份 route manifest 中对应设备的流程中心路径;没有中心条目时返回已声明门户。普通用户无需管理后台权限。应用独立消费 `WorkflowTaskPage` 或 `WorkflowInstancePage` 时,可以传入本应用已声明的 `returnPath`,例如 `<WorkflowInstancePage variant="mobile" returnPath="/m/work-center" />`。抽屉的 `onDismiss` 仍负责关闭当前抽屉;返回导航不授予目标页面权限。
108
162
 
163
+ 会签第一票或转交成功后,节点可能继续等待其他人,但原用户的办理席位已经结束。
164
+ 标准页面确认旧任务不可读时重新核验实例访问,再显示当前进度;不重投原命令。
165
+ 嵌入任务面板的 `onCommandCompleted(result, context)` 保留原平台结果,第二参数
166
+ `taskSurfaceUnavailable` 为 SDK 的成功后读取事实,供宿主关闭旧办理面并读取实例。
167
+ 通用网络错误或无关权限拒绝仍显示读取失败;已确认成功的读取失败允许返回,
168
+ 未知写入继续要求先查询原结果。`advanced` 仍只表示流程推进,不表示个人席位状态。
169
+
109
170
  抄送由动态 Surface 提供 `cc` 操作,使用 `user_select` 多选组件收集 1 至 20 位人员。任务 Surface 可以正式包含实例抄送命令;前端按 `operation.execute.href` 和该 Surface 的 token 提交,不另造任务命令或 token。公共事件为 `openxiangda.workflow.instance.cc_added.v2`。
110
171
 
111
172
  ## 钉钉卡片已读查询
@@ -396,8 +457,10 @@ Workflow instance-scoped preview/content 路由,平台在每次文件读取时
396
457
  `/m/workflows/:workflowCode/start`。definition 必须显式声明
397
458
  `launch: { mode: 'standalone' | 'hidden-handoff' | ... }`,缺失会被编译器拒绝;
398
459
  factProjection 把 option/user/department/resource-ref/cascade 字段投影为
399
- `{ label, value }` 对象,对应 inputSchema 属性必须声明为 `type: 'object'`
400
- (multiple 类字段为 array + object items),条件表达式用 `path: '<fact>.value'`
460
+ `{ label, value }` 对象,对应 inputSchema 属性声明为 `type: 'object'`;
461
+ 允许尚未填写的单值引用显式使用 `type: ['object', 'null']`,数组顺序无关。
462
+ Native必填校验仍执行;multiple类字段保持array + object items(cascade.multiple
463
+ 为array + array + object)。条件表达式用 `path: '<fact>.value'`
401
464
  比较;声明成标量会在运行时 INPUT_SCHEMA_MISMATCH 并无限重试,编译器现已拦截。`standalone`/`hidden-handoff` 缺省使用同一个
402
465
  compiler-owned `processOperationCode`、subject declaration 和标准 process commit;平台在一个
403
466
  事务中写业务数据和 durable command。action-owned 资源改为声明
@@ -440,6 +503,19 @@ return <WorkflowSubmissionPage workflowCode="reinstatement" variant="mobile" for
440
503
  及已接受命令的展示优先于该提示。应用应始终挂载标准页,避免当前资格变化遮蔽原结果恢复。
441
504
  这些规则属于应用代码,不能通过管理员节点配置修改;服务器仍独立校验实际业务资格与字段。
442
505
 
506
+ `valueLinkage(changed, values)`在用户输入后同步返回canonical字段patch,例如
507
+ `Object.hasOwn(changed, 'owner') ? { specialist: values.owner ?? null } : {}`。
508
+ 改负责人复制专员,手改专员保留;再次改负责人重新复制,清空用null。回调只能改当前
509
+ 可见、可编辑且匹配launch的字段,不能改系统/只读/隐藏字段或返回Promise。输入是副本,
510
+ 写回沿用codec并标记已触碰,迟到预填不会覆盖联动值。预填、草稿、恢复、程序写回不触发
511
+ 联动;PC/手机规则一致。异常保留输入并阻断新提交,下一次成功输入可恢复,原提交结果
512
+ 查询及已接受命令仍优先。异步资料读取放准备阶段,服务器仍校验资格和业务不变量。
513
+
514
+ 具名owned新建表单可传`subtableReadonlyFields: { lines: ['employeeNumber'] }`,
515
+ 把当前子行的派生值显示为只读,并从提交输入排除。表名与字段必须属于当前sealed
516
+ owned闭包;配置错误会拒绝新表单。值仍可供应用联动展示,Server应重新计算可信值。
517
+ 此选项只收窄页面交互,不授予读写权限,也不改变普通CRUD或审批任务的字段规则。
518
+
443
519
  通用应用待办页通过 `frontend.user.applicationTodoCenter: true` 启用,平台同时提供
444
520
  `/todos` 和 `/m/todos`。它只投影当前登录用户的 Notification Hub 收件人数据,
445
521
  `查看详情` 使用上述统一导航解析;待办页不常驻业务详情,审批命令仍在目标 Workflow
@@ -544,6 +620,31 @@ outbox容量等事务故障会回滚,处理器按原键核对后重交,不
544
620
  等待处理器的失败从原事件管理受控重放,保留原执行键。普通详情只显示安全状态
545
621
  摘要;原输入、输出和外部回执不放入普通时间线。
546
622
 
623
+ ### 在固定步骤内写入业务数据 {#step-data-transaction}
624
+
625
+ 审批中途需要更新台账时,使用 `reconciled-effect` 动作,并在固定 `handler` 上声明
626
+ `dataTransaction: true`。编译器自动要求 `workflow.step-data-transaction@1.0.0`,
627
+ 标记进入签名处理器合同;旧具名版本不可直接改变,增加新版本时保留原实现。
628
+
629
+ 在已验证并领取的事件处理器内,注入 `OpenXiangdaApplicationDataApiService`。
630
+ `reconcile` 先调用 `resolveWorkflowStepData()`:`committed` 用原事务结果重建固定
631
+ `output/receipt`,`not_observed` 返回 `not-executed`,响应未知或失败保留原执行键。
632
+ `handle` 调用 `commitWorkflowStepData({ guards, operations })`。执行ID从事件上下文
633
+ 取得,应用不传人、角色、下一节点或幂等键。每个 execution 只允许一个 Native 事务,
634
+ 内容不同返回冲突;普通事务不能使用保留的 `workflow-step:` 键。
635
+
636
+ 计划必须包含流程 subject 的 `record-match` 或 `record-assert` guard,其 `revision`
637
+ 等于事件数据的固定 `dataRevision`。Native 继续强制来源读取权限、目标写入权限和
638
+ CAS;此接口不能直接修改流程 subject,任务补填仍使用原任务页面。数据计划遵循
639
+ Native 的2016操作、2MiB限制,不支持 decimalReservation 或排队命令。复用原应用
640
+ `data:read/data:write/data:transaction` 权限,不额外授予全校数据读取。
641
+
642
+ 平台先取得环境围栏、实例和步骤锁,再检查有效投递/领取租约,最后执行数据计划。
643
+ 业务行、数据事件、Native回执和步骤的回执引用一起提交;任一行失败或租约过期全部
644
+ 回滚。取消先取得锁时拒绝新写;数据先提交后取消、换阶段或换Head,resolve仍恢复
645
+ 同一原回执。原回执缺失时明确拒绝,不能以未知结果为由重新写入。步骤输出和后续
646
+ 流转继续由原事件回执处理,晚拒绝不自动撤销已写台账;补偿属于应用显式业务规则。
647
+
547
648
  ## 代码任务页面与补填 {#task-page-submit}
548
649
 
549
650
  需要办理人补填时,在定义的 `taskPages` 声明命名页面,审批节点以
@@ -557,6 +658,50 @@ outbox容量等事务故障会回滚,处理器按原键核对后重交,不
557
658
  系统、流水号和隐藏字段不可作为补填入口。根附件、图片复用平台托管文件;
558
659
  签名和富文本同样保留 Native 字段协议;普通 owned 子行以固定页面白名单和行 CAS 提交。
559
660
 
661
+ ### 任务内的资源引用选择
662
+
663
+ 任务页面中的 `resource-ref.single` / `resource-ref.multiple` 自动使用当前任务的选择接口。
664
+ 当前办理人不需要业务资料的普通 `update` 权限,但必须拥有目标字典的 `read` 权限;
665
+ 字段来源、读取投影、行权限和有界分页继续由 Native Data 执行。
666
+ 仅当前固定 taskPage 中可见且可填写的根字段能够查询,隐藏、只读、条件尚未生效的字段
667
+ 和子表字段拒绝查询。字段显隐与填写规则仍归页面声明。
668
+
669
+ 自定义任务表单使用公开 `searchResource` 时传入当前 Surface 的 task ID、业务资料修订及
670
+ 任务版本;不能把 task 与普通 create、launch 或管理员 record edit 上下文混用:
671
+
672
+ ```ts
673
+ await searchResource(resourceCode, fieldCode, {
674
+ operation: 'update', keyword: '司机',
675
+ task: { taskId, expectedRevision: businessDetail.sourceRevision, expectedTaskVersion: task.version },
676
+ });
677
+ ```
678
+
679
+ 底层请求使用 `workflow-task-field-source-query/v2`,返回已有的
680
+ `data-field-source-page/v2`。依赖字段的过滤仅使用声明的 bindings:当前可填写的依赖
681
+ 可取表单值,显式清空会返回空选项;只读或不在当前页面的依赖取已保存资料。
682
+ 游标绑定当前任务、版本、页面、定义和资料修订。409 时刷新任务并保留用户输入,
683
+ 403 时展示实际拒绝,不扩大普通 CRUD 权限。标准 PC 和移动任务组件自动传递这些上下文。
684
+
685
+ ### 前后端共享的表达式计算
686
+
687
+ PC、手机与 Nest 共用的业务模块从纯入口导入,应用仍只声明统一根包精确依赖:
688
+
689
+ ```ts
690
+ import { evaluateWorkflowTaskPageExpression, type WorkflowExpression } from 'openxiangda/expressions';
691
+
692
+ const when: WorkflowExpression = {
693
+ op: 'gt',
694
+ left: { op: 'path', path: 'values.amount' },
695
+ right: { op: 'literal', value: 1000 },
696
+ };
697
+ const needsReview = Boolean(evaluateWorkflowTaskPageExpression(when, { amount: 1500 }));
698
+ ```
699
+
700
+ 路径以 `values.` 开始,第二参数直接传字段值对象。此入口复用原任务页计算器,
701
+ 支持浏览器打包器和原生 Node ESM;共享模块不要导入浏览器传输入口
702
+ `openxiangda/core`,也不直接依赖内部物理包。计算结果不授予审批或数据权限,
703
+ 真实流转和写入继续由平台固定定义与当前用户授权核验。
704
+
560
705
  ### 标准流程的主子表发起
561
706
 
562
707
  标准发起页复用 Field Kit 的 PC/手机子表,通过原 `standard-commands` 的
@@ -568,8 +713,35 @@ outbox容量等事务故障会回滚,处理器按原键核对后重交,不
568
713
  发起仍执行当前用户的父、子资源及字段授权。拥有主表权限不会自动得到通用子表权限。
569
714
  已有记录提交核对父 CAS 和完整已读子行集合,包括未修改行;删除显式保留原 id/revision,
570
715
  跨单、遗漏、重复或陈旧行均拒绝。原命令已提交时先返回同一回执,再也不重新展开当前行。
571
- 每表100/累计400、完整意图双倍、整请求2MiB及展开后1000操作与 Native 预算一致。
572
- 普通显式 Named Action 的16个业务操作预算不变;其自定义子表提交仍按自身合同实现。
716
+ 每表最多500/累计500、完整意图双倍、整请求2MiB及展开后1016操作与 Native 预算一致。
717
+ 超过原单表100或累计400的声明自动要求 `data.extended-owned-subtable-capacity@1.0.0`;
718
+ 较小声明及默认20行不自动扩容,新能力由支持的Server自动开启。
719
+ 普通显式 Named Action 的16个业务操作预算不变。
720
+
721
+ 具名发起操作需要标准页填写一层子表时,在该操作声明固定的创建闭包:
722
+
723
+ ```ts
724
+ platformAccess: {
725
+ workflow: { codes: ['expense-approval'] },
726
+ ownedSubject: {
727
+ mode: 'create', resourceCode: 'expense-requests',
728
+ subtables: [{ fieldCode: 'items', fieldCodes: ['description', 'amount', 'evidence'] }],
729
+ },
730
+ managedFiles: [{ resourceCode: 'expense-items', fieldCodes: ['evidence'], intents: ['create'] }],
731
+ }
732
+ ```
733
+
734
+ `items` 必须是主体模型声明的一层 owned 关系。子字段清单不含外键、排序字段、嵌套子表或序号。
735
+ 含文件、图片、签名或富文本的字段同时声明该操作的 `managedFiles` 创建权限。平台自动核验
736
+ `workflow.named-owned-create@1.0.0` 能力,无需手动开关。
737
+
738
+ 标准 PC/移动页从发起 Surface 读取此闭包,填写新的子行,上传通过原具名操作执行。
739
+ 申请人不需要子资源的普通 create/update/delete 权限。应用后端完成业务校验后,
740
+ `BusinessProcess.commit` 只传一个主体 create,其 `data` 包含上述行结构,subject 指向此操作。
741
+ 平台在同一 Native 事务内展开子行、维护外键与顺序并接受流程命令;原回执恢复先于子行规划。
742
+ 这一契约只支持新建,拒绝 existing/update、混合显式操作以及 decimal reservation;
743
+ 原有不声明 `ownedSubject` 的具名操作继续使用显式事务。最多16张子表、合计500行,
744
+ 仍执行原事务字节、字段、文件与授权检查。
573
745
 
574
746
  ### 任务 owned 子表
575
747
 
@@ -585,11 +757,11 @@ outbox容量等事务故障会回滚,处理器按原键核对后重交,不
585
757
 
586
758
  关系沿用模型的 `resourceCode/foreignKey/orderField/maxRows`,不接受任务调用者自报。
587
759
  子字段可声明同样的只读、可见和条件必填规则;这些属于页面代码,管理员不能覆盖。
588
- `create/delete/reorder` 省略时关闭。仅一层,每表 maxRows 最多100,父资源及任务页
589
- 声明总量最多400;默认仍20。完整意图最多为每表 maxRows 的两倍,可在一次提交中
760
+ `create/delete/reorder` 省略时关闭。仅一层,每表 maxRows 最多500,父资源及任务页
761
+ 声明总量最多500;默认仍20。完整意图最多为每表 maxRows 的两倍,可在一次提交中
590
762
  删除满表旧行并新增同等数量,仍按有效行数校验上限。整个 form/任务私有草稿值限1MiB,
591
763
  页面定义仍64KiB/64根字段;系统、隐藏、关系键和顺序字段不进入子字段白名单。
592
- 标准主子表提交复用同一个 Native 原子事务,最多1000操作/2MiB(400行全量替换加主表为801操作),
764
+ 标准主子表提交复用同一个 Native 原子事务,最多1016操作/2MiB(500行全量替换加主表为1001操作),
593
765
  任一行失败全单回滚,不静默截断或分批提交。普通表单草稿values最多2MiB,含原值与当前值;
594
766
  状态字段的独立配额不变。附件上传数量/字节与详情读取预算仍独立执行,行数容量不豁免文件配额。
595
767
  子行支持 file/image/signature/text.rich,使用同一个任务上传入口并绑定准确行;普通
@@ -645,8 +817,12 @@ approve/resubmit 携带 `form: { expectedRevision, values }` 时,Native 业务
645
817
  拒绝后的读取失败明确提示本次未提交,锁住旧操作,恢复读取后再核对;未知结果仍只能恢复原请求。
646
818
  操作确认表单使用同一 Surface 的必填、字符数与非空白规则校验意见和原因。
647
819
  拒绝意见默认必填。固定节点显式设置 `operationPolicy.reject.commentRequired: false` 时允许省略或空意见,目标须具备 `workflow.optional-rejection-comment@1.0.0`;已进入任务使用冻结规则,后续配置不追溯。选填仍限制为最多4000字符的字符串,不写入假意见。
648
- `commentRequired` 只配置同意/拒绝的 `comment`;转交、委托、加签、退回与管理员改派
649
- 继续使用原合同的必填 `reason`,不用再填写第二份意见。
820
+ `commentRequired` 只配置同意/拒绝的 `comment`。转交、委托、加签、退回使用 `reason`,
821
+ 默认必填;固定节点可显式声明如 `operationPolicy.transfer: { reasonRequired: false }`,
822
+ 目标须具备 `workflow.optional-operation-reason@1.0.0`,平台自动提供,无需初始化开关。
823
+ 选填允许省略、空字符串或空白,仍拒绝非字符串和超过4000字符的值,不生成代替原因。
824
+ 开放对应 `administration.operations` 后,管理员可以收紧选填规则,不能放宽代码必填规则;
825
+ 已进入的任务保留冻结规则。管理员改派始终必填,不用再填写第二份审批意见。
650
826
  明确拒绝后,实例、任务版本及操作签名仍一致且操作仍可用时,对话框保留输入供人工核对;
651
827
  任务、权限、字段规则或版本变化时不能沿用旧操作。刷新和保留输入都不会自动重发请求。
652
828
  补填刷新固定事实投影;
@@ -770,6 +946,42 @@ append 处理签名或 HTML。当前任务、实例、记录或字段改变时
770
946
 
771
947
  退回补正(return_review)必须有人实际处理,不能靠空人跳过;重提后的正常 replay/resume 流转继续采用原节点策略。使用此声明时,生成契约要求目标平台支持 `workflow.approval-empty-policy@1.0.0`,缺少该能力的旧服务端不能接收。
772
948
 
949
+ ## 审批通过后整批授权代理 {#approved-delegation}
950
+
951
+ `subject.factProjection`可以引用主体已声明的owned子表。平台在Native创建事务中按真实
952
+ 父记录读取明细,输出带持久行`key`的扁平业务对象;外键、排序和系统字段不进入事实。
953
+ 日期、数字和人员引用采用Native规范值,按声明排序字段及id稳定排序。所有本次创建行
954
+ 必须经原RLS可见;权限不足、修订或集合变化、单表超限均回滚,不接受客户端自报数组。
955
+ 空的可选子表保留`[]`,单表最大500行,完整流程事实仍限256KiB;初期不支持嵌套子表。
956
+ 声明自动要求`workflow.owned-initial-facts@1.0.0`,平台初始化自动提供,无额外开关。
957
+ 这是初始冻结能力;任务修改owned明细后的事实更新应另行确认,不能用初始快照代替。
958
+
959
+ 代理申请使用固定定义的 `approvedDelegation`:
960
+
961
+ ```ts
962
+ approvedDelegation: {
963
+ confirmationNodeId: 'confirm',
964
+ confirmer: 'delegate', // 或 initiator:由发起人本人确认
965
+ requestsField: 'requests',
966
+ maxRequests: 500,
967
+ },
968
+ ```
969
+
970
+ `requests` 必须是 Native 主体投影到实例的闭合、有界明细数组;只读取固定实例
971
+ `fact_snapshot.data`,每行采用 `WorkflowApprovedDelegationRequest`,保存本人和代理人的
972
+ 职责键及修订、代理人引用、流程/可选节点范围、起止时间与原因。最多500行、整批256KiB。
973
+ 代理人确认的整批必须为同一代理人;同一流程不同节点的明细分别保留。
974
+
975
+ 所有批准路径必须经过声明的单人、人工、非空确认节点。自动处理、超时、管理员跳过、
976
+ 转交和代理确认不能产生确认证据;明细变化或再次退回后必须在当前周期重新确认。
977
+ 最终批准在同一事务核验真实人工任务和双方当前账号、职责、窗口、修订与冲突,
978
+ 然后调用已有平台代理所有者整批授权。任一行失败时,批准、全部授权和批次回执一起回滚。
979
+ 创建、审核中、拒绝、退回和撤回不授权;原命令恢复不重新创建或激活已撤销的授权。
980
+
981
+ 声明自动要求 `workflow.approved-delegation@1.0.0`,无需增加默认关闭的开关。
982
+ 不提供 HTTP/system 代建入口,不授予代理人额外资料权限。图通过 `readability.logic`
983
+ 说明确认、当前职责核验及授权处理;这些逻辑和连线由代码固定。
984
+
773
985
  ## 按业务资料权限查看办理历史 {#record-history-read}
774
986
 
775
987
  发起人、审批人、抄送人使用既有 Workflow 详情。业务查看人员需要根据当前资料范围读取办理记录时,模型可显式声明:
@@ -827,3 +1039,85 @@ launchPreflight: { requiredApprovalNodes: ['departmentReview', 'finalReview'] }
827
1039
  节点必须为实际 approval,最多20个且不能重复;本版只支持 fixed_users、initiator、app_role、app_role_in_scope,不能依赖未来任务补填、外部 provider 或候选字段。BusinessProcess.commit 在业务写入的同一事务内,按当前有效节点配置和真实成员/范围解析这些节点;空人、失效账号、超限或需要尚未提供的交互输入时整笔回滚。emptyPolicy:skip 不能绕过该准入。命中原提交回执时不会重新执行检查。
828
1040
 
829
1041
  这是提交准入,不预先冻结未来审批人;实际进入节点仍重新解析。声明要求目标具备 workflow.launch-preflight@1.0.0,未声明的流程保持原提交行为。实际角色与事务回滚需在目标平台验收。
1042
+
1043
+ ## 审批自动完成期限
1044
+
1045
+ 可选评价等允许不填写内容的审批节点,可以声明固定期限:
1046
+
1047
+ ```ts
1048
+ completionDeadline: { afterSeconds: 600, action: 'approve' }
1049
+ ```
1050
+
1051
+ 此声明需要 `workflow.completion-deadline@1.0.0`。平台在正常任务进入事务中保存
1052
+ 期限,以数据库创建时间计时,默认每30秒扫描,到期可能有扫描及排队延迟。
1053
+ 期限支持1秒至30天,由应用代码拥有,管理员不能改变期限、条件或连接。
1054
+ 没有声明的节点继续人工办理;退回补正和恢复源任务仍须人工处理。
1055
+
1056
+ 到期是整个节点自动完成,剩余审批席位取消,保留已提交内容与真实人工意见,
1057
+ 历史标记“超时自动同意”,不会给申请人或未处理人员伪造审批票。
1058
+ 只允许可选任务输入;必填字段、条件必填、子表必填、`edit_required`、同意意见
1059
+ 必填和自定义 approve handler 与期限冲突,编译及平台注册都会拒绝。
1060
+ 节点配置不能把此类节点改成必须填写同意意见。
1061
+
1062
+ 人工处理与后台执行共用实例锁,先完成者生效。任务离开、退回、撤回、终止或
1063
+ 删除时,在同一事务取消期限。下游失败回滚整次流转,保存脱敏错误码并有界退避;
1064
+ 最多12次失败后停止自动重试,保留人工办理。PC/手机任务详情显示截止时间或
1065
+ 失败提示,管理流程图显示固定规则,实例轨迹显示实际自动完成证据。
1066
+ 本批没有管理员重试或改期限入口。
1067
+
1068
+ 平台重启后继续处理原持久期限,无需额外启用开关。已有期限的版本回滚时,
1069
+ 必须保留兼容的平台后台,或先完成/明确取消在途任务;不能删除期限与历史。
1070
+
1071
+
1072
+ ## 退回发起人补正 {#initiator-correction}
1073
+
1074
+ 数据更正使用独立的固定节点,不作为本人审批票:
1075
+
1076
+ ```ts
1077
+ startAt: 'calculate',
1078
+ taskPages: { correction: { title: '补正申请', fields: [{ code: 'amount', required: true }] } },
1079
+ nodes: {
1080
+ // calculate 和 review 沿应用已声明的代码步骤及审批节点。
1081
+ correct: { id: 'correct', kind: 'correction', title: '发起人补正',
1082
+ taskPageCode: 'correction', next: 'calculate' },
1083
+ // review.returnTargets 声明 ['correct'];其他正向边不能进入 correct。
1084
+ },
1085
+ ```
1086
+
1087
+ `next` 必须等于固定 `startAt`。该节点只能由声明的审批退回进入,包括第一个审批节点;
1088
+ 退回到其他审批节点仍需真实人工办理历史。补正沿当前任务页、Native授权和资料修订,
1089
+ 仅原发起人直接办理,只有保存补填和重新提交,不代理、转交、加签或管理员代填。
1090
+ 原职责失效或修订改变时拒绝,不换身份继续。
1091
+
1092
+ 保存不推进。重新提交在同一事务更新标量资料与事实、关闭补正任务及退回会话,
1093
+ 从固定起点重新执行条件与代码步骤。补正重提记录 `resubmitted`,不产生同意票或
1094
+ 代理申请的人工批准确认。只支持 `replay`,请求 `resume_current` 明确拒绝。
1095
+ 初期补正页不支持 owned 子表;创建时的 owned 事实快照不能代替补正后的事实刷新。
1096
+
1097
+ 图显示旁侧补正节点、直角虚线退回/重提路径,正向分支的层级保持;拓扑和代码仍固定。
1098
+ 声明自动要求 `workflow.initiator-correction@1.0.0`,无需增加默认关闭的开关。
1099
+ 结果未知使用当前任务原命令回执恢复,不重建申请;数据、任务或事件失败一起回滚。
1100
+
1101
+ ### 补正重提的业务校验 {#correction-business-command}
1102
+
1103
+ 补正涉及年限资格、培训日期、报销上限或当前主数据时,字段 Schema 之外还要重新执行
1104
+ 业务规则。在固定定义声明 `commandHandlers.resubmit: { operationCode: 'requests.resubmit' }`,
1105
+ 把该操作的 `platformAccess.workflow` 声明为
1106
+ `{ codes: ['request-approval'], businessCommands: ['resubmit'] }`。
1107
+ 操作必须是受控、强制幂等的 POST,绑定同一父资源的 `recordId`;请求 Schema 使用
1108
+ `openxiangda/config` 导出的 `workflowBusinessCommandInvocationSchema`。
1109
+ 响应使用完整、闭合的实际结果 Schema,不能以开放的顶层对象绕过受控操作检查。
1110
+
1111
+ 标准 PC/手机任务页根据当前 Surface 调用该具名操作;应用不复制审批流转或预测下游
1112
+ 动态人员。处理器先调用 `businessProcess.resolveOriginalTaskCommand(invocation)` 查询原结果,
1113
+ 再合并受限任务字段、重验当前业务规则和资料,最后调用 `commandWithData`,
1114
+ 使用 `expectedTransition: { kind: 'correction-replay' }`。平台验证真实本人补正、原任务与
1115
+ 资料修订,在同一事务保存字段、刷新事实、核验 Native guards、关闭退回会话并重新计算。
1116
+ 业务 mutation 仅写非事实的可信快照或审计字段,不能直接改变 `factProjection` 字段。
1117
+
1118
+ `save_form` 仍可保存未填完的资料;只有通过业务重校验才能重提。直接调用原 Workflow
1119
+ 重提入口会被拒绝,管理员不能替原发起人绕过该处理器。仅声明 resubmit 的流程可保留
1120
+ 普通审批的退回和自动策略;包含 approve/reject/withdraw 业务 handler 的既有限制不变。
1121
+ 自动协商 `workflow.correction-business-command@1.0.0`,无需额外开启功能。
1122
+ 本人标量补正支持 Native 写入,owned 子表补正仍不支持;原已成功结果先于当前资料重验
1123
+ 恢复,不因日期推移或资料后来停用而被误报为失败。接入方法见[后端](backend.md#correction-business-command)。