@stalfh233/omc-cli 0.4.100 → 0.4.102
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.
- package/README.md +13 -1
- package/coverage/m1-coverage-manifest-v1.json +9 -3
- package/dist/cli-contract.js +5 -1
- package/dist/cli.js +1 -1
- package/dist/compound-writes.js +16 -5
- package/dist/discovery.js +7 -0
- package/dist/field-change.js +11 -6
- package/dist/field-families.js +60 -2
- package/dist/goal-routes.js +69 -0
- package/dist/governed-execution.js +1 -1
- package/dist/lanes/b2-registration.js +20 -3
- package/dist/lanes/businessrule-contract.js +4 -0
- package/dist/lanes/businessrule-coverage.js +1 -1
- package/dist/lanes/businessrule.js +93 -0
- package/dist/lanes/datarule-protocol.js +100 -0
- package/dist/lanes/field.js +29 -3
- package/dist/lanes/form-contract.js +1 -1
- package/dist/lanes/form.js +2 -2
- package/dist/lanes/online-js.js +19 -1
- package/dist/lanes/workflow.js +41 -6
- package/dist/online-js-source.js +7 -2
- package/dist/recipes/b2-registration.js +8 -7
- package/dist/recipes/businessrule.js +59 -1
- package/dist/rule-support.js +96 -0
- package/docs/images/omc-nixi/README.md +41 -0
- package/docs/omc-introduction.md +851 -0
- package/package.json +4 -4
- package/skills/omc-capabilities.md +3 -3
- package/skills/omc-refusals.md +1 -1
- package/skills/omc-tool-selection.md +1 -1
- package/skills/omc-write-hazards.md +14 -0
package/README.md
CHANGED
|
@@ -28,6 +28,9 @@
|
|
|
28
28
|
- **应用目录(分组)**——`folder.tree/create/update/delete` + `model.move`;
|
|
29
29
|
应用树是嵌套 `children`,读面已递归展平并给 `path`/`depth`。
|
|
30
30
|
- **方法论即可读**——`skills/` 下的方法论通过 `omc help <topic>` 按需读取;agent 不必去翻仓库文件。
|
|
31
|
+
- **一次调用就是答案(0.4.100)**——`omc help --search "<词>"` 跨全部 topic 全文检索、命中节正文内联(严禁自己 `--all | grep`);`omc describe <tool>` 默认带 `firstCall` 最小可跑形状(上下文键占位符化并列入 missing,绝不冒充合法值);`omc next "<目标>"` 首答只给一个可执行动作或一个问题(`firstAnswer`);`omc commands --filter` 支持能力语言(聚合/字典/业务集成…),零命中回 ≤3 个最近候选。
|
|
32
|
+
- **拒绝即教学**——写前拒绝信封自带「你错在哪、正确形状、同源配方命令、不要做什么」;平台/权限/环境类只给「先修什么」,不给注定失败的重试命令。
|
|
33
|
+
- **复合写命令**——`omc submit-verify`(逐条提交 → 真实 id 读回 → 差异汇总)、`omc rule-save-enable`(保存成功才启用,回执回答「会不会真的运行」)、`omc workflow-update-publish`(显式合成最终模板:默认保留旧节点,`--confirm-replace` 才丢弃)。全部默认 dry-run、第一步失败立即停、不自动重试。
|
|
31
34
|
- **凭据零回显**——凭据(0600 存储或环境变量)不会出现在任何输出、产物或错误里。
|
|
32
35
|
- **不确定性语义**——传输失败时冻结(`status: "uncertain", frozen: true`),
|
|
33
36
|
绝不伪造成功。
|
|
@@ -130,10 +133,19 @@ omc call discover_environment --env <env> --args '{}' # 环境就绪(真实
|
|
|
130
133
|
- **intent**:`partial` 表示该能力可声明式(`omc intent`)覆盖一部分——通常 create/update;
|
|
131
134
|
`none` 表示只走 `omc call`。删除类、方法面、分类/连接池等仍走 `omc call` 逃生舱。
|
|
132
135
|
|
|
133
|
-
## 近期能力(0.4.
|
|
136
|
+
## 近期能力(0.4.84 → 0.4.100)
|
|
134
137
|
|
|
135
138
|
| 版本 | 新增 / 修复 |
|
|
136
139
|
|---|---|
|
|
140
|
+
| 0.4.100 | **回合经济学批(r19–r20,同题面实测 184 调用/503KB → 84 调用/98KB)**:`omc help --search "<词>"` 全文检索(命中节正文内联,单节 >6KB 只给精确回放命令);`describe` 默认带 `firstCall`(offline-valid 最小可跑形状,`executable === missing为空`);`omc next` 首答 `firstAnswer`(ready=无占位符可跑 / needs-input=一个问题 / blocked=不伪造路径),`discover_environment` 退出 firstCommands 首位、章节指针 ≤1 且非通用草稿模板、输出 ≤4096B;`commands --filter` 语义检索(工具名/能力别名/配方/摘要,大小写与分隔符归一;零命中最小信封+≤3 候选;宽词按能力分组 ≤3.5KB);拒绝即教学(拒绝信封自带正确形状+与 firstCall 同源的配方命令+不要做什么;平台/权限类只给先修什么);三条复合写命令 `submit-verify` / `rule-save-enable` / `workflow-update-publish`(默认 dry-run、第一步失败立即停、合并语义显式);`OMC_NO_USAGE=1` 计量隔离(读侧不受影响);报表能力按裁决暂缓(报表类目标 boundary 首答;`/dashboard/v1/*` 502 教学「环境边界不要重试」;`report.create` 半成功显式警示+清理命令) |
|
|
141
|
+
| 0.4.99 | 聚合算法三态实测定案:`LARGE/SMALL` 必带 k(缺 → 50000 掩码)、`UNION` 的 `resultType` 只能 0/1;审批提交**顶层 `agree` 才有效**(`approval.agree/opinion` 被静默忽略且仍按通过处理——写前拒绝 + `approvalNote` 顶层参数);聚合事件回算窗口 ≈3 分钟实测(`field.publish --force` 重置) |
|
|
142
|
+
| 0.4.98 | 聚合指向子表 schema 写前拒绝(会让目标模型整体 40000 无明细且匹配恒 0);`40000` 裸掩码给排查解释(先疑字段级配置);`omc next` 造数据场景路由 |
|
|
143
|
+
| 0.4.97 | 三个写前 gate:日期毫秒时间戳(落库 NULL)、单值关联传数组(50000)、更新时无 id 子表行按新增插入不判重(实测 2 行变 8 行);跨模型聚合行为图谱(混用条件/多组 OR/COUNTIF/MAX/来源删除回算) |
|
|
144
|
+
| 0.4.96 | 聚合定案(反编译 1.1.25 运行版 jar):`propertyType/operatorType/resultType` 等 `@JsonCreator get(int)` 枚举**只认整数码**(写名字 → 305003 掩码,CLI 自动转码);同模型子表聚合是平台死路(`checkCycleOfAggregation:1032` 判环,`field.create` 接受、`field.publish` 炸)→ 写前拒绝;跨模型汇总配方 `formula-aggregation-cross-model`(DYNAMIC 条件两侧字段分属两模型、`queryConditions` 不能为空) |
|
|
145
|
+
| 0.4.95 | `omc compute` 诚实化:`datarule` type2(shortTextStitch)在 REST 路径**永不执行**(`CalculateHandler` 由 `forceExecuteCalculateRule` 门控、全库 0 调用方;门户「能算」是客户端 JS 求值器)——不再假装可配 |
|
|
146
|
+
| 0.4.94 | QUERY 输出不可寻址写前拒绝;`100005` 判「同 workItem 已提交」(永久,非时间窗) |
|
|
147
|
+
| 0.4.92 | `rule.run` 真门(此前是假成功);custompage 八工具 |
|
|
148
|
+
| 0.4.89/90 | 修 0.4.88 DRAFT 实例回归;`/organization/department/tree` 深度 BFS(deptIds 只回直接子级);组织树参与人解析按 `ouScope ∩ 发起人部门链`(全空=静默回退发起人 → 写前拒绝);结构化 nextCommand |
|
|
137
149
|
| 0.4.88 | **`omc model-purge`**:一条命令按依赖倒序清掉一个模型(流程实例 → model.delete 级联带走流程/视图/字段/表单/规则)。这是 r12–r14 三次「脏沙盒幽灵故障」的根治——顺手修了它的死锁(视图最后一条不能删 306103 + 列表绑定流程 303024,所以不删流程、让模型级联)。另:ladder 的 **recipe 命令提到 firstCommands**(r14 抱怨「ladder 没给可直接跑的命令」)、recipe 增加 `aliases` 意图词、`listview.configure` 的拒绝恢复补上 `targetCode` 按 bindAction 的语义 |
|
|
138
150
|
| 0.4.87 | **r14 真机探针修复**:`workitem.list` 的 50000 **不再是无解题**——按 `participant` 走 JPA 查询端点(能查**别人**名下的待办、且不解析流程模板),全局搜索失败时回 `workitem-search-unavailable` 带成因与三条绕行;`workflow.delete` 的 402508 改 `workflow-has-instances` 并说明**不要改用 model.delete 绕过**;`model.delete` 在有未完结待办时回 `workflowWarning`(级联会留脏待办 → 全局待办搜索整页 NPE)。另修两个 composer 模板参数名写错(`form-default --value`→`--default`、`rule-when --rule`→`--on`),并加**模板 vs `--help` 自动校验测试** |
|
|
139
151
|
| 0.4.86 | **r13 真机探针修复**:`omc next` 增加 **composer 路由**(含建模型+字段/审批流/计算/条件规则/表单默认值 → 直接给出对应一条命令;读面仍走 ladder,条件分支流程仍走 recipe)——r13 读了文档里那行 `model-scaffold` 却仍调 26 次 `field.create`,说明「写进文档」不等于「能被发现」。`data.submit` 审批提交**写前校验身份**:非本人在办的待办直接回 `submit-workitem-not-mine`(带待办持有者 + 三条出路),不再让平台回一个 `40005 权限不足`让 agent 猜 |
|
|
@@ -1240,7 +1240,7 @@
|
|
|
1240
1240
|
{
|
|
1241
1241
|
"capability": "rule.list latest business-rule listing and rule.get node route definition readback",
|
|
1242
1242
|
"status": "executable",
|
|
1243
|
-
"scope": "GET /api/api/runtime/business_rule/list_by_schemacode returning the full latest-version business-field set (no server-side filters); GET /api/api/app/businessrule/get returning the saved node and route graph for readback",
|
|
1243
|
+
"scope": "GET /api/api/runtime/business_rule/list_by_schemacode returning the full latest-version business-field set (no server-side filters); GET /api/api/app/businessrule/get returning the saved node and route graph for readback; cross-rule reference search over model.list + businessrule list/get scans (rule.references)",
|
|
1244
1244
|
"version_family": "8.6",
|
|
1245
1245
|
"anchor_version": "8.6.26",
|
|
1246
1246
|
"evidence": [
|
|
@@ -1251,16 +1251,22 @@
|
|
|
1251
1251
|
{
|
|
1252
1252
|
"path": "packages/omc-cli/test/rule-list.test.ts",
|
|
1253
1253
|
"anchor": "contract tests"
|
|
1254
|
+
},
|
|
1255
|
+
{
|
|
1256
|
+
"path": "packages/omc-cli/test/rule-references.test.ts",
|
|
1257
|
+
"anchor": "contract tests"
|
|
1254
1258
|
}
|
|
1255
1259
|
],
|
|
1256
1260
|
"lane": "B1-businessrule",
|
|
1257
1261
|
"tools": [
|
|
1258
1262
|
"rule.list",
|
|
1259
|
-
"rule.get"
|
|
1263
|
+
"rule.get",
|
|
1264
|
+
"rule.references"
|
|
1260
1265
|
],
|
|
1261
1266
|
"scopes": [
|
|
1262
1267
|
"latest rule business-field listing",
|
|
1263
|
-
"rule node route readback"
|
|
1268
|
+
"rule node route readback",
|
|
1269
|
+
"cross-rule reference search"
|
|
1264
1270
|
]
|
|
1265
1271
|
},
|
|
1266
1272
|
{
|
package/dist/cli-contract.js
CHANGED
|
@@ -220,7 +220,11 @@ export function classifyResult(result) {
|
|
|
220
220
|
return { status: "error", error: withNext({ code: "invalid-input", message: `unknown tool ${String(result.tool ?? "")}${suffix}`, retryable: false }), exitCode: CLI_EXIT_CODES.invalidInput };
|
|
221
221
|
}
|
|
222
222
|
if (result.blocker === "tool-input-invalid")
|
|
223
|
-
return {
|
|
223
|
+
return {
|
|
224
|
+
status: "error",
|
|
225
|
+
error: { ...withNext({ code: "invalid-input", message: String(result.message ?? "tool input is invalid"), retryable: false }), nextCommand: `omc describe ${String(result.tool ?? "<tool>")}` },
|
|
226
|
+
exitCode: CLI_EXIT_CODES.invalidInput,
|
|
227
|
+
};
|
|
224
228
|
if (status === "uncertain")
|
|
225
229
|
return { status, error: withNext({ code: "uncertain", message: String(result.message ?? result.reason ?? "outcome is uncertain"), retryable: true }), exitCode: CLI_EXIT_CODES.uncertain };
|
|
226
230
|
if (status === "refused") {
|
package/dist/cli.js
CHANGED
|
@@ -1508,7 +1508,7 @@ async function main() {
|
|
|
1508
1508
|
.option("--schema <schemaCode>", "模型 code")
|
|
1509
1509
|
.option("--schema-code <schemaCode>")
|
|
1510
1510
|
.option("--rule-code <ruleCode>")
|
|
1511
|
-
.option("--name <name>")
|
|
1511
|
+
.option("--name <name>", "规则显示名(rule.save 的 name 位;与既有规则同名则覆盖更新)")
|
|
1512
1512
|
.option("--node-graph <json>", "规则图 {nodes,routes} JSON")
|
|
1513
1513
|
.option("--publish", "保存后同时发布规则版本")
|
|
1514
1514
|
.option("--env <environment>")
|
package/dist/compound-writes.js
CHANGED
|
@@ -225,18 +225,25 @@ export async function runRuleSaveEnable(options) {
|
|
|
225
225
|
* droppedNodes 列明。
|
|
226
226
|
* 两种路径都不依赖平台的合并/整替行为:最终模板由本命令显式合成。
|
|
227
227
|
*/
|
|
228
|
+
/**
|
|
229
|
+
* 节点码/类型的读取兼容真机与 fake 两种投影(r22 2026-10-27 真机实证:
|
|
230
|
+
* workflow.get_draft 真机节点形如 {"index":0,"code":"Activity1","type":"START"},
|
|
231
|
+
* 而本命令原读法 activityCode/activityType 恒空 → preservedNodes 恒空)。
|
|
232
|
+
*/
|
|
233
|
+
const activityCodeOf = (activity) => String(activity.activityCode ?? activity.code ?? "");
|
|
234
|
+
const activityTypeOf = (activity) => String(activity.activityType ?? activity.type ?? "").toUpperCase();
|
|
228
235
|
function composeFinalTemplate(draftActivities, template, confirmReplace) {
|
|
229
|
-
const newCodes = new Set((template.activities ?? []).map((activity) =>
|
|
236
|
+
const newCodes = new Set((template.activities ?? []).map((activity) => activityCodeOf(activity)));
|
|
230
237
|
const oldNodes = draftActivities.filter((activity) => {
|
|
231
|
-
const code =
|
|
238
|
+
const code = activityCodeOf(activity);
|
|
232
239
|
return code !== "" && !newCodes.has(code);
|
|
233
240
|
});
|
|
234
|
-
const oldCodes = oldNodes.map((activity) =>
|
|
241
|
+
const oldCodes = oldNodes.map((activity) => activityCodeOf(activity));
|
|
235
242
|
if (confirmReplace || oldCodes.length === 0) {
|
|
236
243
|
return { finalTemplate: template, preservedNodes: [], droppedNodes: confirmReplace ? oldCodes : [] };
|
|
237
244
|
}
|
|
238
245
|
const activities = template.activities ?? [];
|
|
239
|
-
const endIndex = activities.findIndex((activity) =>
|
|
246
|
+
const endIndex = activities.findIndex((activity) => activityTypeOf(activity) === "END");
|
|
240
247
|
const merged = endIndex >= 0
|
|
241
248
|
? [...activities.slice(0, endIndex), ...oldNodes, ...activities.slice(endIndex)]
|
|
242
249
|
: [...activities, ...oldNodes];
|
|
@@ -251,7 +258,11 @@ export async function runWorkflowUpdatePublish(options) {
|
|
|
251
258
|
}
|
|
252
259
|
const draft = options.apply ? await invoke("workflow.get_draft", { schemaCode: options.schema, ...(options.code ? { code: options.code } : {}) }) : { status: "ok", data: null };
|
|
253
260
|
const draftData = (draft.data ?? null);
|
|
254
|
-
|
|
261
|
+
// r22 真机实证:真机投影在 workflowSummary.activities;fake 在 activities/template.activities——三口径全兼容。
|
|
262
|
+
const draftActivities = (draftData?.workflowSummary?.activities
|
|
263
|
+
?? draftData?.activities
|
|
264
|
+
?? draftData?.template?.activities
|
|
265
|
+
?? []);
|
|
255
266
|
const { finalTemplate, preservedNodes, droppedNodes } = composeFinalTemplate(draftActivities, options.template, options.confirmReplace === true);
|
|
256
267
|
if (!options.apply) {
|
|
257
268
|
emit({
|
package/dist/discovery.js
CHANGED
|
@@ -182,6 +182,13 @@ function terseType(node) {
|
|
|
182
182
|
const label = `${node.type ?? "string"}: ${values.join("|")}`;
|
|
183
183
|
return label.length > 80 ? `${node.type ?? "string"} (enum)` : label;
|
|
184
184
|
}
|
|
185
|
+
// 券商 issue 1.2(2026-10-27):union 渲染成 "any" 曾让调用方以为 controls 收
|
|
186
|
+
// 任意值、直觉传逗号串被 invalid_union 拒。anyOf 如实标注成员类型。
|
|
187
|
+
if (Array.isArray(node.anyOf) && node.anyOf.length > 0) {
|
|
188
|
+
const members = [...new Set(node.anyOf.map((member) => terseType(member)))];
|
|
189
|
+
const label = members.join("|");
|
|
190
|
+
return label.length > 60 ? `(${label.slice(0, 57)}...)` : label;
|
|
191
|
+
}
|
|
185
192
|
if (node.type === "array")
|
|
186
193
|
return `array<${terseType(node.items)}>`;
|
|
187
194
|
return node.type ?? "any";
|
package/dist/field-change.js
CHANGED
|
@@ -2,6 +2,7 @@ import { FIELD_PROPERTY_TYPES } from "./field-families.js";
|
|
|
2
2
|
import { assertFormConditions, assertNumericalScale, assertFieldOptionsAllowed, buildChildTableSubSchema, buildFieldOptions, ensureRelationDisplayFieldOptions, normalizeFieldPropertyType, relationDisplayFieldCode, } from "./field-families.js";
|
|
3
3
|
import { nameI18nJson } from "./field-families.js";
|
|
4
4
|
import { decodeJsonObjectColumn } from "./json-column.js";
|
|
5
|
+
import { normalizeFormConditionOptions } from "./lanes/datarule-protocol.js";
|
|
5
6
|
import { ToolRefusalError } from "./tool-types.js";
|
|
6
7
|
/**
|
|
7
8
|
* Fail-closed relation-target gate (round17 dogfood R3). A relation field whose
|
|
@@ -75,6 +76,10 @@ export function fieldChangeWireBody(input, prepared) {
|
|
|
75
76
|
};
|
|
76
77
|
}
|
|
77
78
|
export function prepareFieldUpdate(input) {
|
|
79
|
+
// r26 快速装载:符号式/裸值条件就地编译为平台中文规范形状(合规式零改写)。
|
|
80
|
+
const normalizedOptions = input.options === undefined ? undefined : normalizeFormConditionOptions(input.options);
|
|
81
|
+
const effectiveOptions = normalizedOptions?.options;
|
|
82
|
+
const conditionCompiled = normalizedOptions?.compiledNotes ?? [];
|
|
78
83
|
const recordType = Number(input.base.type ?? input.base.propertyType);
|
|
79
84
|
const isChildTable = recordType === FIELD_PROPERTY_TYPES.CHILD_TABLE;
|
|
80
85
|
if (isChildTable && (input.options !== undefined || input.choices !== undefined || input.subFields !== undefined))
|
|
@@ -93,10 +98,10 @@ export function prepareFieldUpdate(input) {
|
|
|
93
98
|
...buildFieldOptions({
|
|
94
99
|
propertyType: recordType,
|
|
95
100
|
...(input.choices ? { choices: input.choices } : {}),
|
|
96
|
-
...(
|
|
101
|
+
...(effectiveOptions ? { options: effectiveOptions } : {}),
|
|
97
102
|
}),
|
|
98
103
|
});
|
|
99
|
-
if (!isChildTable && !
|
|
104
|
+
if (!isChildTable && !effectiveOptions && !input.choices)
|
|
100
105
|
Object.assign(nextOptions, currentOptions);
|
|
101
106
|
if (!isChildTable) {
|
|
102
107
|
// An update that touches a relation's options must keep a resolvable target:
|
|
@@ -104,10 +109,10 @@ export function prepareFieldUpdate(input) {
|
|
|
104
109
|
// field.publish cannot later 300010 on this field.
|
|
105
110
|
assertRelationTargetPresent(recordType, nextOptions, input.relativeCode ?? (typeof input.base.relativeCode === "string" ? input.base.relativeCode : undefined), input.code);
|
|
106
111
|
assertFieldOptionsAllowed(recordType, nextOptions, input.schemaCode);
|
|
107
|
-
if (
|
|
112
|
+
if (effectiveOptions !== undefined || input.choices !== undefined)
|
|
108
113
|
assertNumericalScale(recordType, nextOptions);
|
|
109
|
-
if (
|
|
110
|
-
assertFormConditions(
|
|
114
|
+
if (effectiveOptions !== undefined)
|
|
115
|
+
assertFormConditions(effectiveOptions);
|
|
111
116
|
}
|
|
112
117
|
// Ticket 61: an explicit displayField wins; otherwise preserve the stored
|
|
113
118
|
// top-level relativePropertyCode (the runtime reads it, not options).
|
|
@@ -119,7 +124,7 @@ export function prepareFieldUpdate(input) {
|
|
|
119
124
|
: input.name !== undefined
|
|
120
125
|
? nameI18nJson(input.name)
|
|
121
126
|
: (typeof input.base.name_i18n === "string" ? input.base.name_i18n : undefined);
|
|
122
|
-
return { propertyType: recordType, isChildTable, options: nextOptions, ...(displayCode !== undefined ? { displayCode } : {}), ...(name !== undefined ? { name } : {}), ...(nameI18n !== undefined ? { nameI18n } : {}) };
|
|
127
|
+
return { propertyType: recordType, isChildTable, options: nextOptions, ...(conditionCompiled.length > 0 ? { conditionCompiled } : {}), ...(displayCode !== undefined ? { displayCode } : {}), ...(name !== undefined ? { name } : {}), ...(nameI18n !== undefined ? { nameI18n } : {}) };
|
|
123
128
|
}
|
|
124
129
|
/** The single field-update wire body: the stored row spread with changed keys. */
|
|
125
130
|
export function fieldUpdateWireBody(input, prepared) {
|
package/dist/field-families.js
CHANGED
|
@@ -167,7 +167,7 @@ export const fieldOptionsArg = z
|
|
|
167
167
|
schemaCode: z.string().optional().describe("relation target model code (WORK_SHEET / MULT_WORK_SHEET)"),
|
|
168
168
|
queryCode: z.string().optional().describe("relation query code (usually the target model code)"),
|
|
169
169
|
displayField: z.string().optional().describe("relation display field (usually 'name')"),
|
|
170
|
-
mappings: z.string().optional().describe('relation carry mappings
|
|
170
|
+
mappings: z.union([z.string(), z.array(z.object({ source: z.string(), target: z.string() }))]).optional().describe('relation carry mappings——数组 [{"source":"<target field code>","target":"<this-model field code>"}] 或等价 JSON 字符串均可,CLI 就地归一(空数组/"[]" → 无映射)'),
|
|
171
171
|
searchFormula: jsonString().optional().describe("WORK_SHEET / MULT_WORK_SHEET data restriction range JSON"),
|
|
172
172
|
conditions: jsonString().optional().describe("WORK_SHEET / MULT_WORK_SHEET query conditions JSON"),
|
|
173
173
|
optionsSet: optionsSetArg.optional().describe("selection source: custom, dictionary, or businessModel"),
|
|
@@ -495,6 +495,64 @@ function normalizeBusinessModelCondition(businessModel) {
|
|
|
495
495
|
if (parts.length > 0)
|
|
496
496
|
businessModel.condition = parts.join("&&");
|
|
497
497
|
}
|
|
498
|
+
/**
|
|
499
|
+
* r26 快速装载(2026-10-28):关联带出 mappings 的就地归一。agent 最自然的
|
|
500
|
+
* 写法是数组 `mappings:[{source:"contract_name",target:"contract_name"}]`——
|
|
501
|
+
* 此前静默失效(wire 成数组形态,平台只认 JSON 字符串),必须照 describe
|
|
502
|
+
* 拼字符串(r24/r26 各 ~15 轮试错的来源之一)。归一规则:
|
|
503
|
+
* - 数组:空数组 → `""`(无映射规范形态,规避 `"[]"` 假"已设置"坑);
|
|
504
|
+
* 非空逐项必须是 `{source,target}` 非空字符串 → 紧凑 JSON 字符串
|
|
505
|
+
* - 字符串:`"[]"` → `""`;可 JSON.parse 的必须解析为 `[{source,target}]`
|
|
506
|
+
* 形状(坏结构教学拒绝);parse 失败按原样拒绝
|
|
507
|
+
* - 其它类型拒绝教学
|
|
508
|
+
*/
|
|
509
|
+
export function normalizeMappingsOption(options) {
|
|
510
|
+
if (!("mappings" in options) || options.mappings === undefined)
|
|
511
|
+
return { options };
|
|
512
|
+
const mappings = options.mappings;
|
|
513
|
+
const mappingsError = (detail) => refuseFieldOptions(`relation mappings fail the reproduced protocol`, `rejected: options.mappings ${detail};合法形状:JSON 字符串 [{"source":"<来源字段>","target":"<目标字段>"}] 或等价数组;无映射用 ""(不是 "[]"——"[]" 会让设计器显示"已设置"但弹窗无映射行)`);
|
|
514
|
+
let normalized;
|
|
515
|
+
if (Array.isArray(mappings)) {
|
|
516
|
+
if (mappings.length === 0)
|
|
517
|
+
normalized = "";
|
|
518
|
+
else {
|
|
519
|
+
const rows = mappings.map((row) => {
|
|
520
|
+
if (row === null || typeof row !== "object" || Array.isArray(row))
|
|
521
|
+
mappingsError("数组每项必须是 {source,target} 对象");
|
|
522
|
+
const record = row;
|
|
523
|
+
const source = record.source;
|
|
524
|
+
const target = record.target;
|
|
525
|
+
if (typeof source !== "string" || source.trim() === "" || typeof target !== "string" || target.trim() === "")
|
|
526
|
+
mappingsError("每项的 source/target 必须是非空字符串");
|
|
527
|
+
return { source: source.trim(), target: target.trim() };
|
|
528
|
+
});
|
|
529
|
+
normalized = JSON.stringify(rows);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
else if (typeof mappings === "string") {
|
|
533
|
+
const trimmed = mappings.trim();
|
|
534
|
+
if (trimmed === "" || trimmed === "[]")
|
|
535
|
+
normalized = "";
|
|
536
|
+
else {
|
|
537
|
+
let parsed;
|
|
538
|
+
try {
|
|
539
|
+
parsed = JSON.parse(trimmed);
|
|
540
|
+
}
|
|
541
|
+
catch {
|
|
542
|
+
mappingsError(`字符串必须可解析为 JSON 数组(收到:${trimmed.slice(0, 40)}…)`);
|
|
543
|
+
}
|
|
544
|
+
if (!Array.isArray(parsed) || parsed.some((row) => row === null || typeof row !== "object" || Array.isArray(row) || typeof row.source !== "string" || typeof row.target !== "string" || row.source === "" || row.target === ""))
|
|
545
|
+
mappingsError("解析结果必须是 [{source,target}] 形状(source/target 非空字符串)");
|
|
546
|
+
normalized = JSON.stringify(parsed.map(({ source, target }) => ({ source: source.trim(), target: target.trim() })));
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
else
|
|
550
|
+
mappingsError(`必须是 JSON 字符串或数组(收到 ${typeof mappings})`);
|
|
551
|
+
const next = { ...options, mappings: normalized };
|
|
552
|
+
// note 只在「形态」变化(数组→字符串 / "[]"→"")时给;纯 trim 不算。
|
|
553
|
+
const shapeChanged = Array.isArray(mappings) || (typeof mappings === "string" && mappings.trim() === "[]");
|
|
554
|
+
return { options: next, ...(shapeChanged ? { note: `options.mappings 已归一为平台字符串形态:${normalized === "" ? "空串(无映射)" : String(normalized)}` } : {}) };
|
|
555
|
+
}
|
|
498
556
|
export function buildFieldOptions(args) {
|
|
499
557
|
const explicit = args.options;
|
|
500
558
|
const raw = args.propertyType;
|
|
@@ -513,7 +571,7 @@ export function buildFieldOptions(args) {
|
|
|
513
571
|
refuseFieldOptions("selection field options carry no optionsSet source", `rejected: ${choiceError}`);
|
|
514
572
|
}
|
|
515
573
|
normalizeBusinessModelOrderBy(explicit);
|
|
516
|
-
return explicit;
|
|
574
|
+
return normalizeMappingsOption(explicit).options;
|
|
517
575
|
}
|
|
518
576
|
const choices = args.choices;
|
|
519
577
|
if (Array.isArray(choices) && choices.length > 0) {
|
package/dist/goal-routes.js
CHANGED
|
@@ -556,6 +556,75 @@ export function goalRoute(registry, goal) {
|
|
|
556
556
|
// Composer before the capability solver: a recurring write SHAPE has a single
|
|
557
557
|
// command that does the job with preflight + idempotency, which beats a ladder
|
|
558
558
|
// of atomic tools even when the ladder also matches (r13: 26 field.create).
|
|
559
|
+
// r19 券商 issue 1.1(2026-10-27):子表**控件**(type 201)的显示/只读条件
|
|
560
|
+
// (displayFormula/readonlyFormula)不在字段面——field.update 对子表只管**列**、
|
|
561
|
+
// 对控件条件必拒;唯一路径是表单 attributes(form.get 读 draftAttributesJson
|
|
562
|
+
// 定位控件 → form.draft 改 → form.publish)。此前该场景路由进 field→form 梯子
|
|
563
|
+
// 且首级无可行工具(firstCommands 为空),agent 只能靠拒收文案自己悟。
|
|
564
|
+
// r27 探针(2026-10-28):普通字段的条件必填/显隐/只读——「为字段配置条件必填…规则」
|
|
565
|
+
// 这类措辞被判成 read-ladder("要查看哪个对象?"),agent 被迫自行探索并先撞
|
|
566
|
+
// datarule.create(配方明令禁止的路线)再折回 field.update,单意图浪费约 15 次调用。
|
|
567
|
+
// 触发词面:条件必填/必填条件/条件显隐/显示条件/隐藏条件/条件只读/只读条件 +
|
|
568
|
+
// 配置/设置/改成/修改/加上 等写动词,或直接点名 requiredFormula/displayFormula/
|
|
569
|
+
// readonlyCondition。不提「子表/控件」(那是上一条表单 attributes 的专项路由)。
|
|
570
|
+
if (!/子表/.test(goal)
|
|
571
|
+
&& !/查看|有哪些|是什么|怎么|列出|看看|查询|检查|确认/.test(goal)
|
|
572
|
+
&& (/条件必填|必填条件|条件显隐|显隐条件|显示条件|条件显示|隐藏条件|条件隐藏|条件只读|只读条件|requiredFormula|displayFormula|readonlyCondition/.test(goal))
|
|
573
|
+
&& /配置|设置|改成|修改|加上|加一个|做个|做一|变必填|控制|当.*时/.test(goal)) {
|
|
574
|
+
return {
|
|
575
|
+
status: "ok",
|
|
576
|
+
goal,
|
|
577
|
+
confident: true,
|
|
578
|
+
mode: "modify",
|
|
579
|
+
write: true,
|
|
580
|
+
match: { kind: "recipe", ref: "form-conditional-required", title: "字段层条件必填/显隐/只读(field.update options 面)" },
|
|
581
|
+
route: {
|
|
582
|
+
capability: "field",
|
|
583
|
+
lane: "field-families",
|
|
584
|
+
note: "普通字段的条件必填(requiredFormula)/条件显隐(displayFormula)/条件只读(readonlyCondition)都在字段 options 面:field.update 写入即自动配对 bizdatarule 3/4/5 行(回执 formConditionSync)。表达式可用自然符号式({字段} = \"值\" / {字段} >= 5000 / {字段} 不为空),CLI 就地编译为平台中文形状。**不要用 datarule.create**——type4 不让门户控件变必填(实测)。",
|
|
585
|
+
readiness: DISCOVERY_AUTHORITY.readiness,
|
|
586
|
+
firstCommands: [
|
|
587
|
+
"omc describe field.update --example form-conditional-required",
|
|
588
|
+
"omc call field.list --args '{\"schemaCode\":\"<schemaCode\",\"full\":true}'",
|
|
589
|
+
],
|
|
590
|
+
write: true,
|
|
591
|
+
},
|
|
592
|
+
firstAnswer: {
|
|
593
|
+
state: "needs-input",
|
|
594
|
+
question: "目标模型 schemaCode 与字段 code 是什么?(给我 schemaCode 或业务名,我列清单核对)",
|
|
595
|
+
action: LIST_MODELS_ACTION,
|
|
596
|
+
pendingCommand: "omc call field.update --args '{\"schemaCode\":\"<schemaCode>\",\"code\":\"<字段code>\",\"options\":{\"requiredFormula\":\"{触发字段} 等于 某值\"}}'",
|
|
597
|
+
why: "命中字段层条件三件套路由:field.update 的 options.requiredFormula/displayFormula/readonlyCondition(表达式支持符号式就地编译)",
|
|
598
|
+
},
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
if (/子表|控件/.test(goal) && /子表.*(显示|隐藏|只读|显隐).*(条件|公式|规则)|displayFormula|readonlyFormula/.test(goal)) {
|
|
602
|
+
return {
|
|
603
|
+
status: "ok",
|
|
604
|
+
goal,
|
|
605
|
+
confident: true,
|
|
606
|
+
mode: "modify",
|
|
607
|
+
write: true,
|
|
608
|
+
match: { kind: "capability", ref: "form", title: "子表控件显示/只读条件(表单 attributes 面)" },
|
|
609
|
+
route: {
|
|
610
|
+
capability: "form",
|
|
611
|
+
note: "子表控件的 displayFormula/readonlyFormula 走表单 attributes:form.get(controls:true) 读 draftAttributesJson 定位控件 → form.draft 改该控件条件 → form.publish。field.update 只管子表列,对控件条件必拒(券商 issue 2026-10-27 实证)",
|
|
612
|
+
readiness: DISCOVERY_AUTHORITY.readiness,
|
|
613
|
+
firstCommands: [
|
|
614
|
+
"omc call form.get --args '{\"schemaCode\":\"<schemaCode>\",\"controls\":true}'",
|
|
615
|
+
"omc describe form.draft",
|
|
616
|
+
],
|
|
617
|
+
write: true,
|
|
618
|
+
},
|
|
619
|
+
firstAnswer: {
|
|
620
|
+
state: "needs-input",
|
|
621
|
+
question: "目标模型 schemaCode 与子表控件 code 是什么?(form.get controls:true 读 draftAttributesJson 定位控件,form.draft 改其条件后 form.publish)",
|
|
622
|
+
action: LIST_MODELS_ACTION,
|
|
623
|
+
pendingCommand: "omc call form.draft --args '{\"schemaCode\":\"<schemaCode>\",\"draftAttributesJson\":\"<改好后的完整 attributes JSON>\"}'",
|
|
624
|
+
why: "子表控件(type 201)的显示/只读条件在表单 attributes,不在字段面——field.update 对子表只管列、必拒",
|
|
625
|
+
},
|
|
626
|
+
};
|
|
627
|
+
}
|
|
559
628
|
const composer = matchComposer(goal, mode);
|
|
560
629
|
if (composer) {
|
|
561
630
|
const topics = (composer.topics ?? []).filter((topic) => availableTopics.has(topic));
|
|
@@ -20,7 +20,7 @@ export async function admitGovernedWrite(ctx, request) {
|
|
|
20
20
|
blocker: "environment-write-disabled",
|
|
21
21
|
environment: resolved.name,
|
|
22
22
|
production: resolved.profile.production === true,
|
|
23
|
-
message: `${environmentNotice(resolved.profile)} 请在 omc setup
|
|
23
|
+
message: `${environmentNotice(resolved.profile)} 请在 omc setup 中开启“允许写入此环境”。若你本意是写项目环境:检查当前目录是否项目根(.omc-env 所在目录)——在其它目录跑 omc 会解析不到项目环境而落到全局只读策略,cd 回项目根再试。`,
|
|
24
24
|
});
|
|
25
25
|
let session;
|
|
26
26
|
try {
|
|
@@ -416,7 +416,7 @@ export function registerB2Lane(tools) {
|
|
|
416
416
|
});
|
|
417
417
|
const afterRows = wireRows((await wireCall(session, { path: ENDPOINTS.bizServiceMethodList, query: { serviceCode: args.serviceCode, filterFlag: "false" } })).data);
|
|
418
418
|
const after = afterRows.find((row) => row.code === args.methodCode);
|
|
419
|
-
return { performed: { errcode: performed.errcode }, readback: { methodPresent: after != null, nameMatches: args.name === undefined || after?.name === args.name, configMatches: methodConfigPlain === undefined || after?.configJson === methodConfigPlain, inputParameters: decodeJsonColumn(after?.inputParametersJson), outputParameters: decodeJsonColumn(after?.outputParametersJson) } };
|
|
419
|
+
return { performed: { errcode: performed.errcode }, readback: { methodCode: args.methodCode, methodPresent: after != null, nameMatches: args.name === undefined || after?.name === args.name, configMatches: methodConfigPlain === undefined || after?.configJson === methodConfigPlain, inputParameters: decodeJsonColumn(after?.inputParametersJson), outputParameters: decodeJsonColumn(after?.outputParametersJson), mappingNote: "规则映射直接用这里的参数 code(bizPropertyType 供类型对账),不必再 service.get" } };
|
|
420
420
|
}, (args) => {
|
|
421
421
|
assertStructuredConfig(args.methodConfig, "methodConfig");
|
|
422
422
|
}, undefined, b2ServiceRollbackSpec("method.update"));
|
|
@@ -463,7 +463,7 @@ export function registerB2Lane(tools) {
|
|
|
463
463
|
});
|
|
464
464
|
const afterRows = wireRows((await wireCall(session, { path: ENDPOINTS.bizServiceMethodList, query: { serviceCode: args.serviceCode, filterFlag: "false" } })).data);
|
|
465
465
|
const after = afterRows.find((row) => row.code === args.methodCode);
|
|
466
|
-
return { performed: { errcode: performed.errcode }, readback: { methodPresent: after != null, nameMatches: after?.name === args.name, configMatches: methodConfigPlain === undefined || after?.configJson === methodConfigPlain, inputParameters: decodeJsonColumn(after?.inputParametersJson), outputParameters: decodeJsonColumn(after?.outputParametersJson) } };
|
|
466
|
+
return { performed: { errcode: performed.errcode }, readback: { methodCode: args.methodCode, methodPresent: after != null, nameMatches: after?.name === args.name, configMatches: methodConfigPlain === undefined || after?.configJson === methodConfigPlain, inputParameters: decodeJsonColumn(after?.inputParametersJson), outputParameters: decodeJsonColumn(after?.outputParametersJson), mappingNote: "规则映射直接用这里的参数 code(bizPropertyType 供类型对账),不必再 service.get" } };
|
|
467
467
|
}, (args) => {
|
|
468
468
|
assertStructuredConfig(args.methodConfig, "methodConfig");
|
|
469
469
|
}, undefined, b2ServiceRollbackSpec("method.create"));
|
|
@@ -850,9 +850,26 @@ export function registerB2Lane(tools) {
|
|
|
850
850
|
? wireRows((await wireCall(session, { path: ENDPOINTS.bizServiceMethodList, query: { serviceCode: args.serviceCode, filterFlag: "false" } })).data)
|
|
851
851
|
: [];
|
|
852
852
|
const registered = args.methodName ? methodRows.some((row) => row.code === args.methodName) : undefined;
|
|
853
|
+
// r19 p7:readback 只回布尔时,agent 只能再调 service.get 拿参数 code(12 次里
|
|
854
|
+
// 6 次是这种写后验证读)。方法视图内联进 readback,rule.save 的 methodMapping
|
|
855
|
+
// 直接用这里的 code,不必再读。
|
|
856
|
+
const methodRow = args.methodName ? methodRows.find((row) => row.code === args.methodName) : undefined;
|
|
853
857
|
return {
|
|
854
858
|
performed: { errcode: performed.errcode },
|
|
855
|
-
readback: {
|
|
859
|
+
readback: {
|
|
860
|
+
servicePresent: readback.data != null,
|
|
861
|
+
...(registered !== undefined ? { methodPresent: registered, methodCount: methodRows.length } : {}),
|
|
862
|
+
...(methodRow !== undefined
|
|
863
|
+
? {
|
|
864
|
+
method: {
|
|
865
|
+
code: methodRow.code,
|
|
866
|
+
inputParameters: decodeJsonColumn(methodRow.inputParametersJson),
|
|
867
|
+
outputParameters: decodeJsonColumn(methodRow.outputParametersJson),
|
|
868
|
+
},
|
|
869
|
+
mappingNote: "rule.save 的 methodMapping 直接用上面的参数 code:inputMappings.bizCode=来源字段 code、serviceMethodParameterCode=参数 code;不必再 service.get",
|
|
870
|
+
}
|
|
871
|
+
: {}),
|
|
872
|
+
},
|
|
856
873
|
};
|
|
857
874
|
}, (args) => {
|
|
858
875
|
// Dry-lint (zero wire). `omc call` bypasses zod, so every schema-level
|
|
@@ -151,6 +151,10 @@ export const BUSINESSRULE_LANE_CONTRACT = Object.freeze({
|
|
|
151
151
|
scope: "rule node route readback",
|
|
152
152
|
description: "读回一条业务规则的完整定义(businessrule/get?id=),解析并回显 node/route 图 JSON(触发节点、UPDATE/CREATE 动作节点的 targetObjectCode/inputParam/dataActions/filterCondition 等)——规则族读写链的权威读回面,也是对照设计器生成图形的取证入口。",
|
|
153
153
|
}),
|
|
154
|
+
"rule.references": Object.freeze({
|
|
155
|
+
scope: "cross-rule reference search",
|
|
156
|
+
description: "跨规则引用检索(r23 实证的能力缺失:agent 为查『谁引用了服务 X』枚举了全平台 147 个 schema×ruleCode 组合/171 次 rule.get——本工具一调替代)。kind=service 查 BIZ_ACTION 的 methodMapping.serviceCode;kind=model 查 CREATE/UPDATE/DELETE 的 targetObjectCode 与 methodMapping.schemaCode(删模型前的影响面);kind=field 查 dataActions.targetDataItem 与 inputMappings/outputMappings 的 bizCode(删字段前的影响面;字段 code 跨模型不唯一,建议配 schemaCodes 限范围)。实现为客户端聚合(model.list→rule.list→rule.get 全扫,约几十次 wire 读、几秒完),输出只回命中行。第一版不含:条件叶子里的字段引用、datarule 引用、workflow.bindRule 绑定。",
|
|
157
|
+
}),
|
|
154
158
|
"rule.save": Object.freeze({
|
|
155
159
|
scope: "dead-state CRUD conditional node graphs",
|
|
156
160
|
description: RULE_SAVE_DESCRIPTION,
|
|
@@ -10,7 +10,7 @@ export const BUSINESSRULE_COVERAGE = declareLaneCoverage(BUSINESSRULE_LANE_CONTR
|
|
|
10
10
|
{
|
|
11
11
|
capability: "rule.list latest business-rule listing and rule.get node route definition readback",
|
|
12
12
|
evidence: ["packages/omc-cli/src/tools.ts", "packages/omc-cli/test/rule-list.test.ts"],
|
|
13
|
-
tools: [claim("rule.list", T["rule.list"].scope), claim("rule.get", T["rule.get"].scope)],
|
|
13
|
+
tools: [claim("rule.list", T["rule.list"].scope), claim("rule.get", T["rule.get"].scope), claim("rule.references", T["rule.references"].scope)],
|
|
14
14
|
},
|
|
15
15
|
]);
|
|
16
16
|
//# sourceMappingURL=businessrule-coverage.js.map
|
|
@@ -116,6 +116,99 @@ export function registerBusinessRuleLane(tools) {
|
|
|
116
116
|
};
|
|
117
117
|
}, { full: args.full === true, target: `rule.list:${args.schemaCode}` }),
|
|
118
118
|
}));
|
|
119
|
+
tools.push(defineTool({
|
|
120
|
+
name: "rule.references",
|
|
121
|
+
layer: "low",
|
|
122
|
+
kind: "read",
|
|
123
|
+
lane: RULE_LANE,
|
|
124
|
+
scope: RULE_TOOLS["rule.references"].scope,
|
|
125
|
+
description: RULE_TOOLS["rule.references"].description,
|
|
126
|
+
inputSchema: {
|
|
127
|
+
environment: environmentArg,
|
|
128
|
+
kind: z.enum(["service", "model", "field"]).describe("按什么找引用:service=BIZ_ACTION methodMapping.serviceCode;model=CREATE/UPDATE/DELETE targetObjectCode 与 methodMapping.schemaCode;field=dataActions.targetDataItem 与映射 bizCode"),
|
|
129
|
+
code: z.string().min(1).describe("要查引用的编码(服务 code / 模型 code / 字段 code)"),
|
|
130
|
+
schemaCodes: z.union([z.array(z.string().min(1)), z.string()]).optional().describe("只扫这些模型(省 wire 读);省略=全平台模型"),
|
|
131
|
+
capability: capabilityArg,
|
|
132
|
+
},
|
|
133
|
+
handler: async (ctx, args, self) => governedRead(ctx, self, args, async (session) => {
|
|
134
|
+
// r23(2026-10-27):agent 为查「谁引用了服务 X」枚举了 147 个
|
|
135
|
+
// schema×ruleCode 组合(171 次 rule.get / 229KB)。没有引用检索面时,
|
|
136
|
+
// 全库枚举是理性选择——所以给它一个。客户端聚合:模型清单 → 每模型
|
|
137
|
+
// rule.list → 每规则 rule.get → 图内匹配,一次调用、只回命中行。
|
|
138
|
+
const scoped = args.schemaCodes === undefined
|
|
139
|
+
? wireRows((await wireCall(session, { path: ENDPOINTS.modelList })).data).map((row) => String(row.schemaCode ?? "")).filter((code) => code !== "")
|
|
140
|
+
: (Array.isArray(args.schemaCodes) ? args.schemaCodes : [args.schemaCodes]);
|
|
141
|
+
const wanted = args.code;
|
|
142
|
+
const matches = [];
|
|
143
|
+
let scannedRules = 0;
|
|
144
|
+
let parseFailures = 0;
|
|
145
|
+
for (const schemaCode of scoped) {
|
|
146
|
+
let rows = [];
|
|
147
|
+
try {
|
|
148
|
+
rows = wireRows((await wireCall(session, { path: ENDPOINTS.businessRuleListBySchemaCode, query: { schemaCode } })).data);
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
continue; // 模型无规则面(读失败/空)按无规则处理,不让单个模型挡住全扫
|
|
152
|
+
}
|
|
153
|
+
for (const row of rows) {
|
|
154
|
+
const ruleCode = String(row.code ?? "");
|
|
155
|
+
const ruleId = row.id;
|
|
156
|
+
scannedRules += 1;
|
|
157
|
+
if (typeof ruleId !== "string" || ruleId === "")
|
|
158
|
+
continue;
|
|
159
|
+
let record = null;
|
|
160
|
+
try {
|
|
161
|
+
record = (await wireCall(session, { path: ENDPOINTS.businessRuleGet, query: { id: ruleId } })).data;
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
if (!record)
|
|
167
|
+
continue;
|
|
168
|
+
const nodes = decodeJsonColumn(record.node, JSON_COLUMN_NULL_COALESCE);
|
|
169
|
+
if (!Array.isArray(nodes)) {
|
|
170
|
+
parseFailures += 1;
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
for (const rawNode of nodes) {
|
|
174
|
+
const nodeCode = String(rawNode.nodeCode ?? "");
|
|
175
|
+
const nodeType = String(rawNode.nodeType ?? "");
|
|
176
|
+
const mapping = rawNode.methodMapping;
|
|
177
|
+
if (args.kind === "service") {
|
|
178
|
+
if (nodeType === "BIZ_ACTION" && mapping && String(mapping.serviceCode ?? "") === wanted)
|
|
179
|
+
matches.push({ schemaCode, ruleCode, nodeCode, nodeType, via: "methodMapping.serviceCode" });
|
|
180
|
+
}
|
|
181
|
+
else if (args.kind === "model") {
|
|
182
|
+
if (["CREATE", "UPDATE", "DELETE"].includes(nodeType) && String(rawNode.targetObjectCode ?? "") === wanted)
|
|
183
|
+
matches.push({ schemaCode, ruleCode, nodeCode, nodeType, via: "targetObjectCode" });
|
|
184
|
+
if (mapping && String(mapping.schemaCode ?? "") === wanted)
|
|
185
|
+
matches.push({ schemaCode, ruleCode, nodeCode, nodeType, via: "methodMapping.schemaCode" });
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
if (["CREATE", "UPDATE", "DELETE"].includes(nodeType) && Array.isArray(rawNode.dataActions)
|
|
189
|
+
&& rawNode.dataActions.some((action) => String(action.targetDataItem ?? "") === wanted))
|
|
190
|
+
matches.push({ schemaCode, ruleCode, nodeCode, nodeType, via: "dataActions.targetDataItem" });
|
|
191
|
+
if (mapping) {
|
|
192
|
+
for (const [key, label] of [["inputMappings", "inputMappings.bizCode"], ["outputMappings", "outputMappings.bizCode"]]) {
|
|
193
|
+
if (Array.isArray(mapping[key]) && mapping[key].some((row2) => String(row2.bizCode ?? "") === wanted && (row2.codeType === undefined || row2.codeType === 1)))
|
|
194
|
+
matches.push({ schemaCode, ruleCode, nodeCode, nodeType, via: label });
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return {
|
|
202
|
+
kind: args.kind,
|
|
203
|
+
code: wanted,
|
|
204
|
+
scanned: { models: scoped.length, rules: scannedRules, ...(parseFailures > 0 ? { parseFailures } : {}) },
|
|
205
|
+
matches,
|
|
206
|
+
...(matches.length === 0
|
|
207
|
+
? { note: "零命中=全平台(或指定 schemaCodes 范围内)没有规则引用它。范围:service→methodMapping.serviceCode;model→targetObjectCode/methodMapping.schemaCode;field→dataActions.targetDataItem/映射 bizCode(不含条件叶子与 datarule,第一版边界)" }
|
|
208
|
+
: {}),
|
|
209
|
+
};
|
|
210
|
+
}, { target: `rule.references:${args.kind}:${args.code}` }),
|
|
211
|
+
}));
|
|
119
212
|
tools.push(defineTool({
|
|
120
213
|
name: "rule.get",
|
|
121
214
|
layer: "low",
|