@stalfh233/omc-cli 0.4.32 → 0.4.34
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.
|
@@ -1050,6 +1050,27 @@ const dataListFilter = Object.freeze({
|
|
|
1050
1050
|
refusalRecovery: [{ code: "invalid-filter-DSL", action: "按错误里的 minimalValidFilter/acceptedOps 修正 op/field/value" }],
|
|
1051
1051
|
requiresEnvironmentDerivedIds: false,
|
|
1052
1052
|
});
|
|
1053
|
+
const dataDeleteSweep = Object.freeze({
|
|
1054
|
+
id: "sweep-all-readback",
|
|
1055
|
+
title: "清空模型数据并权威验证(list 取 ids → delete → 复验 total:0)",
|
|
1056
|
+
status: "offline-valid",
|
|
1057
|
+
tool: "data.delete",
|
|
1058
|
+
args: { environment, schemaCode: model, bizObjectIds: ["<data.list 逐条 id>"] },
|
|
1059
|
+
preconditions: [
|
|
1060
|
+
"先 data.list 取全量 ids(page 0 起、size 够大或翻页);**submit 后平台会同时保留 DRAFT 与 COMPLETED 两类行(r30 实证 5 次提交 = 10 行),清理对象按 data.list 全量**",
|
|
1061
|
+
],
|
|
1062
|
+
conditionalRequirements: [
|
|
1063
|
+
"参数名是 `bizObjectIds`(数组)——不是 ids/bizObjectId(单条删除另有其参)",
|
|
1064
|
+
"id 已被删时幂等 no-op,重复清理安全",
|
|
1065
|
+
],
|
|
1066
|
+
priorToolReferences: [
|
|
1067
|
+
{ tool: "data.list", args: { environment, schemaCode: model }, purpose: "取全量 bizObjectIds(含 DRAFT 行)" },
|
|
1068
|
+
],
|
|
1069
|
+
dryRun: { supported: true, command: `omc call data.delete --env ${environment} --dry-run --args '<recipe.args>'`, notes: "dry-run 出删除计划不落库" },
|
|
1070
|
+
expectedReadback: { readback: { deleted: "N", modelResidue: { total: 0 } }, next: ["data.list 复验 total:0 / items:[](权威清空判据)"] },
|
|
1071
|
+
refusalRecovery: [{ code: "wire-business", action: "模型须已创建且有已发布字段;运行面 50000 时先 model.inspect modelPresent 判存在" }],
|
|
1072
|
+
requiresEnvironmentDerivedIds: false,
|
|
1073
|
+
});
|
|
1053
1074
|
const dictionaryKeyValue = Object.freeze({
|
|
1054
1075
|
id: "dictionary-key-value",
|
|
1055
1076
|
title: "建一个键值字典并用其 id 作字段下拉来源",
|
|
@@ -1103,6 +1124,7 @@ export const EXECUTION_METADATA = Object.freeze({
|
|
|
1103
1124
|
"method.update": Object.freeze({ recipes: [bizserviceSelfCallCorp], relatedTools: ["method.get", "method.create", "service.get"] }),
|
|
1104
1125
|
"listview.configure": Object.freeze({ recipes: [listViewConfigure], relatedTools: ["listview.list", "listview.get", "field.list", "field.publish"] }),
|
|
1105
1126
|
"data.list": Object.freeze({ recipes: [dataListFilter], relatedTools: ["data.load", "data.delete", "listview.configure", "field.list"] }),
|
|
1127
|
+
"data.delete": Object.freeze({ recipes: [dataDeleteSweep], relatedTools: ["data.list", "data.load", "data.submit"] }),
|
|
1106
1128
|
});
|
|
1107
1129
|
export function executionMetadataFor(tool) {
|
|
1108
1130
|
return EXECUTION_METADATA[tool.name];
|
package/dist/intent/schema.js
CHANGED
|
@@ -35,6 +35,7 @@ export const RUNNABLE_EXAMPLES = Object.freeze([
|
|
|
35
35
|
" - { code: orderNo, name: 订单号, type: SHORT_TEXT }",
|
|
36
36
|
" - { code: amount, name: 金额, type: NUMERICAL, options: { scale: 2 } }",
|
|
37
37
|
" - { code: level, name: 等级, type: RADIO, choices: [A, B, C] } # choices 只用于 RADIO/CHECKBOX/DROPDOWN_BOX/DROPDOWN_MULTI_BOX",
|
|
38
|
+
" # 字段 code 避开平台保留字(id/name/owner/creater/createdTime/modifiedTime/version/…)——撞保留字 offline 校验即拒(code_reserved);name 是字段中文名,不是 code(r31 实证 code:name 被拒后改 itemName 成功)",
|
|
38
39
|
" form: { publish: true }",
|
|
39
40
|
" listview: { fieldCodes: [orderNo, amount, level] }",
|
|
40
41
|
"",
|
package/dist/lanes/model.js
CHANGED
|
@@ -171,7 +171,7 @@ export function registerModelLane(tools) {
|
|
|
171
171
|
const codes = args.schemaCodes && args.schemaCodes.length > 0 ? args.schemaCodes : args.schemaCode ? [args.schemaCode] : [];
|
|
172
172
|
if (codes.length === 0)
|
|
173
173
|
throw new ToolInputError({ status: "error", blocker: "tool-input-invalid", tool: "model.inspect", message: "需要 schemaCode(单个)或 schemaCodes(数组,批量)" });
|
|
174
|
-
const face = async (name, toolArgs) => {
|
|
174
|
+
const face = async (name, toolArgs, errcodes) => {
|
|
175
175
|
const tool = siblings.find((entry) => entry.name === name);
|
|
176
176
|
if (!tool)
|
|
177
177
|
return undefined;
|
|
@@ -184,17 +184,24 @@ export function registerModelLane(tools) {
|
|
|
184
184
|
const result = (await withoutReadbackCache(ctx, async () => (await tool.handler(ctx, parsed, tool))));
|
|
185
185
|
return result;
|
|
186
186
|
}
|
|
187
|
-
catch {
|
|
187
|
+
catch (error) {
|
|
188
|
+
// r30: record the wire errcode so inspectOne can turn the field
|
|
189
|
+
// face's 300004 into a `modelPresent:false` verdict instead of an
|
|
190
|
+
// all-empty aggregate that reads as "registered, no fields".
|
|
191
|
+
const errcode = error.errcode;
|
|
192
|
+
if (errcodes && typeof errcode === "number")
|
|
193
|
+
errcodes.set(name, errcode);
|
|
188
194
|
return undefined; // a single face being unreachable must not sink the whole inspect
|
|
189
195
|
}
|
|
190
196
|
};
|
|
191
197
|
const inspectOne = async (schemaCode) => {
|
|
198
|
+
const faceErrcodes = new Map();
|
|
192
199
|
// F5: the six read faces are INDEPENDENT wire reads; fan them out in
|
|
193
200
|
// parallel (bounded) instead of paying six sequential round-trips. Only
|
|
194
201
|
// workflow.get_published stays chained (it needs workflowCode from the
|
|
195
202
|
// draft face). Output ordering is unchanged (results land by index).
|
|
196
203
|
const [fieldFace, formFace, listviewFace, ruleFace, dataruleFace, draftFace] = await mapBounded([
|
|
197
|
-
() => face("field.list", { schemaCode, published: true, ...(all ? { all: true } : {}) }),
|
|
204
|
+
() => face("field.list", { schemaCode, published: true, ...(all ? { all: true } : {}) }, faceErrcodes),
|
|
198
205
|
() => face("form.get", { schemaCode }),
|
|
199
206
|
() => face("listview.get", { schemaCode }),
|
|
200
207
|
() => face("rule.list", { schemaCode }),
|
|
@@ -245,15 +252,29 @@ export function registerModelLane(tools) {
|
|
|
245
252
|
activities: Array.isArray(summary?.activities) ? summary.activities.map((activity) => ({ code: activity.code, type: activity.type, name: activity.name, participant: activity.participant })) : [],
|
|
246
253
|
edges: summary?.edges,
|
|
247
254
|
};
|
|
255
|
+
// r30 verdict: field face ok ⇒ model exists. The wire refusal for a
|
|
256
|
+
// missing model is a RETURNED payload (status:"refused", errcode:300004
|
|
257
|
+
// 数据模型不存在), not a throw — read both signals (returned refusal +
|
|
258
|
+
// thrown WireError) before deciding; unreachable face ⇒ undefined.
|
|
259
|
+
const fieldRefused = fieldFace !== undefined && fieldFace.status === "refused";
|
|
260
|
+
const fieldRefusalErrcode = fieldRefused ? Number(fieldFace.errcode) : undefined;
|
|
261
|
+
const absent = fieldRefusalErrcode === 300004 || faceErrcodes.get("field.list") === 300004;
|
|
262
|
+
const modelPresent = absent ? false : fieldFace !== undefined && !fieldRefused ? true : undefined;
|
|
248
263
|
return {
|
|
249
264
|
schemaCode,
|
|
265
|
+
...(modelPresent !== undefined ? { modelPresent } : {}),
|
|
266
|
+
...(modelPresent === false
|
|
267
|
+
? {
|
|
268
|
+
existenceHint: "模型不存在(field.list 300004)——先 model.create / configure_model 再挂字段;勿把空聚合读成『已存在但无字段』(r30 实证该误读导致 field.create 300004 失败往返)",
|
|
269
|
+
}
|
|
270
|
+
: {}),
|
|
250
271
|
fields,
|
|
251
272
|
form,
|
|
252
273
|
listview,
|
|
253
274
|
rules,
|
|
254
275
|
datarules,
|
|
255
276
|
workflow,
|
|
256
|
-
hint: "model.inspect 聚合设计时六面(fields/form/listview/rules/datarules/workflow
|
|
277
|
+
hint: "model.inspect 聚合设计时六面(fields/form/listview/rules/datarules/workflow,摘要)。modelPresent 是存在性判据(field 面 300004=false);字段 options 细节用 field.list --code;规则图用 rule.get schemaCode+ruleCode;运行数据用 data.list(不在此面)。",
|
|
257
278
|
};
|
|
258
279
|
};
|
|
259
280
|
const one = await inspectOne(codes[0]);
|
package/dist/lanes/report.js
CHANGED
|
@@ -319,7 +319,9 @@ export function registerReportLane(tools) {
|
|
|
319
319
|
};
|
|
320
320
|
});
|
|
321
321
|
writeTool(tools, "report.create", LANE, TOOLS["report.create"].scope, TOOLS["report.create"].description, {
|
|
322
|
-
|
|
322
|
+
// r33: the platform refuses report codes longer than 28 chars (errcode
|
|
323
|
+
// 10005) — enforce offline so the shape never costs a wire round trip.
|
|
324
|
+
code: z.string().min(1).max(28).describe("报表 code;平台上限 28 字符(10005 实证 r33)——超长在提交前直接被拒"),
|
|
323
325
|
name: z.string().min(1).max(60),
|
|
324
326
|
appCode: z.string().min(1),
|
|
325
327
|
parentId: z.string().min(1),
|
|
@@ -434,6 +434,26 @@ export function registerRuntimeDataLane(tools) {
|
|
|
434
434
|
if (error instanceof WireError && error.kind === "business" && error.errcode === RUNTIME_DATA_WIRE.emptyModelErrcode) {
|
|
435
435
|
return { schemaCode: args.schemaCode, total: 0, totalPages: 0, page: args.page ?? 0, size: args.size ?? 10, items: [] };
|
|
436
436
|
}
|
|
437
|
+
// r30: a MISSING model answers this face with a generic 50000
|
|
438
|
+
// 服务异常 — indistinguishable from a real outage. One field-face
|
|
439
|
+
// probe disambiguates: its 300004 (数据模型不存在) turns the
|
|
440
|
+
// refusal into an existence verdict so the caller creates instead
|
|
441
|
+
// of retrying; any other outcome rethrows the original 50000.
|
|
442
|
+
if (error instanceof WireError && error.kind === "business" && error.errcode === 50000) {
|
|
443
|
+
try {
|
|
444
|
+
await wireCall(session, { path: ENDPOINTS.fieldList, query: { schemaCode: args.schemaCode, isPublish: "true" } });
|
|
445
|
+
}
|
|
446
|
+
catch (probeError) {
|
|
447
|
+
if (probeError instanceof WireError && probeError.kind === "business" && probeError.errcode === 300004) {
|
|
448
|
+
return {
|
|
449
|
+
status: "refused",
|
|
450
|
+
blocker: "wire-business",
|
|
451
|
+
errcode: 300004,
|
|
452
|
+
message: `数据模型不存在(${args.schemaCode})——运行面读取前模型必须已创建并发布字段;先 model.create / configure_model(存在性判据用 model.inspect 的 modelPresent)`,
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
437
457
|
throw error;
|
|
438
458
|
}
|
|
439
459
|
const page = extractBizObjectPage(result.data);
|
package/dist/lanes/workflow.js
CHANGED
|
@@ -1657,7 +1657,10 @@ export function registerWorkflowLane(tools) {
|
|
|
1657
1657
|
.describe("exact-match client-side filter on the returned items' instanceId (the server filterWorkflowInstanceId field is a NOT-equal exclusion, so it is not used)"),
|
|
1658
1658
|
workflowCode: z.string().optional(),
|
|
1659
1659
|
schemaCode: z.string().optional(),
|
|
1660
|
-
page:
|
|
1660
|
+
// r35: an agent sent page:1 expecting the first page and read
|
|
1661
|
+
// `total:1 / count:0` as a contradiction — the page index is ZERO-based
|
|
1662
|
+
// (like data.list). Say so at the parameter itself.
|
|
1663
|
+
page: z.number().int().min(0).optional().describe("页号,0 基(与 data.list 一致);默认 0"),
|
|
1661
1664
|
size: z
|
|
1662
1665
|
.number()
|
|
1663
1666
|
.int()
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stalfh233/omc-cli",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.34",
|
|
4
4
|
"description": "CloudPivot 8.6 configuration capability CLI — governed, fail-closed, coverage-manifest bounded.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
53
|
"commander": "14.0.3",
|
|
54
|
-
"omc-core": "0.4.
|
|
54
|
+
"omc-core": "0.4.34",
|
|
55
55
|
"proper-lockfile": "4.1.2",
|
|
56
56
|
"write-file-atomic": "6.0.0",
|
|
57
57
|
"zod": "^3.23.8",
|
package/skills/omc-start-here.md
CHANGED
|
@@ -23,7 +23,7 @@ be treated as a second source. Read this, then follow the topics it names.
|
|
|
23
23
|
3. 环境名以 `omc doctor` 的「环境=」或 `.omc-env` 为准;工具不确定先用默认 `describe`/`--example`,只有单个工具缺形状才用 `--full`;`describe` 带 `exampleHint`/`recipeHint` 时优先 `--example`。
|
|
24
24
|
4. 只有工具不在 `next` 命中能力内、或要看 purpose/枚举时,才补一次 `commands --json --compact --lane <lane>`。非显然形状(规则图、流程模板、表单布局、RESTFUL)才单独 `describe --example`。
|
|
25
25
|
5. 键名按对象不同:模型 `schemaCode`、默认表单 `sheetCode`(可省略,默认 == 模型 code)、流程 `workflowCode`/`code`——报错会提示同义键;字段族名认不出会给「最接近的族」。
|
|
26
|
-
6.
|
|
26
|
+
6. **写操作串行**:同一环境不要并发写(含多个 `omc call` 并行进程——环境写锁互斥,r31 实证并行 field.create 2/3 被拒);多个写合并为一条 `omc script` 逐行跑(进程内自动串行);`write-lock-held` 是瞬态,等几秒重试一次即可。
|
|
27
27
|
7. **单个对象优先高阶编排工具**:模型+字段用 `configure_model`(一次含全部字段),表单用 `configure_form`,列表用 `listview.configure`(**默认视图已存在,省略 code 即配置它,不要 `listview.create`**)。原子工具留给单点修改。
|
|
28
28
|
8. 拿到 `detailArtifactId` 先 `artifact get <id> --summary` 看结构,再 `--keys/--jq` 取所需;`field.list`/`workflow.*` 默认面已给清单与节点摘要;`workflow.get_draft` 默认面已含 `edges`,改流程只需传 `activities`+`routes`。
|
|
29
29
|
9. 每个 section 读一次即可;同一 topic 不要反复读。
|