@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,343 @@
1
+ /**
2
+ * Canonical bench scenario — the business demo the whole tool surface is
3
+ * built around: 客户/项目/产出申报 three models, modeling + publish, the
4
+ * data loop (save/list/update), a DYNAMIC cross-model backfill rule, and the
5
+ * two-phase workflow approval (draft save -> start submit -> approval todo ->
6
+ * COMPLETED). Fixtures carry the `omc_mcp_` prefix; live runs clean up via
7
+ * model physical-delete cascade with absence readback.
8
+ */
9
+ // Fixture codes carry a per-run nonce: the platform's physical-delete cascade
10
+ // can orphan a deleted model's default data items (301012 默认数据项不可删除 —
11
+ // they are uncleanable and block re-creation with 601001 模型编码重复), so a
12
+ // fresh run must never race the previous run's residue. Override with
13
+ // OMC_BENCH_FIXTURE for deliberate reuse.
14
+ const RUN_NONCE = process.env.OMC_BENCH_FIXTURE ?? `${new Date().toISOString().slice(2, 10).replaceAll("-", "")}${Math.random().toString(36).slice(2, 6)}`;
15
+ const CUSTOMER = `omc_mcp_bcust_${RUN_NONCE}`;
16
+ const PROJECT = `omc_mcp_bproj_${RUN_NONCE}`;
17
+ const DECLARATION = `omc_mcp_bdecl_${RUN_NONCE}`;
18
+ const WORKFLOW = `omc_mcp_bwf_${RUN_NONCE}`;
19
+ const NO_ASSERTIONS = () => undefined;
20
+ class StepRunner {
21
+ harness;
22
+ steps = [];
23
+ #aborted = false;
24
+ constructor(harness) {
25
+ this.harness = harness;
26
+ }
27
+ get aborted() {
28
+ return this.#aborted;
29
+ }
30
+ /**
31
+ * One scenario step: governed tool call (auto-approved fixture loop) +
32
+ * soft assertions. A failed tool call aborts the scenario (later steps
33
+ * depend on it); failed assertions do not.
34
+ */
35
+ async step(id, tool, args, assertions = NO_ASSERTIONS, options = {}) {
36
+ if (this.#aborted && !options.cleanup)
37
+ return {};
38
+ const started = performance.now();
39
+ let passed = 0;
40
+ let failed = 0;
41
+ const failures = [];
42
+ const check = (description, fn) => {
43
+ try {
44
+ fn();
45
+ passed += 1;
46
+ }
47
+ catch (error) {
48
+ failed += 1;
49
+ failures.push(`${description}: ${error instanceof Error ? error.message : String(error)}`);
50
+ }
51
+ };
52
+ let result = {};
53
+ let ok = false;
54
+ let errorMessage;
55
+ try {
56
+ result = await this.harness.call(tool, args, { approve: true });
57
+ ok = result.status === "ok";
58
+ if (!ok)
59
+ errorMessage = `${result.status}${result.blocker ? `/${String(result.blocker)}` : ""}${result.message ? ` ${String(result.message).slice(0, 200)}` : ""}`;
60
+ if (ok)
61
+ assertions(result, check);
62
+ }
63
+ catch (error) {
64
+ errorMessage = error instanceof Error ? error.message : String(error);
65
+ }
66
+ const record = {
67
+ id,
68
+ tool,
69
+ status: ok ? "ok" : "failed",
70
+ ok,
71
+ durationMs: performance.now() - started,
72
+ responseBytes: Buffer.from(JSON.stringify(result) ?? "null").length,
73
+ assertionsPassed: passed,
74
+ assertionsFailed: failed,
75
+ ...(errorMessage ? { error: errorMessage } : {}),
76
+ };
77
+ if (!options.cleanup) {
78
+ this.steps.push(record);
79
+ if (failures.length > 0)
80
+ this.steps[this.steps.length - 1] = { ...record, error: [...(record.error ? [record.error] : []), ...failures].join(" | ") };
81
+ if (!ok)
82
+ this.#aborted = true;
83
+ }
84
+ return result;
85
+ }
86
+ }
87
+ function performField(result, field) {
88
+ const value = result.performed?.[field];
89
+ return typeof value === "string" ? value : "";
90
+ }
91
+ function readbackField(result, field) {
92
+ return result.readback?.[field];
93
+ }
94
+ function workItemIdOf(item) {
95
+ return typeof item?.workItemId === "string" ? item.workItemId : typeof item?.id === "string" ? item.id : "";
96
+ }
97
+ function requireId(value) {
98
+ if (!value)
99
+ throw new Error("missing id");
100
+ }
101
+ export async function runScenario(harness) {
102
+ const runner = new StepRunner(harness);
103
+ const live = harness.mode === "live-sample";
104
+ // --- modeling: three models, business fields, publish, default forms ---
105
+ await runner.step("model-customer", "model.create", { name: "基准客户", code: CUSTOMER });
106
+ await runner.step("model-project", "model.create", { name: "基准项目", code: PROJECT });
107
+ await runner.step("model-declaration", "model.create", { name: "基准产出申报", code: DECLARATION });
108
+ await runner.step("field-customer-name", "field.create", { schemaCode: CUSTOMER, code: "custName", name: "客户名称", propertyType: "SHORT_TEXT" });
109
+ await runner.step("field-customer-grade", "field.create", { schemaCode: CUSTOMER, code: "creditGrade", name: "信用等级", propertyType: "RADIO", choices: ["A", "B", "C"] });
110
+ await runner.step("field-project-name", "field.create", { schemaCode: PROJECT, code: "projectName", name: "项目名称", propertyType: "SHORT_TEXT" });
111
+ await runner.step("field-project-period", "field.create", { schemaCode: PROJECT, code: "latestPeriod", name: "最近申报期", propertyType: "SHORT_TEXT" });
112
+ await runner.step("field-decl-period", "field.create", { schemaCode: DECLARATION, code: "declarePeriod", name: "申报期", propertyType: "SHORT_TEXT" });
113
+ await runner.step("field-decl-amount", "field.create", { schemaCode: DECLARATION, code: "amount", name: "申报金额", propertyType: "NUMERICAL", options: { scale: 2 } });
114
+ await runner.step("field-decl-project", "field.create", {
115
+ schemaCode: DECLARATION,
116
+ code: "project",
117
+ name: "所属项目",
118
+ propertyType: "WORK_SHEET",
119
+ relativeCode: PROJECT,
120
+ options: { schemaCode: PROJECT, queryCode: PROJECT, displayField: "projectName" },
121
+ });
122
+ await runner.step("field-publish-customer", "field.publish", { schemaCode: CUSTOMER });
123
+ await runner.step("field-publish-project", "field.publish", { schemaCode: PROJECT });
124
+ await runner.step("field-publish-declaration", "field.publish", { schemaCode: DECLARATION });
125
+ await runner.step("form-draft-customer", "form.draft", { schemaCode: CUSTOMER, sheetCode: CUSTOMER });
126
+ await runner.step("form-publish-customer", "form.publish", { schemaCode: CUSTOMER, sheetCode: CUSTOMER });
127
+ await runner.step("form-draft-project", "form.draft", { schemaCode: PROJECT, sheetCode: PROJECT });
128
+ await runner.step("form-publish-project", "form.publish", { schemaCode: PROJECT, sheetCode: PROJECT });
129
+ await runner.step("form-draft-declaration", "form.draft", { schemaCode: DECLARATION, sheetCode: DECLARATION });
130
+ await runner.step("form-publish-declaration", "form.publish", { schemaCode: DECLARATION, sheetCode: DECLARATION });
131
+ // Ticket 07 live knowledge: a fresh model's default list view has no
132
+ // business columns — configure them or the list projection reads back null.
133
+ // Live fidelity: CloudPivot seeds 13 system data items on every fresh model
134
+ // (including `name` 数据标题), so business fields use non-system codes.
135
+ await runner.step("listview-configure-customer", "listview.configure", { schemaCode: CUSTOMER, fieldCodes: ["custName", "creditGrade"] });
136
+ // --- data loop: customer + project rows, filtered list ---
137
+ const customer = await runner.step("data-save-customer", "data.save", { schemaCode: CUSTOMER, formData: { custName: "基准客户A", creditGrade: "A" } }, (result, check) => {
138
+ check("customer row id returned", () => requireId(performField(result, "bizObjectId")));
139
+ });
140
+ void customer;
141
+ // Live rule semantics: a business-rule UPDATE target query only sees
142
+ // non-DRAFT rows (BizObjectUtil filters PROCESSING/COMPLETED), so the
143
+ // backfill target is created AND submitted (in-place, no workflow) in one
144
+ // step — data.save alone would leave it DRAFT and invisible to the rule.
145
+ const project = await runner.step("data-submit-project", "data.submit", { schemaCode: PROJECT, formData: { projectName: "基准项目P1", latestPeriod: "" } }, (result, check) => {
146
+ check("project row id returned", () => requireId(performField(result, "bizObjectId") || readbackField(result, "objectId")));
147
+ check("project row COMPLETED", () => {
148
+ const status = readbackField(result, "sequenceStatus");
149
+ if (status !== "COMPLETED")
150
+ throw new Error(`sequenceStatus=${String(status)}`);
151
+ });
152
+ });
153
+ const projectId = performField(project, "bizObjectId") || readbackField(project, "objectId");
154
+ await runner.step("data-list-customer", "data.list", { schemaCode: CUSTOMER, filter: { op: "eq", field: "custName", value: "基准客户A" } }, (result, check) => {
155
+ const items = result.items;
156
+ check("filtered list hits exactly one row", () => {
157
+ if (!items || items.length !== 1)
158
+ throw new Error(`expected 1 item, got ${items?.length ?? "none"}`);
159
+ });
160
+ check("list projection echoes the business field", () => {
161
+ if (items?.[0]?.fields?.custName !== "基准客户A")
162
+ throw new Error(`custName=${String(items?.[0]?.fields?.custName)}`);
163
+ });
164
+ });
165
+ // --- DYNAMIC cross-model backfill rule: declaration create -> project.latestPeriod ---
166
+ await runner.step("rule-save-dynamic", "rule.save", {
167
+ schemaCode: DECLARATION,
168
+ ruleCode: "Create",
169
+ name: "回填最近申报期",
170
+ nodeGraph: {
171
+ nodes: [
172
+ { nodeCode: "Start", nodeType: "START" },
173
+ {
174
+ nodeCode: "UPDATE_1",
175
+ nodeType: "UPDATE",
176
+ targetObjectCode: PROJECT,
177
+ inputParam: "_input",
178
+ filterCondition: {
179
+ mainConditions: [[{ targetSchemaDataItem: "id", ruleConditionType: "EQ", ruleValueType: "DYNAMIC", currentSchemaDataItem: "project" }]],
180
+ sheetConditions: [],
181
+ },
182
+ dataActions: [{ targetDataItem: "latestPeriod", ruleValueType: "DYNAMIC", value: "", ruleActionType: "EQUALS", currentDataItemValue: "declarePeriod" }],
183
+ },
184
+ { nodeCode: "End", nodeType: "END" },
185
+ ],
186
+ routes: [{ preNode: "Start", postNode: "UPDATE_1" }, { preNode: "UPDATE_1", postNode: "End" }],
187
+ },
188
+ });
189
+ await runner.step("rule-enable-dynamic", "rule.enable", { schemaCode: DECLARATION, ruleCode: "Create" });
190
+ // The Create preset rule fires on the DRAFT→COMPLETED transition, so the
191
+ // declaration is saved, updated (readback normalisation), then submitted
192
+ // in place — data.save alone stays DRAFT and never triggers the rule.
193
+ const declaration = await runner.step("data-save-declaration", "data.save", {
194
+ schemaCode: DECLARATION,
195
+ formData: { declarePeriod: "202609", amount: 1000, project: { id: projectId } },
196
+ }, (result, check) => {
197
+ check("declaration row id returned", () => requireId(performField(result, "bizObjectId")));
198
+ });
199
+ const declarationId = performField(declaration, "bizObjectId");
200
+ await runner.step("data-update-declaration", "data.update", { schemaCode: DECLARATION, bizObjectId: declarationId, formData: { amount: 2000 } }, (result, check) => {
201
+ check("update readback carries the new amount", () => {
202
+ const value = result.readback?.authoritative?.amount;
203
+ if (value !== 2000)
204
+ throw new Error(`amount=${String(value)}`);
205
+ });
206
+ });
207
+ await runner.step("data-submit-declaration", "data.submit", {
208
+ schemaCode: DECLARATION,
209
+ bizObjectId: declarationId,
210
+ formData: { declarePeriod: "202609", amount: 2000, project: { id: projectId } },
211
+ }, (result, check) => {
212
+ check("declaration row COMPLETED", () => {
213
+ const status = readbackField(result, "sequenceStatus");
214
+ if (status !== "COMPLETED")
215
+ throw new Error(`sequenceStatus=${String(status)}`);
216
+ });
217
+ });
218
+ await runner.step("rule-backfill-readback", "data.load", { schemaCode: PROJECT, bizObjectId: projectId }, (result, check) => {
219
+ const period = result.data?.latestPeriod;
220
+ check("DYNAMIC rule backfilled latestPeriod", () => {
221
+ if (period !== "202609")
222
+ throw new Error(`latestPeriod=${String(period)}`);
223
+ });
224
+ });
225
+ // --- two-phase workflow approval: draft -> start submit -> approval todo -> COMPLETED ---
226
+ // Live-proven authoring shape (ticket 03 + 2026-09-12 probe): a bare
227
+ // template-less publish crashes the platform's saveDraftTemplate
228
+ // (cancelNotify copyProperties → 50000). Author = read the workflow's own
229
+ // draft, fill the empty approval participant, update, publish the FULL
230
+ // draft. The fake's minimal draft takes the same path.
231
+ await runner.step("workflow-create", "workflow.create", { schemaCode: DECLARATION, name: "申报审批", code: WORKFLOW });
232
+ const workflowDraft = await runner.step("workflow-draft-readback", "workflow.get_draft", { workflowCode: WORKFLOW, full: true });
233
+ const draftTemplate = (workflowDraft.data ?? { workflowCode: WORKFLOW });
234
+ const filledActivities = Array.isArray(draftTemplate.activities) && draftTemplate.activities.length > 0
235
+ ? draftTemplate.activities.map((activity) => activity.activityType === "PARTICIPANT" && !activity.participant ? { ...activity, participant: "{Originator}" } : activity)
236
+ : [
237
+ { activityCode: "Activity1", activityType: "START", activityName: "开始" },
238
+ { activityCode: "Activity2", activityType: "PARTICIPANT", activityName: "填写申请单", participant: "{Originator}" },
239
+ { activityCode: "Activity3", activityType: "PARTICIPANT", activityName: "审批", participant: "{Originator}" },
240
+ { activityCode: "Activity4", activityType: "END", activityName: "结束" },
241
+ ];
242
+ const workflowTemplate = { ...draftTemplate, activities: filledActivities };
243
+ await runner.step("workflow-update", "workflow.update", { schemaCode: DECLARATION, code: WORKFLOW, template: workflowTemplate });
244
+ await runner.step("workflow-publish", "workflow.publish", { schemaCode: DECLARATION, code: WORKFLOW, template: workflowTemplate }, (result, check) => {
245
+ check("workflow published", () => {
246
+ if (readbackField(result, "published") !== true)
247
+ throw new Error(`published=${String(readbackField(result, "published"))}`);
248
+ });
249
+ });
250
+ const draft = await runner.step("data-save-draft", "data.save", { schemaCode: DECLARATION, workflowCode: WORKFLOW, formData: { declarePeriod: "202610", amount: 3000, project: { id: projectId } } }, (result, check) => {
251
+ check("draft stays DRAFT", () => {
252
+ if (readbackField(result, "sequenceStatus") !== "DRAFT")
253
+ throw new Error(`sequenceStatus=${String(readbackField(result, "sequenceStatus"))}`);
254
+ });
255
+ check("start workItem present", () => requireId(performField(result, "workItemId")));
256
+ });
257
+ const bizObjectId = performField(draft, "bizObjectId");
258
+ const workItemId = performField(draft, "workItemId");
259
+ const workflowInstanceId = performField(draft, "workflowInstanceId");
260
+ await runner.step("data-submit-start", "data.submit", {
261
+ schemaCode: DECLARATION,
262
+ workItemId,
263
+ workflowInstanceId,
264
+ bizObjectId,
265
+ agree: true,
266
+ formData: { declarePeriod: "202610", amount: 3000, project: { id: projectId } },
267
+ }, (result, check) => {
268
+ check("same row id submitted", () => {
269
+ if (readbackField(result, "objectId") !== bizObjectId)
270
+ throw new Error("objectId changed");
271
+ });
272
+ check("row moved to PROCESSING", () => {
273
+ if (readbackField(result, "sequenceStatus") !== "PROCESSING")
274
+ throw new Error(`sequenceStatus=${String(readbackField(result, "sequenceStatus"))}`);
275
+ });
276
+ });
277
+ const todos = await runner.step("workitem-list", "workitem.list", { workflowInstanceId }, (result, check) => {
278
+ check("one approval todo pending", () => {
279
+ const items = result.items;
280
+ if (!items || items.length !== 1)
281
+ throw new Error(`todos=${items?.length ?? 0}`);
282
+ });
283
+ });
284
+ const approvalWorkItemId = workItemIdOf(todos.items?.[0]);
285
+ await runner.step("data-submit-approval", "data.submit", {
286
+ schemaCode: DECLARATION,
287
+ workItemId: approvalWorkItemId,
288
+ workflowInstanceId,
289
+ bizObjectId,
290
+ agree: true,
291
+ formData: { declarePeriod: "202610", amount: 3000, project: { id: projectId } },
292
+ }, (result, check) => {
293
+ check("row COMPLETED after approval", () => {
294
+ if (readbackField(result, "sequenceStatus") !== "COMPLETED")
295
+ throw new Error(`sequenceStatus=${String(readbackField(result, "sequenceStatus"))}`);
296
+ });
297
+ });
298
+ await runner.step("terminal-readback", "data.load", { schemaCode: DECLARATION, bizObjectId }, (result, check) => {
299
+ check("authoritative load shows COMPLETED", () => {
300
+ const status = result.data?.sequenceStatus;
301
+ if (status !== "COMPLETED")
302
+ throw new Error(`sequenceStatus=${String(status)}`);
303
+ });
304
+ });
305
+ if (live) {
306
+ // Live cleanup: model physical-delete cascade + absence readback. Runs
307
+ // even when aborted; excluded from metrics. Fixture codes are per-run
308
+ // nonces, so a slow or partially-orphaned cascade cannot block the next
309
+ // run (orphaned default data items are undeletable — 301012).
310
+ // Ticket 23: the order is dependency-inverted (d→p→c). Back-to-back
311
+ // deletes race the previous model's per-column async cascade (40-60s):
312
+ // live evidence shows the p/c deletes can come back refused
313
+ // (model-referenced-by while the relation columns still exist, or a
314
+ // transient wire-business envelope while the engine is busy) — wait and
315
+ // re-check instead of giving up; the deletes are idempotent
316
+ // (already-absent short-circuits), so retrying is always safe.
317
+ for (const code of [DECLARATION, PROJECT, CUSTOMER]) {
318
+ for (let attempt = 1; attempt <= 3; attempt += 1) {
319
+ // `{ schemaCode }` — model.delete's schema key. The original `{ code }`
320
+ // arg never reached the handler's schemaCode (no zod gate on the
321
+ // direct bench path), so every cleanup delete silently short-circuited
322
+ // as already-absent and left live fixtures behind (ticket 23).
323
+ const result = await runner.step(`cleanup-model-${code}${attempt > 1 ? `-retry-${attempt}` : ""}`, "model.delete", { schemaCode: code }, undefined, { cleanup: true });
324
+ const done = result.status === "ok" && result.readback?.absent === true;
325
+ const retryable = result.blocker === "model-referenced-by" ||
326
+ result.blocker === "wire-business" ||
327
+ result.status === "uncertain";
328
+ if (done || !retryable)
329
+ break;
330
+ if (attempt < 3)
331
+ await new Promise((resolve) => setTimeout(resolve, 45_000));
332
+ }
333
+ }
334
+ await runner.step("cleanup-absence-readback", "form.get", { schemaCode: DECLARATION }, (result, check) => {
335
+ check("default form absence after cascade", () => {
336
+ if (result.absent !== true && result.status !== "refused")
337
+ throw new Error(`absent=${String(result.absent)} status=${String(result.status)}`);
338
+ });
339
+ }, { cleanup: true });
340
+ }
341
+ return { steps: runner.steps, aborted: runner.aborted };
342
+ }
343
+ //# sourceMappingURL=scenario.js.map
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Bench report shape — the five-dimension scoreboard consumed by the
3
+ * autonomy loop (`omc bench`). One run produces one BenchReport; the next
4
+ * run diffs against the stored previous report.
5
+ *
6
+ * Five dimensions (each must carry at least one machine-readable metric):
7
+ * speed — scenario wall clock
8
+ * efficiency — wire call volume, duplicate targets, readback cache hits
9
+ * quality — scenario step pass rate + assertion pass rate
10
+ * token — response bytes + tools/list bytes, tokens ≈ bytes/4
11
+ * intent — golden-trajectory replay pass rate (live-verified sources only)
12
+ */
13
+ /** Metric direction for delta verdicts — drives the autonomy loop's keep/revert decisions. */
14
+ export const METRIC_DIRECTIONS = {
15
+ "speed.wallClockMs": "lower-is-better",
16
+ "efficiency.wireCalls": "lower-is-better",
17
+ "efficiency.duplicateWireCalls": "lower-is-better",
18
+ "efficiency.repeatReadbackTargets": "lower-is-better",
19
+ "efficiency.readbackCacheHits": "higher-is-better",
20
+ "quality.stepsOk": "higher-is-better",
21
+ "quality.assertionRate": "higher-is-better",
22
+ "token.responseBytes": "lower-is-better",
23
+ "token.toolsListBytes": "lower-is-better",
24
+ "token.estimatedTokens": "lower-is-better",
25
+ "intent.intentAccuracy": "higher-is-better",
26
+ };
27
+ /** Flatten the dimension tree into canonical metric keys ("speed.wallClockMs", …). */
28
+ export function flattenMetrics(dimensions) {
29
+ return {
30
+ "speed.wallClockMs": Math.round(dimensions.speed.wallClockMs),
31
+ "speed.slowestStepMs": Math.round(dimensions.speed.slowestStepMs),
32
+ "efficiency.wireCalls": dimensions.efficiency.wireCalls,
33
+ "efficiency.uniqueWirePaths": dimensions.efficiency.uniqueWirePaths,
34
+ "efficiency.duplicateWireCalls": dimensions.efficiency.duplicateWireCalls,
35
+ "efficiency.repeatReadbackTargets": dimensions.efficiency.repeatReadbackTargets,
36
+ "efficiency.readbackCacheHits": dimensions.efficiency.readbackCacheHits,
37
+ "quality.stepsOk": dimensions.quality.stepsOk,
38
+ "quality.stepsTotal": dimensions.quality.stepsTotal,
39
+ "quality.assertionsPassed": dimensions.quality.assertionsPassed,
40
+ "quality.assertionsTotal": dimensions.quality.assertionsTotal,
41
+ "quality.assertionRate": round4(dimensions.quality.assertionRate),
42
+ "token.responseBytes": dimensions.token.responseBytes,
43
+ "token.toolsListBytes": dimensions.token.toolsListBytes,
44
+ "token.estimatedTokens": dimensions.token.estimatedTokens,
45
+ "intent.cases": dimensions.intent.cases,
46
+ "intent.passed": dimensions.intent.passed,
47
+ "intent.intentAccuracy": round4(dimensions.intent.intentAccuracy),
48
+ };
49
+ }
50
+ export function computeDelta(previous, current) {
51
+ const before = flattenMetrics(previous.dimensions);
52
+ const after = flattenMetrics(current.dimensions);
53
+ const delta = {};
54
+ for (const [key, value] of Object.entries(after)) {
55
+ const prior = before[key];
56
+ if (prior === undefined)
57
+ continue;
58
+ const direction = key in METRIC_DIRECTIONS ? METRIC_DIRECTIONS[key] : "neutral";
59
+ let changePct;
60
+ if (prior === 0)
61
+ changePct = value === 0 ? 0 : 1_000_000;
62
+ else
63
+ changePct = round4(((value - prior) / Math.abs(prior)) * 100);
64
+ let verdict = "same";
65
+ if (direction !== "neutral" && changePct !== 0) {
66
+ const gotBetter = direction === "lower-is-better" ? value < prior : value > prior;
67
+ verdict = gotBetter ? "improved" : "regressed";
68
+ }
69
+ delta[key] = { previous: prior, current: value, changePct, direction, verdict };
70
+ }
71
+ return delta;
72
+ }
73
+ function round4(value) {
74
+ return Math.round(value * 10000) / 10000;
75
+ }
76
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1,41 @@
1
+ import { CloudPivotRequestTimeoutError } from "./session.js";
2
+ import { WireError } from "./wire.js";
3
+ /**
4
+ * Drive one BI call and return its decoded `data`. Failures raise `WireError`
5
+ * so the governed read/write pipelines classify them exactly like platform
6
+ * envelope failures: a transport/timeout failure is UNCERTAIN (freeze, never
7
+ * assume success), a BI `errorCode` is a definitive business refusal.
8
+ */
9
+ export async function biCall(session, call) {
10
+ const body = {
11
+ ...(call.body ?? {}),
12
+ corpId: call.auth.corpId,
13
+ config: { token: session.token(), reportCode: call.auth.reportCode, appCode: call.auth.appCode },
14
+ };
15
+ const response = await session
16
+ .request({
17
+ path: call.path,
18
+ method: call.method ?? "POST",
19
+ ...(call.query ? { query: call.query } : {}),
20
+ body,
21
+ })
22
+ .catch((error) => {
23
+ if (error instanceof CloudPivotRequestTimeoutError)
24
+ throw new WireError("transport", error.message);
25
+ throw error;
26
+ });
27
+ if (response.status < 200 || response.status >= 300)
28
+ throw new WireError("http-status", `HTTP ${response.status} for BI ${call.path}`, undefined, { httpStatus: response.status });
29
+ const envelope = response.body;
30
+ if (!envelope || typeof envelope !== "object")
31
+ throw new WireError("malformed-envelope", `non-object BI envelope for ${call.path}`);
32
+ const record = envelope;
33
+ if (record.errorCode !== undefined && record.errorCode !== null && record.errorCode !== "")
34
+ throw new WireError("business", `BI ${call.path} rejected: ${String(record.errorMessage ?? record.errorCode)} [${String(record.errorCode)}]`);
35
+ if (!("data" in record))
36
+ throw new WireError("malformed-envelope", `BI ${call.path} returned no data field`);
37
+ return record.data;
38
+ }
39
+ /** Default BI corp id observed on the aozhe tenant; overridable per call. */
40
+ export const DEFAULT_BI_CORP_ID = "a";
41
+ //# sourceMappingURL=bi-wire.js.map