@stalfh233/omc-cli 0.4.64 → 0.4.65
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/lanes/rule-graph.js
CHANGED
|
@@ -1664,7 +1664,7 @@ export function validateRuleNodeGraph(graph, ruleCode, schemaCode, options = {})
|
|
|
1664
1664
|
code: "rule-graph-cross-model-child-source-fanout",
|
|
1665
1665
|
nodeCode,
|
|
1666
1666
|
key: "filterCondition.mainConditions",
|
|
1667
|
-
message: `${nodeCode}: 跨模型 UPDATE
|
|
1667
|
+
message: `${nodeCode}: 跨模型 UPDATE 的定位条件用了点号来源(${childSourcedLeaves.join("、")})——1.1.25 的 DatasetImpl 把 "x.y" 当作**子表路径**解析(parts[0] 找子表、收集该列所有行的值),既不是"关联字段.id",也不会按每行各更新一次:目标主表仍只查询一次,结果 0 行命中(r5/r6 真机:保存发布全绿、字段不变)。单跳关联字段直接用字段 code(引擎会取关联对象的 id,真机 2026-10-21 已验证);多跳(a.b)没有可用形状——把目标 id 用表单映射落到本模型的一个字段上再引用它,或走二开。`,
|
|
1668
1668
|
});
|
|
1669
1669
|
if (!targetIsChild && Array.isArray(filterRecord.sheetConditions) && filterRecord.sheetConditions.length > 0)
|
|
1670
1670
|
blockers.push({
|
package/dist/lanes/workflow.js
CHANGED
|
@@ -85,6 +85,57 @@ const WORKFLOW_ROUTE_CONDITION_KEYS = ["text", "formula", "utilizeElse", "fixedP
|
|
|
85
85
|
* and the line silently never fires (the same class of silent no-op that bit
|
|
86
86
|
* checkType=2). Only caller-supplied `routes`/`rules` are checked.
|
|
87
87
|
*/
|
|
88
|
+
/**
|
|
89
|
+
* AI-Platform 1.1.25 rejects a template whose first activity after START is not
|
|
90
|
+
* the originator fill node: WorkflowTemplateValidateUtility.validateStartActivity
|
|
91
|
+
* emits 401035 「发起节点的参与者只能是{Originator}」 and publish answers
|
|
92
|
+
* errcode 0 with a ValidateMessage[] (nothing is stored). Live 2026-10-21: a
|
|
93
|
+
* role-approval node placed directly after START failed publish with exactly
|
|
94
|
+
* that code, and the generic message did not name the cause.
|
|
95
|
+
*/
|
|
96
|
+
function assertFirstActivityIsOriginator(template, tool, merged) {
|
|
97
|
+
// Only gate AUTHORING intent: the caller must have sent both a START node and
|
|
98
|
+
// routes. A partial/stored-draft publish is left to the platform validator
|
|
99
|
+
// (401035), which the tool already surfaces faithfully.
|
|
100
|
+
const callerRows = callerActivities(template);
|
|
101
|
+
const callerRoutes = template.routes ?? template.rules ?? template.edges;
|
|
102
|
+
if (!Array.isArray(callerRows) || !Array.isArray(callerRoutes))
|
|
103
|
+
return;
|
|
104
|
+
if (!callerRows.some((activity) => String(activity.activityType ?? activity.type ?? "") === "START"))
|
|
105
|
+
return;
|
|
106
|
+
// Assert on the MERGED body: participant shorthands ({type:Originator,name:…})
|
|
107
|
+
// are normalized to "{Originator}" there.
|
|
108
|
+
const activities = callerActivities(merged ?? template);
|
|
109
|
+
const rawRoutes = merged ? (merged.routes ?? merged.rules ?? merged.edges) : callerRoutes;
|
|
110
|
+
if (!Array.isArray(activities) || !Array.isArray(rawRoutes))
|
|
111
|
+
return;
|
|
112
|
+
const start = activities.find((activity) => String(activity.activityType ?? activity.type ?? "") === "START");
|
|
113
|
+
if (!start)
|
|
114
|
+
return;
|
|
115
|
+
const startCode = activityCodeOf(start);
|
|
116
|
+
const first = rawRoutes
|
|
117
|
+
.map((entry) => (entry ?? {}))
|
|
118
|
+
.find((route) => String(route.preNode ?? route.preActivityCode ?? route.pre ?? route.from ?? "") === startCode);
|
|
119
|
+
if (!first)
|
|
120
|
+
return;
|
|
121
|
+
const nextCode = String(first.postNode ?? first.postActivityCode ?? first.post ?? first.to ?? "");
|
|
122
|
+
const next = activities.find((activity) => activityCodeOf(activity) === nextCode);
|
|
123
|
+
if (!next)
|
|
124
|
+
return;
|
|
125
|
+
const type = String(next.activityType ?? next.type ?? "");
|
|
126
|
+
const participant = String(next.participant ?? "").trim();
|
|
127
|
+
if (type === "PARTICIPANT" && /^\{Originator\}$/i.test(participant))
|
|
128
|
+
return;
|
|
129
|
+
throw new ToolRefusalError({
|
|
130
|
+
status: "refused",
|
|
131
|
+
blocker: "workflow-first-activity-not-originator",
|
|
132
|
+
workflowCode: String(template.workflowCode ?? template.code ?? ""),
|
|
133
|
+
activityCode: nextCode,
|
|
134
|
+
message: `${tool}: START 之后的第一个节点必须是参与者为 {Originator} 的填写节点(当前是 ${type || "?"}「${String(next.activityName ?? next.name ?? nextCode)}」${participant ? `,参与者 ${participant}` : ""})。` +
|
|
135
|
+
`平台校验 WorkflowTemplateValidateUtility.validateStartActivity 对此报 401035「发起节点的参与者只能是{Originator}」,publish 回 errcode 0 但一个 ValidateMessage[] 就把模板挡在门外、不落库(真机 2026-10-21)。` +
|
|
136
|
+
`正确形状:Start → 填写({Originator}) → 审批/条件分支 … → End;审批节点不能直接接在 START 后。`,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
88
139
|
function validateWorkflowRouteConditions(template, tool) {
|
|
89
140
|
const raw = template.routes ?? template.rules ?? template.edges;
|
|
90
141
|
if (!Array.isArray(raw))
|
|
@@ -1818,6 +1869,7 @@ export function registerWorkflowLane(tools) {
|
|
|
1818
1869
|
const merge = args.merge === true || (args.merge !== false && activitiesLackBoundaries(template));
|
|
1819
1870
|
const body = await mergedTemplateBody(session, args.schemaCode, args.code, template, merge);
|
|
1820
1871
|
await backfillDesignerDefaults(session, args.schemaCode, body, template);
|
|
1872
|
+
assertFirstActivityIsOriginator(template, "workflow.update", body);
|
|
1821
1873
|
validateWorkflowRender(body, "workflow.update");
|
|
1822
1874
|
// Live: workflow/update does NOT run the template validator — it stores a
|
|
1823
1875
|
// refused participant silently and only /publish answers the
|
|
@@ -1868,6 +1920,7 @@ export function registerWorkflowLane(tools) {
|
|
|
1868
1920
|
// boundaries is a partial edit → delta merge (see activitiesLackBoundaries).
|
|
1869
1921
|
const body = await mergedTemplateBody(session, args.schemaCode, args.code, template, activitiesLackBoundaries(template));
|
|
1870
1922
|
await backfillDesignerDefaults(session, args.schemaCode, body, template);
|
|
1923
|
+
assertFirstActivityIsOriginator(template, "workflow.publish", body);
|
|
1871
1924
|
const unregistered = unregisteredParticipantFunctions(body);
|
|
1872
1925
|
if (unregistered.length > 0)
|
|
1873
1926
|
throw new ToolRefusalError({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stalfh233/omc-cli",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.65",
|
|
4
4
|
"description": "CloudPivot 8.6 configuration capability CLI — governed, fail-closed, coverage-manifest bounded.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -50,9 +50,9 @@
|
|
|
50
50
|
"access": "public"
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
|
-
"@stalfh233/omc-fake-platform": "0.4.
|
|
53
|
+
"@stalfh233/omc-fake-platform": "0.4.65",
|
|
54
54
|
"commander": "14.0.3",
|
|
55
|
-
"omc-core": "0.4.
|
|
55
|
+
"omc-core": "0.4.65",
|
|
56
56
|
"proper-lockfile": "4.1.2",
|
|
57
57
|
"write-file-atomic": "6.0.0",
|
|
58
58
|
"zod": "^3.23.8",
|
|
@@ -151,6 +151,8 @@ rules:
|
|
|
151
151
|
- **两条路**:模型+表单+流程一揽子交付走**意图**(`intent` 的 `workflow` 能力,声明式一次 apply);只动流程或想逐步控制走**低层链** `workflow.create → workflow.update(权限/节点)→ workflow.publish`,标准链配方见 `omc describe workflow.update`(含多级审批/participant 三形态/自动回填说明)。流程权限细节见下节。
|
|
152
152
|
- 一个模型只能有一个流程;已有流程的模型直接 `workflow.update`,不要另建。
|
|
153
153
|
- **核验状态用投影,别重复整条读**:`data.load --args '{"schemaCode":..,"bizObjectId":..,"fields":["sequenceStatus","<业务状态字段>"]}'` 只回指定字段(缺失项在 `missingFields`)——r4 实测同一对象被整条读 74 次;大结果先 `artifact get <id> --summary` → `--keys/--jq`,别整包内联进推理。
|
|
154
|
+
- **流程第一个节点必须是 `{Originator}` 填写节点**:平台 `WorkflowTemplateValidateUtility.validateStartActivity` 对「START 后直接接审批」报 401035「发起节点的参与者只能是{Originator}」,publish 回 errcode 0 但一个 ValidateMessage[] 就把模板挡在门外、不落库(真机 2026-10-21)。OMC 0.4.65 起在 `workflow.update`/`publish` 出门前拒绝(`workflow-first-activity-not-originator`)。
|
|
155
|
+
- **跨模型回写的目标行必须已提交(COMPLETED)**:DRAFT 行不参与目标查询,运行 0 行命中且日志只有「查找范围不存在数据」。单跳关联字段可直接作 DYNAMIC 来源(引擎取关联对象 id);点号路径按子表解析,`关联字段.id` / 两跳路径都不支持。
|
|
154
156
|
- **参与人解析失败分两种,别当成同一种(1.1.25 实测)**:① **解析为空**(角色零成员、成员未挂部门、自由文本、角色码不存在)→ 引擎把任务**退回发起人**,流程继续,症状是「审批人显示成我自己」,`data.submit` 与 `workitem.list` 都正常;② **参与人函数未注册**(`FindUsersByRoleFunction` / `FindUsersSpecByRoleFunction`,1.1.25 未内置)→ `FunctionNotFoundException`、不生成待办、实例卡 PROCESSING 而接口仍回 `errCode 0`。区分用 `omc call workflow.instanceActivity --args '{"workflowInstanceId":"<id>"}'`:无 `status:1` 节点=卡住;`status:1` 且参与人全是发起人=`diagnosis.warnings` 给 `participant-fallback-to-originator`。不要从 `workflow.get_published` 的摘要回抄函数式参与人;内置写法只有 `FindUserByRole(org, roleCode[, order])`,跨部门/全局角色优先用参与者对象数组(角色 id)。
|
|
155
157
|
|
|
156
158
|
## 流程字段权限
|
|
@@ -144,6 +144,8 @@ omc call field.publish --env local --args '{"schemaCode":"expense"}'
|
|
|
144
144
|
|
|
145
145
|
对应源码:`UpdateServiceExecutor.getUpdatedMainBizObject()` 只把 `mainConditions` 交给目标主表查询;`sheetConditions` 只在 `doUpdateChildData()` 里被读取。
|
|
146
146
|
|
|
147
|
+
两个前提(真机 2026-10-21):① **目标行必须已提交**——DRAFT 行不参与目标查询,`data.save` 建的行永远改不到,日志只有「查找范围不存在数据」;② **单跳关联字段可直接作 DYNAMIC 来源**(引擎取关联对象 id),点号路径按子表解析,`关联字段.id` 与两跳都不支持——用表单映射把目标 id 落到本模型字段再引用。
|
|
148
|
+
|
|
147
149
|
由此得到四条使用纪律:
|
|
148
150
|
|
|
149
151
|
1. **主表目标上的 `sheetConditions` 是死配置**——不会生效(OMC 0.4.64 起给 warning)。
|