@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,367 @@
1
+ import { ENGINE_OWNED_KEYS, RUNTIME_DATA_WIRE, RUNTIME_QUERY_OPERATORS, SUBMIT_STATUS_TRANSITIONS } from "../lanes/runtime-data-contract.js";
2
+ import { applyBusinessRules } from "./businessrule.js";
3
+ import { businessRefusal, NOT_HANDLED } from "./state.js";
4
+ import { completeWorkItem, spawnApprovalWorkItem, startWorkflowDraft, startWorkflowInstance } from "./workflow.js";
5
+ /**
6
+ * verifyReplay (ticket 22 source evidence): the platform registers tokens in
7
+ * Redis via getReplayToken->disposeReplay and consumes them single-use —
8
+ * blank or unknown token -> SYS_REPLAY_ERROR 100005 "频繁重复提交!" (ErrorCode.java:68,
9
+ * RedisCacheService.verifyFormId deletes on success). Only the two start
10
+ * branches run this gate (AbstractFormRuntimeController.handleSubmit workflowCode
11
+ * branch; FormRuntimeController.saveWorkflow isStartWf) — formType=2 and
12
+ * workItemId approval submits never check it.
13
+ */
14
+ function verifyReplayToken(state, body) {
15
+ const token = typeof body.replayToken === "string" ? body.replayToken : "";
16
+ if (!token || !state.replayTokens.has(token)) {
17
+ return businessRefusal(RUNTIME_DATA_WIRE.replayTokenErrcode, "频繁重复提交!");
18
+ }
19
+ state.replayTokens.delete(token);
20
+ return null;
21
+ }
22
+ /** M2-runtime-data lane: query/save/load/submit. Twin of src/lanes/runtime-data.ts. */
23
+ /**
24
+ * Ticket 78 live fidelity: a runtime write against a model whose application
25
+ * is UNPUBLISHED answers a stable 50000 (the same call succeeds on a
26
+ * published app's model).
27
+ */
28
+ function unpublishedAppRefusal(ctx, schemaCode) {
29
+ const code = typeof schemaCode === "string" ? schemaCode : "";
30
+ const model = code ? ctx.state.models.get(code) : undefined;
31
+ const appCode = model?.appCode ?? "test";
32
+ const app = ctx.state.apps.get(appCode);
33
+ return app?.published === false ? businessRefusal(50000, "系统异常:应用未发布") : null;
34
+ }
35
+ export const runtimeDataRoute = (ctx, input) => {
36
+ const q = input.query ?? {};
37
+ const body = (input.body ?? {});
38
+ switch (input.path) {
39
+ case "/api/api/runtime/query/list": {
40
+ // QueryRuntimeController face: queryCondition is a 3-level
41
+ // and>or>and>leaf array (level1 AND across groups, level2 OR across
42
+ // and-groups, level3 AND across leaves), PageVO response.
43
+ const schemaCode = String(body.schemaCode ?? "");
44
+ const rows = [...ctx.state.dataRows.values()].filter((row) => row.schemaCode === schemaCode);
45
+ const groups = Array.isArray(body.queryCondition) ? body.queryCondition : [];
46
+ const filtered = groups.length > 0
47
+ ? rows.filter((row) => groups.every((group) => (Array.isArray(group) ? group : [group]).some((andGroup) => (Array.isArray(andGroup) ? andGroup : [andGroup]).every((leaf) => matchesCondition(leaf, row)))))
48
+ : rows;
49
+ const page = typeof body.page === "number" ? body.page : 0;
50
+ const size = typeof body.size === "number" ? body.size : 20;
51
+ const start = page * size;
52
+ // An unconfigured default list view omits business columns from the
53
+ // projection: the stored value exists but the wire row does not carry
54
+ // it, so explicit display columns read back null (ticket 07).
55
+ const unconfigured = ctx.state.unconfiguredListViews.has(schemaCode);
56
+ const project = (row) => unconfigured
57
+ ? Object.fromEntries(Object.entries(row).filter(([key]) => !ctx.state.fields.has(`${schemaCode}#${key}`)))
58
+ : { ...row };
59
+ return {
60
+ totalElements: filtered.length,
61
+ totalPages: size > 0 ? Math.ceil(filtered.length / size) : 0,
62
+ page,
63
+ size,
64
+ content: filtered.slice(start, start + size).map((row) => ({
65
+ bizObjectId: row.bizObjectId,
66
+ schemaCode,
67
+ data: project(row),
68
+ })),
69
+ };
70
+ }
71
+ case "/api/api/runtime/form/save": {
72
+ const bizObject = (body.bizObject ?? {});
73
+ const data = bizObject.data ?? {};
74
+ // Ticket 78: unpublished-app models answer a stable 50000 on runtime saves.
75
+ const unpublished = unpublishedAppRefusal(ctx, bizObject.schemaCode);
76
+ if (unpublished)
77
+ return unpublished;
78
+ // Live 2026-09-24 (bench31): a SINGLE relation (propertyType 9) given an
79
+ // ARRAY answers a stable 50000 (object/{id}/id work; multi 11 and staff
80
+ // 50/51 legitimately take arrays).
81
+ for (const [key, field] of ctx.state.fields) {
82
+ if (!key.startsWith(`${bizObject.schemaCode}#`) || !field.published)
83
+ continue;
84
+ if (Number(field.type) !== 9)
85
+ continue;
86
+ const value = data[field.code];
87
+ if (Array.isArray(value) && value.length > 0)
88
+ return businessRefusal(50000, "服务异常");
89
+ }
90
+ // Portal draft-save shape (ticket 13): workflowCode + no workItemId +
91
+ // no workflowInstanceId -> FormRuntimeController.saveWorkflow with
92
+ // finishStart=FALSE. The row is created DRAFT (only because data carries
93
+ // sequenceStatus DRAFT; the tool injects it) together with a DRAFT
94
+ // instance and a pending START work item. FormSaveVO returns the triple
95
+ // { objectId, workflowInstanceId, workItem:{id} }.
96
+ if (typeof body.workflowCode === "string" && body.workflowCode && !body.workItemId && !body.workflowInstanceId) {
97
+ // saveWorkflow isStartWf branch runs verifyReplay before creating
98
+ // anything (FormRuntimeController:996) — no token, no draft.
99
+ const replayRefusal = verifyReplayToken(ctx.state, body);
100
+ if (replayRefusal)
101
+ return replayRefusal;
102
+ // The portal carries a client-chosen id in data.id (form SDK echo);
103
+ // the tool generates one when the caller omits it. The fake honors
104
+ // both, exactly like createAndGetBizObject keeping a supplied id.
105
+ const id = typeof data.id === "string" && data.id ? data.id : `wfdraft-${ctx.state.dataRows.size + 1}`;
106
+ const sequenceStatus = data.sequenceStatus === "DRAFT" ? "DRAFT" : "PROCESSING";
107
+ const created = { bizObjectId: id, schemaCode: bizObject.schemaCode, sequenceStatus, ...data, id };
108
+ ctx.state.dataRows.set(id, created);
109
+ applyBusinessRules(ctx.state, "Create", created);
110
+ const started = startWorkflowDraft(ctx.state, {
111
+ workflowCode: body.workflowCode,
112
+ schemaCode: String(bizObject.schemaCode ?? ""),
113
+ bizObjectId: id,
114
+ });
115
+ return {
116
+ objectId: id,
117
+ bizObjectId: id,
118
+ workflowInstanceId: started.workflowInstanceId,
119
+ // Live shape (TID 88a47a7c): getWorkItem reads inside the
120
+ // still-uncommitted request TX, so FormSaveVO carries NO workItem;
121
+ // the todo is only visible to a committed follow-up read.
122
+ ...(ctx.omitDraftSaveWorkItem
123
+ ? {}
124
+ : {
125
+ workItem: {
126
+ id: started.workItemId,
127
+ instanceId: started.workflowInstanceId,
128
+ activityCode: "fill",
129
+ activityName: "填写申请单",
130
+ state: "INPROGRESS",
131
+ workflowCode: body.workflowCode,
132
+ schemaCode: String(bizObject.schemaCode ?? ""),
133
+ bizObjectId: id,
134
+ },
135
+ }),
136
+ };
137
+ }
138
+ // Live shape: the platform reads the target id from data["id"]; a
139
+ // top-level bizObject.id is ignored (kept here as a fallback only).
140
+ const suppliedId = [data.id, bizObject.id].find((value) => typeof value === "string" && value);
141
+ const existing = suppliedId ? ctx.state.dataRows.get(suppliedId) : undefined;
142
+ if (existing) {
143
+ const merged = { ...existing, ...data, bizObjectId: existing.bizObjectId, schemaCode: existing.schemaCode };
144
+ ctx.state.dataRows.set(String(existing.bizObjectId), merged);
145
+ applyBusinessRules(ctx.state, "Update", merged);
146
+ return { objectId: existing.bizObjectId, bizObjectId: existing.bizObjectId };
147
+ }
148
+ // Live quirk: a non-empty id absent from the store does NOT update — the
149
+ // facade silently creates under that id. The tool guards against this.
150
+ const id = suppliedId ?? `row-${ctx.state.dataRows.size + 1}`;
151
+ const created = { bizObjectId: id, schemaCode: bizObject.schemaCode, ...data };
152
+ ctx.state.dataRows.set(id, created);
153
+ applyBusinessRules(ctx.state, "Create", created);
154
+ return { bizObjectId: id, objectId: id };
155
+ }
156
+ case "/api/api/runtime/form/load": {
157
+ const row = ctx.state.dataRows.get(q.bizObjectId ?? q.objectId);
158
+ if (!row)
159
+ return null;
160
+ // The live face nests bizObject alongside the flat map; keep both so
161
+ // flat-field consumers and the submit chain's bizObject.data reader
162
+ // both work.
163
+ return {
164
+ ...row,
165
+ bizObject: {
166
+ schemaCode: row.schemaCode,
167
+ sheetCode: row.schemaCode,
168
+ id: row.bizObjectId,
169
+ data: { ...row },
170
+ },
171
+ };
172
+ }
173
+ case "/api/api/runtime/form/delete": {
174
+ // Ticket 22 source evidence (FormRuntimeController:1233): @RequestParam
175
+ // schemaCode + objectId; validateNotEmpty(schemaCode) -> PortalException
176
+ // 10005 非法的参数 (ResultEnum.ILLEGAL_PARAMETER_ERR, BaseController:887);
177
+ // getBizObject miss -> BIZ_OBJECT_NOT_EXIST 304001 业务对象不存在; success
178
+ // -> ResponseResult<Boolean> data true. The old fake deleted
179
+ // q.bizObjectId (a key the tool never sends) unconditionally.
180
+ const deleteSchemaCode = String(q.schemaCode ?? "");
181
+ const deleteObjectId = String(q.objectId ?? "");
182
+ if (!deleteSchemaCode)
183
+ return businessRefusal(10005, "非法的参数");
184
+ if (!ctx.state.dataRows.has(deleteObjectId))
185
+ return businessRefusal(RUNTIME_DATA_WIRE.absenceErrcode, "业务对象不存在");
186
+ ctx.state.dataRows.delete(deleteObjectId);
187
+ return true;
188
+ }
189
+ case "/api/api/runtime/form/getReplayToken": {
190
+ // disposeReplay registers the token before it is returned
191
+ // (AbstractFormRuntimeController.replayToken -> putFormId).
192
+ const token = `REPLAY${Math.random().toString(36).slice(2).padEnd(32, "0")}`;
193
+ ctx.state.replayTokens.add(token);
194
+ return token;
195
+ }
196
+ case "/api/api/runtime/form/submit": {
197
+ // Ticket 78: unpublished-app models answer a stable 50000 on runtime submits.
198
+ const unpublished = unpublishedAppRefusal(ctx, body.bizObject?.schemaCode);
199
+ if (unpublished)
200
+ return unpublished;
201
+ // Pin the WorkItemSubmitVO contract documented in the issue 13 evidence:
202
+ // - formType="2" -> submitBizSheet: sets sequenceStatus=COMPLETED,
203
+ // returns { objectId, schemaCode, formCode }. When the payload's
204
+ // bizObject carries an id (the proven save->load->submit chain),
205
+ // the existing draft row is completed in place.
206
+ // - formType="1" + workflowCode -> startWorkflow: returns
207
+ // { workflowInstanceId }
208
+ if (body.formType === RUNTIME_DATA_WIRE.formTypeWorkflow) {
209
+ const approvalWorkItemId = typeof body.workItemId === "string" && body.workItemId ? body.workItemId : "";
210
+ if (approvalWorkItemId) {
211
+ // Ticket 22 source evidence: WorkItemSubmitVO has NO top-level
212
+ // bizObjectId — the engine reads bizObject.id only
213
+ // (initSubmitBizObject never backfills it from the work item). When
214
+ // it is empty, loadBizObject returns null (BizObjectManagerImpl:206)
215
+ // and changeBizObject/saveBizObject NPEs on objectExisted.getData()
216
+ // (DataChangeHandlerImpl:104 / SubmitDataChangeHandlerImpl:214) ->
217
+ // errcode 50000. This gate covers BOTH workItemId faces (the START
218
+ // draft item and the approval todo) and fires before any mutation:
219
+ // the platform NPE aborts the request transaction.
220
+ const approvalBizObject = (body.bizObject ?? {});
221
+ const approvalBizObjectId = typeof approvalBizObject.id === "string" && approvalBizObject.id ? approvalBizObject.id : "";
222
+ if (!approvalBizObjectId) {
223
+ return businessRefusal(RUNTIME_DATA_WIRE.approvalNpeErrcode, "engine NPE: workItem submit without bizObject.id (DataChangeHandlerImpl.objectExisted is null)");
224
+ }
225
+ // Submitting the instance's START work item (the portal draft shape):
226
+ // the start item finishes, the approval activity spawns a pending
227
+ // todo, and the SAME row id moves DRAFT -> PROCESSING. Platform
228
+ // response shape: { result, objectId } (no workflowInstanceId).
229
+ const startItem = ctx.state.workItems.get(approvalWorkItemId);
230
+ if (startItem?.isStart) {
231
+ const completed = completeWorkItem(ctx.state, approvalWorkItemId);
232
+ spawnApprovalWorkItem(ctx.state, {
233
+ instanceId: startItem.instanceId,
234
+ workflowCode: startItem.workflowCode,
235
+ schemaCode: startItem.schemaCode,
236
+ bizObjectId: startItem.bizObjectId,
237
+ });
238
+ const row = ctx.state.dataRows.get(startItem.bizObjectId);
239
+ if (row) {
240
+ mergeSubmittedData(row, body);
241
+ row.sequenceStatus = SUBMIT_STATUS_TRANSITIONS.workflowStart;
242
+ }
243
+ return { result: completed, objectId: startItem.bizObjectId };
244
+ }
245
+ // Approval submit: the addressed pending work item is completed and
246
+ // leaves the pending store (search_workitems no longer returns it).
247
+ const completed = completeWorkItem(ctx.state, approvalWorkItemId);
248
+ if (completed) {
249
+ const row = ctx.state.dataRows.get(approvalBizObjectId);
250
+ if (row) {
251
+ mergeSubmittedData(row, body);
252
+ row.sequenceStatus = body.agree === false ? SUBMIT_STATUS_TRANSITIONS.approvalReject : SUBMIT_STATUS_TRANSITIONS.approvalAgree;
253
+ }
254
+ }
255
+ return {
256
+ workflowInstanceId: typeof body.workflowInstanceId === "string" ? body.workflowInstanceId : null,
257
+ objectId: approvalBizObjectId || undefined,
258
+ workItemId: approvalWorkItemId,
259
+ agree: body.agree !== false,
260
+ completed,
261
+ };
262
+ }
263
+ // Start submit: the platform creates the in-flight bizObject and spawns
264
+ // the fill (finished) + approval (pending) work items.
265
+ // handleSubmit verifies the replay token BEFORE dispatching
266
+ // submitWorkflowByWorkflowCode (AbstractFormRuntimeController:156) —
267
+ // the gate precedes every side effect below.
268
+ const replayRefusal = verifyReplayToken(ctx.state, body);
269
+ if (replayRefusal)
270
+ return replayRefusal;
271
+ const bizObject = (body.bizObject ?? {});
272
+ // WorkItemSubmitVO carries no top-level bizObjectId (ticket 22): the
273
+ // created id comes from bizObject.id only — a top-level key is an
274
+ // unknown JSON property the platform silently ignores.
275
+ const explicitId = typeof bizObject.id === "string" && bizObject.id ? bizObject.id : "";
276
+ // Ticket 22 source evidence: submitWorkflowByWorkflowCode ->
277
+ // startWorkflowInstance(create) -> BizObjectFacadeImpl.createAndGetBizObject
278
+ // is a pure INSERT (BizObjectInsertModel, no existence check) — an
279
+ // existing id dies on the duplicate primary key (40000, live TID
280
+ // 771f64ee). The old fake silently mutated the row in place.
281
+ if (explicitId && ctx.state.dataRows.has(explicitId)) {
282
+ return businessRefusal(RUNTIME_DATA_WIRE.workflowStartDuplicateErrcode, `Duplicate entry '${explicitId}' for key 'PRIMARY' (create-only workflow start)`);
283
+ }
284
+ const bizObjectId = explicitId || `wfrow-${ctx.state.dataRows.size + 1}`;
285
+ const submittedData = typeof bizObject.data === "object" && bizObject.data ? bizObject.data : {};
286
+ ctx.state.dataRows.set(bizObjectId, {
287
+ bizObjectId,
288
+ sequenceStatus: SUBMIT_STATUS_TRANSITIONS.workflowStart,
289
+ schemaCode: String(bizObject.schemaCode ?? ""),
290
+ formCode: String(bizObject.sheetCode ?? bizObject.schemaCode ?? ""),
291
+ ...submittedData,
292
+ });
293
+ const started = startWorkflowInstance(ctx.state, {
294
+ workflowCode: String(body.workflowCode ?? ""),
295
+ schemaCode: String(bizObject.schemaCode ?? ""),
296
+ bizObjectId,
297
+ });
298
+ return { workflowInstanceId: started.workflowInstanceId, objectId: bizObjectId, workItemId: started.workItemId };
299
+ }
300
+ if (ctx.forceSubmitMissingObjectId) {
301
+ // Simulate a transport / protocol deviation: submitBizSheet
302
+ // returns no objectId. The tool must surface this rather than
303
+ // fabricate sequenceStatus:COMPLETED.
304
+ const noIdBizObject = (body.bizObject ?? {});
305
+ return { schemaCode: String(noIdBizObject.schemaCode ?? "") };
306
+ }
307
+ const bizObject = (body.bizObject ?? {});
308
+ const existing = typeof bizObject.id === "string" ? ctx.state.dataRows.get(bizObject.id) : undefined;
309
+ if (existing) {
310
+ // submitBizSheet -> saveBizObject: the submitted data persists
311
+ // (updateBizObject mergeObject patch) and the status moves COMPLETED.
312
+ mergeSubmittedData(existing, body);
313
+ existing.sequenceStatus = SUBMIT_STATUS_TRANSITIONS.inPlaceSubmit;
314
+ return { objectId: ctx.forceSubmitMismatchedObjectId ? "unexpected-submit-id" : bizObject.id, schemaCode: String(bizObject.schemaCode ?? ""), formCode: String(bizObject.sheetCode ?? bizObject.schemaCode ?? "") };
315
+ }
316
+ const id = `sub-${ctx.state.dataRows.size + 1}`;
317
+ ctx.state.dataRows.set(id, {
318
+ bizObjectId: id,
319
+ sequenceStatus: SUBMIT_STATUS_TRANSITIONS.inPlaceSubmit,
320
+ schemaCode: String(bizObject.schemaCode ?? ""),
321
+ formCode: String(bizObject.sheetCode ?? bizObject.schemaCode ?? ""),
322
+ ...(typeof bizObject.data === "object" && bizObject.data ? bizObject.data : {}),
323
+ });
324
+ return { objectId: id, schemaCode: String(bizObject.schemaCode ?? ""), formCode: String(bizObject.sheetCode ?? bizObject.schemaCode ?? "") };
325
+ }
326
+ default:
327
+ return NOT_HANDLED;
328
+ }
329
+ };
330
+ /**
331
+ * The submit face persists bizObject.data itself (submitBizSheet ->
332
+ * saveBizObject; SubmitDataChangeHandlerImpl.updateBizObject): the submitted
333
+ * fields merge patch-style onto the stored row. Engine-owned keys the tool
334
+ * strips are the contract constant (ticket 20): a raw caller echoing them
335
+ * must not pin the row's status.
336
+ */
337
+ function mergeSubmittedData(row, body) {
338
+ const bizObject = (body.bizObject ?? {});
339
+ if (!bizObject.data || typeof bizObject.data !== "object")
340
+ return;
341
+ for (const [key, value] of Object.entries(bizObject.data)) {
342
+ if (ENGINE_OWNED_KEYS.includes(key))
343
+ continue;
344
+ row[key] = value;
345
+ }
346
+ }
347
+ /**
348
+ * Leaf evaluation goes through the contract operator table (ticket 20) —
349
+ * the same table the DSL compiler encodes through, so every queryFilterType
350
+ * the compiler can emit is evaluated here (before, Gte/Lte/NotBetween fell
351
+ * through to an unconditional true).
352
+ */
353
+ function matchesCondition(condition, row) {
354
+ if (!condition || typeof condition !== "object")
355
+ return true;
356
+ const value = condition;
357
+ if (value.op === "and" || value.op === "or") {
358
+ const items = Array.isArray(value.items) ? value.items : [];
359
+ const results = items.map((item) => matchesCondition(item, row));
360
+ return value.op === "and" ? results.every(Boolean) : results.some(Boolean);
361
+ }
362
+ const operator = RUNTIME_QUERY_OPERATORS[String(value.queryFilterType ?? "")];
363
+ if (!operator)
364
+ return true;
365
+ return operator.matches(row[String(value.propertyCode ?? "")], String(value.propertyValue ?? ""));
366
+ }
367
+ //# sourceMappingURL=runtime-data.js.map
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Shared in-memory store for the fake CloudPivot 8.6. The per-lane route
3
+ * modules read and mutate this one store; `FakeCloudPivot` owns an instance
4
+ * and exposes it as `state` for tests. This mirrors the single shared
5
+ * governance spine that the `src/lanes/*` adapters stand on.
6
+ */
7
+ export const FAKE_RSA_PUBLIC_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4qB3KApdqvS/Civj0U5oX6nLgRLlLEvsUJpbNZAhT3Jq9+OGDv+th8TzUVSUzb68lKs4L6mDz1sZEJVTw6OKHjhf8pPvMuSPmEb3s6oEwh5kKRbG0qwE5xdaPgkIPSOOYYcPD6WLS9wQfhwh3kWfWttM6DKAOJB65RLfEdufYI52vJ5tbWqLumj/GDeOIub4e7PVD/6pGCJJNLjdg+vzYPZrSuSc+3/W23PqVIpnINjw+QNASi9U8KHDNvXapQa1vSEzaIrBhdDpdqnDiIrnYbVB6tQ3MotCAL9lse8VAEKoxaPwmgD6k8CdwjgZDNfnHD+xC+9qPUpmXpfLvRUHIwIDAQAB";
8
+ /** Sentinel returned by a lane route when the path is outside its lane. */
9
+ export const NOT_HANDLED = Symbol("omc-fake-not-handled");
10
+ export function businessRefusal(errcode, errmsg) {
11
+ return { fakeBusinessRefusal: true, errcode, errmsg };
12
+ }
13
+ export function isBusinessRefusal(value) {
14
+ return typeof value === "object" && value !== null && value.fakeBusinessRefusal === true;
15
+ }
16
+ export function malformedEnvelope(payload) {
17
+ return { fakeMalformedEnvelope: true, payload };
18
+ }
19
+ export function isMalformedEnvelope(value) {
20
+ return typeof value === "object" && value !== null && value.fakeMalformedEnvelope === true;
21
+ }
22
+ export function rawResponse(payload) {
23
+ return { fakeRawResponse: true, payload };
24
+ }
25
+ export function isRawResponse(value) {
26
+ return typeof value === "object" && value !== null && value.fakeRawResponse === true;
27
+ }
28
+ export function createFakeState(options = {}) {
29
+ const defaultAppCode = options.applicationCode ?? "test";
30
+ return {
31
+ fields: new Map(),
32
+ dictionaries: new Map(),
33
+ dictionaryClasses: new Map(),
34
+ sheets: new Map(),
35
+ rules: new Map(),
36
+ ruleHistory: [],
37
+ ruleSeq: 0,
38
+ dataRules: new Map(),
39
+ dataRuleSeq: 0,
40
+ services: new Map(),
41
+ ieUploads: new Map(),
42
+ ieUploadSeq: 0,
43
+ categories: [{ id: "fakecat_seed", name: "插件集成服务" }],
44
+ dbConnPools: new Map([
45
+ // Pre-existing environment pool (ticket 92+ fixtures reference it as
46
+ // their DATA_SQL/DATA_TABLE binding target).
47
+ ["bpm", { id: "fakepool_bpm", code: "bpm", name: "bpm", databaseType: "MySQL", datasourceType: "DATABASE", jdbcUrl: "jdbc:mysql://127.0.0.1:3306/bpm?useSSL=false", username: "bpm", password: "fake-ciphertext" }],
48
+ ]),
49
+ onlineJs: new Map(),
50
+ listViews: new Map(),
51
+ reportPages: new Map(),
52
+ biDashboards: new Map(),
53
+ workflows: new Map(),
54
+ workItems: new Map(),
55
+ dataRows: new Map(),
56
+ replayTokens: new Set(),
57
+ unconfiguredListViews: new Set(),
58
+ /** Ticket 79 live-fidelity: remaining transient 不存在列 publish refusals per schema (self-healing column-sync failure). */
59
+ transientPublishFailures: new Map(),
60
+ fieldDetailDrift: new Map(),
61
+ models: new Map(),
62
+ apps: new Map([[defaultAppCode, { id: "app-1", code: defaultAppCode, name: "Test App", groupId: "group-1" }]]),
63
+ appGroups: new Map([["group-1", { id: "group-1", code: "default", name: "默认分组" }]]),
64
+ callLog: [],
65
+ };
66
+ }
67
+ //# sourceMappingURL=state.js.map