@stalfh233/omc-cli 0.4.15 → 0.4.17

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.
Files changed (185) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +297 -297
  3. package/coverage/m1-coverage-manifest-v1.json +1278 -1277
  4. package/dist/args.js +0 -0
  5. package/dist/artifacts.js +0 -0
  6. package/dist/bench/call-face-eval.js +0 -0
  7. package/dist/bench/context-attribution.js +0 -0
  8. package/dist/bench/discovery-cost-eval.js +0 -0
  9. package/dist/bench/driver.js +0 -0
  10. package/dist/bench/evals.js +0 -0
  11. package/dist/bench/fake-http-server.js +0 -0
  12. package/dist/bench/instrument.js +0 -0
  13. package/dist/bench/intent-face-eval.js +40 -40
  14. package/dist/bench/run.js +0 -0
  15. package/dist/bench/scenario.js +0 -0
  16. package/dist/bench/types.js +0 -0
  17. package/dist/bi-wire.js +0 -0
  18. package/dist/bizservice-config.js +38 -13
  19. package/dist/call.js +0 -0
  20. package/dist/capability-absences.js +0 -0
  21. package/dist/capability-overview.js +2 -2
  22. package/dist/capability-shape.js +0 -0
  23. package/dist/cli-contract.js +0 -0
  24. package/dist/cli-output.js +41 -15
  25. package/dist/cli.js +24 -2
  26. package/dist/code-rules.js +0 -0
  27. package/dist/command-transport.js +0 -0
  28. package/dist/config-store.js +4 -0
  29. package/dist/context.js +0 -0
  30. package/dist/contract-consistency.js +0 -0
  31. package/dist/contract-resources.js +0 -0
  32. package/dist/coverage-consistency.js +0 -0
  33. package/dist/coverage-registry.js +0 -0
  34. package/dist/coverage.js +0 -0
  35. package/dist/data-list-filter.js +0 -0
  36. package/dist/discovery.js +0 -0
  37. package/dist/endpoints.js +2 -0
  38. package/dist/environment-policy.js +0 -0
  39. package/dist/execution-metadata.js +4 -4
  40. package/dist/fake/app.js +0 -0
  41. package/dist/fake/b2-registration.js +26 -0
  42. package/dist/fake/businessrule.js +0 -0
  43. package/dist/fake/datarule.js +0 -0
  44. package/dist/fake/dictionary.js +0 -0
  45. package/dist/fake/environment.js +0 -0
  46. package/dist/fake/field.js +0 -0
  47. package/dist/fake/form.js +0 -0
  48. package/dist/fake/index.js +0 -0
  49. package/dist/fake/list-view.js +0 -0
  50. package/dist/fake/model.js +0 -0
  51. package/dist/fake/online-js.js +0 -0
  52. package/dist/fake/report.js +0 -0
  53. package/dist/fake/routes.js +0 -0
  54. package/dist/fake/rule-lifecycle.js +0 -0
  55. package/dist/fake/runtime-data.js +0 -0
  56. package/dist/fake/state.js +0 -0
  57. package/dist/fake/workflow.js +0 -0
  58. package/dist/field-change.js +0 -0
  59. package/dist/field-families.js +48 -0
  60. package/dist/form-layout.js +0 -0
  61. package/dist/form-support.js +12 -2
  62. package/dist/goal-routes.js +0 -0
  63. package/dist/governed-execution.js +0 -0
  64. package/dist/human-summary.js +0 -0
  65. package/dist/identity.js +0 -0
  66. package/dist/intent/baseline.js +0 -0
  67. package/dist/intent/capabilities/bizservice.js +0 -0
  68. package/dist/intent/capabilities/businessrule.js +0 -0
  69. package/dist/intent/capabilities/datarule.js +0 -0
  70. package/dist/intent/capabilities/field.js +0 -0
  71. package/dist/intent/capabilities/form.js +0 -0
  72. package/dist/intent/capabilities/index.js +0 -0
  73. package/dist/intent/capabilities/listview.js +0 -0
  74. package/dist/intent/capabilities/model.js +0 -0
  75. package/dist/intent/capabilities/onlinejs.js +0 -0
  76. package/dist/intent/capabilities/report.js +0 -0
  77. package/dist/intent/capabilities/workflow.js +0 -0
  78. package/dist/intent/capability.js +0 -0
  79. package/dist/intent/cli.js +0 -0
  80. package/dist/intent/compare.js +0 -0
  81. package/dist/intent/compiler.js +0 -0
  82. package/dist/intent/dsl.js +0 -0
  83. package/dist/intent/plan-file.js +0 -0
  84. package/dist/intent/readback.js +0 -0
  85. package/dist/intent/schema.js +0 -0
  86. package/dist/intent/validation.js +0 -0
  87. package/dist/intent/yaml.js +0 -0
  88. package/dist/json-column.js +0 -0
  89. package/dist/lanes/app-contract.js +0 -0
  90. package/dist/lanes/app-coverage.js +0 -0
  91. package/dist/lanes/app.js +0 -0
  92. package/dist/lanes/apply-changes.js +14 -1
  93. package/dist/lanes/b2-registration-contract.js +5 -0
  94. package/dist/lanes/b2-registration-coverage.js +1 -0
  95. package/dist/lanes/b2-registration.js +55 -6
  96. package/dist/lanes/businessrule-contract.js +0 -0
  97. package/dist/lanes/businessrule-coverage.js +0 -0
  98. package/dist/lanes/businessrule.js +0 -0
  99. package/dist/lanes/contract-support.js +0 -0
  100. package/dist/lanes/coverage-declaration.js +0 -0
  101. package/dist/lanes/datarule-contract.js +0 -0
  102. package/dist/lanes/datarule-coverage.js +0 -0
  103. package/dist/lanes/datarule-protocol.js +0 -0
  104. package/dist/lanes/datarule.js +0 -0
  105. package/dist/lanes/dictionary-contract.js +0 -0
  106. package/dist/lanes/dictionary-coverage.js +0 -0
  107. package/dist/lanes/dictionary.js +0 -0
  108. package/dist/lanes/environment-contract.js +0 -0
  109. package/dist/lanes/environment-coverage.js +0 -0
  110. package/dist/lanes/environment.js +0 -0
  111. package/dist/lanes/field-contract.js +3 -2
  112. package/dist/lanes/field-coverage.js +0 -0
  113. package/dist/lanes/field.js +268 -172
  114. package/dist/lanes/form-contract.js +2 -2
  115. package/dist/lanes/form-coverage.js +0 -0
  116. package/dist/lanes/form.js +4 -0
  117. package/dist/lanes/lane-ids.js +0 -0
  118. package/dist/lanes/list-view-contract.js +0 -0
  119. package/dist/lanes/list-view-coverage.js +0 -0
  120. package/dist/lanes/list-view-shapes.js +0 -0
  121. package/dist/lanes/list-view.js +0 -0
  122. package/dist/lanes/meta-contract.js +0 -0
  123. package/dist/lanes/meta.js +11 -1
  124. package/dist/lanes/model-contract.js +0 -0
  125. package/dist/lanes/model-coverage.js +0 -0
  126. package/dist/lanes/model.js +1 -1
  127. package/dist/lanes/online-js-contract.js +0 -0
  128. package/dist/lanes/online-js-coverage.js +0 -0
  129. package/dist/lanes/online-js.js +0 -0
  130. package/dist/lanes/report-contract.js +0 -0
  131. package/dist/lanes/report-coverage.js +0 -0
  132. package/dist/lanes/report.js +0 -0
  133. package/dist/lanes/rule-graph.js +0 -0
  134. package/dist/lanes/rule-lifecycle-contract.js +0 -0
  135. package/dist/lanes/rule-lifecycle-coverage.js +0 -0
  136. package/dist/lanes/rule-lifecycle.js +0 -0
  137. package/dist/lanes/runtime-data-contract.js +0 -0
  138. package/dist/lanes/runtime-data-coverage.js +0 -0
  139. package/dist/lanes/runtime-data.js +0 -0
  140. package/dist/lanes/workflow-contract.js +0 -0
  141. package/dist/lanes/workflow-coverage.js +0 -0
  142. package/dist/lanes/workflow.js +2 -0
  143. package/dist/omc-core-lazy.js +6 -0
  144. package/dist/online-js-layout.js +0 -0
  145. package/dist/online-js-source.js +28 -28
  146. package/dist/package-tool.js +0 -0
  147. package/dist/package.js +0 -0
  148. package/dist/plan.js +11 -1
  149. package/dist/read.js +0 -0
  150. package/dist/redact.js +0 -0
  151. package/dist/rule-support.js +0 -0
  152. package/dist/runtime-support.js +0 -0
  153. package/dist/service-doctor.js +28 -0
  154. package/dist/session-manager.js +14 -2
  155. package/dist/session.js +68 -13
  156. package/dist/skills.js +0 -0
  157. package/dist/sqlite-warning-filter.js +27 -0
  158. package/dist/support.js +0 -0
  159. package/dist/token-cache.js +65 -0
  160. package/dist/tool-types.js +0 -0
  161. package/dist/tools.js +0 -0
  162. package/dist/usage-log.js +4 -0
  163. package/dist/wire.js +0 -0
  164. package/dist/workflow-support.js +0 -0
  165. package/dist/write-lease.js +0 -0
  166. package/dist/write-lock.js +0 -0
  167. package/dist/write.js +24 -2
  168. package/dist/zip.js +0 -0
  169. package/docs/tool-surface-map.md +42 -42
  170. package/package.json +71 -71
  171. package/skills/omc-acceptance-criteria.md +47 -47
  172. package/skills/omc-business-configuration.md +258 -257
  173. package/skills/omc-capabilities.md +197 -197
  174. package/skills/omc-capability-scouting.md +55 -55
  175. package/skills/omc-config-draft-review.md +171 -171
  176. package/skills/omc-five-piece-flow.md +35 -35
  177. package/skills/omc-glossary.md +86 -86
  178. package/skills/omc-refusals.md +110 -110
  179. package/skills/omc-requirement-analysis.md +161 -161
  180. package/skills/omc-requirement-vocabulary.md +51 -51
  181. package/skills/omc-start-here.md +82 -82
  182. package/skills/omc-tool-selection.md +119 -119
  183. package/skills/omc-write-hazards.md +88 -87
  184. package/dist/approval-token.js +0 -102
  185. package/dist/server.js +0 -92
package/dist/args.js CHANGED
File without changes
package/dist/artifacts.js CHANGED
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -122,24 +122,24 @@ function freshHome(url) {
122
122
  writeFileSync(join(home, "credentials"), JSON.stringify({ eval: { username: "tester", password: "eval-face-password" } }));
123
123
  return home;
124
124
  }
125
- const LADDER_INTENT = `intentVersion: 1
126
- models:
127
- - code: eval_m1
128
- name: 评测建模
129
- fields:
130
- - code: f_title
131
- name: 标题
132
- type: SHORT_TEXT
133
- - code: f_amount
134
- name: 金额
135
- type: NUMERICAL
136
- options:
137
- scale: 3
138
- - code: f_when
139
- name: 日期
140
- type: DATE
141
- form:
142
- publish: true
125
+ const LADDER_INTENT = `intentVersion: 1
126
+ models:
127
+ - code: eval_m1
128
+ name: 评测建模
129
+ fields:
130
+ - code: f_title
131
+ name: 标题
132
+ type: SHORT_TEXT
133
+ - code: f_amount
134
+ name: 金额
135
+ type: NUMERICAL
136
+ options:
137
+ scale: 3
138
+ - code: f_when
139
+ name: 日期
140
+ type: DATE
141
+ form:
142
+ publish: true
143
143
  `;
144
144
  export const INTENT_FACE_TASKS = [
145
145
  {
@@ -163,12 +163,12 @@ export const INTENT_FACE_TASKS = [
163
163
  id: "rule-backfill-ladder",
164
164
  description: "two models via intent + DYNAMIC backfill rule + enable + trigger row + verified target readback (mixed face: intent + escape hatch)",
165
165
  async run(agent, server) {
166
- const modelsIntent = agent.writeIntent("models.yaml", `intentVersion: 1
167
- models:
168
- - code: eval_rs
169
- name: 回填源
170
- - code: eval_rt
171
- name: 回填目标
166
+ const modelsIntent = agent.writeIntent("models.yaml", `intentVersion: 1
167
+ models:
168
+ - code: eval_rs
169
+ name: 回填源
170
+ - code: eval_rt
171
+ name: 回填目标
172
172
  `);
173
173
  await agent.intentValidate(modelsIntent);
174
174
  const { plan, planFile } = await agent.intentPlan(modelsIntent);
@@ -209,27 +209,27 @@ models:
209
209
  id: "input-error-recovery",
210
210
  description: "bad intent -> validate exit 10 with source location -> fix -> plan/apply -> idempotent re-plan/re-apply with zero duplicate wires",
211
211
  async run(agent, server) {
212
- const bad = agent.writeIntent("intent.yaml", `intentVersion: 1
213
- models:
214
- - code: eval_idem
215
- name: 幂等模型
216
- fields:
217
- - code: f_bad
218
- name: 坏类型
219
- type: HOLOGRAM
212
+ const bad = agent.writeIntent("intent.yaml", `intentVersion: 1
213
+ models:
214
+ - code: eval_idem
215
+ name: 幂等模型
216
+ fields:
217
+ - code: f_bad
218
+ name: 坏类型
219
+ type: HOLOGRAM
220
220
  `);
221
221
  const rejected = await agent.run(["intent", "validate", bad]);
222
222
  ok(rejected.code === 10 && rejected.envelope.error?.code === "invalid-input", `expected invalid-input exit 10, got ${String(rejected.code)}`);
223
223
  const first = rejected.envelope.result.errors[0];
224
224
  ok(first.code === "intent-field-type-unknown" && typeof first.line === "number", `located error: ${JSON.stringify(first).slice(0, 200)}`);
225
- const fixed = agent.writeIntent("intent.yaml", `intentVersion: 1
226
- models:
227
- - code: eval_idem
228
- name: 幂等模型
229
- fields:
230
- - code: f_bad
231
- name: 改正的字段
232
- type: SHORT_TEXT
225
+ const fixed = agent.writeIntent("intent.yaml", `intentVersion: 1
226
+ models:
227
+ - code: eval_idem
228
+ name: 幂等模型
229
+ fields:
230
+ - code: f_bad
231
+ name: 改正的字段
232
+ type: SHORT_TEXT
233
233
  `);
234
234
  const validated = await agent.intentValidate(fixed);
235
235
  ok(validated.valid === true, `fixed validate: ${JSON.stringify(validated).slice(0, 200)}`);
package/dist/bench/run.js CHANGED
File without changes
File without changes
File without changes
package/dist/bi-wire.js CHANGED
File without changes
@@ -177,36 +177,56 @@ export function assertStructuredConfig(value, label) {
177
177
  * 全 0 串,则拒绝并要求从任务环境事实取真值。
178
178
  */
179
179
  const MASKED_SECRET_RE = /^\*{3,}$/;
180
- const PLACEHOLDER_CORP_RE = /^RELEVANCE-[A-Za-z0-9]+$/;
180
+ // 0.4.16:含连字符——r6 历史占位形态 "RELEVANCE-client-000…" 本就带连字符,
181
+ // 旧 [A-Za-z0-9]+ 恰好漏拦它自己要拦的形态(T05 测试发现)。
182
+ const PLACEHOLDER_CORP_RE = /^RELEVANCE-[A-Za-z0-9-]{4,}$/;
181
183
  const ALL_ZERO_CREDENTIAL_RE = /^0{16,}$/;
182
- function scanAuthParameterlistForPlaceholders(parameterlist, adapterCode) {
184
+ /**
185
+ * 纯分类函数(不 throw、不碰环境):鉴权 parameterlist 里的掩码/占位值识别。
186
+ * 写面守卫(发现即拒)与 doctor --service(全量报告)共用同一套规则:
187
+ * 字符串三类(legacy "***" / RELEVANCE-xxx / 全 0)+ 结构化掩码对象
188
+ * (masked:true——T05 读面形状,防回灌)。
189
+ */
190
+ export function classifyAuthParameterlist(parameterlist, _adapterCode) {
191
+ const findings = [];
183
192
  for (const entry of parameterlist) {
184
193
  if (!isRecord(entry))
185
194
  continue;
186
195
  const value = entry.value;
196
+ if (isRecord(value) && value.masked === true) {
197
+ findings.push({ code: String(entry.code ?? ""), reason: "结构化读回掩码 masked:true(真值必须从任务环境事实取得,不可抄写)", valueKind: "masked-object" });
198
+ continue;
199
+ }
187
200
  if (typeof value !== "string")
188
201
  continue;
189
202
  const t = value.trim();
190
- let why = "";
191
203
  if (MASKED_SECRET_RE.test(t))
192
- why = "读回掩码 ***(读面按 ticket 115 掩码,不可抄写)";
204
+ findings.push({ code: String(entry.code ?? ""), reason: "读回掩码 ***(读面按 ticket 115 掩码,不可抄写)", valueKind: "masked-string" });
193
205
  else if (PLACEHOLDER_CORP_RE.test(t))
194
- why = "自造 RELEVANCE-xxx 占位(沙盒真实 corpId 才是 RELEVANCE-<真实id>)";
206
+ findings.push({ code: String(entry.code ?? ""), reason: "自造 RELEVANCE-xxx 占位(沙盒真实 corpId 才是 RELEVANCE-<真实id>)", valueKind: "placeholder-corp" });
195
207
  else if (ALL_ZERO_CREDENTIAL_RE.test(t))
196
- why = "全 0 串(脱敏显示值,非真实凭据)";
197
- if (why)
198
- refuse(BIZSERVICE_CONFIG_BLOCKERS.serviceConfigInvalid, `${adapterCode} 服务鉴权凭据包含${why}——平台 service/create 不校验凭据内容,注册成功但运行时自调必败(r6-onlinejs 真机:全 0 corpId 注册成功、自调 401/500)。凭据必须从任务环境事实取真值(如 doctor 显示的 corpId、任务提供的 client_secret)。`, { key: `auth.parameterlist.${String(entry.code ?? "")}`, value: t });
208
+ findings.push({ code: String(entry.code ?? ""), reason: "全 0 串(脱敏显示值,非真实凭据)", valueKind: "all-zero" });
209
+ }
210
+ return findings;
211
+ }
212
+ function scanAuthParameterlistForPlaceholders(parameterlist, adapterCode) {
213
+ for (const finding of classifyAuthParameterlist(parameterlist, adapterCode)) {
214
+ refuse(BIZSERVICE_CONFIG_BLOCKERS.serviceConfigInvalid, `${adapterCode} 服务鉴权凭据 ${finding.code} 包含${finding.reason}——平台 service/create 不校验凭据内容,注册成功但运行时自调必败(r6-onlinejs 真机:全 0 corpId 注册成功、自调 401/500)。凭据必须从任务环境事实取真值(如 doctor 显示的 corpId、任务提供的 client_secret)。`, { key: `auth.parameterlist.${finding.code}`, value: finding.valueKind });
199
215
  }
200
216
  }
201
217
  function requireAuthAndUrl(serviceConfig, adapterCode) {
202
218
  const auth = serviceConfig.auth;
219
+ // T05:掩码/占位分类先于结构校验——结构化掩码对象 {masked:true}(读回形状回灌)
220
+ // 在 string-only 的 validateRestfulAuth 之前就能拿到专属拒因,而不是笼统的
221
+ // "必须是非空字符串"。两道防线都保留(INTERNAL 路径只依赖本扫描)。
222
+ if (isRecord(auth) && Array.isArray(auth.parameterlist))
223
+ scanAuthParameterlistForPlaceholders(auth.parameterlist, adapterCode);
203
224
  if (adapterCode === "RESTFUL" || adapterCode === "SOAP")
204
225
  validateRestfulAuth(auth);
205
226
  if (!isRecord(auth) || typeof auth.code !== "string" || !auth.code || !Array.isArray(auth.parameterlist))
206
227
  refuse(BIZSERVICE_CONFIG_BLOCKERS.serviceConfigInvalid, `${adapterCode} 服务 configJson 需要 auth {code, parameterlist[]}`, { key: "auth" });
207
228
  if (!commonlistValue(serviceConfig, "url"))
208
229
  refuse(BIZSERVICE_CONFIG_BLOCKERS.serviceConfigInvalid, `${adapterCode} 服务 configJson 需要 commonlist [{code:"url", value}]`, { key: "commonlist.url" });
209
- scanAuthParameterlistForPlaceholders(auth.parameterlist, adapterCode);
210
230
  }
211
231
  function requireCommonlist(serviceConfig, key, adapterCode) {
212
232
  if (!commonlistValue(serviceConfig, key))
@@ -504,12 +524,17 @@ export function normalizeMethodConfigReadback(adapterCode, config) {
504
524
  * `auth.parameterlist[].value` carries cleartext credentials (basic
505
525
  * password, oauth client_secret…); handoff §5 says method-config secrets
506
526
  * never echo. Applied ONLY to readback projections (service.get /
507
- * method.get / method update readback): the VALUES become "***" while the
508
- * code/name structure stays readable, so ticket 94's shape-readback
527
+ * method.get / method update readback): the VALUES become a STRUCTURED
528
+ * mask `{masked:true, hint}` (0.4.16 T05 — a bare "***" string was
529
+ * copy-pastable; agents抄回写即注册垃圾凭据,r4-r6 全 0 悬案的根因链).
530
+ * The code/name structure stays readable, so ticket 94's shape-readback
509
531
  * semantics survive. NEVER apply this on a write/echo path — the update
510
532
  * echo must carry the original config verbatim or the mask would be
511
- * persisted over the stored credential.
533
+ * persisted over the stored credential. The write-face guard
534
+ * (classifyAuthParameterlist) rejects both the structured mask and any
535
+ * legacy "***" string on write.
512
536
  */
537
+ export const MASK_HINT = "读回掩码——真值从任务环境事实取,不可抄写";
513
538
  export function maskServiceConfigSecrets(config) {
514
539
  if (!isRecord(config))
515
540
  return config;
@@ -519,7 +544,7 @@ export function maskServiceConfigSecrets(config) {
519
544
  const maskedParameterlist = auth.parameterlist.map((entry) => {
520
545
  if (!isRecord(entry))
521
546
  return entry;
522
- return { ...entry, ...(entry.value !== undefined ? { value: "***" } : {}) };
547
+ return { ...entry, ...(entry.value !== undefined ? { value: { masked: true, hint: MASK_HINT } } : {}) };
523
548
  });
524
549
  return { ...config, auth: { ...auth, parameterlist: maskedParameterlist } };
525
550
  }
package/dist/call.js CHANGED
File without changes
File without changes
@@ -133,8 +133,8 @@ const SURFACES = [
133
133
  id: "bizservice",
134
134
  title: "业务集成",
135
135
  lane: LANES.b2,
136
- description: "业务服务与方法注册(RESTFUL/DATA_SQL/DATA_TABLE/SAP/KAFKA/SOAP)、导入导出、设计器测试。",
137
- operations: ["register", "update", "delete", "export", "import", "check", "test"],
136
+ description: "业务服务与方法注册(RESTFUL/DATA_SQL/DATA_TABLE/SAP/KAFKA/SOAP)、服务清单(按分类/关键词,凭据不回显)、导入导出、设计器测试。",
137
+ operations: ["list", "register", "update", "delete", "export", "import", "check", "test"],
138
138
  requires: [],
139
139
  intent: { level: "partial", note: "声明式支持新建 service.register(可带一个 method)与既有 service/method 的 update(结构化 config、AES 加密、patch 语义);删除、给既有服务加方法、分类/连接池/IE 仍走 escape hatch。" },
140
140
  notes: "方法 configJson 走 AES-128-ECB;RESTFUL/DATA_SQL 绑定前须带适配器必需配置(否则是注册壳)。",
File without changes
File without changes
@@ -1,8 +1,8 @@
1
1
  import { writeFileSync } from "node:fs";
2
2
  import { serializeEnvelope } from "./cli-contract.js";
3
3
  import { recordUsageOutput } from "./usage-log.js";
4
- let options = { quiet: false };
5
- /** Parse `--quiet` and `--out <file>` from the raw argv once, before dispatch. */
4
+ let options = { quiet: false, verbose: false };
5
+ /** Parse `--quiet`, `--verbose` and `--out <file>` from the raw argv once, before dispatch. */
6
6
  export function configureOutput(argv) {
7
7
  const outIndex = argv.indexOf("--out");
8
8
  let outFile;
@@ -14,7 +14,11 @@ export function configureOutput(argv) {
14
14
  throw new Error("--out requires a file path");
15
15
  outFile = value;
16
16
  }
17
- options = { quiet: argv.includes("--quiet"), ...(outFile ? { outFile } : {}) };
17
+ options = {
18
+ quiet: argv.includes("--quiet"),
19
+ verbose: argv.includes("--verbose"),
20
+ ...(outFile ? { outFile } : {}),
21
+ };
18
22
  return options;
19
23
  }
20
24
  /**
@@ -25,7 +29,7 @@ export function stripOutputFlags(argv) {
25
29
  const out = [];
26
30
  for (let index = 0; index < argv.length; index += 1) {
27
31
  const token = argv[index];
28
- if (token === "--quiet")
32
+ if (token === "--quiet" || token === "--verbose")
29
33
  continue;
30
34
  if (token === "--out") {
31
35
  index += 1;
@@ -38,29 +42,51 @@ export function stripOutputFlags(argv) {
38
42
  export function outputOptions() {
39
43
  return options;
40
44
  }
45
+ /**
46
+ * Serialize-boundary compaction: a success envelope (error == null) drops the
47
+ * envelope-level `status`/`requestId`/`error:null` unless `--verbose`. Failures
48
+ * keep the full field set. Non-envelope values pass through untouched.
49
+ */
50
+ function compactForOutput(value) {
51
+ if (options.verbose)
52
+ return value;
53
+ if (!value || typeof value !== "object" || !("ok" in value))
54
+ return value;
55
+ const record = value;
56
+ if (record.error !== null && record.error !== undefined)
57
+ return value;
58
+ return { ok: record.ok, result: record.result };
59
+ }
41
60
  /**
42
61
  * Write the single stdout envelope. When `--out` is set, the identical bytes are
43
62
  * written to that file (UTF-8) FIRST, so a bad path fails before any envelope
44
63
  * reaches stdout — keeping the "exactly one envelope" contract even on failure.
45
- * stdout and the file can never diverge.
64
+ * stdout and the file can never diverge (both follow the same verbose rule).
46
65
  */
47
66
  export function writeEnvelope(value) {
48
- const text = serializeEnvelope(value);
67
+ const outputValue = compactForOutput(value);
68
+ const text = serializeEnvelope(outputValue);
49
69
  if (!options.outFile) {
50
70
  recordUsageOutput(value, Buffer.byteLength(text, "utf8"));
51
71
  process.stdout.write(text);
52
72
  return;
53
73
  }
54
- const pretty = `${JSON.stringify(value, null, 2)}\n`;
74
+ const pretty = `${JSON.stringify(outputValue, null, 2)}\n`;
55
75
  writeFileSync(options.outFile, pretty, "utf8");
56
- const envelopeValue = value && typeof value === "object" ? value : {};
57
- const confirmation = {
58
- ok: envelopeValue.ok ?? true,
59
- status: envelopeValue.status ?? "ok",
60
- result: { out: options.outFile, bytes: Buffer.byteLength(pretty, "utf8") },
61
- requestId: envelopeValue.requestId ?? null,
62
- error: envelopeValue.error ?? null,
63
- };
76
+ const envelopeValue = outputValue && typeof outputValue === "object" ? outputValue : {};
77
+ const failure = envelopeValue.error !== null && envelopeValue.error !== undefined;
78
+ const confirmation = failure || options.verbose
79
+ ? {
80
+ ok: envelopeValue.ok ?? failure ? false : true,
81
+ status: envelopeValue.status ?? (failure ? "error" : "ok"),
82
+ result: { out: options.outFile, bytes: Buffer.byteLength(pretty, "utf8") },
83
+ requestId: envelopeValue.requestId ?? null,
84
+ error: envelopeValue.error ?? null,
85
+ }
86
+ : {
87
+ ok: envelopeValue.ok ?? true,
88
+ result: { out: options.outFile, bytes: Buffer.byteLength(pretty, "utf8") },
89
+ };
64
90
  const confirmationText = serializeEnvelope(confirmation);
65
91
  recordUsageOutput(value, Buffer.byteLength(confirmationText, "utf8"), Buffer.byteLength(pretty, "utf8"));
66
92
  process.stdout.write(confirmationText);
package/dist/cli.js CHANGED
@@ -1,4 +1,6 @@
1
1
  #!/usr/bin/env node
2
+ // T04:SQLite 警告过滤器必须保持第一个 import(先于任何拉起 omc-core/node:sqlite 的模块)。
3
+ import "./sqlite-warning-filter.js";
2
4
  import { createInterface } from "node:readline/promises";
3
5
  import { stdin, argv } from "node:process";
4
6
  import { createRequire } from "node:module";
@@ -116,7 +118,7 @@ async function cmdSetup(store, args) {
116
118
  console.error(`环境 ${name} 已保存(幂等,可随时重跑 omc setup)。凭据存储模式:${store.credentialsMode()}`);
117
119
  return 0;
118
120
  }
119
- async function cmdDoctor(store, environment) {
121
+ async function cmdDoctor(store, environment, serviceCode) {
120
122
  const manifest = loadCoverageManifest();
121
123
  const steps = [];
122
124
  let ok = true;
@@ -166,6 +168,26 @@ async function cmdDoctor(store, environment) {
166
168
  steps.push(`许可证 FAIL 许可证面探测失败:${redactText(error.message, [])}`);
167
169
  }
168
170
  }
171
+ // T09 — optional per-service credential shape check (--service <code>).
172
+ // Non-fatal: a masked/placeholder credential does not make the ENVIRONMENT
173
+ // unhealthy; it explains why that service's calls would 401/500. Findings
174
+ // come from the RAW wire read (get_by_code configJson), classified by the
175
+ // same pure function the write-side guard uses — never the masked
176
+ // projection re-parsed.
177
+ if (serviceCode !== undefined) {
178
+ const { probeServiceCredentials } = await import("./service-doctor.js");
179
+ const probe = await probeServiceCredentials(session, serviceCode);
180
+ if (probe.status === "not-found")
181
+ steps.push(`服务凭据 -- ${serviceCode} 未找到(get_by_code 无行)`);
182
+ else if (probe.status === "error")
183
+ steps.push(`服务凭据 WARN 探测失败:${redactText(probe.message, [])}`);
184
+ else if (probe.findings.length === 0)
185
+ steps.push(`服务凭据 ok ${serviceCode} 鉴权参数形状正常(${probe.parameterCount} 项;掩码/占位/全 0 均未检出)`);
186
+ else
187
+ steps.push(`服务凭据 WARN ${serviceCode} ${probe.findings.length} 项问题(${probe.findings.map((finding) => `${finding.code}:${finding.valueKind}`).join("、")})——掩码/占位值不可用于真实调用;真值从任务环境事实取,不要抄 service.get 读回`);
188
+ if (serviceCode !== undefined)
189
+ steps.push(`连通性 -- 形状检查不能证明凭据有效;真实连通性用 method.test(平台侧发起调用)`);
190
+ }
169
191
  }
170
192
  catch (error) {
171
193
  ok = false;
@@ -1348,7 +1370,7 @@ async function main() {
1348
1370
  command.command("setup").option("--list").option("--edit <environment>").option("--remove <environment>").action(async (options) => {
1349
1371
  commandExitCode = await cmdSetup(new ConfigStore(defaultPaths()), [...(options.list ? ["--list"] : []), ...(options.edit ? ["--edit", options.edit] : []), ...(options.remove ? ["--remove", options.remove] : [])]);
1350
1372
  });
1351
- command.command("doctor").option("--environment <environment>").option("--env <environment>").action(async (options) => { commandExitCode = await cmdDoctor(new ConfigStore(defaultPaths()), options.environment ?? options.env); });
1373
+ command.command("doctor").option("--environment <environment>").option("--env <environment>").option("--service <serviceCode>", "可选:体检单个业务集成服务的鉴权参数形状(掩码/占位/全 0 检出,WARN 非致命)").action(async (options) => { commandExitCode = await cmdDoctor(new ConfigStore(defaultPaths()), options.environment ?? options.env, options.service); });
1352
1374
  command.command("call <tool>").option("--env <environment>").option("--environment <environment>").option("--args <json>").option("--args-file <path>").option("--max-output-bytes <bytes>").option("--request-id <requestId>").option("--timeout-ms <milliseconds>").option("--wait <milliseconds>").option("--dry-run").addHelpText("after", `\n${onboardingHelp()}\n`).action(async (tool, options) => {
1353
1375
  const rest = [tool, ...(options.env ? ["--env", options.env] : []), ...(options.environment ? ["--environment", options.environment] : []), ...(options.args !== undefined ? ["--args", options.args] : []), ...(options.argsFile !== undefined ? ["--args-file", options.argsFile] : []), ...(options.maxOutputBytes !== undefined ? ["--max-output-bytes", options.maxOutputBytes] : []), ...(options.requestId ? ["--request-id", options.requestId] : []), ...(options.timeoutMs ? ["--timeout-ms", options.timeoutMs] : []), ...(options.wait !== undefined ? ["--wait", options.wait] : []), ...(options.dryRun ? ["--dry-run"] : [])];
1354
1376
  commandExitCode = await cmdCall(new ConfigStore(defaultPaths()), rest);
File without changes
File without changes
@@ -30,6 +30,10 @@ export class ConfigStore {
30
30
  constructor(paths = defaultPaths()) {
31
31
  this.paths = paths;
32
32
  }
33
+ /** Config root (config file's directory) — token-cache placement (F1). */
34
+ get configPaths() {
35
+ return this.paths;
36
+ }
33
37
  atomicWrite(path, text, mode) {
34
38
  mkdirSync(dirname(path), { recursive: true });
35
39
  if (existsSync(path)) {
package/dist/context.js CHANGED
File without changes
File without changes
File without changes
File without changes
File without changes
package/dist/coverage.js CHANGED
File without changes
File without changes
package/dist/discovery.js CHANGED
File without changes
package/dist/endpoints.js CHANGED
@@ -70,6 +70,8 @@ export const ENDPOINTS = {
70
70
  bizServiceCreate: "/api/api/bizservice/service/create",
71
71
  bizServiceUpdate: "/api/api/bizservice/service/update",
72
72
  bizServiceGetByCode: "/api/api/bizservice/service/get_by_code",
73
+ bizServiceListByCategoryId: "/api/api/bizservice/service/list_by_category_id",
74
+ bizServiceListByCategoryIdFind: "/api/api/bizservice/service/list_by_category_id_find",
73
75
  bizServiceCategoryList: "/api/api/bizservice/category/list",
74
76
  bizServiceCategoryCreate: "/api/api/bizservice/category/create",
75
77
  bizServiceCategoryUpdate: "/api/api/bizservice/category/update",
File without changes
@@ -738,7 +738,7 @@ const formConditionalRequired = Object.freeze({
738
738
  conditionalRequirements: ["条件串:`{字段} 操作符 [值]`(操作符两侧留空格)。操作符用**中文**(真实设计器产出):等于/不等于/大于/小于/大于等于/小于等于/包含/不包含/包含全部/包含任意一个/等于任意一个/为空/不为空;也接受符号 == != > >= <= Contains IsEmpty。为空/不为空**不带值**(`{reason} 不为空`),其余带 JSON 值 {\"valueType\":...,\"value\":...}。多条件用 ` && `、多组用 ` || `。**不是** eq/gt/isnull——会让门户渲染 SyntaxError"],
739
739
  priorToolReferences: [{ tool: "field.list", args: { environment, schemaCode: model, published: true }, purpose: "确认条件字段与目标字段都已发布" }],
740
740
  dryRun: { supported: true, command: `omc call field.update --env ${environment} --dry-run --args '<recipe.args>'`, notes: "dry-run 校验字段 options 形状,不写入" },
741
- expectedReadback: { readback: { field: { code: "leave_reason", options: { requiredFormula: "<protocol>" } } }, next: ["field.publish", "form.draft(rebuild)", "form.publish"] },
741
+ expectedReadback: { readback: { field: { code: "leave_reason", options: { requiredFormula: "<protocol>" } } }, next: ["(一调)field.update cascadeRepublish:true", "(分步)field.publish → form.draft(rebuild) → form.publish", "(只刷新表单)form.publish rebuild:true"] },
742
742
  refusalRecovery: [
743
743
  { code: "capability-out-of-coverage", action: "条件串用中文或符号操作符(等于/大于/不为空 或 == />/IsEmpty);恒必填用字面量 \"true\"" },
744
744
  ...commonRecovery,
@@ -755,7 +755,7 @@ const formAlwaysReadonly = Object.freeze({
755
755
  conditionalRequirements: ["恒只读用布尔 readonlyFormula:true;条件只读用 readonlyCondition 条件串"],
756
756
  priorToolReferences: [{ tool: "field.list", args: { environment, schemaCode: model, published: true }, purpose: "确认字段已发布" }],
757
757
  dryRun: { supported: true, command: `omc call field.update --env ${environment} --dry-run --args '<recipe.args>'`, notes: "dry-run 校验 readonlyFormula 是布尔", },
758
- expectedReadback: { readback: { field: { code: "approval_level", options: { readonlyFormula: true } } }, next: ["field.publish", "form.draft(rebuild)", "form.publish"] },
758
+ expectedReadback: { readback: { field: { code: "approval_level", options: { readonlyFormula: true } } }, next: ["(一调)field.update cascadeRepublish:true", "(分步)field.publish → form.draft(rebuild) → form.publish", "(只刷新表单)form.publish rebuild:true"] },
759
759
  refusalRecovery: [{ code: "capability-out-of-coverage", action: "readonlyFormula 必须是布尔 true;条件只读才用 readonlyCondition" }, ...commonRecovery],
760
760
  requiresEnvironmentDerivedIds: false,
761
761
  });
@@ -775,7 +775,7 @@ const formConditionalReadonly = Object.freeze({
775
775
  conditionalRequirements: ["条件串协议同必填;恒只读改用 readonlyFormula:true"],
776
776
  priorToolReferences: [{ tool: "field.list", args: { environment, schemaCode: model, published: true }, purpose: "确认字段已发布" }],
777
777
  dryRun: { supported: true, command: `omc call field.update --env ${environment} --dry-run --args '<recipe.args>'`, notes: "dry-run 校验条件串协议" },
778
- expectedReadback: { readback: { field: { code: "approval_level", options: { readonlyCondition: "<protocol>" } } }, next: ["field.publish", "form.draft(rebuild)", "form.publish"] },
778
+ expectedReadback: { readback: { field: { code: "approval_level", options: { readonlyCondition: "<protocol>" } } }, next: ["(一调)field.update cascadeRepublish:true", "(分步)field.publish → form.draft(rebuild) → form.publish", "(只刷新表单)form.publish rebuild:true"] },
779
779
  refusalRecovery: [{ code: "capability-out-of-coverage", action: "使用中文或符号操作符与三段条件串" }, ...commonRecovery],
780
780
  requiresEnvironmentDerivedIds: false,
781
781
  });
@@ -972,7 +972,7 @@ const bizserviceSelfCallCorp = Object.freeze({
972
972
  conditionalRequirements: [
973
973
  "**参数 code 必须精确等于 `X-LowCode-Corpid`**——适配器按「参数 code = header 名」加入请求头(headerParam.add(key, value))。",
974
974
  "**corpid 传递有两种实证形状**:①方法级入参 code 为 X-LowCode-Corpid、configJson.position 为 REQUEST_HEADER 整数 2(r3-wms 实证可用);②规则 BIZ_ACTION 的 inputMappings 加 CONST 映射(serviceMethodParameterCode 为 X-LowCode-Corpid、codeType 为 0、bizCode 放真实 corpId——r4-fin 实证:该环境下方法级 position:2 注入为空头值 [\"\"],此形状才生效)。**先用 ②,双跑验证头值;①失效(头空)就换 ②**。CONST 值必须放 bizCode(平台只读 bizCode)。",
975
- "**corpid 头非空即可(r6-onlinejs R19 实证)**:沙盒 token corpId=null 时,全 0 corpId 头(RELEVANCE-0000…)也 errcode:0;仅空头才 50000。多租户路由才需真值。凭据值不可从读回抄(读面按 ticket 115 掩码为 ***),必须用任务环境事实真值(doctor 显示的 corpId、任务提供的 client_secret)。",
975
+ "**corpid 头非空即可(r6-onlinejs R19 实证)**:沙盒 token corpId=null 时,全 0 corpId 头(RELEVANCE-0000…)也 errcode:0;仅空头才 50000。多租户路由才需真值。凭据值不可从读回抄(读面按 ticket 115 掩码——0.4.16 起为结构化 {masked:true,hint},旧版 ***),必须用任务环境事实真值(doctor 显示的 corpId、任务提供的 client_secret)。",
976
976
  "configJson.position 用 REQUEST_HEADER 名即可(工具写侧编码为整数索引 2;wire 上是整数——写名字平台侧反序列化失败、header 根本不进,源码 @JsonCreator get(int))。",
977
977
  "BIZ_ACTION 节点调用该方法**无需传该常量参数**(方法级常量由平台注入);只需照常映射业务参数。",
978
978
  ],
package/dist/fake/app.js CHANGED
File without changes
@@ -273,6 +273,32 @@ export const b2Route = (ctx, input) => {
273
273
  { sql: `DELETE FROM ${table} WHERE ...`, sqlType: "DELETE" },
274
274
  ];
275
275
  }
276
+ case "/api/api/bizservice/service/list_by_category_id":
277
+ case "/api/api/bizservice/service/list_by_category_id_find": {
278
+ // Live (BizServiceController.java:167-204/264-291): categoryId="" lists
279
+ // ALL services; the _find variant filters name/code case-insensitively.
280
+ // Rows are the FULL BizServiceModel INCLUDING configJson (authorization
281
+ // config, possibly carrying credentials) — returned deliberately so the
282
+ // TOOL-layer whitelist projection is what tests assert.
283
+ const categoryId = String(q.categoryId ?? "");
284
+ const keyword = input.path.endsWith("_find") ? String(q.keyword ?? "") : "";
285
+ const lower = keyword.toLowerCase();
286
+ return [...ctx.state.services.values()]
287
+ .filter((service) => categoryId === "" || service.serviceCategoryId === categoryId)
288
+ .filter((service) => keyword === "" || String(service.code ?? "").toLowerCase().includes(lower) || String(service.name ?? "").toLowerCase().includes(lower))
289
+ .map((service) => ({
290
+ id: service.id,
291
+ code: service.code,
292
+ name: service.name ?? null,
293
+ adapterCode: service.adapterCode ?? null,
294
+ serviceCategoryId: service.serviceCategoryId ?? null,
295
+ description: null,
296
+ shared: service.shared ?? false,
297
+ configJson: service.configJson ?? null,
298
+ createdBy: "fake-admin",
299
+ modifiedBy: "fake-admin",
300
+ }));
301
+ }
276
302
  case "/api/api/bizservice/service/create": {
277
303
  // Live: create with an empty adapterCode → 500005 业务服务关联适配器编码为空.
278
304
  if (typeof body.adapterCode !== "string" || !body.adapterCode)
File without changes
File without changes
File without changes
File without changes
File without changes
package/dist/fake/form.js CHANGED
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes