@stalfh233/omc-cli 0.0.0-stage → 0.4.0

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 (181) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +304 -3
  3. package/coverage/m1-coverage-manifest-v1.json +1278 -0
  4. package/dist/approval-token.js +102 -0
  5. package/dist/args.js +25 -0
  6. package/dist/artifacts.js +119 -0
  7. package/dist/bench/call-face-eval.js +256 -0
  8. package/dist/bench/context-attribution.js +151 -0
  9. package/dist/bench/discovery-cost-eval.js +230 -0
  10. package/dist/bench/driver.js +81 -0
  11. package/dist/bench/evals.js +236 -0
  12. package/dist/bench/fake-http-server.js +65 -0
  13. package/dist/bench/instrument.js +87 -0
  14. package/dist/bench/intent-face-eval.js +343 -0
  15. package/dist/bench/run.js +166 -0
  16. package/dist/bench/scenario.js +343 -0
  17. package/dist/bench/types.js +76 -0
  18. package/dist/bi-wire.js +41 -0
  19. package/dist/bizservice-config.js +581 -0
  20. package/dist/call.js +153 -0
  21. package/dist/capability-absences.js +23 -0
  22. package/dist/capability-overview.js +492 -0
  23. package/dist/capability-shape.js +154 -0
  24. package/dist/cli-contract.js +70 -0
  25. package/dist/cli-output.js +73 -0
  26. package/dist/cli.js +1418 -0
  27. package/dist/code-rules.js +69 -0
  28. package/dist/command-transport.js +155 -0
  29. package/dist/config-store.js +195 -0
  30. package/dist/context.js +21 -0
  31. package/dist/contract-consistency.js +66 -0
  32. package/dist/contract-resources.js +62 -0
  33. package/dist/coverage-consistency.js +62 -0
  34. package/dist/coverage-registry.js +76 -0
  35. package/dist/coverage.js +130 -0
  36. package/dist/data-list-filter.js +114 -0
  37. package/dist/discovery.js +390 -0
  38. package/dist/endpoints.js +154 -0
  39. package/dist/environment-policy.js +26 -0
  40. package/dist/execution-metadata.js +1092 -0
  41. package/dist/fake/app.js +45 -0
  42. package/dist/fake/b2-registration.js +594 -0
  43. package/dist/fake/businessrule.js +242 -0
  44. package/dist/fake/datarule.js +72 -0
  45. package/dist/fake/dictionary.js +108 -0
  46. package/dist/fake/environment.js +30 -0
  47. package/dist/fake/field.js +167 -0
  48. package/dist/fake/form.js +101 -0
  49. package/dist/fake/index.js +121 -0
  50. package/dist/fake/list-view.js +289 -0
  51. package/dist/fake/model.js +136 -0
  52. package/dist/fake/online-js.js +18 -0
  53. package/dist/fake/report.js +253 -0
  54. package/dist/fake/routes.js +47 -0
  55. package/dist/fake/rule-lifecycle.js +37 -0
  56. package/dist/fake/runtime-data.js +367 -0
  57. package/dist/fake/state.js +67 -0
  58. package/dist/fake/workflow.js +364 -0
  59. package/dist/field-change.js +200 -0
  60. package/dist/field-families.js +896 -0
  61. package/dist/form-layout.js +111 -0
  62. package/dist/form-support.js +821 -0
  63. package/dist/goal-routes.js +468 -0
  64. package/dist/governed-execution.js +87 -0
  65. package/dist/human-summary.js +212 -0
  66. package/dist/identity.js +62 -0
  67. package/dist/intent/baseline.js +57 -0
  68. package/dist/intent/capabilities/bizservice.js +274 -0
  69. package/dist/intent/capabilities/businessrule.js +493 -0
  70. package/dist/intent/capabilities/datarule.js +187 -0
  71. package/dist/intent/capabilities/field.js +545 -0
  72. package/dist/intent/capabilities/form.js +136 -0
  73. package/dist/intent/capabilities/index.js +64 -0
  74. package/dist/intent/capabilities/listview.js +157 -0
  75. package/dist/intent/capabilities/model.js +100 -0
  76. package/dist/intent/capabilities/onlinejs.js +108 -0
  77. package/dist/intent/capabilities/report.js +355 -0
  78. package/dist/intent/capabilities/workflow.js +458 -0
  79. package/dist/intent/capability.js +6 -0
  80. package/dist/intent/cli.js +91 -0
  81. package/dist/intent/compare.js +56 -0
  82. package/dist/intent/compiler.js +79 -0
  83. package/dist/intent/dsl.js +129 -0
  84. package/dist/intent/plan-file.js +63 -0
  85. package/dist/intent/readback.js +65 -0
  86. package/dist/intent/schema.js +158 -0
  87. package/dist/intent/validation.js +30 -0
  88. package/dist/intent/yaml.js +315 -0
  89. package/dist/json-column.js +68 -0
  90. package/dist/lanes/app-contract.js +95 -0
  91. package/dist/lanes/app-coverage.js +16 -0
  92. package/dist/lanes/app.js +174 -0
  93. package/dist/lanes/apply-changes.js +231 -0
  94. package/dist/lanes/b2-registration-contract.js +292 -0
  95. package/dist/lanes/b2-registration-coverage.js +48 -0
  96. package/dist/lanes/b2-registration.js +1187 -0
  97. package/dist/lanes/businessrule-contract.js +232 -0
  98. package/dist/lanes/businessrule-coverage.js +16 -0
  99. package/dist/lanes/businessrule.js +221 -0
  100. package/dist/lanes/contract-support.js +65 -0
  101. package/dist/lanes/coverage-declaration.js +9 -0
  102. package/dist/lanes/datarule-contract.js +155 -0
  103. package/dist/lanes/datarule-coverage.js +19 -0
  104. package/dist/lanes/datarule-protocol.js +308 -0
  105. package/dist/lanes/datarule.js +818 -0
  106. package/dist/lanes/dictionary-contract.js +78 -0
  107. package/dist/lanes/dictionary-coverage.js +24 -0
  108. package/dist/lanes/dictionary.js +235 -0
  109. package/dist/lanes/environment-contract.js +70 -0
  110. package/dist/lanes/environment-coverage.js +14 -0
  111. package/dist/lanes/environment.js +163 -0
  112. package/dist/lanes/field-contract.js +223 -0
  113. package/dist/lanes/field-coverage.js +27 -0
  114. package/dist/lanes/field.js +374 -0
  115. package/dist/lanes/form-contract.js +97 -0
  116. package/dist/lanes/form-coverage.js +16 -0
  117. package/dist/lanes/form.js +185 -0
  118. package/dist/lanes/lane-ids.js +34 -0
  119. package/dist/lanes/list-view-contract.js +175 -0
  120. package/dist/lanes/list-view-coverage.js +20 -0
  121. package/dist/lanes/list-view-shapes.js +1207 -0
  122. package/dist/lanes/list-view.js +578 -0
  123. package/dist/lanes/meta-contract.js +85 -0
  124. package/dist/lanes/meta.js +255 -0
  125. package/dist/lanes/model-contract.js +168 -0
  126. package/dist/lanes/model-coverage.js +20 -0
  127. package/dist/lanes/model.js +986 -0
  128. package/dist/lanes/online-js-contract.js +99 -0
  129. package/dist/lanes/online-js-coverage.js +28 -0
  130. package/dist/lanes/online-js.js +127 -0
  131. package/dist/lanes/report-contract.js +144 -0
  132. package/dist/lanes/report-coverage.js +21 -0
  133. package/dist/lanes/report.js +476 -0
  134. package/dist/lanes/rule-graph.js +1846 -0
  135. package/dist/lanes/rule-lifecycle-contract.js +92 -0
  136. package/dist/lanes/rule-lifecycle-coverage.js +20 -0
  137. package/dist/lanes/rule-lifecycle.js +176 -0
  138. package/dist/lanes/runtime-data-contract.js +264 -0
  139. package/dist/lanes/runtime-data-coverage.js +25 -0
  140. package/dist/lanes/runtime-data.js +1054 -0
  141. package/dist/lanes/workflow-contract.js +223 -0
  142. package/dist/lanes/workflow-coverage.js +40 -0
  143. package/dist/lanes/workflow.js +1813 -0
  144. package/dist/online-js-layout.js +58 -0
  145. package/dist/online-js-source.js +276 -0
  146. package/dist/package-tool.js +51 -0
  147. package/dist/package.js +73 -0
  148. package/dist/plan.js +73 -0
  149. package/dist/read.js +144 -0
  150. package/dist/redact.js +28 -0
  151. package/dist/rule-support.js +587 -0
  152. package/dist/runtime-support.js +134 -0
  153. package/dist/server.js +92 -0
  154. package/dist/session-manager.js +30 -0
  155. package/dist/session.js +149 -0
  156. package/dist/skills.js +112 -0
  157. package/dist/support.js +98 -0
  158. package/dist/tool-types.js +127 -0
  159. package/dist/tools.js +59 -0
  160. package/dist/usage-log.js +197 -0
  161. package/dist/wire.js +287 -0
  162. package/dist/workflow-support.js +99 -0
  163. package/dist/write-lease.js +26 -0
  164. package/dist/write-lock.js +109 -0
  165. package/dist/write.js +176 -0
  166. package/dist/zip.js +156 -0
  167. package/docs/tool-surface-map.md +42 -0
  168. package/package.json +71 -6
  169. package/skills/omc-acceptance-criteria.md +47 -0
  170. package/skills/omc-business-configuration.md +257 -0
  171. package/skills/omc-capabilities.md +197 -0
  172. package/skills/omc-capability-scouting.md +55 -0
  173. package/skills/omc-config-draft-review.md +171 -0
  174. package/skills/omc-five-piece-flow.md +35 -0
  175. package/skills/omc-glossary.md +86 -0
  176. package/skills/omc-refusals.md +110 -0
  177. package/skills/omc-requirement-analysis.md +161 -0
  178. package/skills/omc-requirement-vocabulary.md +51 -0
  179. package/skills/omc-start-here.md +82 -0
  180. package/skills/omc-tool-selection.md +119 -0
  181. package/skills/omc-write-hazards.md +87 -0
@@ -0,0 +1,85 @@
1
+ import { assertDescriptionCarries, assertLaneContract, behaviorBinding, CONTRACT_FAMILY, CONTRACT_REFERENCE_VERSION } from "./contract-support.js";
2
+ import { LANES } from "./lane-ids.js";
3
+ /**
4
+ * meta-governance wire facts. This is the thin, no-wire-endpoint governance
5
+ * surface (coverage manifest, status, setup, doctor); its contract is the
6
+ * single source for the tool descriptions only.
7
+ */
8
+ export const META_WIRE = Object.freeze({
9
+ coverageManifest: "the runtime tool surface is bounded by coverage/m1-coverage-manifest-v1.json; out-of-coverage calls are rejected and cite the entry",
10
+ writePermission: "writing is decided by the environment's writable permission (the single machine gate); a production environment additionally requires a per-write confirmProduction acknowledgement recorded in the audit artifact; the human gate is the conversational draft review",
11
+ credentials: "setup.commit writes omc.config.json (non-secret) and a 0600 credential store; secrets never appear in any output",
12
+ doctor: "doctor runs read-only: config readable → credentials → login → version-family compatibility (advisory, anchored on 8.6) → environment fingerprint",
13
+ versionFamily: CONTRACT_FAMILY,
14
+ });
15
+ export const META_LANE_CONTRACT = Object.freeze({
16
+ lane: LANES.meta,
17
+ family: CONTRACT_FAMILY,
18
+ referenceVersion: CONTRACT_REFERENCE_VERSION,
19
+ endpoints: Object.freeze({}),
20
+ wire: Object.freeze({
21
+ coverageManifest: META_WIRE.coverageManifest,
22
+ writePermission: META_WIRE.writePermission,
23
+ credentials: META_WIRE.credentials,
24
+ doctor: META_WIRE.doctor,
25
+ versionFamily: META_WIRE.versionFamily,
26
+ }),
27
+ wireBindings: Object.freeze({
28
+ coverageManifest: Object.freeze([behaviorBinding("test/coverage-gate.test.ts#S23: the gate is structured", assertMetaContract)]),
29
+ writePermission: Object.freeze([behaviorBinding("test/ticket-87-production-write-denial.test.ts#production write without confirmation is refused", assertMetaContract)]),
30
+ credentials: Object.freeze([behaviorBinding("test/config-store.test.ts#S22: credential store is written with mode 0600", assertMetaContract)]),
31
+ doctor: Object.freeze([behaviorBinding("test/protocol.test.ts#tool surface: doctor returns the step chain", assertMetaContract)]),
32
+ versionFamily: Object.freeze([behaviorBinding("test/coverage-gate.test.ts#version family is advisory: an off-family version is accepted with a compatibility notice", assertMetaContract)]),
33
+ }),
34
+ evidence: Object.freeze([
35
+ "packages/omc-cli/src/lanes/meta.ts",
36
+ "packages/omc-cli/src/args.ts",
37
+ "packages/omc-cli/src/plan.ts",
38
+ "packages/omc-cli/src/write.ts",
39
+ "packages/omc-cli/src/package.ts",
40
+ "packages/omc-cli/src/context.ts",
41
+ "packages/omc-cli/test/protocol.test.ts",
42
+ ]),
43
+ tools: Object.freeze({
44
+ "coverage.manifest": Object.freeze({
45
+ scope: "coverage manifest readback",
46
+ description: "List the M1 coverage manifest with status per capability. The tool surface is bounded by this manifest; out-of-coverage calls are rejected citing the entry.",
47
+ }),
48
+ get_status: Object.freeze({
49
+ scope: "environment health status",
50
+ description: "Environment health: connectivity, login, version-family compatibility, credential store mode, lease state, coverage summary, write posture (writable permission; production requires per-write confirmation; removed config keys).",
51
+ }),
52
+ "setup.start": Object.freeze({
53
+ scope: "setup bootstrap",
54
+ description: "配置引导入口(中文):返回需要填写的字段与当前非密值,供人工在会话内配置;填好后用 setup.commit 提交。",
55
+ }),
56
+ "setup.commit": Object.freeze({
57
+ scope: "setup commit",
58
+ description: "提交环境配置(幂等):写 omc.config.json 与 0600 凭据库;配置是否生产环境(风险标签)与是否允许写入(唯一的写入机器门);随后连通性+登录+版本族兼容提示。",
59
+ }),
60
+ doctor: Object.freeze({
61
+ scope: "health self-check",
62
+ description: "健康自检:配置可读 → 连通性 → 登录 → 8.6 版本族兼容 → 环境指纹。只报告,不修改。",
63
+ }),
64
+ }),
65
+ });
66
+ export function assertMetaContract() {
67
+ assertLaneContract(META_LANE_CONTRACT);
68
+ if (META_WIRE.versionFamily !== "8.6")
69
+ throw new Error("meta contract pins the version family to 8.6");
70
+ assertDescriptionCarries(META_LANE_CONTRACT, "coverage.manifest", ["manifest", "out-of-coverage"]);
71
+ assertDescriptionCarries(META_LANE_CONTRACT, "get_status", ["version-family", "coverage"]);
72
+ assertDescriptionCarries(META_LANE_CONTRACT, "setup.start", ["setup.commit"]);
73
+ assertDescriptionCarries(META_LANE_CONTRACT, "setup.commit", ["omc.config.json", "0600", "生产环境", "允许写入"]);
74
+ assertDescriptionCarries(META_LANE_CONTRACT, "get_status", ["writable", "confirmation"]);
75
+ assertDescriptionCarries(META_LANE_CONTRACT, "doctor", ["只报告", "8.6"]);
76
+ if (!META_WIRE.coverageManifest.includes("m1-coverage-manifest-v1.json"))
77
+ throw new Error("meta contract must pin the coverage manifest ledger");
78
+ if (!META_WIRE.writePermission.includes("writable"))
79
+ throw new Error("meta contract must pin the write permission gate");
80
+ if (!META_WIRE.credentials.includes("0600"))
81
+ throw new Error("meta contract must pin the 0600 credential store");
82
+ if (!META_WIRE.doctor.includes("version-family"))
83
+ throw new Error("meta contract must pin the doctor step chain");
84
+ }
85
+ //# sourceMappingURL=meta-contract.js.map
@@ -0,0 +1,255 @@
1
+ import { z } from "zod";
2
+ import { environmentArg } from "../args.js";
3
+ import { environmentNotice, environmentWritable, requiresProductionConfirmation } from "../environment-policy.js";
4
+ import { removedProfileKeys } from "../config-store.js";
5
+ import { defineTool } from "../tool-types.js";
6
+ import { META_LANE_CONTRACT } from "./meta-contract.js";
7
+ const META_TOOLS = META_LANE_CONTRACT.tools;
8
+ export function registerMetaLane(tools) {
9
+ tools.push(defineTool({
10
+ name: "coverage.manifest",
11
+ layer: "meta",
12
+ kind: "meta",
13
+ description: META_TOOLS["coverage.manifest"].description,
14
+ inputSchema: { full: z.boolean().optional().describe("返回完整覆盖投影;默认返回紧凑摘要") },
15
+ handler: async (ctx, args) => {
16
+ const full = args.full === true;
17
+ const items = ctx.manifest.items.map((item) => full
18
+ ? {
19
+ lane: item.lane,
20
+ capability: item.capability,
21
+ status: item.status,
22
+ scope: item.scope,
23
+ ...(item.tools ? { tools: item.tools } : {}),
24
+ ...(item.scopes ? { scopes: item.scopes } : {}),
25
+ ...(item.excluded_or_unsupported_reason ? { reason: item.excluded_or_unsupported_reason } : {}),
26
+ }
27
+ : {
28
+ lane: item.lane,
29
+ capability: item.capability,
30
+ status: item.status,
31
+ });
32
+ if (full) {
33
+ return {
34
+ status: "ok",
35
+ manifest: {
36
+ version_family: ctx.manifest.version_family,
37
+ anchor_version: ctx.manifest.anchor_version,
38
+ items,
39
+ },
40
+ };
41
+ }
42
+ const byStatus = ctx.manifest.items.reduce((acc, item) => {
43
+ acc[item.status] = (acc[item.status] ?? 0) + 1;
44
+ return acc;
45
+ }, {});
46
+ return {
47
+ status: "ok",
48
+ manifest: {
49
+ version_family: ctx.manifest.version_family,
50
+ anchor_version: ctx.manifest.anchor_version,
51
+ counts: { total: items.length, byStatus },
52
+ items,
53
+ hint: "coverage.manifest full:true 取完整投影(含 scope/reason/tools/scopes);lane 能力面用 omc capabilities / omc capability <id>;工具清单用 commands --json --compact",
54
+ },
55
+ };
56
+ },
57
+ }));
58
+ tools.push(defineTool({
59
+ name: "get_status",
60
+ layer: "high",
61
+ kind: "meta",
62
+ description: META_TOOLS["get_status"].description,
63
+ inputSchema: { environment: environmentArg },
64
+ handler: async (ctx, args, self) => {
65
+ const store = ctx.sessions.config();
66
+ const resolved = store.resolveEnvironment(args.environment);
67
+ const environments = store.listEnvironments();
68
+ let sessionState;
69
+ try {
70
+ const session = await ctx.sessions.session(args.environment);
71
+ const version = session.detectedVersion();
72
+ sessionState = {
73
+ login: "ok",
74
+ version: version ?? null,
75
+ versionFamilyOk: version ? version.startsWith(ctx.manifest.version_family + ".") : null,
76
+ environmentFingerprint: session.environmentFingerprint(),
77
+ };
78
+ }
79
+ catch (error) {
80
+ sessionState = { login: "failed", error: error.message };
81
+ }
82
+ const statusCounts = ctx.manifest.items.reduce((acc, item) => {
83
+ acc[item.status] = (acc[item.status] ?? 0) + 1;
84
+ return acc;
85
+ }, {});
86
+ return {
87
+ status: "ok",
88
+ environment: {
89
+ name: resolved.name,
90
+ source: resolved.source,
91
+ production: requiresProductionConfirmation(resolved.profile),
92
+ writable: environmentWritable(resolved.profile),
93
+ writeRequiresConfirmation: requiresProductionConfirmation(resolved.profile),
94
+ removedConfigKeys: removedProfileKeys(resolved.profile),
95
+ },
96
+ configuredEnvironments: environments.map((e) => e.name),
97
+ credentialsMode: store.credentialsMode(),
98
+ session: sessionState,
99
+ coverage: { total: ctx.manifest.items.length, byStatus: statusCounts },
100
+ writeLease: { activeHolder: ctx.lease.holder(resolved.name) ?? null },
101
+ };
102
+ },
103
+ }));
104
+ tools.push(defineTool({
105
+ name: "setup.start",
106
+ layer: "meta",
107
+ kind: "meta",
108
+ description: META_TOOLS["setup.start"].description,
109
+ inputSchema: {},
110
+ handler: async (ctx) => {
111
+ const store = ctx.sessions.config();
112
+ const environments = store.listEnvironments();
113
+ return {
114
+ status: "ok",
115
+ fields: [
116
+ { name: "environmentName", required: true, description: "环境名称,例如 test" },
117
+ { name: "baseUrl", required: true, description: "CloudPivot 服务地址" },
118
+ { name: "applicationCode", required: true, description: "默认应用 code(可选:Agent 可用 discover_environment 自行发现其它应用)" },
119
+ { name: "isDefault", required: false, description: "是否设为默认环境" },
120
+ { name: "production", required: false, description: "是否生产环境(高危风险标签):写入仍需每笔显式 confirmProduction:true;它不是第二把钥匙,也不改变 writable" },
121
+ { name: "writable", required: false, description: "是否允许新增、修改、删除和发布(唯一的写入机器门);关闭后仍可查询" },
122
+ { name: "username", required: true, secret: true, description: "登录账号" },
123
+ { name: "password", required: true, secret: true, description: "登录密码;以 0600 存储,绝不回显" },
124
+ ],
125
+ current: environments.map((entry) => ({
126
+ name: entry.name,
127
+ baseUrl: entry.profile.baseUrl,
128
+ applicationCode: entry.profile.applicationCode,
129
+ isDefault: entry.profile.isDefault === true,
130
+ production: requiresProductionConfirmation(entry.profile),
131
+ writable: environmentWritable(entry.profile),
132
+ removedConfigKeys: removedProfileKeys(entry.profile),
133
+ })),
134
+ credentialsMode: store.credentialsMode(),
135
+ note: "密钥绝不会出现在任何输出中;请由人工提供账号密码后调用 setup.commit 提交",
136
+ };
137
+ },
138
+ }));
139
+ tools.push(defineTool({
140
+ name: "setup.commit",
141
+ layer: "meta",
142
+ kind: "meta",
143
+ description: META_TOOLS["setup.commit"].description,
144
+ inputSchema: {
145
+ environmentName: z.string().min(1),
146
+ baseUrl: z.string().url(),
147
+ applicationCode: z.string().min(1),
148
+ isDefault: z.boolean().optional(),
149
+ production: z.boolean().optional(),
150
+ writable: z.boolean().optional(),
151
+ username: z.string().optional(),
152
+ password: z.string().optional(),
153
+ setProjectEnv: z.boolean().optional(),
154
+ },
155
+ handler: async (ctx, args, self) => {
156
+ const store = ctx.sessions.config();
157
+ const name = args.environmentName;
158
+ const previous = store.readConfig().environments[name];
159
+ const production = args.production ?? previous?.production ?? false;
160
+ store.upsertEnvironment(name, {
161
+ baseUrl: args.baseUrl,
162
+ applicationCode: args.applicationCode,
163
+ ...(args.isDefault === true ? { isDefault: true } : previous?.isDefault === true ? { isDefault: true } : {}),
164
+ production,
165
+ ...(args.writable !== undefined ? { writable: args.writable } : previous?.writable !== undefined ? { writable: previous.writable } : {}),
166
+ });
167
+ if (args.username && args.password)
168
+ store.writeCredential(name, { username: args.username, password: args.password });
169
+ if (args.setProjectEnv === true)
170
+ store.bindProjectEnvironment(name);
171
+ let check;
172
+ try {
173
+ const session = await ctx.sessions.session(name);
174
+ check = { login: "ok", version: session.detectedVersion() ?? null, environmentFingerprint: session.environmentFingerprint() };
175
+ }
176
+ catch (error) {
177
+ check = { login: "failed", error: error.message };
178
+ }
179
+ const saved = store.readConfig().environments[name];
180
+ return {
181
+ status: "ok",
182
+ environment: name,
183
+ idempotent: previous !== undefined,
184
+ policy: {
185
+ production: saved ? requiresProductionConfirmation(saved) : false,
186
+ writable: saved ? environmentWritable(saved) : false,
187
+ writeRequiresConfirmation: saved ? requiresProductionConfirmation(saved) : false,
188
+ },
189
+ removedConfigKeys: saved ? removedProfileKeys(saved) : [],
190
+ credentialsMode: store.credentialsMode(),
191
+ check,
192
+ };
193
+ },
194
+ }));
195
+ tools.push(defineTool({
196
+ name: "doctor",
197
+ layer: "meta",
198
+ kind: "meta",
199
+ description: META_TOOLS["doctor"].description,
200
+ inputSchema: { environment: environmentArg },
201
+ handler: async (ctx, args, self) => {
202
+ const store = ctx.sessions.config();
203
+ const steps = [];
204
+ let resolved;
205
+ const resolveEnv = () => store.resolveEnvironment(args.environment);
206
+ try {
207
+ resolved = resolveEnv();
208
+ steps.push({
209
+ step: "配置",
210
+ ok: true,
211
+ environment: resolved.name,
212
+ source: resolved.source,
213
+ production: requiresProductionConfirmation(resolved.profile),
214
+ writable: environmentWritable(resolved.profile),
215
+ writeRequiresConfirmation: requiresProductionConfirmation(resolved.profile),
216
+ removedConfigKeys: removedProfileKeys(resolved.profile),
217
+ notice: environmentNotice(resolved.profile),
218
+ });
219
+ }
220
+ catch (error) {
221
+ steps.push({ step: "配置", ok: false, error: error.message });
222
+ return { status: "failed", steps };
223
+ }
224
+ try {
225
+ const credential = store.readCredential(resolved.name);
226
+ steps.push({ step: "凭据", ok: true, mode: store.credentialsMode(), username: credential.username });
227
+ }
228
+ catch (error) {
229
+ steps.push({ step: "凭据", ok: false, error: error.message });
230
+ return { status: "failed", steps };
231
+ }
232
+ try {
233
+ const session = await ctx.sessions.session(args.environment);
234
+ const version = session.detectedVersion();
235
+ steps.push({ step: "登录", ok: true, enterpriseId: session.enterpriseId(), administrator: session.administrator() });
236
+ const familyOk = version ? version.startsWith(ctx.manifest.version_family + ".") : null;
237
+ steps.push({
238
+ step: "版本族",
239
+ ok: familyOk === true,
240
+ version: version ?? null,
241
+ expectedFamily: ctx.manifest.version_family,
242
+ anchor: ctx.manifest.anchor_version,
243
+ note: familyOk === null ? "缺少版本证据;写入将保守拒绝" : familyOk ? "兼容" : "不兼容;写入将保守拒绝",
244
+ });
245
+ steps.push({ step: "环境指纹", ok: true, fingerprint: session.environmentFingerprint() });
246
+ return { status: "ok", steps };
247
+ }
248
+ catch (error) {
249
+ steps.push({ step: "登录", ok: false, error: error.message });
250
+ return { status: "failed", steps };
251
+ }
252
+ },
253
+ }));
254
+ }
255
+ //# sourceMappingURL=meta.js.map
@@ -0,0 +1,168 @@
1
+ import { assertDescriptionCarries, assertLaneContract, behaviorBinding, CONTRACT_FAMILY, CONTRACT_REFERENCE_VERSION, emitterBinding } from "./contract-support.js";
2
+ import { LANES } from "./lane-ids.js";
3
+ /**
4
+ * model-management wire facts (live-proven). The create body, the
5
+ * exits_bizschema readback, and the physical-delete cascade are the single
6
+ * source for the handler descriptions and the coverage declarations.
7
+ */
8
+ export const MODEL_WIRE = Object.freeze({
9
+ createBody: "bizmodels/create carries appCode, parentId (folder), modelType, bizSchemaCode, icon, name_i18n, pcAble/mobileAble, options{statisticEnabled:false}",
10
+ readback: "exits_bizschema readback via apppackage/trees (type==='BizModel' && code===schemaCode)",
11
+ folderResolution: "explicit folderId > first non-BizModel tree node (auto-default-folder); none found → 302001",
12
+ deleteBody: "apppackage/physically_delete body [{id,code,type:'BizModel'}] where id MUST be the app-tree node id (AppFunction id)",
13
+ deleteIdNamespace: "physically_delete resolves each row id as an AppFunction id (removeBizModel → getAppFunction(id), AppIntegrationFacadeImpl) and answers 601003 业务模型不存在 for a biz-model record id; the two id spaces are NOT interchangeable. model.delete/deleteMany resolve the node across applications (default app first, then applicationList per-app trees) and refuse fail-closed with model-application-unresolved when the record exists but no tree node does — never guessing a record id",
14
+ deleteTolerance: "already-absent short-circuits; the authoritative record face polls to verified absence; the tree node is eventually consistent and reported as treeNodePending",
15
+ referencePrecheck: "before physically_delete, every same-app sibling model's bizproperty faces (draft + published) are scanned for relation fields targeting the model (relativeCode or options.schemaCode of WORK_SHEET/MULT_WORK_SHEET/RELEVANCE_DATA); any hit refuses with blocker model-referenced-by + referencingModels — the server-side async 'xxx被模型 yyy 引用,不能删除' (AppIntegrationFacadeImpl.deleteFunctions:2201, live TID xydiq8enji3ziq4bl2diurx9c) surfaced at call time instead of errcode 0 followed by a silent async failure",
16
+ dependencyInvertedDelete: "referenced cleanup is dependency-inverted: delete referencing models first, then the referenced model; the physical delete cascades per-column asynchronously (3-4s per column, 40-60s for a 12-column model), so absent:false + treeNodePending:true means wait-and-recheck (the poll runs ~60s and returns nextAction guidance), never an immediate failure verdict — especially right after deleting a referencing model, whose cascade may still be dropping the relation columns",
17
+ usableLadder: "model.create → field.create → model-scoped field.publish (rung-1..3); configure_model performs an authoritative readback immediately after every model.create, field.create, field.publish, form.draft, and form.publish step and stops with completed/failed step evidence on failure; form draft/publish is the separate configure_form capability",
18
+ summaryWire: "bizmodels/get_summary (GET ?schemaCode) returns the data-title string; bizmodels/update_summary (POST {schemaCode, summary}) requires a non-empty summary (validateNotEmpty) and answers 300014 BIZ_SCHEMA_NOT_PUBLISHED (ErrorCode.java:194) for a missing schema — 8.6.26 BizSchemaController.java:668-736",
19
+ });
20
+ /**
21
+ * Fake-side refusal errcode for a referenced physical delete. Live evidence
22
+ * (ticket 23): the sync face answers errcode 0 + deletedId while the async
23
+ * deleteFunctions dispatcher fails with ServiceException — no live envelope
24
+ * exists for the check. The fake surfaces the check synchronously so the
25
+ * blocker and the referencing-model list are visible even when a caller
26
+ * bypasses the tool precheck; 50000 stands for the generic ServiceException
27
+ * envelope family and the authoritative signal is the verbatim errmsg text.
28
+ */
29
+ export const MODEL_REFERENCED_DELETE_ERRCODE = 50000;
30
+ /**
31
+ * The platform create-body defaults the emitter sends verbatim. The emitter
32
+ * imports THIS constant to build the wire body (model.create and the
33
+ * configure_model inline step), so changing a value here changes the actual
34
+ * request — the contract fact is enforced, not just documented.
35
+ */
36
+ export const MODEL_CREATE_BODY_DEFAULTS = Object.freeze({
37
+ modelType: "LIST",
38
+ icon: "h-icon-all-yingyong",
39
+ pcAble: true,
40
+ mobileAble: true,
41
+ options: JSON.stringify({ statisticEnabled: false }),
42
+ });
43
+ const MODEL_SCOPE = "create publish physical delete absence read list inspect";
44
+ export const MODEL_LANE_CONTRACT = Object.freeze({
45
+ lane: LANES.model,
46
+ family: CONTRACT_FAMILY,
47
+ referenceVersion: CONTRACT_REFERENCE_VERSION,
48
+ endpoints: Object.freeze({
49
+ list: "modelList",
50
+ get: "modelGet",
51
+ create: "modelCreate",
52
+ tree: "applicationTree",
53
+ physicallyDelete: "appPackagePhysicallyDelete",
54
+ fieldCreate: "fieldCreate",
55
+ fieldPublish: "fieldPublish",
56
+ fieldList: "fieldList",
57
+ }),
58
+ wire: Object.freeze({
59
+ createBody: MODEL_WIRE.createBody,
60
+ readback: MODEL_WIRE.readback,
61
+ folderResolution: MODEL_WIRE.folderResolution,
62
+ deleteBody: MODEL_WIRE.deleteBody,
63
+ deleteIdNamespace: MODEL_WIRE.deleteIdNamespace,
64
+ deleteTolerance: MODEL_WIRE.deleteTolerance,
65
+ referencePrecheck: MODEL_WIRE.referencePrecheck,
66
+ dependencyInvertedDelete: MODEL_WIRE.dependencyInvertedDelete,
67
+ usableLadder: MODEL_WIRE.usableLadder,
68
+ }),
69
+ wireBindings: Object.freeze({
70
+ createBody: Object.freeze([
71
+ emitterBinding("lanes/model-contract.MODEL_CREATE_BODY_DEFAULTS", () => {
72
+ if (MODEL_CREATE_BODY_DEFAULTS.modelType !== "LIST")
73
+ throw new Error("model create body default modelType must be LIST");
74
+ if (MODEL_CREATE_BODY_DEFAULTS.icon !== "h-icon-all-yingyong")
75
+ throw new Error("model create body default icon drifted");
76
+ if (MODEL_CREATE_BODY_DEFAULTS.pcAble !== true || MODEL_CREATE_BODY_DEFAULTS.mobileAble !== true)
77
+ throw new Error("model create body must enable pc/mobile");
78
+ if (MODEL_CREATE_BODY_DEFAULTS.options !== JSON.stringify({ statisticEnabled: false }))
79
+ throw new Error("model create body options drifted");
80
+ }),
81
+ behaviorBinding("test/model-create-body.test.ts#model.create emits the platform create body", assertModelContract),
82
+ ]),
83
+ readback: Object.freeze([behaviorBinding("test/configure-model-multi.test.ts#configure_model: N fields + form publish in one call reaches the structurally usable shape", assertModelContract)]),
84
+ folderResolution: Object.freeze([behaviorBinding("test/e2e.test.ts#S26-cli happy path", assertModelContract)]),
85
+ deleteBody: Object.freeze([behaviorBinding("test/model-delete-many.test.ts#model.deleteMany removes two models in one request", assertModelContract)]),
86
+ deleteIdNamespace: Object.freeze([behaviorBinding("test/model-delete-cross-app.test.ts#model.delete refuses fail-closed when the record exists", assertModelContract)]),
87
+ deleteTolerance: Object.freeze([behaviorBinding("test/model-delete-many.test.ts#model.deleteMany short-circuits already-absent codes", assertModelContract)]),
88
+ referencePrecheck: Object.freeze([
89
+ emitterBinding("lanes/model-contract.MODEL_REFERENCED_DELETE_ERRCODE", assertModelContract),
90
+ behaviorBinding("test/model-delete-reference-block.test.ts#model.delete refuses with model-referenced-by", assertModelContract),
91
+ ]),
92
+ dependencyInvertedDelete: Object.freeze([behaviorBinding("test/model-delete-reference-block.test.ts#dependency-inverted cleanup chain passes", assertModelContract)]),
93
+ usableLadder: Object.freeze([behaviorBinding("test/configure-model-multi.test.ts#configure_model: N fields + form publish in one call", assertModelContract)]),
94
+ }),
95
+ evidence: Object.freeze([
96
+ ".scratch/omc-route-wayfinder/leg2-closeout.md",
97
+ "packages/omc-cli/src/lanes/model.ts",
98
+ "packages/omc-cli/src/field-families.ts (field.create default options)",
99
+ ]),
100
+ tools: Object.freeze({
101
+ "model.list": Object.freeze({
102
+ scope: MODEL_SCOPE,
103
+ description: "List the models in an application (apppackage/trees, type==='BizModel') as a compact { code, name } inventory. Fills the gap where enumerating models otherwise required discover_environment + an artifact read. `applicationCode` optional (configured/default app); `filter` is a substring match on code/name.",
104
+ }),
105
+ "model.inspect": Object.freeze({
106
+ scope: MODEL_SCOPE,
107
+ description: "Read ONE model's whole design-time configuration in a single call: business fields (SYSTEM_FIELD_CODES filtered unless all:true), default form (present/published/control count), default list view (columns), business rules (platform defaultRule filtered unless all:true), field-level data rules, and the model's workflow summary (by schemaCode). Aggregates the existing read faces; `--all` includes system fields and platform default rules. Runtime data (data.*) is deliberately NOT included.",
108
+ }),
109
+ "model.create": Object.freeze({
110
+ scope: MODEL_SCOPE,
111
+ description: "Create a design-time model (bizmodels/create) with exits_bizschema readback. folderId optional (auto-resolved from the app tree). applicationCode optional — defaults to the configured app, else the tenant's first application.",
112
+ }),
113
+ "model.delete": Object.freeze({
114
+ scope: MODEL_SCOPE,
115
+ description: "Physically delete a model (apppackage/physically_delete with the proven [{id,code,type:BizModel}] body) with exact-absence readback. The row id MUST be the app-tree node id (AppFunction id) — a biz-model record id is refused by the platform with 601003 业务模型不存在; without `applicationCode` the tool resolves the node across every application (default app first, then applicationList per-app trees) and refuses fail-closed (`model-application-unresolved`) when the record exists but no tree node does, never guessing an id. Inbound references are prechecked: relation fields (relativeCode / options.schemaCode) on same-app models refuse with blocker `model-referenced-by` plus the referencingModels list — clean up dependency-inverted (delete referencing models first, then the referenced one). The async per-column cascade takes 40-60s: absent:false with treeNodePending:true means wait ~60s and re-run (idempotent; already-absent short-circuits; nextAction guidance is returned) rather than judging failure. Accepts an explicit `applicationCode` (priority over the configured default) for cross-application model management.",
116
+ }),
117
+ configure_model: Object.freeze({
118
+ scope: MODEL_SCOPE,
119
+ description: "High-level: create a model with one field (fieldCode/fieldName/propertyType, legacy) or N fields (fields:[{code,name,propertyType,choices?,relativeCode?,options?}]) and drive it to the structurally usable state (model.create → field.create×N → model-scoped field.publish), optionally drafting and publishing the default form in the same package (publishForm:true → form.draft → form.publish) and configuring the default list view (`listview:{fieldCodes,...}` → listview.configure). Resumable: if the model already exists in the application tree, the model.create step is SKIPPED (reported as `skipped:\"model-exists\"`) and the field/publish steps still run — it does not abort on a pre-existing model. Family gates run per field BEFORE the write permission gate; for the form-only path use configure_form.",
120
+ }),
121
+ "model.deleteMany": Object.freeze({
122
+ scope: MODEL_SCOPE,
123
+ description: "Physically delete several models in ONE request: apppackage/physically_delete is list-shaped (@RequestBody List<AppDeleteParamVO>, AppPackageController.java:1424), so schemaCodes[] is sent as one [{id,code,type:BizModel}] batch. Each row id is the app-tree node id (AppFunction id) resolved across applications; a target whose record exists but has no tree node refuses the whole batch fail-closed (`model-application-unresolved`) rather than sending a record id. Every target's inbound references are prechecked first — a referenced model refuses the whole batch with per-target blockedTargets evidence and zero side effects (batch members themselves never block each other; put referencing models INTO the list for dependency-inverted cleanup). Already-absent codes short-circuit; per-model absence is polled with the same wait-and-recheck guidance as model.delete.",
124
+ }),
125
+ "model.setSummary": Object.freeze({
126
+ scope: MODEL_SCOPE,
127
+ description: "Set the model's data title / 数据标题 (POST bizmodels/update_summary {schemaCode, summary}) — the fallback display of relation fields and the portal list title. The platform stores `Summary[]` JSON and runtime resolution runs JsonUtility.toList(summaryExpression, Summary.class) (BizObjectTitleSetter :140-160); Summary is {int isDataItem (1=表单字段, 2=查询字段), String code, String param}. The tool normalizes: canonical Summary[] JSON is validated/re-serialized, a PURE {field} placeholder template is converted to [{isDataItem:1,code}], and anything else (literal text, malformed JSON) refuses `model-summary-invalid` — a raw template stored verbatim poisoned the model so ALL later data.save/data.submit answered a bare 50000 (live-verified). Readback is the sibling GET bizmodels/get_summary face; it warns when the STORED value is not a valid Summary[]. Empty summary refuses `model-summary-empty`; a missing schema answers 300014 数据模型未发布.",
128
+ }),
129
+ "model.update": Object.freeze({
130
+ scope: MODEL_SCOPE,
131
+ description: "Rename a model's app-tree node (POST bizmodels/update with AppFunctionVO; source-pinned BizSchemaController.update:266-305 — bizSchemaCode, id=<AppFunction node id>, name, parentId are ALL validateNotEmpty-required; the tool resolves id/parentId from the app tree). The endpoint patches the MENU NODE only: the underlying schema is fixed at create (modelType refused `model-type-immutable`; get_all keeps the original type/name — the authoritative rename readback is the tree node, recordName reported informationally). Absent tree node refuses `model-update-target-absent`; an empty patch refuses `model-update-empty-patch` (data title belongs to model.setSummary).",
132
+ }),
133
+ }),
134
+ });
135
+ export function assertModelContract() {
136
+ assertLaneContract(MODEL_LANE_CONTRACT);
137
+ if (!MODEL_WIRE.usableLadder.includes("rung-1..3"))
138
+ throw new Error("model contract pins the usable-model ladder to rung-1..3");
139
+ assertDescriptionCarries(MODEL_LANE_CONTRACT, "model.create", ["bizmodels/create", "exits_bizschema", "folderId", "applicationCode"]);
140
+ assertDescriptionCarries(MODEL_LANE_CONTRACT, "model.delete", ["apppackage/physically_delete", "BizModel", "absence", "`applicationCode`", "model-referenced-by", "dependency-inverted", "treeNodePending", "601003", "model-application-unresolved"]);
141
+ if (!MODEL_WIRE.deleteIdNamespace.includes("601003"))
142
+ throw new Error("model contract must carry the physically_delete id-namespace 601003 fact");
143
+ assertDescriptionCarries(MODEL_LANE_CONTRACT, "configure_model", ["model.create", "field.create", "field.publish", "publishForm", "configure_form"]);
144
+ if (!MODEL_WIRE.usableLadder.includes("authoritative readback immediately after every"))
145
+ throw new Error("model contract must require per-step authoritative readbacks");
146
+ assertDescriptionCarries(MODEL_LANE_CONTRACT, "model.deleteMany", ["physically_delete", "List<AppDeleteParamVO>", "blockedTargets", "wait-and-recheck"]);
147
+ assertDescriptionCarries(MODEL_LANE_CONTRACT, "model.setSummary", ["update_summary", "get_summary", "数据标题", "300014", "model-summary-empty", "model-summary-invalid", "Summary[]"]);
148
+ assertDescriptionCarries(MODEL_LANE_CONTRACT, "model.update", ["bizmodels/update", "get_all", "model-type-immutable", "model-update-target-absent", "model-update-empty-patch"]);
149
+ if (!MODEL_WIRE.summaryWire.includes("validateNotEmpty"))
150
+ throw new Error("model contract must carry the update_summary validateNotEmpty fact");
151
+ if (!MODEL_WIRE.createBody.includes("appCode") || !MODEL_WIRE.createBody.includes("name_i18n"))
152
+ throw new Error("model contract must pin the create body");
153
+ if (!MODEL_WIRE.readback.includes("exits_bizschema"))
154
+ throw new Error("model contract must pin the create readback");
155
+ if (!MODEL_WIRE.folderResolution.includes("auto-default-folder") || !MODEL_WIRE.folderResolution.includes("302001"))
156
+ throw new Error("model contract must pin folder resolution and its absent-folder errcode");
157
+ if (!MODEL_WIRE.deleteBody.includes("physically_delete"))
158
+ throw new Error("model contract must pin the physical delete endpoint");
159
+ if (!MODEL_WIRE.deleteIdNamespace.includes("601003"))
160
+ throw new Error("model contract must pin the delete id-namespace errcode");
161
+ if (!MODEL_WIRE.deleteTolerance.includes("treeNodePending"))
162
+ throw new Error("model contract must pin the delete tolerance readback");
163
+ if (!MODEL_WIRE.referencePrecheck.includes("model-referenced-by"))
164
+ throw new Error("model contract must pin the reference-precheck blocker");
165
+ if (!MODEL_WIRE.dependencyInvertedDelete.includes("dependency-inverted"))
166
+ throw new Error("model contract must pin the dependency-inverted cleanup");
167
+ }
168
+ //# sourceMappingURL=model-contract.js.map
@@ -0,0 +1,20 @@
1
+ import { claim, declareLaneCoverage } from "./coverage-declaration.js";
2
+ import { MODEL_LANE_CONTRACT } from "./model-contract.js";
3
+ const T = MODEL_LANE_CONTRACT.tools;
4
+ export const MODEL_COVERAGE = declareLaneCoverage(MODEL_LANE_CONTRACT.lane, [
5
+ {
6
+ capability: "Model management lifecycle (create/publish/physical delete/absence)",
7
+ evidence: [".scratch/omc-route-wayfinder/leg2-closeout.md", "packages/omc-cli/src/lanes/model.ts"],
8
+ tools: [
9
+ claim("model.create", T["model.create"].scope),
10
+ claim("model.update", T["model.update"].scope),
11
+ claim("model.delete", T["model.delete"].scope),
12
+ claim("model.deleteMany", T["model.deleteMany"].scope),
13
+ claim("model.setSummary", T["model.setSummary"].scope),
14
+ claim("configure_model", T["configure_model"].scope),
15
+ claim("model.list", T["model.list"].scope),
16
+ claim("model.inspect", T["model.inspect"].scope),
17
+ ],
18
+ },
19
+ ]);
20
+ //# sourceMappingURL=model-coverage.js.map