@stalfh233/omc-cli 0.6.3 → 0.6.4

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/support.js CHANGED
@@ -118,6 +118,18 @@ export function applicationScopeHint(scope, noun) {
118
118
  }
119
119
  return `本次只扫描了应用 ${scope.code}(source=${scope.source})——零命中只代表该应用没有${noun};跨应用请先 app.list 取 applicationCode。`;
120
120
  }
121
+ /**
122
+ * T4(GitLab #17):同一条作用域事实,但**非零结果也返回**。调用方看到
123
+ * 「只有 3 个模型」时无从判断是租户就这么少、还是被应用边界裁掉了;
124
+ * model.list 之前只在零命中时才说作用域,于是这个裁剪完全不可见。
125
+ */
126
+ export function applicationScopeNotice(scope) {
127
+ const count = scope.applicationCount ?? 1;
128
+ const where = scope.source === "first-tenant-application" && count > 1
129
+ ? `租户首个应用 ${scope.code}(租户共 ${count} 个应用)`
130
+ : `应用 ${scope.code}`;
131
+ return `本次只在${where}内扫描(source=${scope.source})——结果被应用边界裁剪,不代表租户全集;跨应用先 app.list 取 applicationCode 再传 applicationCode。`;
132
+ }
121
133
  export function flattenTreeRows(rows) {
122
134
  const flat = [];
123
135
  const visit = (row) => {
@@ -164,12 +164,30 @@ export function parseToolInput(tool, input) {
164
164
  const result = z.object(tool.inputSchema).safeParse(normalized);
165
165
  if (result.success)
166
166
  return result.data;
167
+ // T3(GitLab #17,2026-11-04):封闭取值此前只能靠报错试出来(`dir:"desc"`
168
+ // → zod 原生 "Expected 'ASC' | 'DESC'",而 filter op 的枚举又只在拒绝文案里)。
169
+ // 把「这个参数到底收哪些值」直接放进错误信封:zod 的 invalid_enum_value 自带
170
+ // options;schema 表达不了的(paramEnums 声明的 filter.op)用声明值兜底。
171
+ const acceptedValues = {};
172
+ for (const issue of result.error.issues) {
173
+ const path = issue.path.join(".") || "args";
174
+ const options = issue.options;
175
+ if (issue.code === "invalid_enum_value" && Array.isArray(options) && options.length > 0)
176
+ acceptedValues[path] = options.map((value) => String(value));
177
+ }
178
+ for (const [key, values] of Object.entries(tool.paramEnums ?? {})) {
179
+ if (acceptedValues[key] === undefined)
180
+ acceptedValues[key] = values.split("|");
181
+ }
182
+ const firstPath = result.error.issues[0]?.path.join(".") || undefined;
167
183
  throw new ToolInputError({
168
184
  status: "error",
169
185
  blocker: "tool-input-invalid",
170
186
  tool: tool.name,
171
187
  message: result.error.issues.map((issue) => `${issue.path.join(".") || "args"}: ${issue.message}${keyRenameHint(issue.path, normalized, tool) ?? ""}`).join("; "),
172
188
  issues: result.error.issues.map((issue) => ({ path: issue.path, code: issue.code, message: issue.message, ...(keyRenameHint(issue.path, normalized, tool) ? { hint: keyRenameHint(issue.path, normalized, tool) } : {}) })),
189
+ ...(Object.keys(acceptedValues).length > 0 ? { acceptedValues } : {}),
190
+ ...(firstPath !== undefined ? { nextCommand: `omc describe ${tool.name} --param ${firstPath.split(".")[0]}` } : {}),
173
191
  });
174
192
  }
175
193
  //# sourceMappingURL=tool-types.js.map
package/dist/wire.js CHANGED
@@ -10,13 +10,21 @@ export class WireError extends Error {
10
10
  kind;
11
11
  errcode;
12
12
  diagnostic;
13
+ hint;
13
14
  constructor(kind, message, errcode,
14
15
  /** Round-2 T-06: diagnosable context for malformed/http-status envelopes. */
15
- diagnostic) {
16
+ diagnostic,
17
+ /**
18
+ * P1-4(GitLab #18):调用方可在抛出前附加一条**不改判定**的排查提示
19
+ * (例如「同一 schemaCode 去掉 filter 能读 → 是过滤形状问题」)。信封照实
20
+ * 保留 wire-business/errcode,只多一行可执行线索。
21
+ */
22
+ hint) {
16
23
  super(message);
17
24
  this.kind = kind;
18
25
  this.errcode = errcode;
19
26
  this.diagnostic = diagnostic;
27
+ this.hint = hint;
20
28
  }
21
29
  }
22
30
  /** Redact common secret keys and truncate — the malformed-envelope preview must be safe to log. */
package/dist/write.js CHANGED
@@ -34,7 +34,7 @@ import { WireError } from "./wire.js";
34
34
  * → session → version-family gate (fail closed without evidence) → production
35
35
  * confirmation → write lease → execute with authoritative readback.
36
36
  */
37
- async function governedWrite(ctx, tool, args, execute, dryRunPayload) {
37
+ async function governedWrite(ctx, tool, args, execute, dryRunPayload, readOnlyEscape) {
38
38
  const requestFingerprint = args.requestId ? sha256({ requestId: args.requestId, args: plannableArgsRaw(args) }) : undefined;
39
39
  // The shared admission owns the gate sequence; this face only supplies its
40
40
  // plan and its dry-run / replay payload shapes.
@@ -43,6 +43,8 @@ async function governedWrite(ctx, tool, args, execute, dryRunPayload) {
43
43
  confirmProduction: args.confirmProduction,
44
44
  dryRun: args.dryRun,
45
45
  capability: args.capability,
46
+ args,
47
+ ...(readOnlyEscape !== undefined ? { readOnlyEscape } : {}),
46
48
  coverage: { lane: args.capability ?? tool.lane, scope: tool.scope ?? "" },
47
49
  plan: (session) => planFor(ctx, args.environment, tool.name, args),
48
50
  // A face may supply a session-aware dry-run projection (e.g. model.delete's
@@ -53,7 +55,7 @@ async function governedWrite(ctx, tool, args, execute, dryRunPayload) {
53
55
  });
54
56
  if (outcome.kind === "settled")
55
57
  return outcome.result;
56
- const { session, plan, productionConfirmed } = outcome.admission;
58
+ const { session, plan, productionConfirmed, readOnlyEscape: readOnlyEscapeNote } = outcome.admission;
57
59
  return ctx.lease.run(session.environmentName, `${tool.name}:${randomUUID().slice(0, 8)}`, async () => {
58
60
  // Rollback before-capture: the pre-write state of the declared target is
59
61
  // fetched INSIDE the lease (serialized with the write itself) and stored
@@ -180,7 +182,7 @@ async function governedWrite(ctx, tool, args, execute, dryRunPayload) {
180
182
  });
181
183
  rollbackRef = { seq: entry.seq, hint: `omc rollback plan ${entry.seq}(--env ${entry.environment})` };
182
184
  }
183
- return { status: "ok", deliveryState, performed, readback, ...(readbackWarning ? { readbackWarning } : {}), ...(steps !== undefined ? { steps } : {}), ...(halfSuccess !== undefined ? { halfSuccess } : {}), ...(args.requestId ? { requestId: args.requestId, idempotency: idempotencyDeclaration(tool.name) } : {}), ...(productionConfirmed ? { productionConfirmed: true } : {}), ...(rollbackRef ? { rollback: rollbackRef } : {}), summary: writeSummary(tool.name, args, { performed, readback }, "done"), detailFile: artifactRef.detailFile, detailSha256: artifactRef.detailSha256, artifactFingerprint: artifactRef.detailSha256 };
185
+ return { status: "ok", deliveryState, ...(readOnlyEscapeNote !== undefined ? { readOnlyEscape: readOnlyEscapeNote } : {}), performed, readback, ...(readbackWarning ? { readbackWarning } : {}), ...(steps !== undefined ? { steps } : {}), ...(halfSuccess !== undefined ? { halfSuccess } : {}), ...(args.requestId ? { requestId: args.requestId, idempotency: idempotencyDeclaration(tool.name) } : {}), ...(productionConfirmed ? { productionConfirmed: true } : {}), ...(rollbackRef ? { rollback: rollbackRef } : {}), summary: writeSummary(tool.name, args, { performed, readback }, "done"), detailFile: artifactRef.detailFile, detailSha256: artifactRef.detailSha256, artifactFingerprint: artifactRef.detailSha256 };
184
186
  });
185
187
  }
186
188
  /**
@@ -223,7 +225,7 @@ function findCompleted(root, requestId, tool, environment, requestFingerprint) {
223
225
  return undefined;
224
226
  }
225
227
  /** Low-level write registration factory: shared by every lane adapter. */
226
- export function writeTool(tools, name, lane, scope, description, inputSchema, execute, preflight, dryRunPayload, rollback) {
228
+ export function writeTool(tools, name, lane, scope, description, inputSchema, execute, preflight, dryRunPayload, rollback, readOnlyEscape) {
227
229
  const rawExecute = execute;
228
230
  const semanticGate = (args) => {
229
231
  if (name === "field.create")
@@ -280,7 +282,7 @@ export function writeTool(tools, name, lane, scope, description, inputSchema, ex
280
282
  inputSchema: { environment: environmentArg, capability: capabilityArg, confirmProduction: confirmProductionArg, requestId: z.string().min(1).optional(), dryRun: z.boolean().optional(), ...inputSchema },
281
283
  handler: async (ctx, args, self) => {
282
284
  semanticGate(args);
283
- return governedWrite(ctx, self, args, (session) => rawExecute(session, args), dryRunPayload);
285
+ return governedWrite(ctx, self, args, (session) => rawExecute(session, args), dryRunPayload, readOnlyEscape);
284
286
  },
285
287
  }),
286
288
  // Ticket 46: the batch entry drives the SAME executor/gates, so a batch
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stalfh233/omc-cli",
3
- "version": "0.6.3",
3
+ "version": "0.6.4",
4
4
  "description": "CloudPivot 8.6 configuration capability CLI — governed, fail-closed, coverage-manifest bounded.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -52,9 +52,9 @@
52
52
  "access": "public"
53
53
  },
54
54
  "dependencies": {
55
- "@stalfh233/omc-fake-platform": "0.6.3",
55
+ "@stalfh233/omc-fake-platform": "0.6.4",
56
56
  "commander": "14.0.3",
57
- "omc-core": "0.6.3",
57
+ "omc-core": "0.6.4",
58
58
  "proper-lockfile": "4.1.2",
59
59
  "write-file-atomic": "6.0.0",
60
60
  "zod": "^3.23.8",
@@ -12,7 +12,7 @@ be treated as a second source. Read this, then follow the topics it names.
12
12
 
13
13
  ## 最小路径(先读这段就能开始)
14
14
 
15
- 0. **调用与检索(跨平台)**:`omc call ... --args '<json>'` 的内联 JSON 在 Windows PowerShell 下因引号转义不可靠——**PS 环境优先 `--args-file <path>`**(把 JSON 写临时文件再引用;解析失败的报错里也有此提示)。读命令输出默认把中文转义为 `\uXXXX`(防乱码)——**对输出做内容检索前先加 `--utf8`(单次明文)或先解 JSON,"grep 搜不到中文"≠"没有配置"**(静默假阴性坑)。stdout 恒为纯 JSON、人读进度在 stderr——脚本解析勿 `2>&1` 合流。
15
+ 0. **调用与检索(跨平台)**:`omc call ... --args '<json>'` 的内联 JSON 在 Windows PowerShell 下因引号转义不可靠——**PS 环境优先 `--args-file <path>`**(把 JSON 写临时文件再引用;解析失败的报错里也有此提示)。读命令输出默认把中文转义为 `\uXXXX`(防乱码)——**这是有意为之**:stdout 恒为「恰好一个合法 JSON envelope」,在任何代码页下都能被 `JSON.parse`/`ConvertFrom-Json` 还原(GitLab #17 T2 实证:曾让 `--utf8` 关掉转义,GBK 管道把信封结构解码坏、`ConvertFrom-Json` 中途失败)。`--utf8` 只让**人类诊断文本**(stderr/散文)按 UTF-8 原文输出;要连 stdout 也是中文明文,必须先让管道按 UTF-8 解码(`chcp 65001`)——只设 `OMC_ASCII_STDOUT=0` 是关写出端转义、不改管道解码,在 cp936 管道下会把信封解码坏。对输出做内容检索时**先解 JSON 再检索**(或 `--jq` 取值),"grep 搜不到中文"≠"没有配置"(静默假阴性坑)。stdout 恒为纯 JSON、人读进度在 stderr——脚本解析勿 `2>&1` 合流。
16
16
 
17
17
  0. **拼装器优先(高频写面一条命令)**:整模型建模(模型+N 字段+子表+发布+表单)`omc model-scaffold`;**存量模型加字段(建→发布→重建表单→列表加列)`omc field-add`**;字段计算 `omc compute`;条件→动作 `omc rule-when`;线性审批流 `omc approval-flow`;表单默认值 `omc form-default`;存量盘点 `data.list all:true`——命中这些形态先看 `--help`,比 recipe 手拼省 10+ 轮。
18
18
  0c. **清理一个模型用 `omc model-purge --code <x> [--purge-instances] --apply`**——按依赖倒序(实例 → 模型级联带走流程/视图/字段/表单),**不要手工逐层删**:删部门/字段留下缓存或孤儿,会让后续调用出现「假 40009 / 假孤儿 / 全局待办 50000」这类幽灵故障(r12–r14 三次实测)。若返回 `unremovableInstances`(业务对象已删的停滞实例,平台 `delete_instance` NPE),按返回的 `maintainerAction` 让维护者清 `biz_workflow_instance`;**不要改用 model.delete 硬来**。