@stalfh233/omc-cli 0.0.0-stage → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (181) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +304 -3
  3. package/coverage/m1-coverage-manifest-v1.json +1278 -0
  4. package/dist/approval-token.js +102 -0
  5. package/dist/args.js +25 -0
  6. package/dist/artifacts.js +119 -0
  7. package/dist/bench/call-face-eval.js +256 -0
  8. package/dist/bench/context-attribution.js +151 -0
  9. package/dist/bench/discovery-cost-eval.js +230 -0
  10. package/dist/bench/driver.js +81 -0
  11. package/dist/bench/evals.js +236 -0
  12. package/dist/bench/fake-http-server.js +65 -0
  13. package/dist/bench/instrument.js +87 -0
  14. package/dist/bench/intent-face-eval.js +343 -0
  15. package/dist/bench/run.js +166 -0
  16. package/dist/bench/scenario.js +343 -0
  17. package/dist/bench/types.js +76 -0
  18. package/dist/bi-wire.js +41 -0
  19. package/dist/bizservice-config.js +581 -0
  20. package/dist/call.js +153 -0
  21. package/dist/capability-absences.js +23 -0
  22. package/dist/capability-overview.js +492 -0
  23. package/dist/capability-shape.js +154 -0
  24. package/dist/cli-contract.js +70 -0
  25. package/dist/cli-output.js +73 -0
  26. package/dist/cli.js +1418 -0
  27. package/dist/code-rules.js +69 -0
  28. package/dist/command-transport.js +155 -0
  29. package/dist/config-store.js +195 -0
  30. package/dist/context.js +21 -0
  31. package/dist/contract-consistency.js +66 -0
  32. package/dist/contract-resources.js +62 -0
  33. package/dist/coverage-consistency.js +62 -0
  34. package/dist/coverage-registry.js +76 -0
  35. package/dist/coverage.js +130 -0
  36. package/dist/data-list-filter.js +114 -0
  37. package/dist/discovery.js +390 -0
  38. package/dist/endpoints.js +154 -0
  39. package/dist/environment-policy.js +26 -0
  40. package/dist/execution-metadata.js +1092 -0
  41. package/dist/fake/app.js +45 -0
  42. package/dist/fake/b2-registration.js +594 -0
  43. package/dist/fake/businessrule.js +242 -0
  44. package/dist/fake/datarule.js +72 -0
  45. package/dist/fake/dictionary.js +108 -0
  46. package/dist/fake/environment.js +30 -0
  47. package/dist/fake/field.js +167 -0
  48. package/dist/fake/form.js +101 -0
  49. package/dist/fake/index.js +121 -0
  50. package/dist/fake/list-view.js +289 -0
  51. package/dist/fake/model.js +136 -0
  52. package/dist/fake/online-js.js +18 -0
  53. package/dist/fake/report.js +253 -0
  54. package/dist/fake/routes.js +47 -0
  55. package/dist/fake/rule-lifecycle.js +37 -0
  56. package/dist/fake/runtime-data.js +367 -0
  57. package/dist/fake/state.js +67 -0
  58. package/dist/fake/workflow.js +364 -0
  59. package/dist/field-change.js +200 -0
  60. package/dist/field-families.js +896 -0
  61. package/dist/form-layout.js +111 -0
  62. package/dist/form-support.js +821 -0
  63. package/dist/goal-routes.js +468 -0
  64. package/dist/governed-execution.js +87 -0
  65. package/dist/human-summary.js +212 -0
  66. package/dist/identity.js +62 -0
  67. package/dist/intent/baseline.js +57 -0
  68. package/dist/intent/capabilities/bizservice.js +274 -0
  69. package/dist/intent/capabilities/businessrule.js +493 -0
  70. package/dist/intent/capabilities/datarule.js +187 -0
  71. package/dist/intent/capabilities/field.js +545 -0
  72. package/dist/intent/capabilities/form.js +136 -0
  73. package/dist/intent/capabilities/index.js +64 -0
  74. package/dist/intent/capabilities/listview.js +157 -0
  75. package/dist/intent/capabilities/model.js +100 -0
  76. package/dist/intent/capabilities/onlinejs.js +108 -0
  77. package/dist/intent/capabilities/report.js +355 -0
  78. package/dist/intent/capabilities/workflow.js +458 -0
  79. package/dist/intent/capability.js +6 -0
  80. package/dist/intent/cli.js +91 -0
  81. package/dist/intent/compare.js +56 -0
  82. package/dist/intent/compiler.js +79 -0
  83. package/dist/intent/dsl.js +129 -0
  84. package/dist/intent/plan-file.js +63 -0
  85. package/dist/intent/readback.js +65 -0
  86. package/dist/intent/schema.js +158 -0
  87. package/dist/intent/validation.js +30 -0
  88. package/dist/intent/yaml.js +315 -0
  89. package/dist/json-column.js +68 -0
  90. package/dist/lanes/app-contract.js +95 -0
  91. package/dist/lanes/app-coverage.js +16 -0
  92. package/dist/lanes/app.js +174 -0
  93. package/dist/lanes/apply-changes.js +231 -0
  94. package/dist/lanes/b2-registration-contract.js +292 -0
  95. package/dist/lanes/b2-registration-coverage.js +48 -0
  96. package/dist/lanes/b2-registration.js +1187 -0
  97. package/dist/lanes/businessrule-contract.js +232 -0
  98. package/dist/lanes/businessrule-coverage.js +16 -0
  99. package/dist/lanes/businessrule.js +221 -0
  100. package/dist/lanes/contract-support.js +65 -0
  101. package/dist/lanes/coverage-declaration.js +9 -0
  102. package/dist/lanes/datarule-contract.js +155 -0
  103. package/dist/lanes/datarule-coverage.js +19 -0
  104. package/dist/lanes/datarule-protocol.js +308 -0
  105. package/dist/lanes/datarule.js +818 -0
  106. package/dist/lanes/dictionary-contract.js +78 -0
  107. package/dist/lanes/dictionary-coverage.js +24 -0
  108. package/dist/lanes/dictionary.js +235 -0
  109. package/dist/lanes/environment-contract.js +70 -0
  110. package/dist/lanes/environment-coverage.js +14 -0
  111. package/dist/lanes/environment.js +163 -0
  112. package/dist/lanes/field-contract.js +223 -0
  113. package/dist/lanes/field-coverage.js +27 -0
  114. package/dist/lanes/field.js +374 -0
  115. package/dist/lanes/form-contract.js +97 -0
  116. package/dist/lanes/form-coverage.js +16 -0
  117. package/dist/lanes/form.js +185 -0
  118. package/dist/lanes/lane-ids.js +34 -0
  119. package/dist/lanes/list-view-contract.js +175 -0
  120. package/dist/lanes/list-view-coverage.js +20 -0
  121. package/dist/lanes/list-view-shapes.js +1207 -0
  122. package/dist/lanes/list-view.js +578 -0
  123. package/dist/lanes/meta-contract.js +85 -0
  124. package/dist/lanes/meta.js +255 -0
  125. package/dist/lanes/model-contract.js +168 -0
  126. package/dist/lanes/model-coverage.js +20 -0
  127. package/dist/lanes/model.js +986 -0
  128. package/dist/lanes/online-js-contract.js +99 -0
  129. package/dist/lanes/online-js-coverage.js +28 -0
  130. package/dist/lanes/online-js.js +127 -0
  131. package/dist/lanes/report-contract.js +144 -0
  132. package/dist/lanes/report-coverage.js +21 -0
  133. package/dist/lanes/report.js +476 -0
  134. package/dist/lanes/rule-graph.js +1846 -0
  135. package/dist/lanes/rule-lifecycle-contract.js +92 -0
  136. package/dist/lanes/rule-lifecycle-coverage.js +20 -0
  137. package/dist/lanes/rule-lifecycle.js +176 -0
  138. package/dist/lanes/runtime-data-contract.js +264 -0
  139. package/dist/lanes/runtime-data-coverage.js +25 -0
  140. package/dist/lanes/runtime-data.js +1054 -0
  141. package/dist/lanes/workflow-contract.js +223 -0
  142. package/dist/lanes/workflow-coverage.js +40 -0
  143. package/dist/lanes/workflow.js +1813 -0
  144. package/dist/online-js-layout.js +58 -0
  145. package/dist/online-js-source.js +276 -0
  146. package/dist/package-tool.js +51 -0
  147. package/dist/package.js +73 -0
  148. package/dist/plan.js +73 -0
  149. package/dist/read.js +144 -0
  150. package/dist/redact.js +28 -0
  151. package/dist/rule-support.js +587 -0
  152. package/dist/runtime-support.js +134 -0
  153. package/dist/server.js +92 -0
  154. package/dist/session-manager.js +30 -0
  155. package/dist/session.js +149 -0
  156. package/dist/skills.js +112 -0
  157. package/dist/support.js +98 -0
  158. package/dist/tool-types.js +127 -0
  159. package/dist/tools.js +59 -0
  160. package/dist/usage-log.js +197 -0
  161. package/dist/wire.js +287 -0
  162. package/dist/workflow-support.js +99 -0
  163. package/dist/write-lease.js +26 -0
  164. package/dist/write-lock.js +109 -0
  165. package/dist/write.js +176 -0
  166. package/dist/zip.js +156 -0
  167. package/docs/tool-surface-map.md +42 -0
  168. package/package.json +71 -6
  169. package/skills/omc-acceptance-criteria.md +47 -0
  170. package/skills/omc-business-configuration.md +257 -0
  171. package/skills/omc-capabilities.md +197 -0
  172. package/skills/omc-capability-scouting.md +55 -0
  173. package/skills/omc-config-draft-review.md +171 -0
  174. package/skills/omc-five-piece-flow.md +35 -0
  175. package/skills/omc-glossary.md +86 -0
  176. package/skills/omc-refusals.md +110 -0
  177. package/skills/omc-requirement-analysis.md +161 -0
  178. package/skills/omc-requirement-vocabulary.md +51 -0
  179. package/skills/omc-start-here.md +82 -0
  180. package/skills/omc-tool-selection.md +119 -0
  181. package/skills/omc-write-hazards.md +87 -0
@@ -0,0 +1,364 @@
1
+ import { businessRefusal, malformedEnvelope, NOT_HANDLED } from "./state.js";
2
+ const PENDING_STATES = new Set(["NOTSTARTED", "INPROGRESS"]);
3
+ const ACTIVITY_TYPES = new Set(["START", "END", "CONNECTION", "PARTICIPANT", "SUB_INSTANCE", "CIRCULATE", "SYSTEM_ACTIVITY", "AGENT_ACTIVITY"]);
4
+ const POINT = /^-?\d+(?:\.\d+)?,\s-?\d+(?:\.\d+)?$/;
5
+ /**
6
+ * Live 8.6.26 fidelity (2026-09-17 probe): workflow/validate and /publish
7
+ * answer HTTP 200 + errcode 0 and put the truth in `data` as
8
+ * ValidateMessage[]. The originate (first PARTICIPANT) node's `participant`
9
+ * must be the platform keyword `"{Originator}"`; a plain value ("admin")
10
+ * answers `401035 <activityName> ERROR` and the flow never goes live.
11
+ * `"{Creator}"` on a non-originate participant node is a `401062` WARN that
12
+ * does not block.
13
+ */
14
+ function validateWorkflowParticipants(activities) {
15
+ if (!Array.isArray(activities))
16
+ return [];
17
+ const rows = activities;
18
+ const participants = rows.filter((activity) => String(activity?.activityType ?? "") === "PARTICIPANT");
19
+ const messages = [];
20
+ const originate = participants[0];
21
+ if (originate && String(originate.participant ?? "") !== "{Originator}")
22
+ messages.push({ errCode: 401035, errContent: originate.activityName ?? originate.activityCode ?? "", errMsg: null, errorLevel: "ERROR" });
23
+ for (const activity of participants.slice(1)) {
24
+ if (String(activity.participant ?? "") === "{Creator}")
25
+ messages.push({ errCode: 401062, errContent: activity.activityName ?? activity.activityCode ?? "", errMsg: null, errorLevel: "WARN" });
26
+ }
27
+ return messages;
28
+ }
29
+ function invalidRenderShape(body) {
30
+ if (!Array.isArray(body.activities))
31
+ return "activities";
32
+ const codes = new Set();
33
+ for (const activity of body.activities) {
34
+ const record = activity;
35
+ const code = String(record?.activityCode ?? "").trim();
36
+ const type = String(record?.activityType ?? "").trim();
37
+ if (!code || codes.has(code) || !ACTIVITY_TYPES.has(type))
38
+ return "activities";
39
+ codes.add(code);
40
+ }
41
+ if (!Array.isArray(body.rules))
42
+ return "rules";
43
+ for (const rule of body.rules) {
44
+ const record = rule;
45
+ const pre = String(record?.preActivityCode ?? "");
46
+ const post = String(record?.postActivityCode ?? "");
47
+ const points = record?.points;
48
+ if (!codes.has(pre) || !codes.has(post) || !Array.isArray(points) || points.length === 0 || points.some((point) => typeof point !== "string" || !POINT.test(point)))
49
+ return "rules";
50
+ }
51
+ return null;
52
+ }
53
+ /**
54
+ * Spawn the two work items a workflow start produces: the originator fill item
55
+ * (already FINISHED — the start submit is the fill) and the pending approval
56
+ * item that `workitem.list` must surface for `data.submit`.
57
+ */
58
+ export function startWorkflowInstance(state, input) {
59
+ const sequence = state.workItems.size + 1;
60
+ const instanceId = `wf-${sequence}`;
61
+ const fillId = `wi-${sequence}`;
62
+ const approveId = `wi-${sequence + 1}`;
63
+ state.workItems.set(fillId, {
64
+ id: fillId,
65
+ instanceId,
66
+ activityCode: "fill",
67
+ activityName: "填写申请单",
68
+ participant: "tester",
69
+ state: "FINISHED",
70
+ workflowCode: input.workflowCode,
71
+ schemaCode: input.schemaCode,
72
+ bizObjectId: input.bizObjectId,
73
+ });
74
+ state.workItems.set(approveId, {
75
+ id: approveId,
76
+ instanceId,
77
+ activityCode: "approve",
78
+ activityName: "审批",
79
+ participant: "tester",
80
+ state: "INPROGRESS",
81
+ workflowCode: input.workflowCode,
82
+ schemaCode: input.schemaCode,
83
+ bizObjectId: input.bizObjectId,
84
+ });
85
+ return { workflowInstanceId: instanceId, workItemId: approveId };
86
+ }
87
+ /** Complete a pending work item (approval submit). Returns false if unknown. */
88
+ export function completeWorkItem(state, workItemId) {
89
+ const item = state.workItems.get(workItemId);
90
+ if (!item)
91
+ return false;
92
+ item.state = "FINISHED";
93
+ return true;
94
+ }
95
+ /**
96
+ * Portal draft-save shape (finishStart=FALSE): one instance in DRAFT with a
97
+ * single PENDING start work item. Submitting that item (startWorkflowDraft's
98
+ * workItemId) is what advances the instance — see startWorkflowInstance for
99
+ * the finishStart=TRUE twin.
100
+ */
101
+ export function startWorkflowDraft(state, input) {
102
+ const sequence = state.workItems.size + 1;
103
+ const instanceId = `wf-${sequence}`;
104
+ const startId = `wi-${sequence}`;
105
+ state.workItems.set(startId, {
106
+ id: startId,
107
+ instanceId,
108
+ activityCode: "fill",
109
+ activityName: "填写申请单",
110
+ participant: "tester",
111
+ state: "INPROGRESS",
112
+ workflowCode: input.workflowCode,
113
+ schemaCode: input.schemaCode,
114
+ bizObjectId: input.bizObjectId,
115
+ isStart: true,
116
+ });
117
+ return { workflowInstanceId: instanceId, workItemId: startId };
118
+ }
119
+ /** After the START work item is submitted, the next (approval) activity spawns a pending todo. */
120
+ export function spawnApprovalWorkItem(state, input) {
121
+ const approveId = `wi-${state.workItems.size + 1}`;
122
+ state.workItems.set(approveId, {
123
+ id: approveId,
124
+ instanceId: input.instanceId,
125
+ activityCode: "approve",
126
+ activityName: "审批",
127
+ participant: "tester",
128
+ state: "INPROGRESS",
129
+ workflowCode: input.workflowCode,
130
+ schemaCode: input.schemaCode,
131
+ bizObjectId: input.bizObjectId,
132
+ });
133
+ return approveId;
134
+ }
135
+ function toWorkItemVO(item) {
136
+ return {
137
+ id: item.id,
138
+ instanceId: item.instanceId,
139
+ activityCode: item.activityCode,
140
+ activityName: item.activityName,
141
+ state: item.state,
142
+ workflowCode: item.workflowCode,
143
+ schemaCode: item.schemaCode,
144
+ participant: item.participant,
145
+ participantName: item.participantName ?? item.participant,
146
+ originator: item.originator,
147
+ sequenceNo: item.sequenceNo,
148
+ };
149
+ }
150
+ /** W7-S29-greenfield lane: workflow template authoring + runtime work-item query. Twin of src/lanes/workflow.ts. */
151
+ export const workflowRoute = (ctx, input) => {
152
+ const q = input.query ?? {};
153
+ const body = (input.body ?? {});
154
+ switch (input.path) {
155
+ case "/api/api/workflow/create": {
156
+ const code = String(body.workflowCode ?? body.code ?? "");
157
+ const name = String(body.workflowName ?? body.name ?? code);
158
+ const schemaCode = String(body.schemaCode ?? "");
159
+ // Ticket 76 live fidelity: creating a workflow when the SCHEMA already
160
+ // has one answers an envelope with NO numeric errcode (live:
161
+ // "envelope for /api/api/workflow/create has no numeric errcode") — the
162
+ // tool must decode it as a duplicate-create, not a malformed failure.
163
+ if ([...ctx.state.workflows.values()].some((wf) => wf.schemaCode === schemaCode) && !ctx.state.workflows.has(code))
164
+ return malformedEnvelope({ workflowCode: code, workflowName: name, schemaCode, exist: true });
165
+ if (!ctx.state.workflows.has(code)) {
166
+ // Ticket 112 platform fidelity: the real create auto-generates a
167
+ // full draft (START → PARTICIPANT 填写申请单 → END with canvas rules;
168
+ // WorkflowTemplateController update @ApiImplicitParam example) — the
169
+ // fake seeds the same platform-shaped template so the merge path has
170
+ // real activity boxes and rule shapes to echo.
171
+ ctx.state.workflows.set(code, {
172
+ code,
173
+ name,
174
+ schemaCode,
175
+ template: {
176
+ workflowCode: code,
177
+ name,
178
+ workflowName: name,
179
+ schemaCode,
180
+ // Live create seeds templateType DRAFT ("SAVE" is not a
181
+ // WorkflowTemplateStatus member — S29 contract).
182
+ templateType: "DRAFT",
183
+ activities: [
184
+ { activityCode: "Activity1", activityType: "START", activityName: "开始", x: 327, y: 36, width: 158, height: 40 },
185
+ { activityCode: "Activity2", activityType: "PARTICIPANT", activityName: "填写申请单", participant: "{Originator}", x: 327, y: 116, width: 158, height: 40 },
186
+ { activityCode: "Activity3", activityType: "END", activityName: "结束", x: 327, y: 196, width: 158, height: 40 },
187
+ ],
188
+ rules: [
189
+ { preActivityCode: "Activity1", postActivityCode: "Activity2", points: ["406, 76", "406, 116"] },
190
+ { preActivityCode: "Activity2", postActivityCode: "Activity3", points: ["406, 156", "406, 196"] },
191
+ ],
192
+ },
193
+ published: null,
194
+ });
195
+ }
196
+ return { code };
197
+ }
198
+ case "/api/api/workflow/isworkflow": {
199
+ // WorkflowTemplateController.isWorkFlow(schemaCode): any header for the
200
+ // schema answers true.
201
+ const schemaCode = String(q.schemaCode ?? "");
202
+ return [...ctx.state.workflows.values()].some((wf) => wf.schemaCode === schemaCode);
203
+ }
204
+ case "/api/api/workflow/getWorkflow": {
205
+ // WorkflowTemplateController.getWorkflow(schemaCode): the published
206
+ // template for the schema (carries workflowCode), or null when the model
207
+ // has none. Used to auto-start a workflow-backed data.submit.
208
+ const schemaCode = String(q.schemaCode ?? "");
209
+ const wf = [...ctx.state.workflows.values()].find((candidate) => candidate.schemaCode === schemaCode);
210
+ if (!wf)
211
+ return null;
212
+ const template = (wf.published ?? wf.template);
213
+ return { ...template, workflowCode: wf.code };
214
+ }
215
+ case "/api/api/workflow/get_header": {
216
+ const wf = ctx.state.workflows.get(String(q.workflowCode ?? ""));
217
+ // Round-2 live fidelity (BUG-05): these endpoints answer 401001
218
+ // 流程不存在! for an ABSENT workflow rather than a null payload.
219
+ if (!wf)
220
+ return businessRefusal(401001, "流程不存在!");
221
+ return { code: wf.code, name: wf.name };
222
+ }
223
+ case "/api/api/workflow/get_draft": {
224
+ const wf = ctx.state.workflows.get(String(q.workflowCode ?? ""));
225
+ if (!wf)
226
+ return businessRefusal(401001, "流程不存在!");
227
+ return wf.template;
228
+ }
229
+ case "/api/api/workflow/get_published": {
230
+ const wf = ctx.state.workflows.get(String(q.workflowCode ?? ""));
231
+ // Live fidelity: like get_header/get_draft, an ABSENT workflow answers
232
+ // 401001 流程不存在! rather than a null payload; the tool degrades that
233
+ // to exists:false. Ticket 77: an UNPUBLISHED workflow still answers the
234
+ // DRAFT shape here — data presence is not publish evidence. The
235
+ // published face carries the templateType PUBLISH marker.
236
+ if (!wf)
237
+ return businessRefusal(401001, "流程不存在!");
238
+ return wf.published ?? wf.template ?? null;
239
+ }
240
+ case "/api/api/workflow/update": {
241
+ const code = String(body.workflowCode ?? q.workflowCode ?? "");
242
+ const wf = ctx.state.workflows.get(code);
243
+ if (!wf)
244
+ return null;
245
+ // Ticket 112 platform fidelity (WorkflowTemplateUtil.java:90-171): an
246
+ // illegal templateType ("BusinessProcess" is not a
247
+ // WorkflowTemplateStatus member — valueOf at :136) or an activities
248
+ // element without a type makes the converter swallow the exception
249
+ // into a PARTIAL model; the save chain then fails with errcode 50000.
250
+ if (body.templateType === "BusinessProcess")
251
+ return businessRefusal(50000, "服务异常");
252
+ if (Array.isArray(body.activities) &&
253
+ body.activities.some((activity) => {
254
+ const record = activity;
255
+ const type = record != null && typeof record === "object" ? (record.type ?? record.activityType) : undefined;
256
+ return type === undefined || type === null || String(type).trim() === "";
257
+ }))
258
+ return businessRefusal(50000, "服务异常");
259
+ if (body.activities !== undefined && invalidRenderShape(body))
260
+ return businessRefusal(50000, "服务异常");
261
+ // Platform fidelity (live 2026-09-23; bench12/13/15): an existing
262
+ // activityCode cannot be RENAMED or RETYPED — the stored name/type win.
263
+ // A caller that wants a different name/type must send it under a fresh
264
+ // code (the tool reconciles this before the wire); re-sending the same
265
+ // code with a changed name is a silent no-op on the platform, so the fake
266
+ // must reproduce it rather than store the body verbatim.
267
+ const stored = wf.template.activities;
268
+ const storedByCode = new Map((Array.isArray(stored) ? stored : []).map((activity) => [String(activity.activityCode ?? ""), activity]));
269
+ const pinned = Array.isArray(body.activities)
270
+ ? {
271
+ ...body,
272
+ activities: body.activities.map((activity) => {
273
+ const prior = storedByCode.get(String(activity.activityCode ?? ""));
274
+ return prior
275
+ ? { ...activity, activityName: prior.activityName, i18nMessage: prior.i18nMessage, activityType: prior.activityType }
276
+ : activity;
277
+ }),
278
+ }
279
+ : body;
280
+ wf.template = { ...pinned };
281
+ return { code };
282
+ }
283
+ case "/api/api/workflow/validate":
284
+ case "/api/api/workflow/publish": {
285
+ const code = String(body.workflowCode ?? q.workflowCode ?? "");
286
+ const wf = ctx.state.workflows.get(code);
287
+ if (!wf)
288
+ return null;
289
+ if (body.activities !== undefined && invalidRenderShape(body))
290
+ return businessRefusal(50000, "服务异常");
291
+ // Live 8.6.26 fidelity (2026-09-17): validate/publish REFUSE with HTTP
292
+ // 200 + errcode 0 and the truth in `data` as ValidateMessage[]
293
+ // ({errCode, errContent, errorLevel}). The originate (first PARTICIPANT)
294
+ // node's participant must be the platform keyword "{Originator}" — a
295
+ // plain user/role value answers 401035 ERROR while errcode stays 0, and
296
+ // the flow never goes live. `{Creator}` on a non-originate node is a WARN
297
+ // (401062) that does not block.
298
+ const activities = Array.isArray(body.activities) ? body.activities : wf.template.activities;
299
+ const validation = validateWorkflowParticipants(activities);
300
+ if (input.path === "/api/api/workflow/validate")
301
+ return validation;
302
+ if (validation.some((message) => message.errorLevel === "ERROR"))
303
+ return validation;
304
+ // Ticket 77: the published face carries the PUBLISH marker (live readback
305
+ // evidence: publish state is identified by templateType:PUBLISH /
306
+ // version fields, never by data presence).
307
+ wf.published = { ...body, templateType: "PUBLISH", workflowVersion: (wf.published ? Number(wf.published.workflowVersion ?? 1) : 0) + 1 };
308
+ return { code, workflowVersion: wf.published.workflowVersion };
309
+ }
310
+ case "/api/api/workflow/delete":
311
+ ctx.state.workflows.delete(String(body.workflowCode ?? q.workflowCode ?? q.code ?? ""));
312
+ return null;
313
+ case "/api/api/runtime/workflow/search_workitems": {
314
+ // The real endpoint returns the caller's pending todos (biz_workitem only
315
+ // holds pending rows); finished items move to the finished store. The
316
+ // server's filterWorkflowInstanceId is a NOT-equal exclusion, so it is
317
+ // deliberately not honored here — exact instance matching is client-side.
318
+ let rows = [...ctx.state.workItems.values()].filter((item) => PENDING_STATES.has(item.state));
319
+ if (q.workflowCode)
320
+ rows = rows.filter((item) => item.workflowCode === q.workflowCode);
321
+ if (q.schemaCode)
322
+ rows = rows.filter((item) => item.schemaCode === q.schemaCode);
323
+ const page = Number(q.page ?? 0);
324
+ const size = Number(q.size ?? 20);
325
+ const start = page * size;
326
+ return {
327
+ totalElements: rows.length,
328
+ totalPages: size > 0 ? Math.ceil(rows.length / size) : 0,
329
+ content: rows.slice(start, start + size).map(toWorkItemVO),
330
+ };
331
+ }
332
+ case "/api/api/runtime/workflow/getBizObjectByWfInstanceId": {
333
+ const instanceId = String(q.workflowInstanceId ?? "");
334
+ const item = [...ctx.state.workItems.values()].find((candidate) => candidate.instanceId === instanceId);
335
+ if (!item)
336
+ return null;
337
+ return { id: item.bizObjectId, schemaCode: item.schemaCode };
338
+ }
339
+ case "/api/api/organization/role/list":
340
+ // Real shape: groups with `children` roles (see real-env observation).
341
+ return [
342
+ { id: "grp-1", name: "默认", code: "SYS_1000000", children: [{ id: "role-lead", name: "部门负责人", code: "SYS260630023816197", roleType: "SYS", corpId: "corp-1" }] },
343
+ { id: "role-finance", name: "财务负责人", code: "SYS260515031207258", roleType: "SYS", corpId: "corp-1" },
344
+ ];
345
+ case "/api/api/organization/department/tree":
346
+ return { departmentList: [{ id: "dept-1", name: "总所", code: "1", corpId: "corp-1", parentId: null }, { id: "dept-2", name: "业务部", code: "1_1", corpId: "corp-1", parentId: "dept-1" }] };
347
+ case "/api/api/organization/user/search": {
348
+ // UserController.search: @RequestBody {wd,page,size} → PageVO<UserVO>.
349
+ const wd = String(body.wd ?? "");
350
+ if (!wd)
351
+ return businessRefusal(10005, "关键字不能为空");
352
+ const users = [
353
+ { id: "user-1", username: "18900000001", name: "付文彬", departmentId: "dept-1", userId: "18900000001", status: "ENABLE", deleted: false, corpId: "corp-1" },
354
+ { id: "user-2", username: "18900000002", name: "李四", departmentId: "dept-2", userId: "18900000002", status: "ENABLE", deleted: false, corpId: "corp-1" },
355
+ ].filter((user) => user.name.includes(wd) || user.username.includes(wd));
356
+ const page = Number(body.page ?? 0);
357
+ const size = Number(body.size ?? 100);
358
+ return { totalElements: users.length, totalPages: size > 0 ? Math.ceil(users.length / size) : 0, content: users.slice(page * size, page * size + size) };
359
+ }
360
+ default:
361
+ return NOT_HANDLED;
362
+ }
363
+ };
364
+ //# sourceMappingURL=workflow.js.map
@@ -0,0 +1,200 @@
1
+ import { FIELD_PROPERTY_TYPES } from "./field-families.js";
2
+ import { assertFormConditions, assertNumericalScale, assertFieldOptionsAllowed, buildChildTableSubSchema, buildFieldOptions, ensureRelationDisplayFieldOptions, normalizeFieldPropertyType, relationDisplayFieldCode, } from "./field-families.js";
3
+ import { nameI18nJson } from "./field-families.js";
4
+ import { decodeJsonObjectColumn } from "./json-column.js";
5
+ import { ToolRefusalError } from "./tool-types.js";
6
+ /**
7
+ * Fail-closed relation-target gate (round17 dogfood R3). A relation field whose
8
+ * options lack `schemaCode` is ACCEPTED by the platform at create, but then
9
+ * `field.publish` fails with an opaque `errcode 300010 数据模型编码不能为空!` and
10
+ * the column is unusable. Backfill `options.schemaCode` from the top-level
11
+ * `relativeCode` when present (they name the same target); refuse when neither
12
+ * carries a target, naming the fix instead of letting the model be poisoned.
13
+ */
14
+ function assertRelationTargetPresent(propertyType, options, relativeCode, code) {
15
+ if (propertyType !== FIELD_PROPERTY_TYPES.WORK_SHEET && propertyType !== FIELD_PROPERTY_TYPES.MULT_WORK_SHEET)
16
+ return;
17
+ const optionCode = typeof options.schemaCode === "string" ? options.schemaCode.trim() : "";
18
+ if (optionCode)
19
+ return;
20
+ // Accept the target under any of the synonyms callers naturally reach for
21
+ // (top-level relativeCode; options.relativeCode) — they all name the target
22
+ // model. The platform's authoritative relation key is options.schemaCode.
23
+ const candidates = [relativeCode, options.relativeCode, options.relativeSchemaCode]
24
+ .map((value) => (typeof value === "string" ? value.trim() : ""))
25
+ .filter((value) => value.length > 0);
26
+ const target = candidates[0];
27
+ if (target) {
28
+ options.schemaCode = target;
29
+ if (typeof options.queryCode !== "string" || options.queryCode.trim() === "")
30
+ options.queryCode = target;
31
+ return;
32
+ }
33
+ throw new ToolRefusalError({
34
+ status: "refused",
35
+ blocker: "field-relation-target-missing",
36
+ code,
37
+ message: "关联字段缺少目标模型:options.schemaCode 与顶层 relativeCode 都为空。平台建字段时接受它,但 field.publish 会以 300010『数据模型编码不能为空』拒绝,之后该列不可用。请传 relativeCode:'<目标模型code>'(工具会自动补全 options.schemaCode/queryCode),或在 options 里直接给 schemaCode",
38
+ });
39
+ }
40
+ /** Normalize one field change and run every reproduced write gate exactly once. */
41
+ export function prepareFieldChange(input) {
42
+ const propertyType = normalizeFieldPropertyType({ propertyType: input.propertyType });
43
+ const isChildTable = propertyType === FIELD_PROPERTY_TYPES.CHILD_TABLE;
44
+ if (isChildTable) {
45
+ const subSchema = buildChildTableSubSchema({ code: input.code, subFields: input.subFields });
46
+ return { propertyType, isChildTable, options: {}, subSchema };
47
+ }
48
+ const raw = buildFieldOptions({
49
+ propertyType: input.propertyType,
50
+ ...(input.choices ? { choices: input.choices } : {}),
51
+ ...(input.options ? { options: input.options } : {}),
52
+ });
53
+ assertRelationTargetPresent(propertyType, raw, input.relativeCode, input.code);
54
+ // Gates run here so every entry point (direct, orchestrator, intent) shares them.
55
+ assertFieldOptionsAllowed(propertyType, raw, input.schemaCode);
56
+ assertNumericalScale(propertyType, raw);
57
+ assertFormConditions(raw);
58
+ const options = ensureRelationDisplayFieldOptions(propertyType, raw);
59
+ const displayCode = relationDisplayFieldCode(propertyType, options);
60
+ return { propertyType, isChildTable, options, ...(displayCode ? { displayCode } : {}) };
61
+ }
62
+ /** The single platform wire body for a field create. */
63
+ export function fieldChangeWireBody(input, prepared) {
64
+ return {
65
+ name: input.name,
66
+ name_i18n: nameI18nJson(input.name),
67
+ code: input.code,
68
+ schemaCode: input.schemaCode,
69
+ propertyType: prepared.propertyType,
70
+ propertyEmpty: input.propertyEmpty ?? false,
71
+ ...(prepared.isChildTable
72
+ ? { defaultValue: null, subSchema: prepared.subSchema }
73
+ : { defaultValue: "", ...(prepared.displayCode ? { relativePropertyCode: prepared.displayCode } : {}), options: JSON.stringify(prepared.options) }),
74
+ ...(input.relativeCode ? { relativeCode: input.relativeCode } : {}),
75
+ };
76
+ }
77
+ export function prepareFieldUpdate(input) {
78
+ const recordType = Number(input.base.type ?? input.base.propertyType);
79
+ const isChildTable = recordType === FIELD_PROPERTY_TYPES.CHILD_TABLE;
80
+ if (isChildTable && (input.options !== undefined || input.choices !== undefined || input.subFields !== undefined))
81
+ throw new ToolRefusalError({
82
+ status: "refused",
83
+ blocker: "child-table-update-unsupported",
84
+ schemaCode: input.schemaCode,
85
+ propertyCode: input.code,
86
+ message: `子表字段 ${input.schemaCode}.${input.code} 不接受 options/choices/subFields。子表列就是 schemaCode=${input.code} 上的普通字段:增列用 field.create --args '{"schemaCode":"${input.code}","code":"<子列>",...}',改列/删列用 field.update/field.delete(同 schemaCode),改完对父模型 field.publish 即发布`,
87
+ });
88
+ const currentOptions = decodeJsonObjectColumn(input.base.options, { invalid: "throw", array: "accept", object: "accept" }) ?? {};
89
+ const nextOptions = isChildTable
90
+ ? currentOptions
91
+ : ensureRelationDisplayFieldOptions(recordType, {
92
+ ...currentOptions,
93
+ ...buildFieldOptions({
94
+ propertyType: recordType,
95
+ ...(input.choices ? { choices: input.choices } : {}),
96
+ ...(input.options ? { options: input.options } : {}),
97
+ }),
98
+ });
99
+ if (!isChildTable && !input.options && !input.choices)
100
+ Object.assign(nextOptions, currentOptions);
101
+ if (!isChildTable) {
102
+ // An update that touches a relation's options must keep a resolvable target:
103
+ // backfill schemaCode from relativeCode (top-level or already stored) so
104
+ // field.publish cannot later 300010 on this field.
105
+ assertRelationTargetPresent(recordType, nextOptions, input.relativeCode ?? (typeof input.base.relativeCode === "string" ? input.base.relativeCode : undefined), input.code);
106
+ assertFieldOptionsAllowed(recordType, nextOptions, input.schemaCode);
107
+ if (input.options !== undefined || input.choices !== undefined)
108
+ assertNumericalScale(recordType, nextOptions);
109
+ if (input.options !== undefined)
110
+ assertFormConditions(input.options);
111
+ }
112
+ // Ticket 61: an explicit displayField wins; otherwise preserve the stored
113
+ // top-level relativePropertyCode (the runtime reads it, not options).
114
+ const displayCode = relationDisplayFieldCode(recordType, nextOptions)
115
+ ?? (typeof input.base.relativePropertyCode === "string" && input.base.relativePropertyCode.trim() ? input.base.relativePropertyCode.trim() : undefined);
116
+ const name = input.name ?? (typeof input.base.name === "string" ? input.base.name : undefined);
117
+ const nameI18n = input.name_i18n !== undefined
118
+ ? (typeof input.name_i18n === "string" ? input.name_i18n : JSON.stringify(input.name_i18n))
119
+ : input.name !== undefined
120
+ ? nameI18nJson(input.name)
121
+ : (typeof input.base.name_i18n === "string" ? input.base.name_i18n : undefined);
122
+ return { propertyType: recordType, isChildTable, options: nextOptions, ...(displayCode !== undefined ? { displayCode } : {}), ...(name !== undefined ? { name } : {}), ...(nameI18n !== undefined ? { nameI18n } : {}) };
123
+ }
124
+ /** The single field-update wire body: the stored row spread with changed keys. */
125
+ export function fieldUpdateWireBody(input, prepared) {
126
+ return {
127
+ ...input.base,
128
+ schemaCode: input.schemaCode,
129
+ code: input.code,
130
+ ...(prepared.isChildTable ? {} : { options: JSON.stringify(prepared.options) }),
131
+ ...(prepared.displayCode !== undefined ? { relativePropertyCode: prepared.displayCode } : {}),
132
+ ...(prepared.name !== undefined ? { name: prepared.name } : {}),
133
+ ...(prepared.nameI18n !== undefined ? { name_i18n: prepared.nameI18n } : {}),
134
+ ...(input.relativeCode ? { relativeCode: input.relativeCode } : {}),
135
+ ...(input.propertyEmpty !== undefined ? { propertyEmpty: input.propertyEmpty } : {}),
136
+ };
137
+ }
138
+ function canonical(value) {
139
+ if (Array.isArray(value))
140
+ return value.map(canonical);
141
+ if (value && typeof value === "object") {
142
+ const record = value;
143
+ return Object.fromEntries(Object.keys(record).sort().map((key) => [key, canonical(record[key])]));
144
+ }
145
+ return value;
146
+ }
147
+ function parseStoredOptions(value) {
148
+ if (typeof value === "string") {
149
+ try {
150
+ return JSON.parse(value);
151
+ }
152
+ catch {
153
+ return value;
154
+ }
155
+ }
156
+ return value;
157
+ }
158
+ /** Canonical, storage-independent view used to decide "same field, no change". */
159
+ export function fieldSemanticDescriptor(row) {
160
+ const type = Number(row.type ?? row.propertyType);
161
+ const subSchema = (row.subSchema ?? null);
162
+ const subFields = subSchema?.properties
163
+ ? Object.values(subSchema.properties).map((property) => ({
164
+ code: String(property.code ?? ""),
165
+ type: Number.isNaN(Number(property.type ?? property.propertyType)) ? null : Number(property.type ?? property.propertyType),
166
+ options: canonical(parseStoredOptions(property.options)),
167
+ }))
168
+ : [];
169
+ return {
170
+ code: String(row.code ?? row.propertyCode ?? ""),
171
+ name: (row.name ?? null),
172
+ type: Number.isNaN(type) ? null : type,
173
+ options: canonical(parseStoredOptions(row.options)),
174
+ subFields: [...subFields].sort((left, right) => left.code.localeCompare(right.code)),
175
+ };
176
+ }
177
+ /** True when a stored field already matches the requested change (name/type/options/subFields). */
178
+ export function sameFieldChange(stored, input) {
179
+ const descriptor = fieldSemanticDescriptor(stored);
180
+ if (descriptor.code !== input.code)
181
+ return false;
182
+ if (descriptor.name !== input.name)
183
+ return false;
184
+ const prepared = prepareFieldChange(input);
185
+ if (descriptor.type !== prepared.propertyType)
186
+ return false;
187
+ if (prepared.isChildTable) {
188
+ const wanted = prepared.subSchema?.properties;
189
+ const wantedFields = wanted
190
+ ? Object.values(wanted).map((property) => ({
191
+ code: String(property.code ?? ""),
192
+ type: Number(property.type ?? property.propertyType),
193
+ options: canonical(parseStoredOptions(property.options)),
194
+ })).sort((left, right) => left.code.localeCompare(right.code))
195
+ : [];
196
+ return JSON.stringify(descriptor.subFields) === JSON.stringify(wantedFields);
197
+ }
198
+ return JSON.stringify(descriptor.options) === JSON.stringify(canonical(prepared.options));
199
+ }
200
+ //# sourceMappingURL=field-change.js.map