@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,242 @@
1
+ import { RULE_STATUS } from "../lanes/businessrule-contract.js";
2
+ import { DYNAMIC_ACTION_SOURCE_KEY, DYNAMIC_CONDITION_SOURCE_KEY } from "../lanes/rule-graph.js";
3
+ import { NOT_HANDLED } from "./state.js";
4
+ /**
5
+ * B1-businessrule lane: rule listing/read + save (upsert). Twin of
6
+ * src/lanes/businessrule.ts. Both list faces share one serializer: the app
7
+ * face is driven by the rule-lifecycle lane, the runtime face by this lane.
8
+ */
9
+ /**
10
+ * Ticket 97: scan a saved rule node graph for methodMapping bindings and flag
11
+ * the bound biz-service methods referenced (the platform derives the same
12
+ * flag from its mapping tables; a referenced method refuses method/delete
13
+ * with 500108).
14
+ */
15
+ function markReferencedMethods(ctx, nodeJson) {
16
+ if (typeof nodeJson !== "string")
17
+ return;
18
+ let nodes;
19
+ try {
20
+ nodes = JSON.parse(nodeJson);
21
+ }
22
+ catch {
23
+ return;
24
+ }
25
+ if (!Array.isArray(nodes))
26
+ return;
27
+ for (const entry of nodes) {
28
+ if (!entry || typeof entry !== "object")
29
+ continue;
30
+ const mapping = entry.methodMapping;
31
+ if (!mapping || typeof mapping !== "object" || Array.isArray(mapping))
32
+ continue;
33
+ const m = mapping;
34
+ const serviceCode = typeof m.serviceCode === "string" ? m.serviceCode : "";
35
+ const serviceMethodCode = typeof m.serviceMethodCode === "string" ? m.serviceMethodCode : "";
36
+ if (!serviceCode || !serviceMethodCode)
37
+ continue;
38
+ const method = ctx.state.services.get(serviceCode)?.methods.find((candidate) => candidate.code === serviceMethodCode);
39
+ if (method)
40
+ method.referenced = true;
41
+ }
42
+ }
43
+ export const businessRuleRoute = (ctx, input) => {
44
+ const q = input.query ?? {};
45
+ const body = (input.body ?? {});
46
+ switch (input.path) {
47
+ case "/api/api/app/businessrule/list_by_schemacode":
48
+ case "/api/api/runtime/business_rule/list_by_schemacode":
49
+ return [...ctx.state.rules.values()].filter((rule) => rule.schemaCode === q.schemaCode).map((rule) => ({
50
+ id: rule.id,
51
+ schemaCode: rule.schemaCode,
52
+ code: rule.code,
53
+ ruleCode: rule.code,
54
+ name: rule.name,
55
+ enabled: rule.enabled,
56
+ status: rule.status ?? (rule.enabled ? RULE_STATUS.enabled : RULE_STATUS.disabled),
57
+ priority: rule.priority ?? 0,
58
+ createdTime: rule.createdTime ?? 1730000000000,
59
+ creator: rule.creator ?? "fake-user",
60
+ // latestPublished must read the COMPUTED status, not the raw optional
61
+ // field: a rule enabled by businessrule/publish has no explicit
62
+ // publishStatus and previously reported latestPublished:false here.
63
+ publishStatus: rule.publishStatus ?? (rule.enabled ? "PUBLISHED" : "DRAFT"),
64
+ developmentMode: rule.developmentMode ?? "CODING",
65
+ ruleVersion: rule.ruleVersion ?? 1,
66
+ latestPublished: (rule.publishStatus ?? (rule.enabled ? "PUBLISHED" : "DRAFT")) === "PUBLISHED",
67
+ }));
68
+ case "/api/api/app/businessrule/get": {
69
+ // Ticket 109 fidelity: superseded rows stay readable by their OLD id
70
+ // (historical versions) — the current rows map only holds the newest.
71
+ const rule = [...ctx.state.rules.values()].find((row) => row.id === q.id) ?? ctx.state.ruleHistory.find((row) => row.id === q.id);
72
+ if (!rule)
73
+ return null;
74
+ return {
75
+ id: rule.id,
76
+ code: rule.code,
77
+ name: rule.name,
78
+ schemaCode: rule.schemaCode,
79
+ bizRuleType: rule.bizRuleType ?? 0,
80
+ enabled: rule.enabled,
81
+ status: rule.status ?? "DISABLED",
82
+ defaultRule: false,
83
+ developmentMode: 0,
84
+ ruleVersion: rule.ruleVersion ?? 1,
85
+ node: rule.node ?? JSON.stringify([{ nodeCode: "Start", nodeType: "START" }]),
86
+ route: rule.route ?? JSON.stringify([]),
87
+ schedulerSetting: rule.schedulerSetting ?? null,
88
+ };
89
+ }
90
+ case "/api/api/app/businessrule/create":
91
+ case "/api/api/app/businessrule/update": {
92
+ const code = String(body.code ?? q.code ?? "");
93
+ const schemaCode = String(body.schemaCode ?? q.schemaCode ?? "");
94
+ const existing = [...ctx.state.rules.values()].find((rule) => rule.code === code && rule.schemaCode === schemaCode);
95
+ // Ticket 109 platform fidelity: every save (create OR update of the
96
+ // same code) mints a NEW record id and bumps ruleVersion; the
97
+ // superseded row moves to ruleHistory where get?id= still reads it —
98
+ // reading a stale id shows the OLD graph (live: 6509ed… → 8a8313… →
99
+ // 8f850e…, ruleVersion 1→4→5).
100
+ if (existing)
101
+ ctx.state.ruleHistory.push(existing);
102
+ ctx.state.ruleSeq = (ctx.state.ruleSeq ?? 0) + 1;
103
+ const id = `rule-${ctx.state.ruleSeq}`;
104
+ ctx.state.rules.set(`${schemaCode}#${code}`, {
105
+ ...existing,
106
+ id,
107
+ schemaCode,
108
+ code,
109
+ name: String(body.name ?? code),
110
+ enabled: body.enabled === true,
111
+ bizRuleType: typeof body.bizRuleType === "number" ? body.bizRuleType : existing?.bizRuleType,
112
+ node: typeof body.node === "string" ? body.node : existing?.node,
113
+ route: typeof body.route === "string" ? body.route : existing?.route,
114
+ schedulerSetting: typeof body.schedulerSetting === "string" ? body.schedulerSetting : body.schedulerSetting === null ? null : existing?.schedulerSetting,
115
+ priority: typeof body.priority === "number" ? body.priority : existing?.priority,
116
+ createdTime: typeof body.createdTime === "number" ? body.createdTime : existing?.createdTime,
117
+ creator: typeof body.creator === "string" ? body.creator : existing?.creator,
118
+ publishStatus: typeof body.publishStatus === "string" ? body.publishStatus : existing?.publishStatus,
119
+ developmentMode: typeof body.developmentMode === "string" ? body.developmentMode : existing?.developmentMode,
120
+ status: typeof body.status === "string" ? body.status : RULE_STATUS.disabled,
121
+ ruleVersion: (existing?.ruleVersion ?? 0) + 1,
122
+ });
123
+ // Ticket 97 fidelity: a saved methodMapping (serviceCode+serviceMethodCode
124
+ // inside the node graph) marks the bound method referenced — the
125
+ // platform derives the same flag from the mapping tables, and a
126
+ // referenced method refuses method/delete with 500108.
127
+ markReferencedMethods(ctx, typeof body.node === "string" ? body.node : existing?.node);
128
+ return null;
129
+ }
130
+ default:
131
+ return NOT_HANDLED;
132
+ }
133
+ };
134
+ export function applyBusinessRules(state, event, source) {
135
+ for (const rule of state.rules.values()) {
136
+ if (!rule.enabled || rule.schemaCode !== String(source.schemaCode ?? "") || rule.code !== event)
137
+ continue;
138
+ for (const node of parseJsonArray(rule.node)) {
139
+ if (node.nodeType !== "UPDATE" || typeof node.targetObjectCode !== "string")
140
+ continue;
141
+ const conditions = readConditionGroups(node.filterCondition);
142
+ for (const target of state.dataRows.values()) {
143
+ if (target.schemaCode !== node.targetObjectCode || !conditions.every((group) => group.some((andGroup) => andGroup.every((leaf) => matchesRuleCondition(leaf, target, source)))))
144
+ continue;
145
+ applyActions(target, node.dataActions, source);
146
+ }
147
+ }
148
+ }
149
+ }
150
+ function parseJsonArray(value) {
151
+ if (!value)
152
+ return [];
153
+ try {
154
+ const parsed = JSON.parse(value);
155
+ return Array.isArray(parsed) ? parsed.filter(isRecord) : [];
156
+ }
157
+ catch {
158
+ return [];
159
+ }
160
+ }
161
+ function readConditionGroups(filterCondition) {
162
+ if (!isRecord(filterCondition) || !Array.isArray(filterCondition.mainConditions))
163
+ return [];
164
+ return filterCondition.mainConditions.map((group) => {
165
+ if (!Array.isArray(group))
166
+ return [];
167
+ if (group.every(isRecord))
168
+ return [group];
169
+ return group.map((andGroup) => (Array.isArray(andGroup) ? andGroup.filter(isRecord) : []));
170
+ });
171
+ }
172
+ function matchesRuleCondition(condition, target, source) {
173
+ const targetField = condition.targetSchemaDataItem;
174
+ if (typeof targetField !== "string")
175
+ return false;
176
+ if (condition.ruleValueType === "DYNAMIC") {
177
+ // Shared wire key (rule-graph.ts): the DYNAMIC filter source lives in
178
+ // currentSchemaDataItem and value must not carry it (ticket 09).
179
+ const sourceField = condition[DYNAMIC_CONDITION_SOURCE_KEY];
180
+ if (typeof sourceField !== "string" || !sourceField || condition.value !== undefined)
181
+ return false;
182
+ return compareRuleValues(readRuleField(target, targetField), source[sourceField], String(condition.ruleConditionType ?? "EQ"));
183
+ }
184
+ if (condition.ruleValueType === "FIXED")
185
+ return compareRuleValues(readRuleField(target, targetField), condition.value, String(condition.ruleConditionType ?? "EQ"));
186
+ return false;
187
+ }
188
+ function readRuleField(target, field) {
189
+ const value = field === "id" ? target.id ?? target.bizObjectId : target[field];
190
+ if (value && typeof value === "object" && !Array.isArray(value) && typeof value.id === "string")
191
+ return value.id;
192
+ return value;
193
+ }
194
+ function compareRuleValues(actual, expected, operator) {
195
+ switch (operator) {
196
+ case "EQ": return fixedValueEqual(actual, expected);
197
+ case "NEQ": return !fixedValueEqual(actual, expected);
198
+ case "GT": return Number(actual) > Number(expected);
199
+ case "LT": return Number(actual) < Number(expected);
200
+ case "GTEQ": return Number(actual) >= Number(expected);
201
+ case "LTEQ": return Number(actual) <= Number(expected);
202
+ case "CT": return String(actual ?? "").includes(String(expected ?? ""));
203
+ case "NCT": return !String(actual ?? "").includes(String(expected ?? ""));
204
+ case "EP": return actual === null || actual === undefined || actual === "";
205
+ case "NEP": return actual !== null && actual !== undefined && actual !== "";
206
+ default: return false;
207
+ }
208
+ }
209
+ function applyActions(target, actions, source) {
210
+ if (!Array.isArray(actions))
211
+ return;
212
+ for (const action of actions) {
213
+ if (!isRecord(action) || action.ruleActionType !== "EQUALS" || typeof action.targetDataItem !== "string")
214
+ continue;
215
+ if (action.ruleValueType === "DYNAMIC") {
216
+ // Shared wire key (rule-graph.ts): the DYNAMIC action source lives in
217
+ // currentDataItemValue with value pinned to the empty string.
218
+ const sourceField = action[DYNAMIC_ACTION_SOURCE_KEY];
219
+ if (typeof sourceField === "string" && sourceField && action.value === "")
220
+ target[action.targetDataItem] = source[sourceField];
221
+ }
222
+ else if (action.ruleValueType === "FIXED") {
223
+ target[action.targetDataItem] = action.value;
224
+ }
225
+ }
226
+ }
227
+ function fixedValueEqual(actual, expected) {
228
+ const actualId = relationId(actual) ?? actual;
229
+ const expectedId = relationId(expected) ?? expected;
230
+ if (Array.isArray(actualId) || Array.isArray(expectedId))
231
+ return JSON.stringify(actualId) === JSON.stringify(expectedId);
232
+ return actualId === expectedId || String(actualId ?? "") === String(expectedId ?? "");
233
+ }
234
+ function relationId(value) {
235
+ if (!isRecord(value) || typeof value.id !== "string")
236
+ return undefined;
237
+ return value.id;
238
+ }
239
+ function isRecord(value) {
240
+ return typeof value === "object" && value !== null && !Array.isArray(value);
241
+ }
242
+ //# sourceMappingURL=businessrule.js.map
@@ -0,0 +1,72 @@
1
+ import { businessRefusal, NOT_HANDLED } from "./state.js";
2
+ /** G1-datarule lane: 8.6.26 field-level data rules. Twin of src/lanes/datarule.ts. */
3
+ function normalizeRule(id, body) {
4
+ const propertyCodes = Array.isArray(body.propertyCodes) ? body.propertyCodes.map((code) => String(code)) : [];
5
+ const propertyCode = body.propertyCode === null || body.propertyCode === undefined ? "" : String(body.propertyCode);
6
+ return {
7
+ id,
8
+ schemaCode: String(body.schemaCode),
9
+ propertyCode: propertyCode || (propertyCodes.length === 1 ? propertyCodes[0] : ""),
10
+ ...(propertyCodes.length > 0 ? { propertyCodes } : {}),
11
+ dataRuleType: Number(body.dataRuleType),
12
+ options: typeof body.options === "string" ? body.options : JSON.stringify(body.options ?? {}),
13
+ ...(body.checkType !== undefined ? { checkType: Number(body.checkType) } : {}),
14
+ ...(body.childRule !== undefined ? { childRule: Boolean(body.childRule) } : {}),
15
+ ...(body.name !== undefined ? { name: String(body.name) } : {}),
16
+ // Platform fidelity: the live engine stores a defaulted `sortKey: 0` on every
17
+ // row even when the caller omits it (bench56). The fake used to omit it, which
18
+ // hid a readback false-positive on an omitted sortKey.
19
+ sortKey: Number(body.sortKey ?? 0),
20
+ };
21
+ }
22
+ export const dataRuleRoute = (ctx, input) => {
23
+ const q = input.query ?? {};
24
+ const body = (input.body ?? {});
25
+ switch (input.path) {
26
+ case "/api/api/app/bizdatarule/list": {
27
+ const schemaCode = String(q.schemaCode ?? "");
28
+ const type = q.dataRuleType !== undefined && q.dataRuleType !== "" ? Number(q.dataRuleType) : undefined;
29
+ return [...ctx.state.dataRules.values()]
30
+ .filter((rule) => rule.schemaCode === schemaCode && (type === undefined || rule.dataRuleType === type))
31
+ .map((rule) => ({ ...rule }));
32
+ }
33
+ case "/api/api/app/bizdatarule/create": {
34
+ if (typeof body.schemaCode !== "string" || !body.schemaCode)
35
+ return businessRefusal(10005, "数据模型编码不能为空");
36
+ if (typeof body.dataRuleType !== "number")
37
+ return businessRefusal(10005, "规则类型不能为空");
38
+ // Ticket 78 live fidelity: on an UNPUBLISHED app, type2 CALCULATE answers
39
+ // a FALSE dead-cycle 305003 — checkCalculateRulesDeadCycle concatenates
40
+ // `schemaCode.propertyCode` when getAppFunctionByCode(schemaCode) is
41
+ // null, mismatching the assignment/parameter code口径. Types 3/4/5 are
42
+ // unaffected.
43
+ if (Number(body.dataRuleType) === 2) {
44
+ const model = ctx.state.models.get(String(body.schemaCode));
45
+ const app = ctx.state.apps.get(model?.appCode ?? "test");
46
+ if (app?.published === false)
47
+ return businessRefusal(305003, "计算规则存在死循环");
48
+ }
49
+ ctx.state.dataRuleSeq += 1;
50
+ const id = `datarule-${ctx.state.dataRuleSeq}`;
51
+ ctx.state.dataRules.set(id, normalizeRule(id, body));
52
+ return null;
53
+ }
54
+ case "/api/api/app/bizdatarule/update": {
55
+ const id = String(body.id ?? "");
56
+ const existing = ctx.state.dataRules.get(id);
57
+ if (!existing)
58
+ return businessRefusal(304001, "数据规则不存在");
59
+ // The platform update is a full-row overwrite; callers must echo every
60
+ // persisted column or the omitted nullable columns are cleared.
61
+ ctx.state.dataRules.set(id, normalizeRule(id, body));
62
+ return null;
63
+ }
64
+ case "/api/api/app/bizdatarule/delete": {
65
+ ctx.state.dataRules.delete(String(q.id ?? ""));
66
+ return null;
67
+ }
68
+ default:
69
+ return NOT_HANDLED;
70
+ }
71
+ };
72
+ //# sourceMappingURL=datarule.js.map
@@ -0,0 +1,108 @@
1
+ import { NOT_HANDLED } from "./state.js";
2
+ function flatten(records) {
3
+ return records.flatMap((record) => [record, ...(record.children ? flatten(record.children) : [])]);
4
+ }
5
+ function classes(ctx) {
6
+ return [...ctx.state.dictionaryClasses.values()].map((item) => ({ ...item, dictionaryInfos: item.dictionaryIds.map((id) => ctx.state.dictionaries.get(id)).filter(Boolean) }));
7
+ }
8
+ function page(records, query) {
9
+ const searchWord = query?.searchWord ?? "";
10
+ const filtered = records.filter((record) => !searchWord || record.name.includes(searchWord) || record.code.includes(searchWord));
11
+ const pageNumber = Math.max(1, Number(query?.page ?? 1));
12
+ const size = Math.max(1, Number(query?.size ?? 20));
13
+ const content = filtered.slice((pageNumber - 1) * size, pageNumber * size);
14
+ return { totalElements: filtered.length, totalPages: Math.ceil(filtered.length / size), content };
15
+ }
16
+ export function dictionaryRoute(ctx, input) {
17
+ const dictionaries = ctx.state.dictionaries;
18
+ switch (input.path) {
19
+ case "/api/api/data_dictionary/searchDictionary": {
20
+ const rows = [...dictionaries.values()].filter((dictionary) => !input.query?.classificationId || dictionary.classificationId === input.query.classificationId).filter((dictionary) => !input.query?.searchWord || dictionary.name.includes(input.query.searchWord) || dictionary.code.includes(input.query.searchWord));
21
+ return { totalElements: rows.length, totalPages: rows.length ? 1 : 0, content: rows };
22
+ }
23
+ case "/api/api/data_dictionary/getDictionaryById":
24
+ return dictionaries.get(String(input.query?.id)) ?? {};
25
+ case "/api/api/data_dictionary/getDictionaryByCode":
26
+ return [...dictionaries.values()].find((dictionary) => dictionary.code === input.query?.code) ?? {};
27
+ case "/api/api/data_dictionary/searchDictionaryRecord": {
28
+ const dictionary = dictionaries.get(String(input.query?.id));
29
+ return page(dictionary ? flatten(dictionary.records) : [], input.query);
30
+ }
31
+ case "/api/api/data_dictionary/searchDictionaryRecordByCode": {
32
+ const dictionary = [...dictionaries.values()].find((item) => item.code === input.query?.code);
33
+ return page(dictionary ? flatten(dictionary.records) : [], input.query);
34
+ }
35
+ case "/api/api/data_dictionary/getEnableRecordsByDictionaryId": {
36
+ const dictionary = dictionaries.get(String(input.query?.dicId));
37
+ return dictionary?.status ? flatten(dictionary.records).filter((record) => record.status) : [];
38
+ }
39
+ case "/api/api/data_dictionary/getEnableRecordsByDictionaryCode": {
40
+ const dictionary = [...dictionaries.values()].find((item) => item.code === input.query?.code);
41
+ return dictionary?.status ? flatten(dictionary.records).filter((record) => record.status) : [];
42
+ }
43
+ case "/api/api/data_dictionary/getDictionaryRecordByParentId": {
44
+ const records = [...dictionaries.values()].flatMap((dictionary) => flatten(dictionary.records)).filter((record) => record.parentId === input.query?.parentId);
45
+ return records;
46
+ }
47
+ case "/api/api/data_dictionary/getDictionaryClass":
48
+ return classes(ctx);
49
+ case "/api/api/data_dictionary/getDictionaryClassDetail":
50
+ return classes(ctx);
51
+ case "/api/api/data_dictionary/saveDictionaryClass": {
52
+ const body = input.body;
53
+ const id = String(body.id ?? `class-${ctx.state.dictionaryClasses.size + 1}`);
54
+ ctx.state.dictionaryClasses.set(id, { id, name: String(body.name), sortKey: Number(body.sortKey ?? 0), dictionaryIds: Array.isArray(body.dictionaryIds) ? body.dictionaryIds.map(String) : [] });
55
+ return null;
56
+ }
57
+ case "/api/api/data_dictionary/deleteDictionaryClass": {
58
+ const body = input.body;
59
+ ctx.state.dictionaryClasses.delete(String(body.id));
60
+ return 1;
61
+ }
62
+ case "/api/api/data_dictionary/deleteDictionaryClassBatch": {
63
+ const body = input.body;
64
+ for (const id of Array.isArray(body.ids) ? body.ids : [])
65
+ ctx.state.dictionaryClasses.delete(String(id));
66
+ return 1;
67
+ }
68
+ case "/api/api/data_dictionary/updateDictionaryRecordUsed": {
69
+ const body = input.body;
70
+ const ids = String(body.id ?? "").split(";");
71
+ for (const dictionary of dictionaries.values())
72
+ for (const record of flatten(dictionary.records))
73
+ if (ids.includes(record.id))
74
+ record.initialUsed = true;
75
+ return null;
76
+ }
77
+ case "/api/api/data_dictionary/saveDictionary": {
78
+ const body = input.body;
79
+ const id = String(body.id ?? `dictionary-${dictionaries.size + 1}`);
80
+ const records = Array.isArray(body.records) ? body.records : [];
81
+ const normalize = (items, parentId = null) => items.map((item, index) => {
82
+ const recordId = String(item.id ?? `${id}-record-${index + 1}`);
83
+ return { id: recordId, code: String(item.code), name: String(item.name), name_i18n: typeof item.name_i18n === "string" ? item.name_i18n : JSON.stringify(item.name_i18n ?? { "zh-CN": item.name }), parentId, status: item.status !== false, dictionaryId: id, sortKey: Number(item.sortKey ?? index + 1), initialUsed: item.initialUsed === true, ...(Array.isArray(item.children) ? { children: normalize(item.children, recordId) } : {}) };
84
+ });
85
+ const dictionary = { id, code: String(body.code), name: String(body.name), name_i18n: typeof body.name_i18n === "string" ? body.name_i18n : undefined, dictionaryType: Number(body.dictionaryType ?? 0), status: body.status !== false, sortKey: Number(body.sortKey ?? 0), classificationId: String(body.classificationId ?? ""), records: normalize(records) };
86
+ dictionaries.set(id, dictionary);
87
+ return null;
88
+ }
89
+ case "/api/api/data_dictionary/updateDictionaryStatus": {
90
+ const body = input.body;
91
+ const dictionary = dictionaries.get(String(body.id));
92
+ if (dictionary)
93
+ dictionary.status = body.status === true;
94
+ return 1;
95
+ }
96
+ case "/api/api/data_dictionary/updateDictionaryRecordStatus": {
97
+ const body = input.body;
98
+ for (const dictionary of dictionaries.values()) {
99
+ const record = flatten(dictionary.records).find((item) => item.id === String(body.id));
100
+ if (record)
101
+ record.status = body.status === true;
102
+ }
103
+ return 1;
104
+ }
105
+ default: return NOT_HANDLED;
106
+ }
107
+ }
108
+ //# sourceMappingURL=dictionary.js.map
@@ -0,0 +1,30 @@
1
+ import { FAKE_RSA_PUBLIC_KEY, NOT_HANDLED } from "./state.js";
2
+ /**
3
+ * environment-governance lane: the auth flow the session manager drives to
4
+ * reach the environment. Wire-contract twin of src/lanes/environment.ts.
5
+ */
6
+ export const environmentRoute = (ctx, input) => {
7
+ const q = input.query ?? {};
8
+ switch (input.path) {
9
+ case "/api/public/system/config":
10
+ return {
11
+ corpId: "fake-corp",
12
+ client_secret: "fake-client-secret",
13
+ pcServerUrl: ctx.options.baseUrl ?? "https://fake.cloudpivot.example",
14
+ version: ctx.options.version ?? "8.6.26",
15
+ };
16
+ case "/user/public/getKey":
17
+ return { key: FAKE_RSA_PUBLIC_KEY, index: "1" };
18
+ case "/user/login/Authentication/get_code":
19
+ return { code: "fake-auth-code" };
20
+ case "/user/login/Authentication/get_token":
21
+ return { access_token: q.code === "fake-auth-code" ? "fake-token" : "denied" };
22
+ case "/api/api/organization/user/info_login":
23
+ if ((input.headers ?? {}).Authorization !== "Bearer fake-token")
24
+ return undefined;
25
+ return { permissions: ["ADMIN"], name: "fake-admin" };
26
+ default:
27
+ return NOT_HANDLED;
28
+ }
29
+ };
30
+ //# sourceMappingURL=environment.js.map
@@ -0,0 +1,167 @@
1
+ import { capabilityShapeError } from "../capability-shape.js";
2
+ import { businessRefusal, NOT_HANDLED } from "./state.js";
3
+ /** field-families lane: bizproperty CRUD + model-scoped publish. Twin of src/lanes/field.ts. */
4
+ export const fieldRoute = (ctx, input) => {
5
+ const q = input.query ?? {};
6
+ const body = (input.body ?? {});
7
+ switch (input.path) {
8
+ case "/api/api/app/bizproperty/list": {
9
+ const rows = [...ctx.state.fields.entries()]
10
+ .filter(([key]) => key.startsWith(`${q.schemaCode}#`))
11
+ .map(([, field]) => ({
12
+ code: field.code,
13
+ name: field.name,
14
+ name_i18n: field.name_i18n ?? null,
15
+ type: field.type,
16
+ options: field.options ?? null,
17
+ ...(field.relativeCode ? { relativeCode: field.relativeCode } : {}),
18
+ ...(field.relativePropertyCode ? { relativePropertyCode: field.relativePropertyCode } : {}),
19
+ ...(field.subSchema ? { subSchema: field.subSchema } : {}),
20
+ }))
21
+ .filter((row) => (q.isPublish === "true" ? ctx.state.fields.get(`${q.schemaCode}#${row.code}`)?.published : !ctx.state.fields.get(`${q.schemaCode}#${row.code}`)?.published));
22
+ return rows;
23
+ }
24
+ case "/api/api/app/bizproperty/get": {
25
+ const key = `${q.schemaCode}#${q.propertyCode}`;
26
+ const field = ctx.state.fields.get(key);
27
+ if (!field)
28
+ return businessRefusal(50000, `属性不存在:${q.propertyCode}`);
29
+ const drift = ctx.state.fieldDetailDrift.get(key);
30
+ const propertyType = drift?.propertyType ?? Number(field.type);
31
+ return {
32
+ code: field.code,
33
+ name: drift?.name ?? field.name,
34
+ name_i18n: field.name_i18n ?? null,
35
+ propertyType,
36
+ type: propertyType,
37
+ options: field.options ?? null,
38
+ ...(field.relativeCode ? { relativeCode: field.relativeCode } : {}),
39
+ };
40
+ }
41
+ case "/api/api/app/bizproperty/create":
42
+ case "/api/api/app/bizproperty/update": {
43
+ const key = `${body.schemaCode}#${body.code}`;
44
+ const existing = ctx.state.fields.get(key);
45
+ const type = Number(body.propertyType ?? body.type ?? existing?.type);
46
+ let options = {};
47
+ if (typeof body.options === "string") {
48
+ try {
49
+ options = JSON.parse(body.options);
50
+ }
51
+ catch {
52
+ return businessRefusal(50000, "options 不是合法 JSON");
53
+ }
54
+ }
55
+ else if (body.options && typeof body.options === "object") {
56
+ options = body.options;
57
+ }
58
+ if (Number.isInteger(type)) {
59
+ const shapeError = capabilityShapeError(type, "field", options);
60
+ if (shapeError)
61
+ return businessRefusal(50000, shapeError);
62
+ }
63
+ let subSchema;
64
+ if (body.subSchema && typeof body.subSchema === "object")
65
+ subSchema = body.subSchema;
66
+ else if (typeof body.subSchema === "string") {
67
+ try {
68
+ subSchema = JSON.parse(body.subSchema);
69
+ }
70
+ catch {
71
+ return businessRefusal(50000, "subSchema 不是合法 JSON");
72
+ }
73
+ }
74
+ else if (existing?.subSchema)
75
+ subSchema = existing.subSchema;
76
+ ctx.state.fields.set(key, {
77
+ code: String(body.code),
78
+ name: String(body.name ?? body.code),
79
+ type: String(body.propertyType ?? body.type ?? "TEXT"),
80
+ published: existing?.published ?? false,
81
+ ...(typeof body.name_i18n === "string" ? { name_i18n: body.name_i18n } : existing?.name_i18n !== undefined ? { name_i18n: existing.name_i18n } : {}),
82
+ ...(body.options !== undefined ? { options: String(body.options) } : existing?.options !== undefined ? { options: existing.options } : {}),
83
+ ...(body.relativeCode ? { relativeCode: String(body.relativeCode) } : existing?.relativeCode ? { relativeCode: existing.relativeCode } : {}),
84
+ ...(body.relativePropertyCode !== undefined ? { relativePropertyCode: String(body.relativePropertyCode) } : existing?.relativePropertyCode !== undefined ? { relativePropertyCode: existing.relativePropertyCode } : {}),
85
+ ...(subSchema ? { subSchema } : {}),
86
+ });
87
+ // Live 2026-09-18: creating/updating a field whose schemaCode IS a
88
+ // CHILD_TABLE field code adds/updates that column and syncs the parent
89
+ // field's subSchema.properties. Mirror it so the form scaffold sees new
90
+ // child columns.
91
+ const childSchema = String(body.schemaCode ?? "");
92
+ const childCode = String(body.code ?? "");
93
+ for (const parent of ctx.state.fields.values()) {
94
+ if (parent.type !== "8" || parent.code !== childSchema)
95
+ continue;
96
+ const sub = parent.subSchema;
97
+ if (!sub || !Array.isArray(sub.properties))
98
+ continue;
99
+ const row = {
100
+ code: childCode,
101
+ name: String(body.name ?? childCode),
102
+ ...(typeof body.name_i18n === "string" ? { name_i18n: body.name_i18n } : {}),
103
+ schemaCode: childSchema,
104
+ propertyType: type,
105
+ propertyEmpty: body.propertyEmpty === true,
106
+ sortKey: null,
107
+ ...(body.options !== undefined ? { options: String(body.options) } : {}),
108
+ defaultProperty: false,
109
+ published: false,
110
+ };
111
+ const at = sub.properties.findIndex((candidate) => candidate.code === childCode);
112
+ if (at >= 0)
113
+ sub.properties[at] = { ...sub.properties[at], ...row };
114
+ else
115
+ sub.properties.push(row);
116
+ }
117
+ return null;
118
+ }
119
+ case "/api/api/app/bizproperty/publish": {
120
+ const schemaCode = String(body.bizSchemaCode ?? body.schemaCode);
121
+ // Ticket 79 live-fidelity (docker log evidence, 2026-09-13 21:43-21:45):
122
+ // under DB connection-pool contention a publish can fail with
123
+ // `表单:<t>_<schema>不存在列:<col>` (BizSchemaDbManagerImpl addIndexes/
124
+ // updateColumns assert against the serializer view) while the DDL from
125
+ // the failed attempt auto-commits — the state SELF-HEALS across
126
+ // retries (live: attempt#2 failed on ownerDeptQueryCode, #3 on
127
+ // omc_mcp_wt_num, #4 succeeded). Tests seed the remaining count via
128
+ // state.transientPublishFailures.
129
+ const remaining = ctx.state.transientPublishFailures.get(schemaCode) ?? 0;
130
+ if (remaining > 0) {
131
+ ctx.state.transientPublishFailures.set(schemaCode, remaining - 1);
132
+ return businessRefusal(50000, `表单:ixtw3_${schemaCode}不存在列:ownerDeptQueryCode`);
133
+ }
134
+ for (const [key, field] of ctx.state.fields)
135
+ if (key.startsWith(`${schemaCode}#`))
136
+ field.published = true;
137
+ // Publishing a model publishes its child tables' schemas too (their
138
+ // properties live under the CHILD_TABLE field's code on the same model).
139
+ for (const field of ctx.state.fields.values()) {
140
+ if (field.type !== "8" || !ctx.state.fields.get(`${schemaCode}#${field.code}`))
141
+ continue;
142
+ for (const [key, child] of ctx.state.fields)
143
+ if (key.startsWith(`${field.code}#`))
144
+ child.published = true;
145
+ }
146
+ return null;
147
+ }
148
+ case "/api/api/app/bizproperty/force_delete":
149
+ case "/api/api/app/bizproperty/delete": {
150
+ const delSchema = String(q.bizSchemaCode ?? q.schemaCode ?? body.schemaCode ?? "");
151
+ const delCode = String(q.bizPropertyCode ?? q.code ?? body.code ?? "");
152
+ ctx.state.fields.delete(`${delSchema}#${delCode}`);
153
+ // Deleting a child column drops it from the parent child-table subSchema.
154
+ for (const parent of ctx.state.fields.values()) {
155
+ if (parent.type !== "8" || parent.code !== delSchema)
156
+ continue;
157
+ const sub = parent.subSchema;
158
+ if (sub && Array.isArray(sub.properties))
159
+ sub.properties = sub.properties.filter((candidate) => candidate.code !== delCode);
160
+ }
161
+ return null;
162
+ }
163
+ default:
164
+ return NOT_HANDLED;
165
+ }
166
+ };
167
+ //# sourceMappingURL=field.js.map