@stalfh233/omc-cli 0.4.35 → 0.4.36

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 CHANGED
@@ -36,6 +36,7 @@ import { listSkills, readSkill, readSkillSection } from "./skills.js";
36
36
  import { parseToolInput, ToolInputError, ToolRefusalError } from "./tool-types.js";
37
37
  import { WireError, wireCall } from "./wire.js";
38
38
  import { ENDPOINTS } from "./endpoints.js";
39
+ import { extractBizObjectPage } from "./lanes/runtime-data.js";
39
40
  import { makeToolContext } from "./context.js";
40
41
  import { SessionManager } from "./session-manager.js";
41
42
  async function ask(question) {
@@ -843,6 +844,23 @@ async function cmdIntentPlan(file, environment, planOut, transport) {
843
844
  const planJson = JSON.stringify(plan, null, 2) + "\n";
844
845
  const artifact = createArtifact(artifactRoot(), planJson);
845
846
  const warnings = intentWarnings(intent);
847
+ // r39: a destructive plan that deletes a model carrying runtime data got
848
+ // ZERO warning — the approval object must surface the blast radius. One
849
+ // cheap total-only query per declared-absent model (never blocks the plan:
850
+ // an unreadable runtime face just means no count to show).
851
+ for (const model of intent.models) {
852
+ if (model.absent !== true)
853
+ continue;
854
+ try {
855
+ const query = await wireCall(session, { path: ENDPOINTS.runtimeQueryList, method: "POST", body: { schemaCode: model.code, queryCode: model.code, filters: [], mobile: false, page: 0, size: 1, queryVersion: 1, showTotal: true } });
856
+ const total = extractBizObjectPage(query.data).total;
857
+ if (total > 0)
858
+ warnings.push({ resource: `model:${model.code}`, code: "destructive-with-data", message: `模型 ${model.code} 含 ${total} 条运行数据,absent:true 删除将一并销毁(不可恢复)——确认后再 apply` });
859
+ }
860
+ catch {
861
+ // 运行面不可读(模型/视图未就绪等)= 无法计数,不阻塞 plan
862
+ }
863
+ }
846
864
  const toolCounts = new Map();
847
865
  for (const change of compiled.changes)
848
866
  toolCounts.set(change.tool, (toolCounts.get(change.tool) ?? 0) + 1);
@@ -869,6 +887,7 @@ async function cmdIntentPlan(file, environment, planOut, transport) {
869
887
  changeSummary,
870
888
  planArtifactId: artifact.artifactId,
871
889
  planArtifactSha256: artifact.sha256,
890
+ ...(warnings.length > 0 ? { warnings } : {}),
872
891
  nextAction: `omc intent apply ${planOut}`,
873
892
  };
874
893
  }
@@ -1176,12 +1195,24 @@ async function cmdIntentApply(planPath, options, transport) {
1176
1195
  if (options.verify === false)
1177
1196
  return autoPlanned ? { ...base, plannedFrom: "intent-yaml" } : base;
1178
1197
  let verification;
1179
- try {
1180
- const report = await verifyIntentLive(session, plan.intent);
1181
- verification = { ...dietIntentLive(report, context), status: report.equivalent === true ? "ok" : "verification-failed" };
1182
- }
1183
- catch (error) {
1184
- verification = { status: "error", equivalent: false, error: error instanceof Error ? error.message : String(error) };
1198
+ // r39: a destructive (absent:true) delete tears the model down in
1199
+ // stages — fields/views drop first, the model shell lags a moment. An
1200
+ // immediate readback then reports a phantom mismatch. Retry the live
1201
+ // verification briefly (bounded) before declaring failure.
1202
+ const declaresAbsence = plan.intent.models.some((model) => model.absent === true);
1203
+ const attempts = declaresAbsence ? 3 : 1;
1204
+ for (let attempt = 1;; attempt++) {
1205
+ try {
1206
+ const report = await verifyIntentLive(session, plan.intent);
1207
+ verification = { ...dietIntentLive(report, context), status: report.equivalent === true ? "ok" : "verification-failed" };
1208
+ }
1209
+ catch (error) {
1210
+ verification = { status: "error", equivalent: false, error: error instanceof Error ? error.message : String(error) };
1211
+ }
1212
+ if (verification.equivalent === true || attempt >= attempts)
1213
+ break;
1214
+ console.error(`intent apply ${planId}:删除后读回暂不等价(第 ${attempt}/${attempts} 次)——疑似删除传播中,${attempt === attempts - 1 ? "最后" : "1s"}后再核`);
1215
+ await new Promise((resolve) => setTimeout(resolve, 1000));
1185
1216
  }
1186
1217
  if (verification.equivalent === true) {
1187
1218
  console.error(`intent apply ${planId}:最终读回等价 ✓(意图 ${plan.intent.models.length} 个模型)`);
@@ -1193,6 +1224,11 @@ async function cmdIntentApply(planPath, options, transport) {
1193
1224
  status: "refused",
1194
1225
  blocker: "intent-readback-mismatch",
1195
1226
  verification,
1227
+ ...(declaresAbsence
1228
+ ? {
1229
+ note: "本变更含 absent:true 删除:平台删除分阶段传播(字段/视图先撤、模型壳后撤),已做 3 次间隔读回仍不等价——先用 model.inspect 的 modelPresent 独立复核再判定;若 modelPresent:false 则删除实际已生效",
1230
+ }
1231
+ : {}),
1196
1232
  message: "变更已执行,但最终权威读回与意图不等价:交付可能不完整或环境已漂移。按 differences 定位后重新 intent plan(不要盲重试)",
1197
1233
  nextAction: "inspect verification.differences, then re-run intent plan against the live baseline",
1198
1234
  };
@@ -311,6 +311,7 @@ const workflowTemplateUpdate = Object.freeze({
311
311
  "**activities 用数组**(也接受 {0:…,1:…} 索引对象);每个活动必须带 activityCode(Activity1..N)与 activityType,缺了会被拒——以 get_draft 的值为基线改,不要凭记忆拼;",
312
312
  "**一个模型只能有一个流程**:workflow.create 对已有流程的模型会拒(一个模型只能有一个流程);要改现有流程直接 workflow.update,不要另建新 code;",
313
313
  "多级审批:为每个审批节点各写一个 PARTICIPANT 活动(activityCode 唯一),END 放最后;数组里按顺序就是审批链顺序;新增的审批节点由工具自动接入链路(补全到 END 的连线),无需自己写 routes;",
314
+ "**{Originator}=发起人自己**:审批节点用它=提交人自审自己——提交人与审批人同账号时平台可能直接自动通过(COMPLETED,无待办;r40 实证,与节点结构有关)。需要独立审批人时绑定具体用户 id 或 `FindUserByRole({Originator.OU},\"<角色code>\",\"random\")`;验收要 PENDING 待办时先确认审批人≠提交人;",
314
315
  "**participant 必须是平台可解析的三种形态之一(真实生产 2026-09-23 观察)**:(a) 关键字 `{Originator}`;(b) 参与者对象数组 JSON `[{\"code\":\"<角色code>\",\"corpId\":\"<corpId|other>\",\"id\":\"<roleId>\",\"name\":\"<角色名>\",\"type\":2}]`(type 2=角色,3=用户;多个用 `+` 连接);(c) 函数 `FindUserByRole({Originator.OU},\"<roleCode>\",\"random\")`。**自由文本角色名(如 `部门负责人`)能 publish 但解析不到人、不生成待办**——先用 `org.role.list` / `org.department.list` 拿 code/id 再构造。工具会在 readback 里以 `participantWarnings` 标出自由文本。",
315
316
  "**审批节点必须带 sheetCode(=模型默认表单槽位,通常等于 schemaCode)——S29 契约:participant sheetCode 为空 publish 会 401026 WORKFLOW_ACTIVITY_NO_SHEET。工具已自动回填,正常无需手填**;",
316
317
  "节点类型 START / PARTICIPANT / END;省略的既有节点由 merge 保留;",
@@ -30,7 +30,7 @@ async function resolvePublishedWorkflowCode(session, schemaCode) {
30
30
  const code = template && typeof template.workflowCode === "string" && template.workflowCode.length > 0 ? template.workflowCode : null;
31
31
  return code;
32
32
  }
33
- function extractBizObjectPage(value) {
33
+ export function extractBizObjectPage(value) {
34
34
  const root = value && typeof value === "object" ? value : {};
35
35
  const candidate = root.bizObjectPage && typeof root.bizObjectPage === "object" ? root.bizObjectPage : root;
36
36
  const content = Array.isArray(candidate.content) ? candidate.content.filter((row) => !!row && typeof row === "object") : [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stalfh233/omc-cli",
3
- "version": "0.4.35",
3
+ "version": "0.4.36",
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.35",
54
+ "omc-core": "0.4.36",
55
55
  "proper-lockfile": "4.1.2",
56
56
  "write-file-atomic": "6.0.0",
57
57
  "zod": "^3.23.8",
@@ -54,7 +54,7 @@ only the section for the capability you need —
54
54
 
55
55
  - **能做哪些事**:列出应用包、创建、改名/分组移动、删除(删除级联其模型)。
56
56
  - **工具**:`app.list`、`app.create`、`app.update`、`app.delete`
57
- - **边界 / 陷阱**:环境已带默认 `applicationCode`,建模**不必**先建应用;未进 intent,用 `omc call app.*`。
57
+ - **边界 / 陷阱**:环境已带默认 `applicationCode`,建模**不必**先建应用;`app` 未进 intent 声明式面。**要把 intent 落到新应用**:`omc call app.create`(原子面)→ 把该环境的 `applicationCode` 指向新应用(`omc setup` 重配或改 `~/.config/omc/` 环境配置)→ 再 `intent plan/apply`,模型即落在新应用(用户实测摩擦:曾误以为应用能进 intent)。
58
58
 
59
59
  ## 数据字典 `dictionary`
60
60
 
@@ -25,7 +25,8 @@ be treated as a second source. Read this, then follow the topics it names.
25
25
  5. 键名按对象不同:模型 `schemaCode`、默认表单 `sheetCode`(可省略,默认 == 模型 code)、流程 `workflowCode`/`code`——报错会提示同义键;字段族名认不出会给「最接近的族」。
26
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
- 8. 拿到 `detailArtifactId` 先 `artifact get <id> --summary` 看结构,再 `--keys/--jq` 取所需;`field.list`/`workflow.*` 默认面已给清单与节点摘要;`workflow.get_draft` 默认面已含 `edges`,改流程只需传 `activities`+`routes`。
28
+ 8. 拿到 `detailArtifactId` 先 `artifact get <id> --summary` 看结构,再 `--keys/--jq` 取所需;`field.list`/`workflow.*` 默认面已给清单与节点摘要;`workflow.get_draft` 默认面已含 `edges`,改流程只需传 `activities`+`routes`。**注意**:`form.get --full`、`workflow.get_draft` 的 `full:true` 大结果返回的是 `{length, sha256}` 摘要桩——真身走 `artifact get <id>`,勿把桩误判为「子表/控件没配」(用户实测陷阱)。
29
+ 9. **Windows/PowerShell 乱码**:CLI 输出是 UTF-8,PowerShell 管道默认按 GBK(936) 解码会全屏乱码——根因在终端不在 CLI。消费端先 `chcp 65001` + `[Console]::OutputEncoding=[Text.Encoding]::UTF8` 再读 CLI 输出。
29
30
  9. 每个 section 读一次即可;同一 topic 不要反复读。
30
31
  10. **复杂流程先预检角色**:读 `org.role.list`/`org.user.search`,用真实可解析对象;缺独立角色就报告阻塞,不伪造。主子表条件按规则行 `schemaCode` 回所属子模型验证。
31
32
  11. **验收不只看回读**:配置后用 opt-in `npm run test:live`(`OMC_LIVE=1`),验设计器控件/条件 Set、门户字段/列表列/选项/流程;业务规则走 `data.save(workflowCode)`→`workitem.list`→`data.submit`→`data.load`。门户断言硬重载≥2次。