@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,212 @@
1
+ /**
2
+ * Ticket 49 — the single human-summary source. Every write's plan
3
+ * ("即将…") and its result ("已…") are produced HERE from the real arguments,
4
+ * so the reviewed text and the reported text cannot diverge. Summaries are
5
+ * Chinese, name identity codes (schemaCode / field code / ruleCode / …), use
6
+ * Chinese field/control type names, and state relation mappings. They are
7
+ * ADDED beside the integrity fields (performed/readback/fingerprints) and
8
+ * never replace them.
9
+ */
10
+ /** Chinese names for the live-accepted field families (skill omc-config-draft-review §2). */
11
+ export const FIELD_TYPE_NAMES_ZH = {
12
+ SHORT_TEXT: "短文本",
13
+ LONG_TEXT: "长文本",
14
+ NUMERICAL: "数值",
15
+ DATE: "日期",
16
+ LOGICAL: "布尔",
17
+ SELECTION: "混合选人",
18
+ ATTACHMENT: "附件",
19
+ WORK_SHEET: "关联单选",
20
+ ADDRESS: "地址",
21
+ MULT_WORK_SHEET: "关联多选",
22
+ RADIO: "单选",
23
+ CHECKBOX: "复选",
24
+ DROPDOWN_BOX: "下拉",
25
+ DROPDOWN_MULTI_BOX: "下拉多选",
26
+ RELEVANCE_DATA: "引用字段",
27
+ HYPER_LINK: "超链接",
28
+ TIME: "时间",
29
+ STAFF_SELECTOR: "人员单选",
30
+ STAFF_MULTI_SELECTOR: "人员多选",
31
+ DEPARTMENT_SELECTOR: "部门单选",
32
+ DEPARTMENT_MULTI_SELECTOR: "部门多选",
33
+ FORMULA: "公式",
34
+ };
35
+ const DATA_RULE_TYPE_NAMES_ZH = {
36
+ 1: "校验规则",
37
+ 2: "计算拼接",
38
+ 3: "显示规则",
39
+ 4: "必填规则",
40
+ 5: "只读规则",
41
+ };
42
+ function stringArg(args, key) {
43
+ const value = args[key];
44
+ return typeof value === "string" && value.length > 0 ? value : undefined;
45
+ }
46
+ function fieldTypeZh(propertyType) {
47
+ const type = String(propertyType ?? "");
48
+ return FIELD_TYPE_NAMES_ZH[type] ?? (type.length > 0 ? type : "字段");
49
+ }
50
+ function choiceList(choices) {
51
+ if (!Array.isArray(choices) || choices.length === 0)
52
+ return undefined;
53
+ return `,选项:${choices.map(String).join(" / ")}`;
54
+ }
55
+ function relationClause(args) {
56
+ const relativeCode = stringArg(args, "relativeCode");
57
+ const options = args.options;
58
+ let mappings;
59
+ if (typeof options?.mappings === "string" && options.mappings.trim() && options.mappings.trim() !== "[]") {
60
+ try {
61
+ const parsed = JSON.parse(options.mappings);
62
+ if (Array.isArray(parsed) && parsed.length > 0) {
63
+ mappings = parsed.map((entry) => `${entry.source ?? "?"}→${entry.target ?? "?"}`).join("、");
64
+ }
65
+ }
66
+ catch {
67
+ mappings = undefined;
68
+ }
69
+ }
70
+ if (!relativeCode)
71
+ return "";
72
+ return `,关联 ${relativeCode}${mappings ? `,带出 ${mappings}` : ""}`;
73
+ }
74
+ function nodeCountOf(nodeGraph) {
75
+ if (Array.isArray(nodeGraph?.nodes))
76
+ return nodeGraph.nodes.length;
77
+ if (typeof nodeGraph?.nodes === "string") {
78
+ try {
79
+ const parsed = JSON.parse(nodeGraph.nodes);
80
+ if (Array.isArray(parsed))
81
+ return parsed.length;
82
+ }
83
+ catch {
84
+ return undefined;
85
+ }
86
+ }
87
+ return undefined;
88
+ }
89
+ function noChange(outcome) {
90
+ return typeof outcome?.performed?.skipped === "string";
91
+ }
92
+ /**
93
+ * The one human phrase for a write. `phase:"plan"` prefixes 即将 (approval
94
+ * card), `phase:"done"` prefixes 已 (result). Skipped no-op writes surface as
95
+ * 无变更 instead of pretending something changed.
96
+ */
97
+ export function writeSummary(tool, args, outcome, phase = "done") {
98
+ const phrase = phraseOf(tool, args, outcome);
99
+ // An explicitly requested cross-application target is named in BOTH the
100
+ // plan and the result (same source, no divergence).
101
+ const app = typeof args.applicationCode === "string" && args.applicationCode ? `(应用 ${args.applicationCode})` : "";
102
+ const prefix = phase === "plan" ? "即将" : "已";
103
+ if (noChange(outcome))
104
+ return `${prefix}${phrase}${app}(无变更:${String(outcome?.performed?.skipped)})`;
105
+ return `${prefix}${phrase}${app}`;
106
+ }
107
+ function phraseOf(tool, args, outcome) {
108
+ const schemaCode = stringArg(args, "schemaCode");
109
+ switch (tool) {
110
+ case "model.create":
111
+ return `创建模型 ${stringArg(args, "name") ?? ""}(${stringArg(args, "code") ?? "?"}),${args.modelType === "TREE" ? "树形" : "列表"}型`;
112
+ case "model.delete":
113
+ return `删除模型 ${schemaCode ?? stringArg(args, "code") ?? "?"}`;
114
+ case "model.deleteMany": {
115
+ const codes = Array.isArray(args.schemaCodes) ? args.schemaCodes.map(String) : [];
116
+ return `删除 ${codes.length} 个模型:${codes.join("、")}`;
117
+ }
118
+ case "field.create": {
119
+ const target = stringArg(args, "schemaCode") ?? "?";
120
+ return `创建字段 ${stringArg(args, "name") ?? ""}(${stringArg(args, "code") ?? "?"}),${fieldTypeZh(args.propertyType)},挂在 ${target}${relationClause(args)}${choiceList(args.choices) ?? ""}`;
121
+ }
122
+ case "field.update":
123
+ return `更新字段 ${stringArg(args, "code") ?? "?"} 的配置(${schemaCode ?? "?"})`;
124
+ case "field.delete":
125
+ return `删除字段 ${stringArg(args, "code") ?? "?"}(${schemaCode ?? "?"})`;
126
+ case "field.publish":
127
+ return `发布模型 ${schemaCode ?? "?"} 的全部草稿字段(模型级一次发布)`;
128
+ case "form.draft":
129
+ return `保存表单 ${stringArg(args, "sheetCode") ?? "?"}(模型 ${schemaCode ?? "?"})的草稿布局`;
130
+ case "form.publish":
131
+ return `发布表单 ${stringArg(args, "sheetCode") ?? "?"}(模型 ${schemaCode ?? "?"})`;
132
+ case "configure_model": {
133
+ const fields = args.fields;
134
+ const count = Array.isArray(fields) ? fields.length : stringArg(args, "fieldCode") ? 1 : 0;
135
+ return `创建模型 ${stringArg(args, "name") ?? ""}(${stringArg(args, "code") ?? "?"})并发布 ${count} 个字段${args.publishForm === true ? ",默认表单已发布" : ""}`;
136
+ }
137
+ case "configure_form": {
138
+ const codes = args.fieldCodes;
139
+ return `配置并发布表单 ${stringArg(args, "sheetCode") ?? "?"}(模型 ${schemaCode ?? "?"})${Array.isArray(codes) && codes.length > 0 ? `,绑定字段 ${codes.map(String).join("、")}` : "(绑定全部已发布字段)"}`;
140
+ }
141
+ case "configure_rule":
142
+ return `保存并启用业务规则 ${stringArg(args, "name") ?? ""}(${stringArg(args, "ruleCode") ?? "?"},模型 ${schemaCode ?? "?"})`;
143
+ case "rule.save": {
144
+ const nodes = nodeCountOf(args.nodeGraph);
145
+ return `保存业务规则 ${stringArg(args, "name") ?? stringArg(args, "ruleCode") ?? "?"}(${stringArg(args, "ruleCode") ?? "?"},模型 ${schemaCode ?? "?"}${nodes ? `,图 ${nodes} 节点` : ""})`;
146
+ }
147
+ case "rule.enable":
148
+ return `启用业务规则 ${stringArg(args, "ruleCode") ?? "?"}(${schemaCode ?? "?"})`;
149
+ case "rule.disable":
150
+ return `停用业务规则 ${stringArg(args, "ruleCode") ?? "?"}(${schemaCode ?? "?"})`;
151
+ case "rule.delete":
152
+ return `删除业务规则 ${stringArg(args, "ruleCode") ?? "?"}(${schemaCode ?? "?"})`;
153
+ case "datarule.create":
154
+ case "datarule.update": {
155
+ const type = DATA_RULE_TYPE_NAMES_ZH[Number(args.dataRuleType)] ?? `类型${String(args.dataRuleType ?? "?")}`;
156
+ return `${tool === "datarule.create" ? "创建" : "更新"}字段规则(${stringArg(args, "propertyCode") ?? "?"},${type}),挂在 ${schemaCode ?? "?"}`;
157
+ }
158
+ case "datarule.delete":
159
+ return `删除字段规则 ${stringArg(args, "id") ?? "?"}(${schemaCode ?? "?"})`;
160
+ case "workflow.create":
161
+ return `创建流程 ${stringArg(args, "name") ?? ""}(${stringArg(args, "code") ?? "?"},模型 ${schemaCode ?? "?"})`;
162
+ case "workflow.update":
163
+ return `更新流程模板 ${stringArg(args, "code") ?? "?"}(${schemaCode ?? "?"})`;
164
+ case "workflow.publish":
165
+ return `发布流程 ${stringArg(args, "code") ?? "?"}(${schemaCode ?? "?"})`;
166
+ case "workflow.bindRule": {
167
+ const codes = Array.isArray(args.ruleCodes) ? args.ruleCodes.join("、") : stringArg(args, "ruleCode") ?? "?";
168
+ return `绑定流程事件规则:${stringArg(args, "workflowCode") ?? "?"} 的 ${stringArg(args, "handler") ?? "end"} 处理器 ← ${codes}`;
169
+ }
170
+ case "workflow.delete":
171
+ return `删除流程 ${stringArg(args, "code") ?? "?"}`;
172
+ case "onlinejs.compose":
173
+ return `编排并发布表单脚本 customScript(模型 ${schemaCode ?? "?"} / 表单 ${stringArg(args, "sheetCode") ?? "?"})`;
174
+ case "service.register":
175
+ return `注册后端服务 ${stringArg(args, "serviceCode") ?? "?"}`;
176
+ case "service.delete":
177
+ return `删除后端服务 ${stringArg(args, "serviceCode") ?? "?"}`;
178
+ case "method.delete":
179
+ return `删除业务集成方法 ${stringArg(args, "methodCode") ?? "?"}(服务 ${stringArg(args, "serviceCode") ?? "?"})`;
180
+ case "data.save":
181
+ return args.submit === true
182
+ ? `保存数据(模型 ${schemaCode ?? "?"})——submit:true 不走表单提交管线:行落 DRAFT、预置 Create/Update 规则不派发。要建行并触发规则:直接调 data.submit(不带 bizObjectId=新建并提交,行 COMPLETED、规则派发,r5-crm 实证 2026-10-09);data.save 仅用于存草稿`
183
+ : `保存数据(模型 ${schemaCode ?? "?"})`;
184
+ case "data.update":
185
+ return `更新数据 ${stringArg(args, "bizObjectId") ?? "?"}(${schemaCode ?? "?"})`;
186
+ case "data.submit":
187
+ return `提交数据(模型 ${schemaCode ?? "?"}${stringArg(args, "workItemId") ? ",审批待办" : ""})`;
188
+ case "data.delete": {
189
+ const ids = Array.isArray(args.bizObjectIds) ? args.bizObjectIds.map(String) : [];
190
+ const target = ids.length > 0 ? `${ids.length} 条数据(${ids.join("、")})` : (stringArg(args, "bizObjectId") ?? "?");
191
+ return `删除数据 ${target}(${schemaCode ?? "?"})`;
192
+ }
193
+ case "listview.create":
194
+ return `创建列表视图 ${stringArg(args, "name") ?? ""}(${stringArg(args, "code") ?? schemaCode ?? "?"},模型 ${schemaCode ?? "?"})`;
195
+ case "listview.configure": {
196
+ const codes = Array.isArray(args.fieldCodes) ? args.fieldCodes.map(String).join("、") : "";
197
+ const sections = ["sorts", "conditions", "actions", "buttons", "presentation", "options", "presentationType", "gantt", "queryLayer", "multiTableHeaderConfig"].filter((key) => args[key] !== undefined);
198
+ return `配置列表视图 ${stringArg(args, "code") ?? schemaCode ?? "?"}(模型 ${schemaCode ?? "?"}):列 ${codes || "(未变)"}${sections.length > 0 ? `;同时配置 ${sections.join("、")}` : ""}`;
199
+ }
200
+ case "listview.delete":
201
+ return `删除列表视图 ${stringArg(args, "code") ?? "?"}(${schemaCode ?? "?"})`;
202
+ case "app.create":
203
+ return `创建应用 ${stringArg(args, "name") ?? ""}(${stringArg(args, "code") ?? "?"})`;
204
+ case "app.update":
205
+ return `更新应用 ${stringArg(args, "code") ?? "?"}${stringArg(args, "groupId") ? `(分组 → ${stringArg(args, "groupId")})` : ""}`;
206
+ case "app.delete":
207
+ return `删除应用 ${stringArg(args, "code") ?? "?"}`;
208
+ default:
209
+ return `执行 ${tool}`;
210
+ }
211
+ }
212
+ //# sourceMappingURL=human-summary.js.map
@@ -0,0 +1,62 @@
1
+ import { ENDPOINTS } from "./endpoints.js";
2
+ import { wireCall, wireRows } from "./wire.js";
3
+ /**
4
+ * Identity seam (round 10, ergonomics).
5
+ *
6
+ * One object carries several names across tools: a model is `schemaCode`, a
7
+ * field is `code`/`propertyCode`, a business-rule row's wire key is `id` while
8
+ * its business key is `(schemaCode, ruleCode)`, a workflow is `workflowCode`
9
+ * while the caller only knows the model's `schemaCode`. Today that forces a
10
+ * `list → get` dance and per-lane key memorisation.
11
+ *
12
+ * These resolvers let a tool accept the BUSINESS key and resolve the wire
13
+ * identity internally. They are read-only and return `null` for a genuine miss,
14
+ * so the caller can fail closed with a precise message.
15
+ */
16
+ /** Resolve a rule ROW id from `(schemaCode, ruleCode)`. Reuses the list face. */
17
+ export async function resolveRuleRowId(session, schemaCode, ruleCode) {
18
+ const rows = wireRows((await wireCall(session, { path: ENDPOINTS.businessRuleListBySchemaCode, query: { schemaCode } })).data);
19
+ const match = rows.find((row) => row.code === ruleCode);
20
+ return match?.id !== undefined && match.id !== null && String(match.id) ? String(match.id) : null;
21
+ }
22
+ /**
23
+ * Resolve a data-rule ROW from `(schemaCode, propertyCode[, dataRuleType])`.
24
+ * `bizdatarule` rows are keyed physically by `id`; the business locator is the
25
+ * owning field code (frontend matches `(propertyCodes||[propertyCode])`) plus the
26
+ * rule type. The caller gets the full row so it can also patch it.
27
+ */
28
+ export async function resolveDataRuleRow(session, schemaCode, locator) {
29
+ if (locator.id)
30
+ return { ok: true, row: { id: locator.id, schemaCode } };
31
+ const query = { schemaCode };
32
+ if (locator.dataRuleType !== undefined)
33
+ query.dataRuleType = String(locator.dataRuleType);
34
+ const rows = wireRows((await wireCall(session, { path: ENDPOINTS.dataRuleList, query })).data);
35
+ const wanted = new Set([...(locator.propertyCode ? [locator.propertyCode] : []), ...(locator.propertyCodes ?? [])]);
36
+ const matches = rows.filter((row) => {
37
+ const codes = [...(Array.isArray(row.propertyCodes) ? row.propertyCodes.map(String) : []), ...(typeof row.propertyCode === "string" ? [row.propertyCode] : [])];
38
+ const codeHit = wanted.size === 0 ? true : codes.some((code) => wanted.has(code));
39
+ const typeHit = locator.dataRuleType === undefined ? true : Number(row.dataRuleType) === locator.dataRuleType;
40
+ return codeHit && typeHit;
41
+ });
42
+ // Ambiguous locator (several rows share the field, e.g. required + readonly):
43
+ // the caller must disambiguate with dataRuleType, so we refuse rather than
44
+ // pick one silently.
45
+ if (matches.length !== 1)
46
+ return { ok: false, reason: matches.length === 0 ? "absent" : "ambiguous", count: matches.length };
47
+ return { ok: true, row: matches[0] };
48
+ }
49
+ /**
50
+ * Resolve the published workflowCode for a model. The platform knows the link
51
+ * (`workflow/isworkflow` → `workflow/getWorkflow`); the CLI never surfaced it as
52
+ * a tool, so a caller holding only `schemaCode` was stuck.
53
+ */
54
+ export async function resolveWorkflowCodeBySchema(session, schemaCode) {
55
+ const isWorkflow = (await wireCall(session, { path: ENDPOINTS.workflowIsWorkflow, query: { schemaCode } })).data === true;
56
+ if (!isWorkflow)
57
+ return null;
58
+ const template = (await wireCall(session, { path: ENDPOINTS.workflowGetBySchema, query: { schemaCode } })).data;
59
+ const code = template && typeof template.workflowCode === "string" && template.workflowCode.length > 0 ? template.workflowCode : null;
60
+ return code;
61
+ }
62
+ //# sourceMappingURL=identity.js.map
@@ -0,0 +1,57 @@
1
+ import { resolveModelTreeNode } from "../lanes/model.js";
2
+ import { canonicalJson } from "./dsl.js";
3
+ import { sha256 } from "../support.js";
4
+ import { capabilitiesForModel, orderedCapabilities } from "./capabilities/index.js";
5
+ const absentForm = { present: false, published: false, existDraft: false, boundFieldCodes: [] };
6
+ const absentListview = { present: false, columns: [] };
7
+ /** Read the baseline scope of an intent (its models, fields, default forms). */
8
+ export async function readBaseline(session, intent) {
9
+ const models = [];
10
+ const seenTreeModels = new Set();
11
+ const intentCodes = new Set(intent.models.map((model) => model.code));
12
+ const capabilities = orderedCapabilities();
13
+ for (const model of intent.models) {
14
+ const resolved = await resolveModelTreeNode(session, model.code, undefined);
15
+ const present = resolved.node !== undefined;
16
+ for (const row of resolved.treeRows)
17
+ if (row.type === "BizModel" && typeof row.code === "string")
18
+ seenTreeModels.add(row.code);
19
+ const nodeName = typeof resolved.node?.name === "string" && resolved.node.name.length > 0 ? resolved.node.name : null;
20
+ const draft = { code: model.code, name: nodeName, present, fields: [], form: absentForm, listview: absentListview, rules: [], datarules: [] };
21
+ // Only read model-scoped capabilities when the model exists. On a real
22
+ // platform an absent model makes the sub-reads error (bizproperty/list →
23
+ // 300004 "数据模型不存在!", bizdatarule/list → 601003 "业务模型不存在") and
24
+ // readBaseline would abort a fresh-model plan; the fake returns empty, which
25
+ // hid this. Absent model ⇒ nothing to read.
26
+ if (present)
27
+ for (const capability of capabilitiesForModel(model))
28
+ if (capability.read)
29
+ await capability.read(session, model, draft);
30
+ // Omit an empty rules array: a model without declared rules stays
31
+ // byte-identical to the pre-rule baseline (keeps plans/hashes unchanged).
32
+ const { rules, datarules, ...rest } = draft;
33
+ models.push({ ...rest, ...(rules.length > 0 ? { rules } : {}), ...(datarules.length > 0 ? { datarules } : {}) });
34
+ }
35
+ const services = [];
36
+ let reportRows = [];
37
+ for (const capability of capabilities) {
38
+ if (!capability.readRoot)
39
+ continue;
40
+ const rows = await capability.readRoot(session, intent);
41
+ if (capability.id === "report")
42
+ reportRows = rows;
43
+ else
44
+ services.push(...rows);
45
+ }
46
+ return {
47
+ models,
48
+ ...(services.length > 0 ? { services } : {}),
49
+ ...(reportRows.length > 0 ? { reports: reportRows } : {}),
50
+ outsideModels: [...seenTreeModels].filter((code) => !intentCodes.has(code)).sort(),
51
+ };
52
+ }
53
+ /** Stable fingerprint of the baseline scope: any drift changes the hash. */
54
+ export function baselineFingerprintOf(baseline) {
55
+ return sha256({ scope: "omc-intent-baseline-v1", baseline: JSON.parse(canonicalJson(baseline)) });
56
+ }
57
+ //# sourceMappingURL=baseline.js.map
@@ -0,0 +1,274 @@
1
+ import { ENDPOINTS } from "../../endpoints.js";
2
+ import { assertBizServiceAdapterWhitelisted, compileMethodConfigPlain, compileParameterFace, compileServiceConfig, normalizeMethodConfigReadback, normalizeParameterFaceReadback } from "../../bizservice-config.js";
3
+ import { wireCall, wireRows, WireError } from "../../wire.js";
4
+ import { refuse } from "../capability.js";
5
+ import { sameValue } from "../compare.js";
6
+ import { isScalarString, pushError } from "../validation.js";
7
+ import { toPlain } from "../yaml.js";
8
+ import { errorDetail } from "../../tool-types.js";
9
+ const SERVICE_KEYS = new Set(["code", "name", "adapterCode", "serviceCategoryId", "serviceConfig", "method"]);
10
+ const METHOD_KEYS = new Set(["code", "name", "methodConfig", "inputParameters", "outputParameters"]);
11
+ function configError(error) {
12
+ return errorDetail(error);
13
+ }
14
+ function compileService(service) {
15
+ assertBizServiceAdapterWhitelisted(service.adapterCode);
16
+ const serviceConfig = compileServiceConfig(service.adapterCode, service.serviceConfig);
17
+ if (!service.method)
18
+ return { serviceConfig };
19
+ const methodConfig = service.method.methodConfig === undefined ? undefined : compileMethodConfigPlain(service.adapterCode, service.method.methodConfig);
20
+ const input = service.method.inputParameters === undefined ? undefined : compileParameterFace(service.adapterCode, service.method.inputParameters, "methodInputParameters");
21
+ const output = service.method.outputParameters === undefined ? undefined : compileParameterFace(service.adapterCode, service.method.outputParameters, "methodOutputParameters");
22
+ return { serviceConfig, ...(methodConfig !== undefined ? { methodConfig } : {}), ...(input !== undefined ? { input } : {}), ...(output !== undefined ? { output } : {}) };
23
+ }
24
+ function serviceArgs(service) {
25
+ compileService(service);
26
+ return {
27
+ serviceCode: service.code,
28
+ name: service.name,
29
+ adapterCode: service.adapterCode,
30
+ serviceCategoryId: service.serviceCategoryId,
31
+ serviceConfig: service.serviceConfig,
32
+ ...(service.method ? {
33
+ methodName: service.method.code,
34
+ methodConfig: service.method.methodConfig,
35
+ ...(service.method.inputParameters ? { methodInputParameters: service.method.inputParameters } : {}),
36
+ ...(service.method.outputParameters ? { methodOutputParameters: service.method.outputParameters } : {}),
37
+ } : {}),
38
+ };
39
+ }
40
+ /**
41
+ * Expected canonical method config: the intent carries the DECLARED form, but
42
+ * the write face compiles it (enum→index, RESTFUL connectTime/retryTimes
43
+ * defaults) and the readback normalizes it back. Projecting the intent through
44
+ * the SAME compiler + readback normalization makes the drift comparison
45
+ * apples-to-apples (otherwise compiler-added canonical keys read as drift).
46
+ */
47
+ function expectedMethodConfig(adapterCode, methodConfig) {
48
+ if (methodConfig === undefined)
49
+ return undefined;
50
+ return normalizeMethodConfigReadback(adapterCode, JSON.parse(compileMethodConfigPlain(adapterCode, methodConfig)));
51
+ }
52
+ /** Expected canonical parameter face: intent → compile → readback-normalize (mirrors the stored row projection). */
53
+ function expectedParameterFace(adapterCode, parameters, label) {
54
+ if (parameters === undefined)
55
+ return undefined;
56
+ return normalizeParameterFaceReadback(adapterCode, JSON.parse(compileParameterFace(adapterCode, parameters, label)));
57
+ }
58
+ function serviceFromRow(service, method, adapterCode) {
59
+ const decode = (value) => {
60
+ if (typeof value !== "string")
61
+ return value;
62
+ try {
63
+ return JSON.parse(value);
64
+ }
65
+ catch {
66
+ return value;
67
+ }
68
+ };
69
+ const methodFace = method ? {
70
+ code: String(method.code ?? ""),
71
+ present: true,
72
+ name: typeof method.name === "string" ? method.name : null,
73
+ methodConfig: normalizeMethodConfigReadback(adapterCode, decode(method.configJson)),
74
+ inputParameters: normalizeParameterFaceReadback(adapterCode, decode(method.inputParametersJson)),
75
+ outputParameters: normalizeParameterFaceReadback(adapterCode, decode(method.outputParametersJson)),
76
+ } : undefined;
77
+ return {
78
+ code: String(service?.code ?? ""),
79
+ present: service !== null,
80
+ name: typeof service?.name === "string" ? service.name : null,
81
+ adapterCode: typeof service?.adapterCode === "string" ? service.adapterCode : null,
82
+ serviceConfig: decode(service?.configJson),
83
+ ...(methodFace ? { method: methodFace } : {}),
84
+ };
85
+ }
86
+ function validateServiceNode(serviceNode, index, draft, validation) {
87
+ const base = `services[${index}]`;
88
+ for (const entry of serviceNode.entries)
89
+ if (!SERVICE_KEYS.has(entry.key))
90
+ pushError(validation, "intent-key-unknown", `service 不支持键 "${entry.key}"`, `${base}.${entry.key}`);
91
+ const value = (key) => serviceNode.entries.find((entry) => entry.key === key)?.value;
92
+ const codeNode = value("code");
93
+ const nameNode = value("name");
94
+ const adapterNode = value("adapterCode");
95
+ const categoryNode = value("serviceCategoryId");
96
+ const configNode = value("serviceConfig");
97
+ const methodNode = value("method");
98
+ if (!isScalarString(codeNode) || codeNode.value.trim().length === 0)
99
+ pushError(validation, "intent-service-code-invalid", "service.code 必须是非空字符串", `${base}.code`, "value");
100
+ if (!isScalarString(nameNode) || nameNode.value.trim().length === 0)
101
+ pushError(validation, "intent-service-name-invalid", "service.name 必须是非空字符串", `${base}.name`, "value");
102
+ if (!isScalarString(adapterNode) || adapterNode.value.trim().length === 0)
103
+ pushError(validation, "intent-service-adapter-invalid", "service.adapterCode 必须是非空字符串", `${base}.adapterCode`, "value");
104
+ if (!isScalarString(categoryNode) || categoryNode.value.trim().length === 0)
105
+ pushError(validation, "intent-service-category-invalid", "service.serviceCategoryId 必须是非空字符串", `${base}.serviceCategoryId`, "value");
106
+ if (configNode?.kind !== "map")
107
+ pushError(validation, "intent-service-config-invalid", "service.serviceConfig 必须是映射", `${base}.serviceConfig`, "value");
108
+ if (methodNode !== undefined && methodNode.kind !== "map")
109
+ pushError(validation, "intent-service-method-invalid", "service.method 必须是映射", `${base}.method`, "value");
110
+ if (!isScalarString(codeNode) || !isScalarString(nameNode) || !isScalarString(adapterNode) || !isScalarString(categoryNode) || configNode?.kind !== "map")
111
+ return;
112
+ const serviceConfig = toPlain(configNode);
113
+ let method;
114
+ try {
115
+ const methodMap = methodNode?.kind === "map" ? methodNode : undefined;
116
+ if (methodMap) {
117
+ for (const entry of methodMap.entries)
118
+ if (!METHOD_KEYS.has(entry.key))
119
+ pushError(validation, "intent-key-unknown", `service.method 不支持键 "${entry.key}"`, `${base}.method.${entry.key}`);
120
+ const methodValue = (key) => methodMap.entries.find((entry) => entry.key === key)?.value;
121
+ const methodCode = methodValue("code");
122
+ const methodName = methodValue("name");
123
+ const methodConfig = methodValue("methodConfig");
124
+ const input = methodValue("inputParameters");
125
+ const output = methodValue("outputParameters");
126
+ if (!isScalarString(methodCode) || methodCode.value.trim().length === 0)
127
+ pushError(validation, "intent-method-code-invalid", "service.method.code 必须是非空字符串", `${base}.method.code`, "value");
128
+ if (!isScalarString(methodName) || methodName.value.trim().length === 0)
129
+ pushError(validation, "intent-method-name-invalid", "service.method.name 必须是非空字符串", `${base}.method.name`, "value");
130
+ if (methodConfig !== undefined && methodConfig.kind !== "map")
131
+ pushError(validation, "intent-method-config-invalid", "service.method.methodConfig 必须是映射", `${base}.method.methodConfig`, "value");
132
+ if (input !== undefined && input.kind !== "seq")
133
+ pushError(validation, "intent-method-input-invalid", "service.method.inputParameters 必须是列表", `${base}.method.inputParameters`, "value");
134
+ if (output !== undefined && output.kind !== "seq")
135
+ pushError(validation, "intent-method-output-invalid", "service.method.outputParameters 必须是列表", `${base}.method.outputParameters`, "value");
136
+ if (isScalarString(methodCode) && isScalarString(methodName)) {
137
+ if (methodCode.value.trim() !== methodName.value.trim())
138
+ pushError(validation, "intent-method-name-code-mismatch", "当前 service.register 面 methodName 同时作为方法 code/name;method.name 必须等于 method.code", `${base}.method.name`, "value");
139
+ method = {
140
+ code: methodCode.value.trim(),
141
+ name: methodName.value.trim(),
142
+ ...(methodConfig?.kind === "map" ? { methodConfig: toPlain(methodConfig) } : {}),
143
+ ...(input?.kind === "seq" ? { inputParameters: input.items.map((item) => toPlain(item)) } : {}),
144
+ ...(output?.kind === "seq" ? { outputParameters: output.items.map((item) => toPlain(item)) } : {}),
145
+ };
146
+ }
147
+ }
148
+ compileService({ code: codeNode.value.trim(), name: nameNode.value.trim(), adapterCode: adapterNode.value.trim(), serviceCategoryId: categoryNode.value.trim(), serviceConfig, ...(method ? { method } : {}) });
149
+ }
150
+ catch (error) {
151
+ pushError(validation, "intent-service-config-unsupported", configError(error), base, "value");
152
+ return;
153
+ }
154
+ draft.services.push({ code: codeNode.value.trim(), name: nameNode.value.trim(), adapterCode: adapterNode.value.trim(), serviceCategoryId: categoryNode.value.trim(), serviceConfig, ...(method ? { method } : {}) });
155
+ }
156
+ export const bizserviceCapability = {
157
+ id: "bizservice",
158
+ title: "业务集成",
159
+ requires: [],
160
+ operations: ["register"],
161
+ validateRoot(tree, draft, validation) {
162
+ const servicesNode = tree.entries.find((entry) => entry.key === "services")?.value;
163
+ if (servicesNode === undefined || (servicesNode.kind === "scalar" && servicesNode.value === null))
164
+ return;
165
+ if (servicesNode.kind !== "seq") {
166
+ pushError(validation, "intent-services-not-a-list", "services 必须是列表", "services", "value");
167
+ return;
168
+ }
169
+ const seen = new Set();
170
+ servicesNode.items.forEach((node, index) => {
171
+ if (node.kind !== "map") {
172
+ pushError(validation, "intent-service-not-a-map", "service 必须是映射", `services[${index}]`, "value");
173
+ return;
174
+ }
175
+ const before = draft.services.length;
176
+ validateServiceNode(node, index, draft, validation);
177
+ const added = draft.services[before];
178
+ if (added && seen.has(added.code))
179
+ pushError(validation, "intent-service-code-duplicate", `service code 重复:${added.code}`, `services[${index}].code`, "value");
180
+ if (added)
181
+ seen.add(added.code);
182
+ });
183
+ },
184
+ async readRoot(session, intent) {
185
+ const services = intent.services ?? [];
186
+ const result = [];
187
+ for (const service of services) {
188
+ let row = null;
189
+ try {
190
+ row = (await wireCall(session, { path: ENDPOINTS.bizServiceGetByCode, query: { serviceCode: service.code } })).data;
191
+ }
192
+ catch (error) {
193
+ if (!(error instanceof WireError && error.kind === "business" && error.errcode === 500001))
194
+ throw error;
195
+ }
196
+ if (!row) {
197
+ result.push({ code: service.code, present: false, name: null, adapterCode: null });
198
+ continue;
199
+ }
200
+ let method;
201
+ if (service.method) {
202
+ const rows = wireRows((await wireCall(session, { path: ENDPOINTS.bizServiceMethodList, query: { serviceCode: service.code, filterFlag: "false" } })).data);
203
+ method = rows.find((candidate) => candidate.code === service.method.code);
204
+ }
205
+ result.push(serviceFromRow(row, method, String(row.adapterCode ?? service.adapterCode)));
206
+ }
207
+ return result;
208
+ },
209
+ planRoot(intent, baseline, emit) {
210
+ const services = intent.services ?? [];
211
+ const current = baseline.services ?? [];
212
+ for (const service of services) {
213
+ const live = current.find((entry) => entry.code === service.code);
214
+ const args = serviceArgs(service);
215
+ const resource = `service:${service.code}`;
216
+ if (!live?.present) {
217
+ emit.change("service.register", args, resource, "service-absent");
218
+ continue;
219
+ }
220
+ // An existing service is patched, not recreated. adapterCode is fixed at
221
+ // creation (the update face derives it from the stored row), so a declared
222
+ // adapter change is a genuine refusal.
223
+ if (live.adapterCode !== service.adapterCode)
224
+ refuse("intent-service-adapter-immutable", `业务服务 ${service.code} 的 adapterCode 已固定为 ${live.adapterCode ?? "(未知)"},意图声明 ${service.adapterCode}——adapter 创建后不可迁移;修正意图或走 omc call`, { resource });
225
+ if (live.name !== service.name || !sameValue(live.serviceConfig, service.serviceConfig))
226
+ emit.change("service.update", { serviceCode: service.code, name: service.name, serviceConfig: service.serviceConfig }, resource, "service-drift");
227
+ else
228
+ emit.skip(resource, "service-present");
229
+ if (!service.method)
230
+ continue;
231
+ const methodResource = `method:${service.code}.${service.method.code}`;
232
+ if (!live.method?.present)
233
+ // The low-level face has method.update/delete but no method.create, and
234
+ // method.update refuses an absent method — adding a method to an
235
+ // existing service has no declarative tool. Refuse with the path.
236
+ refuse("intent-method-create-unsupported", `业务服务 ${service.code} 已存在但没有方法 ${service.method.code};当前面没有声明式“加方法”能力(method.update 要求方法已存在)。删除服务后重建,或走 omc call`, { resource: methodResource });
237
+ if (live.method.name !== service.method.name || !sameValue(live.method.methodConfig, expectedMethodConfig(service.adapterCode, service.method.methodConfig)) || !sameValue(live.method.inputParameters, expectedParameterFace(service.adapterCode, service.method.inputParameters, "methodInputParameters")) || !sameValue(live.method.outputParameters, expectedParameterFace(service.adapterCode, service.method.outputParameters, "methodOutputParameters")))
238
+ emit.change("method.update", {
239
+ serviceCode: service.code,
240
+ methodCode: service.method.code,
241
+ name: service.method.name,
242
+ ...(service.method.methodConfig !== undefined ? { methodConfig: service.method.methodConfig } : {}),
243
+ ...(service.method.inputParameters !== undefined ? { methodInputParameters: service.method.inputParameters } : {}),
244
+ ...(service.method.outputParameters !== undefined ? { methodOutputParameters: service.method.outputParameters } : {}),
245
+ }, methodResource, "method-drift");
246
+ else
247
+ emit.skip(methodResource, "method-present");
248
+ }
249
+ },
250
+ decodeRoot(baseline, live) {
251
+ if (baseline.services !== undefined)
252
+ live.services = baseline.services.map((service) => ({ ...service }));
253
+ },
254
+ compareRoot(intent, live, emit) {
255
+ for (const service of intent.services ?? []) {
256
+ const current = live.services?.find((entry) => entry.code === service.code);
257
+ const resource = `service:${service.code}`;
258
+ if (!current?.present) {
259
+ emit.difference("service-missing", resource, `业务服务 ${service.code} 不存在`);
260
+ continue;
261
+ }
262
+ if (current.name !== service.name || current.adapterCode !== service.adapterCode || !sameValue(current.serviceConfig, service.serviceConfig))
263
+ emit.difference("service-mismatch", resource, `业务服务 ${service.code} 的名称/适配器/configJson 不等价`);
264
+ if (service.method) {
265
+ const method = current.method;
266
+ if (!method?.present)
267
+ emit.difference("method-missing", `method:${service.code}.${service.method.code}`, `业务方法 ${service.code}/${service.method.code} 不存在`);
268
+ else if (method.name !== service.method.name || !sameValue(method.methodConfig, expectedMethodConfig(service.adapterCode, service.method.methodConfig)) || !sameValue(method.inputParameters, expectedParameterFace(service.adapterCode, service.method.inputParameters, "methodInputParameters")) || !sameValue(method.outputParameters, expectedParameterFace(service.adapterCode, service.method.outputParameters, "methodOutputParameters")))
269
+ emit.difference("method-mismatch", `method:${service.code}.${service.method.code}`, `业务方法 ${service.code}/${service.method.code} 不等价`);
270
+ }
271
+ }
272
+ },
273
+ };
274
+ //# sourceMappingURL=bizservice.js.map