@stalfh233/omc-cli 0.5.5 → 0.5.7

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 (228) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +359 -359
  3. package/coverage/m1-coverage-manifest-v1.json +1414 -1414
  4. package/dist/approval-flow.js +0 -0
  5. package/dist/args.js +0 -0
  6. package/dist/artifacts.js +0 -0
  7. package/dist/batch-call.js +0 -0
  8. package/dist/bench/call-face-eval.js +0 -0
  9. package/dist/bench/context-attribution.js +0 -0
  10. package/dist/bench/discovery-cost-eval.js +0 -0
  11. package/dist/bench/driver.js +0 -0
  12. package/dist/bench/evals.js +0 -0
  13. package/dist/bench/instrument.js +0 -0
  14. package/dist/bench/intent-face-eval.js +40 -40
  15. package/dist/bench/run.js +0 -0
  16. package/dist/bench/scenario.js +0 -0
  17. package/dist/bench/types.js +0 -0
  18. package/dist/bi-wire.js +0 -0
  19. package/dist/bizservice-config.js +0 -0
  20. package/dist/call.js +0 -0
  21. package/dist/capability-absences.js +0 -0
  22. package/dist/capability-overview.js +0 -0
  23. package/dist/capability-shape.js +0 -0
  24. package/dist/cli-contract.js +0 -0
  25. package/dist/cli-output.js +0 -0
  26. package/dist/cli.js +31 -1
  27. package/dist/closest-match.js +0 -0
  28. package/dist/code-rules.js +0 -0
  29. package/dist/command-transport.js +0 -0
  30. package/dist/compound-writes.js +0 -0
  31. package/dist/compute.js +0 -0
  32. package/dist/config-store.js +0 -0
  33. package/dist/context.js +0 -0
  34. package/dist/contract-consistency.js +0 -0
  35. package/dist/contract-resources.js +0 -0
  36. package/dist/coverage-consistency.js +0 -0
  37. package/dist/coverage-registry.js +0 -0
  38. package/dist/coverage.js +0 -0
  39. package/dist/data-list-filter.js +0 -0
  40. package/dist/describe-cache.js +0 -0
  41. package/dist/discovery.js +0 -0
  42. package/dist/endpoints.js +0 -0
  43. package/dist/environment-policy.js +0 -0
  44. package/dist/execution-metadata.js +0 -0
  45. package/dist/field-add.js +238 -0
  46. package/dist/field-change.js +0 -0
  47. package/dist/field-families.js +26 -2
  48. package/dist/form-default.js +0 -0
  49. package/dist/form-layout.js +0 -0
  50. package/dist/form-support.js +0 -0
  51. package/dist/formula-lint.js +0 -0
  52. package/dist/goal-routes.js +28 -16
  53. package/dist/governed-execution.js +0 -0
  54. package/dist/graph.js +50 -33
  55. package/dist/human-summary.js +0 -0
  56. package/dist/identity.js +0 -0
  57. package/dist/intent/baseline.js +0 -0
  58. package/dist/intent/capabilities/bizservice.js +0 -0
  59. package/dist/intent/capabilities/businessrule.js +0 -0
  60. package/dist/intent/capabilities/datarule.js +0 -0
  61. package/dist/intent/capabilities/field.js +0 -0
  62. package/dist/intent/capabilities/form.js +0 -0
  63. package/dist/intent/capabilities/index.js +0 -0
  64. package/dist/intent/capabilities/listview.js +0 -0
  65. package/dist/intent/capabilities/model.js +0 -0
  66. package/dist/intent/capabilities/onlinejs.js +0 -0
  67. package/dist/intent/capabilities/report.js +0 -0
  68. package/dist/intent/capabilities/workflow.js +0 -0
  69. package/dist/intent/capability.js +0 -0
  70. package/dist/intent/cli.js +0 -0
  71. package/dist/intent/compare.js +0 -0
  72. package/dist/intent/compiler.js +0 -0
  73. package/dist/intent/dsl.js +0 -0
  74. package/dist/intent/plan-apply.js +0 -0
  75. package/dist/intent/plan-file.js +0 -0
  76. package/dist/intent/readback.js +0 -0
  77. package/dist/intent/schema.js +0 -0
  78. package/dist/intent/validation.js +0 -0
  79. package/dist/intent/yaml.js +0 -0
  80. package/dist/journal.js +0 -0
  81. package/dist/json-column.js +0 -0
  82. package/dist/lanes/app-contract.js +0 -0
  83. package/dist/lanes/app-coverage.js +0 -0
  84. package/dist/lanes/app.js +0 -0
  85. package/dist/lanes/apply-changes.js +0 -0
  86. package/dist/lanes/b2-registration-contract.js +0 -0
  87. package/dist/lanes/b2-registration-coverage.js +0 -0
  88. package/dist/lanes/b2-registration.js +0 -0
  89. package/dist/lanes/businessrule-contract.js +0 -0
  90. package/dist/lanes/businessrule-coverage.js +0 -0
  91. package/dist/lanes/businessrule.js +11 -3
  92. package/dist/lanes/contract-support.js +0 -0
  93. package/dist/lanes/coverage-declaration.js +0 -0
  94. package/dist/lanes/custom-page.js +0 -0
  95. package/dist/lanes/datarule-contract.js +0 -0
  96. package/dist/lanes/datarule-coverage.js +0 -0
  97. package/dist/lanes/datarule-protocol.js +0 -0
  98. package/dist/lanes/datarule.js +6 -0
  99. package/dist/lanes/dictionary-contract.js +0 -0
  100. package/dist/lanes/dictionary-coverage.js +0 -0
  101. package/dist/lanes/dictionary.js +0 -0
  102. package/dist/lanes/environment-contract.js +0 -0
  103. package/dist/lanes/environment-coverage.js +0 -0
  104. package/dist/lanes/environment.js +0 -0
  105. package/dist/lanes/field-contract.js +0 -0
  106. package/dist/lanes/field-coverage.js +0 -0
  107. package/dist/lanes/field.js +0 -0
  108. package/dist/lanes/folder-contract.js +0 -0
  109. package/dist/lanes/folder-coverage.js +0 -0
  110. package/dist/lanes/folder.js +0 -0
  111. package/dist/lanes/form-contract.js +0 -0
  112. package/dist/lanes/form-coverage.js +0 -0
  113. package/dist/lanes/form.js +0 -0
  114. package/dist/lanes/lane-ids.js +0 -0
  115. package/dist/lanes/list-view-contract.js +0 -0
  116. package/dist/lanes/list-view-coverage.js +0 -0
  117. package/dist/lanes/list-view-shapes.js +0 -0
  118. package/dist/lanes/list-view.js +6 -0
  119. package/dist/lanes/meta-contract.js +0 -0
  120. package/dist/lanes/meta.js +0 -0
  121. package/dist/lanes/model-contract.js +0 -0
  122. package/dist/lanes/model-coverage.js +0 -0
  123. package/dist/lanes/model.js +0 -0
  124. package/dist/lanes/online-js-contract.js +0 -0
  125. package/dist/lanes/online-js-coverage.js +0 -0
  126. package/dist/lanes/online-js.js +0 -0
  127. package/dist/lanes/org-tree.js +0 -0
  128. package/dist/lanes/organization-coverage.js +27 -0
  129. package/dist/lanes/organization.js +10 -1
  130. package/dist/lanes/report-contract.js +0 -0
  131. package/dist/lanes/report-coverage.js +0 -0
  132. package/dist/lanes/report.js +0 -0
  133. package/dist/lanes/rule-graph.js +0 -0
  134. package/dist/lanes/rule-lifecycle-contract.js +0 -0
  135. package/dist/lanes/rule-lifecycle-coverage.js +0 -0
  136. package/dist/lanes/rule-lifecycle.js +0 -0
  137. package/dist/lanes/runtime-data-contract.js +0 -0
  138. package/dist/lanes/runtime-data-coverage.js +0 -0
  139. package/dist/lanes/runtime-data.js +24 -0
  140. package/dist/lanes/workflow-contract.js +0 -0
  141. package/dist/lanes/workflow-coverage.js +0 -0
  142. package/dist/lanes/workflow.js +7 -2
  143. package/dist/mcp-graph.js +0 -0
  144. package/dist/model-purge.js +0 -0
  145. package/dist/model-record.js +0 -0
  146. package/dist/model-scaffold.js +0 -0
  147. package/dist/omc-core-lazy.js +0 -0
  148. package/dist/online-js-layout.js +0 -0
  149. package/dist/online-js-source.js +28 -28
  150. package/dist/package-tool.js +0 -0
  151. package/dist/package.js +0 -0
  152. package/dist/path-projection.js +0 -0
  153. package/dist/plan.js +0 -0
  154. package/dist/read.js +0 -0
  155. package/dist/recipes/b2-registration.js +0 -0
  156. package/dist/recipes/businessrule.js +0 -0
  157. package/dist/recipes/datarule.js +0 -0
  158. package/dist/recipes/dictionary.js +0 -0
  159. package/dist/recipes/field.js +1 -1
  160. package/dist/recipes/form.js +0 -0
  161. package/dist/recipes/list-view.js +0 -0
  162. package/dist/recipes/model.js +0 -0
  163. package/dist/recipes/online-js.js +0 -0
  164. package/dist/recipes/runtime-data.js +0 -0
  165. package/dist/recipes/shared.js +0 -0
  166. package/dist/recipes/workflow.js +0 -0
  167. package/dist/redact.js +0 -0
  168. package/dist/refusal-guidance.js +0 -0
  169. package/dist/rollback-spec.js +0 -0
  170. package/dist/rollback.js +0 -0
  171. package/dist/rule-support.js +0 -0
  172. package/dist/rule-when.js +4 -3
  173. package/dist/runtime-support.js +0 -0
  174. package/dist/script.js +0 -0
  175. package/dist/service-doctor.js +0 -0
  176. package/dist/session-manager.js +0 -0
  177. package/dist/session.js +0 -0
  178. package/dist/skills.js +0 -0
  179. package/dist/sqlite-warning-filter.js +0 -0
  180. package/dist/support.js +0 -0
  181. package/dist/token-cache.js +0 -0
  182. package/dist/tool-types.js +0 -0
  183. package/dist/tools.js +0 -0
  184. package/dist/usage-log.js +0 -0
  185. package/dist/wire.js +0 -0
  186. package/dist/workflow-support.js +0 -0
  187. package/dist/write-lease.js +0 -0
  188. package/dist/write-lock.js +0 -0
  189. package/dist/write.js +0 -0
  190. package/dist/zip.js +0 -0
  191. package/docs/images/omc-nixi/README.md +41 -41
  192. package/docs/omc-introduction.md +851 -851
  193. package/docs/tool-surface-map.md +42 -42
  194. package/package.json +3 -3
  195. package/skills/omc-acceptance-criteria.md +50 -50
  196. package/skills/omc-business-configuration.md +336 -334
  197. package/skills/omc-capabilities.md +214 -214
  198. package/skills/omc-capability-scouting.md +55 -55
  199. package/skills/omc-config-draft-review.md +175 -175
  200. package/skills/omc-five-piece-flow.md +35 -35
  201. package/skills/omc-glossary.md +86 -86
  202. package/skills/omc-refusals.md +129 -129
  203. package/skills/omc-requirement-analysis.md +161 -161
  204. package/skills/omc-requirement-vocabulary.md +61 -61
  205. package/skills/omc-start-here.md +107 -106
  206. package/skills/omc-tool-selection.md +119 -119
  207. package/skills/omc-write-hazards.md +222 -200
  208. package/dist/approval-token.js +0 -102
  209. package/dist/bench/fake-http-server.js +0 -65
  210. package/dist/fake/app.js +0 -45
  211. package/dist/fake/b2-registration.js +0 -594
  212. package/dist/fake/businessrule.js +0 -242
  213. package/dist/fake/datarule.js +0 -72
  214. package/dist/fake/dictionary.js +0 -108
  215. package/dist/fake/environment.js +0 -30
  216. package/dist/fake/field.js +0 -167
  217. package/dist/fake/form.js +0 -101
  218. package/dist/fake/index.js +0 -121
  219. package/dist/fake/list-view.js +0 -289
  220. package/dist/fake/model.js +0 -136
  221. package/dist/fake/online-js.js +0 -18
  222. package/dist/fake/report.js +0 -253
  223. package/dist/fake/routes.js +0 -47
  224. package/dist/fake/rule-lifecycle.js +0 -37
  225. package/dist/fake/runtime-data.js +0 -367
  226. package/dist/fake/state.js +0 -67
  227. package/dist/fake/workflow.js +0 -364
  228. package/dist/server.js +0 -92
@@ -1,334 +1,336 @@
1
- ---
2
- name: omc-business-configuration
3
- description: 中文 CloudPivot 业务配置 runbook。用于把模型、字段、表单、视图、子表、流程权限、业务规则、按钮、在线 JS 和现存业务集成方法按需求落地并验收。
4
- ---
5
-
6
- # CloudPivot 业务配置 Runbook
7
-
8
- 本技能讲业务需求如何落成配置包;底层 wire 形状和白屏陷阱见 `omc-write-hazards`。
9
-
10
- ## 入口
11
-
12
- ```bash
13
- CLI="packages/omc-cli/dist/cli.js"
14
- node "$CLI" commands --json
15
- node "$CLI" describe <tool>
16
- node "$CLI" contract <lane>
17
- node "$CLI" call discover_environment --env <env> --args '{}'
18
- node "$CLI" help omc-config-draft-review
19
- node "$CLI" help omc-five-piece-flow
20
- ```
21
-
22
- ## 需求五维
23
-
24
- 把需求拆成五个维度(注意:这是**需求维度**,不同于 `omc-five-piece-flow` 的"五件套"执行五阶段):
25
-
26
- 1. **模型**:主对象、应用、业务 code。
27
- 2. **字段**:类型、选项、关联目标、模型可空性。
28
- 3. **表单与视图**:控件、子表 Sheet201、列表列、排序、筛选、按钮。
29
- 4. **流程**:节点、参与人、每节点字段权限。
30
- 5. **自动化**:规则、流程绑定、按钮、业务集成、在线 JS。
31
-
32
- ## 标准顺序
33
-
34
- **配置顺序以 `omc-config-draft-review` §8 为唯一真相**(模型 → 字段 → 发布 → 表单 → 视图 → 规则 → 流程);执行阶段见 `omc-five-piece-flow`。本 runbook 只补充业务侧要点:
35
-
36
- 1. 读环境基线:应用、模型、字段、表单、视图、流程、规则、服务。
37
- 2. 出中文草稿(`omc-config-draft-review`):环境、应用、资源 code、字段表、流程图、映射、按钮、清理方案。
38
- 3. 取得用户明确确认;确认前不写环境。
39
- 4. 需求包含运行行为时,用 CLI 真实触发;测试数据的保留/清理要写进草稿。
40
-
41
- 完成标准:每项资源有 readback;每个运行写入有 delivery 状态;每个映射字段都有目标值;失败步骤、已完成步骤和清理动作可追踪。
42
-
43
- ## 模型与字段
44
-
45
- - **code 长度平台硬限**:模型 code ≤26 字符、字段 code ≤28(`business-code-invalid` 出门校验;长业务名先起短 code,长名放 name)。
46
- - 模型层字段默认可空;必填、只读、可见性在表单和流程节点配置。
47
- - `propertyEmpty` 默认 `false`,不要把业务必填硬编码在数据模型。
48
- - 关联单选优先读目标模型已发布字段,明确显示字段;无映射不要写空数组字符串。
49
-
50
- ## 子表
51
-
52
- 一个主表可以有多个子表,但子表不能嵌套子表。
53
-
54
- 创建父字段:
55
-
56
- ```json
57
- {
58
- "schemaCode": "expense",
59
- "code": "expenseItems",
60
- "name": "费用明细",
61
- "propertyType": "CHILD_TABLE",
62
- "subFields": [
63
- { "code": "itemName", "name": "费用项目", "propertyType": "SHORT_TEXT" },
64
- { "code": "itemAmount", "name": "金额", "propertyType": "NUMERICAL" },
65
- { "code": "itemDate", "name": "发生日期", "propertyType": "DATE" }
66
- ]
67
- }
68
- ```
69
-
70
- 子列像主表字段一样 CRUD,但 schemaCode 是父字段 code:
71
-
72
- ```bash
73
- # --env 在单环境项目(.omc-env 在位)可省;显式写出仅为多环境演示
74
- omc call field.create --env local --args '{"schemaCode":"expenseItems","code":"itemNote","name":"明细备注","propertyType":"LONG_TEXT"}'
75
- omc call field.update --env local --args '{"schemaCode":"expenseItems","code":"itemNote","name":"备注"}'
76
- omc call field.delete --env local --args '{"schemaCode":"expenseItems","code":"itemNote"}'
77
- omc call field.publish --env local --args '{"schemaCode":"expense"}'
78
- ```
79
-
80
- 父表单重建后,Sheet201 控件应有业务子列,子列 key 不应出现在普通 `viewJson`。
81
-
82
- 声明式面同样支持子表(`intent.yaml`),子列写在父字段的 `subFields` 下;子列只接受标量族,不能嵌套子表/关联/公式:
83
-
84
- ```yaml
85
- models:
86
- - code: expense
87
- name: 费用报销单
88
- fields:
89
- - code: expenseItems
90
- name: 费用明细
91
- type: CHILD_TABLE
92
- subFields:
93
- - code: itemName
94
- name: 费用项目
95
- type: SHORT_TEXT
96
- - code: itemAmount
97
- name: 金额
98
- type: NUMERICAL
99
- ```
100
-
101
- `intent plan/apply/readback` 会把子列的缺失、改名、类型漂移当成独立差异(`subfield:` 资源);新增子列自动编译成 `field.create --schemaCode <父字段 code>` 并触发模型级 `field.publish`。父字段本身改名仍用 `field.update`,`field.update` 不接受 `subFields`。
102
-
103
- `intent apply` 默认在交付后自动做一次最终读回等价确认:等价则 `verification.status:"ok"`;不等价则整体 `refused` + `blocker:"intent-readback-mismatch"`(exit 12),**绝不当作成功**——按 `verification.differences` 定位后重新 `intent plan`,不要盲重试。`--no-verify` 可关闭该确认(只在你已单独跑 readback 时)。
104
-
105
- 业务规则也进了声明式面(`models[].rules[]`)。常用数据事件规则用紧凑 `actions`,编译器合成 nodeGraph;复杂图用 `nodeGraph` 原样透传(二选一):
106
-
107
- ```yaml
108
- rules:
109
- - code: Update # 平台预置码(Create/Update/Delete…)或自定义码
110
- name: 更新归档
111
- enabled: true # true → 额外 rule.enable(publish+启用);false → 已启用则 rule.disable
112
- actions:
113
- - type: UPDATE
114
- target: archive_model # 同模型可省略 target(默认本模型);跨模型才必须写
115
- filter:
116
- - field: source_no
117
- from: doc_no # from = 动态(触发字段);或比较键:equals/notEquals/gt/gte/lt/lte: <标量>
118
- set:
119
- - field: amount
120
- from: total # from = 动态;也可 value: <字面量>
121
- ```
122
-
123
- - **每个 filter 项必须有且只有一个条件来源**:`from`(动态 EQ)或一个比较键(`equals`/`notEquals`/`gt`/`gte`/`lt`/`lte` + 标量)。缺来源 `intent-rule-leaf-source-missing` 拒绝;**绝不**静默成 `EQ null`。
124
- - 动作节点的条件分「定位数据」与「执行门」两类,**别混**(详见下「规则图的触发条件」)。
125
- - **跨模型写(`targetObjectCode`≠规则模型)平台支持、可运行时生效**(2026-09-28 真机复核)。两个前提:① 承载它的规则必须真的触发——`Create` 规则在提交时触发;`Update` 规则只在 `data.update` 触发,**submit 不执行它**(放进 `Update` 是典型静默空转,lint 警告 `rule-graph-cross-model-update-trigger-inert`;也可绑定流程事件如 `end`)。② 目标行必须已提交——目标查询不匹配 DRAFT 行,`data.save` 未 submit 的行不回写。
126
- - 关联字段:`type: WORK_SHEET` + `relativeCode: <目标模型>` + `options.displayField`,与 `field.create` 行形状一致。(键名是 `relativeCode`,wire 键 `relativeSchemaCode`,别混。)
127
- - 节点显示名用 activity 级的 `name`(`participant.name` 只作参与者说明,平台不认作节点名;`{type: Originator, name: 部门负责人}` 会被工具提升为节点名)。
128
- - **字段级数据规则**(`models[].datarules[]`,`dataRuleType` 1校验/2计算/3拼接/4必填/5只读)——注意是 `dataRuleType`(不是 `type`):
129
- ```yaml
130
- datarules:
131
- - { propertyCode: order_note, dataRuleType: 4, options: { requiredFormula: '{amount} 大于 {"valueType":"custom","value":5000}' }, name: 金额超5000说明必填 }
132
- ```
133
- ⚠️ **type 3/4/5 不驱动门户表单控件**(真机复核):平台存成 bizdatarule,只镜像进控件 `historyOpt`,运行时门户读的是控件 `options`。门户条件(如「金额>5000 时必填」)必须写在 `fields[].options`(`requiredFormula`/`readonlyCondition`/`shortTextStitch`);写进 `datarules[]` 设计器可见但门户不拦(validate/plan 给 `datarule-not-portal-facing` 警告)。datarule 只用于服务端引擎(校验/计算)。
134
-
135
- - 规则排在字段发布之后(编译器按能力排序:先建全部模型/字段,再跑规则)。
136
- - `rule.save` 只写草稿;`enabled` 由 `rule.enable` 保证(publish+启用)——readback 用 `enabled` 和**语义图摘要**(忽略画布几何)判等价。
137
-
138
- ## 表单与视图
139
-
140
- - 新字段加入已有表单:`form.publish --args '{...,"rebuild":true}'` 一步(重建脚手架+强制提拔);或分步 `form.draft --rebuild` → `form.publish`。
141
- - rebuild 会保留已识别的关系映射、显示和布局 options;Sheet201 columns 按最新子表字段刷新。
142
- - **配置列表视图用 `listview.configure`(省略 `code` 即平台自动生成的默认视图 code=schemaCode),不要先 `listview.create`——那会多出一个视图。** 只有确实要第二个视图才 create;两者职责分开。
143
- - 数值控件:`scale` 是小数位数(0=整数,1=1位,2=2位…);「金额两位小数」用 `scale:2`。列表列 displayFormat 由 scale 推导。
144
- - **列表列只放业务字段**:平台 `listview.create` 会预置系统列(`name/creater/createdTime/sequenceNo`)和一条 `createdTime` 默认筛选;intent 的 `fieldCodes` 是权威集合,编译器用 `replaceColumns:true + hideDefaultSystemFields:true + conditions:[]` 清掉预置项。要展示某系统列/筛选就显式点名。
145
- - 筛选条件默认显示:`conditions[].visible` 不传也应为 true。
146
- - 配置默认视图后即可用 `data.list`;否则部分 8.6.26 读面业务列可能为 null,单行用 `data.load` 核对。
147
- - 读回视图用 `listview.get`(默认回列/排序/条件/动作紧凑清单)与 `listview.list`。
148
-
149
- ## 流程
150
-
151
- - **两条路**:模型+表单+流程一揽子交付走**意图**(`intent` 的 `workflow` 能力,声明式一次 apply);只动流程或想逐步控制走**低层链** `workflow.create → workflow.update(权限/节点)→ workflow.publish`,标准链配方见 `omc describe workflow.update`(含多级审批/participant 三形态/自动回填说明)。流程权限细节见下节。
152
- - 一个模型只能有一个流程;已有流程的模型直接 `workflow.update`,不要另建。
153
- - **清理测试数据用批量删除**:`data.delete --args '{"schemaCode":"<code>","bizObjectIds":["id1","id2"]}'` 一次往返删多条(真机 r6:207 次逐条 `data.delete` 占 13.5% 调用)。
154
- - **「聚合计算」是字段能力,不是规则节点**:FORMULA(计算公式)字段的 `options.formulaConfig.formulaType="AGGREGATION"` 就是 UI 里的「配置方式 → 聚合计算」。**只能汇总另一个模型**(跨模型),**同模型/自己的子表会 305003**(`field.publish` 时 `publishFormula` 把相对码改写成子表父模型 = 本模型,`checkCycleOfAggregation:1032` 判成环;工具已写前拒绝 `aggregation-self-schema-cycle`)。可直接抄 `omc describe field.create --example formula-aggregation-cross-model`。形状:
155
- `options = {persistent:true, formulaConfig: JSON字符串({resultType:2, formulaType:"AGGREGATION", aggregation: JSON字符串({schemaCode:"<来源模型>", dataItemCode:"<来源列>", calculateType:"SUM", formula:"SUM({<来源模型>.<列>})", queryConditions:[[{propertyCode:"<来源模型字段>", currentPropertyCode:"<目标模型字段>", operatorType:1, valueType:"DYNAMIC"}]]})})}`。
156
- - **`queryConditions` 不能为空**:它是反向过滤器的唯一输入,空数组时回算日志照打但目标字段恒 0(r17 实测)。
157
- - **聚合目标必须是来源主表的标量列**(r18 2026-10-26 真机):`aggregation.schemaCode` 指向**子表 schema** 时能发布,但目标模型从此整个不可写(`data.submit`/`data.update` 全回 40000 无明细),且子表无父单单号列、匹配恒 0;`dataItemCode` 写 `<子表>.<子列>` 则 publish 回 309004。要子表合计:先在来源主模型物化一个数值字段,再聚合该标量。
158
- - **DYNAMIC 条件的两个 code 分属两侧,写反了静默 0**:`propertyCode` = 来源模型字段,`currentPropertyCode` = 目标模型字段;语义「来源行的 X 等于本目标行的 Y」。
159
- - **`propertyType`/`currentPropertyType`(BizPropertyType)与 `operatorType`(OperatorType)必须写整数**——两者都是 `@JsonCreator get(int)`:propertyType 0=SHORT_TEXT 2=NUMERICAL 3=DATE 9=WORK_SHEET…;operatorType 1=EQ 2=NEQ 3=GT 4=IT(小于)5=GTEQ 6=ITEQ(小于等于)9=IS_NULL 10=IS_NOT_NULL…。写名字会被 `ObjectMapperUtils` 抛 json processing exception,再被死循环校验掩码成 305003(工具接受名字并自动转码)。`calculateType` 相反,是**字符串**(SUM/COUNT/COUNTIF/SUMIF/AVG/MAX/MIN/LARGE/SMALL/UNION);LARGE/SMALL 必须带 k(`LARGE({来源.列},2)`),UNION 是文本拼接、resultType 必须是 0/1。
160
- - **回算时机(r17b 2026-10-26 完整行为图谱)**:① 目标行**自己 submit** 时镜像规则一定算(最可靠);② 来源行 submit/update/**delete** 触发 `RollUpEventListener`→`FormulaPropertySupport` 事件回算(FIXED+DYNAMIC 混用、多组 OR、COUNTIF/MAX、子表行级条件都验过);③ ⚠️ 事件回算**只在目标模型发布后几分钟内可靠**,之后静默停止(无报错)——`field.publish --force` 目标模型恢复。发布后平台自动生成 t100 `CALCULATE_RULE` 镜像行,别删。r17 真机:目标行 srcNo=C1、来源 C1 行 amt=100 → 读 100;再提交一条 C1/amt=5 → 读 105。
161
- - **普通数值字段 + 计算规则(type2)不能替代**:REST 路径永不执行(`CalculateHandler.java:50` 的 `forceExecuteCalculateRule` 从不置真),门户里才算。
162
- - **DYNAMIC 来源不要写点号**:`dataActions` 的 `currentDataItemValue` 写 `items.sealNo` 会被引擎当成**子表列集合**(DatasetImpl),标量目标收到集合 → 运行期 `submit/update` 报 50000(`WorkSheetBizField.fillClientContent: Incorrect value type`)。单跳关联字段直接写字段 code(引擎取 `.id`);子表列只能用于公式/聚合。`rule.save` 已写前拒绝(`rule-graph-create-dynamic-source-is-child-collection`)。
163
- - **单选字段的 `dataCondition` 在 1.1.25 不生效**:用 FIXED 值比较 RADIO/CHECKBOX/DROPDOWN 时,运行时日志「解析选框类型数据项的比较值失败」,条件被跳过、节点**无条件执行**(r8:异常归还被当成已归还回写)。`rule.save` 会把它放进 `readback.lintHazards`(`rule-graph-radio-condition-unparseable`);改用 BRANCH 判断该字段,或拆两条规则。
164
- - **预置 Update 规则里的跨模型回写只在 `data.update` 触发**:`rule-graph-cross-model-update-trigger-inert` 现在是 hazard(在 `readback.lintHazards`,不混进 `lintWarnings`);create/submit 不执行它。要提交时生效,把节点放进 Create 规则或流程事件规则。
165
- - **部门/员工写面**:`org.department.create`(`deptType` 传名字或 0..3,平台按 **int 枚举**反序列化,写 `"dept"` 会 10009)→ `org.department.update`(先读再合并)/`org.department.delete`(`strike_out`);`org.user.create` 必填 `name/username/departmentId`,**平台对 `userLabelIds`/`userManagerConfigList`/`roleIds` 的 null 直接 NPE**(50000,`UserHelper.dealUserLabel:428`),工具已补空数组;`org.user.update` 同样必须先补 `deptIds` 等数组(`UserHelper.modified` 对 null 的 `deptIds` NPE)。`org.department.list` 递归展开整棵树(`/department/tree` 每次只返回一层);`org.department.users` / `org.user.info` / `org.user.departments` 用于回读。新建用户的 `active` 平台回读为 false(账号激活另有流程),不要把 active 当作创建成功标志。
166
- - **组织写面(角色/成员/管理范围)**:界面上的「分组」= 角色(`group:false`),容器是角色组(`group:true`)。链路:`org.roleGroup.create`(**parentId 必填**,1.1.25 传空会 50000 `parentIds cannot be empty`)→ `org.role.create`(roleId 平台生成,code 随后补 `SYS<时间戳>`)→ `org.role.addUsers`(`id` 必须传 **recId**,不是账号;工具补 `type:3`/`unitType:"3"`)→ `org.role.setUserScope`(**平台覆盖语义、无回滚**:默认读旧值合并,`mode:"replace"` 才整体替换;`unitType` 传数字 3)。读回用 `org.role.users` / `org.role.userScope`。
167
- - **`corpId` 默认写当前企业,不要写 `"other"`**(r9 2026-10-22 更正):`RoleGroupHelper.createRoleGroupModel` 只从 body 取 corpId,缺省才按 parentId 反查企业设置、查不到就 NPE(被吞成 errcode 0);`other` 是平台**合法的「非组织分组」语义**,不是兜底值。工具现在默认 `session.enterpriseId()`,并回读校验(不一致给 warning);只有明确要建非组织分组时才显式传 `corpId:"other"`。
168
- - **`org.role.list` 必须带 `expandAll=true`**:`/role/list` 不带该参数只展开**第一个**角色组的 children(`RoleController.list`,1.1.25),所以建在别的组下的角色读回为「不存在」——r9 因此误判「角色未创建」并重复读了 8 次。工具已默认展开,`expandAll:false` 才拿容器面。
169
- - **`org.role.update` 的平台前置条件**:`modify_role` 的 `validateRoleParam` 要求 `roleName` 与 `roleGroupId` **都非空**(否则 202001 / 202012),所以只改备注也要回填这两项——工具已从 `role/get` 自动补齐。`corpId` 在 update 里不生效,换企业只能重建角色。
170
- - **角色参与人可用性**:r9 对照实验证明,`corpId=other` 的角色**仍能正常解析参与人**(把非发起人加进该角色后,待办正确落到该人)。所以「审批人变成发起人」要按**解析为空**排查(角色零成员 / 成员没挂部门 / 自由文本 / 角色码不存在),不要归因于 corpId。
171
- - **系统持久化节点是平台自带的,不要手写**:预置 `Create/Update/Delete` 行本来就带 `SYSTEM_CREATE/SYSTEM_UPDATE/SYSTEM_DELETE`(实测基线 `Start → Create(SYSTEM_CREATE) → End`)。`rule.save` 会丢弃调用方传入的 `SYSTEM_*` 并**从平台基线原样携带**那一个节点(code/name/几何都不由我们猜)。手写的后果:预置规则里类型错配 → `data.submit` 50000;自定义规则/流程事件里带系统节点 → 结束事件无当前对象,`Object <id> is not serialized`,每步仍回 DELIVERED 但什么都没写。只写平台允许配置的业务节点(UPDATE/CREATE/DELETE/BRANCH/ASSIGN/MESSAGE/BIZ_ACTION…)。
172
- - **QUERY 节点的三个「静默空结果」入口(1.1.25 源码,r9 §14.1)**:`QueryActivityBehavior:47-50`(输入数据为空,或 `dataCondition` 过滤后为空 → `return null`)、`:78-86`(`mainConditions` 为空 → `emptyList`;目标带 `.` 时 `sheetConditions` 为空也 `emptyList`)、`ActivityBizObjectManagerImpl:172-178`(真查询 0 命中 → `emptyList`)。**三条都不写日志**,所以「节点执行开始后什么都没有」既可能是配置错也可能是 0 命中。前两条工具已在写前拦:`rule-graph-query-conditions-empty`(无条件叶,fail closed——QUERY 的空条件**不是**「查全表」,与 UPDATE 语义相反;要全表请写恒真叶)与 `rule-graph-query-datacondition-empty`(hazard)。第三条只能在运行期验证:先 `data.list` 确认目标行存在且状态已提交。
173
- - **取 artifact 先投影**:`artifact get <id> --summary` 看形状 → `--jq '<path>'` 取那一段;只有确实要整份时才裸取(r6:47 次整包拉取 650KB,0 次用投影)。
174
- - **读回产物会自动清理,也可手动清**:每次大结果落一个 artifact(机器级目录,跨项目累积;实测一轮沙盒 504 个/3.4MB)。`createArtifact` 每 24h 顺手删 7 天未动的;要立刻回收用 `omc artifact prune --older-than 7d --max-bytes 268435456`(默认预算 256MB,超出按最旧优先删),`--json` 回 removed/freedBytes/kept。
175
- - **核验状态用投影,别重复整条读**:`data.load --args '{"schemaCode":..,"bizObjectId":..,"fields":["sequenceStatus","<业务状态字段>"]}'` 只回指定字段(缺失项在 `missingFields`)——r4 实测同一对象被整条读 74 次;大结果先 `artifact get <id> --summary` → `--keys/--jq`,别整包内联进推理。
176
- - **流程第一个节点必须是 `{Originator}` 填写节点**:平台 `WorkflowTemplateValidateUtility.validateStartActivity` 对「START 后直接接审批」报 401035「发起节点的参与者只能是{Originator}」,publish 回 errcode 0 但一个 ValidateMessage[] 就把模板挡在门外、不落库(真机 2026-10-21)。OMC 0.4.65 起在 `workflow.update`/`publish` 出门前拒绝(`workflow-first-activity-not-originator`)。
177
- - **跨模型回写的目标行必须已提交(COMPLETED)**:DRAFT 行不参与目标查询,运行 0 行命中且日志只有「查找范围不存在数据」。单跳关联字段可直接作 DYNAMIC 来源(引擎取关联对象 id);点号路径按子表解析,`关联字段.id` / 两跳路径都不支持。
178
- - **参与人解析失败分两种,别当成同一种(1.1.25 实测)**:① **解析为空**(角色零成员、成员未挂部门、自由文本、角色码不存在)→ 引擎把任务**退回发起人**,流程继续,症状是「审批人显示成我自己」,`data.submit` 与 `workitem.list` 都正常;② **参与人函数未注册**(`FindUsersByRoleFunction` / `FindUsersSpecByRoleFunction`,1.1.25 未内置)→ `FunctionNotFoundException`、不生成待办、实例卡 PROCESSING 而接口仍回 `errCode 0`。区分用 `omc call workflow.instanceActivity --args '{"workflowInstanceId":"<id>"}'`:无 `status:1` 节点=卡住;`status:1` 且参与人全是发起人=`diagnosis.warnings` 给 `participant-fallback-to-originator`。不要从 `workflow.get_published` 的摘要回抄函数式参与人;内置写法只有 `FindUserByRole(org, roleCode[, order])`,跨部门/全局角色优先用参与者对象数组(角色 id)。
179
-
180
- ## 流程字段权限
181
-
182
- 新增字段后,必须重新检查每个参与节点的 `propertyPermissions`:
183
-
184
- - 填写节点:需求要求的字段可编辑,必要字段必填。
185
- - 审批节点:通常 `visible:true, editable:false, required:false`。
186
- - 不需要展示的字段明确 `visible:false`。
187
-
188
- **怎么配**(没有单独的权限工具,别把「找不到入口」当不支持→假阻塞):**增量即可**——`workflow.update` 直接发
189
- `activities:[{activityCode, propertyPermissions:[{propertyCode, visible, editable, required, subPropertyPermissions}]}]`
190
- (**type 可省**:activityCode 命中草稿节点时类型/名称继承草稿、code 稳定不铸 `_2`;不必先读 `get_draft --full` 整回写。
191
- `summary` 无需配,工具自动补 `true`),改完 `workflow.publish`。(`summary:true` 才进运行时 `activitySummaries`,工具已自动处理;
192
- 否则 `form/load` 的 `formPermission.dataPermissions` 回退默认,读不到你的 `editable:false`);子表列用 `subPropertyPermissions`。
193
- **子表属性本身不可设 `required:true`**(接口接受但设计器节点字段配置不支持——工具拒 `workflow-child-table-not-required`):子表整行只配 visible/editable,字段级必填配到子表内部字段。**新增节点用语义 code**(如 `approval_finance`),不要给 `ActivityN` 加 `_1/_2` 后缀;改名才会铸新码(平台不能原地改名)。**子表操作按钮与驳回默认已由工具自动补齐**:填写节点自动配 `buttonPermission.childTableBtns`(每子表 新增/删除/导入/导出 启用、批量编辑禁用),`permittedAction` 按设计器默认归一(填写人不可驳回自己 `showReject:false`;审批节点驳回回发起 `rejectToStart:true`)——显式传 `permittedAction`/`buttonPermission` 可覆盖。
194
- 验收用 `npm run test:live-permission` + **非管理员参与者**(管理员会被后端强制 `e:true` 掩盖)。
195
-
196
- 声明式流程(intent)的 `workflow.template` 用 **`activities` + `routes`**(不是 `nodes`/`edges`):活动 `{id, type: START|END|PARTICIPANT|…, participant?}`,路由 `{preNode, postNode}` 必须指向已声明活动 code;`workflow.update` 只从 `activities` 读节点,写 `nodes` 会在 apply 时 `rules[0] references an unknown activity`(validate 会提前拦)。
197
-
198
- 示例:
199
-
200
- ```text
201
- 开始 → 填写申请单 → 部门审批 → 财务审批 → 结束
202
- ```
203
-
204
- 如果环境里存在测试节点,先确认保留还是删除,不要静默覆盖。
205
-
206
- ## 业务规则
207
-
208
- **先选对机制,别一律上规则图**(三者代价差很大):
209
-
210
- | 需求形态 | 用哪个 | 入口 |
211
- | --- | --- | --- |
212
- | 条件必填 / 条件只读 / 恒只读(表单交互层) | 字段 `options` | `field.update --example form-conditional-required` / `form-conditional-readonly` / `form-always-readonly` |
213
- | 服务端字段级校验 / 计算 / 拼接 / 显示(type 1..5) | 数据规则 | `datarule.create`(字段级引擎) |
214
- | 数据事件自动化(创建/更新时改字段、调服务、发起流程) | 规则图 | 本节 `rule.save`(`--example standard-business-rule-graph`) |
215
-
216
- 「超过某值必填 / 等于某值只读」= **字段 options**,**不要**建规则图、也**不要**用 `datarule.create`
217
- (实测:`datarule` type3/4/5 不会让门户表单控件变必填/只读,且符号式经 `historyOpt` 镜像会让表单渲染崩溃;
218
- 平台的 bizdatarule 会被镜像进表单控件 `historyOpt`,而设计器/运行时读的是控件 `options`)。
219
-
220
- ### 条件式(必填/只读/显示)怎么写
221
-
222
- - **恒必填**:`requiredFormula: "true"`(字面量);**条件必填**:`requiredFormula: "{触发字段} 操作符 {\"valueType\":...,\"value\":...}"`。
223
- - 操作符用**中文**(设计器产出):等于/不等于/大于/小于/大于等于/小于等于/包含/包含全部/等于任意一个/为空/不为空;也接受符号 `== != > >= <= Contains IsEmpty`。为空/不为空**不带值**。
224
- - 写完后 `field.publish` → `configure_form`(同步进控件)→ **门户打开表单改触发字段的值**,断言目标控件变 `required` / `disabled`(存储回读不算验证)。
225
- - 恒只读:`readonlyFormula: true`。
226
-
227
- ### 日期类校验(不早于今天 / 截止日不早于开始日)
228
-
229
- **这类需求走表单的「提交校验」,不要用规则图 BRANCH。** 平台有专门的日期值类型 `today`/`yesterday`/`tomorrow`(不是"没有今天的系统变量"):
230
-
231
- - **字段 vs 今天**(`planDate` 不得早于今天)→ `datarule.create {dataRuleType:1, checkType:2}`:
232
- ```json
233
- {"schemaCode":"<m>","propertyCode":"planDate","dataRuleType":1,"checkType":2,
234
- "name":"计划日期不早于今天",
235
- "options":{"conditions":"{planDate} 大于等于 {\"valueType\":\"today\"}",
236
- "prompt":"计划日期不能早于今天"}}
237
- ```
238
- - **字段 vs 字段**(结束日不早于开始日)→ 同一 `checkType:2`,右值必须写成值对象:
239
- `{end_date} 大于等于 {"valueType":"currentModel","value":"start_date"}`(**裸 `{start_date}` 右值不会生效**)。
240
- - **规则图里要按"今天"分支**(例如给逾期记录打标)→ 条件叶用 `ruleValueType:"today"` + `currentSchemaDataItem:"<offset>"`(`"0"`=今天、`"-7"`=7 天前、`"30"`=30 天后,可带单位 `"-1-week"`);**不要用 `ruleValueType:"DYNAMIC"` 去和另一个日期字段比**——那会 `data.submit` 稳定 50000(`SingletonDataset.getBizObjectProperty` NPE,r10 复现)。
241
- - 校验规则的执行面:**门户前端提交时**执行;REST `data.submit`/`data.update` **不拦**(r9 已用越界数据实测)。要用 API 兜底就得二开。
242
-
243
- ### 规则图的触发条件(关键)
244
-
245
- 规则支持**触发条件**,不要用「两条无条件规则」表达互斥结果:
246
-
247
- - **节点级条件有两大类,别混**(各节点类型都可能有,完整字段/样例见 `describe rule.save --full` 的 RULE_NODE_TEMPLATES):
248
- - **`ASSIGN`(数据赋值)不是新节点,新的是它的形状**:8.6.26 自带 `BusinessRuleUpgradeServiceImpl` + `UpgradeRuleHandler.upgradeAssignNode`,职责就是把旧形状(`triggerObjectCode`/`currentDataModel`/`targetSchemaModel`/`tableConditionGroups`)升级为 `inputParam`+`targetObjectCode`+`nonMatchingAdded`——存在升级器就说明 8.6 之前已经有 ASSIGN。**现代形状从 8.6 起**,AI-Platform 1.x 同形;<8.6 存的是旧形状,写现代形状会被静默误读,OMC 直接拒绝(`rule-graph-assign-version-unsupported`)。
249
- - **`ASSIGN` 的 `targetObjectCode` 是写入目标,不是取数来源**:默认 `_input` = 当前单;`dataActions.targetDataItem` 必须是**该目标模型已发布字段**(写成别的模型 + 本单字段 → 550018)。要"从关联单取值回填本单",就 `targetObjectCode:"_input"` + `currentDataItemValue:"<本单来源字段>"`(关联字段引擎取 .id),不要把它指到对面模型。漏 `nonMatchingAdded` 会让 quote 阶段 NPE(publish 50000),工具已按设计器键补齐(warning `rule-graph-assign-keys-defaulted`)。
250
- - **定位要操作哪些数据**(`filterCondition`:叶 `targetSchemaDataItem`+`ruleConditionType`):UPDATE=改哪条(空=整表)、DELETE=删哪条、CREATE=存在符合则不新增+`findMainData`、QUERY=`targetConditionGroups`、**ASSIGN=在目标表里匹配要赋值的行(不是执行门!)**。⚠️ 非唯一字段(金额等)会命中**多条**;改「当前/关联那一条」用唯一键(`id` 或 `from`)。`actions.filter` 表达这类(同模型省略→默认 `id=id`;跨模型省略 fail-closed)。
251
- - **执行门 / 满足条件触发**(`dataCondition`,BIZ_ACTION 用 `execConditionGroups`):**与定位两码事**——决定「要不要执行」,不决定「改哪条」。compact `actions.when` 表达 UPDATE 执行门(叶 `currentSchemaDataItem`+`ruleDataConditionType`);其它节点用 `nodeGraph`。**条件改自身**:`when: [{field: amount, gt: 10000}]` + 省略 `filter`(默认 `id=id`)——别把条件写进 `filter`,那会改掉所有符合的行(bench66 多行互覆)。
252
- - 子表定位 `filterSubCondition`;子表触发范围 `childTriggerConditionType∈ADD/MODIFY/DELETE`。
253
- - **触发时机**:数据事件(`Create`/`Update`/`Delete`)**只分发到平台预置 code 行**;流程相关用预置 `Available`(流程生效)/`Cancel`(流程作废),或流程事件绑定 `workflow.bindRule` 的 `end`/`cancel`/`start`/`activate`。
254
- **"流程结束后执行"的两个正式入口(2026-10-16 真机反馈消歧)**:① **流程设计 → 流程结束后**(对应 `workflow.bindRule` 的 `end`,绑定自定义规则承载动作节点);② **平台自带业务规则的"流程生效"(预置 `Available`)行**。两者都在**流程实例走完归档/审批通过那一刻**触发,**不是进入流程时**——进入流程时刻是 `start`/`activate`。业务要"审批通过后执行某逻辑"时只用 ①或②,**禁止**绑 `start`/`activate`,也勿误用 `Create`(提交即触发,时序等同流程发起)。
255
- - **预置规则身份不可改**:`Create`/`Update`/`Delete`/`Load`/`GetList`/`Available`/`Cancel` 名称由平台维护(`Create`=数据新增);传 `name` 不生效(仅自定义规则名称可改),可改的是节点名 `nodeName`。
256
- - **节点级事件**:`workflow.bindRule` 还可传 `activityCode`+`nodeEvent`(`endActivity`=提交离开该节点、`activityApproved`/`activityRejected`、`beforeActivate`/`afterActivate`、`cancelActivity`、`jobSubmitted`/`jobRejected`),与 `handler` 互斥。
257
- - **流程事件只绑自定义规则**:`workflow.bindRule` 的 `ruleCodes` 只能给自定义 code——设计器的事件规则选择器不提供预置触发行(绑 `Create` 到 `end` 非法,工具 fail closed)。要「审批通过后执行某逻辑」:新建自定义规则承载动作节点再绑 `end`。
258
- - **审批通过/驳回回写**:通过绑 `end`(或 `Available`)、驳回绑 `cancel`(或 `Cancel`)——**不要**把两条各自 FIXED 赋值的规则都绑到 `endEventHandler`(两条都会执行、后者覆盖前者;工具会在 `bizActions.length>1` 时回读 `advisory` 提醒)。真实生产只在 `endEventHandler` 绑**一条**多分支规则图。
259
-
260
- 规则图需要显式:
261
-
262
- - `ruleCode`、名称、触发方式
263
- - 每个节点 `nodeName`
264
- - `nodeType`、路由、目标模型
265
- - 主表/子表 `findMainData`
266
- - 每个映射的 source/target 字段:**新增/更新节点的字段映射 = `dataActions`**(每目标字段一条叶:`{targetDataItem, ruleValueType:"DYNAMIC"/"FIXED", value:""/字面量, ruleActionType:"EQUALS", currentDataItemValue:"<来源字段>"}`)。**`execFormMapping` 只是展示开关,不是映射来源**——只写 `execFormMapping:true` 不写 `dataActions`,「主表字段值设置」是空的、新增行业务字段全 null(工具对 `execFormMapping:true` 缺 `dataActions` fail closed `rule-graph-create-mapping-missing`)。**写跨表节点前先 `rule.get` 读一条真实同型规则(如 `purchase_request`/`Create`)当基线**,不要从模板推断。
267
-
268
- 流程结束后新增目标表单,配置发布后要真实跑完流程,再读目标表单每个字段(`data.load`,不是 `data.list`——后者只投影列表列);只看到规则 `ENABLED` 不等于映射成功。
269
-
270
- ## 业务集成
271
-
272
- 优先复用现存 service/method,不做无必要的部署:
273
-
274
- ```bash
275
- omc call service.get --env local --args '{"serviceCode":"omc_demo_restful"}'
276
- ```
277
-
278
- - **新写一个接口 ≠ 新建一个服务**。同一个服务下可以挂多个方法:新服务用 `service.register`(`serviceCode` 已存在会 `duplicate-service` 拒绝),**往已有服务加方法用 `method.create`**(服务不存在 → 拒绝并提示先用 `service.register`;方法已存在 → 拒绝并提示改用 `method.update`)。不要为加一个方法而删服务重建(会连带丢掉服务的其它方法与规则绑定)。
279
-
280
- ### 方法参数面(RESTFUL)
281
-
282
- - **`configJson.position` 只接受五个枚举名**:`QUERY_PARAMS`、`URL_VARIABLE`、`REQUEST_HEADER`、`REQUEST_BODY`、`PATH_PARAMS`。直觉写法 `REQUEST_PARAM` 不存在。入参与出参**都必须带 position**(出参缺它会写成功、但 invoke 时 NPE)。
283
- - **写侧用名字、读侧是整数索引**(`{"position":2}` = `REQUEST_HEADER`)——核对时不要因类型变化误判"没写进去"。映射以写入名为准。
284
- - **常量头(平台注入,如 `X-LowCode-Corpid`/`X-LowCode-EngineCode`)标 `required:false`**:标成必填会让 `method.test` 判缺参而 fail-closed;常量值放 `codeType:0` + `bizCode`。
285
- - `method.test` 缺常量头时拒跑是设计:要么先补齐实参,要么把常量声明为 `required:false`。
286
- - 一次性把**全部**非法参数项改完再重试;0.4.56 起校验会一次列出全部非法项(`issues[]`)。
287
- - **请求头与查询参数都写在方法参数面**(`REQUEST_HEADER` / `QUERY_PARAMS`),不要另外写代码拦截器注入;平台按参数面注入,声明处就是事实源。
288
-
289
- ### 方法 URL 前缀 × 鉴权方式(混用即 401)
290
-
291
- | 方法 url 写法 | 实际拼接 | 鉴权 | 结果 |
292
- |---|---|---|---|
293
- | `/openapi/...` | `/api/openapi/...`(单层) | `OAuth2 Client Credentials`(`xclient`) | ✅ |
294
- | `/api/...` | `/api/api/...`(双层) | 用户身份(用户 token,`client_id=api`) | ✅ |
295
- | `/api/...` + `xclient` | 双层 | 混用 | ❌ `401 权限不够` |
296
- | `/externalLink/...` | 单层免鉴权 | 无 | ⚠️ 能通但方向错误,**禁止**用于二开接口 |
297
-
298
- - **排障先找同 adapter 的既有成功服务当形状模板**:`service.list` → `service.get`,看它的方法 url 前缀与鉴权方式(如 `save` = `/openapi/employee/save` + `xclient`),照抄形状,别从零试。
299
- - 二开扩展接口走 `/openapi` + `xclient`;不要用免鉴权前缀承载业务接口。
300
-
301
- BIZ_ACTION:
302
-
303
- - 先读方法输入/输出参数面。
304
- - `codeType: 1` 的 PROPERTY 输入:`bizCode` 是源字段 code,`propertyName` 是显示名,`serviceMethodParameterCode` 必须命中方法参数。
305
- - **常量入参用 `codeType: 0`(CONST)**(如固定请求头 `X-LowCode-Corpid`):值可放 `bizCode`,也可放 `value`(工具会自动移入 `bizCode`)。平台 CONST **只读 `bizCode`**——`value`/`expressionValue` 原样存着的话,运行时取到的是字面 `null`。
306
- - 参数面每参数必带 `bizPropertyType`(参数类型),否则业务规则按类型匹配字段会失败、映射不到(缺失即拒绝);`externParameterType`/`description` 工具补齐。**返回值参数默认不必填(`required:false`),入参按需必填**——返回值声明为必填会经规则 designer 的 `required` 镜像强制每条输出映射也必填,是绑定摩擦的常见来源。
307
- - `rule.save` 自动补 `serviceName`/`serviceMethodName`/节点 `nodeCategory:"actionNode"`/映射元素 `index`(弹窗异步加载,稍候再判空)。
308
- - 配规则后读回 methodMapping,再用 `rule.run` 或流程实际触发一次。
309
- - 服务、方法、参数不确定时拒绝猜,先读证据。
310
-
311
- ## 按钮与在线 JS
312
-
313
- - 直接执行规则:`useLocation: ROW` + `operateType: WITH_OUT_VIEW`,`bindAction: BUSINESS_RULE` + `targetCode: <自定义 ruleCode>`。
314
- - `WITH_VIEW` 是打开表单。
315
- - FLOW 按钮需要 workflowCode;CLI 会补安全的 actionConfig。
316
- - **按钮规则验收用 `rule.run`**(= 按钮点击的请求):副作用用 `data.load` 另读(返回是执行前快照);规则须自定义且 enabled,被更新行须 COMPLETED(引擎不查 DRAFT)。
317
- - **在线 JS 分两条**:本 CLI 只做**读写与发布**——**读现存脚本用 `onlinejs.get`**(抽 customScript innerHTML 原文+SHA-256,不必拉 form.get --full 六份 JSON;据此决定 replace 还是默认 INCREMENTAL 并入),用 `onlinejs.compose` 把已定稿的 `source` 组合进平台 skeleton 的 `customScript` 再 publish,回读 `publishedHtmlJson`+SHA-256。**确认是否落地用 `summary:true`**(只回 sha/字节数/`draftPublishedInSync`,不回原文,约 600B;默认回显也已去重,不再把同一段脚本回三次)。**脚本内容的编写/增量合并不在本 CLI**:若当前项目自带在线 JS 编写规范(例如其 `.cpdocs`/技能或项目文档),遵循它;否则向用户/设计者索取已定稿的 `source`。不要在 omc 侧手写或臆造 JS 逻辑。
318
-
319
- ## 运行验收与清理
320
-
321
- 运行测试前在草稿里列出:
322
-
323
- - 测试输入值
324
- - 预计生成的记录/调用
325
- - 每个字段的验收值
326
- - 清理哪些测试数据,保留哪些配置
327
-
328
- 推荐顺序:
329
-
330
- ```text
331
- 配置读回 → CLI 运行触发 → workitem/data/service 读回 → 比较字段 → 清理测试数据
332
- ```
333
-
334
- 浏览器不是常规验收步骤;只有用户报告 UI 回归时才做专项诊断。
1
+ ---
2
+ name: omc-business-configuration
3
+ description: 中文 CloudPivot 业务配置 runbook。用于把模型、字段、表单、视图、子表、流程权限、业务规则、按钮、在线 JS 和现存业务集成方法按需求落地并验收。
4
+ ---
5
+
6
+ # CloudPivot 业务配置 Runbook
7
+
8
+ 本技能讲业务需求如何落成配置包;底层 wire 形状和白屏陷阱见 `omc-write-hazards`。
9
+
10
+ ## 入口
11
+
12
+ ```bash
13
+ CLI="packages/omc-cli/dist/cli.js"
14
+ node "$CLI" commands --json
15
+ node "$CLI" describe <tool>
16
+ node "$CLI" contract <lane>
17
+ node "$CLI" call discover_environment --env <env> --args '{}'
18
+ node "$CLI" help omc-config-draft-review
19
+ node "$CLI" help omc-five-piece-flow
20
+ ```
21
+
22
+ ## 需求五维
23
+
24
+ 把需求拆成五个维度(注意:这是**需求维度**,不同于 `omc-five-piece-flow` 的"五件套"执行五阶段):
25
+
26
+ 1. **模型**:主对象、应用、业务 code。
27
+ 2. **字段**:类型、选项、关联目标、模型可空性。
28
+ 3. **表单与视图**:控件、子表 Sheet201、列表列、排序、筛选、按钮。
29
+ 4. **流程**:节点、参与人、每节点字段权限。
30
+ 5. **自动化**:规则、流程绑定、按钮、业务集成、在线 JS。
31
+
32
+ ## 标准顺序
33
+
34
+ **配置顺序以 `omc-config-draft-review` §8 为唯一真相**(模型 → 字段 → 发布 → 表单 → 视图 → 规则 → 流程);执行阶段见 `omc-five-piece-flow`。本 runbook 只补充业务侧要点:
35
+
36
+ 1. 读环境基线:应用、模型、字段、表单、视图、流程、规则、服务。
37
+ 2. 出中文草稿(`omc-config-draft-review`):环境、应用、资源 code、字段表、流程图、映射、按钮、清理方案。
38
+ 3. 取得用户明确确认;确认前不写环境。
39
+ 4. 需求包含运行行为时,用 CLI 真实触发;测试数据的保留/清理要写进草稿。
40
+
41
+ 完成标准:每项资源有 readback;每个运行写入有 delivery 状态;每个映射字段都有目标值;失败步骤、已完成步骤和清理动作可追踪。
42
+
43
+ ## 模型与字段
44
+
45
+ - **code 长度平台硬限**:模型 code ≤26 字符、字段 code ≤28(`business-code-invalid` 出门校验;长业务名先起短 code,长名放 name)。
46
+ - 模型层字段默认可空;必填、只读、可见性在表单和流程节点配置。
47
+ - `propertyEmpty` 默认 `false`,不要把业务必填硬编码在数据模型。
48
+ - 关联单选优先读目标模型已发布字段,明确显示字段;无映射不要写空数组字符串。
49
+
50
+ ## 子表
51
+
52
+ 一个主表可以有多个子表,但子表不能嵌套子表。
53
+
54
+ 创建父字段:
55
+
56
+ ```json
57
+ {
58
+ "schemaCode": "expense",
59
+ "code": "expenseItems",
60
+ "name": "费用明细",
61
+ "propertyType": "CHILD_TABLE",
62
+ "subFields": [
63
+ { "code": "itemName", "name": "费用项目", "propertyType": "SHORT_TEXT" },
64
+ { "code": "itemAmount", "name": "金额", "propertyType": "NUMERICAL" },
65
+ { "code": "itemDate", "name": "发生日期", "propertyType": "DATE" }
66
+ ]
67
+ }
68
+ ```
69
+
70
+ 子列像主表字段一样 CRUD,但 schemaCode 是父字段 code:
71
+
72
+ ```bash
73
+ # --env 在单环境项目(.omc-env 在位)可省;显式写出仅为多环境演示
74
+ omc call field.create --env local --args '{"schemaCode":"expenseItems","code":"itemNote","name":"明细备注","propertyType":"LONG_TEXT"}'
75
+ omc call field.update --env local --args '{"schemaCode":"expenseItems","code":"itemNote","name":"备注"}'
76
+ omc call field.delete --env local --args '{"schemaCode":"expenseItems","code":"itemNote"}'
77
+ omc call field.publish --env local --args '{"schemaCode":"expense"}'
78
+ ```
79
+
80
+ 父表单重建后,Sheet201 控件应有业务子列,子列 key 不应出现在普通 `viewJson`。
81
+
82
+ 声明式面同样支持子表(`intent.yaml`),子列写在父字段的 `subFields` 下;子列只接受标量族,不能嵌套子表/关联/公式:
83
+
84
+ ```yaml
85
+ models:
86
+ - code: expense
87
+ name: 费用报销单
88
+ fields:
89
+ - code: expenseItems
90
+ name: 费用明细
91
+ type: CHILD_TABLE
92
+ subFields:
93
+ - code: itemName
94
+ name: 费用项目
95
+ type: SHORT_TEXT
96
+ - code: itemAmount
97
+ name: 金额
98
+ type: NUMERICAL
99
+ ```
100
+
101
+ `intent plan/apply/readback` 会把子列的缺失、改名、类型漂移当成独立差异(`subfield:` 资源);新增子列自动编译成 `field.create --schemaCode <父字段 code>` 并触发模型级 `field.publish`。父字段本身改名仍用 `field.update`,`field.update` 不接受 `subFields`。
102
+
103
+ `intent apply` 默认在交付后自动做一次最终读回等价确认:等价则 `verification.status:"ok"`;不等价则整体 `refused` + `blocker:"intent-readback-mismatch"`(exit 12),**绝不当作成功**——按 `verification.differences` 定位后重新 `intent plan`,不要盲重试。`--no-verify` 可关闭该确认(只在你已单独跑 readback 时)。
104
+
105
+ 业务规则也进了声明式面(`models[].rules[]`)。常用数据事件规则用紧凑 `actions`,编译器合成 nodeGraph;复杂图用 `nodeGraph` 原样透传(二选一):
106
+
107
+ ```yaml
108
+ rules:
109
+ - code: Update # 平台预置码(Create/Update/Delete…)或自定义码
110
+ name: 更新归档
111
+ enabled: true # true → 额外 rule.enable(publish+启用);false → 已启用则 rule.disable
112
+ actions:
113
+ - type: UPDATE
114
+ target: archive_model # 同模型可省略 target(默认本模型);跨模型才必须写
115
+ filter:
116
+ - field: source_no
117
+ from: doc_no # from = 动态(触发字段);或比较键:equals/notEquals/gt/gte/lt/lte: <标量>
118
+ set:
119
+ - field: amount
120
+ from: total # from = 动态;也可 value: <字面量>
121
+ ```
122
+
123
+ - **每个 filter 项必须有且只有一个条件来源**:`from`(动态 EQ)或一个比较键(`equals`/`notEquals`/`gt`/`gte`/`lt`/`lte` + 标量)。缺来源 `intent-rule-leaf-source-missing` 拒绝;**绝不**静默成 `EQ null`。
124
+ - 动作节点的条件分「定位数据」与「执行门」两类,**别混**(详见下「规则图的触发条件」)。
125
+ - **跨模型写(`targetObjectCode`≠规则模型)平台支持、可运行时生效**(2026-09-28 真机复核)。两个前提:① 承载它的规则必须真的触发——`Create` 规则在提交时触发;`Update` 规则只在 `data.update` 触发,**submit 不执行它**(放进 `Update` 是典型静默空转,lint 警告 `rule-graph-cross-model-update-trigger-inert`;也可绑定流程事件如 `end`)。② 目标行必须已提交——目标查询不匹配 DRAFT 行,`data.save` 未 submit 的行不回写。
126
+ - 关联字段:`type: WORK_SHEET` + `relativeCode: <目标模型>` + `options.displayField`,与 `field.create` 行形状一致。(键名是 `relativeCode`,wire 键 `relativeSchemaCode`,别混。)
127
+ - 节点显示名用 activity 级的 `name`(`participant.name` 只作参与者说明,平台不认作节点名;`{type: Originator, name: 部门负责人}` 会被工具提升为节点名)。
128
+ - **字段级数据规则**(`models[].datarules[]`,`dataRuleType` 1校验/2计算/3拼接/4必填/5只读)——注意是 `dataRuleType`(不是 `type`):
129
+ ```yaml
130
+ datarules:
131
+ - { propertyCode: order_note, dataRuleType: 4, options: { requiredFormula: '{amount} 大于 {"valueType":"custom","value":5000}' }, name: 金额超5000说明必填 }
132
+ ```
133
+ ⚠️ **type 3/4/5 不驱动门户表单控件**(真机复核):平台存成 bizdatarule,只镜像进控件 `historyOpt`,运行时门户读的是控件 `options`。门户条件(如「金额>5000 时必填」)必须写在 `fields[].options`(`requiredFormula`/`readonlyCondition`/`shortTextStitch`);写进 `datarules[]` 设计器可见但门户不拦(validate/plan 给 `datarule-not-portal-facing` 警告)。datarule 只用于服务端引擎(校验/计算)。
134
+
135
+ - 规则排在字段发布之后(编译器按能力排序:先建全部模型/字段,再跑规则)。
136
+ - `rule.save` 只写草稿;`enabled` 由 `rule.enable` 保证(publish+启用)——readback 用 `enabled` 和**语义图摘要**(忽略画布几何)判等价。
137
+
138
+ ## 表单与视图
139
+
140
+ - 新字段加入已有表单:`form.publish --args '{...,"rebuild":true}'` 一步(重建脚手架+强制提拔);或分步 `form.draft --rebuild` → `form.publish`。
141
+ - rebuild 会保留已识别的关系映射、显示和布局 options;Sheet201 columns 按最新子表字段刷新。
142
+ - **配置列表视图用 `listview.configure`(省略 `code` 即平台自动生成的默认视图 code=schemaCode),不要先 `listview.create`——那会多出一个视图。** 只有确实要第二个视图才 create;两者职责分开。
143
+ - 数值控件:`scale` 是小数位数(0=整数,1=1位,2=2位…);「金额两位小数」用 `scale:2`。列表列 displayFormat 由 scale 推导。
144
+ - **`percentile` 不是百分比**(高频误读,2026-10-30 现场反馈):表单控件的 `format` 里 `integer`(200) / `tenths`(2,000.0) / `percentile`(2,000.00) 都是小数位;**百分比要带 `ratio` 词**——`ratio`(20%) / `ratio.tenths`(20.0%) / `ratio.percentile`(20.00),货币用 `RMB.*`/`dollar.*`。字段行 `options.format` 缺 `scale` 时控件按平台默认 `integer` 生成(不是两位小数),所以「金额」务必显式 `scale:2`。
145
+ - **列表列只放业务字段**:平台 `listview.create` 会预置系统列(`name/creater/createdTime/sequenceNo`)和一条 `createdTime` 默认筛选;intent 的 `fieldCodes` 是权威集合,编译器用 `replaceColumns:true + hideDefaultSystemFields:true + conditions:[]` 清掉预置项。要展示某系统列/筛选就显式点名。
146
+ - 筛选条件默认显示:`conditions[].visible` 不传也应为 true。
147
+ - 配置默认视图后即可用 `data.list`;否则部分 8.6.26 读面业务列可能为 null,单行用 `data.load` 核对。
148
+ - 读回视图用 `listview.get`(默认回列/排序/条件/动作紧凑清单)与 `listview.list`。
149
+
150
+ ## 流程
151
+
152
+ - **两条路**:模型+表单+流程一揽子交付走**意图**(`intent` 的 `workflow` 能力,声明式一次 apply);只动流程或想逐步控制走**低层链** `workflow.create → workflow.update(权限/节点)→ workflow.publish`,标准链配方见 `omc describe workflow.update`(含多级审批/participant 三形态/自动回填说明)。流程权限细节见下节。
153
+ - 一个模型只能有一个流程;已有流程的模型直接 `workflow.update`,不要另建。
154
+ - **清理测试数据用批量删除**:`data.delete --args '{"schemaCode":"<code>","bizObjectIds":["id1","id2"]}'` 一次往返删多条(真机 r6:207 次逐条 `data.delete` 占 13.5% 调用)。
155
+ - **「聚合计算」是字段能力,不是规则节点**:FORMULA(计算公式)字段的 `options.formulaConfig.formulaType="AGGREGATION"` 就是 UI 里的「配置方式 → 聚合计算」。**只能汇总另一个模型**(跨模型),**同模型/自己的子表会 305003**(`field.publish` 时 `publishFormula` 把相对码改写成子表父模型 = 本模型,`checkCycleOfAggregation:1032` 判成环;工具已写前拒绝 `aggregation-self-schema-cycle`)。可直接抄 `omc describe field.create --example formula-aggregation-cross-model`。形状:
156
+ `options = {persistent:true, formulaConfig: JSON字符串({resultType:2, formulaType:"AGGREGATION", aggregation: JSON字符串({schemaCode:"<来源模型>", dataItemCode:"<来源列>", calculateType:"SUM", formula:"SUM({<来源模型>.<列>})", queryConditions:[[{propertyCode:"<来源模型字段>", currentPropertyCode:"<目标模型字段>", operatorType:1, valueType:"DYNAMIC"}]]})})}`。
157
+ - **`queryConditions` 不能为空**:它是反向过滤器的唯一输入,空数组时回算日志照打但目标字段恒 0(r17 实测)。
158
+ - **聚合目标必须是来源主表的标量列**(r18 2026-10-26 真机):`aggregation.schemaCode` 指向**子表 schema** 时能发布,但目标模型从此整个不可写(`data.submit`/`data.update` 全回 40000 无明细),且子表无父单单号列、匹配恒 0;`dataItemCode` 写 `<子表>.<子列>` 则 publish 回 309004。要子表合计:先在来源主模型物化一个数值字段,再聚合该标量。
159
+ - **DYNAMIC 条件的两个 code 分属两侧,写反了静默 0**:`propertyCode` = 来源模型字段,`currentPropertyCode` = 目标模型字段;语义「来源行的 X 等于本目标行的 Y」。
160
+ - **`propertyType`/`currentPropertyType`(BizPropertyType)与 `operatorType`(OperatorType)必须写整数**——两者都是 `@JsonCreator get(int)`:propertyType 0=SHORT_TEXT 2=NUMERICAL 3=DATE 9=WORK_SHEET…;operatorType 1=EQ 2=NEQ 3=GT 4=IT(小于)5=GTEQ 6=ITEQ(小于等于)9=IS_NULL 10=IS_NOT_NULL…。写名字会被 `ObjectMapperUtils` 抛 json processing exception,再被死循环校验掩码成 305003(工具接受名字并自动转码)。`calculateType` 相反,是**字符串**(SUM/COUNT/COUNTIF/SUMIF/AVG/MAX/MIN/LARGE/SMALL/UNION);LARGE/SMALL 必须带 k(`LARGE({来源.列},2)`),UNION 是文本拼接、resultType 必须是 0/1。
161
+ - **回算时机(r17b 2026-10-26 完整行为图谱)**:① 目标行**自己 submit** 时镜像规则一定算(最可靠);② 来源行 submit/update/**delete** 触发 `RollUpEventListener`→`FormulaPropertySupport` 事件回算(FIXED+DYNAMIC 混用、多组 OR、COUNTIF/MAX、子表行级条件都验过);③ ⚠️ 事件回算**只在目标模型发布后几分钟内可靠**,之后静默停止(无报错)——`field.publish --force` 目标模型恢复。发布后平台自动生成 t100 `CALCULATE_RULE` 镜像行,别删。r17 真机:目标行 srcNo=C1、来源 C1 行 amt=100 → 读 100;再提交一条 C1/amt=5 → 读 105。
162
+ - **普通数值字段 + 计算规则(type2)不能替代**:REST 路径永不执行(`CalculateHandler.java:50` 的 `forceExecuteCalculateRule` 从不置真),门户里才算。
163
+ - **DYNAMIC 来源不要写点号**:`dataActions` 的 `currentDataItemValue` 写 `items.sealNo` 会被引擎当成**子表列集合**(DatasetImpl),标量目标收到集合 → 运行期 `submit/update` 报 50000(`WorkSheetBizField.fillClientContent: Incorrect value type`)。单跳关联字段直接写字段 code(引擎取 `.id`);子表列只能用于公式/聚合。`rule.save` 已写前拒绝(`rule-graph-create-dynamic-source-is-child-collection`)。
164
+ - **单选字段的 `dataCondition` 在 1.1.25 不生效**:用 FIXED 值比较 RADIO/CHECKBOX/DROPDOWN 时,运行时日志「解析选框类型数据项的比较值失败」,条件被跳过、节点**无条件执行**(r8:异常归还被当成已归还回写)。`rule.save` 会把它放进 `readback.lintHazards`(`rule-graph-radio-condition-unparseable`);改用 BRANCH 判断该字段,或拆两条规则。
165
+ - **预置 Update 规则里的跨模型回写只在 `data.update` 触发**:`rule-graph-cross-model-update-trigger-inert` 现在是 hazard(在 `readback.lintHazards`,不混进 `lintWarnings`);create/submit 不执行它。要提交时生效,把节点放进 Create 规则或流程事件规则。
166
+ - **部门/员工写面**:`org.department.create`(`deptType` 传名字或 0..3,平台按 **int 枚举**反序列化,写 `"dept"` 会 10009)→ `org.department.update`(先读再合并)/`org.department.delete`(`strike_out`);`org.user.create` 必填 `name/username/departmentId`,**平台对 `userLabelIds`/`userManagerConfigList`/`roleIds` 的 null 直接 NPE**(50000,`UserHelper.dealUserLabel:428`),工具已补空数组;`org.user.update` 同样必须先补 `deptIds` 等数组(`UserHelper.modified` 对 null 的 `deptIds` NPE)。`org.department.list` 递归展开整棵树(`/department/tree` 每次只返回一层);`org.department.users` / `org.user.info` / `org.user.departments` 用于回读。新建用户的 `active` 平台回读为 false(账号激活另有流程),不要把 active 当作创建成功标志。
167
+ - **组织写面(角色/成员/管理范围)**:界面上的「分组」= 角色(`group:false`),容器是角色组(`group:true`)。链路:`org.roleGroup.create`(**parentId 必填**,1.1.25 传空会 50000 `parentIds cannot be empty`)→ `org.role.create`(roleId 平台生成,code 随后补 `SYS<时间戳>`)→ `org.role.addUsers`(`id` 必须传 **recId**,不是账号;工具补 `type:3`/`unitType:"3"`)→ `org.role.setUserScope`(**平台覆盖语义、无回滚**:默认读旧值合并,`mode:"replace"` 才整体替换;`unitType` 传数字 3)。读回用 `org.role.users` / `org.role.userScope`。
168
+ - **数据作用域三档口径**(r33 实测反推,非官方定义):作用域条目带 `type` 标识口径——全公司(无条目/空即全司)、部门范围(`ouScope` 指向部门 id,成员只见该部门数据)、自定义(多条目组合)。`setUserScope` 默认 merge、`mode:"replace"` 整替;判口径看 `org.role.userScope` 读回的条目数与指向,空≠未配置失败(空=全公司或不可解析,结合 `org.role.users` 的 `scopeCount` 区分)。
169
+ - **`corpId` 默认写当前企业,不要写 `"other"`**(r9 2026-10-22 更正):`RoleGroupHelper.createRoleGroupModel` 只从 body 取 corpId,缺省才按 parentId 反查企业设置、查不到就 NPE(被吞成 errcode 0);`other` 是平台**合法的「非组织分组」语义**,不是兜底值。工具现在默认 `session.enterpriseId()`,并回读校验(不一致给 warning);只有明确要建非组织分组时才显式传 `corpId:"other"`。
170
+ - **`org.role.list` 必须带 `expandAll=true`**:`/role/list` 不带该参数只展开**第一个**角色组的 children(`RoleController.list`,1.1.25),所以建在别的组下的角色读回为「不存在」——r9 因此误判「角色未创建」并重复读了 8 次。工具已默认展开,`expandAll:false` 才拿容器面。
171
+ - **`org.role.update` 的平台前置条件**:`modify_role` 的 `validateRoleParam` 要求 `roleName` 与 `roleGroupId` **都非空**(否则 202001 / 202012),所以只改备注也要回填这两项——工具已从 `role/get` 自动补齐。`corpId` 在 update 里不生效,换企业只能重建角色。
172
+ - **角色参与人可用性**:r9 对照实验证明,`corpId=other` 的角色**仍能正常解析参与人**(把非发起人加进该角色后,待办正确落到该人)。所以「审批人变成发起人」要按**解析为空**排查(角色零成员 / 成员没挂部门 / 自由文本 / 角色码不存在),不要归因于 corpId。
173
+ - **系统持久化节点是平台自带的,不要手写**:预置 `Create/Update/Delete` 行本来就带 `SYSTEM_CREATE/SYSTEM_UPDATE/SYSTEM_DELETE`(实测基线 `Start → Create(SYSTEM_CREATE) → End`)。`rule.save` 会丢弃调用方传入的 `SYSTEM_*` 并**从平台基线原样携带**那一个节点(code/name/几何都不由我们猜)。手写的后果:预置规则里类型错配 → `data.submit` 50000;自定义规则/流程事件里带系统节点 → 结束事件无当前对象,`Object <id> is not serialized`,每步仍回 DELIVERED 但什么都没写。只写平台允许配置的业务节点(UPDATE/CREATE/DELETE/BRANCH/ASSIGN/MESSAGE/BIZ_ACTION…)。
174
+ - **QUERY 节点的三个「静默空结果」入口(1.1.25 源码,r9 §14.1)**:`QueryActivityBehavior:47-50`(输入数据为空,或 `dataCondition` 过滤后为空 → `return null`)、`:78-86`(`mainConditions` 为空 → `emptyList`;目标带 `.` 时 `sheetConditions` 为空也 `emptyList`)、`ActivityBizObjectManagerImpl:172-178`(真查询 0 命中 → `emptyList`)。**三条都不写日志**,所以「节点执行开始后什么都没有」既可能是配置错也可能是 0 命中。前两条工具已在写前拦:`rule-graph-query-conditions-empty`(无条件叶,fail closed——QUERY 的空条件**不是**「查全表」,与 UPDATE 语义相反;要全表请写恒真叶)与 `rule-graph-query-datacondition-empty`(hazard)。第三条只能在运行期验证:先 `data.list` 确认目标行存在且状态已提交。
175
+ - **取 artifact 先投影**:`artifact get <id> --summary` 看形状 → `--jq '<path>'` 取那一段;只有确实要整份时才裸取(r6:47 次整包拉取 650KB,0 次用投影)。
176
+ - **读回产物会自动清理,也可手动清**:每次大结果落一个 artifact(机器级目录,跨项目累积;实测一轮沙盒 504 个/3.4MB)。`createArtifact` 每 24h 顺手删 7 天未动的;要立刻回收用 `omc artifact prune --older-than 7d --max-bytes 268435456`(默认预算 256MB,超出按最旧优先删),`--json` 回 removed/freedBytes/kept。
177
+ - **核验状态用投影,别重复整条读**:`data.load --args '{"schemaCode":..,"bizObjectId":..,"fields":["sequenceStatus","<业务状态字段>"]}'` 只回指定字段(缺失项在 `missingFields`)——r4 实测同一对象被整条读 74 次;大结果先 `artifact get <id> --summary` → `--keys/--jq`,别整包内联进推理。
178
+ - **流程第一个节点必须是 `{Originator}` 填写节点**:平台 `WorkflowTemplateValidateUtility.validateStartActivity` 对「START 后直接接审批」报 401035「发起节点的参与者只能是{Originator}」,publish 回 errcode 0 但一个 ValidateMessage[] 就把模板挡在门外、不落库(真机 2026-10-21)。OMC 0.4.65 起在 `workflow.update`/`publish` 出门前拒绝(`workflow-first-activity-not-originator`)。
179
+ - **跨模型回写的目标行必须已提交(COMPLETED)**:DRAFT 行不参与目标查询,运行 0 行命中且日志只有「查找范围不存在数据」。单跳关联字段可直接作 DYNAMIC 来源(引擎取关联对象 id);点号路径按子表解析,`关联字段.id` / 两跳路径都不支持。
180
+ - **参与人解析失败分两种,别当成同一种(1.1.25 实测)**:① **解析为空**(角色零成员、成员未挂部门、自由文本、角色码不存在)→ 引擎把任务**退回发起人**,流程继续,症状是「审批人显示成我自己」,`data.submit` 与 `workitem.list` 都正常;② **参与人函数未注册**(`FindUsersByRoleFunction` / `FindUsersSpecByRoleFunction`,1.1.25 未内置)→ `FunctionNotFoundException`、不生成待办、实例卡 PROCESSING 而接口仍回 `errCode 0`。区分用 `omc call workflow.instanceActivity --args '{"workflowInstanceId":"<id>"}'`:无 `status:1` 节点=卡住;`status:1` 且参与人全是发起人=`diagnosis.warnings` 给 `participant-fallback-to-originator`。不要从 `workflow.get_published` 的摘要回抄函数式参与人;内置写法只有 `FindUserByRole(org, roleCode[, order])`,跨部门/全局角色优先用参与者对象数组(角色 id)。
181
+
182
+ ## 流程字段权限
183
+
184
+ 新增字段后,必须重新检查每个参与节点的 `propertyPermissions`:
185
+
186
+ - 填写节点:需求要求的字段可编辑,必要字段必填。
187
+ - 审批节点:通常 `visible:true, editable:false, required:false`。
188
+ - 不需要展示的字段明确 `visible:false`。
189
+
190
+ **怎么配**(没有单独的权限工具,别把「找不到入口」当不支持→假阻塞):**增量即可**——`workflow.update` 直接发
191
+ `activities:[{activityCode, propertyPermissions:[{propertyCode, visible, editable, required, subPropertyPermissions}]}]`
192
+ (**type 可省**:activityCode 命中草稿节点时类型/名称继承草稿、code 稳定不铸 `_2`;不必先读 `get_draft --full` 整回写。
193
+ `summary` 无需配,工具自动补 `true`),改完 `workflow.publish`。(`summary:true` 才进运行时 `activitySummaries`,工具已自动处理;
194
+ 否则 `form/load` 的 `formPermission.dataPermissions` 回退默认,读不到你的 `editable:false`);子表列用 `subPropertyPermissions`。
195
+ **子表属性本身不可设 `required:true`**(接口接受但设计器节点字段配置不支持——工具拒 `workflow-child-table-not-required`):子表整行只配 visible/editable,字段级必填配到子表内部字段。**新增节点用语义 code**(如 `approval_finance`),不要给 `ActivityN` 加 `_1/_2` 后缀;改名才会铸新码(平台不能原地改名)。**子表操作按钮与驳回默认已由工具自动补齐**:填写节点自动配 `buttonPermission.childTableBtns`(每子表 新增/删除/导入/导出 启用、批量编辑禁用),`permittedAction` 按设计器默认归一(填写人不可驳回自己 `showReject:false`;审批节点驳回回发起 `rejectToStart:true`)——显式传 `permittedAction`/`buttonPermission` 可覆盖。
196
+ 验收用 `npm run test:live-permission` + **非管理员参与者**(管理员会被后端强制 `e:true` 掩盖)。
197
+
198
+ 声明式流程(intent)的 `workflow.template` 用 **`activities` + `routes`**(不是 `nodes`/`edges`):活动 `{id, type: START|END|PARTICIPANT|…, participant?}`,路由 `{preNode, postNode}` 必须指向已声明活动 code;`workflow.update` 只从 `activities` 读节点,写 `nodes` 会在 apply 时 `rules[0] references an unknown activity`(validate 会提前拦)。
199
+
200
+ 示例:
201
+
202
+ ```text
203
+ 开始 → 填写申请单 → 部门审批 → 财务审批 → 结束
204
+ ```
205
+
206
+ 如果环境里存在测试节点,先确认保留还是删除,不要静默覆盖。
207
+
208
+ ## 业务规则
209
+
210
+ **先选对机制,别一律上规则图**(三者代价差很大):
211
+
212
+ | 需求形态 | 用哪个 | 入口 |
213
+ | --- | --- | --- |
214
+ | 条件必填 / 条件只读 / 恒只读(表单交互层) | 字段 `options` | `field.update --example form-conditional-required` / `form-conditional-readonly` / `form-always-readonly` |
215
+ | 服务端字段级校验 / 计算 / 拼接 / 显示(type 1..5) | 数据规则 | `datarule.create`(字段级引擎) |
216
+ | 数据事件自动化(创建/更新时改字段、调服务、发起流程) | 规则图 | 本节 `rule.save`(`--example standard-business-rule-graph`) |
217
+
218
+ 「超过某值必填 / 等于某值只读」= **字段 options**,**不要**建规则图、也**不要**用 `datarule.create`
219
+ (实测:`datarule` type3/4/5 不会让门户表单控件变必填/只读,且符号式经 `historyOpt` 镜像会让表单渲染崩溃;
220
+ 平台的 bizdatarule 会被镜像进表单控件 `historyOpt`,而设计器/运行时读的是控件 `options`)。
221
+
222
+ ### 条件式(必填/只读/显示)怎么写
223
+
224
+ - **恒必填**:`requiredFormula: "true"`(字面量);**条件必填**:`requiredFormula: "{触发字段} 操作符 {\"valueType\":...,\"value\":...}"`。
225
+ - 操作符用**中文**(设计器产出):等于/不等于/大于/小于/大于等于/小于等于/包含/包含全部/等于任意一个/为空/不为空;也接受符号 `== != > >= <= Contains IsEmpty`。为空/不为空**不带值**。
226
+ - 写完后 `field.publish` → `configure_form`(同步进控件)→ **门户打开表单改触发字段的值**,断言目标控件变 `required` / `disabled`(存储回读不算验证)。
227
+ - 恒只读:`readonlyFormula: true`。
228
+
229
+ ### 日期类校验(不早于今天 / 截止日不早于开始日)
230
+
231
+ **这类需求走表单的「提交校验」,不要用规则图 BRANCH。** 平台有专门的日期值类型 `today`/`yesterday`/`tomorrow`(不是"没有今天的系统变量"):
232
+
233
+ - **字段 vs 今天**(`planDate` 不得早于今天)→ `datarule.create {dataRuleType:1, checkType:2}`:
234
+ ```json
235
+ {"schemaCode":"<m>","propertyCode":"planDate","dataRuleType":1,"checkType":2,
236
+ "name":"计划日期不早于今天",
237
+ "options":{"conditions":"{planDate} 大于等于 {\"valueType\":\"today\"}",
238
+ "prompt":"计划日期不能早于今天"}}
239
+ ```
240
+ - **字段 vs 字段**(结束日不早于开始日)→ 同一 `checkType:2`,右值必须写成值对象:
241
+ `{end_date} 大于等于 {"valueType":"currentModel","value":"start_date"}`(**裸 `{start_date}` 右值不会生效**)。
242
+ - **规则图里要按"今天"分支**(例如给逾期记录打标)→ 条件叶用 `ruleValueType:"today"` + `currentSchemaDataItem:"<offset>"`(`"0"`=今天、`"-7"`=7 天前、`"30"`=30 天后,可带单位 `"-1-week"`);**不要用 `ruleValueType:"DYNAMIC"` 去和另一个日期字段比**——那会 `data.submit` 稳定 50000(`SingletonDataset.getBizObjectProperty` NPE,r10 复现)。
243
+ - 校验规则的执行面:**门户前端提交时**执行;REST `data.submit`/`data.update` **不拦**(r9 已用越界数据实测)。要用 API 兜底就得二开。
244
+
245
+ ### 规则图的触发条件(关键)
246
+
247
+ 规则支持**触发条件**,不要用「两条无条件规则」表达互斥结果:
248
+
249
+ - **节点级条件有两大类,别混**(各节点类型都可能有,完整字段/样例见 `describe rule.save --full` 的 RULE_NODE_TEMPLATES):
250
+ - **`ASSIGN`(数据赋值)不是新节点,新的是它的形状**:8.6.26 自带 `BusinessRuleUpgradeServiceImpl` + `UpgradeRuleHandler.upgradeAssignNode`,职责就是把旧形状(`triggerObjectCode`/`currentDataModel`/`targetSchemaModel`/`tableConditionGroups`)升级为 `inputParam`+`targetObjectCode`+`nonMatchingAdded`——存在升级器就说明 8.6 之前已经有 ASSIGN。**现代形状从 8.6 起**,AI-Platform 1.x 同形;<8.6 存的是旧形状,写现代形状会被静默误读,OMC 直接拒绝(`rule-graph-assign-version-unsupported`)。
251
+ - **`ASSIGN` 的 `targetObjectCode` 是写入目标,不是取数来源**:默认 `_input` = 当前单;`dataActions.targetDataItem` 必须是**该目标模型已发布字段**(写成别的模型 + 本单字段 → 550018)。要"从关联单取值回填本单",就 `targetObjectCode:"_input"` + `currentDataItemValue:"<本单来源字段>"`(关联字段引擎取 .id),不要把它指到对面模型。漏 `nonMatchingAdded` 会让 quote 阶段 NPE(publish 50000),工具已按设计器键补齐(warning `rule-graph-assign-keys-defaulted`)。
252
+ - **定位要操作哪些数据**(`filterCondition`:叶 `targetSchemaDataItem`+`ruleConditionType`):UPDATE=改哪条(空=整表)、DELETE=删哪条、CREATE=存在符合则不新增+`findMainData`、QUERY=`targetConditionGroups`、**ASSIGN=在目标表里匹配要赋值的行(不是执行门!)**。⚠️ 非唯一字段(金额等)会命中**多条**;改「当前/关联那一条」用唯一键(`id` 或 `from`)。`actions.filter` 表达这类(同模型省略→默认 `id=id`;跨模型省略 fail-closed)。
253
+ - **执行门 / 满足条件触发**(`dataCondition`,BIZ_ACTION 用 `execConditionGroups`):**与定位两码事**——决定「要不要执行」,不决定「改哪条」。compact `actions.when` 表达 UPDATE 执行门(叶 `currentSchemaDataItem`+`ruleDataConditionType`);其它节点用 `nodeGraph`。**条件改自身**:`when: [{field: amount, gt: 10000}]` + 省略 `filter`(默认 `id=id`)——别把条件写进 `filter`,那会改掉所有符合的行(bench66 多行互覆)。
254
+ - 子表定位 `filterSubCondition`;子表触发范围 `childTriggerConditionType∈ADD/MODIFY/DELETE`。
255
+ - **触发时机**:数据事件(`Create`/`Update`/`Delete`)**只分发到平台预置 code 行**;流程相关用预置 `Available`(流程生效)/`Cancel`(流程作废),或流程事件绑定 `workflow.bindRule` 的 `end`/`cancel`/`start`/`activate`。
256
+ **"流程结束后执行"的两个正式入口(2026-10-16 真机反馈消歧)**:① **流程设计 → 流程结束后**(对应 `workflow.bindRule` 的 `end`,绑定自定义规则承载动作节点);② **平台自带业务规则的"流程生效"(预置 `Available`)行**。两者都在**流程实例走完归档/审批通过那一刻**触发,**不是进入流程时**——进入流程时刻是 `start`/`activate`。业务要"审批通过后执行某逻辑"时只用 ①或②,**禁止**绑 `start`/`activate`,也勿误用 `Create`(提交即触发,时序等同流程发起)。
257
+ - **预置规则身份不可改**:`Create`/`Update`/`Delete`/`Load`/`GetList`/`Available`/`Cancel` 名称由平台维护(`Create`=数据新增);传 `name` 不生效(仅自定义规则名称可改),可改的是节点名 `nodeName`。
258
+ - **节点级事件**:`workflow.bindRule` 还可传 `activityCode`+`nodeEvent`(`endActivity`=提交离开该节点、`activityApproved`/`activityRejected`、`beforeActivate`/`afterActivate`、`cancelActivity`、`jobSubmitted`/`jobRejected`),与 `handler` 互斥。
259
+ - **流程事件只绑自定义规则**:`workflow.bindRule` 的 `ruleCodes` 只能给自定义 code——设计器的事件规则选择器不提供预置触发行(绑 `Create` 到 `end` 非法,工具 fail closed)。要「审批通过后执行某逻辑」:新建自定义规则承载动作节点再绑 `end`。
260
+ - **审批通过/驳回回写**:通过绑 `end`(或 `Available`)、驳回绑 `cancel`(或 `Cancel`)——**不要**把两条各自 FIXED 赋值的规则都绑到 `endEventHandler`(两条都会执行、后者覆盖前者;工具会在 `bizActions.length>1` 时回读 `advisory` 提醒)。真实生产只在 `endEventHandler` 绑**一条**多分支规则图。
261
+
262
+ 规则图需要显式:
263
+
264
+ - `ruleCode`、名称、触发方式
265
+ - 每个节点 `nodeName`
266
+ - `nodeType`、路由、目标模型
267
+ - 主表/子表 `findMainData`
268
+ - 每个映射的 source/target 字段:**新增/更新节点的字段映射 = `dataActions`**(每目标字段一条叶:`{targetDataItem, ruleValueType:"DYNAMIC"/"FIXED", value:""/字面量, ruleActionType:"EQUALS", currentDataItemValue:"<来源字段>"}`)。**`execFormMapping` 只是展示开关,不是映射来源**——只写 `execFormMapping:true` 不写 `dataActions`,「主表字段值设置」是空的、新增行业务字段全 null(工具对 `execFormMapping:true` 缺 `dataActions` fail closed `rule-graph-create-mapping-missing`)。**写跨表节点前先 `rule.get` 读一条真实同型规则(如 `purchase_request`/`Create`)当基线**,不要从模板推断。
269
+
270
+ 流程结束后新增目标表单,配置发布后要真实跑完流程,再读目标表单每个字段(`data.load`,不是 `data.list`——后者只投影列表列);只看到规则 `ENABLED` 不等于映射成功。
271
+
272
+ ## 业务集成
273
+
274
+ 优先复用现存 service/method,不做无必要的部署:
275
+
276
+ ```bash
277
+ omc call service.get --env local --args '{"serviceCode":"omc_demo_restful"}'
278
+ ```
279
+
280
+ - **新写一个接口 ≠ 新建一个服务**。同一个服务下可以挂多个方法:新服务用 `service.register`(`serviceCode` 已存在会 `duplicate-service` 拒绝),**往已有服务加方法用 `method.create`**(服务不存在 → 拒绝并提示先用 `service.register`;方法已存在 → 拒绝并提示改用 `method.update`)。不要为加一个方法而删服务重建(会连带丢掉服务的其它方法与规则绑定)。
281
+
282
+ ### 方法参数面(RESTFUL)
283
+
284
+ - **`configJson.position` 只接受五个枚举名**:`QUERY_PARAMS`、`URL_VARIABLE`、`REQUEST_HEADER`、`REQUEST_BODY`、`PATH_PARAMS`。直觉写法 `REQUEST_PARAM` 不存在。入参与出参**都必须带 position**(出参缺它会写成功、但 invoke 时 NPE)。
285
+ - **写侧用名字、读侧是整数索引**(`{"position":2}` = `REQUEST_HEADER`)——核对时不要因类型变化误判"没写进去"。映射以写入名为准。
286
+ - **常量头(平台注入,如 `X-LowCode-Corpid`/`X-LowCode-EngineCode`)标 `required:false`**:标成必填会让 `method.test` 判缺参而 fail-closed;常量值放 `codeType:0` + `bizCode`。
287
+ - `method.test` 缺常量头时拒跑是设计:要么先补齐实参,要么把常量声明为 `required:false`。
288
+ - 一次性把**全部**非法参数项改完再重试;0.4.56 起校验会一次列出全部非法项(`issues[]`)。
289
+ - **请求头与查询参数都写在方法参数面**(`REQUEST_HEADER` / `QUERY_PARAMS`),不要另外写代码拦截器注入;平台按参数面注入,声明处就是事实源。
290
+
291
+ ### 方法 URL 前缀 × 鉴权方式(混用即 401)
292
+
293
+ | 方法 url 写法 | 实际拼接 | 鉴权 | 结果 |
294
+ |---|---|---|---|
295
+ | `/openapi/...` | `/api/openapi/...`(单层) | `OAuth2 Client Credentials`(`xclient`) | ✅ |
296
+ | `/api/...` | `/api/api/...`(双层) | 用户身份(用户 token,`client_id=api`) | ✅ |
297
+ | `/api/...` + `xclient` | 双层 | 混用 | ❌ `401 权限不够` |
298
+ | `/externalLink/...` | 单层免鉴权 | 无 | ⚠️ 能通但方向错误,**禁止**用于二开接口 |
299
+
300
+ - **排障先找同 adapter 的既有成功服务当形状模板**:`service.list` → `service.get`,看它的方法 url 前缀与鉴权方式(如 `save` = `/openapi/employee/save` + `xclient`),照抄形状,别从零试。
301
+ - 二开扩展接口走 `/openapi` + `xclient`;不要用免鉴权前缀承载业务接口。
302
+
303
+ BIZ_ACTION:
304
+
305
+ - 先读方法输入/输出参数面。
306
+ - `codeType: 1` 的 PROPERTY 输入:`bizCode` 是源字段 code,`propertyName` 是显示名,`serviceMethodParameterCode` 必须命中方法参数。
307
+ - **常量入参用 `codeType: 0`(CONST)**(如固定请求头 `X-LowCode-Corpid`):值可放 `bizCode`,也可放 `value`(工具会自动移入 `bizCode`)。平台 CONST **只读 `bizCode`**——`value`/`expressionValue` 原样存着的话,运行时取到的是字面 `null`。
308
+ - 参数面每参数必带 `bizPropertyType`(参数类型),否则业务规则按类型匹配字段会失败、映射不到(缺失即拒绝);`externParameterType`/`description` 工具补齐。**返回值参数默认不必填(`required:false`),入参按需必填**——返回值声明为必填会经规则 designer 的 `required` 镜像强制每条输出映射也必填,是绑定摩擦的常见来源。
309
+ - `rule.save` 自动补 `serviceName`/`serviceMethodName`/节点 `nodeCategory:"actionNode"`/映射元素 `index`(弹窗异步加载,稍候再判空)。
310
+ - 配规则后读回 methodMapping,再用 `rule.run` 或流程实际触发一次。
311
+ - 服务、方法、参数不确定时拒绝猜,先读证据。
312
+
313
+ ## 按钮与在线 JS
314
+
315
+ - 直接执行规则:`useLocation: ROW` + `operateType: WITH_OUT_VIEW`,`bindAction: BUSINESS_RULE` + `targetCode: <自定义 ruleCode>`。
316
+ - `WITH_VIEW` 是打开表单。
317
+ - FLOW 按钮需要 workflowCode;CLI 会补安全的 actionConfig。
318
+ - **按钮规则验收用 `rule.run`**(= 按钮点击的请求):副作用用 `data.load` 另读(返回是执行前快照);规则须自定义且 enabled,被更新行须 COMPLETED(引擎不查 DRAFT)。
319
+ - **在线 JS 分两条**:本 CLI 只做**读写与发布**——**读现存脚本用 `onlinejs.get`**(抽 customScript innerHTML 原文+SHA-256,不必拉 form.get --full 六份 JSON;据此决定 replace 还是默认 INCREMENTAL 并入),用 `onlinejs.compose` 把已定稿的 `source` 组合进平台 skeleton 的 `customScript` 再 publish,回读 `publishedHtmlJson`+SHA-256。**确认是否落地用 `summary:true`**(只回 sha/字节数/`draftPublishedInSync`,不回原文,约 600B;默认回显也已去重,不再把同一段脚本回三次)。**脚本内容的编写/增量合并不在本 CLI**:若当前项目自带在线 JS 编写规范(例如其 `.cpdocs`/技能或项目文档),遵循它;否则向用户/设计者索取已定稿的 `source`。不要在 omc 侧手写或臆造 JS 逻辑。
320
+
321
+ ## 运行验收与清理
322
+
323
+ 运行测试前在草稿里列出:
324
+
325
+ - 测试输入值
326
+ - 预计生成的记录/调用
327
+ - 每个字段的验收值
328
+ - 清理哪些测试数据,保留哪些配置
329
+
330
+ 推荐顺序:
331
+
332
+ ```text
333
+ 配置读回 → CLI 运行触发 → workitem/data/service 读回 → 比较字段 → 清理测试数据
334
+ ```
335
+
336
+ 浏览器不是常规验收步骤;只有用户报告 UI 回归时才做专项诊断。