@stalfh233/omc-cli 0.4.36 → 0.4.38

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 (51) hide show
  1. package/README.md +2 -2
  2. package/dist/bench/call-face-eval.js +1 -1
  3. package/dist/bench/discovery-cost-eval.js +1 -1
  4. package/dist/bench/driver.js +1 -1
  5. package/dist/bench/intent-face-eval.js +1 -1
  6. package/dist/capability-shape.js +2 -153
  7. package/dist/cli.js +42 -578
  8. package/dist/contract-consistency.js +5 -1
  9. package/dist/execution-metadata.js +24 -1160
  10. package/dist/field-families.js +2 -22
  11. package/dist/intent/plan-apply.js +587 -0
  12. package/dist/json-column.js +2 -67
  13. package/dist/lanes/businessrule-contract.js +4 -3
  14. package/dist/lanes/list-view-shapes.js +13 -98
  15. package/dist/lanes/model-contract.js +2 -2
  16. package/dist/lanes/rule-graph.js +2 -2
  17. package/dist/lanes/runtime-data-contract.js +6 -83
  18. package/dist/recipes/b2-registration.js +87 -0
  19. package/dist/recipes/businessrule.js +269 -0
  20. package/dist/recipes/datarule.js +215 -0
  21. package/dist/recipes/dictionary.js +42 -0
  22. package/dist/recipes/field.js +230 -0
  23. package/dist/recipes/form.js +19 -0
  24. package/dist/recipes/list-view.js +31 -0
  25. package/dist/recipes/model.js +34 -0
  26. package/dist/recipes/online-js.js +32 -0
  27. package/dist/recipes/runtime-data.js +113 -0
  28. package/dist/recipes/shared.js +8 -0
  29. package/dist/recipes/workflow.js +119 -0
  30. package/dist/tool-types.js +2 -7
  31. package/dist/zip.js +2 -155
  32. package/package.json +3 -2
  33. package/dist/bench/fake-http-server.js +0 -65
  34. package/dist/fake/app.js +0 -45
  35. package/dist/fake/b2-registration.js +0 -620
  36. package/dist/fake/businessrule.js +0 -242
  37. package/dist/fake/datarule.js +0 -72
  38. package/dist/fake/dictionary.js +0 -108
  39. package/dist/fake/environment.js +0 -30
  40. package/dist/fake/field.js +0 -167
  41. package/dist/fake/form.js +0 -101
  42. package/dist/fake/index.js +0 -121
  43. package/dist/fake/list-view.js +0 -289
  44. package/dist/fake/model.js +0 -136
  45. package/dist/fake/online-js.js +0 -18
  46. package/dist/fake/report.js +0 -253
  47. package/dist/fake/routes.js +0 -47
  48. package/dist/fake/rule-lifecycle.js +0 -37
  49. package/dist/fake/runtime-data.js +0 -367
  50. package/dist/fake/state.js +0 -67
  51. package/dist/fake/workflow.js +0 -364
@@ -0,0 +1,269 @@
1
+ import { environment, model, commonRecovery } from "./shared.js";
2
+ const ruleGraph = Object.freeze({
3
+ id: "standard-business-rule-graph",
4
+ title: "保存并启用最小数据事件规则图",
5
+ status: "requires-prior-readback",
6
+ tool: "rule.save",
7
+ args: {
8
+ environment,
9
+ schemaCode: model,
10
+ ruleCode: "Create",
11
+ name: "创建时写入状态",
12
+ nodeGraph: {
13
+ nodes: [
14
+ { nodeCode: "Start", nodeType: "START" },
15
+ { 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" }] },
16
+ { nodeCode: "Create", nodeName: "新增数据", nodeType: "SYSTEM_CREATE", isSystemNode: true },
17
+ { nodeCode: "End", nodeType: "END" },
18
+ ],
19
+ routes: [
20
+ { preNode: "Start", postNode: "UPDATE_1" },
21
+ { preNode: "UPDATE_1", postNode: "Create" },
22
+ { preNode: "Create", postNode: "End" },
23
+ ],
24
+ },
25
+ publish: true,
26
+ },
27
+ preconditions: ["模型和 target field 已发布;Create 是平台预置触发规则;目标字段可由规则动作写入"],
28
+ conditionalRequirements: ["BIZ_ACTION 的 outputMappings 必须在 SYSTEM_CREATE/SYSTEM_UPDATE 前被消费;自定义 ruleCode 不会被数据事件自动分发"],
29
+ priorToolReferences: [
30
+ { tool: "field.list", args: { environment, schemaCode: model, published: true }, purpose: "确认 status 已发布" },
31
+ ],
32
+ dryRun: { supported: true, command: `omc call rule.save --env ${environment} --dry-run --args '<recipe.args>'`, notes: "dry-run 执行 rule graph dry lint;不会保存/发布规则" },
33
+ expectedReadback: { performed: { published: true, enabled: false }, readback: { published: true, next: "rule.enable", inputParam: "_input", systemNodeOrder: "UPDATE_1 → SYSTEM_CREATE" } },
34
+ refusalRecovery: [
35
+ { code: "rule-graph-validation-failed", action: "按 validation 修正 nodeType、路线、_input 和系统节点顺序后重新 dry-run" },
36
+ { code: "rule-schema-unresolved", action: "先确认模型存在,不要让 list/save 为幻影 schema 补建预置规则" },
37
+ { code: "rule-graph-bizmethod-unresolved", action: "先读取真实 BizMethod 及参数面;不要绑定 registration shell" },
38
+ ...commonRecovery,
39
+ ],
40
+ requiresEnvironmentDerivedIds: false,
41
+ prerequisiteData: { source: "field.list(published:true)", note: "ruleCode=Create 为平台预置代码;自定义代码需 workflow/button 触发" },
42
+ });
43
+ const ruleBranchGraph = Object.freeze({
44
+ id: "business-rule-branch-conditional",
45
+ title: "金额分级:BRANCH 判断 + 两条 UPDATE 分支(真机验证)",
46
+ status: "requires-prior-readback",
47
+ tool: "rule.save",
48
+ args: {
49
+ environment,
50
+ schemaCode: model,
51
+ ruleCode: "Create",
52
+ name: "金额分级",
53
+ nodeGraph: {
54
+ nodes: [
55
+ { nodeCode: "Start", nodeType: "START" },
56
+ { nodeCode: "Create", nodeName: "新增数据", nodeType: "SYSTEM_CREATE", isSystemNode: true },
57
+ { nodeCode: "Branch_1", nodeType: "BRANCH", sourceInputMethod: "PRE_OUT", exitConditionGroups: [[{ currentSchemaDataItem: "amount", ruleDataConditionType: "GT", ruleValueType: "FIXED", value: 10000 }]] },
58
+ { nodeCode: "UPDATE_BIG", nodeType: "UPDATE", targetObjectCode: model, dataActions: [{ targetDataItem: "level", ruleValueType: "FIXED", value: "大额", ruleActionType: "EQUALS" }] },
59
+ { nodeCode: "UPDATE_NORMAL", nodeType: "UPDATE", targetObjectCode: model, dataActions: [{ targetDataItem: "level", ruleValueType: "FIXED", value: "普通", ruleActionType: "EQUALS" }] },
60
+ { nodeCode: "End", nodeType: "END" },
61
+ ],
62
+ routes: [
63
+ { preNode: "Start", postNode: "Create" },
64
+ { preNode: "Create", postNode: "Branch_1" },
65
+ { preNode: "Branch_1", postNode: "UPDATE_BIG" },
66
+ { preNode: "Branch_1", postNode: "UPDATE_NORMAL", routeCondition: false },
67
+ { preNode: "UPDATE_BIG", postNode: "End" },
68
+ { preNode: "UPDATE_NORMAL", postNode: "End" },
69
+ ],
70
+ },
71
+ publish: true,
72
+ },
73
+ preconditions: ["模型和 target field(level/amount)已发布;Create 是平台预置触发规则;目标字段可由规则动作写入"],
74
+ conditionalRequirements: [
75
+ "**系统节点顺序:BRANCH 必须排在 `SYSTEM_CREATE` 之后**——BRANCH 直接接在 START 后会运行时静默不生效、动作不回写(工具 fail closed)。注意这与 BIZ_ACTION 回写场景相反(那里系统节点要在动作之后)——本 recipe 是分支场景。**`SYSTEM_CREATE` 必须显式写**(工具对分支图的自动注入不可靠)。",
76
+ "**出口条件叶只 4 个键** `{currentSchemaDataItem(左值字段),ruleDataConditionType(操作符),ruleValueType,value}`;用 UI 键 `targetSchemaDataItem`+`ruleConditionType` 会 save+publish 过但运行时 50000(工具 fail closed `rule-graph-branch-condition-keys`)。",
77
+ "**下列键可省、工具自动补**(省 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/二开。**",
78
+ "预置规则的系统节点类型必须与 ruleCode 匹配(Create→SYSTEM_CREATE);放 SYSTEM_UPDATE 会 data.submit 50000(工具 fail closed)。",
79
+ ],
80
+ priorToolReferences: [
81
+ { tool: "field.list", args: { environment, schemaCode: model, published: true }, purpose: "确认 amount/level 已发布" },
82
+ ],
83
+ dryRun: { supported: true, command: `omc call rule.save --env ${environment} --dry-run --args '<recipe.args>'`, notes: "dry-run 执行 rule graph dry lint;不写不发布" },
84
+ expectedReadback: { performed: { published: true, enabled: false }, readback: { nodes: "Start,SYSTEM_CREATE,Branch_1,UPDATE_BIG,UPDATE_NORMAL,End", next: "rule.enable 后用 data.submit 两条(命中/不命中)验 level" } },
85
+ refusalRecovery: [
86
+ { code: "rule-graph-branch-before-system-node", action: "把 SYSTEM_CREATE 挪到 BRANCH 之前(本 recipe 顺序)" },
87
+ { code: "rule-graph-branch-condition-keys", action: "出口叶改用 currentSchemaDataItem + ruleDataConditionType(见 conditionalRequirements)" },
88
+ { code: "rule-graph-branch-input-param", action: "给 BRANCH 补 inputParam:\"_input\"" },
89
+ { code: "rule-graph-branch-judge-method", action: "给 BRANCH 补 judgeMethod:\"DEFAULT\"" },
90
+ { code: "rule-graph-preset-system-node-mismatch", action: "把 SYSTEM_* 改成与 ruleCode 匹配的类型(Create→SYSTEM_CREATE)" },
91
+ { code: "rule-graph-validation-failed", action: "按 validation 逐项修正后重新 dry-run" },
92
+ ...commonRecovery,
93
+ ],
94
+ requiresEnvironmentDerivedIds: false,
95
+ prerequisiteData: { source: "field.list(published:true)", note: "ruleCode=Create 为平台预置代码" },
96
+ });
97
+ const ruleTimingTask = Object.freeze({
98
+ id: "rule-timing-task",
99
+ title: "定时任务:每天/周期触发规则(TIMING_TASK,SCHEDULER 节点,配置能力)",
100
+ status: "offline-valid",
101
+ tool: "rule.save",
102
+ args: {
103
+ environment,
104
+ schemaCode: model,
105
+ ruleCode: "<customRuleCode e.g. daily_scan>",
106
+ name: "每日定时作业",
107
+ nodeGraph: {
108
+ bizRuleType: 2,
109
+ nodes: [
110
+ { nodeCode: "Start", nodeName: "开始", nodeType: "START" },
111
+ { nodeCode: "SCHEDULER", nodeName: "定时作业", nodeType: "SCHEDULER", isSystemNode: true, nodeCategory: "systemNode" },
112
+ { nodeCode: "UPDATE_1", nodeName: "执行动作", nodeType: "UPDATE", targetObjectCode: model, dataActions: [{ targetDataItem: "<field>", ruleValueType: "FIXED", value: "<值>", ruleActionType: "EQUALS" }] },
113
+ { nodeCode: "End", nodeName: "结束", nodeType: "END" },
114
+ ],
115
+ routes: [
116
+ { preNode: "Start", postNode: "SCHEDULER", routeCondition: true },
117
+ { preNode: "SCHEDULER", postNode: "UPDATE_1", routeCondition: true },
118
+ { preNode: "UPDATE_1", postNode: "End", routeCondition: true },
119
+ ],
120
+ schedulerSetting: {
121
+ triggerTimeType: "SELECTED_TIME",
122
+ cycleType: "DAY",
123
+ executeType: "CYCLE",
124
+ execCycle: 1,
125
+ hour: 1,
126
+ minute: 0,
127
+ second: 0,
128
+ startTime: "<yyyy-MM-dd HH:mm:ss,未来时刻>",
129
+ endType: "NEVER",
130
+ // 扫描本表:以本模型为触发数据源,filterConditionGroups 是扫描过滤条件;
131
+ // 输入数据 = 被过滤出的行,下游以 inputParam:"_input" 消费其字段。
132
+ dataSourceType: "GET_LIST",
133
+ filterConditionGroups: [[{ targetSchemaDataItem: "<field>", ruleConditionType: "EQ", ruleValueType: "FIXED", value: "<值>" }]],
134
+ latestVersion: true,
135
+ consolidation: false,
136
+ },
137
+ },
138
+ publish: true,
139
+ },
140
+ preconditions: [
141
+ "模型和动作目标字段已发布;本环境需 `cloudpivot.task.enabled=true`(否则平台不建调度任务)",
142
+ "**「每天定时执行」= 定时任务 TIMING_TASK,是平台原生配置能力,不是外部定时器、不是在线 JS、无需二开**",
143
+ ],
144
+ conditionalRequirements: [
145
+ "`bizRuleType:2` 写在 **`nodeGraph.bizRuleType`**(顶层参数被忽略)——平台只对 bizRuleType=2 的行建调度任务(BizRuleScheduling.java:202-205);nodeGraph 内未传时工具推断并 warning。",
146
+ "调度参数在 `nodeGraph.schedulerSetting`,**不在 SCHEDULER 节点上**(ScheduleNode 无时机字段);缺它则规则能存能启用但**永不触发**。`latestVersion` **必须 true**(false 走旧 builder 读 null startDate → publish 50000)。",
147
+ "**「定时扫描某表按条件执行」用 `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。",
148
+ "`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`。",
149
+ ],
150
+ priorToolReferences: [
151
+ { tool: "field.list", args: { environment, schemaCode: model, published: true }, purpose: "确认动作目标字段已发布" },
152
+ ],
153
+ dryRun: { supported: true, command: `omc call rule.save --env ${environment} --dry-run --args '<recipe.args>'`, notes: "dry-run 跑图校验(含 schedulerSetting 形状/枚举),不写不发布" },
154
+ expectedReadback: { performed: { published: true, enabled: false }, readback: { bizRuleType: 2, nodes: "Start,SCHEDULER,UPDATE_1,End", next: "rule.enable;平台到 startTime 建 cron 并触发" } },
155
+ refusalRecovery: [
156
+ { code: "rule-graph-scheduler-setting-shape", action: "`nodeGraph.schedulerSetting` 必须是对象/JSON 串(不是写在 SCHEDULER 节点上)" },
157
+ { code: "rule-graph-scheduler-setting-enum", action: "按 8.6.26 枚举修正 schedulerSetting 字段值(见 conditionalRequirements)" },
158
+ { code: "rule-graph-bizruletype-inferred-timing-task", action: "已自动推断 bizRuleType=2;如非定时目的请去掉 SCHEDULER 节点" },
159
+ { code: "rule-graph-validation-failed", action: "按 validation 逐项修正后重新 dry-run" },
160
+ ...commonRecovery,
161
+ ],
162
+ requiresEnvironmentDerivedIds: false,
163
+ });
164
+ const dataDuplicateFlag = Object.freeze({
165
+ id: "data-duplicate-flag",
166
+ title: "字段唯一性防重打标(8.6.26 实证唯一可靠路径):预置 Create 规则挂 BIZ_ACTION 自调二开 markDuplicates(规则图内 QUERY→UPDATE 打标会静默空转)",
167
+ status: "offline-valid",
168
+ tool: "rule.save",
169
+ args: {
170
+ environment,
171
+ schemaCode: "wms_material",
172
+ ruleCode: "Create",
173
+ name: "数据新增",
174
+ nodeGraph: {
175
+ nodes: [
176
+ { nodeCode: "Start", nodeName: "开始", nodeType: "START" },
177
+ { 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: [] } },
178
+ { nodeCode: "Create", nodeName: "新增数据", nodeType: "SYSTEM_CREATE", isSystemNode: true },
179
+ { nodeCode: "End", nodeName: "结束", nodeType: "END" },
180
+ ],
181
+ routes: [
182
+ { preNode: "Start", postNode: "A1", routeCondition: true },
183
+ { preNode: "A1", postNode: "Create", routeCondition: true },
184
+ { preNode: "Create", postNode: "End", routeCondition: true },
185
+ ],
186
+ },
187
+ publish: true,
188
+ },
189
+ preconditions: [
190
+ "目标模型已有「重复标记」字段(如 `mat_dup_flag` 短文本,表单+列表可见)。",
191
+ "需求方已接受**打标降级**(平台无硬拦原语;BRANCH 不能按 QUERY 空结果分支)。",
192
+ "二开侧已有 markDuplicates 接口:查同键既有行(代码+号码一级、仅键二级覆盖无代码场景)→ UPDATE 给**既有那条**打标(宁可误报原则)。注册为 RESTFUL 服务并自调(oauth_password + 方法参数 X-LowCode-Corpid CONST——见 bizservice-self-call-corp)。",
193
+ "**改写的是预置 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)。",
194
+ ],
195
+ conditionalRequirements: [
196
+ "**为什么不是规则图内 QUERY→UPDATE**:8.6.26 UPDATE 节点 inputParam 只认 `_input`(触发行),`_QN_output` 引用被静默空转、UPDATE 始终命中新建那条——与打标既有行的需求相反(r4-fin 两次真机复现 2026-10-09)。规则图内打标不可靠,必须走二开。",
197
+ "预置规则改写要点:SYSTEM_CREATE 身份节点必须保留(nodeCode/nodeName/isSystemNode 平台预置身份,错配会让触发行写错);BIZ_ACTION 插在 START 之后、SYSTEM_CREATE 之前。",
198
+ "写完 `rule.enable`;**验证**:data.save 同键两条 → 既有那条带标记、新提交那条干净;无代码场景用仅键匹配覆盖。",
199
+ "掩码/占位值(RELEVANCE-全 0、全 0 id、整值 <…>、未替换 ${var})会被 rule-graph-masked-or-placeholder-value 出门拦截——从回读复制常量时取真实值。",
200
+ ],
201
+ priorToolReferences: [
202
+ { tool: "model.inspect", args: { environment, schemaCode: "<model>" }, purpose: "确认标记字段已存在且已发布" },
203
+ { tool: "rule.get", args: { environment, schemaCode: "<model>", ruleCode: "Create", full: true }, purpose: "改写预置规则前先取现图,整图带回(保留 SYSTEM_CREATE 身份)" },
204
+ ],
205
+ dryRun: { supported: true, command: `omc call rule.save --env ${environment} --dry-run --args '<recipe.args>'`, notes: "dry-run 校验预置身份/BIZ_ACTION 映射/掩码值,零 wire" },
206
+ expectedReadback: { performed: { errcode: 0 }, readback: { rulePresent: true, enabled: true, bizRuleType: "DATA_OP" }, next: ["同键双跑:既有行打标、新行干净;唯一键不触发"] },
207
+ refusalRecovery: [
208
+ { code: "rule-graph-preset-system-node-mismatch", action: "预置 Create 必须保留 SYSTEM_CREATE 身份节点(不能换成别的系统节点类型)" },
209
+ { code: "rule-graph-masked-or-placeholder-value", action: "常量值是掩码/占位(全 0 corpId、<…>、${var})——用真实值(corpId 取环境事实/方法参数 position:2 方案)" },
210
+ { code: "runtime-50000-corpId-blank", action: "补方法参数 X-LowCode-Corpid(见 bizservice-self-call-corp);HTTP 200 不代表成功,看响应体 errcode" },
211
+ ...commonRecovery,
212
+ ],
213
+ requiresEnvironmentDerivedIds: false,
214
+ });
215
+ const ruleCrossModelCreate = Object.freeze({
216
+ id: "rule-cross-model-create",
217
+ title: "跨模型新增:查/取源后向另一模型 CREATE(含 QUERY 扇出,真机验证)",
218
+ status: "offline-valid",
219
+ tool: "rule.save",
220
+ args: {
221
+ environment,
222
+ schemaCode: model,
223
+ ruleCode: "<Create|Update|或 TIMING ruleCode>",
224
+ name: "跨模型生成",
225
+ nodeGraph: {
226
+ nodes: [
227
+ { nodeCode: "Start", nodeName: "开始", nodeType: "START" },
228
+ { nodeCode: "SYSTEM_CREATE_OR_UPDATE", nodeName: "触发", nodeType: "<SYSTEM_CREATE 或 SYSTEM_UPDATE>", isSystemNode: true },
229
+ { nodeCode: "C1", nodeName: "新增到目标", nodeType: "CREATE", targetObjectCode: "<targetModel 且 ≠ 承载本规则的模型>", inputParam: "_input", originatorValueType: "DYNAMIC", originatorValue: "<触发字段>", dataActions: [{ targetDataItem: "<目标字段>", ruleValueType: "DYNAMIC", value: "", ruleActionType: "EQUALS", currentDataItemValue: "<来源字段>" }] },
230
+ { nodeCode: "End", nodeName: "结束", nodeType: "END" },
231
+ ],
232
+ routes: [
233
+ { preNode: "Start", postNode: "SYSTEM_CREATE_OR_UPDATE", routeCondition: true },
234
+ { preNode: "SYSTEM_CREATE_OR_UPDATE", postNode: "C1", routeCondition: true },
235
+ { preNode: "C1", postNode: "End", routeCondition: true },
236
+ ],
237
+ },
238
+ publish: true,
239
+ },
240
+ preconditions: [
241
+ "目标模型与目标字段已存在并发布;触发字段存在",
242
+ "**`targetObjectCode` 不能等于承载该 Create 触发规则的同一模型**(否则自触发死循环 → 运行时 550009「业务规则执行异常」;平台有时 publish 直接 550018)",
243
+ ],
244
+ conditionalRequirements: [
245
+ "`originatorValueType` **必填**(DYNAMIC=触发字段 code / FIXED=完整用户对象 JSON 串)+ `originatorValue`;`dataActions` **必填**(目标字段映射);缺任一 → rule.save 真机 50000。",
246
+ "**`dataActions` 要把下游记录所需的业务字段逐条映射**(编号/日期/金额/状态/备注…),**别只挂一个关联 id**——否则下游记录其余字段全 null(真机 2026-10-08)。",
247
+ "**`originatorValueType:\"DYNAMIC\"` 的 `originatorValue` 必须指向人员字段**(STAFF_SELECTOR 等);指向文本/编号字段会致发起人错误或为空。",
248
+ "「当前时间」不是规则的 value 类型:`ruleValueType:\"SYSTEM\"` 不会填 now(真机 2026-10-08 落 null)。要让下游日期=生成时刻,把该**日期字段的默认值**设为「当前时间」(平台 `{value:\"current\"}`),或走二开;纯规则映射填不出 now。",
249
+ "上游是 QUERY 时 `inputParam=\"_<QUERY节点code>_output\"`(QUERY 查 N 行→CREATE 逐条生成即 N→N 扇出,真机验证支持,同/跨模型均可);否则 `\"_input\"`。",
250
+ "`QUERY` 节点只能查**主模型行**:查子表 schema(`parent.child` 或子 schema 名)空条件返回 0 行、带条件 550018(真机 2026-10-08)——逐子表明细扇出才需要二开。",
251
+ "跨模型 CREATE/UPDATE 真机验证支持(2026-09-28 / 2026-10-08);承载规则的触发时机要对(Create 在提交时触发、Update 只在 data.update 触发、TIMING 定时触发)。",
252
+ ],
253
+ priorToolReferences: [
254
+ { tool: "field.list", args: { environment, schemaCode: "<targetModel>", published: true }, purpose: "确认目标模型/字段已发布" },
255
+ ],
256
+ dryRun: { supported: true, command: `omc call rule.save --env ${environment} --dry-run --args '<recipe.args>'`, notes: "dry-run 跑图 lint(不会暴露跨模型 CREATE 的 50000,需真机 save 复核)" },
257
+ expectedReadback: { readback: { published: true, next: "rule.enable;构造数据触发后回读目标模型行数" } },
258
+ refusalRecovery: [
259
+ { code: "wire-business", action: "rule.save 真机 50000:多为 CREATE 缺 originatorValueType/dataActions,或 QUERY 查了子表 schema;按 conditionalRequirements 修正" },
260
+ { code: "rule-graph-self-trigger-create-loop", action: "CREATE 目标不能是承载本 Create 规则的同一模型;改为写别的模型或换触发载体" },
261
+ { code: "rule-graph-validation-failed", action: "按 validation 修正节点/路线/inputParam 后重新 dry-run" },
262
+ ...commonRecovery,
263
+ ],
264
+ requiresEnvironmentDerivedIds: false,
265
+ });
266
+ export const BUSINESSRULE_METADATA = Object.freeze({
267
+ "rule.save": Object.freeze({ recipes: [ruleGraph, ruleBranchGraph, ruleTimingTask, dataDuplicateFlag, ruleCrossModelCreate], relatedTools: ["field.list", "rule.get", "rule.enable"] }),
268
+ });
269
+ //# sourceMappingURL=businessrule.js.map
@@ -0,0 +1,215 @@
1
+ import { environment, model, commonRecovery } from "./shared.js";
2
+ const submitCrossFieldCheck = Object.freeze({
3
+ id: "submit-cross-field-check",
4
+ title: "表单提交校验(跨字段比较):datarule type1 + checkType:2 + conditions(右值结构值)+ prompt——在线 JS onValidate 之前的配置正解",
5
+ status: "offline-valid",
6
+ tool: "datarule.create",
7
+ args: {
8
+ environment,
9
+ schemaCode: "svc_repair",
10
+ propertyCode: "rep_plan_finish_time",
11
+ dataRuleType: 1,
12
+ checkType: 2,
13
+ name: "完成时间不早于报告时间",
14
+ options: {
15
+ conditions: `{rep_plan_finish_time} 大于等于 {"valueType":"currentModel","value":"rep_report_time"}`,
16
+ prompt: "预计完成时间不能早于报告时间",
17
+ },
18
+ },
19
+ preconditions: [
20
+ "需求是**提交时跨字段校验并弹提示拦截**(A 字段与 B 字段的比较)——这有**配置正解**,先于在线 JS onValidate 考虑(在线 JS 只在该协议确实表达不了时用)。",
21
+ "比较符支持:等于/不等于/大于/大于等于/小于/小于等于/包含/属于/介于 等(中文或符号)。",
22
+ ],
23
+ conditionalRequirements: [
24
+ `**右值必须用结构值** {"valueType":"currentModel","value":"<字段code>"}——裸 {字段code} 右值平台存储成功但**永不解析、规则静默不生效**(2026-09-24 真机;工具 fail-closed 拦截 + 读回 shapeWarning 标记存量雷)。`,
25
+ "**prompt 存纯文本**:门户对顶层 options.prompt 原样显示,URL 编码会弹 %E5.. 乱码(真机 equipmaint_workorder 案)。",
26
+ "单字段正则/长度类校验用 checkType:1/4/5(REGEX/MAX_LENGTH/EMPTY_ROW)+ 对应 options 键。",
27
+ ],
28
+ priorToolReferences: [{ tool: "field.list", args: { environment, schemaCode: "<模型>" }, purpose: "确认两个比较字段已发布" }],
29
+ dryRun: { supported: true, command: `omc call datarule.create --env ${environment} --dry-run --args '<recipe.args>'`, notes: "dry-run 过协议校验(裸右值/prompt 编码当场拒),零 wire" },
30
+ expectedReadback: { performed: { errcode: 0 }, readback: { rulePresent: true, shapeWarning: null }, next: ["门户提交错误日期 → 弹 prompt 文案并拦截(前台点验)"] },
31
+ refusalRecovery: [
32
+ { code: "datarule-submit-cross-field-bare", action: `右值换成结构值 {"valueType":"currentModel","value":"字段code"}` },
33
+ { code: "datarule-submit-prompt-encoded", action: "prompt 存纯文本,别 URL 编码" },
34
+ ...commonRecovery,
35
+ ],
36
+ requiresEnvironmentDerivedIds: false,
37
+ });
38
+ const dataruleCalcRule = Object.freeze({
39
+ id: "field-compute-rule",
40
+ title: "字段计算规则:普通字段 + 计算规则(type2,勿用「计算公式」字段类型)",
41
+ status: "offline-valid",
42
+ tool: "datarule.create",
43
+ args: {
44
+ environment,
45
+ schemaCode: model,
46
+ propertyCode: "c",
47
+ dataRuleType: 2,
48
+ options: {
49
+ shortTextStitch: JSON.stringify({
50
+ formula: "{a}+{b}",
51
+ editorText: "<field-omc0001>a</field-omc0001>+<field-omc0001>b</field-omc0001>",
52
+ editorMark: "field-omc0001",
53
+ }),
54
+ },
55
+ name: "c=a+b",
56
+ },
57
+ preconditions: [
58
+ "**计算类需求:建普通业务字段(数值优先,文本亦可)+ 本计算规则;不要用「计算公式」字段类型(FORMULA)**——平台约定是普通字段顺带配计算规则(真机 2026-10-08 复核:普通数值字段 + type2 计算规则,门户算出 7.00,设计器「计算规则」弹窗显示公式)。",
59
+ "参与计算的字段已发布",
60
+ ],
61
+ conditionalRequirements: [
62
+ "`options.shortTextStitch` 的值必须是 JSON 对象字符串,**三个键都要给全**:`{\"formula\":\"{a}+{b}\",\"editorText\":\"<field-omc0001>a</field-omc0001>+<field-omc0001>b</field-omc0001>\",\"editorMark\":\"field-omc0001\"}`。",
63
+ "**`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`)。",
64
+ "**`formula` 引用语法**(真机 production + 设计器核验):同类字段裸 `{字段code}`(`{a}+{b}`);**子表列用 `{子表字段code.子列code}` 点号全限定**(`{prop_repair_detail.qty}*{prop_repair_detail.unit_price}`);子表聚合 `SUM({子表.子列})`;支持 `IF/CASE/VALUE/AND/=、字符串字面量、运算符空格`。",
65
+ "**计算由门户前端执行、API 不回填**:写测试数据带预算值;正确性由门户表单填表确认。设计器「计算规则」弹窗读取 `bizdatarule/list`(本规则即写入该表)。",
66
+ ],
67
+ priorToolReferences: [
68
+ { tool: "field.list", args: { environment, schemaCode: model, published: true }, purpose: "确认目标字段与来源字段已发布、且目标字段是普通字段" },
69
+ ],
70
+ dryRun: { supported: true, command: `omc call datarule.create --env ${environment} --dry-run --args '<recipe.args>'`, notes: "dry-run 校验类型/公式协议,不写入" },
71
+ expectedReadback: { readback: { dataRuleType: 2, options: { shortTextStitch: "<含 formula 的 JSON 串>" } } },
72
+ refusalRecovery: [
73
+ { code: "datarule-options-invalid", action: "shortTextStitch 必须是含 formula 的 JSON 对象串;同类字段用裸 {字段},子表列用 {子表.子列}" },
74
+ { code: "wire-business", action: "305003 假死循环常见于未发布应用或坏 formula 形状;确认应用已发布、formula 为合法引用" },
75
+ ...commonRecovery,
76
+ ],
77
+ requiresEnvironmentDerivedIds: false,
78
+ });
79
+ const dataruleSubtableSum = Object.freeze({
80
+ id: "field-compute-subtable-sum",
81
+ title: "字段计算规则:子表列求和回主表(普通字段 + type2,SUM({子表.子列}))",
82
+ status: "offline-valid",
83
+ tool: "datarule.create",
84
+ args: {
85
+ environment,
86
+ schemaCode: model,
87
+ propertyCode: "total",
88
+ dataRuleType: 2,
89
+ options: {
90
+ shortTextStitch: JSON.stringify({
91
+ formula: "SUM({<childSchemaCode>.amount})",
92
+ editorText: "SUM(<field-omc0001><childSchemaCode>.amount</field-omc0001>)",
93
+ editorMark: "field-omc0001",
94
+ }),
95
+ },
96
+ name: "合计=明细金额之和",
97
+ },
98
+ preconditions: [
99
+ "**目标 `total` 是普通数值字段(不是「计算公式」字段)**;`<childSchemaCode>` 是子表字段的 code(如 `prop_repair_detail` / `Sub_item_information_table`),`amount` 是其子列 code",
100
+ "子表与其列已发布",
101
+ ],
102
+ conditionalRequirements: [
103
+ "子表列引用用 `{子表字段code.子列code}` 点号全限定;求和 `SUM({子表.子列})`(真机核验:`SUM({prop_repair_detail.amount})`)。",
104
+ "**给全 `editorText`/`editorMark`**:`editorMark=\"field-omc0001\"`,`editorText=\"SUM(<field-omc0001>prop_repair_detail.amount</field-omc0001>)\"`;缺则设计器标红。",
105
+ "计算由门户前端执行、API 不回填(写测试数据带预算值;门户填表确认)。",
106
+ ],
107
+ priorToolReferences: [
108
+ { tool: "field.list", args: { environment, schemaCode: model, published: true }, purpose: "确认主表 total、子表字段及其子列已发布" },
109
+ ],
110
+ dryRun: { supported: true, command: `omc call datarule.create --env ${environment} --dry-run --args '<recipe.args>'`, notes: "dry-run 校验类型/公式协议,不写入" },
111
+ expectedReadback: { readback: { dataRuleType: 2, options: { shortTextStitch: "<含 SUM({子表.子列}) 的 JSON 串>" } } },
112
+ refusalRecovery: [{ code: "datarule-options-invalid", action: "子表列引用必须是 {子表字段code.子列code};SUM 包裹其外" }, ...commonRecovery],
113
+ requiresEnvironmentDerivedIds: false,
114
+ });
115
+ const dataruleChildColumn = Object.freeze({
116
+ id: "field-compute-child-column",
117
+ title: "字段计算规则:子表列同行运算(普通字段 + type2,如 金额=数量×单价)",
118
+ status: "offline-valid",
119
+ tool: "datarule.create",
120
+ args: {
121
+ environment,
122
+ schemaCode: "<childSchemaCode e.g. <model>_items>",
123
+ propertyCode: "amount",
124
+ dataRuleType: 2,
125
+ options: {
126
+ shortTextStitch: JSON.stringify({
127
+ formula: "{<childSchemaCode>.qty}*{<childSchemaCode>.price}",
128
+ editorText: "<field-omc0001><childSchemaCode>.qty</field-omc0001>*<field-omc0001><childSchemaCode>.price</field-omc0001>",
129
+ editorMark: "field-omc0001",
130
+ }),
131
+ },
132
+ name: "金额=数量×单价",
133
+ },
134
+ preconditions: ["**子表列 `amount` 是普通数值字段(不是「计算公式」字段)**,且是子表内的列(schemaCode=子表字段 code)"],
135
+ conditionalRequirements: [
136
+ "**子表内列引用用 `{子表schemaCode.子列code}` 点号全限定**(真机 2026-10-08 设计器产出:`{prop_repair_detail.qty}*{prop_repair_detail.unit_price}`);不要写裸 `{qty}`。",
137
+ "**给全 `editorText`/`editorMark`**:`editorMark=\"field-omc0001\"`,每个引用包成 `<field-omc0001>子表.子列</field-omc0001>`(**标签内裸 token,不带花括号**——编辑器会自己套 `{}`);缺或带括号则设计器标红/已失效。",
138
+ "计算由门户前端执行、API 不回填(门户填表确认)。",
139
+ ],
140
+ priorToolReferences: [{ tool: "field.list", args: { environment, schemaCode: "<childSchemaCode>" }, purpose: "确认子表及其列已发布" }],
141
+ dryRun: { supported: true, command: `omc call datarule.create --env ${environment} --dry-run --args '<recipe.args>'`, notes: "dry-run 校验类型/公式协议,不写入" },
142
+ expectedReadback: { readback: { dataRuleType: 2, options: { shortTextStitch: "<含 {子表.子列} 点号引用的 JSON 串>" } } },
143
+ refusalRecovery: [{ code: "datarule-options-invalid", action: "子表内列用 {子表schemaCode.子列code} 点号;不要裸 {子列code}" }, ...commonRecovery],
144
+ requiresEnvironmentDerivedIds: false,
145
+ });
146
+ const dataruleRequired = Object.freeze({
147
+ id: "datarule-conditional-required",
148
+ title: "金额超过阈值时说明必填(字段级 type4)",
149
+ status: "offline-valid",
150
+ tool: "datarule.create",
151
+ args: {
152
+ environment,
153
+ schemaCode: model,
154
+ propertyCode: "leaveReason",
155
+ dataRuleType: 4,
156
+ options: { requiredFormula: '{leave_amount} 大于 {"valueType":"custom","value":5000}' },
157
+ name: "金额超过阈值说明必填",
158
+ sortKey: 1,
159
+ },
160
+ preconditions: ["目标字段已发布;amount 字段 code 已确认"],
161
+ conditionalRequirements: ["条件串必须是 {field} OP {JSON valueType} 三段单空格;多条件用 &&/||"],
162
+ priorToolReferences: [{ tool: "field.list", args: { environment, schemaCode: model, published: true }, purpose: "确认 leave_amount 与 leave_reason 已发布" }],
163
+ dryRun: { supported: true, command: `omc call datarule.create --env ${environment} --dry-run --args '<recipe.args>'`, notes: "dry-run 校验类型/条件协议,不写入" },
164
+ expectedReadback: { readback: { dataRuleType: 4, options: { requiredFormula: "<protocol>" } } },
165
+ refusalRecovery: [{ code: "datarule-options-invalid", action: "不要写 `{field}>5000` 或裸 true;复制 recipe 的三段协议" }, ...commonRecovery],
166
+ requiresEnvironmentDerivedIds: false,
167
+ });
168
+ const dataruleReadonly = Object.freeze({
169
+ id: "datarule-conditional-readonly",
170
+ title: "字段级条件只读(type5,恒真条件式)",
171
+ status: "offline-valid",
172
+ tool: "datarule.create",
173
+ args: {
174
+ environment,
175
+ schemaCode: model,
176
+ propertyCode: "approvalLevel",
177
+ dataRuleType: 5,
178
+ options: { readonlyCondition: '{approvalLevel} 大于等于 {"valueType":"custom","value":0}' },
179
+ name: "审批级别只读",
180
+ sortKey: 1,
181
+ },
182
+ preconditions: ["目标字段已发布"],
183
+ conditionalRequirements: ["无条件 readonly 的 functionType 形状尚未被 live 复现;使用已验证恒真条件式"],
184
+ priorToolReferences: [{ tool: "field.list", args: { environment, schemaCode: model, published: true }, purpose: "确认 approvalLevel 已发布" }],
185
+ dryRun: { supported: true, command: `omc call datarule.create --env ${environment} --dry-run --args '<recipe.args>'`, notes: "dry-run 校验条件协议,不写入" },
186
+ expectedReadback: { readback: { dataRuleType: 5, options: { readonlyCondition: "<protocol>" } } },
187
+ refusalRecovery: [{ code: "datarule-options-invalid", action: "readonlyCondition 必须是三段条件式;`readonlyCondition:true` 会破坏门户渲染" }, ...commonRecovery],
188
+ requiresEnvironmentDerivedIds: false,
189
+ });
190
+ const dataruleDisplay = Object.freeze({
191
+ id: "datarule-conditional-display",
192
+ title: "字段级显示条件(type3)",
193
+ status: "offline-valid",
194
+ tool: "datarule.create",
195
+ args: {
196
+ environment,
197
+ schemaCode: model,
198
+ propertyCode: "approvalLevel",
199
+ dataRuleType: 3,
200
+ options: { displayFormula: '{isUrgent} == {"valueType":"custom","value":true}' },
201
+ name: "加急时显示审批级别",
202
+ sortKey: 1,
203
+ },
204
+ preconditions: ["目标字段已发布"],
205
+ conditionalRequirements: ["displayFormula 与 requiredFormula/readonlyCondition 共用三段条件协议"],
206
+ priorToolReferences: [{ tool: "field.list", args: { environment, schemaCode: model, published: true }, purpose: "确认字段 codes" }],
207
+ dryRun: { supported: true, command: `omc call datarule.create --env ${environment} --dry-run --args '<recipe.args>'`, notes: "dry-run 校验条件协议,不写入" },
208
+ expectedReadback: { readback: { dataRuleType: 3, options: { displayFormula: "<protocol>" } } },
209
+ refusalRecovery: [{ code: "datarule-options-invalid", action: "复制三段条件协议;不要传裸公式字符串" }, ...commonRecovery],
210
+ requiresEnvironmentDerivedIds: false,
211
+ });
212
+ export const DATARULE_METADATA = Object.freeze({
213
+ "datarule.create": Object.freeze({ recipes: [submitCrossFieldCheck, dataruleCalcRule, dataruleSubtableSum, dataruleChildColumn, dataruleRequired, dataruleReadonly, dataruleDisplay], relatedTools: ["field.list", "datarule.list"] }),
214
+ });
215
+ //# sourceMappingURL=datarule.js.map
@@ -0,0 +1,42 @@
1
+ import { environment, commonRecovery } from "./shared.js";
2
+ const dictionaryKeyValue = Object.freeze({
3
+ id: "dictionary-key-value",
4
+ title: "建一个键值字典并用其 id 作字段下拉来源",
5
+ status: "offline-valid",
6
+ tool: "dictionary.save",
7
+ args: {
8
+ environment,
9
+ code: "visitType",
10
+ name: "回访方式",
11
+ dictionaryType: 0,
12
+ records: [
13
+ { code: "phone", name: "电话" },
14
+ { code: "visit", name: "上门" },
15
+ { code: "online", name: "线上" },
16
+ ],
17
+ },
18
+ preconditions: ["字典 code 尚未存在(存在须改传 id 更新);dictionaryType 0=KEY_VALUE / 1=TREE"],
19
+ conditionalRequirements: [
20
+ "**字段下拉绑定字典时 `checkedDictionary` 必须填字典 id(不是 name)**——前端 a-select 的 option value 就是 id;填 name 运行时会返回『字典已被禁用,无数据』、下拉空白(真机 ticket 02/34)。拿到 id:`dictionary.get`/`dictionary.names` 读回。",
21
+ "TREE 字典的 record 可带 `parentId`;给既有字典加记录也走 `dictionary.save`(带 id + 完整 records)。",
22
+ "字段侧用 `field.create` 的 `options.optionsSet = {optionsType:'dictionary',dictionary:{checkedDictionary:'<id>',checkedDictionaryCode:'<code>',useDictionariesData:[{code,name,id}]}}`;`choices` 是「手写选项」的另一条路,不要与字典混用。",
23
+ ],
24
+ priorToolReferences: [
25
+ { tool: "dictionary.names", args: { environment }, purpose: "查 code/name 冲突并读回既有 id(字典多时结果会溢成 artifact;只需 code/name,用 `artifact get <id> --keys` 或 --jq,别整份取)" },
26
+ { tool: "dictionary.get", args: { environment, code: "visitType" }, purpose: "按 code 精确查存在性(小结果,优于 dictionary.names 全量)" },
27
+ ],
28
+ dryRun: { supported: true, command: `omc call dictionary.save --env ${environment} --dry-run --args '<recipe.args>'`, notes: "dry-run 预检 code 冲突/形状,不写" },
29
+ expectedReadback: { performed: { errcode: 0 }, readback: { present: true, dictionary: { id: "<dictionary.id>", code: "visitType", records: ["phone", "visit", "online"] } }, next: ["field.create --example dictionary-backed-single-select(用回读的 id)"] },
30
+ refusalRecovery: [
31
+ { code: "dictionary-create-duplicate", action: "code 已存在:从 dictionary.names 取 id 后改传 id 做更新" },
32
+ { code: "wire-business", action: "平台 `errcode -1 系统中已经存在相同名称的字典` = **字典名称全局唯一**(code 唯一还不够)——换一个 name(真机 2026-09-25:新 code + 旧 name 仍被拒)" },
33
+ { code: "capability-out-of-coverage", action: "确认 dictionaryType 0/1 与 records[{code,name}] 形状" },
34
+ ...commonRecovery,
35
+ ],
36
+ requiresEnvironmentDerivedIds: true,
37
+ prerequisiteData: { source: "dictionary.get / dictionary.names", fields: ["dictionary.id"] },
38
+ });
39
+ export const DICTIONARY_METADATA = Object.freeze({
40
+ "dictionary.save": Object.freeze({ recipes: [dictionaryKeyValue], relatedTools: ["dictionary.names", "dictionary.records", "field.create"] }),
41
+ });
42
+ //# sourceMappingURL=dictionary.js.map