@stalfh233/omc-cli 0.4.68 → 0.4.70

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/endpoints.js CHANGED
@@ -55,6 +55,7 @@ export const ENDPOINTS = {
55
55
  businessRuleCreate: "/api/api/app/businessrule/create",
56
56
  businessRuleUpdate: "/api/api/app/businessrule/update",
57
57
  businessRulePublish: "/api/api/app/businessrule/publish",
58
+ businessRuleNodeRunMap: "/api/api/app/businessrule/node_run_map",
58
59
  businessRuleEnable: "/api/api/app/businessrule/enable",
59
60
  businessRuleDelete: "/api/api/app/businessrule/delete",
60
61
  dataRuleCreate: "/api/api/app/bizdatarule/create",
@@ -3,7 +3,7 @@ import { ENDPOINTS } from "../endpoints.js";
3
3
  import { writeTool } from "../write.js";
4
4
  import { registerPackageTool } from "../package-tool.js";
5
5
  import { writeSummary } from "../human-summary.js";
6
- import { executeRuleSaveWrite, ruleNodeGraphArg, assertRuleSchemaExists } from "../rule-support.js";
6
+ import { executeRuleSaveWrite, ruleNodeGraphArg, assertRuleSchemaExists, explainRuleSelfCheck } from "../rule-support.js";
7
7
  import { ToolRefusalError } from "../tool-types.js";
8
8
  import { prepareRuleGraph, validateRuleNodeGraph, PLATFORM_TRIGGER_RULE_CODES } from "./rule-graph.js";
9
9
  import { wireCall, wireRows, WireError } from "../wire.js";
@@ -52,7 +52,25 @@ export async function executeRuleEnableWrite(session, args) {
52
52
  const full = (await wireCall(session, { path: ENDPOINTS.businessRuleGet, query: { id: String(row.id) } })).data;
53
53
  if (!full)
54
54
  throw new WireError("business", `rule ${args.ruleCode} 完整模型读回为空(businessrule/get id ${row.id}),拒绝用列表投影发布`, 302024);
55
- const published = await wireCall(session, { path: ENDPOINTS.businessRulePublish, method: "POST", body: { ...full, enabled: true, status: "ENABLED" } });
55
+ let published;
56
+ try {
57
+ published = await wireCall(session, { path: ENDPOINTS.businessRulePublish, method: "POST", body: { ...full, enabled: true, status: "ENABLED" } });
58
+ }
59
+ catch (error) {
60
+ // 550018 is the platform's self-check refusal (see explainRuleSelfCheck).
61
+ if (error instanceof WireError && error.errcode === 550018) {
62
+ const detail = await explainRuleSelfCheck(session, { ...full, enabled: true, status: "ENABLED" });
63
+ throw new ToolRefusalError({
64
+ status: "refused",
65
+ blocker: "rule-self-check-failed",
66
+ schemaCode: args.schemaCode,
67
+ ruleCode: args.ruleCode,
68
+ ...detail,
69
+ message: `规则 ${args.ruleCode} 启用时被平台自检拒绝(550018)。${detail.nodeChecks.length > 0 ? `问题节点:${JSON.stringify(detail.nodeChecks).slice(0, 600)}` : "先用 field.list(all:true) 核对引用的字段是否已发布。"}`,
70
+ });
71
+ }
72
+ throw error;
73
+ }
56
74
  let enableErrcode = 0;
57
75
  let alreadyEnabled = false;
58
76
  try {
@@ -1048,22 +1048,14 @@ export function registerRuntimeDataLane(tools) {
1048
1048
  draftObjectId = targetObjectId;
1049
1049
  }
1050
1050
  else if (formType === "2") {
1051
- const saved = await runtimeWriteWithAppMapping(session, args.schemaCode, 50000, "data.submit 运行时提交稳定失败(50000)", () => wireCall(session, {
1052
- path: ENDPOINTS.runtimeFormSave,
1053
- method: "POST",
1054
- body: { bizObject: { schemaCode: args.schemaCode, sheetCode, data: formData } },
1055
- }));
1056
- const savedData = (saved.data ?? {});
1057
- draftObjectId = typeof savedData.bizObjectId === "string" ? savedData.bizObjectId : typeof savedData.objectId === "string" ? savedData.objectId : null;
1058
- if (!draftObjectId)
1059
- throw new WireError("business", "submit chain: draft save returned no bizObjectId", 304001);
1060
- // No post-save form/load (ticket 18): the submit persists
1061
- // bizObject.data itself and updateBizObject merges patch-style onto
1062
- // the stored row, and initSubmitBizObject.setOwner re-reads the
1063
- // stored row's owner server-side — so formData + the saved id is the
1064
- // complete submit payload.
1051
+ // Live 1.1.25 (r7-vehicle 2026-10-21): the previous chain did
1052
+ // form/save (creating a DRAFT row) and then submitted with that id —
1053
+ // the platform INSERTED A SECOND ROW instead of completing the first,
1054
+ // leaving one COMPLETED + one orphan DRAFT per submit (verified in
1055
+ // ia38g_veh_vehicle: same plateNo, same second, statuses COMPLETED and
1056
+ // DRAFT). form/submit persists bizObject.data itself (ticket 18), so
1057
+ // skip the pre-save and let the platform create+complete in one call.
1065
1058
  payload.bizObject = {
1066
- id: draftObjectId,
1067
1059
  schemaCode: args.schemaCode,
1068
1060
  sheetCode,
1069
1061
  data: stripEngineOwnedKeys(formData),
@@ -77,7 +77,7 @@ const ruleBranchGraph = Object.freeze({
77
77
  conditionalRequirements: [
78
78
  "**系统节点顺序:BRANCH 必须排在 `SYSTEM_CREATE` 之后**——BRANCH 直接接在 START 后会运行时静默不生效、动作不回写(工具 fail closed)。注意这与 BIZ_ACTION 回写场景相反(那里系统节点要在动作之后)——本 recipe 是分支场景。**`SYSTEM_CREATE` 必须显式写**(工具对分支图的自动注入不可靠)。",
79
79
  "**出口条件叶只 4 个键** `{currentSchemaDataItem(左值字段),ruleDataConditionType(操作符),ruleValueType,value}`;用 UI 键 `targetSchemaDataItem`+`ruleConditionType` 会 save+publish 过但运行时 50000(工具 fail closed `rule-graph-branch-condition-keys`)。",
80
- "**下列键可省、工具自动补**(省 token;真机验证极简图 1170B vs 全显式 1690B,−31%):每个节点的 `nodeName`;BRANCH 的 `inputParam:\"_input\"`/`judgeMethod:\"DEFAULT\"`;自更新 UPDATE(`targetObjectCode`=本模型)的 `inputParam:\"_input\"`/`filterCondition`(身份匹配);非兜底路由的 `routeCondition:true`/`matchType`。仅 BRANCH 的 `sourceInputMethod` 与兜底 else 的 `routeCondition:false` 需显式。**跨模型写(UPDATE/CREATE/DELETE 的 `targetObjectCode`≠规则模型)运行时未验证:lint 给非阻断 warning `rule-graph-cross-model-unverified`(2026-09-27 真机 data.submit 探测只有同模型 UPDATE 生效),交付前必须实测;稳妥用同模型回写或 BIZ_ACTION/二开。**",
80
+ "**下列键可省、工具自动补**(省 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` 不可用。",
81
81
  "预置规则的系统节点类型必须与 ruleCode 匹配(Create→SYSTEM_CREATE);放 SYSTEM_UPDATE 会 data.submit 50000(工具 fail closed)。",
82
82
  ],
83
83
  priorToolReferences: [
@@ -463,6 +463,71 @@ export async function computeCarriedNodeCodes(session, schemaCode, ruleCode, pre
463
463
  return undefined; // 取不到存量图 → 维持严格闭集,行为与放宽前一致
464
464
  }
465
465
  }
466
+ /**
467
+ * 550018 is the platform's catch-all "rule self-check failed" (ErrorCode
468
+ * BUSINESS_RULE_PARAM_ERROR from BusinessRuleFacadeImpl.publishBusinessRule when
469
+ * checkBizRuleIsEnable() is false). The message names nothing, and the real原因
470
+ * is one of: a referenced property that does not exist, a missing org object, or
471
+ * a dead cycle. `businessrule/node_run_map` (BusinessRuleController.getNodeRunMaps
472
+ * → checkBizRuleError) returns exactly that per node, so a refusal can name the
473
+ * offending node and field instead of asking the caller to open the designer.
474
+ */
475
+ const RULE_SELF_CHECK_ERRCODE = 550018;
476
+ function ruleSelfCheckMessage(ruleCode, checks) {
477
+ if (checks.length === 0)
478
+ return `规则 ${ruleCode} 发布被平台自检拒绝(550018「保存规则失败,规则存在异常」),但 node_run_map 未给出具体节点——常见原因:引用的字段未发布/不存在、组织对象失效、或存在死循环。先用 field.list(all:true) 核对引用的字段是否已发布。`;
479
+ const detail = checks.map((check) => {
480
+ const parts = [];
481
+ const missing = check.missingProperties;
482
+ if (missing?.length)
483
+ parts.push(`字段不存在/未发布:${missing.map((item) => `${item.objectName ?? "?"}.${item.propertyName ?? item.propertyCode ?? "?"}`).join("、")}`);
484
+ if (check.missingOrgObjects)
485
+ parts.push("组织对象不存在");
486
+ if (check.deadCycles)
487
+ parts.push("存在死循环");
488
+ return `${String(check.nodeCode ?? "?")}${check.nodeName ? `(${String(check.nodeName)})` : ""} ${parts.join(";")}`;
489
+ });
490
+ return `规则 ${ruleCode} 发布被平台自检拒绝(550018):${detail.join(" | ")}。节点内引用了不存在或未发布的字段时,保存能过、发布必拒——先补字段并 field.publish,再重发。`;
491
+ }
492
+ export async function explainRuleSelfCheck(session, model) {
493
+ try {
494
+ const response = await wireCall(session, { path: ENDPOINTS.businessRuleNodeRunMap, method: "POST", body: model });
495
+ const rows = wireRows(response.data);
496
+ const nodeChecks = rows.flatMap((row) => {
497
+ // Wire shape (QuoteObjectVO): {objectCode, objectName, quotePropertyList:
498
+ // [{propertyCode, propertyName, exist, propertyPublished}]} — the property
499
+ // sits one level deeper than the object.
500
+ const missingProperties = Array.isArray(row.notExistPropertyObjectList)
501
+ ? row.notExistPropertyObjectList.flatMap((object) => {
502
+ const properties = Array.isArray(object.quotePropertyList) ? object.quotePropertyList : [];
503
+ const broken = properties.filter((property) => property.exist === false || property.propertyPublished === false);
504
+ const source = broken.length > 0 ? broken : properties;
505
+ return source.map((property) => ({
506
+ objectName: object.objectName ?? object.objectCode ?? null,
507
+ propertyCode: property.propertyCode ?? null,
508
+ propertyName: property.propertyName ?? null,
509
+ published: property.propertyPublished ?? null,
510
+ }));
511
+ })
512
+ : [];
513
+ const missingOrgObjects = Array.isArray(row.notExistOrgObjectList) ? row.notExistOrgObjectList : [];
514
+ const deadCycles = Array.isArray(row.deadCycleRunMapList) ? row.deadCycleRunMapList : [];
515
+ if (missingProperties.length === 0 && missingOrgObjects.length === 0 && deadCycles.length === 0)
516
+ return [];
517
+ return [{
518
+ nodeCode: row.nodeCode ?? null,
519
+ nodeName: row.nodeName ?? null,
520
+ ...(missingProperties.length > 0 ? { missingProperties } : {}),
521
+ ...(missingOrgObjects.length > 0 ? { missingOrgObjects } : {}),
522
+ ...(deadCycles.length > 0 ? { deadCycles } : {}),
523
+ }];
524
+ });
525
+ return { nodeChecks };
526
+ }
527
+ catch {
528
+ return { nodeChecks: [] };
529
+ }
530
+ }
466
531
  export async function executeRuleSaveWrite(session, args) {
467
532
  // Pre-flight dry lint (ticket 14): a mis-shaped graph must be refused BEFORE
468
533
  // any wire call, with structured blockers locating the node/key — never
@@ -594,7 +659,23 @@ export async function executeRuleSaveWrite(session, args) {
594
659
  // schedule and a TIMING_TASK rule never gets a quartz job (2026-10-06). Use
595
660
  // the authoritative `businessrule/get?id` model (same as rule.get/enable).
596
661
  const full = (await wireCall(session, { path: ENDPOINTS.businessRuleGet, query: { id: String(row.id) } })).data;
597
- await wireCall(session, { path: ENDPOINTS.businessRulePublish, method: "POST", body: full ?? row });
662
+ try {
663
+ await wireCall(session, { path: ENDPOINTS.businessRulePublish, method: "POST", body: full ?? row });
664
+ }
665
+ catch (error) {
666
+ if (error instanceof WireError && error.errcode === RULE_SELF_CHECK_ERRCODE) {
667
+ const detail = await explainRuleSelfCheck(session, (full ?? row));
668
+ throw new ToolRefusalError({
669
+ status: "refused",
670
+ blocker: "rule-self-check-failed",
671
+ schemaCode: args.schemaCode,
672
+ ruleCode: args.ruleCode,
673
+ ...detail,
674
+ message: ruleSelfCheckMessage(args.ruleCode, detail.nodeChecks),
675
+ });
676
+ }
677
+ throw error;
678
+ }
598
679
  }
599
680
  const effective = await currentRow();
600
681
  // Live 8.6.26 (2026-09-17): businessrule/publish mints a version but does
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stalfh233/omc-cli",
3
- "version": "0.4.68",
3
+ "version": "0.4.70",
4
4
  "description": "CloudPivot 8.6 configuration capability CLI — governed, fail-closed, coverage-manifest bounded.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -50,9 +50,9 @@
50
50
  "access": "public"
51
51
  },
52
52
  "dependencies": {
53
- "@stalfh233/omc-fake-platform": "0.4.68",
53
+ "@stalfh233/omc-fake-platform": "0.4.70",
54
54
  "commander": "14.0.3",
55
- "omc-core": "0.4.68",
55
+ "omc-core": "0.4.70",
56
56
  "proper-lockfile": "4.1.2",
57
57
  "write-file-atomic": "6.0.0",
58
58
  "zod": "^3.23.8",