@stalfh233/omc-cli 0.5.10 → 0.5.12
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/dist/cli-contract.js +1 -1
- package/dist/cli.js +6 -4
- package/dist/lanes/rule-graph.js +26 -0
- package/dist/lanes/workflow.js +20 -0
- package/dist/skills.js +51 -0
- package/dist/tool-types.js +48 -0
- package/package.json +3 -3
- package/skills/omc-business-configuration.md +1 -0
- package/skills/omc-write-hazards.md +9 -7
package/dist/cli-contract.js
CHANGED
|
@@ -256,7 +256,7 @@ export function classifyResult(result) {
|
|
|
256
256
|
const suffix = candidates.length > 0 ? `;did you mean: ${candidates.join("、")}` : ";用 `omc commands --json --compact --lane <lane>` 或 `omc next` 定位工具";
|
|
257
257
|
return { status: "error", error: withNext({ code: "invalid-input", message: `unknown tool ${String(result.tool ?? "")}${suffix}`, retryable: false }), exitCode: CLI_EXIT_CODES.invalidInput };
|
|
258
258
|
}
|
|
259
|
-
if (result.blocker === "tool-input-invalid")
|
|
259
|
+
if (result.blocker === "tool-input-invalid" || result.blocker === "tool-input-unknown-keys")
|
|
260
260
|
return {
|
|
261
261
|
status: "error",
|
|
262
262
|
error: { ...withNext({ code: "invalid-input", message: String(result.message ?? "tool input is invalid"), retryable: false }), nextCommand: `omc describe ${String(result.tool ?? "<tool>")}` },
|
package/dist/cli.js
CHANGED
|
@@ -42,7 +42,7 @@ import { absencePointer } from "./capability-absences.js";
|
|
|
42
42
|
import { intentConfigRoot, readIntentFile, validateIntentText } from "./intent/cli.js";
|
|
43
43
|
import { intentSchemaBlock, intentExample, intentExampleIds } from "./intent/schema.js";
|
|
44
44
|
import { planIntent, readbackIntent, applyIntent, intentWarnings } from "./intent/plan-apply.js";
|
|
45
|
-
import { listSkills, readSkill, readSkillOutline, readSkillSection, searchSkills } from "./skills.js";
|
|
45
|
+
import { listSkills, readSkill, readSkillOutline, readSkillSection, searchSkills, suggestHelpTopics } from "./skills.js";
|
|
46
46
|
import { parseToolInput, ToolInputError } from "./tool-types.js";
|
|
47
47
|
import { WireError, wireCall } from "./wire.js";
|
|
48
48
|
import { DEFAULT_PULL_CONCURRENCY, GraphIndexAbsentError, GraphPullLockHeldError, MAX_PULL_CONCURRENCY, impactGraph, publishGraphPull, queryGraphReferences, readGraphStatus, searchGraphRules, } from "./graph.js";
|
|
@@ -204,7 +204,7 @@ async function cmdDoctor(store, environment, serviceCode) {
|
|
|
204
204
|
const errcode = error instanceof WireError ? error.errcode : undefined;
|
|
205
205
|
if (errcode === 109001 || /许可证|license/i.test(error.message)) {
|
|
206
206
|
ok = false;
|
|
207
|
-
steps.push(`许可证 FAIL 已过期(平台拒绝 apppackage/list:${String(errcode ?? "")}
|
|
207
|
+
steps.push(`许可证 FAIL 已过期(平台拒绝 apppackage/list:${String(errcode ?? "")})——续期或更换环境。注意:109001 是**分面拦截**不是全环境不可用(r40 实证:报表/集成/部分读面被拒,模型/字段/数据面仍可交付)——不要因本项 FAIL 一刀切停步,按需逐面探测并用最小写验证`);
|
|
208
208
|
}
|
|
209
209
|
else {
|
|
210
210
|
ok = false;
|
|
@@ -1361,7 +1361,8 @@ function cmdHelpSearch(topic, query, transport, searchMode, limit) {
|
|
|
1361
1361
|
if (singleSection !== undefined) {
|
|
1362
1362
|
const sliced = readSkillSection(topic, singleSection);
|
|
1363
1363
|
if (sliced === undefined) {
|
|
1364
|
-
const
|
|
1364
|
+
const suggestions = suggestHelpTopics(topic);
|
|
1365
|
+
const message = `未知 topic "${topic}";可用 topic:${topics.map((entry) => entry.name).join("、")}(命令清单见 omc help commands)${commandPointer}${suggestions.length > 0 ? `;你可能要找:${suggestions.join("、")}` : ""}`;
|
|
1365
1366
|
console.error(`help: ${message}`);
|
|
1366
1367
|
return emitInvalid(transport, { message });
|
|
1367
1368
|
}
|
|
@@ -1382,7 +1383,8 @@ function cmdHelpSearch(topic, query, transport, searchMode, limit) {
|
|
|
1382
1383
|
}
|
|
1383
1384
|
const document = readSkill(topic);
|
|
1384
1385
|
if (document === undefined) {
|
|
1385
|
-
const
|
|
1386
|
+
const suggestions = suggestHelpTopics(topic);
|
|
1387
|
+
const message = `未知 topic "${topic}";可用 topic:${topics.map((entry) => entry.name).join("、")}(命令清单见 omc help commands)${commandPointer}${suggestions.length > 0 ? `;你可能要找:${suggestions.join("、")}` : ""}`;
|
|
1386
1388
|
console.error(`help: ${message}`);
|
|
1387
1389
|
return emitInvalid(transport, { message });
|
|
1388
1390
|
}
|
package/dist/lanes/rule-graph.js
CHANGED
|
@@ -1491,6 +1491,32 @@ export function validateRuleNodeGraph(graph, ruleCode, schemaCode, options = {})
|
|
|
1491
1491
|
const relaxEnums = options.relaxEnums === true;
|
|
1492
1492
|
if (nodeRecords.length !== nodes.length)
|
|
1493
1493
|
blockers.push({ code: "rule-graph-node-not-object", message: "nodes 中存在非对象元素" });
|
|
1494
|
+
// r42 头号返工根因(维护者同踩):filterCondition 的目标查找条件键是
|
|
1495
|
+
// mainConditions(FilterCondition 模型,List<List<TableCondition>>),不是
|
|
1496
|
+
// 触发条件/BRANCH 出口的 conditionGroups——错键被平台静默反序列化为空,
|
|
1497
|
+
// 引擎零循环恒报「查找范围不存在数据」,形似"跨模型 UPDATE 不可用"(曾据
|
|
1498
|
+
// 此误判平台边界,反编译 FilterCondition.mainConditions + mainConditions 正形
|
|
1499
|
+
// 真机回写成功双重实证后纠正)。fail closed:错键当场拒绝。
|
|
1500
|
+
for (const node of nodeRecords) {
|
|
1501
|
+
const nodeType = String(node.nodeType ?? "");
|
|
1502
|
+
if (nodeType !== "UPDATE" && nodeType !== "DELETE" && nodeType !== "CREATE" && nodeType !== "ASSIGN" && nodeType !== "QUERY")
|
|
1503
|
+
continue;
|
|
1504
|
+
const fc = node.filterCondition;
|
|
1505
|
+
if (fc === undefined || fc === null || typeof fc !== "object" || Array.isArray(fc))
|
|
1506
|
+
continue;
|
|
1507
|
+
const record = fc;
|
|
1508
|
+
if ("mainConditions" in record)
|
|
1509
|
+
continue;
|
|
1510
|
+
const wrongKeys = ["conditionGroups", "conditions", "mainTableConditions", "filterGroups", "targetConditionGroups"].filter((key) => key in record);
|
|
1511
|
+
if (wrongKeys.length > 0 || Object.keys(record).length === 0) {
|
|
1512
|
+
blockers.push({
|
|
1513
|
+
code: "rule-graph-filter-condition-wrong-key",
|
|
1514
|
+
nodeCode: String(node.nodeCode ?? ""),
|
|
1515
|
+
key: "filterCondition",
|
|
1516
|
+
message: `${nodeType} "${String(node.nodeCode ?? "")}" 的 filterCondition 键写错(${wrongKeys.join("、") || "空对象"})——目标查找条件用 mainConditions: [[{targetSchemaDataItem, ruleConditionType, ruleValueType, value|currentSchemaDataItem}]];conditionGroups 是触发条件/BRANCH 出口的键,放这里会被平台静默忽略 → 引擎恒报「查找范围不存在数据」(形似跨模型回写不可用)。形状见 omc describe rule.save --example business-rule-branch-conditional`,
|
|
1517
|
+
});
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1494
1520
|
// Cross-model write-back: SUPPORTED (verified live 2026-09-28). An UPDATE whose
|
|
1495
1521
|
// `targetObjectCode` is a DIFFERENT model than the rule's own model writes back
|
|
1496
1522
|
// correctly once TWO prerequisites hold — a 2026-09-27 probe that reported
|
package/dist/lanes/workflow.js
CHANGED
|
@@ -1423,6 +1423,23 @@ async function refuseRequiredOnChildTableFields(session, schemaCode, template, t
|
|
|
1423
1423
|
* same edge). This is the "read back, then send the delta" path — without it a
|
|
1424
1424
|
* subset silently REPLACED the whole activity list and dropped nodes.
|
|
1425
1425
|
*/
|
|
1426
|
+
function refusePartialBtnConfigList(activities) {
|
|
1427
|
+
// r32 实证(backlog 清算批):btnConfigList 局部行=整行替换——只传 {code, remark}
|
|
1428
|
+
// 会把 enabled 静默置 false、commentPermission 置 null(驳回意见必填丢失)。
|
|
1429
|
+
// fail-closed:按钮行不带显式 enabled 一律拒绝,指路 hazards 的整行回写法。
|
|
1430
|
+
for (const activity of activities) {
|
|
1431
|
+
const rows = activity.btnConfigList;
|
|
1432
|
+
if (!Array.isArray(rows))
|
|
1433
|
+
continue;
|
|
1434
|
+
const partial = rows.filter((row) => row !== null && typeof row === "object" && !("enabled" in row));
|
|
1435
|
+
if (partial.length > 0)
|
|
1436
|
+
throw new ToolRefusalError({
|
|
1437
|
+
status: "refused",
|
|
1438
|
+
blocker: "workflow-btnconfig-partial-row",
|
|
1439
|
+
message: `workflow.update: 节点 ${String(activity.activityCode ?? activity.id ?? "")} 的 btnConfigList 存在不带 enabled 的按钮行(${partial.map((row) => String(row.code ?? "?")).join("、")})——该平台按**整行替换**处理,缺省键会被静默重置(enabled→false、commentPermission→null)。先 workflow.get_published/get_draft 取该按钮整行,改目标属性后整条回写(见 omc help omc-write-hazards --section 按钮)。`,
|
|
1440
|
+
});
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1426
1443
|
function mergeCallerDelta(template, draft, options = {}) {
|
|
1427
1444
|
const { routes: routesRaw, rules: rulesRaw, activities: _raw, edges: edgesRaw, ...rest } = template;
|
|
1428
1445
|
// `routes` and `edges` are the recipe spellings; `rules` is the platform DTO
|
|
@@ -1486,6 +1503,7 @@ function mergeCallerDelta(template, draft, options = {}) {
|
|
|
1486
1503
|
return { ...activity, ...override };
|
|
1487
1504
|
});
|
|
1488
1505
|
const newActivities = [...callerByOriginal.values()];
|
|
1506
|
+
refusePartialBtnConfigList([...mergedActivities, ...newActivities]);
|
|
1489
1507
|
// A caller-only code that arrived WITHOUT a type has no draft row to inherit
|
|
1490
1508
|
// from — refuse it here (the lint allows type-less permission-shape rows
|
|
1491
1509
|
// precisely because they are expected to match an existing node).
|
|
@@ -1595,6 +1613,8 @@ function normalizeWorkflowTemplate(template, draft, options = {}) {
|
|
|
1595
1613
|
// comment); give every changed/new node a fresh code, and remap caller routes
|
|
1596
1614
|
// so they follow.
|
|
1597
1615
|
const reconciled = rawCaller ? reconcileCallerActivityCodes(rawCaller, draftActivities, options) : undefined;
|
|
1616
|
+
if (rawCaller !== undefined)
|
|
1617
|
+
refusePartialBtnConfigList(rawCaller);
|
|
1598
1618
|
const activities = reconciled?.activities;
|
|
1599
1619
|
const codeMap = reconciled?.codeMap ?? new Map();
|
|
1600
1620
|
const remapCode = (value) => codeMap.get(String(value ?? "")) ?? value;
|
package/dist/skills.js
CHANGED
|
@@ -48,6 +48,57 @@ export function listSkills() {
|
|
|
48
48
|
return { name, description };
|
|
49
49
|
});
|
|
50
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
* r43 后 backlog:`omc help 编号` 曾零命中(高频中文业务词不是 topic 名)。
|
|
53
|
+
* 双路建议:①精选同义词表(高频业务词→topic,r24–r43 探针实测检索失败样本)
|
|
54
|
+
* ②全 topic 节标题子串扫描(自维护——节名改了建议跟着变)。
|
|
55
|
+
*/
|
|
56
|
+
const HELP_TOPIC_SYNONYMS = Object.freeze({
|
|
57
|
+
编号: ["omc-capabilities"],
|
|
58
|
+
序列号: ["omc-capabilities"],
|
|
59
|
+
单号: ["omc-capabilities"],
|
|
60
|
+
流水号: ["omc-capabilities"],
|
|
61
|
+
自动编号: ["omc-capabilities"],
|
|
62
|
+
审批: ["omc-five-piece-flow"],
|
|
63
|
+
流程: ["omc-five-piece-flow"],
|
|
64
|
+
回写: ["omc-business-configuration"],
|
|
65
|
+
跨模型: ["omc-business-configuration"],
|
|
66
|
+
规则: ["omc-business-configuration"],
|
|
67
|
+
字典: ["omc-business-configuration"],
|
|
68
|
+
权限: ["omc-business-configuration"],
|
|
69
|
+
视图: ["omc-write-hazards"],
|
|
70
|
+
过滤: ["omc-write-hazards"],
|
|
71
|
+
条件: ["omc-write-hazards"],
|
|
72
|
+
删除: ["omc-write-hazards"],
|
|
73
|
+
清理: ["omc-write-hazards"],
|
|
74
|
+
子表: ["omc-write-hazards"],
|
|
75
|
+
表单: ["omc-write-hazards"],
|
|
76
|
+
验收: ["omc-acceptance-criteria"],
|
|
77
|
+
入门: ["omc-start-here"],
|
|
78
|
+
拒绝: ["omc-refusals"],
|
|
79
|
+
});
|
|
80
|
+
export function suggestHelpTopics(query) {
|
|
81
|
+
if (query === undefined || query.length === 0)
|
|
82
|
+
return [];
|
|
83
|
+
const hits = new Set(HELP_TOPIC_SYNONYMS[query] ?? []);
|
|
84
|
+
for (const skill of listSkills()) {
|
|
85
|
+
if (skill.name.includes(query) || (skill.description ?? "").includes(query))
|
|
86
|
+
hits.add(skill.name);
|
|
87
|
+
}
|
|
88
|
+
// 节标题扫描:`omc help <topic> --section "<heading>"` 直达
|
|
89
|
+
for (const name of listSkills().map((skill) => skill.name)) {
|
|
90
|
+
const outline = readSkillOutline(name);
|
|
91
|
+
if (outline === undefined)
|
|
92
|
+
continue;
|
|
93
|
+
for (const section of outline.sections) {
|
|
94
|
+
if (section.heading.includes(query)) {
|
|
95
|
+
hits.add(`omc help ${name} --section "${section.heading}"`);
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return [...hits].slice(0, 5);
|
|
101
|
+
}
|
|
51
102
|
/** Read one methodology topic for `omc help <topic>`. */
|
|
52
103
|
export function readSkill(name) {
|
|
53
104
|
if (!/^[a-z0-9-]+$/.test(name))
|
package/dist/tool-types.js
CHANGED
|
@@ -91,6 +91,9 @@ function applyInputAliases(tool, input) {
|
|
|
91
91
|
continue;
|
|
92
92
|
normalized ??= { ...input };
|
|
93
93
|
normalized[canonical] = input[hit];
|
|
94
|
+
// 同义词键就地删除:归一后的输入只留规范键——写面未知键 fail-closed
|
|
95
|
+
// (r43 后)不把已消费的同义词误报成未知键。
|
|
96
|
+
delete normalized[hit];
|
|
94
97
|
}
|
|
95
98
|
return normalized ?? input;
|
|
96
99
|
}
|
|
@@ -111,8 +114,53 @@ function keyRenameHint(issuePath, input, tool) {
|
|
|
111
114
|
// "你传的是 schemaCode", which pointed at the wrong key.
|
|
112
115
|
return `(该对象在本工具里的键是 ${key},你传的是 ${synonyms.join("/")};键名对照见 omc-tool-selection 的 Key-name table)`;
|
|
113
116
|
}
|
|
117
|
+
/** Cheap closest-key suggestion: exact→prefix→shared-start bigrams, threshold keeps it honest. */
|
|
118
|
+
function closestSchemaKey(key, schemaKeys) {
|
|
119
|
+
if (schemaKeys.has(key))
|
|
120
|
+
return key;
|
|
121
|
+
let best;
|
|
122
|
+
let bestScore = 0;
|
|
123
|
+
for (const candidate of schemaKeys) {
|
|
124
|
+
const lower = key.toLowerCase();
|
|
125
|
+
const candLower = candidate.toLowerCase();
|
|
126
|
+
let score = 0;
|
|
127
|
+
if (candLower.startsWith(lower) || lower.startsWith(candLower))
|
|
128
|
+
score = 0.9;
|
|
129
|
+
else if (candLower.includes(lower) || lower.includes(candLower))
|
|
130
|
+
score = 0.7;
|
|
131
|
+
else {
|
|
132
|
+
let shared = 0;
|
|
133
|
+
for (let i = 0; i + 2 <= lower.length; i++)
|
|
134
|
+
if (candLower.includes(lower.slice(i, i + 2)))
|
|
135
|
+
shared++;
|
|
136
|
+
score = lower.length > 2 ? shared / (lower.length - 1) : 0;
|
|
137
|
+
}
|
|
138
|
+
if (score > bestScore) {
|
|
139
|
+
bestScore = score;
|
|
140
|
+
best = candidate;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return bestScore >= 0.4 ? best : undefined;
|
|
144
|
+
}
|
|
114
145
|
export function parseToolInput(tool, input) {
|
|
115
146
|
const normalized = applyInputAliases(tool, input);
|
|
147
|
+
// r30 引出、r42/r43 再证:静默丢弃错键是头号返工源(dictionary.save items
|
|
148
|
+
// 未知键、跨模型 filterCondition 错键同族)。写面 fail-closed——未知顶层键
|
|
149
|
+
// 当场拒绝并给最接近的合法键提示;读面保持宽松(探查性调用常见无害附加)。
|
|
150
|
+
if (tool.kind === "write") {
|
|
151
|
+
const schemaKeys = new Set(Object.keys(tool.inputSchema));
|
|
152
|
+
const unknown = Object.keys(normalized).filter((key) => !schemaKeys.has(key));
|
|
153
|
+
if (unknown.length > 0) {
|
|
154
|
+
const suggestions = unknown.map((key) => `${key}→${closestSchemaKey(key, schemaKeys) ?? "(无相近键,omc describe <tool> 看参数表)"}`);
|
|
155
|
+
throw new ToolInputError({
|
|
156
|
+
status: "error",
|
|
157
|
+
blocker: "tool-input-unknown-keys",
|
|
158
|
+
tool: tool.name,
|
|
159
|
+
message: `写面工具不接受未知键(会被静默丢弃):${unknown.join("、")}。最接近的合法键:${suggestions.join(";")}。参数表:omc describe ${tool.name}`,
|
|
160
|
+
issues: unknown.map((key) => ({ path: [key], code: "unrecognized_key", message: `unknown key; closest: ${closestSchemaKey(key, schemaKeys) ?? "none"}` })),
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
116
164
|
const result = z.object(tool.inputSchema).safeParse(normalized);
|
|
117
165
|
if (result.success)
|
|
118
166
|
return result.data;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stalfh233/omc-cli",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.12",
|
|
4
4
|
"description": "CloudPivot 8.6 configuration capability CLI — governed, fail-closed, coverage-manifest bounded.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -52,9 +52,9 @@
|
|
|
52
52
|
"access": "public"
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
|
-
"@stalfh233/omc-fake-platform": "0.5.
|
|
55
|
+
"@stalfh233/omc-fake-platform": "0.5.12",
|
|
56
56
|
"commander": "14.0.3",
|
|
57
|
-
"omc-core": "0.5.
|
|
57
|
+
"omc-core": "0.5.12",
|
|
58
58
|
"proper-lockfile": "4.1.2",
|
|
59
59
|
"write-file-atomic": "6.0.0",
|
|
60
60
|
"zod": "^3.23.8",
|
|
@@ -122,6 +122,7 @@ rules:
|
|
|
122
122
|
|
|
123
123
|
- **每个 filter 项必须有且只有一个条件来源**:`from`(动态 EQ)或一个比较键(`equals`/`notEquals`/`gt`/`gte`/`lt`/`lte` + 标量)。缺来源 `intent-rule-leaf-source-missing` 拒绝;**绝不**静默成 `EQ null`。
|
|
124
124
|
- 动作节点的条件分「定位数据」与「执行门」两类,**别混**(详见下「规则图的触发条件」)。
|
|
125
|
+
- **跨模型 UPDATE 目标查找键=`filterCondition.mainConditions`**(`[[{targetSchemaDataItem, ruleConditionType, ruleValueType, value|currentSchemaDataItem}]]`,非触发条件的 conditionGroups——错键静默反序列化为空,引擎恒报「查找范围不存在数据」形似跨模型不可用,r42 曾误判;校验 `rule-graph-filter-condition-wrong-key` 已拦)。连坑:叶取值键 `value` 非 fixedValue;`rule.node.update` 只写 DRAFT,改完须 `rule.enable`/`--publish` 重发布才生效。
|
|
125
126
|
- **跨模型写(`targetObjectCode`≠规则模型)平台支持、可运行时生效**(2026-09-28 真机复核)。两个前提:① 承载它的规则必须真的触发——`Create` 规则在提交时触发;`Update` 规则只在 `data.update` 触发,**submit 不执行它**(放进 `Update` 是典型静默空转,lint 警告 `rule-graph-cross-model-update-trigger-inert`;也可绑定流程事件如 `end`)。② 目标行必须已提交——目标查询不匹配 DRAFT 行,`data.save` 未 submit 的行不回写。
|
|
126
127
|
- 关联字段:`type: WORK_SHEET` + `relativeCode: <目标模型>` + `options.displayField`,与 `field.create` 行形状一致。(键名是 `relativeCode`,wire 键 `relativeSchemaCode`,别混。)
|
|
127
128
|
- 节点显示名用 activity 级的 `name`(`participant.name` 只作参与者说明,平台不认作节点名;`{type: Originator, name: 部门负责人}` 会被工具提升为节点名)。
|
|
@@ -55,7 +55,7 @@ omc help omc-write-hazards
|
|
|
55
55
|
- **BIZ_ACTION 判成功只看传输**:REST 适配器解析响应后直接返回,**不校验 `errcode`**。扩展接口回 `{"errcode":50000}` + HTTP 200 会被当成节点成功(日志 `biz action node execute successful`)。要阻断必须走表单 `onValidate` 或数据规则,不能靠业务返回码。
|
|
56
56
|
- **`rule.run`/自定义规则不回写出参**(r26 实证 2 轮):出参合并只发生在预置 Update 规则的执行上下文;`rule.run` 手动触发与自定义规则不把服务出参写回本模型——出参落表要走「BIZ_ACTION 节点 outputMappings + 数据事件触发」,验收用 data.load 看字段而非 rule.run 回执。
|
|
57
57
|
- **出参回写三前提(r28b 8 组实验定案,单点总纲)**:① 响应 JSON 根级平铺出参键 ② 预置 Create/Update 规则上下文 ③ data.submit/data.update 触发(data.save 不触发)。三者缺一出参恒 null 且节点日志仍 successful——外部接口回信封的(`{code,message,data:[...]}`),先落平铺整形端点(平台扩展)再配 outputMappings,不要反复换规则形状试。
|
|
58
|
-
- **布尔出参映射到文本字段 → ClassCastException 50000**(r28b
|
|
58
|
+
- **布尔出参映射到文本字段 → ClassCastException 50000**(r28b):LOGICAL 出参映射 SHORT_TEXT 即炸——布尔只映射 LOGICAL 字段,落文本先在扩展侧转字符串。
|
|
59
59
|
- **审批提交的 formData 带子表数组 = 追加行**(r28b 实证):approval submit 的 formData 里带子表数组会被平台当「追加行」处理,审批回执只回传主表字段——审批链路里要改子表行,用 data.update 单独写,不要塞进 formData。
|
|
60
60
|
- **workItemId 不在 instanceActivity 里**(r28b 实证):流程实例活动查询不返回 workItemId;办理待办要的 workItemId 从 `search_workitems?state=PENDING` 的 `content[].id` 取。
|
|
61
61
|
- **模型创建/删除非原子,会有半态残留**(r28b 实证 ~20min):model.create 在表单层撞错(如「表单已存在」)时 schema 可能已半落库——refused 后换名重试前先 model.list 核对半态并清理;同理模型删除后平台读面不可见但库里可能留僵尸行(引擎报 `appFunction not exist` 就是它在作祟),迁移/复用编码前先核对。
|
|
@@ -193,13 +193,15 @@ omc call field.publish --env local --args '{"schemaCode":"expense"}'
|
|
|
193
193
|
- **`transferDepartment` 迁人不迁作用域**(r33 真机):换部门后 `role.userScope` 仍指旧部门——迁移后复核成员作用域。
|
|
194
194
|
- **`user.delete` 是软删**(r33 真机):回 `deleted:true` 但 `user.info` 仍 `exists:true`(DISABLE)——验证口径用列表/状态,别称物理清除。
|
|
195
195
|
- **「名称重复 200001」≠ 失败**(r33 真机):多为首次已建成、重发才撞重复——先读回确认首建结果再决定。
|
|
196
|
-
- **删了的东西≠删干净(孤儿三连,1101
|
|
197
|
-
- **两套条件 DSL(r38/r39)**:listview
|
|
198
|
-
-
|
|
199
|
-
- **submit 被拒仍可能已落草稿(r40
|
|
200
|
-
- **空聚合≠模型存在(r40)**:rule.list
|
|
201
|
-
- **form.publish(rebuild) 后立即 submit 可能丢字段(r41 疑似竞态)**:链内演示行
|
|
196
|
+
- **删了的东西≠删干净(孤儿三连,1101 三例)**:①删模型留收藏/h_biz_* 行→日志 appFunction not exist;②规则死服务 A1→submit 500015;③流程模板删后绑定残留→getWorkflow 50000。都是报错在别处——先查孤儿引用。
|
|
197
|
+
- **两套条件 DSL(r38/r39)**:listview `conditions`={propertyCode, filterType, defaultValue};data.list `filter`={op, field, value}。互抄必错。
|
|
198
|
+
- **表单读回以控件清单为准(r40)**:`published` 布尔会抖动、draftViewJson 可含悬空引用(rebuild 自清)——判定依据=publishedControlCodes 内容;configure_form 回执已点名 missingFieldCodes。
|
|
199
|
+
- **submit 被拒仍可能已落草稿(r40)**:许可证/权限拒绝可能拒答前已写草稿——拒后对账 total,残留单清。
|
|
200
|
+
- **空聚合≠模型存在(r40)**:rule.list 对不存在模型静默回空(已修,空时标 modelPresent:false);datarule.list 响亮拒 601003。判存在性用 model.inspect。
|
|
201
|
+
- **form.publish(rebuild) 后立即 submit 可能丢字段(r41 疑似竞态)**:链内演示行 order_no 落 null,延迟复现正常。落行后 data.load 抽验,缺值单补。
|
|
202
202
|
- **data.list 选项列投影可为 null(r40/r41)**:`all:true`/queryCode 下 RADIO 读 null,`data.load`/`filter` 才有值——验证选项字段以 data.load 为权威。
|
|
203
|
+
- **流程绑定行不可 data.save 直改(r42)**:COMPLETED 行直改打回 PROCESSING、值不落、可能岔行——走提交流程。连续 submit 撞限流 100005——批量先 workitem.list 取 workItemId。
|
|
204
|
+
- **带流程模型 data.submit 必停审批位(r43)**:批量初始化滞留待办——流程加条件分支按单据类型直达结束。SEQNO 不回收。
|
|
203
205
|
- **读回形状≠可写形状**(r38):`listview.get` 读回条件带 `null` 字段,原样回写被拒——读回是输出形态,可写面有自己的契约;null 归一(false/省略)或只回写要改的键。
|
|
204
206
|
- **data.list 的 columns 收窄可能静默空**(r38):非视图列字段投影 null;甚至 columns+分页 total>0 而 items 0——判"无数据"先去 columns 复查或用 data.load。
|
|
205
207
|
|