@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
|
@@ -103,6 +103,9 @@ export async function executeRuleEnableWrite(session, args) {
|
|
|
103
103
|
if (!full)
|
|
104
104
|
throw new WireError("business", `rule ${args.ruleCode} 完整模型读回为空(businessrule/get id ${row.id}),拒绝用列表投影发布`, 302024);
|
|
105
105
|
let published;
|
|
106
|
+
// 8.1 族走 `businessrule/update` 下探时,平台**没有** DRAFT/PUBLISH 两面
|
|
107
|
+
// (没有 publish 端点就没有双面模型),下面的 DRAFT 交叉校验对它不适用。
|
|
108
|
+
let publishEndpointAbsent = false;
|
|
106
109
|
try {
|
|
107
110
|
published = await wireCall(session, { path: ENDPOINTS.businessRulePublish, method: "POST", body: { ...full, enabled: true, status: "ENABLED" } });
|
|
108
111
|
}
|
|
@@ -123,7 +126,7 @@ export async function executeRuleEnableWrite(session, args) {
|
|
|
123
126
|
// `businessrule/publish` 端点(HTTP 404)。已验证的等价路径是
|
|
124
127
|
// `businessrule/update` 携完整实体 + enabled:true —— 实体完整时一次调用
|
|
125
128
|
// 同时翻转 enabled 与 status(见 issue 复现脚本)。仅 404 下探,其余原样抛。
|
|
126
|
-
|
|
129
|
+
publishEndpointAbsent = error instanceof WireError && error.kind === "http-status" && error.diagnostic?.httpStatus === 404;
|
|
127
130
|
if (!publishEndpointAbsent)
|
|
128
131
|
throw error;
|
|
129
132
|
published = await wireCall(session, { path: ENDPOINTS.businessRuleUpdate, method: "POST", body: { ...full, enabled: true, status: "ENABLED" } });
|
|
@@ -144,160 +147,311 @@ export async function executeRuleEnableWrite(session, args) {
|
|
|
144
147
|
const after = wireRows(readback.data).find((item) => item.code === args.ruleCode);
|
|
145
148
|
if (after?.enabled !== true)
|
|
146
149
|
throw new ToolRefusalError({ status: "refused", blocker: "rule-enable-readback-failed", message: `rule ${args.ruleCode} 发布后未启用;enabled 必须为 true` });
|
|
147
|
-
|
|
150
|
+
// r28g F23/F27:list.enabled 是 PUBLISH 投影,运行时读 DRAFT。交叉读 get,
|
|
151
|
+
// 把 DRAFT 的 enabled 钉进 readback。硬门槛只认 draft.enabled===true(list
|
|
152
|
+
// 已闸过);status 列可能滞后(平台 quirk / fake 复现),标 statusLag 而不
|
|
153
|
+
// 因此 fail closed——否则会把「enabled 已翻、status 还 DISABLED」的合法态拒掉。
|
|
154
|
+
const draft = publishEndpointAbsent
|
|
155
|
+
? null
|
|
156
|
+
: (await wireCall(session, { path: ENDPOINTS.businessRuleGet, query: { id: String(row.id) } })).data;
|
|
157
|
+
const draftEnabled = draft === null ? null : draft.enabled === true;
|
|
158
|
+
const draftStatus = draft?.status == null ? null : String(draft.status);
|
|
159
|
+
// draftEnabled === null 表示"本版本族没有 DRAFT 面"(8.1 下探路径)——
|
|
160
|
+
// 此时以 list(唯一的面)为准,不做 DRAFT 闸。
|
|
161
|
+
if (draftEnabled === false) {
|
|
162
|
+
throw new ToolRefusalError({
|
|
163
|
+
status: "refused",
|
|
164
|
+
blocker: "rule-enable-not-effective",
|
|
165
|
+
schemaCode: args.schemaCode,
|
|
166
|
+
ruleCode: args.ruleCode,
|
|
167
|
+
message: `rule.enable 后 list 报 enabled=true,但 DRAFT 行仍 enabled=false(status=${draftStatus ?? "null"})。运行时 getExecutableBySchemaCodeAndCode 读的是 DRAFT;请重试 rule.enable,或 rule.get 核对后再 rule.run。`,
|
|
168
|
+
readback: { listEnabled: true, draftEnabled: false, draftStatus, executable: false },
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
const statusLag = draftStatus !== null && draftStatus !== "ENABLED";
|
|
172
|
+
return {
|
|
173
|
+
performed: { errcode: published.errcode, enableErrcode, ...(alreadyEnabled ? { alreadyEnabled: true } : {}) },
|
|
174
|
+
readback: {
|
|
175
|
+
enabled: true,
|
|
176
|
+
listEnabled: after?.enabled === true,
|
|
177
|
+
listStatus: after?.status ?? null,
|
|
178
|
+
draftEnabled,
|
|
179
|
+
draftStatus,
|
|
180
|
+
executable: true,
|
|
181
|
+
verified: true,
|
|
182
|
+
...(statusLag ? { statusLag: true, statusNote: "DRAFT status 列仍非 ENABLED(平台滞后常见);enabled=true 才是分发真相" } : {}),
|
|
183
|
+
source: "draft-get+list",
|
|
184
|
+
effectHint: "executable/verified 以 DRAFT get.enabled 为准;listEnabled 来自 PUBLISH 投影。rule.run 前若仍拒,再 rule.get 看 draftStatus",
|
|
185
|
+
},
|
|
186
|
+
};
|
|
148
187
|
}
|
|
149
188
|
export function registerRuleLifecycleLane(tools) {
|
|
150
|
-
writeTool(
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
189
|
+
writeTool({
|
|
190
|
+
tools,
|
|
191
|
+
name: "rule.enable",
|
|
192
|
+
lane: RULE_LIFECYCLE_LANE,
|
|
193
|
+
scope: RULE_LIFECYCLE_TOOLS["rule.enable"].scope,
|
|
194
|
+
description: RULE_LIFECYCLE_TOOLS["rule.enable"].description,
|
|
195
|
+
inputSchema: { schemaCode: z.string().min(1), ruleCode: z.string().min(1) },
|
|
196
|
+
execute: async (session, args) => executeRuleEnableWrite(session, args),
|
|
197
|
+
rollback: lifecycleRollbackSpec("rule.enable"),
|
|
198
|
+
});
|
|
199
|
+
writeTool({
|
|
200
|
+
tools,
|
|
201
|
+
name: "rule.disable",
|
|
202
|
+
lane: RULE_LIFECYCLE_LANE,
|
|
203
|
+
scope: RULE_LIFECYCLE_TOOLS["rule.disable"].scope,
|
|
204
|
+
description: RULE_LIFECYCLE_TOOLS["rule.disable"].description,
|
|
205
|
+
inputSchema: { schemaCode: z.string().min(1), ruleCode: z.string().min(1) },
|
|
206
|
+
execute: async (session, args) => {
|
|
207
|
+
await assertRuleSchemaExists(session, args.schemaCode);
|
|
208
|
+
const list = await wireCall(session, { path: ENDPOINTS.businessRuleListBySchemaCode, query: { schemaCode: args.schemaCode } });
|
|
209
|
+
const row = wireRows(list.data).find((item) => item.code === args.ruleCode);
|
|
210
|
+
if (!row?.id)
|
|
211
|
+
throw new WireError("business", `rule ${args.ruleCode} not found for disable`, 302024);
|
|
212
|
+
// r28g F22/F24 真机(1.1.25,2026-11-05):`businessrule/disable` 只把 DRAFT
|
|
213
|
+
// 行的 status 置 DISABLED,**不翻 enabled**——随后 list 仍报 enabled:true/
|
|
214
|
+
// ENABLED,get 报 enabled:true+ruleStatus:DISABLED+statusLag,enable 误报
|
|
215
|
+
// alreadyEnabled,run 仍放行。`rule.disableMany` 走 update 写
|
|
216
|
+
// `{enabled:false,status:DISABLED}` 才是真停用(stillEnabled:[] + get.enabled=false)。
|
|
217
|
+
// 契约本来就钉 `businessrule/update`;这里与 disableMany 对齐。残缺 list
|
|
218
|
+
// 投影在 8.1 会 50000 时,改读完整 get 模型再 update(同 enable 路径)。
|
|
219
|
+
const alreadyDisabled = row.enabled !== true;
|
|
220
|
+
if (alreadyDisabled) {
|
|
221
|
+
return {
|
|
222
|
+
performed: { errcode: 0, alreadyDisabled: true, noop: true },
|
|
223
|
+
readback: { enabled: false, status: row.status ?? null, source: "list", note: "list 行已 enabled=false,未发 wire" },
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
// 与 disableMany 同路径:list 投影 + update{enabled:false,status:DISABLED}。
|
|
227
|
+
// 真机上这条路径会同时翻 PUBLISH/DRAFT;假平台也认 list 投影。
|
|
228
|
+
// 读回以 list.enabled 为门(disableMany.stillEnabled 同口径);get 作补充,
|
|
229
|
+
// 若 get 仍滞后只标 statusLag,不因 get 单独 fail closed。
|
|
230
|
+
const performed = await wireCall(session, {
|
|
231
|
+
path: ENDPOINTS.businessRuleUpdate,
|
|
232
|
+
method: "POST",
|
|
233
|
+
body: { ...row, enabled: false, status: "DISABLED" },
|
|
175
234
|
});
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
235
|
+
const afterList = wireRows((await wireCall(session, { path: ENDPOINTS.businessRuleListBySchemaCode, query: { schemaCode: args.schemaCode } })).data).find((item) => item.code === args.ruleCode);
|
|
236
|
+
const listEnabled = afterList?.enabled === true;
|
|
237
|
+
if (listEnabled) {
|
|
238
|
+
throw new ToolRefusalError({
|
|
239
|
+
status: "refused",
|
|
240
|
+
blocker: "rule-disable-not-effective",
|
|
241
|
+
schemaCode: args.schemaCode,
|
|
242
|
+
ruleCode: args.ruleCode,
|
|
243
|
+
message: `rule.disable 已发 businessrule/update(enabled:false),但 list 仍 enabled=true;不要信任 summary。可改试 rule.disableMany {schemaCode, ruleCodes:[${JSON.stringify(args.ruleCode)}]}`,
|
|
244
|
+
readback: { enabled: true, listEnabled: true, listStatus: afterList?.status ?? null },
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
let draftEnabled = null;
|
|
248
|
+
let draftStatus = null;
|
|
249
|
+
try {
|
|
250
|
+
const afterGet = (await wireCall(session, { path: ENDPOINTS.businessRuleGet, query: { id: String(row.id) } })).data;
|
|
251
|
+
if (afterGet) {
|
|
252
|
+
draftEnabled = afterGet.enabled === true;
|
|
253
|
+
draftStatus = afterGet.status == null ? null : String(afterGet.status);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
catch {
|
|
257
|
+
// list 已证明停用;get 失败不挡
|
|
258
|
+
}
|
|
259
|
+
return {
|
|
260
|
+
performed: { errcode: performed.errcode },
|
|
261
|
+
readback: {
|
|
262
|
+
enabled: false,
|
|
263
|
+
listEnabled: false,
|
|
264
|
+
listStatus: afterList?.status ?? null,
|
|
265
|
+
...(draftEnabled !== null ? { draftEnabled, draftStatus } : {}),
|
|
266
|
+
...(draftEnabled === true ? { statusLag: true, statusNote: "list.enabled=false 已生效;DRAFT get.enabled 仍 true(面滞后)——以 list / 本 readback.enabled 为准" } : {}),
|
|
267
|
+
source: "list",
|
|
268
|
+
effectHint: "停用读回以本 readback.enabled(list 面,与 disableMany 同口径)为准;rule.get 可作交叉核对",
|
|
269
|
+
},
|
|
270
|
+
};
|
|
271
|
+
},
|
|
272
|
+
rollback: lifecycleRollbackSpec("rule.disable"),
|
|
273
|
+
});
|
|
274
|
+
writeTool({
|
|
275
|
+
tools,
|
|
276
|
+
name: "rule.delete",
|
|
277
|
+
lane: RULE_LIFECYCLE_LANE,
|
|
278
|
+
scope: RULE_LIFECYCLE_TOOLS["rule.delete"].scope,
|
|
279
|
+
description: RULE_LIFECYCLE_TOOLS["rule.delete"].description,
|
|
280
|
+
inputSchema: { schemaCode: z.string().min(1), ruleCode: z.string().min(1) },
|
|
281
|
+
execute: async (session, args) => {
|
|
282
|
+
// Ticket 96 finding: the preset rule codes are platform-managed —
|
|
283
|
+
// listViewsBySchemaCode re-fills any missing preset for the schema, so
|
|
284
|
+
// deleting one via the API is unreachable (it resurrects on the next
|
|
285
|
+
// list). Refuse up front instead of "succeeding" and multiplying rows.
|
|
286
|
+
if (PLATFORM_TRIGGER_RULE_CODES.has(args.ruleCode))
|
|
287
|
+
throw new ToolRefusalError({
|
|
288
|
+
status: "refused",
|
|
289
|
+
blocker: "rule-preset-delete-unreachable",
|
|
290
|
+
message: `ruleCode "${args.ruleCode}" 是平台预置触发规则——list_by_schemacode 会自动重灌缺失预置(BusinessRuleFacadeImpl.listViewsBySchemaCode:179-185),API 删除不可达且会增殖版本行;预置规则请用 rule.save 覆盖图或 rule.disable 停用`,
|
|
291
|
+
});
|
|
292
|
+
await assertRuleSchemaExists(session, args.schemaCode);
|
|
293
|
+
const list = await wireCall(session, { path: ENDPOINTS.businessRuleListBySchemaCode, query: { schemaCode: args.schemaCode } });
|
|
294
|
+
const row = wireRows(list.data).find((item) => item.code === args.ruleCode);
|
|
295
|
+
if (!row?.id)
|
|
296
|
+
throw new WireError("business", `rule ${args.ruleCode} not found for delete`, 302024);
|
|
297
|
+
const performed = await wireCall(session, { path: ENDPOINTS.businessRuleDelete, method: "POST", query: { id: String(row.id) } });
|
|
298
|
+
const readback = await wireCall(session, { path: ENDPOINTS.businessRuleListBySchemaCode, query: { schemaCode: args.schemaCode } });
|
|
299
|
+
return { performed: { errcode: performed.errcode }, readback: { absent: !wireRows(readback.data).some((item) => item.code === args.ruleCode) } };
|
|
300
|
+
},
|
|
301
|
+
rollback: lifecycleRollbackSpec("rule.delete"),
|
|
302
|
+
});
|
|
185
303
|
// Round 10 issue 05 — lifecycle batch. Disabling a model's rules one by one was
|
|
186
304
|
// N calls; this flips `enabled:false` on every matching rule in one tool. Only
|
|
187
305
|
// CUSTOM rules by default (platform preset trigger rows must not be disabled —
|
|
188
306
|
// they are platform-managed); `includePresets:true` opts in explicitly.
|
|
189
|
-
writeTool(
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
status: "refused",
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
throw new ToolRefusalError({
|
|
212
|
-
status: "refused",
|
|
213
|
-
blocker: "rule-run-disabled",
|
|
214
|
-
ruleCode: args.ruleCode,
|
|
215
|
-
message: `规则 ${args.ruleCode} 当前未启用(enabled=${String(row.enabled)})——先 rule.enable(publish+enable 两步),否则设计器显示「失效·未启用」且按钮不会执行它`,
|
|
216
|
-
});
|
|
217
|
-
let performed;
|
|
218
|
-
try {
|
|
219
|
-
performed = await wireCall(session, {
|
|
220
|
-
path: ruleRuntimeExecutePath(args.schemaCode, args.ruleCode),
|
|
221
|
-
method: "POST",
|
|
222
|
-
body: { schemaCode: args.schemaCode, ids, ruleCode: args.ruleCode, consolidation: args.consolidation === true },
|
|
223
|
-
});
|
|
224
|
-
}
|
|
225
|
-
catch (error) {
|
|
226
|
-
// 550001 业务规则不存在 on the launcher runtime path means the rule was
|
|
227
|
-
// not REGISTERED as a custom trigger (r15: a data-event graph saved with
|
|
228
|
-
// customTrigger:true still 550001s). Name the semantic instead of
|
|
229
|
-
// forwarding a bare platform code.
|
|
230
|
-
if (error instanceof WireError && error.kind === "business" && error.errcode === 550001)
|
|
307
|
+
writeTool({
|
|
308
|
+
tools,
|
|
309
|
+
name: "rule.run",
|
|
310
|
+
lane: RULE_LIFECYCLE_LANE,
|
|
311
|
+
scope: RULE_LIFECYCLE_TOOLS["rule.run"].scope,
|
|
312
|
+
description: RULE_LIFECYCLE_TOOLS["rule.run"].description,
|
|
313
|
+
inputSchema: {
|
|
314
|
+
schemaCode: z.string().min(1),
|
|
315
|
+
ruleCode: z.string().min(1),
|
|
316
|
+
ids: z.array(z.string().min(1)).min(1).describe("要执行的业务数据 id(data.load/data.list 的 bizObjectId / row.bizObjectId)——就是平台按钮点击时提交的 ids"),
|
|
317
|
+
consolidation: z.boolean().optional().describe("平台 body 字段,默认 false(按钮点击抓包值);合并执行场景才传 true"),
|
|
318
|
+
},
|
|
319
|
+
execute: async (session, args) => {
|
|
320
|
+
await assertRuleSchemaExists(session, args.schemaCode);
|
|
321
|
+
const ids = [...new Set(args.ids.map(String))];
|
|
322
|
+
if (ids.length !== args.ids.length)
|
|
323
|
+
throw new ToolRefusalError({ status: "refused", blocker: "rule-run-ids-duplicated", message: "ids 有重复项——按去重后的列表执行,别让同一行在一条规则里被执行两次" });
|
|
324
|
+
const list = await wireCall(session, { path: ENDPOINTS.businessRuleListBySchemaCode, query: { schemaCode: args.schemaCode } });
|
|
325
|
+
const row = wireRows(list.data).find((item) => String(item.code) === args.ruleCode);
|
|
326
|
+
if (!row)
|
|
327
|
+
throw new ToolRefusalError({ status: "refused", blocker: "rule-run-rule-absent", schemaCode: args.schemaCode, ruleCode: args.ruleCode, message: `模型 ${args.schemaCode} 没有规则 ${args.ruleCode}——先 rule.list 核对 code` });
|
|
328
|
+
if (row.defaultRule === true || PLATFORM_TRIGGER_RULE_CODES.has(args.ruleCode))
|
|
231
329
|
throw new ToolRefusalError({
|
|
232
330
|
status: "refused",
|
|
233
|
-
blocker: "rule-
|
|
234
|
-
schemaCode: args.schemaCode,
|
|
331
|
+
blocker: "rule-run-preset-rule",
|
|
235
332
|
ruleCode: args.ruleCode,
|
|
236
|
-
|
|
237
|
-
message: `550001「业务规则不存在」在这里的真实含义是「该规则当前不可执行」,不是规则不存在,也不是「只认 CUSTOM_NODE」。` +
|
|
238
|
-
`反编译 8.6.26 的 BusinessRuleTableImpl#getExecutableBySchemaCodeAndCode 是唯一判定:` +
|
|
239
|
-
`① **DRAFT 行**必须存在;② DRAFT 行 enabled=true **且** status != DISABLED;③ 至少一条 PUBLISH 版本。` +
|
|
240
|
-
`rule.save --publish 只写 PUBLISH 行,**DRAFT 行的 status 仍是 DISABLED**——所以发布完直接 rule.run 必然 550001。` +
|
|
241
|
-
`修法:rule.enable(它先 publish 再调平台 /enable,把 DRAFT 行 status 置 ENABLED)。` +
|
|
242
|
-
`注意 rule.list 显示的是 PUBLISH 行的 enabled/status,看起来"已启用"但运行期读的是 DRAFT 行——不要用 list 判定可执行性。`,
|
|
333
|
+
message: `${args.ruleCode} 是平台预置触发规则(defaultRule:true)——它由数据事件(新增/更新/删除/流程生效…)自动分发,设计器的按钮规则选择器不提供预置行。要手动跑请新建自定义规则(CUSTOM_NODE 触发)再用 button/rule.run`,
|
|
243
334
|
});
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
await assertRuleSchemaExists(session, args.schemaCode);
|
|
273
|
-
const list = await wireCall(session, { path: ENDPOINTS.businessRuleListBySchemaCode, query: { schemaCode: args.schemaCode } });
|
|
274
|
-
const wanted = args.ruleCodes ? new Set(args.ruleCodes) : undefined;
|
|
275
|
-
const targets = wireRows(list.data).filter((row) => {
|
|
276
|
-
const code = String(row.code ?? "");
|
|
277
|
-
if (wanted && !wanted.has(code))
|
|
278
|
-
return false;
|
|
279
|
-
// NOTE: presets are INCLUDED by default — user logic is frequently
|
|
280
|
-
// authored INTO preset trigger rows (Create/Update/…), so skipping them
|
|
281
|
-
// would silently leave the user's rules enabled. customOnly:true opts out.
|
|
282
|
-
if (args.customOnly === true && PLATFORM_TRIGGER_RULE_CODES.has(code))
|
|
283
|
-
return false;
|
|
284
|
-
return true;
|
|
285
|
-
});
|
|
286
|
-
const disabled = [];
|
|
287
|
-
const skipped = [];
|
|
288
|
-
for (const row of targets) {
|
|
289
|
-
const code = String(row.code ?? "");
|
|
335
|
+
// r28g F27:list.enabled 是 PUBLISH 投影;运行时门禁读 DRAFT.enabled。
|
|
336
|
+
// status 列可滞后(enable 后仍 DISABLED),不单独据此拒绝——与 rule.get
|
|
337
|
+
// 的 statusLag 注释一致。只看 list 会在 disable 半生效时假通过。
|
|
338
|
+
let draftEnabled = row.enabled === true;
|
|
339
|
+
let draftStatus = row.status == null ? null : String(row.status);
|
|
340
|
+
if (row.id) {
|
|
341
|
+
try {
|
|
342
|
+
const draft = (await wireCall(session, { path: ENDPOINTS.businessRuleGet, query: { id: String(row.id) } })).data;
|
|
343
|
+
if (draft) {
|
|
344
|
+
draftEnabled = draft.enabled === true;
|
|
345
|
+
draftStatus = draft.status == null ? null : String(draft.status);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
catch {
|
|
349
|
+
// fall back to list projection
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
if (!draftEnabled)
|
|
353
|
+
throw new ToolRefusalError({
|
|
354
|
+
status: "refused",
|
|
355
|
+
blocker: "rule-run-disabled",
|
|
356
|
+
ruleCode: args.ruleCode,
|
|
357
|
+
listEnabled: row.enabled === true,
|
|
358
|
+
draftEnabled,
|
|
359
|
+
draftStatus,
|
|
360
|
+
message: `规则 ${args.ruleCode} 当前未启用(DRAFT enabled=false, status=${draftStatus ?? "null"};list.enabled=${String(row.enabled)})——先 rule.enable(publish+enable 两步),否则设计器显示「失效·未启用」且按钮不会执行它`,
|
|
361
|
+
});
|
|
362
|
+
let performed;
|
|
290
363
|
try {
|
|
291
|
-
await wireCall(session, {
|
|
292
|
-
|
|
364
|
+
performed = await wireCall(session, {
|
|
365
|
+
path: ruleRuntimeExecutePath(args.schemaCode, args.ruleCode),
|
|
366
|
+
method: "POST",
|
|
367
|
+
body: { schemaCode: args.schemaCode, ids, ruleCode: args.ruleCode, consolidation: args.consolidation === true },
|
|
368
|
+
});
|
|
293
369
|
}
|
|
294
370
|
catch (error) {
|
|
295
|
-
|
|
371
|
+
// 550001 业务规则不存在 on the launcher runtime path means the rule was
|
|
372
|
+
// not REGISTERED as a custom trigger (r15: a data-event graph saved with
|
|
373
|
+
// customTrigger:true still 550001s). Name the semantic instead of
|
|
374
|
+
// forwarding a bare platform code.
|
|
375
|
+
if (error instanceof WireError && error.kind === "business" && error.errcode === 550001)
|
|
376
|
+
throw new ToolRefusalError({
|
|
377
|
+
status: "refused",
|
|
378
|
+
blocker: "rule-not-executable",
|
|
379
|
+
schemaCode: args.schemaCode,
|
|
380
|
+
ruleCode: args.ruleCode,
|
|
381
|
+
nextCommand: `omc call rule.enable --args '${JSON.stringify({ schemaCode: args.schemaCode, ruleCode: args.ruleCode })}'`,
|
|
382
|
+
message: `550001「业务规则不存在」在这里的真实含义是「该规则当前不可执行」,不是规则不存在,也不是「只认 CUSTOM_NODE」。` +
|
|
383
|
+
`反编译 8.6.26 的 BusinessRuleTableImpl#getExecutableBySchemaCodeAndCode 是唯一判定:` +
|
|
384
|
+
`① **DRAFT 行**必须存在;② DRAFT 行 enabled=true **且** status != DISABLED;③ 至少一条 PUBLISH 版本。` +
|
|
385
|
+
`rule.save --publish 只写 PUBLISH 行,**DRAFT 行的 status 仍是 DISABLED**——所以发布完直接 rule.run 必然 550001。` +
|
|
386
|
+
`修法:rule.enable(它先 publish 再调平台 /enable,把 DRAFT 行 status 置 ENABLED)。` +
|
|
387
|
+
`注意 rule.list 显示的是 PUBLISH 行的 enabled/status,看起来"已启用"但运行期读的是 DRAFT 行——不要用 list 判定可执行性。`,
|
|
388
|
+
});
|
|
389
|
+
throw error;
|
|
296
390
|
}
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
391
|
+
const payload = (performed.data ?? null);
|
|
392
|
+
const objects = Array.isArray(payload) ? payload : [];
|
|
393
|
+
return {
|
|
394
|
+
performed: { errcode: performed.errcode, ruleCode: args.ruleCode, ids },
|
|
395
|
+
readback: {
|
|
396
|
+
schemaCode: args.schemaCode,
|
|
397
|
+
ruleCode: args.ruleCode,
|
|
398
|
+
executedIds: ids,
|
|
399
|
+
returnedObjects: objects.length,
|
|
400
|
+
// 302045 = 数据被占用(前端把同样的码映射成"有人正在编辑")。
|
|
401
|
+
...(performed.errcode === 302045
|
|
402
|
+
? { dataInUse: true, hint: "302045:数据被占用(有人正在编辑该行 / 行锁未释放),稍后重试" }
|
|
403
|
+
: {}),
|
|
404
|
+
effectHint: "平台只回执行结果信封;返回的 data 是执行前快照(reacquireDataFromDb:false)。规则副作用必须另行读回:data.load {schemaCode, bizObjectId} 或 data.list(data.list 默认不含 DRAFT,草稿行请用 data.load)",
|
|
405
|
+
// NOT latestPublished: the platform's list projection reports
|
|
406
|
+
// latestPublished:false even for rows it executes fine (live
|
|
407
|
+
// 2026-10-23, documented in rule-support.ts) — a warning here would be
|
|
408
|
+
// noise. Liveness is `enabled`, which is gated above.
|
|
409
|
+
},
|
|
410
|
+
};
|
|
411
|
+
},
|
|
412
|
+
});
|
|
413
|
+
writeTool({
|
|
414
|
+
tools,
|
|
415
|
+
name: "rule.disableMany",
|
|
416
|
+
lane: RULE_LIFECYCLE_LANE,
|
|
417
|
+
scope: RULE_LIFECYCLE_TOOLS["rule.disableMany"].scope,
|
|
418
|
+
description: RULE_LIFECYCLE_TOOLS["rule.disableMany"].description,
|
|
419
|
+
inputSchema: {
|
|
420
|
+
schemaCode: z.string().min(1),
|
|
421
|
+
ruleCodes: z.array(z.string().min(1)).optional().describe("只停用这些 code;省略=该模型全部规则"),
|
|
422
|
+
customOnly: z.boolean().optional().describe("默认 false:停用全部匹配规则(含平台预置触发码 Create/Update/…,因为用户逻辑常写在其中);true 只停用自定义规则、跳过预置。"),
|
|
423
|
+
},
|
|
424
|
+
execute: async (session, args) => {
|
|
425
|
+
await assertRuleSchemaExists(session, args.schemaCode);
|
|
426
|
+
const list = await wireCall(session, { path: ENDPOINTS.businessRuleListBySchemaCode, query: { schemaCode: args.schemaCode } });
|
|
427
|
+
const wanted = args.ruleCodes ? new Set(args.ruleCodes) : undefined;
|
|
428
|
+
const targets = wireRows(list.data).filter((row) => {
|
|
429
|
+
const code = String(row.code ?? "");
|
|
430
|
+
if (wanted && !wanted.has(code))
|
|
431
|
+
return false;
|
|
432
|
+
// NOTE: presets are INCLUDED by default — user logic is frequently
|
|
433
|
+
// authored INTO preset trigger rows (Create/Update/…), so skipping them
|
|
434
|
+
// would silently leave the user's rules enabled. customOnly:true opts out.
|
|
435
|
+
if (args.customOnly === true && PLATFORM_TRIGGER_RULE_CODES.has(code))
|
|
436
|
+
return false;
|
|
437
|
+
return true;
|
|
438
|
+
});
|
|
439
|
+
const disabled = [];
|
|
440
|
+
const skipped = [];
|
|
441
|
+
for (const row of targets) {
|
|
442
|
+
const code = String(row.code ?? "");
|
|
443
|
+
try {
|
|
444
|
+
await wireCall(session, { path: ENDPOINTS.businessRuleUpdate, method: "POST", body: { ...row, enabled: false, status: "DISABLED" } });
|
|
445
|
+
disabled.push(code);
|
|
446
|
+
}
|
|
447
|
+
catch (error) {
|
|
448
|
+
skipped.push({ ruleCode: code, reason: error instanceof Error ? error.message : String(error) });
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
const after = wireRows((await wireCall(session, { path: ENDPOINTS.businessRuleListBySchemaCode, query: { schemaCode: args.schemaCode } })).data);
|
|
452
|
+
const stillEnabled = after.filter((row) => row.enabled === true && disabled.includes(String(row.code ?? ""))).map((row) => String(row.code));
|
|
453
|
+
return { performed: { disabled }, readback: { disabledCount: disabled.length, disabled, skipped, stillEnabled } };
|
|
454
|
+
},
|
|
301
455
|
});
|
|
302
456
|
registerPackageTool(tools, {
|
|
303
457
|
name: "configure_rule",
|
|
@@ -5,7 +5,7 @@ export { RUNTIME_DATA_WIRE, RUNTIME_QUERY_OPERATORS, ENGINE_OWNED_KEYS, SUBMIT_S
|
|
|
5
5
|
import { RUNTIME_DATA_WIRE } from "omc-core/wire-facts";
|
|
6
6
|
export function dataListDescription() {
|
|
7
7
|
const wire = RUNTIME_DATA_WIRE;
|
|
8
|
-
return `按简化过滤 DSL 查询业务对象(runtime/query/list);DSL 在 CLI 内翻译为平台 queryCondition(${wire.dslShape}),并按发布字段元数据为每个条件注入 propertyType(未知过滤字段将被拒绝,可用 rawCondition
|
|
8
|
+
return `按简化过滤 DSL 查询业务对象(runtime/query/list);DSL 在 CLI 内翻译为平台 queryCondition(${wire.dslShape}),并按发布字段元数据为每个条件注入 propertyType(未知过滤字段将被拒绝,可用 rawCondition 逃生);响应行在 items[],业务字段在 items[].fields.<code>(不是 rows[],也不是 item 顶层——--jq 常用 items[].fields.sku);空模型返回空列表;未配列的自定义字段投影为 null——这是显示面缺列配置、不是数据缺失(data.load 有值),显式选列在结果中全为 null 而发布字段元数据显示其存在时,readback 追加 projectionHint(先用 listview.configure 配置该模型的默认视图列,或用 data.load 读单条权威值)。`;
|
|
9
9
|
}
|
|
10
10
|
export function dataUpdateDescription() {
|
|
11
11
|
const wire = RUNTIME_DATA_WIRE;
|
|
@@ -21,7 +21,7 @@ export function dataUpdateDescription() {
|
|
|
21
21
|
}
|
|
22
22
|
export function dataSaveDescription() {
|
|
23
23
|
const wire = RUNTIME_DATA_WIRE;
|
|
24
|
-
return describeTool("
|
|
24
|
+
return describeTool("默认存草稿(DRAFT,不触发规则);要提交态用顶层 submit:true 或随后 data.submit;formData.name 会被平台改写为「用户名+模型名+时间」,要控标题请传业务字段。M2 runtime/form/save — returns bizObjectId for authoritative readback via data.load.", [
|
|
25
25
|
`带可选 workflowCode 时按门户暂存形状创建流程草稿:${wire.workflowDraftSave}。`,
|
|
26
26
|
"formData 禁止携带 id(带 id 的就地发起在 8.6.26 无 REST 面,会被拒绝;既有无实例草稿请改用两段式或 data.submit 的 workItemId 审批链)。",
|
|
27
27
|
"关联值形状(live 2026-09-24):**单选关联(propertyType 9)传 id 字符串或 { id };传数组会 50000**;多选关联(11)才用数组。**人员/组织(50/51/60/61)传选择对象数组 `[{\"id\":\"<id>\",\"type\":3,\"name\":\"<名>\"}]`——`type` 必填**(BizField.parseFromClient 把 `type` 强转 Integer 并 intValue(),缺 `type` 稳定 50000);`type` 取 UnitType 索引:用户=3、部门=1。字段 `mappings` 由表单运行时在用户选择时套用,**服务端 data.save 不会自动回填映射目标字段**——API 侧调用需自行写入被映射字段,否则读回为空(不要误判为映射配置失败)。",
|
|
@@ -138,8 +138,8 @@ function desc(lead) {
|
|
|
138
138
|
}
|
|
139
139
|
export function assertRuntimeDataContract() {
|
|
140
140
|
assertLaneContract(RUNTIME_DATA_LANE_CONTRACT);
|
|
141
|
-
assertDescriptionCarries(RUNTIME_DATA_LANE_CONTRACT, "data.list", ["queryCondition", "propertyType", "rawCondition", "projectionHint", "listview.configure", "data.load"]);
|
|
142
|
-
assertDescriptionCarries(RUNTIME_DATA_LANE_CONTRACT, "data.save", ["bizObjectId", "sheetCode", "bizsheet/get", "workflowCode", "sequenceStatus", "workItem", "propertyType 9", "50000", "mappings", "rowStatus"]);
|
|
141
|
+
assertDescriptionCarries(RUNTIME_DATA_LANE_CONTRACT, "data.list", ["queryCondition", "propertyType", "rawCondition", "projectionHint", "listview.configure", "data.load", "items[].fields"]);
|
|
142
|
+
assertDescriptionCarries(RUNTIME_DATA_LANE_CONTRACT, "data.save", ["bizObjectId", "sheetCode", "bizsheet/get", "workflowCode", "sequenceStatus", "workItem", "propertyType 9", "50000", "mappings", "rowStatus", "DRAFT", "submit:true"]);
|
|
143
143
|
assertDescriptionCarries(RUNTIME_DATA_LANE_CONTRACT, "data.update", ["runtime/form/save", "bizObjectId", "COMPLETED", "304001", "form/load", "rowStatus"]);
|
|
144
144
|
assertDescriptionCarries(RUNTIME_DATA_LANE_CONTRACT, "data.delete", ["absence", "sheetCode"]);
|
|
145
145
|
assertDescriptionCarries(RUNTIME_DATA_LANE_CONTRACT, "data.submit", ["getReplayToken", "replayToken", "bizObjectId", "objectId", "DRAFT", "COMPLETED", "10009", "100005", "NPE", "两段式", "finishStart=FALSE"]);
|