@stalfh233/omc-cli 0.4.21 → 0.4.23
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/dist/cli.js +33 -4
- package/dist/execution-metadata.js +21 -1
- package/dist/lanes/rule-graph.js +5 -0
- package/dist/lanes/runtime-data.js +21 -1
- package/dist/rule-support.js +13 -2
- package/package.json +2 -2
- package/skills/omc-capabilities.md +2 -1
package/dist/cli.js
CHANGED
|
@@ -1067,11 +1067,40 @@ async function cmdIntentApply(planPath, options, transport) {
|
|
|
1067
1067
|
}
|
|
1068
1068
|
}
|
|
1069
1069
|
let parsed;
|
|
1070
|
+
let autoPlanned = false;
|
|
1070
1071
|
try {
|
|
1071
1072
|
parsed = JSON.parse(planText);
|
|
1072
1073
|
}
|
|
1073
1074
|
catch (error) {
|
|
1074
|
-
|
|
1075
|
+
// r17 finding (two delegated agents, same wall): `intent apply <file>` read
|
|
1076
|
+
// as "apply this intent file", but only a compiled plan JSON was accepted —
|
|
1077
|
+
// each agent paid a help round-trip to discover plan --plan-out. Accept the
|
|
1078
|
+
// SOURCE intent YAML directly: compile it in-process (same pipeline as
|
|
1079
|
+
// `intent plan`) and apply the resulting plan.
|
|
1080
|
+
const read = readIntentOrInvalid(planPath, transport);
|
|
1081
|
+
if (read.kind !== "exit") {
|
|
1082
|
+
try {
|
|
1083
|
+
const context = makeToolContext({ sessions: new SessionManager(new ConfigStore(defaultPaths())) });
|
|
1084
|
+
const session = await context.sessions.session(options.env);
|
|
1085
|
+
const baseline = await readBaseline(session, read.intent);
|
|
1086
|
+
const compiled = compileIntent(read.intent, baseline);
|
|
1087
|
+
parsed = buildPlan({
|
|
1088
|
+
environment: session.environmentName,
|
|
1089
|
+
environmentFingerprint: session.environmentFingerprint(),
|
|
1090
|
+
compiler: INTENT_COMPILER_VERSION,
|
|
1091
|
+
baseline,
|
|
1092
|
+
intent: read.intent,
|
|
1093
|
+
compiled,
|
|
1094
|
+
});
|
|
1095
|
+
autoPlanned = true;
|
|
1096
|
+
}
|
|
1097
|
+
catch (planError) {
|
|
1098
|
+
return invalid(undefined, `文件既不是合法 plan JSON,也不能编译为意图计划:${planError instanceof Error ? planError.message : String(planError)}(若想先看计划再应用:intent plan <yaml> --env <env> --plan-out <plan.json>)`);
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
else {
|
|
1102
|
+
return invalid(undefined, `plan 文件不是合法 JSON:${error instanceof Error ? error.message : String(error)};意图 YAML 请先 intent plan <yaml> --plan-out <plan.json>,或直接 intent apply <yaml> --env <env>(自动编译)`);
|
|
1103
|
+
}
|
|
1075
1104
|
}
|
|
1076
1105
|
const verified = verifyPlan(parsed);
|
|
1077
1106
|
if (!verified.ok)
|
|
@@ -1137,7 +1166,7 @@ async function cmdIntentApply(planPath, options, transport) {
|
|
|
1137
1166
|
// incomplete or drifted and the agent must re-plan, not retry blindly.
|
|
1138
1167
|
const confirmReadback = async (base) => {
|
|
1139
1168
|
if (options.verify === false)
|
|
1140
|
-
return base;
|
|
1169
|
+
return autoPlanned ? { ...base, plannedFrom: "intent-yaml" } : base;
|
|
1141
1170
|
let verification;
|
|
1142
1171
|
try {
|
|
1143
1172
|
const report = await verifyIntentLive(session, plan.intent);
|
|
@@ -1148,7 +1177,7 @@ async function cmdIntentApply(planPath, options, transport) {
|
|
|
1148
1177
|
}
|
|
1149
1178
|
if (verification.equivalent === true) {
|
|
1150
1179
|
console.error(`intent apply ${planId}:最终读回等价 ✓(意图 ${plan.intent.models.length} 个模型)`);
|
|
1151
|
-
return { ...base, verification };
|
|
1180
|
+
return { ...base, verification, ...(autoPlanned ? { plannedFrom: "intent-yaml" } : {}) };
|
|
1152
1181
|
}
|
|
1153
1182
|
console.error(`intent apply ${planId}:最终读回不等价——${Array.isArray(verification.differences) ? `${verification.differences.length} 项差异` : String(verification.error ?? "读回失败")};不得当作成功`);
|
|
1154
1183
|
return {
|
|
@@ -1447,7 +1476,7 @@ const request = requestId();
|
|
|
1447
1476
|
main().then((code) => {
|
|
1448
1477
|
// Commands that own their stdout never grow a trailing envelope. `--version`/
|
|
1449
1478
|
// `-V` are here because their short-circuit already wrote the bare version.
|
|
1450
|
-
if (argv[2] === "call" || argv[2] === "artifact" || argv[2] === "commands" || argv[2] === "describe" || argv[2] === "contract" || argv[2] === "capabilities" || argv[2] === "capability" || argv[2] === "next" || argv[2] === "usage" || argv[2] === "intent" || argv[2] === "help" || argv[2] === "--version" || argv[2] === "-V") {
|
|
1479
|
+
if (argv[2] === "call" || argv[2] === "script" || argv[2] === "artifact" || argv[2] === "commands" || argv[2] === "describe" || argv[2] === "contract" || argv[2] === "capabilities" || argv[2] === "capability" || argv[2] === "next" || argv[2] === "usage" || argv[2] === "intent" || argv[2] === "help" || argv[2] === "--version" || argv[2] === "-V") {
|
|
1451
1480
|
completeUsage(code);
|
|
1452
1481
|
finish(code);
|
|
1453
1482
|
return;
|
|
@@ -419,6 +419,26 @@ const workflowSave = Object.freeze({
|
|
|
419
419
|
requiresEnvironmentDerivedIds: true,
|
|
420
420
|
prerequisiteData: { source: "workflow.get_published; data.save authoritative readback", fields: ["workflow.code", "bizObjectId", "workflowInstanceId", "workItemId"] },
|
|
421
421
|
});
|
|
422
|
+
const plainSubmit = Object.freeze({
|
|
423
|
+
id: "plain-data-submit",
|
|
424
|
+
title: "无流程模型:草稿直提(DRAFT→COMPLETED,触发 Create 规则)",
|
|
425
|
+
status: "requires-prior-readback",
|
|
426
|
+
tool: "data.submit",
|
|
427
|
+
args: { environment, schemaCode: model, bizObjectId: "<data.save.performed.bizObjectId>", formData: { name: "recipe draft" } },
|
|
428
|
+
preconditions: ["模型无 workflowCode(有流程走 workflow-backed-data-submit)", "data.save 已成功并读回 DRAFT 草稿", "bizObjectId 取 data.save 读回,不要手造"],
|
|
429
|
+
conditionalRequirements: ["无需 workflowInstanceId/workItemId——无流程模型 submit 自持久化(runtime/form/submit 就地置 COMPLETED)"],
|
|
430
|
+
priorToolReferences: [
|
|
431
|
+
{ tool: "data.save", args: { environment, schemaCode: model, formData: { name: "recipe draft" } }, purpose: "建 DRAFT 草稿并读回 bizObjectId(其 draftRuleHint 会指回本链)" },
|
|
432
|
+
],
|
|
433
|
+
dryRun: { supported: true, command: `omc call data.submit --env ${environment} --dry-run --args '<recipe.args>'`, notes: "dry-run 验证参数关系,不推进状态" },
|
|
434
|
+
expectedReadback: { performed: { errcode: 0 }, readback: { sequenceStatus: "COMPLETED" } },
|
|
435
|
+
refusalRecovery: [
|
|
436
|
+
{ code: "submit-biz-object-absent", action: "bizObjectId 在平台无行(304001)——回 data.save 重建草稿后再直提" },
|
|
437
|
+
{ code: "invalid-input", action: "带了 workItemId/workflowInstanceId 时必须同时带 bizObjectId(fail-closed)" },
|
|
438
|
+
],
|
|
439
|
+
notes: "规则验证路径:Create/Update 规则只在 COMPLETED 分发——plain save 是 DRAFT 不触发(DomainServiceImpl.java:240-248);要验证规则效果就用本链提交后 data.load 核对",
|
|
440
|
+
requiresEnvironmentDerivedIds: true,
|
|
441
|
+
});
|
|
422
442
|
const workflowSubmit = Object.freeze({
|
|
423
443
|
id: "workflow-backed-data-submit",
|
|
424
444
|
title: "消费 data.save 读回身份并提交 workflow work item",
|
|
@@ -1080,7 +1100,7 @@ export const EXECUTION_METADATA = Object.freeze({
|
|
|
1080
1100
|
"workflow.update": Object.freeze({ recipes: [workflowTemplateUpdate, workflowConditionalBranch], relatedTools: ["workflow.get_draft", "workflow.publish"] }),
|
|
1081
1101
|
"workflow.bindRule": Object.freeze({ recipes: [workflowBinding], relatedTools: ["workflow.create", "workflow.get_draft", "workflow.get_published", "rule.list"] }),
|
|
1082
1102
|
"data.save": Object.freeze({ recipes: [workflowSave], relatedTools: ["workflow.get_published", "data.submit", "workitem.list"] }),
|
|
1083
|
-
"data.submit": Object.freeze({ recipes: [workflowSubmit], relatedTools: ["data.save", "workitem.list", "data.load"] }),
|
|
1103
|
+
"data.submit": Object.freeze({ recipes: [plainSubmit, workflowSubmit], relatedTools: ["data.save", "workitem.list", "data.load"] }),
|
|
1084
1104
|
"service.register": Object.freeze({ recipes: [restfulQueryService], relatedTools: ["service.category.list", "service.adapter_config", "service.get", "method.get"] }),
|
|
1085
1105
|
"method.update": Object.freeze({ recipes: [bizserviceSelfCallCorp], relatedTools: ["method.get", "method.create", "service.get"] }),
|
|
1086
1106
|
"listview.configure": Object.freeze({ recipes: [listViewConfigure], relatedTools: ["listview.list", "listview.get", "field.list", "field.publish"] }),
|
package/dist/lanes/rule-graph.js
CHANGED
|
@@ -1405,6 +1405,11 @@ export function validateRuleNodeGraph(graph, ruleCode, schemaCode) {
|
|
|
1405
1405
|
const endNodes = nodeRecords.filter((node) => node.nodeType === "END");
|
|
1406
1406
|
if (startNodes.length !== 1)
|
|
1407
1407
|
blockers.push({ code: "rule-graph-start-count", message: `必须恰好 1 个 START 节点(当前 ${startNodes.length})` });
|
|
1408
|
+
// r15 harness finding: a multi-node graph with ZERO routes saves fine but
|
|
1409
|
+
// the platform publish answers 550014 孤立节点 for every non-boundary node —
|
|
1410
|
+
// usually the caller passed the WRONG key (edges/from/to instead of
|
|
1411
|
+
// routes/preNode/postNode, which zod strips silently). Refuse locally, name
|
|
1412
|
+
// the right shape, save the publish round-trip.
|
|
1408
1413
|
if (endNodes.length < 1)
|
|
1409
1414
|
blockers.push({ code: "rule-graph-end-missing", message: "至少需要 1 个 END 节点" });
|
|
1410
1415
|
const systemPersistenceTypes = new Set(["SYSTEM_CREATE", "SYSTEM_UPDATE"]);
|
|
@@ -11,6 +11,7 @@ import { wireCall, wireRows, WireError } from "../wire.js";
|
|
|
11
11
|
import { CHILD_TABLE_PROPERTY_TYPE, runtimeWriteWithAppMapping } from "../runtime-support.js";
|
|
12
12
|
import { getReplayToken, searchWorkItems, wireString } from "../workflow-support.js";
|
|
13
13
|
import { ENGINE_OWNED_KEYS, RUNTIME_DATA_LANE_CONTRACT, RUNTIME_DATA_WIRE, runtimeFormDataArg, } from "./runtime-data-contract.js";
|
|
14
|
+
import { SYSTEM_FIELD_CODES } from "../field-families.js";
|
|
14
15
|
const RUNTIME_LANE = RUNTIME_DATA_LANE_CONTRACT.lane;
|
|
15
16
|
const RUNTIME_TOOLS = RUNTIME_DATA_LANE_CONTRACT.tools;
|
|
16
17
|
/**
|
|
@@ -462,6 +463,16 @@ export function registerRuntimeDataLane(tools) {
|
|
|
462
463
|
// for EVERY selected column, and the published metadata confirms those
|
|
463
464
|
// are real model fields, surface the recovery hint. `items` is never
|
|
464
465
|
// reshaped — the hint is a sibling readback field.
|
|
466
|
+
// Candidate keys first (pure — zero wires); metadata only when there is
|
|
467
|
+
// at least one all-null column to classify (preserves the rawCondition
|
|
468
|
+
// path's zero-extra-wire contract when there is nothing to hint).
|
|
469
|
+
const businessNullColumns = async () => {
|
|
470
|
+
const candidates = Object.keys(items[0]?.fields ?? {}).filter((field) => !SYSTEM_FIELD_CODES.has(field) && items.every((item) => item.fields[field] === null));
|
|
471
|
+
if (candidates.length === 0)
|
|
472
|
+
return [];
|
|
473
|
+
const typeByCode = await publishedTypeByCode();
|
|
474
|
+
return candidates.filter((field) => typeByCode.has(field));
|
|
475
|
+
};
|
|
465
476
|
if (fields &&
|
|
466
477
|
fields.length > 0 &&
|
|
467
478
|
items.length > 0 &&
|
|
@@ -470,6 +481,15 @@ export function registerRuntimeDataLane(tools) {
|
|
|
470
481
|
if (fields.every((field) => typeByCode.has(field)))
|
|
471
482
|
readback.projectionHint = RUNTIME_DATA_WIRE.projectionHint;
|
|
472
483
|
}
|
|
484
|
+
else if (!fields && items.length > 0) {
|
|
485
|
+
// r17 finding: BOTH delegated agents tripped on implicit projection —
|
|
486
|
+
// business fields absent from the list view render as null in every
|
|
487
|
+
// row with no explanation. Name the null'd business columns so the
|
|
488
|
+
// agent reads data.load instead of misdiagnosing "data lost".
|
|
489
|
+
const nullColumns = await businessNullColumns();
|
|
490
|
+
if (nullColumns.length > 0)
|
|
491
|
+
readback.projectionHint = `这些列在默认视图未配置(值可能存在但列表投影为 null):${nullColumns.join("、")};先用 listview.configure 配置列,或用 data.load 读单条权威值`;
|
|
492
|
+
}
|
|
473
493
|
return readback;
|
|
474
494
|
}, { full: args.full === true, target: `data.list:${JSON.stringify({ schemaCode: args.schemaCode, queryCode: args.queryCode ?? args.schemaCode, filter: args.filter, filters: args.filters, filterGroup: args.filterGroup, rawCondition: args.rawCondition, displayFields: args.displayFields, columns: args.columns, page: args.page ?? 0, size: args.size ?? 10 })}` });
|
|
475
495
|
},
|
|
@@ -620,7 +640,7 @@ export function registerRuntimeDataLane(tools) {
|
|
|
620
640
|
// branch) — business rules only run once data.submit advances the
|
|
621
641
|
// row. Saying so on every plain-save readback prevents the
|
|
622
642
|
// "rule didn't fire" misdiagnosis.
|
|
623
|
-
draftRuleHint: "草稿态(DRAFT)保存不触发业务规则——DRAFT 行跳过 Create/Update 分发(DomainServiceImpl.java:240-248);data.submit 提交(DRAFT→COMPLETED)后才触发 Create
|
|
643
|
+
draftRuleHint: "草稿态(DRAFT)保存不触发业务规则——DRAFT 行跳过 Create/Update 分发(DomainServiceImpl.java:240-248);data.submit 提交(DRAFT→COMPLETED)后才触发 Create 规则。无流程模型直提形状:data.submit {schemaCode, bizObjectId: 本读回的 bizObjectId, formData}(免流程身份,submit 自持久化)",
|
|
624
644
|
},
|
|
625
645
|
};
|
|
626
646
|
});
|
package/dist/rule-support.js
CHANGED
|
@@ -458,8 +458,19 @@ export async function executeRuleSaveWrite(session, args) {
|
|
|
458
458
|
validation: blockers,
|
|
459
459
|
});
|
|
460
460
|
}
|
|
461
|
-
const nodes = preparedGraph.nodes
|
|
462
|
-
const routes = preparedGraph.routes
|
|
461
|
+
const nodes = Array.isArray(preparedGraph.nodes) ? preparedGraph.nodes : [];
|
|
462
|
+
const routes = Array.isArray(preparedGraph.routes) ? preparedGraph.routes : [];
|
|
463
|
+
// r15 harness finding: a multi-node graph with ZERO routes SAVES fine but the
|
|
464
|
+
// platform publish answers 550014 孤立节点 for every non-boundary node — the
|
|
465
|
+
// usual cause is the WRONG edge key (edges/from/to instead of routes/
|
|
466
|
+
// preNode/postNode, silently stripped). Refuse at the save gate, name the
|
|
467
|
+
// right shape, save the publish round-trip.
|
|
468
|
+
if (nodes.length >= 2 && routes.length === 0)
|
|
469
|
+
throw new ToolRefusalError({
|
|
470
|
+
status: "refused",
|
|
471
|
+
blocker: "rule-graph-routes-empty",
|
|
472
|
+
message: `图有 ${nodes.length} 个节点但 0 条 routes——平台发布会判「孤立节点」(550014)。边用 routes:[{preNode:"Start",postNode:"UPDATE_1"},...](不是 edges/from/to,那会被静默剥掉)`,
|
|
473
|
+
});
|
|
463
474
|
await assertCreateSelectionCompatibility(session, args.schemaCode, Array.isArray(nodes) ? nodes : []);
|
|
464
475
|
await assertBizActionMappingFieldsExist(session, args.schemaCode, Array.isArray(nodes) ? nodes : []);
|
|
465
476
|
// Ticket 96: phantom-schema gate — BEFORE the first businessrule wire call
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stalfh233/omc-cli",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.23",
|
|
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.23",
|
|
55
55
|
"proper-lockfile": "4.1.2",
|
|
56
56
|
"write-file-atomic": "6.0.0",
|
|
57
57
|
"zod": "^3.23.8",
|
|
@@ -170,7 +170,8 @@ only the section for the capability you need —
|
|
|
170
170
|
- **工具**:`data.list`、`data.load`、`data.save`、`data.update`、`data.delete`、`data.submit`
|
|
171
171
|
- **recipe**:
|
|
172
172
|
- `omc describe data.save --example workflow-backed-data-save`
|
|
173
|
-
- `omc describe data.submit --example workflow-backed-data-submit
|
|
173
|
+
- `omc describe data.submit --example workflow-backed-data-submit`(有流程)
|
|
174
|
+
- 无流程模型的规则验证路径:`data.save`(DRAFT,不触发规则)→ `data.submit {schemaCode, bizObjectId, formData}` 直提(COMPLETED,触发 Create 规则)——示例 `omc describe data.submit --example plain-data-submit`
|
|
174
175
|
- **边界 / 陷阱**:读实际业务记录是 OMC 独有能力。**计算字段(FORMULA/子表计算)不由 API 回填**——写测试数据带预算值,公式正确性人工前台确认;带子表计算字段的模型 API 写可能 50000(服务端重算 NPE),前台正常。
|
|
175
176
|
|
|
176
177
|
---
|