@stalfh233/omc-cli 0.0.0-stage → 0.4.10

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 (181) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +293 -3
  3. package/coverage/m1-coverage-manifest-v1.json +1278 -0
  4. package/dist/approval-token.js +102 -0
  5. package/dist/args.js +25 -0
  6. package/dist/artifacts.js +119 -0
  7. package/dist/bench/call-face-eval.js +256 -0
  8. package/dist/bench/context-attribution.js +151 -0
  9. package/dist/bench/discovery-cost-eval.js +230 -0
  10. package/dist/bench/driver.js +81 -0
  11. package/dist/bench/evals.js +236 -0
  12. package/dist/bench/fake-http-server.js +65 -0
  13. package/dist/bench/instrument.js +87 -0
  14. package/dist/bench/intent-face-eval.js +343 -0
  15. package/dist/bench/run.js +166 -0
  16. package/dist/bench/scenario.js +343 -0
  17. package/dist/bench/types.js +76 -0
  18. package/dist/bi-wire.js +41 -0
  19. package/dist/bizservice-config.js +581 -0
  20. package/dist/call.js +153 -0
  21. package/dist/capability-absences.js +23 -0
  22. package/dist/capability-overview.js +492 -0
  23. package/dist/capability-shape.js +154 -0
  24. package/dist/cli-contract.js +70 -0
  25. package/dist/cli-output.js +73 -0
  26. package/dist/cli.js +1418 -0
  27. package/dist/code-rules.js +69 -0
  28. package/dist/command-transport.js +155 -0
  29. package/dist/config-store.js +195 -0
  30. package/dist/context.js +21 -0
  31. package/dist/contract-consistency.js +66 -0
  32. package/dist/contract-resources.js +62 -0
  33. package/dist/coverage-consistency.js +62 -0
  34. package/dist/coverage-registry.js +76 -0
  35. package/dist/coverage.js +130 -0
  36. package/dist/data-list-filter.js +114 -0
  37. package/dist/discovery.js +390 -0
  38. package/dist/endpoints.js +154 -0
  39. package/dist/environment-policy.js +26 -0
  40. package/dist/execution-metadata.js +1092 -0
  41. package/dist/fake/app.js +45 -0
  42. package/dist/fake/b2-registration.js +594 -0
  43. package/dist/fake/businessrule.js +242 -0
  44. package/dist/fake/datarule.js +72 -0
  45. package/dist/fake/dictionary.js +108 -0
  46. package/dist/fake/environment.js +30 -0
  47. package/dist/fake/field.js +167 -0
  48. package/dist/fake/form.js +101 -0
  49. package/dist/fake/index.js +121 -0
  50. package/dist/fake/list-view.js +289 -0
  51. package/dist/fake/model.js +136 -0
  52. package/dist/fake/online-js.js +18 -0
  53. package/dist/fake/report.js +253 -0
  54. package/dist/fake/routes.js +47 -0
  55. package/dist/fake/rule-lifecycle.js +37 -0
  56. package/dist/fake/runtime-data.js +367 -0
  57. package/dist/fake/state.js +67 -0
  58. package/dist/fake/workflow.js +364 -0
  59. package/dist/field-change.js +200 -0
  60. package/dist/field-families.js +896 -0
  61. package/dist/form-layout.js +111 -0
  62. package/dist/form-support.js +821 -0
  63. package/dist/goal-routes.js +468 -0
  64. package/dist/governed-execution.js +87 -0
  65. package/dist/human-summary.js +212 -0
  66. package/dist/identity.js +62 -0
  67. package/dist/intent/baseline.js +57 -0
  68. package/dist/intent/capabilities/bizservice.js +274 -0
  69. package/dist/intent/capabilities/businessrule.js +493 -0
  70. package/dist/intent/capabilities/datarule.js +187 -0
  71. package/dist/intent/capabilities/field.js +545 -0
  72. package/dist/intent/capabilities/form.js +136 -0
  73. package/dist/intent/capabilities/index.js +64 -0
  74. package/dist/intent/capabilities/listview.js +157 -0
  75. package/dist/intent/capabilities/model.js +100 -0
  76. package/dist/intent/capabilities/onlinejs.js +108 -0
  77. package/dist/intent/capabilities/report.js +355 -0
  78. package/dist/intent/capabilities/workflow.js +458 -0
  79. package/dist/intent/capability.js +6 -0
  80. package/dist/intent/cli.js +91 -0
  81. package/dist/intent/compare.js +56 -0
  82. package/dist/intent/compiler.js +79 -0
  83. package/dist/intent/dsl.js +129 -0
  84. package/dist/intent/plan-file.js +63 -0
  85. package/dist/intent/readback.js +65 -0
  86. package/dist/intent/schema.js +158 -0
  87. package/dist/intent/validation.js +30 -0
  88. package/dist/intent/yaml.js +315 -0
  89. package/dist/json-column.js +68 -0
  90. package/dist/lanes/app-contract.js +95 -0
  91. package/dist/lanes/app-coverage.js +16 -0
  92. package/dist/lanes/app.js +174 -0
  93. package/dist/lanes/apply-changes.js +231 -0
  94. package/dist/lanes/b2-registration-contract.js +292 -0
  95. package/dist/lanes/b2-registration-coverage.js +48 -0
  96. package/dist/lanes/b2-registration.js +1187 -0
  97. package/dist/lanes/businessrule-contract.js +232 -0
  98. package/dist/lanes/businessrule-coverage.js +16 -0
  99. package/dist/lanes/businessrule.js +221 -0
  100. package/dist/lanes/contract-support.js +65 -0
  101. package/dist/lanes/coverage-declaration.js +9 -0
  102. package/dist/lanes/datarule-contract.js +155 -0
  103. package/dist/lanes/datarule-coverage.js +19 -0
  104. package/dist/lanes/datarule-protocol.js +308 -0
  105. package/dist/lanes/datarule.js +818 -0
  106. package/dist/lanes/dictionary-contract.js +78 -0
  107. package/dist/lanes/dictionary-coverage.js +24 -0
  108. package/dist/lanes/dictionary.js +235 -0
  109. package/dist/lanes/environment-contract.js +70 -0
  110. package/dist/lanes/environment-coverage.js +14 -0
  111. package/dist/lanes/environment.js +163 -0
  112. package/dist/lanes/field-contract.js +223 -0
  113. package/dist/lanes/field-coverage.js +27 -0
  114. package/dist/lanes/field.js +374 -0
  115. package/dist/lanes/form-contract.js +97 -0
  116. package/dist/lanes/form-coverage.js +16 -0
  117. package/dist/lanes/form.js +185 -0
  118. package/dist/lanes/lane-ids.js +34 -0
  119. package/dist/lanes/list-view-contract.js +175 -0
  120. package/dist/lanes/list-view-coverage.js +20 -0
  121. package/dist/lanes/list-view-shapes.js +1207 -0
  122. package/dist/lanes/list-view.js +578 -0
  123. package/dist/lanes/meta-contract.js +85 -0
  124. package/dist/lanes/meta.js +255 -0
  125. package/dist/lanes/model-contract.js +168 -0
  126. package/dist/lanes/model-coverage.js +20 -0
  127. package/dist/lanes/model.js +986 -0
  128. package/dist/lanes/online-js-contract.js +99 -0
  129. package/dist/lanes/online-js-coverage.js +28 -0
  130. package/dist/lanes/online-js.js +127 -0
  131. package/dist/lanes/report-contract.js +144 -0
  132. package/dist/lanes/report-coverage.js +21 -0
  133. package/dist/lanes/report.js +476 -0
  134. package/dist/lanes/rule-graph.js +1846 -0
  135. package/dist/lanes/rule-lifecycle-contract.js +92 -0
  136. package/dist/lanes/rule-lifecycle-coverage.js +20 -0
  137. package/dist/lanes/rule-lifecycle.js +176 -0
  138. package/dist/lanes/runtime-data-contract.js +264 -0
  139. package/dist/lanes/runtime-data-coverage.js +25 -0
  140. package/dist/lanes/runtime-data.js +1054 -0
  141. package/dist/lanes/workflow-contract.js +223 -0
  142. package/dist/lanes/workflow-coverage.js +40 -0
  143. package/dist/lanes/workflow.js +1813 -0
  144. package/dist/online-js-layout.js +58 -0
  145. package/dist/online-js-source.js +276 -0
  146. package/dist/package-tool.js +51 -0
  147. package/dist/package.js +73 -0
  148. package/dist/plan.js +73 -0
  149. package/dist/read.js +144 -0
  150. package/dist/redact.js +28 -0
  151. package/dist/rule-support.js +587 -0
  152. package/dist/runtime-support.js +134 -0
  153. package/dist/server.js +92 -0
  154. package/dist/session-manager.js +30 -0
  155. package/dist/session.js +149 -0
  156. package/dist/skills.js +112 -0
  157. package/dist/support.js +98 -0
  158. package/dist/tool-types.js +127 -0
  159. package/dist/tools.js +59 -0
  160. package/dist/usage-log.js +197 -0
  161. package/dist/wire.js +287 -0
  162. package/dist/workflow-support.js +99 -0
  163. package/dist/write-lease.js +26 -0
  164. package/dist/write-lock.js +109 -0
  165. package/dist/write.js +176 -0
  166. package/dist/zip.js +156 -0
  167. package/docs/tool-surface-map.md +42 -0
  168. package/package.json +71 -6
  169. package/skills/omc-acceptance-criteria.md +47 -0
  170. package/skills/omc-business-configuration.md +257 -0
  171. package/skills/omc-capabilities.md +197 -0
  172. package/skills/omc-capability-scouting.md +55 -0
  173. package/skills/omc-config-draft-review.md +171 -0
  174. package/skills/omc-five-piece-flow.md +35 -0
  175. package/skills/omc-glossary.md +86 -0
  176. package/skills/omc-refusals.md +110 -0
  177. package/skills/omc-requirement-analysis.md +161 -0
  178. package/skills/omc-requirement-vocabulary.md +51 -0
  179. package/skills/omc-start-here.md +82 -0
  180. package/skills/omc-tool-selection.md +119 -0
  181. package/skills/omc-write-hazards.md +87 -0
@@ -0,0 +1,1092 @@
1
+ const commonRecovery = Object.freeze([
2
+ Object.freeze({ code: "tool-input-invalid", action: "修正 recipe.args 后重新 dry-run;inputSchema 是参数权威" }),
3
+ Object.freeze({ code: "write-lock-held", action: "等待当前写锁释放后重试;不要并行重放写入" }),
4
+ ]);
5
+ const environment = "<environment>";
6
+ const model = "omc_recipe_model";
7
+ const dictionarySelection = Object.freeze({
8
+ id: "dictionary-backed-single-select",
9
+ title: "先读字典,再创建字典-backed 单选字段",
10
+ status: "requires-prior-readback",
11
+ tool: "field.create",
12
+ args: {
13
+ environment,
14
+ schemaCode: model,
15
+ code: "status",
16
+ name: "状态",
17
+ propertyType: "DROPDOWN_BOX",
18
+ options: {
19
+ optionsSet: {
20
+ optionsType: "dictionary",
21
+ dictionary: {
22
+ checkedDictionary: "<dictionary.id>",
23
+ checkedDictionaryCode: "<dictionary.code>",
24
+ useDictionariesData: [{ code: "<record.code>", name: "<record.name>", id: "<record.id>" }],
25
+ },
26
+ },
27
+ },
28
+ },
29
+ preconditions: ["目标模型已存在;字段 code 尚未存在;字典及其至少一个启用记录可由只读发现取得"],
30
+ conditionalRequirements: ["若字典树有 parentId,dictionary.records 必须带 parentId;否则用 dictionary.records 的 id 或 code"],
31
+ priorToolReferences: [
32
+ { tool: "dictionary.names", args: { environment }, purpose: "选择环境中的 dictionary id/name/code;checkedDictionary 必须填 **id**(前端 a-select 的 option value 就是 a.id;填 name 运行时 getEnableRecordsByDictionaryId 会返回『字典已被禁用,无数据』,下拉空白)" },
33
+ { tool: "dictionary.records", args: { environment, code: "<dictionary.code>", enabledOnly: true }, purpose: "取得 useDictionariesData;替换 recipe 中的环境 ID" },
34
+ ],
35
+ dryRun: { supported: true, command: `omc call field.create --env ${environment} --dry-run --args '<recipe.args>'`, notes: "dry-run 只做 schema/语义/治理预检,不创建字段" },
36
+ expectedReadback: { performed: { dryRun: true }, readback: { draftFieldPresent: false, next: "field.publish" } },
37
+ refusalRecovery: [
38
+ { code: "field-family-options-invalid", action: "确保 options.optionsSet.dictionary 的 checkedDictionary、checkedDictionaryCode 和 useDictionariesData 来自同一字典" },
39
+ { code: "duplicate-field", action: "先 field.list 读取草稿/发布面;已有字段改用 field.update" },
40
+ ...commonRecovery,
41
+ ],
42
+ requiresEnvironmentDerivedIds: true,
43
+ prerequisiteData: { source: "dictionary.names → dictionary.records(enabledOnly:true)", fields: ["dictionary.code", "record.code", "record.id"] },
44
+ });
45
+ const childTable = Object.freeze({
46
+ id: "minimal-child-table",
47
+ title: "创建带两个标量列的子表字段",
48
+ status: "offline-valid",
49
+ tool: "field.create",
50
+ args: {
51
+ environment,
52
+ schemaCode: model,
53
+ code: "<model>_items",
54
+ name: "明细",
55
+ propertyType: "CHILD_TABLE",
56
+ subFields: [
57
+ { code: "item_name", name: "名称", propertyType: "SHORT_TEXT" },
58
+ { code: "item_qty", name: "数量", propertyType: "NUMERICAL", options: { scale: 1 } },
59
+ ],
60
+ },
61
+ preconditions: ["目标模型已存在;父字段 code 和子列 codes 尚未存在;父模型字段发布后再配置表单"],
62
+ conditionalRequirements: [
63
+ "**子表 `code` 会成为全局子 schema code——必须模型作用域唯一(如 `<model>_items`),不要用通用名如 `items`/`purchase_items`,否则撞名时平台只回 errcode 40000「调用数据库发生异常」(实测)**;",
64
+ "子表列不得是 CHILD_TABLE、OCR、ELECTRONIC_SIGN、RELEVANCE_DATA 或 FORMULA;关系列必须另带 relativeCode 与 options;子列 NUMERICAL 同样必须带 options.scale",
65
+ ],
66
+ priorToolReferences: [],
67
+ dryRun: { supported: true, command: `omc call field.create --env ${environment} --dry-run --args '<recipe.args>'`, notes: "dry-run 验证 subFields 并跳过环境写入" },
68
+ expectedReadback: { readback: { field: { code: "items", propertyType: "CHILD_TABLE", subSchema: { properties: ["item_name", "item_qty"] } } }, next: ["field.publish", "form.draft", "form.publish"] },
69
+ refusalRecovery: [
70
+ { code: "capability-out-of-coverage", action: "补齐 subFields,并移除不支持的嵌套/高级子列族" },
71
+ { code: "child-table-update-unsupported", action: "子列用 schemaCode=items 的 field.create/update;不要向父字段 update 传 options/choices/subFields" },
72
+ ...commonRecovery,
73
+ ],
74
+ requiresEnvironmentDerivedIds: false,
75
+ });
76
+ const ruleGraph = Object.freeze({
77
+ id: "standard-business-rule-graph",
78
+ title: "保存并启用最小数据事件规则图",
79
+ status: "requires-prior-readback",
80
+ tool: "rule.save",
81
+ args: {
82
+ environment,
83
+ schemaCode: model,
84
+ ruleCode: "Create",
85
+ name: "创建时写入状态",
86
+ nodeGraph: {
87
+ nodes: [
88
+ { nodeCode: "Start", nodeType: "START" },
89
+ { nodeCode: "UPDATE_1", nodeType: "UPDATE", targetObjectCode: model, inputParam: "_input", filterCondition: { mainConditions: [[{ targetSchemaDataItem: "id", ruleConditionType: "EQ", ruleValueType: "DYNAMIC", currentSchemaDataItem: "id" }]], sheetConditions: [] }, dataActions: [{ targetDataItem: "status", ruleValueType: "FIXED", value: "待处理", ruleActionType: "EQUALS" }] },
90
+ { nodeCode: "Create", nodeName: "新增数据", nodeType: "SYSTEM_CREATE", isSystemNode: true },
91
+ { nodeCode: "End", nodeType: "END" },
92
+ ],
93
+ routes: [
94
+ { preNode: "Start", postNode: "UPDATE_1" },
95
+ { preNode: "UPDATE_1", postNode: "Create" },
96
+ { preNode: "Create", postNode: "End" },
97
+ ],
98
+ },
99
+ publish: true,
100
+ },
101
+ preconditions: ["模型和 target field 已发布;Create 是平台预置触发规则;目标字段可由规则动作写入"],
102
+ conditionalRequirements: ["BIZ_ACTION 的 outputMappings 必须在 SYSTEM_CREATE/SYSTEM_UPDATE 前被消费;自定义 ruleCode 不会被数据事件自动分发"],
103
+ priorToolReferences: [
104
+ { tool: "field.list", args: { environment, schemaCode: model, published: true }, purpose: "确认 status 已发布" },
105
+ ],
106
+ dryRun: { supported: true, command: `omc call rule.save --env ${environment} --dry-run --args '<recipe.args>'`, notes: "dry-run 执行 rule graph dry lint;不会保存/发布规则" },
107
+ expectedReadback: { performed: { published: true, enabled: false }, readback: { published: true, next: "rule.enable", inputParam: "_input", systemNodeOrder: "UPDATE_1 → SYSTEM_CREATE" } },
108
+ refusalRecovery: [
109
+ { code: "rule-graph-validation-failed", action: "按 validation 修正 nodeType、路线、_input 和系统节点顺序后重新 dry-run" },
110
+ { code: "rule-schema-unresolved", action: "先确认模型存在,不要让 list/save 为幻影 schema 补建预置规则" },
111
+ { code: "rule-graph-bizmethod-unresolved", action: "先读取真实 BizMethod 及参数面;不要绑定 registration shell" },
112
+ ...commonRecovery,
113
+ ],
114
+ requiresEnvironmentDerivedIds: false,
115
+ prerequisiteData: { source: "field.list(published:true)", note: "ruleCode=Create 为平台预置代码;自定义代码需 workflow/button 触发" },
116
+ });
117
+ const ruleBranchGraph = Object.freeze({
118
+ id: "business-rule-branch-conditional",
119
+ title: "金额分级:BRANCH 判断 + 两条 UPDATE 分支(真机验证)",
120
+ status: "requires-prior-readback",
121
+ tool: "rule.save",
122
+ args: {
123
+ environment,
124
+ schemaCode: model,
125
+ ruleCode: "Create",
126
+ name: "金额分级",
127
+ nodeGraph: {
128
+ nodes: [
129
+ { nodeCode: "Start", nodeType: "START" },
130
+ { nodeCode: "Create", nodeName: "新增数据", nodeType: "SYSTEM_CREATE", isSystemNode: true },
131
+ { nodeCode: "Branch_1", nodeType: "BRANCH", sourceInputMethod: "PRE_OUT", exitConditionGroups: [[{ currentSchemaDataItem: "amount", ruleDataConditionType: "GT", ruleValueType: "FIXED", value: 10000 }]] },
132
+ { nodeCode: "UPDATE_BIG", nodeType: "UPDATE", targetObjectCode: model, dataActions: [{ targetDataItem: "level", ruleValueType: "FIXED", value: "大额", ruleActionType: "EQUALS" }] },
133
+ { nodeCode: "UPDATE_NORMAL", nodeType: "UPDATE", targetObjectCode: model, dataActions: [{ targetDataItem: "level", ruleValueType: "FIXED", value: "普通", ruleActionType: "EQUALS" }] },
134
+ { nodeCode: "End", nodeType: "END" },
135
+ ],
136
+ routes: [
137
+ { preNode: "Start", postNode: "Create" },
138
+ { preNode: "Create", postNode: "Branch_1" },
139
+ { preNode: "Branch_1", postNode: "UPDATE_BIG" },
140
+ { preNode: "Branch_1", postNode: "UPDATE_NORMAL", routeCondition: false },
141
+ { preNode: "UPDATE_BIG", postNode: "End" },
142
+ { preNode: "UPDATE_NORMAL", postNode: "End" },
143
+ ],
144
+ },
145
+ publish: true,
146
+ },
147
+ preconditions: ["模型和 target field(level/amount)已发布;Create 是平台预置触发规则;目标字段可由规则动作写入"],
148
+ conditionalRequirements: [
149
+ "**系统节点顺序:BRANCH 必须排在 `SYSTEM_CREATE` 之后**——BRANCH 直接接在 START 后会运行时静默不生效、动作不回写(工具 fail closed)。注意这与 BIZ_ACTION 回写场景相反(那里系统节点要在动作之后)——本 recipe 是分支场景。**`SYSTEM_CREATE` 必须显式写**(工具对分支图的自动注入不可靠)。",
150
+ "**出口条件叶只 4 个键** `{currentSchemaDataItem(左值字段),ruleDataConditionType(操作符),ruleValueType,value}`;用 UI 键 `targetSchemaDataItem`+`ruleConditionType` 会 save+publish 过但运行时 50000(工具 fail closed `rule-graph-branch-condition-keys`)。",
151
+ "**下列键可省、工具自动补**(省 token;真机验证极简图 1170B vs 全显式 1690B,−31%):每个节点的 `nodeName`;BRANCH 的 `inputParam:\"_input\"`/`judgeMethod:\"DEFAULT\"`;自更新 UPDATE(`targetObjectCode`=本模型)的 `inputParam:\"_input\"`/`filterCondition`(身份匹配);非兜底路由的 `routeCondition:true`/`matchType`。仅 BRANCH 的 `sourceInputMethod` 与兜底 else 的 `routeCondition:false` 需显式。**跨模型写(UPDATE/CREATE/DELETE 的 `targetObjectCode`≠规则模型)运行时未验证:lint 给非阻断 warning `rule-graph-cross-model-unverified`(2026-09-27 真机 data.submit 探测只有同模型 UPDATE 生效),交付前必须实测;稳妥用同模型回写或 BIZ_ACTION/二开。**",
152
+ "预置规则的系统节点类型必须与 ruleCode 匹配(Create→SYSTEM_CREATE);放 SYSTEM_UPDATE 会 data.submit 50000(工具 fail closed)。",
153
+ ],
154
+ priorToolReferences: [
155
+ { tool: "field.list", args: { environment, schemaCode: model, published: true }, purpose: "确认 amount/level 已发布" },
156
+ ],
157
+ dryRun: { supported: true, command: `omc call rule.save --env ${environment} --dry-run --args '<recipe.args>'`, notes: "dry-run 执行 rule graph dry lint;不写不发布" },
158
+ expectedReadback: { performed: { published: true, enabled: false }, readback: { nodes: "Start,SYSTEM_CREATE,Branch_1,UPDATE_BIG,UPDATE_NORMAL,End", next: "rule.enable 后用 data.submit 两条(命中/不命中)验 level" } },
159
+ refusalRecovery: [
160
+ { code: "rule-graph-branch-before-system-node", action: "把 SYSTEM_CREATE 挪到 BRANCH 之前(本 recipe 顺序)" },
161
+ { code: "rule-graph-branch-condition-keys", action: "出口叶改用 currentSchemaDataItem + ruleDataConditionType(见 conditionalRequirements)" },
162
+ { code: "rule-graph-branch-input-param", action: "给 BRANCH 补 inputParam:\"_input\"" },
163
+ { code: "rule-graph-branch-judge-method", action: "给 BRANCH 补 judgeMethod:\"DEFAULT\"" },
164
+ { code: "rule-graph-preset-system-node-mismatch", action: "把 SYSTEM_* 改成与 ruleCode 匹配的类型(Create→SYSTEM_CREATE)" },
165
+ { code: "rule-graph-validation-failed", action: "按 validation 逐项修正后重新 dry-run" },
166
+ ...commonRecovery,
167
+ ],
168
+ requiresEnvironmentDerivedIds: false,
169
+ prerequisiteData: { source: "field.list(published:true)", note: "ruleCode=Create 为平台预置代码" },
170
+ });
171
+ const ruleTimingTask = Object.freeze({
172
+ id: "rule-timing-task",
173
+ title: "定时任务:每天/周期触发规则(TIMING_TASK,SCHEDULER 节点,配置能力)",
174
+ status: "offline-valid",
175
+ tool: "rule.save",
176
+ args: {
177
+ environment,
178
+ schemaCode: model,
179
+ ruleCode: "<customRuleCode e.g. daily_scan>",
180
+ name: "每日定时作业",
181
+ nodeGraph: {
182
+ bizRuleType: 2,
183
+ nodes: [
184
+ { nodeCode: "Start", nodeName: "开始", nodeType: "START" },
185
+ { nodeCode: "SCHEDULER", nodeName: "定时作业", nodeType: "SCHEDULER", isSystemNode: true, nodeCategory: "systemNode" },
186
+ { nodeCode: "UPDATE_1", nodeName: "执行动作", nodeType: "UPDATE", targetObjectCode: model, dataActions: [{ targetDataItem: "<field>", ruleValueType: "FIXED", value: "<值>", ruleActionType: "EQUALS" }] },
187
+ { nodeCode: "End", nodeName: "结束", nodeType: "END" },
188
+ ],
189
+ routes: [
190
+ { preNode: "Start", postNode: "SCHEDULER", routeCondition: true },
191
+ { preNode: "SCHEDULER", postNode: "UPDATE_1", routeCondition: true },
192
+ { preNode: "UPDATE_1", postNode: "End", routeCondition: true },
193
+ ],
194
+ schedulerSetting: {
195
+ triggerTimeType: "SELECTED_TIME",
196
+ cycleType: "DAY",
197
+ executeType: "CYCLE",
198
+ execCycle: 1,
199
+ hour: 1,
200
+ minute: 0,
201
+ second: 0,
202
+ startTime: "<yyyy-MM-dd HH:mm:ss,未来时刻>",
203
+ endType: "NEVER",
204
+ // 扫描本表:以本模型为触发数据源,filterConditionGroups 是扫描过滤条件;
205
+ // 输入数据 = 被过滤出的行,下游以 inputParam:"_input" 消费其字段。
206
+ dataSourceType: "GET_LIST",
207
+ filterConditionGroups: [[{ targetSchemaDataItem: "<field>", ruleConditionType: "EQ", ruleValueType: "FIXED", value: "<值>" }]],
208
+ latestVersion: true,
209
+ consolidation: false,
210
+ },
211
+ },
212
+ publish: true,
213
+ },
214
+ preconditions: [
215
+ "模型和动作目标字段已发布;本环境需 `cloudpivot.task.enabled=true`(否则平台不建调度任务)",
216
+ "**「每天定时执行」= 定时任务 TIMING_TASK,是平台原生配置能力,不是外部定时器、不是在线 JS、无需二开**",
217
+ ],
218
+ conditionalRequirements: [
219
+ "`bizRuleType:2` 写在 **`nodeGraph.bizRuleType`**(顶层参数被忽略)——平台只对 bizRuleType=2 的行建调度任务(BizRuleScheduling.java:202-205);nodeGraph 内未传时工具推断并 warning。",
220
+ "调度参数在 `nodeGraph.schedulerSetting`,**不在 SCHEDULER 节点上**(ScheduleNode 无时机字段);缺它则规则能存能启用但**永不触发**。`latestVersion` **必须 true**(false 走旧 builder 读 null startDate → publish 50000)。",
221
+ "**「定时扫描某表按条件执行」用 `schedulerSetting.dataSourceType:\"GET_LIST\"` + `filterConditionGroups`(扫描过滤条件,二维数组,与 filterCondition 同构)——本模型即触发数据源,被过滤出的行作输入,下游节点以 `inputParam:\"_input\"` 消费其字段**。不要用 `NONE`(无输入数据集:整图无输入,下游 MESSAGE 字段解析不到、设计器映射框选不出字段),也不要为此额外加 QUERY 节点。`BIZ_SERVICE`=以业务方法返回集合为源(配 methodMapping)。真机 2026-10-08 SCM 沙盒:agent 用 NONE+QUERY,站内通知节点字段选不出。**日期过滤叶用 `ruleValueType:\"today\"` + `currentSchemaDataItem:\"<offset>\"`**(\"0\"=今天、\"-7\"=7天前、\"30\"=30天后、可带单位 \"-1-week\";DateFieldFilter 按 Integer.parseInt(source) 解析,**缺 source 会抛 NumberFormatException → 扫描 0 行 → 规则「永不触发」**,真机 2026-10-08);\"yesterday\"/\"tomorrow\" 无需 source。",
222
+ "`startTime` 用未来时刻;`cycleType`∈NEVER/HOUR/DAY/WEEK/MONTH/YEAR/LAST_DAY_OF_MONTH/CUSTOM。**先分清两类语义**:①时刻型——『每天/每周/每月的某个时刻』(每天 09:00、每周三、每月 1 日 02:00)是平台**原生**(cycleType DAY/WEEK/MONTH/CUSTOM + 触发时刻,真机先例 r3-wms:每日 07:00/07:30、每月 1 日),**别误读成定不了任意时刻**;②间隔型——『每 N 小时/分钟/秒』**也是平台原生**(前台+源码双证 2026-10-09):`cycleType:CUSTOM` + `cycleValue:{unit:HOUR, value, minute, second}`,间隔秒数=value*3600+minute*60+second(设计器 Custom+Hour 单位下有 [0-59]分/[0-59]秒 下拉;JobModelBuilder buildExecuteRule L195-197)。『每 5 分钟』=value:0+minute:5;『每 30 秒』=value:0+minute:0+second:30;『每 2 小时 15 分』=value:2+minute:15。**旧结论『cycleType 最小 HOUR→分钟级无门』是误读**(MES 轮试了不存在的 cycleType=MINUTE 枚举值,漏了 CUSTOM+HOUR 的分/秒槽,源码+设计器复核推翻);扩展内 ScheduledThreadPoolExecutor 仅在需要请求上下文/非规则语义时用。`executeType`∈POLLING/CYCLE 是后端从上述形状推导的内部表示,写面不用直接给;`triggerTimeType`∈SELECTED_TIME/DATE_FIELD(后者须带 triggerDataItem)。保存后需 `rule.enable`。",
223
+ ],
224
+ priorToolReferences: [
225
+ { tool: "field.list", args: { environment, schemaCode: model, published: true }, purpose: "确认动作目标字段已发布" },
226
+ ],
227
+ dryRun: { supported: true, command: `omc call rule.save --env ${environment} --dry-run --args '<recipe.args>'`, notes: "dry-run 跑图校验(含 schedulerSetting 形状/枚举),不写不发布" },
228
+ expectedReadback: { performed: { published: true, enabled: false }, readback: { bizRuleType: 2, nodes: "Start,SCHEDULER,UPDATE_1,End", next: "rule.enable;平台到 startTime 建 cron 并触发" } },
229
+ refusalRecovery: [
230
+ { code: "rule-graph-scheduler-setting-shape", action: "`nodeGraph.schedulerSetting` 必须是对象/JSON 串(不是写在 SCHEDULER 节点上)" },
231
+ { code: "rule-graph-scheduler-setting-enum", action: "按 8.6.26 枚举修正 schedulerSetting 字段值(见 conditionalRequirements)" },
232
+ { code: "rule-graph-bizruletype-inferred-timing-task", action: "已自动推断 bizRuleType=2;如非定时目的请去掉 SCHEDULER 节点" },
233
+ { code: "rule-graph-validation-failed", action: "按 validation 逐项修正后重新 dry-run" },
234
+ ...commonRecovery,
235
+ ],
236
+ requiresEnvironmentDerivedIds: false,
237
+ });
238
+ const ruleCrossModelCreate = Object.freeze({
239
+ id: "rule-cross-model-create",
240
+ title: "跨模型新增:查/取源后向另一模型 CREATE(含 QUERY 扇出,真机验证)",
241
+ status: "offline-valid",
242
+ tool: "rule.save",
243
+ args: {
244
+ environment,
245
+ schemaCode: model,
246
+ ruleCode: "<Create|Update|或 TIMING ruleCode>",
247
+ name: "跨模型生成",
248
+ nodeGraph: {
249
+ nodes: [
250
+ { nodeCode: "Start", nodeName: "开始", nodeType: "START" },
251
+ { nodeCode: "SYSTEM_CREATE_OR_UPDATE", nodeName: "触发", nodeType: "<SYSTEM_CREATE 或 SYSTEM_UPDATE>", isSystemNode: true },
252
+ { nodeCode: "C1", nodeName: "新增到目标", nodeType: "CREATE", targetObjectCode: "<targetModel 且 ≠ 承载本规则的模型>", inputParam: "_input", originatorValueType: "DYNAMIC", originatorValue: "<触发字段>", dataActions: [{ targetDataItem: "<目标字段>", ruleValueType: "DYNAMIC", value: "", ruleActionType: "EQUALS", currentDataItemValue: "<来源字段>" }] },
253
+ { nodeCode: "End", nodeName: "结束", nodeType: "END" },
254
+ ],
255
+ routes: [
256
+ { preNode: "Start", postNode: "SYSTEM_CREATE_OR_UPDATE", routeCondition: true },
257
+ { preNode: "SYSTEM_CREATE_OR_UPDATE", postNode: "C1", routeCondition: true },
258
+ { preNode: "C1", postNode: "End", routeCondition: true },
259
+ ],
260
+ },
261
+ publish: true,
262
+ },
263
+ preconditions: [
264
+ "目标模型与目标字段已存在并发布;触发字段存在",
265
+ "**`targetObjectCode` 不能等于承载该 Create 触发规则的同一模型**(否则自触发死循环 → 运行时 550009「业务规则执行异常」;平台有时 publish 直接 550018)",
266
+ ],
267
+ conditionalRequirements: [
268
+ "`originatorValueType` **必填**(DYNAMIC=触发字段 code / FIXED=完整用户对象 JSON 串)+ `originatorValue`;`dataActions` **必填**(目标字段映射);缺任一 → rule.save 真机 50000。",
269
+ "**`dataActions` 要把下游记录所需的业务字段逐条映射**(编号/日期/金额/状态/备注…),**别只挂一个关联 id**——否则下游记录其余字段全 null(真机 2026-10-08)。",
270
+ "**`originatorValueType:\"DYNAMIC\"` 的 `originatorValue` 必须指向人员字段**(STAFF_SELECTOR 等);指向文本/编号字段会致发起人错误或为空。",
271
+ "「当前时间」不是规则的 value 类型:`ruleValueType:\"SYSTEM\"` 不会填 now(真机 2026-10-08 落 null)。要让下游日期=生成时刻,把该**日期字段的默认值**设为「当前时间」(平台 `{value:\"current\"}`),或走二开;纯规则映射填不出 now。",
272
+ "上游是 QUERY 时 `inputParam=\"_<QUERY节点code>_output\"`(QUERY 查 N 行→CREATE 逐条生成即 N→N 扇出,真机验证支持,同/跨模型均可);否则 `\"_input\"`。",
273
+ "`QUERY` 节点只能查**主模型行**:查子表 schema(`parent.child` 或子 schema 名)空条件返回 0 行、带条件 550018(真机 2026-10-08)——逐子表明细扇出才需要二开。",
274
+ "跨模型 CREATE/UPDATE 真机验证支持(2026-09-28 / 2026-10-08);承载规则的触发时机要对(Create 在提交时触发、Update 只在 data.update 触发、TIMING 定时触发)。",
275
+ ],
276
+ priorToolReferences: [
277
+ { tool: "field.list", args: { environment, schemaCode: "<targetModel>", published: true }, purpose: "确认目标模型/字段已发布" },
278
+ ],
279
+ dryRun: { supported: true, command: `omc call rule.save --env ${environment} --dry-run --args '<recipe.args>'`, notes: "dry-run 跑图 lint(不会暴露跨模型 CREATE 的 50000,需真机 save 复核)" },
280
+ expectedReadback: { readback: { published: true, next: "rule.enable;构造数据触发后回读目标模型行数" } },
281
+ refusalRecovery: [
282
+ { code: "wire-business", action: "rule.save 真机 50000:多为 CREATE 缺 originatorValueType/dataActions,或 QUERY 查了子表 schema;按 conditionalRequirements 修正" },
283
+ { code: "rule-graph-self-trigger-create-loop", action: "CREATE 目标不能是承载本 Create 规则的同一模型;改为写别的模型或换触发载体" },
284
+ { code: "rule-graph-validation-failed", action: "按 validation 修正节点/路线/inputParam 后重新 dry-run" },
285
+ ...commonRecovery,
286
+ ],
287
+ requiresEnvironmentDerivedIds: false,
288
+ });
289
+ const workflowTemplateUpdate = Object.freeze({
290
+ id: "workflow-approval-template-update",
291
+ title: "改流程草稿模板(开始→审批→结束,可多级)并发布",
292
+ status: "requires-prior-readback",
293
+ tool: "workflow.update",
294
+ args: {
295
+ environment,
296
+ schemaCode: model,
297
+ code: "apply_workflow",
298
+ template: {
299
+ workflowCode: "apply_workflow",
300
+ activities: [
301
+ { activityCode: "Activity1", activityType: "START", activityName: "开始", i18nMessage: "开始" },
302
+ { activityCode: "Activity2", activityType: "PARTICIPANT", activityName: "填写申请单", i18nMessage: "填写申请单", participant: "{Originator}" },
303
+ { activityCode: "Activity3", activityType: "PARTICIPANT", activityName: "部门负责人审批", i18nMessage: "部门负责人审批", participant: "FindUserByRole({Originator.OU},\"<部门负责人角色code>\",\"random\")" },
304
+ { activityCode: "Activity4", activityType: "PARTICIPANT", activityName: "财务负责人审批", i18nMessage: "财务负责人审批", participant: "FindUserByRole({Originator.OU},\"<财务负责人角色code>\",\"random\")" },
305
+ { activityCode: "Activity5", activityType: "END", activityName: "结束", i18nMessage: "结束" },
306
+ ],
307
+ },
308
+ },
309
+ preconditions: ["workflow.create 已完成;先用 workflow.get_draft 取草稿基线——**默认(紧凑)面已含 activities 的 activityCode/type/name 与 edges,够改流程,不要 --full(30–75KB)**"],
310
+ conditionalRequirements: [
311
+ "**activities 用数组**(也接受 {0:…,1:…} 索引对象);每个活动必须带 activityCode(Activity1..N)与 activityType,缺了会被拒——以 get_draft 的值为基线改,不要凭记忆拼;",
312
+ "**一个模型只能有一个流程**:workflow.create 对已有流程的模型会拒(一个模型只能有一个流程);要改现有流程直接 workflow.update,不要另建新 code;",
313
+ "多级审批:为每个审批节点各写一个 PARTICIPANT 活动(activityCode 唯一),END 放最后;数组里按顺序就是审批链顺序;新增的审批节点由工具自动接入链路(补全到 END 的连线),无需自己写 routes;",
314
+ "**participant 必须是平台可解析的三种形态之一(真实生产 2026-09-23 观察)**:(a) 关键字 `{Originator}`;(b) 参与者对象数组 JSON `[{\"code\":\"<角色code>\",\"corpId\":\"<corpId|other>\",\"id\":\"<roleId>\",\"name\":\"<角色名>\",\"type\":2}]`(type 2=角色,3=用户;多个用 `+` 连接);(c) 函数 `FindUserByRole({Originator.OU},\"<roleCode>\",\"random\")`。**自由文本角色名(如 `部门负责人`)能 publish 但解析不到人、不生成待办**——先用 `org.role.list` / `org.department.list` 拿 code/id 再构造。工具会在 readback 里以 `participantWarnings` 标出自由文本。",
315
+ "**审批节点必须带 sheetCode(=模型默认表单槽位,通常等于 schemaCode)——S29 契约:participant sheetCode 为空 publish 会 401026 WORKFLOW_ACTIVITY_NO_SHEET。工具已自动回填,正常无需手填**;",
316
+ "节点类型 START / PARTICIPANT / END;省略的既有节点由 merge 保留;",
317
+ "**平台 workflow.create 会自动生成含「填写申请单」发起节点的草稿(START → 填写申请单 → 审批 → END);workflow.update 是 merge,不删除既有节点——要严格三节点需在设计器删该节点,或如实接受该发起节点**",
318
+ "**读回 participant 为空是平台模板更新面的已知残留,不要反复 update 试图修正(实测 6 次 update 仍为空);记录该差异并留待设计器核对,一次读回确认即可**",
319
+ ],
320
+ priorToolReferences: [{ tool: "workflow.get_draft", args: { environment, workflowCode: "apply_workflow" }, purpose: "取草稿基线作为 merge 基线(默认面即含 activityCode/name/edges;不要 --full)" }],
321
+ dryRun: { supported: true, command: `omc call workflow.update --env ${environment} --dry-run --args '<recipe.args>'`, notes: "dry-run 校验模板形状,不写入" },
322
+ expectedReadback: { readback: { draftUpdated: true, activities: "<节点数=你数组长度>", edges: "<链路>" }, next: ["用默认 workflow.get_draft 复核 activities/edges(不要 --full)", "workflow.publish", "workflow.bindRule"] },
323
+ refusalRecovery: [
324
+ { code: "tool-input-invalid", action: "每个 activity 补 activityCode(从 get_draft 抄);templateType 省略" },
325
+ { code: "workflow-publish-invalid", action: "publish 被 ValidateMessage[] 拒(401026/401027/401017)时,多为审批节点 sheetCode 为空或新增节点未接入链路——工具已自动回填 sheetCode 并补全链,改完模板再 update 一次;不要反复 publish" },
326
+ ...commonRecovery,
327
+ ],
328
+ requiresEnvironmentDerivedIds: true,
329
+ prerequisiteData: { source: "workflow.get_draft", fields: ["activities[].activityCode"] },
330
+ });
331
+ const workflowConditionalBranch = Object.freeze({
332
+ id: "workflow-conditional-branch",
333
+ title: "按字段条件分流(urgent 走主管审批,否则直接结束)",
334
+ status: "requires-prior-readback",
335
+ tool: "workflow.update",
336
+ args: {
337
+ environment,
338
+ schemaCode: model,
339
+ code: "apply_workflow",
340
+ template: {
341
+ workflowCode: "apply_workflow",
342
+ activities: [
343
+ { activityCode: "A1", activityType: "START", activityName: "开始", i18nMessage: "开始" },
344
+ { activityCode: "A2", activityType: "PARTICIPANT", activityName: "填写申请单", i18nMessage: "填写申请单", participant: "{Originator}" },
345
+ { activityCode: "A3", activityType: "PARTICIPANT", activityName: "主管审批", i18nMessage: "主管审批", participant: "FindUserByRole({Originator.OU},\"SYS_1000000\",\"random\")" },
346
+ { activityCode: "A4", activityType: "END", activityName: "结束", i18nMessage: "结束" },
347
+ ],
348
+ routes: [
349
+ { preNode: "A1", postNode: "A2" },
350
+ { preNode: "A2", postNode: "A3", ruleConditionGroup: [[{ currentDataItem: "r16_level", bizPropertyType: 12, conditionType: "EQ", value: "urgent" }]] },
351
+ { preNode: "A2", postNode: "A4", utilizeElse: true },
352
+ { preNode: "A3", postNode: "A4" },
353
+ ],
354
+ },
355
+ },
356
+ preconditions: ["workflow.create 已完成;路由的活动 code(preNode/postNode)必须命中 activities 里的 activityCode;先用 workflow.get_draft 取基线"],
357
+ conditionalRequirements: [
358
+ "**条件连线 = routes[] 里的 ruleConditionGroup**(外层 OR、内层 AND);叶只能有 4 个键 `{currentDataItem, bizPropertyType, conditionType, value}`——**多一个键(如照抄业务规则的 ruleValueType/targetDataItem)会让平台回 workflow/update 误导性的 `401001 流程不存在!`,工具已写前拒绝**。",
359
+ "`currentDataItem` = 当前模型字段 code(子表列用 `childSchemaCode.fieldCode`);`bizPropertyType` **用数字索引**(SHORT_TEXT=0 / NUMERICAL=2 / DATE=3 / LOGICAL=4 / RADIO=12 / DROPDOWN_BOX=14 / STAFF_SELECTOR=50…;工具也接受枚举名并转索引);`conditionType` ∈ EQ/NEQ/GT/LT/GTEQ/LTEQ/CT/NCT/EP/NEP/BETWEEN/...;`value` 为字面量。",
360
+ "**else/兜底连线用 `utilizeElse:true`**(其余条件不满足时走);工具会自动把条件连线置 `popupType:EXPRESSION`(否则平台静默跳过)。",
361
+ "分支可只写 activities + routes:工具按数组顺序补全到 END 的连线(`con` 兜底线仍需你显式给,除非它就是主链)。",
362
+ ],
363
+ priorToolReferences: [
364
+ { tool: "workflow.get_draft", args: { environment, workflowCode: "apply_workflow" }, purpose: "取草稿基线(apply_workflow 换成真实 workflowCode;默认面即含 activityCode/name/edges,不要 --full)" },
365
+ { tool: "org.role.list", args: { environment }, purpose: "取真实参与人/角色 code,避免自由文本名解析不到人" },
366
+ ],
367
+ dryRun: { supported: true, command: `omc call workflow.update --env ${environment} --dry-run --args '<recipe.args>'`, notes: "dry-run 校验 activities/routes/条件叶,不写入" },
368
+ expectedReadback: { readback: { draftUpdated: true, rules: [{ postActivityCode: "A3", ruleConditionGroup: [[{ currentDataItem: "r16_level", bizPropertyType: 12, conditionType: "EQ", value: "urgent" }]], popupType: "EXPRESSION" }, { postActivityCode: "A4", utilizeElse: true }] }, next: ["workflow.publish", "data.submit 两条(命中/不命中)验 workitem.list"] },
369
+ refusalRecovery: [
370
+ { code: "workflow-route-condition-invalid", action: "条件叶只留 currentDataItem/bizPropertyType/conditionType/value;补 currentDataItem 与 conditionType;bizPropertyType 用数字索引或已知枚举名" },
371
+ { code: "workflow-not-found", action: "先 workflow.create,再 workflow.get_draft 取基线——不要把 workflow 模板 id 当 workflowCode" },
372
+ { code: "workflow-route-not-found", action: "preNode/postNode 必须是 activities 里真实存在的 activityCode(从 get_draft 抄)" },
373
+ ...commonRecovery,
374
+ ],
375
+ requiresEnvironmentDerivedIds: true,
376
+ prerequisiteData: { source: "workflow.get_draft", fields: ["activities[].activityCode", "org.role code"] },
377
+ });
378
+ const workflowBinding = Object.freeze({
379
+ id: "workflow-handler-binding",
380
+ title: "将规则合并绑定到 workflow handler 并发布",
381
+ status: "requires-prior-readback",
382
+ tool: "workflow.bindRule",
383
+ args: { environment, schemaCode: model, workflowCode: "<workflow.code>", ruleCode: "Create", handler: "end", publish: true },
384
+ preconditions: ["模型只有一个目标 workflow;workflow.create 已完成且 draft 存在;规则已保存"],
385
+ conditionalRequirements: ["handler 只能是 start、cancel、activate、end;workflow.bindRule 是 merge,不会删除既有 bizActions"],
386
+ priorToolReferences: [
387
+ { tool: "workflow.get_draft", args: { environment, workflowCode: "<workflow.code>" }, purpose: "取得完整 draft,并确认目标 handler 字段" },
388
+ { tool: "rule.list", args: { environment, schemaCode: model }, purpose: "确认 Create 规则存在" },
389
+ ],
390
+ dryRun: { supported: true, command: `omc call workflow.bindRule --env ${environment} --dry-run --args '<recipe.args>'`, notes: "dry-run 验证 handler/code 互斥和写治理,不更新 workflow" },
391
+ expectedReadback: { readback: { handler: "end", handlerField: "endEventHandler", bound: true, published: true, publishedBound: true, semantics: "merge" } },
392
+ refusalRecovery: [
393
+ { code: "workflow-not-found", action: "先 workflow.create,再用 workflow.get_draft 取得完整模板" },
394
+ { code: "workflow-bindrule-codes-invalid", action: "只传 ruleCode 或只传 ruleCodes;不要同时传两者" },
395
+ { code: "workflow-publish-not-applied", action: "先 workflow.get_draft/validate 修复完整模板,再重新 bindRule;以 published-face marker 为准" },
396
+ ...commonRecovery,
397
+ ],
398
+ requiresEnvironmentDerivedIds: true,
399
+ prerequisiteData: { source: "workflow.get_draft + rule.list", fields: ["workflow.code", "rule.code"] },
400
+ });
401
+ const workflowSave = Object.freeze({
402
+ id: "workflow-backed-data-save",
403
+ title: "用 data.save 创建 DRAFT workflow row",
404
+ status: "requires-prior-readback",
405
+ tool: "data.save",
406
+ args: { environment, schemaCode: model, workflowCode: "<workflow.code>", formData: { name: "recipe draft" } },
407
+ preconditions: ["模型默认表单/字段和 workflow 已发布;应用可用;formData 不带已存在 id"],
408
+ conditionalRequirements: ["workflow draft-save 总是 INSERT;不要把已存在记录 id 放进 formData"],
409
+ priorToolReferences: [
410
+ { tool: "workflow.get_published", args: { environment, workflowCode: "<workflow.code>" }, purpose: "确认 published-face marker" },
411
+ ],
412
+ dryRun: { supported: true, command: `omc call data.save --env ${environment} --dry-run --args '<recipe.args>'`, notes: "save dry-run 只校验输入与治理;真实两段式身份只能由执行后的 readback 提供" },
413
+ expectedReadback: { performed: { formType: "save-draft-workflow", captures: ["bizObjectId", "workflowInstanceId", "workItemId"] }, readback: { sequenceStatus: "DRAFT", next: "data.submit" } },
414
+ refusalRecovery: [
415
+ { code: "save-workflow-draft-with-id-unsupported", action: "移除已存在 id;workflow draft-save 只支持 INSERT" },
416
+ { code: "app-not-published", action: "只读确认应用发布状态后修复发布边界;不要盲目重试 runtime 写入" },
417
+ ...commonRecovery,
418
+ ],
419
+ requiresEnvironmentDerivedIds: true,
420
+ prerequisiteData: { source: "workflow.get_published; data.save authoritative readback", fields: ["workflow.code", "bizObjectId", "workflowInstanceId", "workItemId"] },
421
+ });
422
+ const workflowSubmit = Object.freeze({
423
+ id: "workflow-backed-data-submit",
424
+ title: "消费 data.save 读回身份并提交 workflow work item",
425
+ status: "requires-prior-readback",
426
+ tool: "data.submit",
427
+ args: { environment, schemaCode: model, workflowCode: "<workflow.code>", bizObjectId: "<data.save.performed.bizObjectId>", workflowInstanceId: "<data.save.performed.workflowInstanceId>", workItemId: "<data.save.performed.workItemId>", formData: { name: "recipe draft" } },
428
+ preconditions: ["同一 recipe 的 data.save 已成功并读回 DRAFT;workflowInstanceId/workItemId/bizObjectId 均非空"],
429
+ conditionalRequirements: ["workItemId 或 workflowInstanceId 出现时必须传 bizObjectId;身份只能复制 data.save 的 authoritative readback"],
430
+ priorToolReferences: [
431
+ { tool: "data.save", args: { environment, schemaCode: model, workflowCode: "<workflow.code>", formData: { name: "recipe draft" } }, purpose: "提供三个环境生成的提交身份" },
432
+ { tool: "workitem.list", args: { environment }, purpose: "当 data.save 未直接返回 workItemId 时按 workflowInstanceId 解析待办" },
433
+ ],
434
+ dryRun: { supported: true, command: `omc call data.submit --env ${environment} --dry-run --args '<recipe.args>'`, notes: "dry-run 验证两段式参数关系,不推进 workflow" },
435
+ expectedReadback: { performed: { formType: "1" }, readback: { sequenceStatus: "COMPLETED", workflowInstanceId: "<same-as-data.save>", workItemId: "<same-as-data.save>" } },
436
+ refusalRecovery: [
437
+ { code: "submit-biz-object-id-required", action: "把 data.save 的 bizObjectId 原样传给 data.submit" },
438
+ { code: "submit-existing-workflow-unsupported", action: "不要用旧行 + workflowCode 启动;先 data.save 新建 DRAFT,再用返回 workItemId 提交" },
439
+ { code: "submit-target-absent", action: "用 data.load 确认 bizObjectId 属于 schemaCode 后停止盲重试" },
440
+ ...commonRecovery,
441
+ ],
442
+ requiresEnvironmentDerivedIds: true,
443
+ prerequisiteData: { source: "data.save authoritative readback; workitem.list fallback", fields: ["bizObjectId", "workflowInstanceId", "workItemId"] },
444
+ });
445
+ const businessModelDropdown = Object.freeze({
446
+ id: "business-model-dropdown",
447
+ title: "用另一个模型的数据作下拉来源(businessModel)",
448
+ status: "requires-prior-readback",
449
+ tool: "field.create",
450
+ args: {
451
+ environment,
452
+ schemaCode: model,
453
+ code: "category",
454
+ name: "类别",
455
+ propertyType: "DROPDOWN_BOX",
456
+ options: {
457
+ optionsSet: {
458
+ optionsType: "businessModel",
459
+ businessModel: {
460
+ schemaCode: "<source.model>",
461
+ sheetDataItem: "<source.businessField>",
462
+ appCode: "<app.code>",
463
+ },
464
+ },
465
+ },
466
+ },
467
+ preconditions: [
468
+ "目标模型与来源模型都已存在",
469
+ "来源模型的显示字段已 field.publish 并已 form.publish(未发布字段不可作为下拉来源)",
470
+ ],
471
+ conditionalRequirements: [
472
+ "sheetDataItem 指向来源模型上真正承载业务值的字段;**不要用系统字段 name** —— 它是平台自动生成的数据标题(人名+模型名+时间),下拉项会显示这串标题而不是业务值",
473
+ "orderByFields/orderType 可选,用于下拉项排序;conditions 可选,用于过滤来源行",
474
+ ],
475
+ priorToolReferences: [
476
+ { tool: "discover_environment", args: { environment, full: true }, purpose: "取得来源模型 schemaCode 与 app.code" },
477
+ { tool: "field.list", args: { environment, schemaCode: "<source.model>", published: true, fields: ["<source.businessField>"] }, purpose: "确认显示字段已发布并取其 code" },
478
+ ],
479
+ dryRun: { supported: true, command: `omc call field.create --env ${environment} --dry-run --args '<recipe.args>'`, notes: "dry-run 校验 optionsSet.businessModel 的形状,不创建字段" },
480
+ expectedReadback: { readback: { field: { code: "category", propertyType: "DROPDOWN_BOX", options: { optionsSet: { optionsType: "businessModel" } } } }, next: ["field.publish", "form.draft", "form.publish"] },
481
+ refusalRecovery: [
482
+ { code: "field-family-options-invalid", action: "确保 optionsSet.businessModel 带 schemaCode 与 sheetDataItem,且 schemaCode 是已存在的模型" },
483
+ { code: "duplicate-field", action: "先 field.list 读取草稿/发布面;已有字段改用 field.update" },
484
+ ...commonRecovery,
485
+ ],
486
+ requiresEnvironmentDerivedIds: true,
487
+ prerequisiteData: { source: "discover_environment → field.list(published:true)", fields: ["source.model", "source.businessField", "app.code"] },
488
+ });
489
+ const configureModelRecipe = Object.freeze({
490
+ id: "model-with-fields-form",
491
+ title: "一次建模型 + 多字段 + 发布默认表单(高层编排)",
492
+ status: "offline-valid",
493
+ tool: "configure_model",
494
+ args: {
495
+ environment,
496
+ name: "采购申请",
497
+ code: "purchase_apply",
498
+ applicationCode: "<applicationCode>",
499
+ fields: [
500
+ { code: "apply_no", name: "申请编号", propertyType: "SHORT_TEXT" },
501
+ { code: "apply_amount", name: "申请金额", propertyType: "NUMERICAL", options: { scale: 2 } },
502
+ { code: "apply_note", name: "申请说明", propertyType: "LONG_TEXT" },
503
+ ],
504
+ publishForm: true,
505
+ },
506
+ preconditions: ["应用 code 已确认(discover_environment)"],
507
+ conditionalRequirements: [
508
+ "fields[].propertyType 用族名;NUMERICAL 必须 options.scale(scale = 小数位数:金额两位小数用 scale:2,整数用 scale:0)",
509
+ "子表/单选/人员等复杂族建议改用低层 field.create(configure_model 的 fields[] 只覆盖标量族最稳)",
510
+ ],
511
+ priorToolReferences: [{ tool: "discover_environment", args: { environment }, purpose: "取 applicationCode 与已有模型" }],
512
+ dryRun: { supported: true, command: `omc call configure_model --env ${environment} --dry-run --args '<recipe.args>'`, notes: "dry-run 走 schema/语义/治理预检,不创建" },
513
+ expectedReadback: { readback: { modelPresent: true, publishedFields: ["apply_no", "apply_amount", "apply_note"] }, next: ["field.update(表单层条件)", "listview.configure(默认视图已自动存在,直接配置,勿 create)"] },
514
+ refusalRecovery: [{ code: "capability-out-of-coverage", action: "标量族之外改用低层 field.create;NUMERICAL 补 options.scale" }, ...commonRecovery],
515
+ requiresEnvironmentDerivedIds: true,
516
+ prerequisiteData: { source: "discover_environment", fields: ["applicationCode"] },
517
+ });
518
+ const configureFormRecipe = Object.freeze({
519
+ id: "publish-default-form",
520
+ title: "把已发布字段绑进默认表单并发布(高层编排)",
521
+ status: "offline-valid",
522
+ tool: "configure_form",
523
+ args: { environment, schemaCode: model, publish: true },
524
+ preconditions: ["字段已 field.publish"],
525
+ conditionalRequirements: ["表单层条件必填/只读请用 field.update 的 options(form-conditional-required / form-always-readonly recipe),不是本编排器"],
526
+ priorToolReferences: [{ tool: "field.list", args: { environment, schemaCode: model, published: true }, purpose: "确认字段已发布" }],
527
+ dryRun: { supported: true, command: `omc call configure_form --env ${environment} --dry-run --args '<recipe.args>'`, notes: "dry-run 预检,不写" },
528
+ expectedReadback: { readback: { formPublished: true }, next: ["listview.configure(默认视图已存在,勿 listview.create)", "field.update(表单层条件)"] },
529
+ refusalRecovery: [{ code: "capability-out-of-coverage", action: "先发布字段再发表单" }, ...commonRecovery],
530
+ requiresEnvironmentDerivedIds: false,
531
+ });
532
+ const numericTwoDecimals = Object.freeze({
533
+ id: "numeric-two-decimals",
534
+ title: "数值字段(金额,两位小数 / 整数)— scale 必填",
535
+ status: "offline-valid",
536
+ tool: "field.create",
537
+ args: { environment, schemaCode: model, code: "apply_amount", name: "申请金额", propertyType: "NUMERICAL", options: { scale: 3 } },
538
+ preconditions: ["模型已存在"],
539
+ conditionalRequirements: ["NUMERICAL 必须显式 options.scale:scale = 小数位数(0=整数, 1=1位, 2=2位 … 8=8位);缺 scale 门户显示裸 decimal 且工具会拒绝。"],
540
+ priorToolReferences: [{ tool: "discover_environment", args: { environment }, purpose: "确认目标模型已存在" }],
541
+ dryRun: { supported: true, command: `omc call field.create --env ${environment} --dry-run --args '<recipe.args>'`, notes: "dry-run 校验 propertyType/scale/治理,不创建" },
542
+ expectedReadback: { readback: { draftFieldPresent: true }, next: ["field.publish"] },
543
+ refusalRecovery: [{ code: "capability-out-of-coverage", action: "NUMERICAL 必须带 options.scale;金额两位小数用 scale:2,整数用 scale:0" }, ...commonRecovery],
544
+ requiresEnvironmentDerivedIds: false,
545
+ });
546
+ const dataruleCalcRule = Object.freeze({
547
+ id: "field-compute-rule",
548
+ title: "字段计算规则:普通字段 + 计算规则(type2,勿用「计算公式」字段类型)",
549
+ status: "offline-valid",
550
+ tool: "datarule.create",
551
+ args: {
552
+ environment,
553
+ schemaCode: model,
554
+ propertyCode: "c",
555
+ dataRuleType: 2,
556
+ options: {
557
+ shortTextStitch: JSON.stringify({
558
+ formula: "{a}+{b}",
559
+ editorText: "<field-omc0001>a</field-omc0001>+<field-omc0001>b</field-omc0001>",
560
+ editorMark: "field-omc0001",
561
+ }),
562
+ },
563
+ name: "c=a+b",
564
+ },
565
+ preconditions: [
566
+ "**计算类需求:建普通业务字段(数值优先,文本亦可)+ 本计算规则;不要用「计算公式」字段类型(FORMULA)**——平台约定是普通字段顺带配计算规则(真机 2026-10-08 复核:普通数值字段 + type2 计算规则,门户算出 7.00,设计器「计算规则」弹窗显示公式)。",
567
+ "参与计算的字段已发布",
568
+ ],
569
+ conditionalRequirements: [
570
+ "`options.shortTextStitch` 的值必须是 JSON 对象字符串,**三个键都要给全**:`{\"formula\":\"{a}+{b}\",\"editorText\":\"<field-omc0001>a</field-omc0001>+<field-omc0001>b</field-omc0001>\",\"editorMark\":\"field-omc0001\"}`。",
571
+ "**`editorText`/`editorMark` 不是可选**(真机 2026-10-08):设计器「计算规则」弹窗按 `editorText` 里的 `<field-{editorMark}>…</field-{editorMark}>` 标记解析;`editorMark:\"\"` 或 `editorText` 不带标记 → 弹窗**标红/判非法**。约定:`editorMark` = `field-<任意一致 id>`(如 `field-omc0001`),`editorText` 里**每个字段引用**都包成 `<field-omc0001>ref</field-omc0001>`——**标签内只放裸 token,不要带花括号**(前端 `parseFormulaFromEditorText` 会自己给标签内容套上 `{}`:写 `<field-omc0001>{a}</field-omc0001>` 会被反解成 `{{a}}` ≠ `formula`,该规则显示「已失效」;真机 2026-10-08 SCM 沙盒已复现,OMC 现已 fail closed `datarule-stitch-editor-mismatch`)。",
572
+ "**`formula` 引用语法**(真机 production + 设计器核验):同类字段裸 `{字段code}`(`{a}+{b}`);**子表列用 `{子表字段code.子列code}` 点号全限定**(`{prop_repair_detail.qty}*{prop_repair_detail.unit_price}`);子表聚合 `SUM({子表.子列})`;支持 `IF/CASE/VALUE/AND/=、字符串字面量、运算符空格`。",
573
+ "**计算由门户前端执行、API 不回填**:写测试数据带预算值;正确性由门户表单填表确认。设计器「计算规则」弹窗读取 `bizdatarule/list`(本规则即写入该表)。",
574
+ ],
575
+ priorToolReferences: [
576
+ { tool: "field.list", args: { environment, schemaCode: model, published: true }, purpose: "确认目标字段与来源字段已发布、且目标字段是普通字段" },
577
+ ],
578
+ dryRun: { supported: true, command: `omc call datarule.create --env ${environment} --dry-run --args '<recipe.args>'`, notes: "dry-run 校验类型/公式协议,不写入" },
579
+ expectedReadback: { readback: { dataRuleType: 2, options: { shortTextStitch: "<含 formula 的 JSON 串>" } } },
580
+ refusalRecovery: [
581
+ { code: "datarule-options-invalid", action: "shortTextStitch 必须是含 formula 的 JSON 对象串;同类字段用裸 {字段},子表列用 {子表.子列}" },
582
+ { code: "wire-business", action: "305003 假死循环常见于未发布应用或坏 formula 形状;确认应用已发布、formula 为合法引用" },
583
+ ...commonRecovery,
584
+ ],
585
+ requiresEnvironmentDerivedIds: false,
586
+ });
587
+ const dataruleSubtableSum = Object.freeze({
588
+ id: "field-compute-subtable-sum",
589
+ title: "字段计算规则:子表列求和回主表(普通字段 + type2,SUM({子表.子列}))",
590
+ status: "offline-valid",
591
+ tool: "datarule.create",
592
+ args: {
593
+ environment,
594
+ schemaCode: model,
595
+ propertyCode: "total",
596
+ dataRuleType: 2,
597
+ options: {
598
+ shortTextStitch: JSON.stringify({
599
+ formula: "SUM({<childSchemaCode>.amount})",
600
+ editorText: "SUM(<field-omc0001><childSchemaCode>.amount</field-omc0001>)",
601
+ editorMark: "field-omc0001",
602
+ }),
603
+ },
604
+ name: "合计=明细金额之和",
605
+ },
606
+ preconditions: [
607
+ "**目标 `total` 是普通数值字段(不是「计算公式」字段)**;`<childSchemaCode>` 是子表字段的 code(如 `prop_repair_detail` / `Sub_item_information_table`),`amount` 是其子列 code",
608
+ "子表与其列已发布",
609
+ ],
610
+ conditionalRequirements: [
611
+ "子表列引用用 `{子表字段code.子列code}` 点号全限定;求和 `SUM({子表.子列})`(真机核验:`SUM({prop_repair_detail.amount})`)。",
612
+ "**给全 `editorText`/`editorMark`**:`editorMark=\"field-omc0001\"`,`editorText=\"SUM(<field-omc0001>prop_repair_detail.amount</field-omc0001>)\"`;缺则设计器标红。",
613
+ "计算由门户前端执行、API 不回填(写测试数据带预算值;门户填表确认)。",
614
+ ],
615
+ priorToolReferences: [
616
+ { tool: "field.list", args: { environment, schemaCode: model, published: true }, purpose: "确认主表 total、子表字段及其子列已发布" },
617
+ ],
618
+ dryRun: { supported: true, command: `omc call datarule.create --env ${environment} --dry-run --args '<recipe.args>'`, notes: "dry-run 校验类型/公式协议,不写入" },
619
+ expectedReadback: { readback: { dataRuleType: 2, options: { shortTextStitch: "<含 SUM({子表.子列}) 的 JSON 串>" } } },
620
+ refusalRecovery: [{ code: "datarule-options-invalid", action: "子表列引用必须是 {子表字段code.子列code};SUM 包裹其外" }, ...commonRecovery],
621
+ requiresEnvironmentDerivedIds: false,
622
+ });
623
+ const dataruleChildColumn = Object.freeze({
624
+ id: "field-compute-child-column",
625
+ title: "字段计算规则:子表列同行运算(普通字段 + type2,如 金额=数量×单价)",
626
+ status: "offline-valid",
627
+ tool: "datarule.create",
628
+ args: {
629
+ environment,
630
+ schemaCode: "<childSchemaCode e.g. <model>_items>",
631
+ propertyCode: "amount",
632
+ dataRuleType: 2,
633
+ options: {
634
+ shortTextStitch: JSON.stringify({
635
+ formula: "{<childSchemaCode>.qty}*{<childSchemaCode>.price}",
636
+ editorText: "<field-omc0001><childSchemaCode>.qty</field-omc0001>*<field-omc0001><childSchemaCode>.price</field-omc0001>",
637
+ editorMark: "field-omc0001",
638
+ }),
639
+ },
640
+ name: "金额=数量×单价",
641
+ },
642
+ preconditions: ["**子表列 `amount` 是普通数值字段(不是「计算公式」字段)**,且是子表内的列(schemaCode=子表字段 code)"],
643
+ conditionalRequirements: [
644
+ "**子表内列引用用 `{子表schemaCode.子列code}` 点号全限定**(真机 2026-10-08 设计器产出:`{prop_repair_detail.qty}*{prop_repair_detail.unit_price}`);不要写裸 `{qty}`。",
645
+ "**给全 `editorText`/`editorMark`**:`editorMark=\"field-omc0001\"`,每个引用包成 `<field-omc0001>子表.子列</field-omc0001>`(**标签内裸 token,不带花括号**——编辑器会自己套 `{}`);缺或带括号则设计器标红/已失效。",
646
+ "计算由门户前端执行、API 不回填(门户填表确认)。",
647
+ ],
648
+ priorToolReferences: [{ tool: "field.list", args: { environment, schemaCode: "<childSchemaCode>" }, purpose: "确认子表及其列已发布" }],
649
+ dryRun: { supported: true, command: `omc call datarule.create --env ${environment} --dry-run --args '<recipe.args>'`, notes: "dry-run 校验类型/公式协议,不写入" },
650
+ expectedReadback: { readback: { dataRuleType: 2, options: { shortTextStitch: "<含 {子表.子列} 点号引用的 JSON 串>" } } },
651
+ refusalRecovery: [{ code: "datarule-options-invalid", action: "子表内列用 {子表schemaCode.子列code} 点号;不要裸 {子列code}" }, ...commonRecovery],
652
+ requiresEnvironmentDerivedIds: false,
653
+ });
654
+ const dataruleRequired = Object.freeze({
655
+ id: "datarule-conditional-required",
656
+ title: "金额超过阈值时说明必填(字段级 type4)",
657
+ status: "offline-valid",
658
+ tool: "datarule.create",
659
+ args: {
660
+ environment,
661
+ schemaCode: model,
662
+ propertyCode: "leave_reason",
663
+ dataRuleType: 4,
664
+ options: { requiredFormula: '{leave_amount} 大于 {"valueType":"custom","value":5000}' },
665
+ name: "金额超过阈值说明必填",
666
+ sortKey: 1,
667
+ },
668
+ preconditions: ["目标字段已发布;amount 字段 code 已确认"],
669
+ conditionalRequirements: ["条件串必须是 {field} OP {JSON valueType} 三段单空格;多条件用 &&/||"],
670
+ priorToolReferences: [{ tool: "field.list", args: { environment, schemaCode: model, published: true }, purpose: "确认 leave_amount 与 leave_reason 已发布" }],
671
+ dryRun: { supported: true, command: `omc call datarule.create --env ${environment} --dry-run --args '<recipe.args>'`, notes: "dry-run 校验类型/条件协议,不写入" },
672
+ expectedReadback: { readback: { dataRuleType: 4, options: { requiredFormula: "<protocol>" } } },
673
+ refusalRecovery: [{ code: "datarule-options-invalid", action: "不要写 `{field}>5000` 或裸 true;复制 recipe 的三段协议" }, ...commonRecovery],
674
+ requiresEnvironmentDerivedIds: false,
675
+ });
676
+ const dataruleReadonly = Object.freeze({
677
+ id: "datarule-conditional-readonly",
678
+ title: "字段级条件只读(type5,恒真条件式)",
679
+ status: "offline-valid",
680
+ tool: "datarule.create",
681
+ args: {
682
+ environment,
683
+ schemaCode: model,
684
+ propertyCode: "approvalLevel",
685
+ dataRuleType: 5,
686
+ options: { readonlyCondition: '{approvalLevel} 大于等于 {"valueType":"custom","value":0}' },
687
+ name: "审批级别只读",
688
+ sortKey: 1,
689
+ },
690
+ preconditions: ["目标字段已发布"],
691
+ conditionalRequirements: ["无条件 readonly 的 functionType 形状尚未被 live 复现;使用已验证恒真条件式"],
692
+ priorToolReferences: [{ tool: "field.list", args: { environment, schemaCode: model, published: true }, purpose: "确认 approvalLevel 已发布" }],
693
+ dryRun: { supported: true, command: `omc call datarule.create --env ${environment} --dry-run --args '<recipe.args>'`, notes: "dry-run 校验条件协议,不写入" },
694
+ expectedReadback: { readback: { dataRuleType: 5, options: { readonlyCondition: "<protocol>" } } },
695
+ refusalRecovery: [{ code: "datarule-options-invalid", action: "readonlyCondition 必须是三段条件式;`readonlyCondition:true` 会破坏门户渲染" }, ...commonRecovery],
696
+ requiresEnvironmentDerivedIds: false,
697
+ });
698
+ const dataruleDisplay = Object.freeze({
699
+ id: "datarule-conditional-display",
700
+ title: "字段级显示条件(type3)",
701
+ status: "offline-valid",
702
+ tool: "datarule.create",
703
+ args: {
704
+ environment,
705
+ schemaCode: model,
706
+ propertyCode: "approvalLevel",
707
+ dataRuleType: 3,
708
+ options: { displayFormula: '{isUrgent} == {"valueType":"custom","value":true}' },
709
+ name: "加急时显示审批级别",
710
+ sortKey: 1,
711
+ },
712
+ preconditions: ["目标字段已发布"],
713
+ conditionalRequirements: ["displayFormula 与 requiredFormula/readonlyCondition 共用三段条件协议"],
714
+ priorToolReferences: [{ tool: "field.list", args: { environment, schemaCode: model, published: true }, purpose: "确认字段 codes" }],
715
+ dryRun: { supported: true, command: `omc call datarule.create --env ${environment} --dry-run --args '<recipe.args>'`, notes: "dry-run 校验条件协议,不写入" },
716
+ expectedReadback: { readback: { dataRuleType: 3, options: { displayFormula: "<protocol>" } } },
717
+ refusalRecovery: [{ code: "datarule-options-invalid", action: "复制三段条件协议;不要传裸公式字符串" }, ...commonRecovery],
718
+ requiresEnvironmentDerivedIds: false,
719
+ });
720
+ // --- Form-interaction conditions (FIELD options) --------------------------
721
+ // Reproduced live 2026-09-22 (Playwright, dogfood `local`): a datarule row did
722
+ // NOT block the portal form; setting these field options did (red required
723
+ // marker + "Please input ..." + submit blocked + 0 rows). The condition string
724
+ // uses the SERIALIZED Chinese operators (等于/大于/不为空/…); symbols also work.
725
+ const formConditionalRequired = Object.freeze({
726
+ id: "form-conditional-required",
727
+ title: "表单层条件必填:金额大于阈值时说明必填(字段 options)",
728
+ status: "requires-prior-readback",
729
+ tool: "field.update",
730
+ args: {
731
+ environment,
732
+ schemaCode: model,
733
+ code: "leave_reason",
734
+ name: "请假事由",
735
+ options: { requiredFormula: '{leave_amount} 大于 {"valueType":"custom","value":5000}' },
736
+ },
737
+ preconditions: ["目标字段与作为条件的字段都已发布", "这是门户表单交互层(真实门户打开表单、改条件字段值来验证控件是否变必填);**不要用 datarule.create**——实测 type4 不会让门户控件变必填,且符号式经 historyOpt 会让表单渲染崩溃"],
738
+ conditionalRequirements: ["条件串:`{字段} 操作符 [值]`(操作符两侧留空格)。操作符用**中文**(真实设计器产出):等于/不等于/大于/小于/大于等于/小于等于/包含/不包含/包含全部/包含任意一个/等于任意一个/为空/不为空;也接受符号 == != > >= <= Contains IsEmpty。为空/不为空**不带值**(`{reason} 不为空`),其余带 JSON 值 {\"valueType\":...,\"value\":...}。多条件用 ` && `、多组用 ` || `。**不是** eq/gt/isnull——会让门户渲染 SyntaxError"],
739
+ priorToolReferences: [{ tool: "field.list", args: { environment, schemaCode: model, published: true }, purpose: "确认条件字段与目标字段都已发布" }],
740
+ dryRun: { supported: true, command: `omc call field.update --env ${environment} --dry-run --args '<recipe.args>'`, notes: "dry-run 校验字段 options 形状,不写入" },
741
+ expectedReadback: { readback: { field: { code: "leave_reason", options: { requiredFormula: "<protocol>" } } }, next: ["field.publish", "form.draft(rebuild)", "form.publish"] },
742
+ refusalRecovery: [
743
+ { code: "capability-out-of-coverage", action: "条件串用中文或符号操作符(等于/大于/不为空 或 == />/IsEmpty);恒必填用字面量 \"true\"" },
744
+ ...commonRecovery,
745
+ ],
746
+ requiresEnvironmentDerivedIds: false,
747
+ });
748
+ const formAlwaysReadonly = Object.freeze({
749
+ id: "form-always-readonly",
750
+ title: "表单层恒只读字段(字段 options.readonlyFormula:true)",
751
+ status: "offline-valid",
752
+ tool: "field.update",
753
+ args: { environment, schemaCode: model, code: "approval_level", name: "审批级别", options: { readonlyFormula: true } },
754
+ preconditions: ["目标字段已发布"],
755
+ conditionalRequirements: ["恒只读用布尔 readonlyFormula:true;条件只读用 readonlyCondition 条件串"],
756
+ priorToolReferences: [{ tool: "field.list", args: { environment, schemaCode: model, published: true }, purpose: "确认字段已发布" }],
757
+ dryRun: { supported: true, command: `omc call field.update --env ${environment} --dry-run --args '<recipe.args>'`, notes: "dry-run 校验 readonlyFormula 是布尔", },
758
+ expectedReadback: { readback: { field: { code: "approval_level", options: { readonlyFormula: true } } }, next: ["field.publish", "form.draft(rebuild)", "form.publish"] },
759
+ refusalRecovery: [{ code: "capability-out-of-coverage", action: "readonlyFormula 必须是布尔 true;条件只读才用 readonlyCondition" }, ...commonRecovery],
760
+ requiresEnvironmentDerivedIds: false,
761
+ });
762
+ const formConditionalReadonly = Object.freeze({
763
+ id: "form-conditional-readonly",
764
+ title: "表单层条件只读(字段 options.readonlyCondition)",
765
+ status: "offline-valid",
766
+ tool: "field.update",
767
+ args: {
768
+ environment,
769
+ schemaCode: model,
770
+ code: "approval_level",
771
+ name: "审批级别",
772
+ options: { readonlyCondition: '{status} 等于 {"valueType":"specifyOption","value":"已通过"}' },
773
+ },
774
+ preconditions: ["目标字段与条件字段已发布"],
775
+ conditionalRequirements: ["条件串协议同必填;恒只读改用 readonlyFormula:true"],
776
+ priorToolReferences: [{ tool: "field.list", args: { environment, schemaCode: model, published: true }, purpose: "确认字段已发布" }],
777
+ dryRun: { supported: true, command: `omc call field.update --env ${environment} --dry-run --args '<recipe.args>'`, notes: "dry-run 校验条件串协议" },
778
+ expectedReadback: { readback: { field: { code: "approval_level", options: { readonlyCondition: "<protocol>" } } }, next: ["field.publish", "form.draft(rebuild)", "form.publish"] },
779
+ refusalRecovery: [{ code: "capability-out-of-coverage", action: "使用中文或符号操作符与三段条件串" }, ...commonRecovery],
780
+ requiresEnvironmentDerivedIds: false,
781
+ });
782
+ const restfulQueryService = Object.freeze({
783
+ id: "restful-query-service",
784
+ title: "注册 RESTFUL 查询服务 + 一个方法(含参数面)",
785
+ status: "requires-prior-readback",
786
+ tool: "service.register",
787
+ args: {
788
+ environment,
789
+ serviceCode: "<service_code>",
790
+ name: "供应商查询服务",
791
+ adapterCode: "RESTFUL",
792
+ serviceCategoryId: "<category.id>",
793
+ serviceConfig: {
794
+ auth: { code: "customer", parameterlist: [] },
795
+ commonlist: [{ code: "url", value: "http://<host>/api" }],
796
+ },
797
+ methodName: "querySupplier",
798
+ methodConfig: { httpType: "Post", requestBodyType: "JSON", url: "/supplier/query", frontEndExtensionEnable: true },
799
+ methodInputParameters: [{ code: "keyword", name: "关键字", required: true, configJson: { position: "REQUEST_BODY" } }],
800
+ methodOutputParameters: [{ code: "name", name: "供应商名称", configJson: { position: "REQUEST_BODY" } }],
801
+ },
802
+ preconditions: ["serviceCategoryId 由 service.category.list 解析(「全部」是空 id 虚拟项不可用);RESTFUL 服务 configJson 需要 auth 与 commonlist[url];带 methodName 时必须有 methodConfig"],
803
+ conditionalRequirements: [
804
+ "RESTFUL auth.code ∈ customer(parameterlist=[]) / basic(client… username/password) / oauth_password / oauth_client;parameterlist 每项 {code,value} 且必须与 auth.code 期望参数完全一致",
805
+ "RESTFUL 方法 configJson 必需键:httpType(Post/Get/Put/Patch/Delete)、requestBodyType(Form-urlencoded/JSON/Text)、url、frontEndExtensionEnable(布尔)",
806
+ "RESTFUL 每个输入/输出参数必须带 configJson.position ∈ QUERY_PARAMS/REQUEST_HEADER/REQUEST_BODY/PATH_PARAMS(URL_VARIABLE 被运行时拒 501007)",
807
+ "一次写只建一个方法;给既有服务加方法走 method.create(escape hatch)",
808
+ ],
809
+ priorToolReferences: [
810
+ { tool: "service.category.list", args: { environment }, purpose: "取服务分类 id(所属目录必填)" },
811
+ { tool: "service.adapter_config", args: { environment }, purpose: "确认 RESTFUL 适配器的 commonList/authList 必需键与当前实例一致" },
812
+ ],
813
+ dryRun: { supported: true, command: `omc call service.register --env ${environment} --dry-run --args '<recipe.args>'`, notes: "dry-run 做 adapter/serviceConfig/methodConfig/参数面本地预检,零 wire;用它一次性看清缺哪个键" },
814
+ expectedReadback: { performed: { errcode: 0 }, readback: { servicePresent: true, methodPresent: true, methodCount: 1 }, next: ["service.get 复核(凭据已脱敏)"] },
815
+ refusalRecovery: [
816
+ { code: "bizservice-service-config-invalid", action: "serviceConfig 需要 auth{code,parameterlist[]}+commonlist[{code:'url',value}];按消息补键" },
817
+ { code: "bizservice-method-config-invalid", action: "methodConfig 需要 httpType/requestBodyType/url/frontEndExtensionEnable 四键" },
818
+ { code: "bizservice-parameter-face-invalid", action: "每个参数补 configJson.position;URL_VARIABLE 换 QUERY_PARAMS/REQUEST_BODY" },
819
+ { code: "bizservice-required-field-missing", action: "name/adapterCode/serviceCategoryId 是设计器必填;分类 id 不能为空" },
820
+ { code: "duplicate-service", action: "serviceCode 已存在;改用 service.update" },
821
+ { code: "bizservice-adapter-not-whitelisted", action: "写面仅 RESTFUL/DATA_SQL/DATA_TABLE/SAP/KAFKA/SOAP;INTERNAL 走二开扩展包" },
822
+ ...commonRecovery,
823
+ ],
824
+ requiresEnvironmentDerivedIds: true,
825
+ prerequisiteData: { source: "service.category.list", fields: ["category.id"] },
826
+ });
827
+ const fieldSerialNumber = Object.freeze({
828
+ id: "field-serial-number",
829
+ title: "单据号字段:表单 Serial Number 控件(seqNoSetting 分段构建器,按天/月/年重置原生)——不要落 SequenceService SPI 二开",
830
+ status: "offline-valid",
831
+ tool: "field.update",
832
+ args: {
833
+ environment,
834
+ schemaCode: "crm_contract",
835
+ code: "<字段 code>",
836
+ name: "合同编号",
837
+ options: {
838
+ formPropertyType: 100,
839
+ seqNoSetting: [
840
+ { type: "CONSTANT", value: "CT-" },
841
+ { type: "DATE", value: "yyyyMMdd" },
842
+ { type: "CONSTANT", value: "-" },
843
+ { type: "SEQNO", value: { startValue: 1, maxLength: 4, leftPad: true, resetDate: "DAY" } },
844
+ ],
845
+ },
846
+ },
847
+ preconditions: [
848
+ "需求是**编号自动生成**(前缀/日期/流水/按期重置),不是人工填写的编号字段——人工填写用普通短文本,别用本配方。",
849
+ "**单据号=表单 Serial Number 控件(字段类型 100 SequenceNo)**,不是 SHORT_TEXT 字段 + SequenceService SPI 二开(r5-crm 实证:SPI 路线在 8.6.26 无触发配置可写、属重造轮子;表单控件开箱即用)。",
850
+ ],
851
+ conditionalRequirements: [
852
+ "seqNoSetting 是**段数组**(分段构建器,顺序即拼接序):CONSTANT(固定前缀/分隔)/ DATE(value=Java 格式串 yyyyMMdd 等)/ SEQNO(流水)/ DATAITEM(取行内字段值)/ UID。设计师入口=控件属性「Document Number Configuration」。",
853
+ "SEQNO 段:startValue(起始)、maxLength(位数)、leftPad(补零)、**resetDate(重置策略 NONE/DAY/MONTH/YEAR——按天重置是原生能力)**。",
854
+ "写完必须 `field.publish` + `configure_form`(rebuild:true)重建表单并发布,控件才落到表单 attributes 的 options.seqNoSetting。",
855
+ "**编号生成时机=submit(表单提交管线)**:`data.save`(含 submit:true)不生成编号、行落 DRAFT;直接 `data.submit`(不带 bizObjectId=新建并提交)生成编号且行 COMPLETED(r5-crm 真机:CT-20261009-0001→0002 双跑)。",
856
+ "验证:连续两次 data.submit,编号应 +1 且格式符合段拼接;跨天重置由 resetDate 保证(勿自建 Redis 计数)。",
857
+ ],
858
+ priorToolReferences: [{ tool: "field.get", args: { environment, schemaCode: "<模型>", code: "<字段 code>" }, purpose: "确认字段存在且未配置过 seqNoSetting" }],
859
+ dryRun: { supported: true, command: `omc call field.update --env ${environment} --dry-run --args '<recipe.args>'`, notes: "dry-run 校验 options 形状,零 wire" },
860
+ expectedReadback: { performed: { errcode: 0 }, readback: { fieldPresent: true, optionsContain: "seqNoSetting+formPropertyType:100" }, next: ["data.submit 双跑 → 编号 0001/0002、行 COMPLETED"] },
861
+ refusalRecovery: [
862
+ { code: "field-options-freeform", action: "options 是自由对象——形状错误不会被出门校验拦,务必对照本配方逐键核对(type 全大写、resetDate 大写枚举)" },
863
+ ...commonRecovery,
864
+ ],
865
+ requiresEnvironmentDerivedIds: false,
866
+ });
867
+ const submitCrossFieldCheck = Object.freeze({
868
+ id: "submit-cross-field-check",
869
+ title: "表单提交校验(跨字段比较):datarule type1 + checkType:2 + conditions(右值结构值)+ prompt——在线 JS onValidate 之前的配置正解",
870
+ status: "offline-valid",
871
+ tool: "datarule.create",
872
+ args: {
873
+ environment,
874
+ schemaCode: "svc_repair",
875
+ propertyCode: "rep_plan_finish_time",
876
+ dataRuleType: 1,
877
+ checkType: 2,
878
+ name: "完成时间不早于报告时间",
879
+ options: {
880
+ conditions: `{rep_plan_finish_time} 大于等于 {"valueType":"currentModel","value":"rep_report_time"}`,
881
+ prompt: "预计完成时间不能早于报告时间",
882
+ },
883
+ },
884
+ preconditions: [
885
+ "需求是**提交时跨字段校验并弹提示拦截**(A 字段与 B 字段的比较)——这有**配置正解**,先于在线 JS onValidate 考虑(在线 JS 只在该协议确实表达不了时用)。",
886
+ "比较符支持:等于/不等于/大于/大于等于/小于/小于等于/包含/属于/介于 等(中文或符号)。",
887
+ ],
888
+ conditionalRequirements: [
889
+ `**右值必须用结构值** {"valueType":"currentModel","value":"<字段code>"}——裸 {字段code} 右值平台存储成功但**永不解析、规则静默不生效**(2026-09-24 真机;工具 fail-closed 拦截 + 读回 shapeWarning 标记存量雷)。`,
890
+ "**prompt 存纯文本**:门户对顶层 options.prompt 原样显示,URL 编码会弹 %E5.. 乱码(真机 equipmaint_workorder 案)。",
891
+ "单字段正则/长度类校验用 checkType:1/4/5(REGEX/MAX_LENGTH/EMPTY_ROW)+ 对应 options 键。",
892
+ ],
893
+ priorToolReferences: [{ tool: "field.list", args: { environment, schemaCode: "<模型>" }, purpose: "确认两个比较字段已发布" }],
894
+ dryRun: { supported: true, command: `omc call datarule.create --env ${environment} --dry-run --args '<recipe.args>'`, notes: "dry-run 过协议校验(裸右值/prompt 编码当场拒),零 wire" },
895
+ expectedReadback: { performed: { errcode: 0 }, readback: { rulePresent: true, shapeWarning: null }, next: ["门户提交错误日期 → 弹 prompt 文案并拦截(前台点验)"] },
896
+ refusalRecovery: [
897
+ { code: "datarule-submit-cross-field-bare", action: `右值换成结构值 {"valueType":"currentModel","value":"字段code"}` },
898
+ { code: "datarule-submit-prompt-encoded", action: "prompt 存纯文本,别 URL 编码" },
899
+ ...commonRecovery,
900
+ ],
901
+ requiresEnvironmentDerivedIds: false,
902
+ });
903
+ const dataDuplicateFlag = Object.freeze({
904
+ id: "data-duplicate-flag",
905
+ title: "字段唯一性防重打标(8.6.26 实证唯一可靠路径):预置 Create 规则挂 BIZ_ACTION 自调二开 markDuplicates(规则图内 QUERY→UPDATE 打标会静默空转)",
906
+ status: "offline-valid",
907
+ tool: "rule.save",
908
+ args: {
909
+ environment,
910
+ schemaCode: "wms_material",
911
+ ruleCode: "Create",
912
+ name: "数据新增",
913
+ nodeGraph: {
914
+ nodes: [
915
+ { nodeCode: "Start", nodeName: "开始", nodeType: "START" },
916
+ { nodeCode: "A1", nodeName: "防重打标", nodeType: "BIZ_ACTION", inputParam: "_input", methodMapping: { schemaCode: "wms_material", serviceCode: "wms_material_service", serviceMethodCode: "markDuplicates", inputMappings: [{ serviceMethodParameterCode: "id", codeType: 1, bizCode: "id", required: true }, { serviceMethodParameterCode: "code", codeType: 1, bizCode: "mat_code" }], outputMappings: [] } },
917
+ { nodeCode: "Create", nodeName: "新增数据", nodeType: "SYSTEM_CREATE", isSystemNode: true },
918
+ { nodeCode: "End", nodeName: "结束", nodeType: "END" },
919
+ ],
920
+ routes: [
921
+ { preNode: "Start", postNode: "A1", routeCondition: true },
922
+ { preNode: "A1", postNode: "Create", routeCondition: true },
923
+ { preNode: "Create", postNode: "End", routeCondition: true },
924
+ ],
925
+ },
926
+ publish: true,
927
+ },
928
+ preconditions: [
929
+ "目标模型已有「重复标记」字段(如 `mat_dup_flag` 短文本,表单+列表可见)。",
930
+ "需求方已接受**打标降级**(平台无硬拦原语;BRANCH 不能按 QUERY 空结果分支)。",
931
+ "二开侧已有 markDuplicates 接口:查同键既有行(代码+号码一级、仅键二级覆盖无代码场景)→ UPDATE 给**既有那条**打标(宁可误报原则)。注册为 RESTFUL 服务并自调(oauth_password + 方法参数 X-LowCode-Corpid CONST——见 bizservice-self-call-corp)。",
932
+ "**改写的是预置 Create 规则**(不是自定义 ruleCode):数据事件只分发预置行;自定义 code 落库即 GET_LIST、且其 QUERY→UPDATE 同样空转(r4-fin 双证 2026-10-09)。**节点顺序**:BIZ_ACTION 在 SYSTEM_CREATE 前可用 _input 值操作既有行(打标场景=此形);要 getBizObject 读/改触发行自己的动作必须排 SYSTEM_CREATE 之后(否则行未落库 null 空转——r4-fin review F1)。",
933
+ ],
934
+ conditionalRequirements: [
935
+ "**为什么不是规则图内 QUERY→UPDATE**:8.6.26 UPDATE 节点 inputParam 只认 `_input`(触发行),`_QN_output` 引用被静默空转、UPDATE 始终命中新建那条——与打标既有行的需求相反(r4-fin 两次真机复现 2026-10-09)。规则图内打标不可靠,必须走二开。",
936
+ "预置规则改写要点:SYSTEM_CREATE 身份节点必须保留(nodeCode/nodeName/isSystemNode 平台预置身份,错配会让触发行写错);BIZ_ACTION 插在 START 之后、SYSTEM_CREATE 之前。",
937
+ "写完 `rule.enable`;**验证**:data.save 同键两条 → 既有那条带标记、新提交那条干净;无代码场景用仅键匹配覆盖。",
938
+ "掩码/占位值(RELEVANCE-全 0、全 0 id、整值 <…>、未替换 ${var})会被 rule-graph-masked-or-placeholder-value 出门拦截——从回读复制常量时取真实值。",
939
+ ],
940
+ priorToolReferences: [
941
+ { tool: "model.inspect", args: { environment, schemaCode: "<model>" }, purpose: "确认标记字段已存在且已发布" },
942
+ { tool: "rule.get", args: { environment, schemaCode: "<model>", ruleCode: "Create", full: true }, purpose: "改写预置规则前先取现图,整图带回(保留 SYSTEM_CREATE 身份)" },
943
+ ],
944
+ dryRun: { supported: true, command: `omc call rule.save --env ${environment} --dry-run --args '<recipe.args>'`, notes: "dry-run 校验预置身份/BIZ_ACTION 映射/掩码值,零 wire" },
945
+ expectedReadback: { performed: { errcode: 0 }, readback: { rulePresent: true, enabled: true, bizRuleType: "DATA_OP" }, next: ["同键双跑:既有行打标、新行干净;唯一键不触发"] },
946
+ refusalRecovery: [
947
+ { code: "rule-graph-preset-system-node-mismatch", action: "预置 Create 必须保留 SYSTEM_CREATE 身份节点(不能换成别的系统节点类型)" },
948
+ { code: "rule-graph-masked-or-placeholder-value", action: "常量值是掩码/占位(全 0 corpId、<…>、${var})——用真实值(corpId 取环境事实/方法参数 position:2 方案)" },
949
+ { code: "runtime-50000-corpId-blank", action: "补方法参数 X-LowCode-Corpid(见 bizservice-self-call-corp);HTTP 200 不代表成功,看响应体 errcode" },
950
+ ...commonRecovery,
951
+ ],
952
+ requiresEnvironmentDerivedIds: false,
953
+ });
954
+ const bizserviceSelfCallCorp = Object.freeze({
955
+ id: "bizservice-self-call-corp",
956
+ title: "业务规则自调本实例:oauth_password 为基础;X-LowCode-Corpid 头视环境而定(token 是否自带 corpId,8.5+ 部分配置才需要)——非铁律,真机判定",
957
+ status: "offline-valid",
958
+ tool: "method.update",
959
+ args: {
960
+ environment,
961
+ serviceCode: "<service_code>",
962
+ methodCode: "<method_code>",
963
+ inputParameters: [
964
+ { code: "X-LowCode-Corpid", name: "corpId头", required: false, codeType: 0, bizCode: "<corpId 常量值>", configJson: { position: "REQUEST_HEADER" } },
965
+ ],
966
+ },
967
+ preconditions: [
968
+ "自调本实例(commonlist.url 指回本平台 /api)鉴权以 oauth_password 为基础(Authorization Bearer)。**X-LowCode-Corpid 头非铁律**:token 自带 corpId claim 的环境(生产多数)不配任何 corpid;token corpId=null 的环境(本沙盒 api 客户端;8.5+ 部分配置)缺头会 HTTP 200 但响应体 errcode:50000(corpId can not be blank,r5 实测 2026-10-10)。",
969
+ "**判定法(先做再配)**:带 token 直调一次扩展端点看**响应体 errcode**(HTTP 200 不代表成功,平台错误包在体里);errcode:0 则不配 corpid;errcode:50000 则按本配方补方法参数(code=X-LowCode-Corpid、configJson.position=REQUEST_HEADER、codeType:0 CONST、值放 bizCode——平台注入,调用方无需传;手动 curl 加请求头即可)。",
970
+ "出站第三方接口按对方要求配鉴权,与本配方无关。",
971
+ ],
972
+ conditionalRequirements: [
973
+ "**参数 code 必须精确等于 `X-LowCode-Corpid`**——适配器按「参数 code = header 名」加入请求头(headerParam.add(key, value))。",
974
+ "**corpid 传递有两种实证形状**:①方法级入参 code 为 X-LowCode-Corpid、configJson.position 为 REQUEST_HEADER 整数 2(r3-wms 实证可用);②规则 BIZ_ACTION 的 inputMappings 加 CONST 映射(serviceMethodParameterCode 为 X-LowCode-Corpid、codeType 为 0、bizCode 放真实 corpId——r4-fin 实证:该环境下方法级 position:2 注入为空头值 [\"\"],此形状才生效)。**先用 ②,双跑验证头值;①失效(头空)就换 ②**。CONST 值必须放 bizCode(平台只读 bizCode)。",
975
+ "**corpid 头非空即可(r6-onlinejs R19 实证)**:沙盒 token corpId=null 时,全 0 corpId 头(RELEVANCE-0000…)也 errcode:0;仅空头才 50000。多租户路由才需真值。凭据值不可从读回抄(读面按 ticket 115 掩码为 ***),必须用任务环境事实真值(doctor 显示的 corpId、任务提供的 client_secret)。",
976
+ "configJson.position 用 REQUEST_HEADER 名即可(工具写侧编码为整数索引 2;wire 上是整数——写名字平台侧反序列化失败、header 根本不进,源码 @JsonCreator get(int))。",
977
+ "BIZ_ACTION 节点调用该方法**无需传该常量参数**(方法级常量由平台注入);只需照常映射业务参数。",
978
+ ],
979
+ priorToolReferences: [{ tool: "method.get", args: { environment, serviceCode: "<service_code>", methodCode: "<method_code>" }, purpose: "取现有参数面,update 时整组带回(含本常量头)" }],
980
+ dryRun: { supported: true, command: `omc call method.update --env ${environment} --dry-run --args '<recipe.args>'`, notes: "dry-run 校验参数面/CONST 形状,零 wire" },
981
+ expectedReadback: { readback: { methodPresent: true, corpHeaderParam: "code=X-LowCode-Corpid position=REQUEST_HEADER(2) codeType=0 bizCode=<corpId>" }, next: ["BIZ_ACTION 规则触发后 E2E:被调接口不再回 50000 corpId can not be blank"] },
982
+ refusalRecovery: [
983
+ { code: "bizservice-parameter-face-invalid", action: "常量头也是参数:configJson.position=REQUEST_HEADER 必带" },
984
+ { code: "runtime-50000-corpId-blank", action: "header 仍空 = 常量值放错槽(须 bizCode)或参数 code 不是精确的 X-LowCode-Corpid;method.get 读回核对" },
985
+ { code: "bizservice-auth-credential-placeholder", action: "凭据被掩码/占位值守卫拦截——读回掩码 *** 或自造 RELEVANCE-xxx/全 0 不可注册;凭据必须从任务环境事实取真值(doctor 显示的 corpId、任务提供的 client_secret),不可从读回抄" },
986
+ ...commonRecovery,
987
+ ],
988
+ requiresEnvironmentDerivedIds: false,
989
+ });
990
+ const listViewConfigure = Object.freeze({
991
+ id: "configure-default-list",
992
+ title: "配置模型的默认列表视图(列/排序;默认视图已自动存在)",
993
+ status: "requires-prior-readback",
994
+ tool: "listview.configure",
995
+ args: { environment, schemaCode: model, fieldCodes: ["apply_no", "apply_amount", "apply_note"] },
996
+ preconditions: ["字段已 field.publish(视图与表单脚手架都依赖已发布字段)", "默认视图在 model.create 时由平台自动生成,code = schemaCode"],
997
+ conditionalRequirements: [
998
+ "**省略 `code` 即配置默认视图(code = schemaCode);不要先 listview.create——那会多出一个自定义视图。** 只有确实需要第二个视图时才 listview.create。",
999
+ "`fieldCodes` 只列业务字段,可给 propertyCode 或字段显示名;系统列由平台决定。未知引用被拒(listview-columns-unknown-field)。",
1000
+ "数值列 displayFormat 由字段 0 基 `options.scale` 推导(0=整数,1=1位,2=2位…);本工具会原地修正既有列的 displayFormat。",
1001
+ "`query/update` 自动 publish=TRUE——视图无草稿/发布两态,无需独立发布步骤。",
1002
+ ],
1003
+ priorToolReferences: [
1004
+ { tool: "listview.list", args: { environment, schemaCode: model }, purpose: "确认默认视图 code 与是否已有多视图" },
1005
+ { tool: "field.list", args: { environment, schemaCode: model, published: true }, purpose: "确认字段已发布" },
1006
+ ],
1007
+ dryRun: { supported: true, command: `omc call listview.configure --env ${environment} --dry-run --args '<recipe.args>'`, notes: "dry-run 预检 fieldCodes/形状,不写" },
1008
+ expectedReadback: { readback: { code: "<schemaCode>", added: ["apply_no", "apply_amount"], columns: "…" }, next: ["data.list 验证读面"] },
1009
+ refusalRecovery: [
1010
+ { code: "listview-columns-unknown-field", action: "字段未发布或名字写错;先 field.create + field.publish 或改引用" },
1011
+ { code: "listview-not-found", action: "默认视图应已存在;用 listview.list 确认 code,必要时 listview.create" },
1012
+ ...commonRecovery,
1013
+ ],
1014
+ requiresEnvironmentDerivedIds: false,
1015
+ });
1016
+ const dataListFilter = Object.freeze({
1017
+ id: "list-filter-equality",
1018
+ title: "按等值条件读一批业务数据(filter DSL 的可运行最小形状)",
1019
+ status: "offline-valid",
1020
+ tool: "data.list",
1021
+ args: { environment, schemaCode: model, filter: { op: "eq", field: "status", value: "OPEN" }, size: 20 },
1022
+ preconditions: ["schemaCode 的字段已 field.publish(filter 叶子的 propertyType 从发布面读)"],
1023
+ conditionalRequirements: [
1024
+ "filter 是**一个**子句 `{op, field, value?}`;op ∈ eq/neq/gt/lt/gte/lte/like/notlike/in/notin/between/notbetween/isnull/isnotnull。",
1025
+ "多条件 `filters:[…]`(AND);分组 `filterGroup:{op:'and'|'or',clauses:[…]}`;系统/外部字段用 rawCondition。",
1026
+ ],
1027
+ priorToolReferences: [
1028
+ { tool: "field.list", args: { environment, schemaCode: model, published: true }, purpose: "确认要过滤的字段 code 已发布" },
1029
+ ],
1030
+ dryRun: { supported: false, command: `omc call data.list --env ${environment} --args '<recipe.args>'`, notes: "只读,无 dry-run;直接调用即读回" },
1031
+ expectedReadback: { readback: { total: 1, rows: "…" } },
1032
+ refusalRecovery: [{ code: "invalid-filter-DSL", action: "按错误里的 minimalValidFilter/acceptedOps 修正 op/field/value" }],
1033
+ requiresEnvironmentDerivedIds: false,
1034
+ });
1035
+ const dictionaryKeyValue = Object.freeze({
1036
+ id: "dictionary-key-value",
1037
+ title: "建一个键值字典并用其 id 作字段下拉来源",
1038
+ status: "offline-valid",
1039
+ tool: "dictionary.save",
1040
+ args: {
1041
+ environment,
1042
+ code: "visit_type",
1043
+ name: "回访方式",
1044
+ dictionaryType: 0,
1045
+ records: [
1046
+ { code: "phone", name: "电话" },
1047
+ { code: "visit", name: "上门" },
1048
+ { code: "online", name: "线上" },
1049
+ ],
1050
+ },
1051
+ preconditions: ["字典 code 尚未存在(存在须改传 id 更新);dictionaryType 0=KEY_VALUE / 1=TREE"],
1052
+ conditionalRequirements: [
1053
+ "**字段下拉绑定字典时 `checkedDictionary` 必须填字典 id(不是 name)**——前端 a-select 的 option value 就是 id;填 name 运行时会返回『字典已被禁用,无数据』、下拉空白(真机 ticket 02/34)。拿到 id:`dictionary.get`/`dictionary.names` 读回。",
1054
+ "TREE 字典的 record 可带 `parentId`;给既有字典加记录也走 `dictionary.save`(带 id + 完整 records)。",
1055
+ "字段侧用 `field.create` 的 `options.optionsSet = {optionsType:'dictionary',dictionary:{checkedDictionary:'<id>',checkedDictionaryCode:'<code>',useDictionariesData:[{code,name,id}]}}`;`choices` 是「手写选项」的另一条路,不要与字典混用。",
1056
+ ],
1057
+ priorToolReferences: [
1058
+ { tool: "dictionary.names", args: { environment }, purpose: "查 code/name 冲突并读回既有 id(字典多时结果会溢成 artifact;只需 code/name,用 `artifact get <id> --keys` 或 --jq,别整份取)" },
1059
+ { tool: "dictionary.get", args: { environment, code: "visit_type" }, purpose: "按 code 精确查存在性(小结果,优于 dictionary.names 全量)" },
1060
+ ],
1061
+ dryRun: { supported: true, command: `omc call dictionary.save --env ${environment} --dry-run --args '<recipe.args>'`, notes: "dry-run 预检 code 冲突/形状,不写" },
1062
+ expectedReadback: { performed: { errcode: 0 }, readback: { present: true, dictionary: { id: "<dictionary.id>", code: "visit_type", records: ["phone", "visit", "online"] } }, next: ["field.create --example dictionary-backed-single-select(用回读的 id)"] },
1063
+ refusalRecovery: [
1064
+ { code: "dictionary-create-duplicate", action: "code 已存在:从 dictionary.names 取 id 后改传 id 做更新" },
1065
+ { code: "wire-business", action: "平台 `errcode -1 系统中已经存在相同名称的字典` = **字典名称全局唯一**(code 唯一还不够)——换一个 name(真机 2026-09-25:新 code + 旧 name 仍被拒)" },
1066
+ { code: "capability-out-of-coverage", action: "确认 dictionaryType 0/1 与 records[{code,name}] 形状" },
1067
+ ...commonRecovery,
1068
+ ],
1069
+ requiresEnvironmentDerivedIds: true,
1070
+ prerequisiteData: { source: "dictionary.get / dictionary.names", fields: ["dictionary.id"] },
1071
+ });
1072
+ export const EXECUTION_METADATA = Object.freeze({
1073
+ "configure_model": Object.freeze({ recipes: [configureModelRecipe], relatedTools: ["discover_environment", "field.update", "listview.create", "listview.get"] }),
1074
+ "configure_form": Object.freeze({ recipes: [configureFormRecipe], relatedTools: ["field.list", "field.update", "listview.create", "listview.get"] }),
1075
+ "field.update": Object.freeze({ recipes: [fieldSerialNumber, formConditionalRequired, formAlwaysReadonly, formConditionalReadonly], relatedTools: ["field.list", "field.publish", "form.draft", "form.publish"] }),
1076
+ "datarule.create": Object.freeze({ recipes: [submitCrossFieldCheck, dataruleCalcRule, dataruleSubtableSum, dataruleChildColumn, dataruleRequired, dataruleReadonly, dataruleDisplay], relatedTools: ["field.list", "datarule.list"] }),
1077
+ "field.create": Object.freeze({ recipes: [numericTwoDecimals, dictionarySelection, childTable, businessModelDropdown], relatedTools: ["dictionary.names", "dictionary.records", "discover_environment", "field.list", "field.publish", "form.draft", "configure_form", "form.publish"] }),
1078
+ "rule.save": Object.freeze({ recipes: [ruleGraph, ruleBranchGraph, ruleTimingTask, dataDuplicateFlag, ruleCrossModelCreate], relatedTools: ["field.list", "rule.get", "rule.enable"] }),
1079
+ "dictionary.save": Object.freeze({ recipes: [dictionaryKeyValue], relatedTools: ["dictionary.names", "dictionary.records", "field.create"] }),
1080
+ "workflow.update": Object.freeze({ recipes: [workflowTemplateUpdate, workflowConditionalBranch], relatedTools: ["workflow.get_draft", "workflow.publish"] }),
1081
+ "workflow.bindRule": Object.freeze({ recipes: [workflowBinding], relatedTools: ["workflow.create", "workflow.get_draft", "workflow.get_published", "rule.list"] }),
1082
+ "data.save": Object.freeze({ recipes: [workflowSave], relatedTools: ["workflow.get_published", "data.submit", "workitem.list"] }),
1083
+ "data.submit": Object.freeze({ recipes: [workflowSubmit], relatedTools: ["data.save", "workitem.list", "data.load"] }),
1084
+ "service.register": Object.freeze({ recipes: [restfulQueryService], relatedTools: ["service.category.list", "service.adapter_config", "service.get", "method.get"] }),
1085
+ "method.update": Object.freeze({ recipes: [bizserviceSelfCallCorp], relatedTools: ["method.get", "method.create", "service.get"] }),
1086
+ "listview.configure": Object.freeze({ recipes: [listViewConfigure], relatedTools: ["listview.list", "listview.get", "field.list", "field.publish"] }),
1087
+ "data.list": Object.freeze({ recipes: [dataListFilter], relatedTools: ["data.load", "data.delete", "listview.configure", "field.list"] }),
1088
+ });
1089
+ export function executionMetadataFor(tool) {
1090
+ return EXECUTION_METADATA[tool.name];
1091
+ }
1092
+ //# sourceMappingURL=execution-metadata.js.map