@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,101 @@
1
+ import { capabilityShapeError } from "../capability-shape.js";
2
+ import { CAPABILITY_CONTROL_TYPES } from "../capability-shape.js";
3
+ import { businessRefusal, NOT_HANDLED } from "./state.js";
4
+ /** form-publish-chain lane: default-form draft/publish. Twin of src/lanes/form.ts. */
5
+ export const formRoute = (ctx, input) => {
6
+ const q = input.query ?? {};
7
+ const body = (input.body ?? {});
8
+ switch (input.path) {
9
+ case "/api/api/app/bizsheet/get": {
10
+ const sheet = ctx.state.sheets.get(q.sheetCode);
11
+ if (!sheet)
12
+ return null;
13
+ const name = q.sheetCode;
14
+ return {
15
+ id: `sheet-${q.sheetCode}`,
16
+ sheetCode: sheet.sheetCode,
17
+ code: sheet.sheetCode,
18
+ name,
19
+ sheetType: 0,
20
+ existDraft: sheet.existDraft,
21
+ published: sheet.published,
22
+ draftViewJson: sheet.draftViewJson ?? null,
23
+ draftAttributesJson: sheet.draftAttributesJson ?? null,
24
+ draftHtmlJson: sheet.draftHtmlJson ?? null,
25
+ // Ticket 100: real bizsheet columns (BizForm.java:95-100); the
26
+ // designer writes draftActionsJson on save and both faces on publish
27
+ // (frontend-dist/admin/js/4280.cfa75764.js offsets 193538/185347).
28
+ draftActionsJson: sheet.draftActionsJson ?? null,
29
+ publishedViewJson: sheet.publishedViewJson ?? null,
30
+ publishedAttributesJson: sheet.publishedAttributesJson ?? null,
31
+ publishedActionsJson: sheet.publishedActionsJson ?? null,
32
+ publishedHtmlJson: sheet.publishedHtmlJson ?? null,
33
+ binding: { fieldCodes: sheet.fieldCodes },
34
+ };
35
+ }
36
+ case "/api/api/app/bizsheet/create": {
37
+ const code = String(body.code ?? body.sheetCode);
38
+ if (!ctx.state.sheets.has(code))
39
+ ctx.state.sheets.set(code, { sheetCode: code, existDraft: false, published: false, draftHtmlJson: "[]", fieldCodes: [] });
40
+ return null;
41
+ }
42
+ case "/api/api/app/bizsheet/update": {
43
+ let attributes = {};
44
+ if (typeof body.draftAttributesJson === "string") {
45
+ try {
46
+ attributes = JSON.parse(body.draftAttributesJson);
47
+ }
48
+ catch {
49
+ return businessRefusal(50000, "draftAttributesJson 不是合法 JSON");
50
+ }
51
+ }
52
+ for (const [fieldCode, control] of Object.entries(attributes)) {
53
+ const controlType = Number(control.type);
54
+ const propertyType = Object.entries(CAPABILITY_CONTROL_TYPES).find(([, value]) => value === controlType)?.[0];
55
+ if (propertyType === undefined)
56
+ continue;
57
+ const options = control.options && typeof control.options === "object" ? control.options : {};
58
+ const shapeError = capabilityShapeError(Number(propertyType), "control", options);
59
+ if (shapeError)
60
+ return businessRefusal(50000, `${fieldCode}: ${shapeError}`);
61
+ }
62
+ const code = String(body.code ?? body.sheetCode);
63
+ const existing = ctx.state.sheets.get(code) ?? {
64
+ sheetCode: code,
65
+ existDraft: false,
66
+ published: false,
67
+ draftHtmlJson: "[]",
68
+ fieldCodes: [],
69
+ };
70
+ ctx.state.sheets.set(code, {
71
+ ...existing,
72
+ existDraft: true,
73
+ draftViewJson: body.draftViewJson ?? existing.draftViewJson ?? null,
74
+ draftAttributesJson: body.draftAttributesJson ?? existing.draftAttributesJson ?? null,
75
+ fieldCodes: typeof body.draftAttributesJson === "string" ? Object.keys(attributes) : existing.fieldCodes,
76
+ draftActionsJson: body.draftActionsJson ?? existing.draftActionsJson ?? null,
77
+ // draftHtmlJson holds the COMPLETE layout JSON array (including the
78
+ // customScript element when present). Default to "[]" on first
79
+ // draft so onlinejs.compose can parse it as an array.
80
+ draftHtmlJson: body.draftHtmlJson ?? existing.draftHtmlJson ?? "[]",
81
+ });
82
+ return null;
83
+ }
84
+ case "/api/api/app/bizsheet/publish": {
85
+ const code = String(body.code ?? body.sheetCode);
86
+ const sheet = ctx.state.sheets.get(code);
87
+ if (sheet) {
88
+ sheet.published = true;
89
+ sheet.existDraft = false;
90
+ sheet.publishedViewJson = sheet.draftViewJson ?? null;
91
+ sheet.publishedAttributesJson = sheet.draftAttributesJson ?? null;
92
+ sheet.publishedActionsJson = body.publishedActionsJson ?? "[]";
93
+ sheet.publishedHtmlJson = sheet.draftHtmlJson ?? "[]";
94
+ }
95
+ return null;
96
+ }
97
+ default:
98
+ return NOT_HANDLED;
99
+ }
100
+ };
101
+ //# sourceMappingURL=form.js.map
@@ -0,0 +1,121 @@
1
+ import { dispatchFakeRoute } from "./routes.js";
2
+ import { createFakeState, isBusinessRefusal, isMalformedEnvelope, isRawResponse } from "./state.js";
3
+ export { FAKE_RSA_PUBLIC_KEY } from "./state.js";
4
+ /**
5
+ * In-memory fake CloudPivot 8.6 for offline CLI tests: implements the auth
6
+ * flow and every live-proven endpoint the tool surface drives. Supports
7
+ * failure injection (transport outage, non-8.6 version, business errcode).
8
+ *
9
+ * The wire behavior is split into one route module per configuration lane
10
+ * under ./fake/, mirroring src/lanes/*; this class owns the shared store,
11
+ * the failure-injection flags, and the seed/readback test accessors.
12
+ */
13
+ export class FakeCloudPivot {
14
+ options;
15
+ state;
16
+ transportDown = false;
17
+ businessFailure = null;
18
+ /** Round-2 T-06: inject a RAW (non-errcode) response body to exercise malformed-envelope diagnostics. */
19
+ malformedResponse = null;
20
+ /** When true, /api/api/runtime/form/submit returns no objectId for formType=2. */
21
+ forceSubmitMissingObjectId = false;
22
+ forceSubmitMismatchedObjectId = false;
23
+ omitDraftSaveWorkItem = false;
24
+ constructor(options = {}) {
25
+ this.options = options;
26
+ this.state = createFakeState(options);
27
+ }
28
+ /** Test accessor: the stored default-form draft for a schema (undefined if none). */
29
+ sheetDraft(schemaCode) {
30
+ return this.state.sheets.get(schemaCode);
31
+ }
32
+ /** Test accessor: the last request body for the given path (or undefined). */
33
+ lastBody(path) {
34
+ for (let i = this.state.callLog.length - 1; i >= 0; i -= 1) {
35
+ if (this.state.callLog[i].path === path)
36
+ return this.state.callLog[i].body;
37
+ }
38
+ return undefined;
39
+ }
40
+ /** Test accessor: every recorded request body for the given path (in order). */
41
+ bodiesFor(path) {
42
+ return this.state.callLog.filter((entry) => entry.path === path).map((entry) => entry.body);
43
+ }
44
+ queriesFor(path) {
45
+ return this.state.callLog.filter((entry) => entry.path === path).map((entry) => entry.query);
46
+ }
47
+ seedRule(rule) {
48
+ const id = rule.id ?? `rule-${this.state.rules.size + 1}`;
49
+ this.state.rules.set(`${rule.schemaCode}#${rule.code}`, {
50
+ ...rule,
51
+ id,
52
+ schemaCode: rule.schemaCode,
53
+ code: rule.code,
54
+ name: rule.name ?? rule.code,
55
+ enabled: rule.enabled ?? false,
56
+ });
57
+ }
58
+ seedDataRow(schemaCode, data, id = `row-${this.state.dataRows.size + 1}`) {
59
+ this.state.dataRows.set(id, { bizObjectId: id, schemaCode, ...data });
60
+ }
61
+ /**
62
+ * Reproduce ticket 07: mark a model's default list view as having no
63
+ * configured columns. Subsequent /runtime/query/list responses drop business
64
+ * columns from `data` (they read back null) though seedDataRow stored them.
65
+ */
66
+ markListViewUnconfigured(schemaCode) {
67
+ this.state.unconfiguredListViews.add(schemaCode);
68
+ }
69
+ seedApp(app) {
70
+ this.state.apps.set(app.code, {
71
+ id: app.id ?? `app-${this.state.apps.size + 1}`,
72
+ code: app.code,
73
+ name: app.name ?? app.code,
74
+ groupId: app.groupId ?? [...this.state.appGroups.keys()][0] ?? "group-1",
75
+ ...(app.published !== undefined ? { published: app.published } : {}),
76
+ });
77
+ }
78
+ seedField(schemaCode, code, propertyType, published = true) {
79
+ this.state.fields.set(`${schemaCode}#${code}`, { code, name: code, type: String(propertyType), published });
80
+ }
81
+ async request(input) {
82
+ this.state.callLog.push({
83
+ path: input.path,
84
+ method: input.method,
85
+ body: input.body,
86
+ query: input.query,
87
+ ...(input.multipart !== undefined ? { multipart: { filename: input.multipart.filename, contentType: input.multipart.contentType, bytes: input.multipart.content.length } } : {}),
88
+ });
89
+ if (this.transportDown)
90
+ throw new Error("simulated transport outage");
91
+ if (this.businessFailure && input.path.includes(this.businessFailure.pathFragment))
92
+ return { status: 200, body: { errcode: this.businessFailure.errcode, errmsg: "simulated business refusal", data: null } };
93
+ if (this.malformedResponse && input.path.includes(this.malformedResponse.pathFragment))
94
+ return { status: 200, body: this.malformedResponse.body };
95
+ const handler = this.route(input);
96
+ if (isBusinessRefusal(handler))
97
+ return { status: 200, body: { errcode: handler.errcode, errmsg: handler.errmsg, data: null } };
98
+ // Ticket 76: raw-envelope passthrough for platforms answers that carry no
99
+ // numeric errcode (workflow/create duplicate-schema live shape).
100
+ if (isMalformedEnvelope(handler))
101
+ return { status: 200, body: handler.payload };
102
+ // Ticket 106: non-envelope bodies (upload refId plain string, export ZIP
103
+ // bytes) pass through verbatim.
104
+ if (isRawResponse(handler))
105
+ return { status: 200, body: handler.payload };
106
+ if (handler === undefined)
107
+ return { status: 404, body: { errcode: 404, errmsg: "not found", data: null } };
108
+ return { status: 200, body: { errcode: 0, errmsg: "ok", data: handler } };
109
+ }
110
+ route(input) {
111
+ const ctx = {
112
+ state: this.state,
113
+ options: this.options,
114
+ forceSubmitMissingObjectId: this.forceSubmitMissingObjectId,
115
+ forceSubmitMismatchedObjectId: this.forceSubmitMismatchedObjectId,
116
+ omitDraftSaveWorkItem: this.omitDraftSaveWorkItem,
117
+ };
118
+ return dispatchFakeRoute(ctx, input);
119
+ }
120
+ }
121
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,289 @@
1
+ import { businessRefusal, NOT_HANDLED } from "./state.js";
2
+ import { QUERY_ACTION_RELATIVE, QUERY_ACTION_TYPE, validateButtonActionConfig } from "../lanes/list-view-shapes.js";
3
+ /**
4
+ * BizQueryController.dealHistoryDataOfButton (8.6.26 :535-631) injects these
5
+ * default query actions on the query/get read face when the stored view lacks
6
+ * them, so readback from a view that was never configured with actions still
7
+ * carries the platform buttons. The tool filters/decodes them; the fake mirrors
8
+ * the injection so unit tests see the same shape as live. Enum numbers come
9
+ * from the shared list-view shape descriptors (ticket 53).
10
+ */
11
+ const PLATFORM_DEFAULT_ACTIONS = [
12
+ { actionCode: "qr_code", name: "打印二维码", queryActionType: QUERY_ACTION_TYPE.index.QR_CODE, sortKey: 7, icon: "plus", associationType: QUERY_ACTION_RELATIVE.index.BIZSHEET, associationCode: "__schema__", clientType: null },
13
+ { actionCode: "batch_print", name: "批量打印", queryActionType: QUERY_ACTION_TYPE.index.BATCH_PRINT, sortKey: 6, icon: "plus", associationType: QUERY_ACTION_RELATIVE.index.BIZSHEET, associationCode: "__schema__", clientType: null },
14
+ { actionCode: "batch_update", name: "批量修改", queryActionType: QUERY_ACTION_TYPE.index.BATCH_UPDATE, sortKey: 3, icon: "plus", associationType: QUERY_ACTION_RELATIVE.index.BIZSHEET },
15
+ { actionCode: "import_attachment", name: "导入附件", queryActionType: QUERY_ACTION_TYPE.index.IMPORT_ATTACHMENT, sortKey: 10, icon: "upload" },
16
+ { actionCode: "export_attachment", name: "导出附件", queryActionType: QUERY_ACTION_TYPE.index.EXPORT_ATTACHMENT, sortKey: 11, icon: "download" },
17
+ { actionCode: "batch_submit", name: "批量提交", queryActionType: QUERY_ACTION_TYPE.index.BATCH_SUBMIT, sortKey: 3, icon: "plus" },
18
+ ];
19
+ /**
20
+ * Live 2026-09-17: a freshly created PC LIST view is seeded with STORED
21
+ * actions (not merely injected on read): add/delete/import/export/editowner.
22
+ * Measured on two un-configured views (eq_repair_order, a fresh model's
23
+ * default view) whose read face carries the full 11 (these 5 stored + the 6
24
+ * injected below). Because `query/update` REPLACES actions, configuring one
25
+ * action used to delete these five — the list then rendered with NO Add
26
+ * button. The fake must seed them or a regression cannot be caught.
27
+ */
28
+ const DEFAULT_STORED_ACTIONS = [
29
+ { actionCode: "add", name: "新增", queryActionType: QUERY_ACTION_TYPE.index.ADD, icon: "plus" },
30
+ { actionCode: "delete", name: "删除", queryActionType: QUERY_ACTION_TYPE.index.DELETE, icon: "delete" },
31
+ { actionCode: "import", name: "导入", queryActionType: QUERY_ACTION_TYPE.index.IMPORT, icon: "upload" },
32
+ { actionCode: "export", name: "导出", queryActionType: QUERY_ACTION_TYPE.index.EXPORT, icon: "download" },
33
+ { actionCode: "editowner", name: "修改拥有者", queryActionType: QUERY_ACTION_TYPE.index.EDITOWNER, icon: "plus" },
34
+ ];
35
+ function seedStoredActions(view) {
36
+ if (view.queryActions !== undefined)
37
+ return;
38
+ view.queryActions = DEFAULT_STORED_ACTIONS.map((action) => ({ ...action, schemaCode: view.schemaCode, clientType: "PC", associationType: action.actionCode === "add" ? QUERY_ACTION_RELATIVE.index.BIZSHEET : undefined, associationCode: action.actionCode === "add" ? view.schemaCode : undefined }));
39
+ }
40
+ function injectDefaultActions(schemaCode, stored) {
41
+ const actions = stored.map((action) => ({ ...action }));
42
+ const hasType = (type) => actions.some((action) => action.queryActionType === type);
43
+ const hasImportExport = hasType(21) || hasType(22);
44
+ for (const template of PLATFORM_DEFAULT_ACTIONS) {
45
+ const type = Number(template.queryActionType);
46
+ if (type === 21 || type === 22) {
47
+ if (hasImportExport)
48
+ continue;
49
+ }
50
+ else if (hasType(type))
51
+ continue;
52
+ const row = { ...template, schemaCode };
53
+ if (row.associationCode === "__schema__")
54
+ row.associationCode = schemaCode;
55
+ if (row.clientType === undefined)
56
+ row.clientType = "PC";
57
+ actions.push(row);
58
+ }
59
+ return actions;
60
+ }
61
+ /**
62
+ * BizQueryController.executeReSetQueryActionsSortKey (:962-998) resets sortKey
63
+ * to i+1 and backfills queryId/schemaCode/clientType; ModelManagementFacadeImpl
64
+ * .updateBizQuery (:1977-1987) copies a PC `add` action with a relativeCode to
65
+ * an APP `add`. The fake mirrors both so readback matches live.
66
+ */
67
+ function normalizeStoredActions(bodyActions, clientType, schemaCode) {
68
+ const actions = bodyActions.map((action, index) => ({
69
+ ...action,
70
+ sortKey: index + 1,
71
+ clientType,
72
+ schemaCode,
73
+ // Persisted BizQueryAction boolean columns default to false (observed live);
74
+ // platform-injected read-face actions are not persisted, so they stay null.
75
+ systemAction: action.systemAction ?? false,
76
+ customService: action.customService ?? false,
77
+ }));
78
+ for (const action of actions) {
79
+ if (action.actionCode === "add" && (action.associationCode === undefined || action.associationCode === null || action.associationCode === "")) {
80
+ action.associationType = QUERY_ACTION_RELATIVE.index.BIZSHEET;
81
+ action.associationCode = schemaCode;
82
+ }
83
+ }
84
+ if (clientType === "PC") {
85
+ const pcAdd = actions.find((action) => action.actionCode === "add" && action.clientType === "PC" && action.associationCode != null);
86
+ if (pcAdd) {
87
+ const existing = actions.findIndex((action) => action.clientType === "APP" && action.actionCode === "add");
88
+ if (existing >= 0)
89
+ actions.splice(existing, 1);
90
+ actions.push({ ...pcAdd, clientType: "APP", sortKey: actions.length + 1 });
91
+ }
92
+ }
93
+ return actions;
94
+ }
95
+ /**
96
+ * BizButtonFacadeImpl.addBizButtonList(String,String,ButtonTriggerType,String)
97
+ * (8.6.26 :112-137) returns early on a blank `customButtonJson`, so an omitted
98
+ * key leaves the stored buttons untouched; a JSON array fully replaces them
99
+ * (batchDeleteByTrigger then re-add), forcing schemaCode/triggerType QUERY/
100
+ * triggerCode view code and sortKey = array index. The fake mirrors that so the
101
+ * query/update round trip matches live.
102
+ */
103
+ function replaceStoredButtons(customButtonJson, schemaCode, triggerCode) {
104
+ if (typeof customButtonJson !== "string" || customButtonJson.trim() === "")
105
+ return undefined;
106
+ let parsed;
107
+ try {
108
+ parsed = JSON.parse(customButtonJson);
109
+ }
110
+ catch {
111
+ return undefined;
112
+ }
113
+ if (!Array.isArray(parsed))
114
+ return undefined;
115
+ for (const button of parsed) {
116
+ if (!button || typeof button !== "object" || Array.isArray(button))
117
+ return undefined;
118
+ try {
119
+ validateButtonActionConfig(button);
120
+ }
121
+ catch {
122
+ return undefined;
123
+ }
124
+ }
125
+ return parsed.map((button, index) => ({
126
+ ...button,
127
+ schemaCode: button.schemaCode ?? schemaCode,
128
+ triggerType: "QUERY",
129
+ triggerCode,
130
+ sortKey: index,
131
+ }));
132
+ }
133
+ /** W1-list-view lane: design-time query views. Twin of src/lanes/list-view.ts. */
134
+ export const listViewRoute = (ctx, input) => {
135
+ const q = input.query ?? {};
136
+ const body = (input.body ?? {});
137
+ switch (input.path) {
138
+ case "/api/api/app/query/create": {
139
+ const code = String(body.code ?? `view-${ctx.state.listViews.size + 1}`);
140
+ // Live fidelity (ticket 81): creating a view whose code already exists
141
+ // answers 303002 编码重复 — a post-timeout retry hits exactly this.
142
+ const existing = ctx.state.listViews.get(code);
143
+ if (existing && existing.schemaCode === String(body.schemaCode ?? ""))
144
+ return businessRefusal(303002, `编码重复:${code}`);
145
+ const created = { code, name: String(body.name), schemaCode: String(body.schemaCode ?? ""), queryColumns: [] };
146
+ seedStoredActions(created);
147
+ ctx.state.listViews.set(code, created);
148
+ return { code };
149
+ }
150
+ case "/api/api/app/query/get_header": {
151
+ const view = ctx.state.listViews.get(q.code);
152
+ return view
153
+ ? { id: `view-${view.code}`, code: view.code, name: view.name, schemaCode: view.schemaCode, type: "LIST", showOnPc: false, publish: view.publish === true, ...(view.queryPresentationType !== undefined ? { queryPresentationType: view.queryPresentationType } : {}) }
154
+ : null;
155
+ }
156
+ case "/api/api/app/query/update_header": {
157
+ // BizQueryController.updateHeader → updateBizQuery(BizQueryHeaderModel):
158
+ // a PLAIN header update with NO publish gate (unlike /query/update, whose
159
+ // body-driven queryPresentationType assignment is skipped once the view
160
+ // is published — the BUG-11 live root cause).
161
+ const code = String(body.code ?? "");
162
+ const view = ctx.state.listViews.get(code);
163
+ if (!view)
164
+ return null;
165
+ if (body.queryPresentationType !== undefined)
166
+ view.queryPresentationType = body.queryPresentationType;
167
+ if (typeof body.name === "string")
168
+ view.name = body.name;
169
+ if (typeof body.publish === "boolean")
170
+ view.publish = body.publish;
171
+ return { code, queryPresentationType: view.queryPresentationType ?? null };
172
+ }
173
+ case "/api/api/app/query/get": {
174
+ // Live shape: every model owns a default list view (code = schemaCode)
175
+ // that starts without business columns. Lazy-create on first fetch.
176
+ // BizQueryFacadeImpl.getBizQuery sets model.clientType from the request
177
+ // (query/get requires it; query/update refuses a null clientType), so the
178
+ // fake echoes it too.
179
+ const schemaCode = String(q.schemaCode ?? "");
180
+ const code = String(q.code ?? schemaCode);
181
+ let view = ctx.state.listViews.get(code);
182
+ if (!view && code === schemaCode) {
183
+ // Live: a model's default view is created PUBLISHED (h_biz_query.publish=1).
184
+ view = { code, name: code, schemaCode, queryColumns: [], publish: true };
185
+ seedStoredActions(view);
186
+ ctx.state.listViews.set(code, view);
187
+ }
188
+ if (!view)
189
+ return null;
190
+ // BizQueryFacadeImpl.getBizQuery overwrites a condition's stored options
191
+ // with the field's own options before readback, so the read face is
192
+ // field-derived rather than the persisted condition value.
193
+ const queryConditions = view.queryConditions?.map((condition) => {
194
+ const propertyCode = String(condition.propertyCode ?? "");
195
+ const field = propertyCode.includes(".") ? undefined : ctx.state.fields.get(`${view.schemaCode || schemaCode}#${propertyCode}`);
196
+ return { ...condition, options: field?.options ?? null };
197
+ });
198
+ const clientType = String(q.clientType ?? "PC");
199
+ const queryActions = clientType === "PC" ? injectDefaultActions(view.schemaCode || schemaCode, view.queryActions ?? []) : view.queryActions;
200
+ return {
201
+ id: `view-${code}`,
202
+ code: view.code,
203
+ name: view.name,
204
+ schemaCode: view.schemaCode || schemaCode,
205
+ clientType,
206
+ queryColumns: view.queryColumns,
207
+ ...(view.options !== undefined ? { options: view.options } : {}),
208
+ ...(view.queryPresentationType !== undefined ? { queryPresentationType: view.queryPresentationType } : {}),
209
+ ...(view.queryGanttModel !== undefined ? { queryGanttModel: view.queryGanttModel } : {}),
210
+ ...(view.queryLayer !== undefined ? { queryLayer: view.queryLayer } : {}),
211
+ ...(view.multiTableHeaderConfig !== undefined ? { multiTableHeaderConfig: view.multiTableHeaderConfig } : {}),
212
+ ...(view.querySorts ? { querySorts: view.querySorts } : {}),
213
+ ...(queryConditions ? { queryConditions } : {}),
214
+ ...(queryActions && queryActions.length > 0 ? { queryActions } : {}),
215
+ ...(view.queryPresentation ? { queryPresentation: view.queryPresentation } : {}),
216
+ ...(view.customButtons ? { customButtons: view.customButtons } : {}),
217
+ };
218
+ }
219
+ case "/api/api/app/query/update": {
220
+ const code = String(body.code ?? "");
221
+ const view = ctx.state.listViews.get(code);
222
+ if (!view)
223
+ return null;
224
+ // BizQueryController.update calls validateNotEmptyGantt (:1121-1136) before
225
+ // updateBizQuery: a GANTT view requires queryGanttModel with non-null
226
+ // startTime/endTime/title codes.
227
+ if (body.queryPresentationType === "GANTT") {
228
+ const gantt = body.queryGanttModel;
229
+ if (!gantt || typeof gantt !== "object")
230
+ return businessRefusal(10005, "甘特图参数不能为空");
231
+ if (gantt.startTimePropertyCode == null)
232
+ return businessRefusal(10005, "甘特图开始时间映射参数不能为空!");
233
+ if (gantt.endTimePropertyCode == null)
234
+ return businessRefusal(10005, "甘特图结束时间映射参数不能为空!");
235
+ if (gantt.titlePropertyCode == null)
236
+ return businessRefusal(10005, "甘特图标题映射参数不能为空!");
237
+ }
238
+ if (Array.isArray(body.queryColumns))
239
+ view.queryColumns = body.queryColumns;
240
+ // BizQueryModel.options (String) is replaced whenever the update body
241
+ // carries the key; an omitted key leaves it untouched (updateBizQuery's
242
+ // name/options diff branch, ModelManagementFacadeImpl:1966-1973).
243
+ if (body.options !== undefined)
244
+ view.options = body.options;
245
+ // BizQueryHeaderModel.queryPresentationType is assigned by updateBizQuery
246
+ // (:1955) but the entity update block is SKIPPED when the stored view is
247
+ // published (ModelManagementFacadeImpl:1998-2010) — a live-verified
248
+ // silent drop (BUG-11). The tool must therefore drive /update_header for
249
+ // an already-published view.
250
+ if (body.queryPresentationType !== undefined && view.publish !== true)
251
+ view.queryPresentationType = body.queryPresentationType;
252
+ // saveBizQueryDetail only swaps queryGantt when the submitted model is
253
+ // non-null (:178-183), so an omitted key leaves the stored gantt.
254
+ if (body.queryGanttModel && typeof body.queryGanttModel === "object")
255
+ view.queryGanttModel = body.queryGanttModel;
256
+ // saveBizQueryDetail assigns queryLayer/multiTableHeaderConfig
257
+ // unconditionally (:184-186); the tool only sends them when configured.
258
+ if (Array.isArray(body.queryLayer))
259
+ view.queryLayer = body.queryLayer;
260
+ if (body.multiTableHeaderConfig !== undefined)
261
+ view.multiTableHeaderConfig = body.multiTableHeaderConfig;
262
+ if (Array.isArray(body.querySorts))
263
+ view.querySorts = body.querySorts;
264
+ if (Array.isArray(body.queryConditions))
265
+ view.queryConditions = body.queryConditions;
266
+ if (Array.isArray(body.queryActions)) {
267
+ view.queryActions = normalizeStoredActions(body.queryActions, String(body.clientType ?? "PC"), String(body.schemaCode ?? view.schemaCode ?? ""));
268
+ }
269
+ // BizQueryDetailServiceImpl.saveBizQueryDetail removes every presentation
270
+ // whose clientType matches and re-adds the submitted one; an omitted
271
+ // presentation (absent body key) leaves the stored one untouched.
272
+ if (body.queryPresentation && typeof body.queryPresentation === "object") {
273
+ view.queryPresentation = body.queryPresentation;
274
+ }
275
+ const buttons = replaceStoredButtons(body.customButtonJson, String(body.schemaCode ?? view.schemaCode ?? ""), code);
276
+ if (buttons !== undefined)
277
+ view.customButtons = buttons;
278
+ return { code: view.code };
279
+ }
280
+ case "/api/api/app/query/list":
281
+ return [...ctx.state.listViews.values()].map((view) => ({ code: view.code, name: view.name }));
282
+ case "/api/api/app/query/delete":
283
+ ctx.state.listViews.delete(q.code);
284
+ return null;
285
+ default:
286
+ return NOT_HANDLED;
287
+ }
288
+ };
289
+ //# sourceMappingURL=list-view.js.map
@@ -0,0 +1,136 @@
1
+ import { relationTargetOf } from "../field-families.js";
2
+ import { MODEL_REFERENCED_DELETE_ERRCODE } from "../lanes/model-contract.js";
3
+ import { businessRefusal, NOT_HANDLED } from "./state.js";
4
+ /** model-management lane: app tree, model CRUD, physical delete. Twin of src/lanes/model.ts. */
5
+ export const modelRoute = (ctx, input) => {
6
+ const q = input.query ?? {};
7
+ const body = (input.body ?? {});
8
+ switch (input.path) {
9
+ case "/api/api/app/apppackage/trees": {
10
+ // Live fidelity (ticket 68): the tree row id is the AppFunction id, not
11
+ // the biz-model record id; appCode filters the tree, treeHidden models
12
+ // keep a record but no tree node. Ticket 83: rows carry parentId (live
13
+ // AppFunctionModel shape; bizmodels/update requires it) and the node
14
+ // display name comes from nodeName (the menu node is renamed
15
+ // independently of the schema record).
16
+ const appCode = q.appCode === undefined ? undefined : String(q.appCode);
17
+ // Live 8.6.26 fidelity (2026-09-17): the tree is NESTED — a Folder row
18
+ // carrying its BizModel children — not a flat list. The environment lane
19
+ // used a top-level map and silently dropped every model, so a live
20
+ // discover_environment with applicationCode returned no models. Consumers
21
+ // must flattenTreeRows (as resolveModelTreeNode already does); the fake
22
+ // must nest so a regression cannot pass.
23
+ const children = [...ctx.state.models.values()]
24
+ .filter((model) => !model.treeHidden && (appCode === undefined || (model.appCode ?? "test") === appCode))
25
+ .map((model) => ({ id: model.id, type: "BizModel", code: model.code, name: model.nodeName ?? model.name, parentId: "folder-1" }));
26
+ return [{ id: "folder-1", type: "FOLDER", code: "general", name: "General", children }];
27
+ }
28
+ case "/api/api/app/bizmodels/list_schema":
29
+ return [...ctx.state.models.values()].map((model) => ({ id: model.recordId, schemaCode: model.code, name: model.name }));
30
+ case "/api/api/app/bizmodels/create": {
31
+ const code = String(body.bizSchemaCode ?? body.code);
32
+ const n = ctx.state.models.size + 1;
33
+ ctx.state.models.set(code, {
34
+ id: `model-${n}-fn`,
35
+ recordId: `model-${n}-rec`,
36
+ code,
37
+ name: String(body.name ?? code),
38
+ appCode: String(body.applicationCode ?? body.appCode ?? "test"),
39
+ });
40
+ // Live fidelity (ticket 42): bizmodels/create seeds NO form — the model
41
+ // has no h_biz_sheet row until the form is created (bizsheet/create, or
42
+ // form.draft's scaffold-on-absent path). Do not pre-seed a sheet here.
43
+ return { schemaCode: code };
44
+ }
45
+ case "/api/api/app/apppackage/physically_delete": {
46
+ const rows = Array.isArray(input.body) ? input.body : [];
47
+ for (const row of rows) {
48
+ if (row.type === undefined || row.type === null || row.type === "") {
49
+ ctx.state.apps.delete(String(row.code));
50
+ continue;
51
+ }
52
+ const code = String(row.code);
53
+ // deleteFunctions semantics (ticket 23): a model referenced by
54
+ // another model's relation field cannot be deleted. Live: the sync
55
+ // face answers errcode 0 and the async dispatcher fails with
56
+ // ServiceException "xxx被模型 yyy 引用,不能删除"
57
+ // (AppIntegrationFacadeImpl.deleteFunctions:2201); the fake surfaces
58
+ // the check synchronously (MODEL_REFERENCED_DELETE_ERRCODE stands for
59
+ // the ServiceException envelope family) so the blocker and the
60
+ // referencing list are visible even when a caller bypasses the tool
61
+ // precheck. The live per-column async cascade (3-4s/column) is not
62
+ // simulated — the fake removes the model synchronously.
63
+ const referencers = [
64
+ ...new Set([...ctx.state.fields.entries()]
65
+ .filter(([key, field]) => !key.startsWith(`${code}#`) && relationTargetOf(field) === code)
66
+ .map(([key]) => key.split("#")[0])),
67
+ ];
68
+ if (referencers.length > 0)
69
+ return businessRefusal(MODEL_REFERENCED_DELETE_ERRCODE, `${code}被模型 ${referencers.join(",")} 引用,不能删除`);
70
+ // Ticket 68 live fidelity: physically_delete resolves each row id as an
71
+ // AppFunction id (removeBizModel → getAppFunction(id)); a biz-model
72
+ // record id is refused with 601003 业务模型不存在.
73
+ const model = ctx.state.models.get(code);
74
+ if (model && row.id !== model.id)
75
+ return businessRefusal(601003, "业务模型不存在");
76
+ ctx.state.models.delete(code);
77
+ ctx.state.sheets.delete(code);
78
+ // The cascade drops the model's own bizproperty rows too — without
79
+ // this cleanup a deleted referencing model would keep blocking the
80
+ // referenced model's delete (dependency-inverted chains must pass).
81
+ for (const key of [...ctx.state.fields.keys()])
82
+ if (key.startsWith(`${code}#`))
83
+ ctx.state.fields.delete(key);
84
+ }
85
+ return null;
86
+ }
87
+ case "/api/api/app/bizmodels/exits_bizschema":
88
+ return ctx.state.models.has(q.schemaCode);
89
+ case "/api/api/app/bizmodels/get_summary":
90
+ // BizSchemaController.get_summary: an absent schema answers "" (ok), an
91
+ // existing one returns its summary string.
92
+ return ctx.state.models.get(q.schemaCode)?.summary ?? "";
93
+ case "/api/api/app/bizmodels/update_summary": {
94
+ // BizSchemaController.updateSummary: non-empty summary required
95
+ // (validateNotEmpty) and a missing schema answers the
96
+ // BIZ_SCHEMA_NOT_PUBLISHED errcode (ErrorCode.java:194 → 300014).
97
+ const schemaCode = String(body.schemaCode ?? "");
98
+ if (!ctx.state.models.has(schemaCode))
99
+ return businessRefusal(300014, "数据模型未发布");
100
+ const model = ctx.state.models.get(schemaCode);
101
+ model.summary = String(body.summary ?? "");
102
+ return null;
103
+ }
104
+ case "/api/api/app/bizmodels/get_all": {
105
+ const model = ctx.state.models.get(q.schemaCode);
106
+ return model ? { id: model.recordId, name: model.name, modelType: "LIST" } : null;
107
+ }
108
+ case "/api/api/app/bizmodels/update": {
109
+ // Ticket 83 (source-pinned BizSchemaController.update :266-305): the
110
+ // endpoint patches the AppFunction MENU NODE — bizSchemaCode, id, name,
111
+ // parentId are all validateNotEmpty-required; the schema RECORD is not
112
+ // migrated (get_all keeps the original name/type per probe evidence),
113
+ // so the rename lands on nodeName only.
114
+ const schemaCode = String(body.bizSchemaCode ?? body.schemaCode ?? "");
115
+ const model = ctx.state.models.get(schemaCode);
116
+ if (!model)
117
+ return businessRefusal(300014, "数据模型未发布");
118
+ const nodeId = String(body.id ?? "");
119
+ const name = String(body.name ?? "");
120
+ const parentId = String(body.parentId ?? "");
121
+ if (!nodeId)
122
+ return businessRefusal(10005, "id不能为空");
123
+ if (!name)
124
+ return businessRefusal(10005, "名称不能为空");
125
+ if (!parentId)
126
+ return businessRefusal(10005, "父id不可为空");
127
+ if (nodeId !== model.id)
128
+ return businessRefusal(10005, `id不是模型 ${schemaCode} 的应用树节点`);
129
+ model.nodeName = name;
130
+ return null;
131
+ }
132
+ default:
133
+ return NOT_HANDLED;
134
+ }
135
+ };
136
+ //# sourceMappingURL=model.js.map