@stalfh233/omc-cli 0.6.6 → 0.6.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.
- package/README.md +2 -1
- package/dist/approval-flow.js +5 -3
- package/dist/bizservice-config.js +26 -10
- package/dist/call.js +47 -0
- package/dist/capability-overview.js +47 -24
- package/dist/cli-contract.js +90 -17
- package/dist/cli-output.js +41 -2
- package/dist/cli-stdout.js +74 -0
- package/dist/cli.js +162 -54
- package/dist/command-transport.js +8 -1
- package/dist/config-store.js +17 -2
- package/dist/discovery.js +62 -3
- package/dist/field-families.js +35 -6
- package/dist/goal-routes.js +118 -11
- package/dist/human-summary.js +11 -0
- package/dist/lanes/app-contract.js +12 -5
- package/dist/lanes/app.js +221 -134
- package/dist/lanes/apply-changes.js +2 -1
- package/dist/lanes/b2-registration-contract.js +3 -3
- package/dist/lanes/b2-registration.js +890 -693
- package/dist/lanes/businessrule.js +75 -51
- package/dist/lanes/custom-page.js +166 -115
- package/dist/lanes/datarule-contract.js +2 -2
- package/dist/lanes/datarule.js +238 -154
- package/dist/lanes/dictionary.js +253 -110
- package/dist/lanes/environment.js +57 -9
- package/dist/lanes/field.js +160 -107
- package/dist/lanes/folder-contract.js +7 -3
- package/dist/lanes/folder.js +249 -118
- package/dist/lanes/form-contract.js +1 -1
- package/dist/lanes/form.js +188 -64
- package/dist/lanes/list-view.js +405 -369
- package/dist/lanes/meta.js +31 -6
- package/dist/lanes/model-contract.js +1 -1
- package/dist/lanes/model.js +610 -512
- package/dist/lanes/online-js-contract.js +1 -1
- package/dist/lanes/online-js.js +160 -146
- package/dist/lanes/organization.js +905 -640
- package/dist/lanes/programming.js +274 -136
- package/dist/lanes/report-contract.js +2 -2
- package/dist/lanes/report.js +354 -198
- package/dist/lanes/rule-lifecycle.js +294 -140
- package/dist/lanes/runtime-data-contract.js +4 -4
- package/dist/lanes/runtime-data.js +869 -745
- package/dist/lanes/workflow-contract.js +3 -3
- package/dist/lanes/workflow.js +732 -605
- package/dist/model-scaffold.js +26 -5
- package/dist/path-projection.js +196 -14
- package/dist/plan.js +25 -0
- package/dist/programming-workflow.js +55 -3
- package/dist/read-shapes.js +146 -0
- package/dist/read.js +40 -9
- package/dist/recipes/businessrule.js +74 -24
- package/dist/recipes/dictionary.js +7 -4
- package/dist/recipes/form.js +46 -0
- package/dist/recipes/runtime-data.js +3 -2
- package/dist/refusal-guidance.js +29 -5
- package/dist/rule-support.js +14 -1
- package/dist/rule-when.js +2 -2
- package/dist/tool-types.js +9 -3
- package/dist/version-family-gaps.js +101 -0
- package/dist/write.js +2 -1
- package/package.json +6 -4
- package/skills/omc-glossary.md +13 -0
- package/skills/omc-refusals.md +13 -5
- package/skills/omc-start-here.md +2 -2
- package/skills/omc-tool-selection.md +2 -1
- package/skills/omc-write-hazards.md +5 -2
package/dist/read.js
CHANGED
|
@@ -116,6 +116,11 @@ export async function governedRead(ctx, tool, args, execute, options = {}) {
|
|
|
116
116
|
* 真实数据在 detailFile 指向的 artifact 里,根键与内联一致(list=items、load=data)。
|
|
117
117
|
*/
|
|
118
118
|
function externalizedShapePlaceholders(result, dropped) {
|
|
119
|
+
// r26b:什么都没折叠时不要宣称 `truncated:true`——`model.list` 曾出现
|
|
120
|
+
// `truncated:true` + `truncatedKeys:[]`(models 已按 code|name 内联),
|
|
121
|
+
// 自相矛盾的标记会让调用方以为还得去 artifact 取。
|
|
122
|
+
if (dropped.length === 0)
|
|
123
|
+
return {};
|
|
119
124
|
const placeholders = { truncatedKeys: [...dropped], truncated: true };
|
|
120
125
|
for (const key of dropped)
|
|
121
126
|
placeholders[key] = Array.isArray(result[key]) ? [] : null;
|
|
@@ -127,14 +132,11 @@ function droppedShapeKeys(result, payload) {
|
|
|
127
132
|
function compactReadback(result) {
|
|
128
133
|
const compact = {};
|
|
129
134
|
for (const [key, value] of Object.entries(result)) {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
compact[`${key}Count`] = value.length;
|
|
136
|
-
}
|
|
137
|
-
else if (CODE_NAME_LIST_KEYS.has(key) && Array.isArray(value)) {
|
|
135
|
+
// r26b 读回体积审计(2026-11-05):分支顺序曾让 CODE_NAME_LIST_KEYS 对 `models`
|
|
136
|
+
// 变成死代码——`models` 同时在 BULKY_READ_KEYS 里,而那个分支排在最前,于是
|
|
137
|
+
// 永远只回 `modelsCount:30` + `models:[]`,调用方拿不到任何 schemaCode,被迫
|
|
138
|
+
// 再花一次 `--jq`/artifact(997 B + 一整轮)。保形分支必须先于折叠分支。
|
|
139
|
+
if (CODE_NAME_LIST_KEYS.has(key) && Array.isArray(value)) {
|
|
138
140
|
const pairs = value.map(codeNameOfEntry).filter((entry) => entry !== undefined);
|
|
139
141
|
// 40 对上限(~1-2KB):更大的清单回退 count+artifact,保冷启动可扫又不失控
|
|
140
142
|
if (pairs.length <= 40)
|
|
@@ -151,8 +153,37 @@ function compactReadback(result) {
|
|
|
151
153
|
compact[key] = value.map(codeOfEntry).filter((entry) => entry !== undefined);
|
|
152
154
|
compact[`${key}Count`] = value.length;
|
|
153
155
|
}
|
|
156
|
+
else if (ACTIVITY_LIST_KEYS.has(key) && Array.isArray(value)) {
|
|
157
|
+
compact[key] = value.map(activityOfEntry).filter((entry) => entry !== undefined);
|
|
158
|
+
compact[`${key}Count`] = value.length;
|
|
159
|
+
}
|
|
160
|
+
else if (BULKY_READ_KEYS.has(key)) {
|
|
161
|
+
compact[`${key}Count`] = Array.isArray(value) ? value.length : value && typeof value === "object" ? Object.keys(value).length : 0;
|
|
162
|
+
}
|
|
154
163
|
else {
|
|
155
|
-
|
|
164
|
+
// r28a 真机(2026-11-05):`summarizeValue` 对数组给 `{count}`、对对象给
|
|
165
|
+
// `{keys}`,两套占位形状本身没有「数据不在我这里」的语义——`count: 8`
|
|
166
|
+
// 读起来像"已经给你 8 条"。同一个逻辑清单(dictionary.names 回数组、
|
|
167
|
+
// dictionary.list 回分页对象)于是给出两种自相矛盾的占位。
|
|
168
|
+
// 这里统一补 `omitted:true`(真实数据在 detailArtifactId),不动
|
|
169
|
+
// summarizeValue 本身——它还服务于 dry-run 的 argsSummary,那里"摘要"
|
|
170
|
+
// 才是正确语义。
|
|
171
|
+
// r28a 的 omitted 补丁有副作用(本轮全量门抓到):`summarizeValue` 对**任何**
|
|
172
|
+
// 对象都给 `{keys}`,连只有 3 个键的小对象也不例外——`model.list` 的
|
|
173
|
+
// `applicationResolution: {code, source, applicationCount}` 由此变成
|
|
174
|
+
// `{keys, sha256, omitted}`,而它正是「零命中只代表这一个应用」的唯一证据。
|
|
175
|
+
// 规则:小对象(≤4 键且 ≤200B)原样内联——折叠它是纯损失,不是省字节。
|
|
176
|
+
const small = value !== null && typeof value === "object" && !Array.isArray(value)
|
|
177
|
+
&& Object.keys(value).length <= 4
|
|
178
|
+
&& Buffer.byteLength(JSON.stringify(value), "utf8") <= 200;
|
|
179
|
+
if (small) {
|
|
180
|
+
compact[key] = value;
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
const summarized = summarizeValue(value);
|
|
184
|
+
compact[key] = summarized !== null && typeof summarized === "object" && !Array.isArray(summarized)
|
|
185
|
+
? { ...summarized, omitted: true }
|
|
186
|
+
: summarized;
|
|
156
187
|
}
|
|
157
188
|
}
|
|
158
189
|
return compact;
|
|
@@ -12,21 +12,20 @@ const ruleGraph = Object.freeze({
|
|
|
12
12
|
nodeGraph: {
|
|
13
13
|
nodes: [
|
|
14
14
|
{ nodeCode: "Start", nodeType: "START" },
|
|
15
|
-
{ nodeCode: "Create", nodeName: "新增数据", nodeType: "SYSTEM_CREATE", isSystemNode: true },
|
|
16
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" }] },
|
|
17
16
|
{ nodeCode: "End", nodeType: "END" },
|
|
18
17
|
],
|
|
19
18
|
routes: [
|
|
20
|
-
{ preNode: "Start", postNode: "
|
|
21
|
-
{ preNode: "Create", postNode: "UPDATE_1" },
|
|
19
|
+
{ preNode: "Start", postNode: "UPDATE_1" },
|
|
22
20
|
{ preNode: "UPDATE_1", postNode: "End" },
|
|
23
21
|
],
|
|
24
22
|
},
|
|
25
23
|
publish: true,
|
|
26
24
|
},
|
|
27
|
-
preconditions: ["模型和 target field 已发布;Create
|
|
25
|
+
preconditions: ["模型和 target field 已发布;Create 是平台预置触发规则;目标字段可由规则动作写入;**系统节点(SYSTEM_CREATE)由工具注入,不要手写**"],
|
|
28
26
|
conditionalRequirements: [
|
|
29
|
-
"
|
|
27
|
+
"**系统节点由工具注入,不要手写 `SYSTEM_CREATE/SYSTEM_UPDATE/SYSTEM_DELETE`**——调用方传入会被 `rule-graph-system-node-tool-owned` 忽略后按 ruleCode 重注;手拼只写 Start→UPDATE→End。",
|
|
28
|
+
"**自更新 UPDATE 必须排在 SYSTEM_CREATE 之后**(工具注入后最终形 Start→SYSTEM_CREATE→UPDATE→End)——排在前面时规则能存能启用,运行时 `UpdateServiceExecutor` 查不到触发行(日志「查找范围不存在数据,不再做修改数据动作」),字段静默不写回。真机对照 2026-10-20:UPDATE 在 SYSTEM_CREATE 前 → 值不变;挪到其后 → 值写入成功。注意与 ASSIGN/BIZ_ACTION 相反,那两类要排在系统节点之前。",
|
|
30
29
|
"BIZ_ACTION 的 outputMappings 必须在 SYSTEM_CREATE/SYSTEM_UPDATE 前被消费;自定义 ruleCode 不会被数据事件自动分发",
|
|
31
30
|
],
|
|
32
31
|
priorToolReferences: [
|
|
@@ -59,15 +58,13 @@ const ruleBranchGraph = Object.freeze({
|
|
|
59
58
|
nodeGraph: {
|
|
60
59
|
nodes: [
|
|
61
60
|
{ nodeCode: "Start", nodeType: "START" },
|
|
62
|
-
{ nodeCode: "Create", nodeName: "新增数据", nodeType: "SYSTEM_CREATE", isSystemNode: true },
|
|
63
61
|
{ nodeCode: "Branch_1", nodeType: "BRANCH", sourceInputMethod: "PRE_OUT", exitConditionGroups: [[{ currentSchemaDataItem: "amount", ruleDataConditionType: "GT", ruleValueType: "FIXED", value: 10000 }]] },
|
|
64
62
|
{ nodeCode: "UPDATE_BIG", nodeType: "UPDATE", targetObjectCode: model, dataActions: [{ targetDataItem: "level", ruleValueType: "FIXED", value: "大额", ruleActionType: "EQUALS" }] },
|
|
65
63
|
{ nodeCode: "UPDATE_NORMAL", nodeType: "UPDATE", targetObjectCode: model, dataActions: [{ targetDataItem: "level", ruleValueType: "FIXED", value: "普通", ruleActionType: "EQUALS" }] },
|
|
66
64
|
{ nodeCode: "End", nodeType: "END" },
|
|
67
65
|
],
|
|
68
66
|
routes: [
|
|
69
|
-
{ preNode: "Start", postNode: "
|
|
70
|
-
{ preNode: "Create", postNode: "Branch_1" },
|
|
67
|
+
{ preNode: "Start", postNode: "Branch_1" },
|
|
71
68
|
{ preNode: "Branch_1", postNode: "UPDATE_BIG" },
|
|
72
69
|
{ preNode: "Branch_1", postNode: "UPDATE_NORMAL", routeCondition: false },
|
|
73
70
|
{ preNode: "UPDATE_BIG", postNode: "End" },
|
|
@@ -76,12 +73,13 @@ const ruleBranchGraph = Object.freeze({
|
|
|
76
73
|
},
|
|
77
74
|
publish: true,
|
|
78
75
|
},
|
|
79
|
-
preconditions: ["模型和 target field(level/amount)已发布;Create
|
|
76
|
+
preconditions: ["模型和 target field(level/amount)已发布;Create 是平台预置触发规则;目标字段可由规则动作写入;**系统节点由工具注入,不要手写**"],
|
|
80
77
|
conditionalRequirements: [
|
|
81
|
-
"
|
|
78
|
+
"**系统节点由工具注入,不要手写 `SYSTEM_CREATE/SYSTEM_UPDATE/SYSTEM_DELETE`**——调用方传入会被 `rule-graph-system-node-tool-owned` 忽略,再按 ruleCode 重新注入并接到 START 之后。手拼时写 `Start→Branch_1→…` 即可,工具会补上 Create 边。",
|
|
79
|
+
"**系统节点顺序:BRANCH 必须排在 `SYSTEM_CREATE` 之后**——BRANCH 直接接在 START 后会运行时静默不生效、动作不回写(工具 fail closed)。注意这与 BIZ_ACTION 回写场景相反(那里系统节点要在动作之后)——本 recipe 是分支场景。",
|
|
82
80
|
"**出口条件叶只 4 个键** `{currentSchemaDataItem(左值字段),ruleDataConditionType(操作符),ruleValueType,value}`;用 UI 键 `targetSchemaDataItem`+`ruleConditionType` 会 save+publish 过但运行时 50000(工具 fail closed `rule-graph-branch-condition-keys`)。",
|
|
83
81
|
"**下列键可省、工具自动补**(省 token;真机验证极简图 1170B vs 全显式 1690B,−31%):每个节点的 `nodeName`;BRANCH 的 `inputParam:\"_input\"`/`judgeMethod:\"DEFAULT\"`;自更新 UPDATE(`targetObjectCode`=本模型)的 `inputParam:\"_input\"`/`filterCondition`(身份匹配);非兜底路由的 `routeCondition:true`/`matchType`。仅 BRANCH 的 `sourceInputMethod` 与兜底 else 的 `routeCondition:false` 需显式。**跨模型写(`targetObjectCode`≠规则模型)已真机验证可用**(1.1.25,2026-10-21:FIXED id / 文本字段 / 单跳关联字段三种 DYNAMIC 来源均命中)——前提是 ① 承载规则真的触发(Create 在提交时、Update 在 data.update、或绑流程事件);② **目标行必须已提交(COMPLETED)**,DRAFT 行不参与查询(日志「查找范围不存在数据」)。点号路径按子表解析:`关联字段.id` 与两跳 `a.b` 不可用。",
|
|
84
|
-
"预置规则的系统节点类型必须与 ruleCode 匹配(Create→SYSTEM_CREATE
|
|
82
|
+
"预置规则的系统节点类型必须与 ruleCode 匹配(Create→SYSTEM_CREATE);工具按 ruleCode 注入,手写错类型会被忽略后重注。",
|
|
85
83
|
],
|
|
86
84
|
priorToolReferences: [
|
|
87
85
|
{ tool: "field.list", args: { environment, schemaCode: model, published: true }, purpose: "确认 amount/level 已发布" },
|
|
@@ -100,10 +98,63 @@ const ruleBranchGraph = Object.freeze({
|
|
|
100
98
|
requiresEnvironmentDerivedIds: false,
|
|
101
99
|
prerequisiteData: { source: "field.list(published:true)", note: "ruleCode=Create 为平台预置代码" },
|
|
102
100
|
});
|
|
101
|
+
/**
|
|
102
|
+
* r28c 真机(2026-11-05):探针要写「金额 > 5000 则 approvedAmount = amount*0.8」,
|
|
103
|
+
* 但 `rule.save --example business-rule-branch-conditional` 的两条分支全是 FIXED,
|
|
104
|
+
* EXPRESSION 的写法只在 `--full` 的描述里提了一句「ruleValueType ∈ …/EXPRESSION/…」,
|
|
105
|
+
* 没有一条可运行样例 → 探针只能猜 `value` 是字符串,猜错。
|
|
106
|
+
*
|
|
107
|
+
* 这条把「固定条件 + 公式动作」的最小形状钉死:EXPRESSION 的 `value` 是
|
|
108
|
+
* **对象** `{formula:"<表达式>"}`,不是字符串。
|
|
109
|
+
*/
|
|
110
|
+
const ruleConditionFormula = Object.freeze({
|
|
111
|
+
id: "rule-condition-formula-action",
|
|
112
|
+
title: "条件成立时按公式赋值(EXPRESSION action)",
|
|
113
|
+
// r28c:alias 要**窄**。"计算规则" 曾把 compute composer 的
|
|
114
|
+
// 「字段计算规则/子表求和回填」路由抢走(next-first-answer 的 G2 断言
|
|
115
|
+
// 由 needs-input 退化成 ready)——那是字段级 compute 的地盘,不是业务规则。
|
|
116
|
+
aliases: ["按比例赋值", "公式赋值", "百分比", "乘以", "按公式赋值"],
|
|
117
|
+
status: "requires-prior-readback",
|
|
118
|
+
tool: "rule.save",
|
|
119
|
+
args: {
|
|
120
|
+
environment,
|
|
121
|
+
schemaCode: model,
|
|
122
|
+
ruleCode: "Create",
|
|
123
|
+
name: "大额按比例赋值",
|
|
124
|
+
nodeGraph: {
|
|
125
|
+
nodes: [
|
|
126
|
+
{ nodeCode: "Start", nodeType: "START" },
|
|
127
|
+
{ nodeCode: "Branch_1", nodeType: "BRANCH", sourceInputMethod: "PRE_OUT", exitConditionGroups: [[{ currentSchemaDataItem: "amount", ruleDataConditionType: "GT", ruleValueType: "FIXED", value: 5000 }]] },
|
|
128
|
+
// **关键**:EXPRESSION 的 value 是对象 {formula},不是字符串。
|
|
129
|
+
{ nodeCode: "UPDATE_FORMULA", nodeType: "UPDATE", targetObjectCode: model, dataActions: [{ targetDataItem: "approvedAmount", ruleValueType: "EXPRESSION", ruleActionType: "EQUALS", value: { formula: "{amount}*0.8" } }] },
|
|
130
|
+
// 只引用字段(不运算)用 DYNAMIC + currentDataItemValue。
|
|
131
|
+
{ nodeCode: "UPDATE_FULL", nodeType: "UPDATE", targetObjectCode: model, dataActions: [{ targetDataItem: "approvedAmount", ruleValueType: "DYNAMIC", ruleActionType: "EQUALS", value: "", currentDataItemValue: "amount" }] },
|
|
132
|
+
{ nodeCode: "End", nodeType: "END" },
|
|
133
|
+
],
|
|
134
|
+
routes: [
|
|
135
|
+
{ preNode: "Start", postNode: "Branch_1" },
|
|
136
|
+
{ preNode: "Branch_1", postNode: "UPDATE_FORMULA" },
|
|
137
|
+
{ preNode: "Branch_1", postNode: "UPDATE_FULL", routeCondition: false },
|
|
138
|
+
{ preNode: "UPDATE_FORMULA", postNode: "End" },
|
|
139
|
+
{ preNode: "UPDATE_FULL", postNode: "End" },
|
|
140
|
+
],
|
|
141
|
+
},
|
|
142
|
+
publish: true,
|
|
143
|
+
},
|
|
144
|
+
preconditions: ["amount/approvedAmount 已发布;Create 是平台预置触发规则,**系统节点(SYSTEM_CREATE)由工具注入,不要手写**"],
|
|
145
|
+
priorToolReferences: [{ tool: "field.list", args: { environment, schemaCode: model, published: true }, purpose: "确认 amount/approvedAmount 已发布" }],
|
|
146
|
+
conditionalRequirements: [
|
|
147
|
+
"EXPRESSION 的 `value` 必须是对象 `{formula:\"{amount}*0.8\"}`(字符串会被 dry-run 以 `rule-graph-expression-value-shape` 拒绝);纯字段引用用 DYNAMIC + currentDataItemValue,value 留空串",
|
|
148
|
+
"**系统节点由工具注入,不要手写 `SYSTEM_CREATE/SYSTEM_UPDATE`**——传了会被 `rule-graph-system-node-tool-owned` 忽略后重新注入;手拼图只写 Start→Branch→UPDATE→End",
|
|
149
|
+
],
|
|
150
|
+
dryRun: { supported: true, command: `omc call rule.save --env ${environment} --dry-run --args '<recipe.args>'`, notes: "dry-run 的图 lint 会拦公式形状错误" },
|
|
151
|
+
expectedReadback: { performed: { published: true }, readback: { next: "rule.enable 后造两条数据验 approvedAmount" } },
|
|
152
|
+
refusalRecovery: [{ code: "rule-graph-expression-value-shape", action: "把 value 从字符串改成 {formula:\"...\"} 对象" }, ...commonRecovery],
|
|
153
|
+
requiresEnvironmentDerivedIds: false,
|
|
154
|
+
});
|
|
103
155
|
const ruleTimingTask = Object.freeze({
|
|
104
156
|
id: "rule-timing-task",
|
|
105
|
-
title: "定时任务:每天/周期触发规则(TIMING_TASK,SCHEDULER 节点,配置能力)",
|
|
106
|
-
status: "offline-valid",
|
|
157
|
+
title: "定时任务:每天/周期触发规则(TIMING_TASK,SCHEDULER 节点,配置能力)", status: "offline-valid",
|
|
107
158
|
tool: "rule.save",
|
|
108
159
|
args: {
|
|
109
160
|
environment,
|
|
@@ -263,13 +314,11 @@ const dataDuplicateFlag = Object.freeze({
|
|
|
263
314
|
nodes: [
|
|
264
315
|
{ nodeCode: "Start", nodeName: "开始", nodeType: "START" },
|
|
265
316
|
{ 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: [] } },
|
|
266
|
-
{ nodeCode: "Create", nodeName: "新增数据", nodeType: "SYSTEM_CREATE", isSystemNode: true },
|
|
267
317
|
{ nodeCode: "End", nodeName: "结束", nodeType: "END" },
|
|
268
318
|
],
|
|
269
319
|
routes: [
|
|
270
320
|
{ preNode: "Start", postNode: "A1", routeCondition: true },
|
|
271
|
-
{ preNode: "A1", postNode: "
|
|
272
|
-
{ preNode: "Create", postNode: "End", routeCondition: true },
|
|
321
|
+
{ preNode: "A1", postNode: "End", routeCondition: true },
|
|
273
322
|
],
|
|
274
323
|
},
|
|
275
324
|
publish: true,
|
|
@@ -281,8 +330,9 @@ const dataDuplicateFlag = Object.freeze({
|
|
|
281
330
|
"**改写的是预置 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)。",
|
|
282
331
|
],
|
|
283
332
|
conditionalRequirements: [
|
|
333
|
+
"**系统节点由工具注入,不要手写 `SYSTEM_CREATE`**——本 recipe 只写 Start→BIZ_ACTION→End;工具按 Create 规则注入 SYSTEM_CREATE 并排在 BIZ_ACTION 之后(ASSIGN/BIZ_ACTION 必须在系统节点前)。",
|
|
284
334
|
"**为什么不是规则图内 QUERY→UPDATE**:8.6.26 UPDATE 节点 inputParam 只认 `_input`(触发行),`_QN_output` 引用被静默空转、UPDATE 始终命中新建那条——与打标既有行的需求相反(r4-fin 两次真机复现 2026-10-09)。规则图内打标不可靠,必须走二开。",
|
|
285
|
-
"
|
|
335
|
+
"预置规则改写要点:最终生效图必须含与 ruleCode 匹配的 SYSTEM_CREATE(工具注入);BIZ_ACTION 插在 START 之后、SYSTEM_CREATE 之前。",
|
|
286
336
|
"写完 `rule.enable`;**验证**:data.save 同键两条 → 既有那条带标记、新提交那条干净;无代码场景用仅键匹配覆盖。",
|
|
287
337
|
"掩码/占位值(RELEVANCE-全 0、全 0 id、整值 <…>、未替换 ${var})会被 rule-graph-masked-or-placeholder-value 出门拦截——从回读复制常量时取真实值。",
|
|
288
338
|
],
|
|
@@ -313,13 +363,11 @@ const ruleCrossModelCreate = Object.freeze({
|
|
|
313
363
|
nodeGraph: {
|
|
314
364
|
nodes: [
|
|
315
365
|
{ nodeCode: "Start", nodeName: "开始", nodeType: "START" },
|
|
316
|
-
{ nodeCode: "SYSTEM_CREATE_OR_UPDATE", nodeName: "触发", nodeType: "<SYSTEM_CREATE 或 SYSTEM_UPDATE>", isSystemNode: true },
|
|
317
366
|
{ nodeCode: "C1", nodeName: "新增到目标", nodeType: "CREATE", targetObjectCode: "<targetModel 且 ≠ 承载本规则的模型>", inputParam: "_input", originatorValueType: "DYNAMIC", originatorValue: "<触发字段>", dataActions: [{ targetDataItem: "<目标字段>", ruleValueType: "DYNAMIC", value: "", ruleActionType: "EQUALS", currentDataItemValue: "<来源字段>" }] },
|
|
318
367
|
{ nodeCode: "End", nodeName: "结束", nodeType: "END" },
|
|
319
368
|
],
|
|
320
369
|
routes: [
|
|
321
|
-
{ preNode: "Start", postNode: "
|
|
322
|
-
{ preNode: "SYSTEM_CREATE_OR_UPDATE", postNode: "C1", routeCondition: true },
|
|
370
|
+
{ preNode: "Start", postNode: "C1", routeCondition: true },
|
|
323
371
|
{ preNode: "C1", postNode: "End", routeCondition: true },
|
|
324
372
|
],
|
|
325
373
|
},
|
|
@@ -328,8 +376,10 @@ const ruleCrossModelCreate = Object.freeze({
|
|
|
328
376
|
preconditions: [
|
|
329
377
|
"目标模型与目标字段已存在并发布;触发字段存在",
|
|
330
378
|
"**`targetObjectCode` 不能等于承载该 Create 触发规则的同一模型**(否则自触发死循环 → 运行时 550009「业务规则执行异常」;平台有时 publish 直接 550018)",
|
|
379
|
+
"**系统节点由工具按 ruleCode 注入**(Create→SYSTEM_CREATE / Update→SYSTEM_UPDATE);手拼不要写 SYSTEM_*",
|
|
331
380
|
],
|
|
332
381
|
conditionalRequirements: [
|
|
382
|
+
"**系统节点由工具注入,不要手写 `SYSTEM_CREATE/SYSTEM_UPDATE`**——本 recipe 只写 Start→CREATE→End;工具按承载规则的 ruleCode 注入对应系统节点。",
|
|
333
383
|
"`originatorValueType` **必填**(DYNAMIC=触发字段 code / FIXED=完整用户对象 JSON 串)+ `originatorValue`;`dataActions` **必填**(目标字段映射);缺任一 → rule.save 真机 50000。",
|
|
334
384
|
"**`dataActions` 要把下游记录所需的业务字段逐条映射**(编号/日期/金额/状态/备注…),**别只挂一个关联 id**——否则下游记录其余字段全 null(真机 2026-10-08)。",
|
|
335
385
|
"**`originatorValueType:\"DYNAMIC\"` 的 `originatorValue` 必须指向人员字段**(STAFF_SELECTOR 等);指向文本/编号字段会致发起人错误或为空。",
|
|
@@ -374,13 +424,11 @@ const ruleBizactionInvokeService = Object.freeze({
|
|
|
374
424
|
nodes: [
|
|
375
425
|
{ nodeCode: "Start", nodeName: "开始", nodeType: "START" },
|
|
376
426
|
{ nodeCode: "A1", nodeName: "调用服务并回写", nodeType: "BIZ_ACTION", inputParam: "_input", methodMapping: { schemaCode: model, serviceCode: "<svc>", serviceMethodCode: "<method>", inputMappings: [{ serviceMethodParameterCode: "<in_code>", codeType: 1, bizCode: "<src_field>", required: true }], outputMappings: [{ serviceMethodParameterCode: "<out_code>", codeType: 1, bizCode: "<dst_field>" }] } },
|
|
377
|
-
{ nodeCode: "Create", nodeName: "新增数据", nodeType: "SYSTEM_CREATE", isSystemNode: true },
|
|
378
427
|
{ nodeCode: "End", nodeName: "结束", nodeType: "END" },
|
|
379
428
|
],
|
|
380
429
|
routes: [
|
|
381
430
|
{ preNode: "Start", postNode: "A1", routeCondition: true },
|
|
382
|
-
{ preNode: "A1", postNode: "
|
|
383
|
-
{ preNode: "Create", postNode: "End", routeCondition: true },
|
|
431
|
+
{ preNode: "A1", postNode: "End", routeCondition: true },
|
|
384
432
|
],
|
|
385
433
|
},
|
|
386
434
|
publish: true,
|
|
@@ -388,8 +436,10 @@ const ruleBizactionInvokeService = Object.freeze({
|
|
|
388
436
|
preconditions: [
|
|
389
437
|
"模型与来源/目标字段已发布;服务与方法已注册(service.register 的 readback.method 直接给参数 code)",
|
|
390
438
|
"方法出参类型(bizPropertyType)与目标字段类型匹配",
|
|
439
|
+
"**系统节点由工具注入,不要手写 SYSTEM_CREATE**",
|
|
391
440
|
],
|
|
392
441
|
conditionalRequirements: [
|
|
442
|
+
"**系统节点由工具注入**——本 recipe 只写 Start→BIZ_ACTION→End;工具注入 SYSTEM_CREATE 并排在 BIZ_ACTION 之后(ASSIGN/BIZ_ACTION 必须在系统节点前)。",
|
|
393
443
|
"**出参回写三前提(r28b 8 组实验定案,缺一出参恒 null 且节点日志仍 successful)**:① 响应 JSON 根级平铺出参键(信封嵌套如 {data:{...}} 静默不回写)② 挂预置 Create/Update 规则上下文(自定义规则/rule.run 不回写业务字段)③ 用 data.submit/data.update 触发(data.save 存草稿不触发)。回写是否成功只认 data.load 读字段,不认调用日志。",
|
|
394
444
|
"codeType:1 = 映射模型字段(bizCode=字段 code);codeType:0 = 常量(bizCode=常量值);serviceMethodParameterCode 必须与方法声明的参数 code 逐字一致",
|
|
395
445
|
"**接口响应必须在 JSON 根级**返回与出参 code 同名的键——嵌套在信封里(如 {data:{level}})会静默映射不上(p7 实测;r28b 8 组实验全因信封端点失败,换平铺端点后 4 组一次全过)",
|
|
@@ -410,6 +460,6 @@ const ruleBizactionInvokeService = Object.freeze({
|
|
|
410
460
|
requiresEnvironmentDerivedIds: true,
|
|
411
461
|
});
|
|
412
462
|
export const BUSINESSRULE_METADATA = Object.freeze({
|
|
413
|
-
"rule.save": Object.freeze({ recipes: [ruleGraph, ruleBranchGraph, ruleTimingTask, ruleTimingBizService, dataDuplicateFlag, ruleCrossModelCreate, ruleBizactionInvokeService], relatedTools: ["field.list", "rule.get", "rule.enable", "service.get"] }),
|
|
463
|
+
"rule.save": Object.freeze({ recipes: [ruleGraph, ruleBranchGraph, ruleConditionFormula, ruleTimingTask, ruleTimingBizService, dataDuplicateFlag, ruleCrossModelCreate, ruleBizactionInvokeService], relatedTools: ["field.list", "rule.get", "rule.enable", "service.get"] }),
|
|
414
464
|
});
|
|
415
465
|
//# sourceMappingURL=businessrule.js.map
|
|
@@ -22,13 +22,16 @@ const dictionaryKeyValue = Object.freeze({
|
|
|
22
22
|
"字段侧用 `field.create` 的 `options.optionsSet = {optionsType:'dictionary',dictionary:{checkedDictionary:'<id>',checkedDictionaryCode:'<code>',useDictionariesData:[{code,name,id}]}}`;`choices` 是「手写选项」的另一条路,不要与字典混用。",
|
|
23
23
|
],
|
|
24
24
|
priorToolReferences: [
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
// r28a F5:names 不是全量权威面(平台 getDictionaryIdAndNames 覆盖可能 < searchDictionary);
|
|
26
|
+
// 全量/防冲突用 list;names 只作轻量摘要。溢成 artifact 是 list/大结果读门行为,不是 names 特有承诺。
|
|
27
|
+
{ tool: "dictionary.list", args: { environment, page: 1, size: 200 }, purpose: "全量清单 + 防 code/name 冲突(权威面;看顶层 total 或 dictionaries.totalElements;大结果可能溢成 artifact,用 --jq 'dictionaries.content[].code' / artifact get 定向取)" },
|
|
28
|
+
{ tool: "dictionary.get", args: { environment, code: "visitType" }, purpose: "按 code 精确查存在性与 id(小结果;确认单个 code 优于扫清单)" },
|
|
29
|
+
{ tool: "dictionary.names", args: { environment }, purpose: "轻量 id/code/name 摘要(平台端点覆盖可能小于 list;回执带 total+coverageNote——不能当全集)" },
|
|
27
30
|
],
|
|
28
31
|
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)"] },
|
|
32
|
+
expectedReadback: { performed: { errcode: 0 }, readback: { present: true, dictionary: { id: "<dictionary.id>", code: "visitType" }, records: { totalElements: 3, content: [{ code: "phone" }, { code: "visit" }, { code: "online" }] }, orderNote: "records 顺序不保证与提交一致;顺序请用 sortKey(回读已按 sortKey 升序,同值再按 code)" }, next: ["field.create --example dictionary-backed-single-select(用回读的 id)"] },
|
|
30
33
|
refusalRecovery: [
|
|
31
|
-
{ code: "dictionary-create-duplicate", action: "code 已存在:从 dictionary.
|
|
34
|
+
{ code: "dictionary-create-duplicate", action: "code 已存在:从 dictionary.list / dictionary.get 取 id 后改传 id 做更新" },
|
|
32
35
|
{ code: "wire-business", action: "平台 `errcode -1 系统中已经存在相同名称的字典` = **字典名称全局唯一**(code 唯一还不够)——换一个 name(真机 2026-09-25:新 code + 旧 name 仍被拒)" },
|
|
33
36
|
{ code: "capability-out-of-coverage", action: "确认 dictionaryType 0/1 与 records[{code,name}] 形状" },
|
|
34
37
|
...commonRecovery,
|
package/dist/recipes/form.js
CHANGED
|
@@ -13,7 +13,53 @@ const configureFormRecipe = Object.freeze({
|
|
|
13
13
|
refusalRecovery: [{ code: "capability-out-of-coverage", action: "先发布字段再发表单" }, ...commonRecovery],
|
|
14
14
|
requiresEnvironmentDerivedIds: false,
|
|
15
15
|
});
|
|
16
|
+
/**
|
|
17
|
+
* r28e F3(真机复现 2026-11-05):`describe form.draft --param draftViewJson` 只有
|
|
18
|
+
* 一句散文「一行 N 个 code=N 列」,探针据此猜了 3 轮才写出可发布的布局。
|
|
19
|
+
* 这里给一个**最小可抄**的两列 + 末行整宽样例;形状由 `form-layout.ts`
|
|
20
|
+
* 的 validateFormLayout 把关(rows 必须是二维字符串数组、attrs 与 rows 覆盖一致、
|
|
21
|
+
* 控件 options 必须是对象而不是 JSON 字符串)。
|
|
22
|
+
*/
|
|
23
|
+
const formDraftTwoColumn = Object.freeze({
|
|
24
|
+
id: "two-column-layout",
|
|
25
|
+
title: "表单两列布局 + 末行整宽(draftViewJson/draftAttributesJson 最小形状)",
|
|
26
|
+
// 教训(r28c+r28e 各踩一次):alias 必须**窄**。「自定义表单布局」
|
|
27
|
+
// 「表单布局形状」会把整包 ladder(model→form→listview→businessrule→onlinejs)
|
|
28
|
+
// 抢成单配方命中,于是 next 不再给 intent 主路径与方法论节。
|
|
29
|
+
aliases: ["两列布局", "两列表单", "整宽一行"],
|
|
30
|
+
status: "offline-valid",
|
|
31
|
+
tool: "form.draft",
|
|
32
|
+
args: {
|
|
33
|
+
environment,
|
|
34
|
+
schemaCode: model,
|
|
35
|
+
// 行分组 = 列分组:第一行两个控件(两列),第二行一个控件(整宽)。
|
|
36
|
+
draftViewJson: '[[\"visitNo\",\"customer\"],[\"remark\"]]',
|
|
37
|
+
draftAttributesJson: JSON.stringify({
|
|
38
|
+
visitNo: { key: "visitNo", type: 1, options: { name: "拜访单号", span: 12 } },
|
|
39
|
+
customer: { key: "customer", type: 1, options: { name: "客户", span: 12 } },
|
|
40
|
+
remark: { key: "remark", type: 1, options: { name: "备注", span: 24 } },
|
|
41
|
+
}, null, 0),
|
|
42
|
+
},
|
|
43
|
+
preconditions: ["visitNo/customer/remark 三个字段已 field.publish(布局只能引用已发布字段)", "两段 JSON 都是**字符串**(tool 参数是 string,不是对象)"],
|
|
44
|
+
conditionalRequirements: [
|
|
45
|
+
"**列数由行分组决定,不由 span 决定**:一行放几个 code 就是几列;span 只控制控件在行内的 24 栅格宽度,且必须与行分组自洽(两列 span:12+12,整宽 span:24)",
|
|
46
|
+
"draftAttributesJson 的每个控件的 `options` 必须是**对象**,不能是 JSON 字符串(否则运行时写 options.name_i18n 抛错、整页空白)",
|
|
47
|
+
"rows 与 attrs 的 code 集合必须一致,否则 `layout_coverage_mismatch`",
|
|
48
|
+
"改列数就改行分组、重写 attrs 的 span —— 不要只改 span",
|
|
49
|
+
],
|
|
50
|
+
priorToolReferences: [{ tool: "field.list", args: { environment, schemaCode: model, published: true }, purpose: "确认要摆的字段已发布" }],
|
|
51
|
+
dryRun: { supported: true, command: `omc call form.draft --env ${environment} --dry-run --args '<recipe.args>'`, notes: "dry-run 会跑 validateFormLayout,形状不合规当场拒绝" },
|
|
52
|
+
expectedReadback: { performed: { hasDraft: true }, readback: { next: ["form.publish", "form.get full:true 验布局"] } },
|
|
53
|
+
refusalRecovery: [
|
|
54
|
+
{ code: "layout_coverage_mismatch", action: "把 draftViewJson 里出现的每个 code 都补进 draftAttributesJson(反之亦然)" },
|
|
55
|
+
{ code: "control_options_not_object", action: "把控件 options 从 JSON 字符串改成对象" },
|
|
56
|
+
{ code: "form-field-not-published", action: "先 field.publish 再排布局" },
|
|
57
|
+
...commonRecovery,
|
|
58
|
+
],
|
|
59
|
+
requiresEnvironmentDerivedIds: false,
|
|
60
|
+
});
|
|
16
61
|
export const FORM_METADATA = Object.freeze({
|
|
17
62
|
"configure_form": Object.freeze({ recipes: [configureFormRecipe], relatedTools: ["field.list", "field.update", "listview.create", "listview.get"] }),
|
|
63
|
+
"form.draft": Object.freeze({ recipes: [formDraftTwoColumn], relatedTools: ["field.list", "field.publish", "form.get", "form.publish"] }),
|
|
18
64
|
});
|
|
19
65
|
//# sourceMappingURL=form.js.map
|
|
@@ -38,7 +38,7 @@ const plainSubmit = Object.freeze({
|
|
|
38
38
|
{ code: "submit-biz-object-absent", action: "bizObjectId 在平台无行(304001)——回 data.save 重建草稿后再直提" },
|
|
39
39
|
{ code: "invalid-input", action: "带了 workItemId/workflowInstanceId 时必须同时带 bizObjectId(fail-closed)" },
|
|
40
40
|
],
|
|
41
|
-
notes: "
|
|
41
|
+
notes: "本配方曾教先 save 再 submit 两步——正确路径是无 id 一调(正式数据一律 data.submit)。Create/Update 规则只在 COMPLETED 分发(DomainServiceImpl:240-248);效果以 data.load 读回为准(参数名=bizObjectId,用提交读回的 objectId)。data.save 定位=草稿/暂存",
|
|
42
42
|
requiresEnvironmentDerivedIds: true,
|
|
43
43
|
});
|
|
44
44
|
const workflowSubmit = Object.freeze({
|
|
@@ -92,7 +92,8 @@ const dataDeleteSweep = Object.freeze({
|
|
|
92
92
|
tool: "data.delete",
|
|
93
93
|
args: { environment, schemaCode: model, bizObjectIds: ["<data.list 逐条 id>"] },
|
|
94
94
|
preconditions: [
|
|
95
|
-
"先 data.list 取全量 ids
|
|
95
|
+
"先 data.list 取全量 ids(`all:true` 才含 DRAFT;翻页取全),清理对象按 data.list 全量",
|
|
96
|
+
"**行数以 data.list total 为准,别按「N 提交=2N 行」外推**:带流程的 submit 原地推进同一行(r28b 实测 3→3);双行只在平台另起草稿行时出现(r30 的 5→10)",
|
|
96
97
|
],
|
|
97
98
|
conditionalRequirements: [
|
|
98
99
|
"参数名是 `bizObjectIds`(数组)——不是 ids/bizObjectId(单条删除另有其参)",
|
package/dist/refusal-guidance.js
CHANGED
|
@@ -113,17 +113,41 @@ const FIX_FIRST_GATES = Object.freeze({
|
|
|
113
113
|
"capability-out-of-coverage": "需求不在覆盖面内:这份拒绝就是答案——用 omc capabilities 核对运行时清单,不要换相近工具伪造实现",
|
|
114
114
|
"version-family-evidence-missing": "平台版本证据缺失,写被关闭:先 omc doctor / discover_environment 取版本证据,非 8.6/1.1.25 家族按边界处理",
|
|
115
115
|
});
|
|
116
|
-
/**
|
|
117
|
-
|
|
116
|
+
/**
|
|
117
|
+
* `tool-input-invalid` and friends: point at the refused **parameter**, not the
|
|
118
|
+
* whole tool. Whole-tool describe is 4 KB (`rule.save`) to 17 KB (`--full`);
|
|
119
|
+
* the failing path is already in `issues[].path`, so `--param <top key>` is
|
|
120
|
+
* 150–900 B and answers the actual question (HANDOFF §三.1-1).
|
|
121
|
+
*/
|
|
122
|
+
function inputInvalidGuidance(toolName, failedPath) {
|
|
118
123
|
if (!toolName)
|
|
119
124
|
return undefined;
|
|
125
|
+
const param = failedPath?.split(".")[0];
|
|
120
126
|
return {
|
|
121
127
|
whatWentWrong: "入参没过工具的 schema 校验(缺必填或类型不对)",
|
|
122
|
-
correctShape:
|
|
123
|
-
|
|
128
|
+
correctShape: param === undefined
|
|
129
|
+
? `必填参数与类型见 command 指向的 describe(firstCall 自带最小可跑形状);要可运行的一整份参数用 omc describe ${toolName} --example`
|
|
130
|
+
: `只看失败的那个参数:omc describe ${toolName} --param ${param}(含类型/枚举/说明);要可运行的一整份参数用 --example`,
|
|
131
|
+
command: param === undefined ? `omc describe ${toolName}` : `omc describe ${toolName} --param ${param}`,
|
|
124
132
|
doNot: "不要盲目重试——同参数必然同拒绝",
|
|
125
133
|
};
|
|
126
134
|
}
|
|
135
|
+
/** The first failed property path of a shape refusal, if the payload carries one. */
|
|
136
|
+
function firstIssuePath(payload) {
|
|
137
|
+
const issues = payload.issues;
|
|
138
|
+
if (!Array.isArray(issues))
|
|
139
|
+
return undefined;
|
|
140
|
+
for (const issue of issues) {
|
|
141
|
+
if (issue === null || typeof issue !== "object")
|
|
142
|
+
continue;
|
|
143
|
+
const path = issue.path;
|
|
144
|
+
if (typeof path === "string" && path.length > 0)
|
|
145
|
+
return path;
|
|
146
|
+
if (Array.isArray(path) && path.length > 0)
|
|
147
|
+
return path.map((segment) => String(segment)).join(".");
|
|
148
|
+
}
|
|
149
|
+
return undefined;
|
|
150
|
+
}
|
|
127
151
|
/**
|
|
128
152
|
* r19 p14 — the report BI boundary (`/dashboard/v1/*`). A deployment without
|
|
129
153
|
* the report service answers every BI call with a gateway 502 (live p9
|
|
@@ -177,7 +201,7 @@ export function refusalRecovery(blocker, payload, options = {}) {
|
|
|
177
201
|
if (fixFirst)
|
|
178
202
|
return { whatWentWrong: String(payload.message ?? blocker).slice(0, 160), fixFirst };
|
|
179
203
|
if (blocker === "tool-input-invalid")
|
|
180
|
-
return inputInvalidGuidance(options.toolName);
|
|
204
|
+
return inputInvalidGuidance(options.toolName, firstIssuePath(payload));
|
|
181
205
|
return undefined;
|
|
182
206
|
}
|
|
183
207
|
/**
|
package/dist/rule-support.js
CHANGED
|
@@ -959,6 +959,14 @@ export async function executeRuleSaveWrite(session, args) {
|
|
|
959
959
|
// readback therefore pins the CURRENT effective row id/version so callers
|
|
960
960
|
// never have to guess which row the designer now renders.
|
|
961
961
|
const versionNote = "同一 ruleCode 的更新会产生新规则记录 ID(旧 ID 仍可读、前端持旧 ID 显示旧图);readback.id 是当前生效行(rule.get 请用它,或以 rule.list 同编码且 latestPublished:true 的新 ID 为准)";
|
|
962
|
+
// r28j F14 live: rule.save on an already-enabled preset Create returned
|
|
963
|
+
// published:false + enabled:true, nextAction only talked about designer
|
|
964
|
+
// visibility — but data.submit still ran the OLD published graph (77 vs 99).
|
|
965
|
+
// Same "保存≠生效" theme: draft save never becomes the runtime graph until
|
|
966
|
+
// publish (+ enable when the DRAFT switch needs flipping).
|
|
967
|
+
const saveNotEffectiveNextAction = enabled
|
|
968
|
+
? "保存≠生效:本次只写了 DRAFT。规则虽 enabled:true,运行时仍跑上一版已发布图——请再 rule.enable(内部 publish + 翻 DRAFT 启用)或 rule.save 传 publish:true 后再 rule.enable,用 data.submit/data.load 核对新图副作用"
|
|
969
|
+
: "保存≠生效:本次只写了 DRAFT。rule.enable(businessrule/publish 出新版本 + 翻 DRAFT 启用开关)后规则才会分发;或 rule.save 传 publish:true 只发布(启用仍走 rule.enable)";
|
|
962
970
|
const currentRow = async () => wireRows((await wireCall(session, { path: ENDPOINTS.businessRuleListBySchemaCode, query: { schemaCode: args.schemaCode } })).data).find((item) => item.code === args.ruleCode);
|
|
963
971
|
if (args.publish === true) {
|
|
964
972
|
const row = await currentRow();
|
|
@@ -1029,6 +1037,11 @@ export async function executeRuleSaveWrite(session, args) {
|
|
|
1029
1037
|
rulePresent: true,
|
|
1030
1038
|
published: false,
|
|
1031
1039
|
enabled,
|
|
1040
|
+
// F14: enabled:true alongside published:false means the previous published
|
|
1041
|
+
// graph is still what data events run — not the draft just saved.
|
|
1042
|
+
effective: false,
|
|
1043
|
+
requiresPublishAction: "rule.enable",
|
|
1044
|
+
...(existing?.defaultRule === true || PLATFORM_TRIGGER_RULE_CODES.has(args.ruleCode) ? { presetRule: true } : {}),
|
|
1032
1045
|
...(lintWarnings.length > 0 ? { lintWarnings } : {}),
|
|
1033
1046
|
...(lintHazards.length > 0 ? { lintHazards } : {}),
|
|
1034
1047
|
customTrigger: customTriggerApplied,
|
|
@@ -1037,7 +1050,7 @@ export async function executeRuleSaveWrite(session, args) {
|
|
|
1037
1050
|
ruleVersion: effective?.ruleVersion,
|
|
1038
1051
|
latestPublished: effective?.latestPublished,
|
|
1039
1052
|
...(existing?.id ? { versionNote } : {}),
|
|
1040
|
-
nextAction:
|
|
1053
|
+
nextAction: saveNotEffectiveNextAction,
|
|
1041
1054
|
},
|
|
1042
1055
|
};
|
|
1043
1056
|
}
|
package/dist/rule-when.js
CHANGED
|
@@ -114,7 +114,7 @@ export async function cmdRuleWhen(args) {
|
|
|
114
114
|
const name = flag("--name");
|
|
115
115
|
const environment = flag("--env") ?? flag("--environment");
|
|
116
116
|
const apply = args.includes("--apply");
|
|
117
|
-
const usage = '用法:omc rule-when --schema <模型> --on create|update --when "字段=值" [--when ...可多个,AND] --then "set:字段=值|{来源字段}|{公式}" [--then ...] [--apply]\n
|
|
117
|
+
const usage = '用法:omc rule-when --schema <模型> --on create|update --when "字段=值" [--when ...可多个,AND] --then "set:字段=值|{来源字段}|{公式}" [--then ...] [--apply]\n触发时机只有 create|update(**没有 submit**;“提交时”用 create 或 update)。条件操作符:= != > < >= <= ~=(包含) !~(不包含)。默认 dry-run;--apply 保存并发布(rule.enable 仍需显式执行)。';
|
|
118
118
|
const invalid = (message) => {
|
|
119
119
|
writeEnvelope(envelope("error", {}, request, { code: "invalid-input", message: `${message}\n${usage}`, retryable: false }));
|
|
120
120
|
return CLI_EXIT_CODES.invalidInput;
|
|
@@ -122,7 +122,7 @@ export async function cmdRuleWhen(args) {
|
|
|
122
122
|
if (!schema)
|
|
123
123
|
return invalid("--schema 必填");
|
|
124
124
|
if (on !== "create" && on !== "update")
|
|
125
|
-
return invalid("--on 只支持 create|update");
|
|
125
|
+
return invalid("--on 只支持 create|update(没有 submit;提交后执行请用 create 或 update)");
|
|
126
126
|
let createArgs;
|
|
127
127
|
try {
|
|
128
128
|
createArgs = composeRuleWhenArgs({ schema, on: on, when, then, name, environment, apply });
|
package/dist/tool-types.js
CHANGED
|
@@ -213,14 +213,20 @@ export function parseToolInput(tool, input) {
|
|
|
213
213
|
if (guidance !== undefined)
|
|
214
214
|
return guidance;
|
|
215
215
|
const closest = closestSchemaKey(key, schemaKeys);
|
|
216
|
-
|
|
216
|
+
// 语言规则(HANDOFF §三.3-8):人读句子中文、机器字段/键名英文。issues[]
|
|
217
|
+
// 与人读 message 同源(B4),所以这里也是中文句子 + 英文键名。
|
|
218
|
+
return closest === undefined ? "未知键;本工具的合法键里没有相近的" : `未知键;最接近的合法键:${closest}`;
|
|
217
219
|
};
|
|
220
|
+
// 未知键的下一步应指向**正确的那个参数**,不是整份 describe(4–17 KB)。
|
|
221
|
+
// 有近邻时 `--param <nearKey>` 是 150–900 B,且直接回答「本来想写哪个键」。
|
|
222
|
+
const nearestKey = closestSchemaKey(unknown[0], schemaKeys);
|
|
223
|
+
const paramCommand = nearestKey !== undefined ? `omc describe ${tool.name} --param ${nearestKey}` : `omc describe ${tool.name}`;
|
|
218
224
|
throw new ToolInputError({
|
|
219
225
|
status: "error",
|
|
220
226
|
blocker: "tool-input-unknown-keys",
|
|
221
227
|
tool: tool.name,
|
|
222
|
-
message: `写面工具不接受未知键(会被静默丢弃):${unknown.join("、")}。${twoStep.length > 0 ? twoStep.join(" ") : `最接近的合法键:${suggestions.join(";")}
|
|
223
|
-
...(twoStep.length > 0 ? { nextCommand: `omc describe ${tool.name}` } : {}),
|
|
228
|
+
message: `写面工具不接受未知键(会被静默丢弃):${unknown.join("、")}。${twoStep.length > 0 ? twoStep.join(" ") : `最接近的合法键:${suggestions.join(";")}。参数表:${paramCommand}`}`,
|
|
229
|
+
...(twoStep.length > 0 ? { nextCommand: `omc describe ${tool.name}` } : { nextCommand: paramCommand }),
|
|
224
230
|
issues: unknown.map((key) => ({ path: [key], code: "unrecognized_key", message: issueHintFor(key) })),
|
|
225
231
|
});
|
|
226
232
|
}
|