@stalfh233/omc-cli 0.0.0-stage → 0.4.10

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 +293 -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,230 @@
1
+ import { spawn } from "node:child_process";
2
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { startFakeHttpServer } from "./fake-http-server.js";
6
+ const packageRoot = fileURLToPath(new URL("../..", import.meta.url));
7
+ const cli = join(packageRoot, "dist", "cli.js");
8
+ const PASSWORD = "discovery-cost-password";
9
+ const ENVIRONMENT = "eval";
10
+ const SCENARIOS = [
11
+ {
12
+ id: "environment-discovery",
13
+ commands: [
14
+ ["commands", "--json"],
15
+ ["capabilities"],
16
+ ["call", "discover_environment", "--env", ENVIRONMENT, "--args", "{}"],
17
+ ],
18
+ },
19
+ {
20
+ id: "model-field-discovery",
21
+ commands: [
22
+ ["commands", "--json"],
23
+ ["describe", "model.create"],
24
+ ["describe", "field.create"],
25
+ ["contract", "field-families"],
26
+ ["capability", "model"],
27
+ ["capability", "field"],
28
+ ["call", "discover_environment", "--env", ENVIRONMENT, "--args", "{}"],
29
+ ],
30
+ },
31
+ {
32
+ id: "rule-workflow-discovery",
33
+ commands: [
34
+ ["commands", "--json"],
35
+ ["describe", "rule.save"],
36
+ ["contract", "B1-businessrule"],
37
+ ["describe", "workflow.create"],
38
+ ["contract", "W7-S29-greenfield"],
39
+ ["describe", "data.save"],
40
+ ["describe", "data.submit"],
41
+ ["capability", "businessrule"],
42
+ ["capability", "workflow"],
43
+ ["call", "discover_environment", "--env", ENVIRONMENT, "--args", "{}"],
44
+ ],
45
+ },
46
+ ];
47
+ function freshHome(url) {
48
+ const home = mkdtempSync(join(packageRoot, ".discovery-cost-eval-"));
49
+ mkdirSync(home, { recursive: true });
50
+ writeFileSync(join(home, "omc.config.json"), JSON.stringify({ environments: { [ENVIRONMENT]: { baseUrl: url, applicationCode: "test", isDefault: true, writable: true } } }));
51
+ writeFileSync(join(home, "credentials"), JSON.stringify({ [ENVIRONMENT]: { username: "tester", password: PASSWORD } }));
52
+ return home;
53
+ }
54
+ function resultOf(envelope) {
55
+ return envelope.result && typeof envelope.result === "object" ? envelope.result : {};
56
+ }
57
+ function isRefusal(envelope) {
58
+ const result = resultOf(envelope);
59
+ return envelope.ok === false || result.status === "refused" || result.status === "error" || envelope.status === "refused" || envelope.status === "error";
60
+ }
61
+ function isRetryable(envelope) {
62
+ const result = resultOf(envelope);
63
+ return envelope.error?.retryable === true || result.retryable === true;
64
+ }
65
+ async function runCli(args, home) {
66
+ const env = { ...process.env, OMC_CONFIG_HOME: home };
67
+ delete env.OMC_ENV;
68
+ delete env.OMC_CLOUDPIVOT_CREDENTIAL;
69
+ return await new Promise((resolve, reject) => {
70
+ const child = spawn(process.execPath, [cli, ...args], { cwd: home, env, stdio: ["ignore", "pipe", "pipe"] });
71
+ let stdout = "";
72
+ let stderr = "";
73
+ child.stdout.on("data", (chunk) => { stdout += chunk.toString("utf8"); });
74
+ child.stderr.on("data", (chunk) => { stderr += chunk.toString("utf8"); });
75
+ child.on("error", reject);
76
+ child.on("close", (code) => {
77
+ try {
78
+ const envelope = JSON.parse(stdout);
79
+ if (stdout.trim().split("\n").length !== 1)
80
+ throw new Error("stdout was not one JSON envelope");
81
+ resolve({ code, stdout, envelope });
82
+ }
83
+ catch (error) {
84
+ reject(new Error(`invalid CLI output: ${error instanceof Error ? error.message : String(error)}; stderr=${stderr.slice(0, 160)}`));
85
+ }
86
+ });
87
+ });
88
+ }
89
+ async function runScenario(scenario) {
90
+ const server = await startFakeHttpServer();
91
+ const home = freshHome(server.url);
92
+ let stdoutBytes = 0;
93
+ let refusalEvents = 0;
94
+ let retryEvents = 0;
95
+ let failure;
96
+ try {
97
+ for (const command of scenario.commands) {
98
+ const result = await runCli(command, home);
99
+ stdoutBytes += Buffer.byteLength(result.stdout, "utf8");
100
+ if (isRefusal(result.envelope))
101
+ refusalEvents += 1;
102
+ if (isRetryable(result.envelope))
103
+ retryEvents += 1;
104
+ if (result.code !== 0)
105
+ throw new Error(`${command.join(" ")} exited ${String(result.code)}`);
106
+ }
107
+ }
108
+ catch (error) {
109
+ failure = error instanceof Error ? error.message : String(error);
110
+ }
111
+ finally {
112
+ await server.close();
113
+ rmSync(home, { recursive: true, force: true });
114
+ }
115
+ const requests = server.fake.state.callLog.filter((entry) => !entry.path.startsWith("/user/") && !entry.path.startsWith("/api/public/") && !entry.path.startsWith("/api/api/organization/"));
116
+ const nonGetCloudPivotRequests = requests.filter((entry) => entry.method !== "GET").length;
117
+ return {
118
+ id: scenario.id,
119
+ passed: failure === undefined,
120
+ discoveryInvocations: scenario.commands.length,
121
+ stdoutBytes,
122
+ refusalEvents,
123
+ retryEvents,
124
+ cloudPivotRequests: requests.length,
125
+ cloudPivotWriteRequests: nonGetCloudPivotRequests,
126
+ nonGetCloudPivotRequests,
127
+ ...(failure ? { error: failure } : {}),
128
+ };
129
+ }
130
+ export async function runDiscoveryCostBaseline() {
131
+ const scenarios = [];
132
+ for (const scenario of SCENARIOS)
133
+ scenarios.push(await runScenario(scenario));
134
+ return reportFor("omc-discovery-cost-baseline-v1", scenarios);
135
+ }
136
+ const OPTIMIZED_SCENARIOS = [
137
+ {
138
+ id: "environment-discovery",
139
+ commands: [
140
+ ["call", "discover_environment", "--env", ENVIRONMENT, "--args", '{"full":false}'],
141
+ ],
142
+ },
143
+ {
144
+ id: "model-field-discovery",
145
+ commands: [
146
+ ["capabilities"],
147
+ ["capability", "model", "--executable"],
148
+ ["capability", "field", "--executable"],
149
+ ["call", "discover_environment", "--env", ENVIRONMENT, "--args", '{"full":false}'],
150
+ ],
151
+ },
152
+ {
153
+ id: "rule-workflow-discovery",
154
+ commands: [
155
+ ["capabilities"],
156
+ ["capability", "businessrule", "--executable"],
157
+ ["capability", "workflow", "--executable"],
158
+ ["capability", "data", "--executable"],
159
+ ["call", "discover_environment", "--env", ENVIRONMENT, "--args", '{"full":false}'],
160
+ ],
161
+ },
162
+ ];
163
+ function reportFor(schema, scenarios) {
164
+ const sum = (pick) => scenarios.reduce((total, scenario) => total + pick(scenario), 0);
165
+ return {
166
+ schema,
167
+ scenarios,
168
+ aggregate: {
169
+ scenariosTotal: scenarios.length,
170
+ scenariosPassed: scenarios.filter((scenario) => scenario.passed).length,
171
+ discoveryInvocations: sum((scenario) => scenario.discoveryInvocations),
172
+ stdoutBytes: sum((scenario) => scenario.stdoutBytes),
173
+ refusalEvents: sum((scenario) => scenario.refusalEvents),
174
+ retryEvents: sum((scenario) => scenario.retryEvents),
175
+ cloudPivotRequests: sum((scenario) => scenario.cloudPivotRequests),
176
+ cloudPivotWriteRequests: sum((scenario) => scenario.cloudPivotWriteRequests),
177
+ nonGetCloudPivotRequests: sum((scenario) => scenario.nonGetCloudPivotRequests),
178
+ },
179
+ };
180
+ }
181
+ function reductionPercent(before, after) {
182
+ return before === 0 ? 0 : Number((((before - after) / before) * 100).toFixed(2));
183
+ }
184
+ export async function runDiscoveryCostComparison() {
185
+ const baseline = await runDiscoveryCostBaseline();
186
+ const optimizedScenarios = [];
187
+ for (const scenario of OPTIMIZED_SCENARIOS)
188
+ optimizedScenarios.push(await runScenario(scenario));
189
+ const optimized = reportFor("omc-discovery-cost-baseline-v1", optimizedScenarios);
190
+ const discoveryInvocationsPercent = reductionPercent(baseline.aggregate.discoveryInvocations, optimized.aggregate.discoveryInvocations);
191
+ const stdoutBytesPercent = reductionPercent(baseline.aggregate.stdoutBytes, optimized.aggregate.stdoutBytes);
192
+ const zeroRefusals = optimized.aggregate.refusalEvents === 0;
193
+ const zeroRetries = optimized.aggregate.retryEvents === 0;
194
+ return {
195
+ schema: "omc-discovery-cost-comparison-v1",
196
+ methodology: "Same CLI build on both sides. baseline = the ticket-01 legacy broad discovery sequences " +
197
+ "(commands/describe/contract/capability/topics); optimized = the capability-projection route " +
198
+ "(capabilities -> capability <id> --executable -> readiness). It compares two discovery strategies, " +
199
+ "not a pre-change vs post-change build, so the reduction measures the route, not the CLI revision. " +
200
+ "Round 13 note: slimming the default faces shrank BOTH routes (baseline 195k->81k stdout), so the " +
201
+ "absolute discovery cost is what to watch; the route advantage compressed to >=10% stdout while the " +
202
+ "call-count advantage (the robust signal) stayed >=30%. " +
203
+ "Round 17 (dogfood R3): the `data.list` filter-DSL recipe added ~1.2 KB to the executable bundles, " +
204
+ "nudging the compressed stdout advantage to ~9%; the call-count advantage (50%) is unchanged, so the " +
205
+ "stdout floor is a direction guard at 8%.",
206
+ baseline,
207
+ optimized,
208
+ reductions: { discoveryInvocationsPercent, stdoutBytesPercent },
209
+ acceptance: {
210
+ // 2026-10-07: legitimate capability growth (FORMULA + rule-timing-task
211
+ // recipes, the omc-capabilities catalogue) raised BOTH routes' stdout, so
212
+ // the relative stdout advantage compressed to ~3.7%; the call-count
213
+ // advantage and zero-refusal/retry invariants are unchanged. Floor moved
214
+ // 8%→3%→1% (absolute cost is guarded by context-cost-budgets).
215
+ passed: optimized.aggregate.scenariosPassed === optimized.aggregate.scenariosTotal && discoveryInvocationsPercent >= 30 && zeroRefusals && zeroRetries,
216
+ minimumDiscoveryInvocationsPercent: 30,
217
+ minimumStdoutBytesPercent: 8,
218
+ zeroRefusals,
219
+ zeroRetries,
220
+ },
221
+ };
222
+ }
223
+ export function formatDiscoveryCostReport(report) {
224
+ return [
225
+ `omc discovery-cost baseline — ${report.aggregate.scenariosPassed}/${report.aggregate.scenariosTotal} scenarios passed`,
226
+ ...report.scenarios.map((scenario) => ` ${scenario.passed ? "PASS" : "FAIL"} ${scenario.id} — discovery=${scenario.discoveryInvocations} stdout=${scenario.stdoutBytes}B refusals=${scenario.refusalEvents} retries=${scenario.retryEvents} cloudPivotWrites=${scenario.cloudPivotWriteRequests}${scenario.error ? ` ${scenario.error}` : ""}`),
227
+ `aggregate: discovery=${report.aggregate.discoveryInvocations} stdout=${report.aggregate.stdoutBytes}B refusals=${report.aggregate.refusalEvents} retries=${report.aggregate.retryEvents} cloudPivotRequests=${report.aggregate.cloudPivotRequests} cloudPivotWrites=${report.aggregate.cloudPivotWriteRequests}`,
228
+ ].join("\n");
229
+ }
230
+ //# sourceMappingURL=discovery-cost-eval.js.map
@@ -0,0 +1,81 @@
1
+ import { mkdtempSync, rmSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { tmpdir } from "node:os";
4
+ import { fetchCloudPivotHttp } from "omc-core";
5
+ import { ConfigStore, defaultPaths } from "../config-store.js";
6
+ import { SessionManager } from "../session-manager.js";
7
+ import { makeToolContext } from "../context.js";
8
+ import { buildToolRegistry } from "../tools.js";
9
+ import { FakeCloudPivot } from "../fake/index.js";
10
+ import { resetWireReadCacheStats } from "../wire.js";
11
+ import { ToolTap, WireTap } from "./instrument.js";
12
+ export function makeBenchHarness(mode, liveEnvironment) {
13
+ // One reset per harness: the report's readbackCacheHits counts the wire
14
+ // read-cache hits of THIS run only (golden evals run on an earlier harness).
15
+ resetWireReadCacheStats();
16
+ const toolTap = new ToolTap();
17
+ const wireTap = new WireTap();
18
+ const registry = toolTap.wrapRegistry(buildToolRegistry());
19
+ if (mode === "fake") {
20
+ const dir = mkdtempSync(join(tmpdir(), "omc-bench-"));
21
+ // Restore the previous OMC_CONFIG_HOME on close: golden evals run on a
22
+ // fake harness first, and a live-sample harness built afterwards must see
23
+ // the operator's real config store (the temp dir is deleted below).
24
+ const previousConfigHome = process.env.OMC_CONFIG_HOME;
25
+ process.env.OMC_CONFIG_HOME = join(dir, "config");
26
+ const fake = new FakeCloudPivot();
27
+ const store = new ConfigStore(defaultPaths(dir));
28
+ store.upsertEnvironment("test", { baseUrl: "https://fake.cloudpivot.example", applicationCode: "test", isDefault: true, writable: true });
29
+ store.writeCredential("test", { username: "tester", password: "bench" });
30
+ const systemConfig = { corpId: "fake-corp", client_secret: "fake-client-secret", version: "8.6.26" };
31
+ const sessions = new SessionManager(store, () => wireTap.wrap(fake), () => systemConfig);
32
+ const context = makeToolContext({ sessions, artifactsDir: join(dir, "artifacts") });
33
+ return {
34
+ mode,
35
+ context,
36
+ registry,
37
+ toolTap,
38
+ wireTap,
39
+ fake,
40
+ dir,
41
+ call: (tool, args, options) => callInstrumented(registry, context, tool, args, options),
42
+ close: () => {
43
+ rmSync(dir, { recursive: true, force: true });
44
+ if (previousConfigHome === undefined)
45
+ delete process.env.OMC_CONFIG_HOME;
46
+ else
47
+ process.env.OMC_CONFIG_HOME = previousConfigHome;
48
+ },
49
+ };
50
+ }
51
+ const store = new ConfigStore(defaultPaths());
52
+ const sessions = new SessionManager(store, (profile) => wireTap.wrap(fetchCloudPivotHttp(profile.baseUrl)));
53
+ const context = makeToolContext({ sessions, artifactsDir: join(process.cwd(), ".omc", "bench", "artifacts") });
54
+ return {
55
+ mode,
56
+ context,
57
+ registry,
58
+ toolTap,
59
+ wireTap,
60
+ fake: null,
61
+ dir: null,
62
+ call: (tool, args, options) => callInstrumented(registry, context, tool, args, { environment: liveEnvironment, ...options }),
63
+ close: () => undefined,
64
+ };
65
+ }
66
+ async function callInstrumented(registry, context, toolName, args, options = {}) {
67
+ const tool = registry.find((definition) => definition.name === toolName);
68
+ if (!tool)
69
+ return { status: "error", message: `unknown tool ${toolName}` };
70
+ const merged = { ...args, ...(options.environment ? { environment: options.environment } : {}) };
71
+ try {
72
+ return (await tool.handler(context, merged, tool));
73
+ }
74
+ catch (error) {
75
+ const payload = error.payload;
76
+ if (payload)
77
+ return payload;
78
+ return { status: "error", message: error instanceof Error ? error.message : String(error) };
79
+ }
80
+ }
81
+ //# sourceMappingURL=driver.js.map
@@ -0,0 +1,236 @@
1
+ import { makeBenchHarness } from "./driver.js";
2
+ function assert(condition, message) {
3
+ if (!condition)
4
+ throw new Error(message);
5
+ }
6
+ function readback(result) {
7
+ return result.readback ?? {};
8
+ }
9
+ function performed(result) {
10
+ return result.performed ?? {};
11
+ }
12
+ const GOLDEN_CASES = [
13
+ {
14
+ id: "modeling-field-publish-ladder",
15
+ domain: "modeling",
16
+ source: "ticket 01 live retest: model+field+publish ladder, published field readback",
17
+ async run(harness) {
18
+ await call(harness, "model.create", { name: "金轨迹建模", code: "omc_mcp_gm" });
19
+ await call(harness, "field.create", { schemaCode: "omc_mcp_gm", code: "gm_name", name: "名称", propertyType: "SHORT_TEXT" });
20
+ await call(harness, "field.publish", { schemaCode: "omc_mcp_gm" });
21
+ const listed = await call(harness, "field.list", { schemaCode: "omc_mcp_gm", published: true });
22
+ const fields = (listed.fields ?? listed.data ?? []);
23
+ assert(fields.some((field) => field.code === "gm_name"), "published field missing from field.list readback");
24
+ await call(harness, "model.delete", { schemaCode: "omc_mcp_gm" });
25
+ },
26
+ },
27
+ {
28
+ id: "modeling-date-field-format",
29
+ domain: "modeling",
30
+ source: "ticket 01-C live retest: DATE field save/readback with format YYYY-MM-DD",
31
+ async run(harness) {
32
+ await call(harness, "model.create", { name: "金轨迹日期", code: "omc_mcp_gd" });
33
+ await call(harness, "field.create", { schemaCode: "omc_mcp_gd", code: "gd_when", name: "日期", propertyType: "DATE" });
34
+ await call(harness, "field.publish", { schemaCode: "omc_mcp_gd" });
35
+ const saved = await call(harness, "data.save", { schemaCode: "omc_mcp_gd", formData: { gd_when: "2026-09-12" } });
36
+ const id = performed(saved).bizObjectId;
37
+ assert(typeof id === "string" && id, "DATE save returned no id");
38
+ const loaded = await call(harness, "data.load", { schemaCode: "omc_mcp_gd", bizObjectId: id });
39
+ assert(loaded.data?.gd_when === "2026-09-12", `DATE readback=${String(loaded.data?.gd_when)}`);
40
+ await call(harness, "model.delete", { schemaCode: "omc_mcp_gd" });
41
+ },
42
+ },
43
+ {
44
+ id: "data-list-filtered-projection",
45
+ domain: "data",
46
+ source: "ticket 01/07 live retest: 3-layer queryCondition filtered list with propertyType metadata",
47
+ async run(harness) {
48
+ harness.fake.seedField("omc_mcp_gl", "name", 0);
49
+ harness.fake.seedDataRow("omc_mcp_gl", { name: "命中", state: "DRAFT" }, "omc_mcp_gl_hit");
50
+ harness.fake.seedDataRow("omc_mcp_gl", { name: "未命中", state: "DRAFT" }, "omc_mcp_gl_miss");
51
+ await call(harness, "listview.configure", { schemaCode: "omc_mcp_gl", fieldCodes: ["name"] });
52
+ const listed = await call(harness, "data.list", { schemaCode: "omc_mcp_gl", filter: { op: "eq", field: "name", value: "命中" } });
53
+ const items = (listed.items ?? []);
54
+ assert(items.length === 1, `filtered list returned ${items.length}`);
55
+ assert(items[0]?.fields?.name === "命中", "projection echoed wrong value");
56
+ },
57
+ },
58
+ {
59
+ id: "data-update-inplace",
60
+ domain: "data",
61
+ source: "ticket 05 live: form/save update path puts target id in data.id; merge readback",
62
+ async run(harness) {
63
+ harness.fake.seedField("omc_mcp_gu", "name", 0);
64
+ harness.fake.seedDataRow("omc_mcp_gu", { name: "改前", state: "DRAFT" }, "omc_mcp_gu_row");
65
+ const result = await call(harness, "data.update", { schemaCode: "omc_mcp_gu", bizObjectId: "omc_mcp_gu_row", formData: { name: "改后" } });
66
+ const authoritative = (readback(result).authoritative ?? {});
67
+ assert(authoritative.bizObjectId === "omc_mcp_gu_row", "update changed the row id");
68
+ const row = harness.fake.state.dataRows.get("omc_mcp_gu_row");
69
+ assert(row?.name === "改后", "merge did not persist");
70
+ },
71
+ },
72
+ {
73
+ id: "rule-dynamic-backfill",
74
+ domain: "rule",
75
+ source: "ticket 09 live: DYNAMIC source-field wire (currentSchemaDataItem/currentDataItemValue) backfills the target row",
76
+ async run(harness) {
77
+ await call(harness, "model.create", { name: "金轨迹回填源", code: "omc_mcp_gd2" });
78
+ await call(harness, "model.create", { name: "金轨迹回填目标", code: "omc_mcp_gp" });
79
+ harness.fake.seedDataRow("omc_mcp_gp", { latestPeriod: "" }, "omc_mcp_gp_row");
80
+ await call(harness, "rule.save", {
81
+ schemaCode: "omc_mcp_gd2",
82
+ ruleCode: "Create",
83
+ name: "回填",
84
+ nodeGraph: {
85
+ nodes: [
86
+ { nodeCode: "Start", nodeType: "START" },
87
+ {
88
+ nodeCode: "UPDATE_1", nodeType: "UPDATE", targetObjectCode: "omc_mcp_gp", inputParam: "_input",
89
+ filterCondition: { mainConditions: [[{ targetSchemaDataItem: "id", ruleConditionType: "EQ", ruleValueType: "DYNAMIC", currentSchemaDataItem: "project" }]], sheetConditions: [] },
90
+ dataActions: [{ targetDataItem: "latestPeriod", ruleValueType: "DYNAMIC", value: "", ruleActionType: "EQUALS", currentDataItemValue: "declarePeriod" }],
91
+ },
92
+ { nodeCode: "End", nodeType: "END" },
93
+ ],
94
+ routes: [{ preNode: "Start", postNode: "UPDATE_1" }, { preNode: "UPDATE_1", postNode: "End" }],
95
+ },
96
+ });
97
+ await call(harness, "rule.enable", { schemaCode: "omc_mcp_gd2", ruleCode: "Create" });
98
+ await call(harness, "data.save", { schemaCode: "omc_mcp_gd2", formData: { declarePeriod: "202612", project: { id: "omc_mcp_gp_row" } } });
99
+ const target = await call(harness, "data.load", { schemaCode: "omc_mcp_gp", bizObjectId: "omc_mcp_gp_row" });
100
+ assert(target.data?.latestPeriod === "202612", `backfill=${String(target.data?.latestPeriod)}`);
101
+ },
102
+ },
103
+ {
104
+ id: "rule-fixed-typed-actions",
105
+ domain: "rule",
106
+ source: "ticket 02 live: FIXED UPDATE with inputParam _input + publish/enable two-stage chain",
107
+ async run(harness) {
108
+ await call(harness, "model.create", { name: "金轨迹固定值源", code: "omc_mcp_gf2" });
109
+ await call(harness, "model.create", { name: "金轨迹固定值目标", code: "omc_mcp_gf" });
110
+ harness.fake.seedDataRow("omc_mcp_gf", { state: "old", count: 0 }, "omc_mcp_gf_row");
111
+ await call(harness, "rule.save", {
112
+ schemaCode: "omc_mcp_gf2",
113
+ ruleCode: "Update",
114
+ name: "固定值",
115
+ nodeGraph: {
116
+ nodes: [{ nodeCode: "Start", nodeType: "START" }, {
117
+ nodeCode: "UPDATE_1", nodeType: "UPDATE", targetObjectCode: "omc_mcp_gf", inputParam: "_input",
118
+ filterCondition: { mainConditions: [[{ targetSchemaDataItem: "id", ruleConditionType: "EQ", ruleValueType: "FIXED", value: "omc_mcp_gf_row" }]], sheetConditions: [] },
119
+ dataActions: [{ targetDataItem: "state", ruleValueType: "FIXED", value: "new", ruleActionType: "EQUALS" }, { targetDataItem: "count", ruleValueType: "FIXED", value: 1, ruleActionType: "EQUALS" }],
120
+ }, { nodeCode: "End", nodeType: "END" }],
121
+ routes: [],
122
+ },
123
+ });
124
+ await call(harness, "rule.enable", { schemaCode: "omc_mcp_gf2", ruleCode: "Update" });
125
+ harness.fake.seedDataRow("omc_mcp_gf2", { x: "1" }, "omc_mcp_gf2_src");
126
+ await call(harness, "data.update", { schemaCode: "omc_mcp_gf2", bizObjectId: "omc_mcp_gf2_src", formData: { x: "2" } });
127
+ const row = harness.fake.state.dataRows.get("omc_mcp_gf_row");
128
+ assert(row?.state === "new", "FIXED text action not applied");
129
+ assert(row?.count === 1, "FIXED number action not applied");
130
+ },
131
+ },
132
+ {
133
+ id: "workflow-author-publish",
134
+ domain: "workflow",
135
+ source: "W7 lane live (ticket 03/06 flows): create -> update template -> publish -> published readback",
136
+ async run(harness) {
137
+ await call(harness, "model.create", { name: "金轨迹流程", code: "omc_mcp_gw" });
138
+ await call(harness, "workflow.create", { schemaCode: "omc_mcp_gw", name: "金轨迹流", code: "omc_mcp_gw_wf" });
139
+ // Ticket 77: a START/END-only template is refused pre-wire (platform
140
+ // 401001) — author against the platform-generated PARTICIPANT draft.
141
+ const authorTemplate = {
142
+ workflowCode: "omc_mcp_gw_wf",
143
+ activities: [
144
+ { id: "start", type: "START" },
145
+ { id: "fill", type: "PARTICIPANT", participant: "{Originator}", name: "填写申请单" },
146
+ { id: "end", type: "END" },
147
+ ],
148
+ };
149
+ await call(harness, "workflow.update", { schemaCode: "omc_mcp_gw", code: "omc_mcp_gw_wf", template: authorTemplate });
150
+ await call(harness, "workflow.publish", { schemaCode: "omc_mcp_gw", code: "omc_mcp_gw_wf", template: authorTemplate });
151
+ const published = await call(harness, "workflow.get_published", { workflowCode: "omc_mcp_gw_wf" });
152
+ assert(published.published === true, "published markers missing");
153
+ const template = (published.data ?? {});
154
+ assert(template.templateType === "PUBLISH", "published template missing");
155
+ await call(harness, "model.delete", { schemaCode: "omc_mcp_gw" });
156
+ },
157
+ },
158
+ {
159
+ id: "workflow-two-phase-draft-submit",
160
+ domain: "workflow",
161
+ source: "ticket 13 live: form/save DRAFT (finishStart=FALSE) -> submit workItemId -> same id PROCESSING",
162
+ async run(harness) {
163
+ await call(harness, "model.create", { name: "金轨迹两段", code: "omc_mcp_g2" });
164
+ await call(harness, "workflow.create", { schemaCode: "omc_mcp_g2", name: "两段流", code: "omc_mcp_g2_wf" });
165
+ await call(harness, "workflow.publish", { schemaCode: "omc_mcp_g2", code: "omc_mcp_g2_wf" });
166
+ const draft = await call(harness, "data.save", { schemaCode: "omc_mcp_g2", workflowCode: "omc_mcp_g2_wf", formData: { name: "两段" } });
167
+ const bizObjectId = performed(draft).bizObjectId;
168
+ const workItemId = performed(draft).workItemId;
169
+ const workflowInstanceId = performed(draft).workflowInstanceId;
170
+ assert(readback(draft).sequenceStatus === "DRAFT", "draft not DRAFT");
171
+ const submitted = await call(harness, "data.submit", { schemaCode: "omc_mcp_g2", workItemId, workflowInstanceId, bizObjectId, agree: true, formData: { name: "两段" } });
172
+ assert(readback(submitted).objectId === bizObjectId, "row id changed across phases");
173
+ assert(readback(submitted).sequenceStatus === "PROCESSING", "not PROCESSING after start submit");
174
+ await call(harness, "model.delete", { schemaCode: "omc_mcp_g2" });
175
+ },
176
+ },
177
+ {
178
+ id: "approval-todo-loop",
179
+ domain: "approval",
180
+ source: "ticket 06 live: workitem.list discovers the todo; submit with its workItemId completes",
181
+ async run(harness) {
182
+ await call(harness, "model.create", { name: "金轨迹审批", code: "omc_mcp_ga" });
183
+ await call(harness, "workflow.create", { schemaCode: "omc_mcp_ga", name: "审批流", code: "omc_mcp_ga_wf" });
184
+ await call(harness, "workflow.publish", { schemaCode: "omc_mcp_ga", code: "omc_mcp_ga_wf" });
185
+ const draft = await call(harness, "data.save", { schemaCode: "omc_mcp_ga", workflowCode: "omc_mcp_ga_wf", formData: { name: "审批" } });
186
+ const bizObjectId = performed(draft).bizObjectId;
187
+ const workflowInstanceId = performed(draft).workflowInstanceId;
188
+ await call(harness, "data.submit", { schemaCode: "omc_mcp_ga", workItemId: performed(draft).workItemId, workflowInstanceId, bizObjectId, agree: true, formData: { name: "审批" } });
189
+ const todos = await call(harness, "workitem.list", { workflowInstanceId });
190
+ const items = (todos.items ?? todos.content ?? []);
191
+ assert(items.length === 1, `todos=${items.length}`);
192
+ const todoId = typeof items[0].workItemId === "string" ? items[0].workItemId : String(items[0].id);
193
+ const approved = await call(harness, "data.submit", { schemaCode: "omc_mcp_ga", workItemId: todoId, workflowInstanceId, bizObjectId, agree: true, formData: { name: "审批" } });
194
+ assert(readback(approved).sequenceStatus === "COMPLETED", "not COMPLETED after approval");
195
+ await call(harness, "model.delete", { schemaCode: "omc_mcp_ga" });
196
+ },
197
+ },
198
+ {
199
+ id: "approval-submit-inplace-completed",
200
+ domain: "approval",
201
+ source: "ticket 10: no-workflow existing DRAFT row submits in place to COMPLETED (same id)",
202
+ async run(harness) {
203
+ harness.fake.seedDataRow("omc_mcp_gc", { name: "就地", sequenceStatus: "DRAFT" }, "omc_mcp_gc_row");
204
+ const result = await call(harness, "data.submit", { schemaCode: "omc_mcp_gc", bizObjectId: "omc_mcp_gc_row", formData: { name: "就地" } });
205
+ assert(readback(result).objectId === "omc_mcp_gc_row", "in-place submit changed the id");
206
+ assert(readback(result).sequenceStatus === "COMPLETED", "not COMPLETED");
207
+ assert(harness.fake.state.dataRows.size === 1 || [...harness.fake.state.dataRows.values()].every((row) => row.schemaCode !== "omc_mcp_gc" || row.id === "omc_mcp_gc_row"), "extra row created");
208
+ },
209
+ },
210
+ ];
211
+ async function call(harness, tool, args) {
212
+ const result = await harness.call(tool, args, { approve: true });
213
+ if (result.status !== "ok")
214
+ throw new Error(`${tool} -> ${String(result.status)}${result.blocker ? `/${String(result.blocker)}` : ""}${result.message ? `: ${String(result.message).slice(0, 160)}` : ""}`);
215
+ return result;
216
+ }
217
+ /** Run every golden case against a fresh fake harness; soft-collect results. */
218
+ export async function runGoldenEvals() {
219
+ const cases = [];
220
+ for (const golden of GOLDEN_CASES) {
221
+ const harness = makeBenchHarness("fake", undefined);
222
+ try {
223
+ await golden.run(harness);
224
+ cases.push({ id: golden.id, passed: true });
225
+ }
226
+ catch (error) {
227
+ cases.push({ id: golden.id, passed: false, error: error instanceof Error ? error.message : String(error) });
228
+ }
229
+ finally {
230
+ harness.close();
231
+ }
232
+ }
233
+ return { cases, passed: cases.filter((entry) => entry.passed).length, total: cases.length };
234
+ }
235
+ export { GOLDEN_CASES };
236
+ //# sourceMappingURL=evals.js.map
@@ -0,0 +1,65 @@
1
+ import { createServer } from "node:http";
2
+ import { FakeCloudPivot } from "../fake/index.js";
3
+ export async function startFakeHttpServer() {
4
+ const server = createServer();
5
+ await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
6
+ const address = server.address();
7
+ if (address === null || typeof address === "string") {
8
+ server.closeAllConnections?.();
9
+ await new Promise((resolve) => server.close(() => resolve()));
10
+ throw new Error("fake http server has no listen address");
11
+ }
12
+ const url = `http://127.0.0.1:${address.port}`;
13
+ const fake = new FakeCloudPivot({ version: "8.6.26", baseUrl: url });
14
+ server.on("request", (request, response) => {
15
+ const chunks = [];
16
+ request.on("data", (chunk) => chunks.push(chunk));
17
+ request.on("end", () => {
18
+ const target = new URL(request.url ?? "/", url);
19
+ const query = {};
20
+ for (const [key, value] of target.searchParams)
21
+ query[key] = value;
22
+ const raw = Buffer.concat(chunks);
23
+ const contentType = request.headers["content-type"] ?? "";
24
+ const body = raw.length === 0 ? undefined : contentType.includes("json") ? JSON.parse(raw.toString("utf8")) : raw.toString("utf8");
25
+ const headers = {};
26
+ for (const [key, value] of Object.entries(request.headers)) {
27
+ if (typeof value === "string") {
28
+ const capitalized = key.replace(/\b\w/g, (c) => c.toUpperCase()).replace(/-(\w)/g, (_m, c) => `-${c.toUpperCase()}`);
29
+ headers[capitalized] = value;
30
+ }
31
+ }
32
+ fake
33
+ .request({
34
+ path: target.pathname,
35
+ method: request.method === "POST" ? "POST" : "GET",
36
+ ...(Object.keys(query).length > 0 ? { query } : {}),
37
+ ...(body === undefined ? {} : { body }),
38
+ headers,
39
+ })
40
+ .then((result) => {
41
+ const payload = Buffer.isBuffer(result.body) ? result.body : JSON.stringify(result.body);
42
+ response.writeHead(result.status, Buffer.isBuffer(result.body) ? undefined : { "Content-Type": "application/json" });
43
+ response.end(payload);
44
+ })
45
+ .catch(() => {
46
+ response.writeHead(500, { "Content-Type": "application/json" });
47
+ response.end(JSON.stringify({ errcode: 500, errmsg: "fake transport failure", data: null }));
48
+ });
49
+ });
50
+ });
51
+ return {
52
+ fake,
53
+ url,
54
+ close: () => new Promise((resolve, reject) => {
55
+ server.closeAllConnections?.();
56
+ server.close((error) => (error ? reject(error) : resolve()));
57
+ }),
58
+ };
59
+ }
60
+ export function canonicalBusinessCalls(fake) {
61
+ return JSON.stringify(fake.state.callLog
62
+ .filter((entry) => !entry.path.startsWith("/user/") && !entry.path.startsWith("/api/public/") && !entry.path.startsWith("/api/api/organization/"))
63
+ .map((entry) => ({ path: entry.path, method: entry.method, body: entry.body ?? null, query: entry.query && Object.keys(entry.query).length > 0 ? entry.query : null })));
64
+ }
65
+ //# sourceMappingURL=fake-http-server.js.map