@stalfh233/omc-cli 0.4.81 → 0.4.82
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 +2 -1
- package/coverage/m1-coverage-manifest-v1.json +4 -0
- package/dist/endpoints.js +12 -0
- package/dist/lanes/runtime-data.js +2 -2
- package/dist/lanes/workflow-contract.js +15 -0
- package/dist/lanes/workflow-coverage.js +3 -0
- package/dist/lanes/workflow.js +188 -0
- package/dist/runtime-support.js +92 -1
- package/package.json +3 -3
- package/skills/omc-capabilities.md +1 -1
package/README.md
CHANGED
|
@@ -130,10 +130,11 @@ omc call discover_environment --env <env> --args '{}' # 环境就绪(真实
|
|
|
130
130
|
- **intent**:`partial` 表示该能力可声明式(`omc intent`)覆盖一部分——通常 create/update;
|
|
131
131
|
`none` 表示只走 `omc call`。删除类、方法面、分类/连接池等仍走 `omc call` 逃生舱。
|
|
132
132
|
|
|
133
|
-
## 近期能力(0.4.74 → 0.4.
|
|
133
|
+
## 近期能力(0.4.74 → 0.4.82)
|
|
134
134
|
|
|
135
135
|
| 版本 | 新增 / 修复 |
|
|
136
136
|
|---|---|
|
|
137
|
+
| 0.4.82 | **流程发起权限(40009)**:新增 `workflow.permissions` / `workflow.addPermission` / `workflow.removePermission`(`/workflow/list_permission`、`create_permission`、`delete_permission`,unitType 线上为枚举序号 1=部门 2=角色 3=用户)。`data.submit`/`data.save+workflowCode` 的 40009 现在直接回 `workflow-start-permission-denied`:带上该流程的权限行现状、两种成因(无权限行 / 发起人部门记录已失效)与可粘贴的修复命令,不再是「流程发起权限不足」五个字 |
|
|
137
138
|
| 0.4.81 | **r11 真机探针修复**:`rule.save` 对 `AGGREGATION` 规则节点**写前 fail-closed**(平台会存进去、运行时 50000,真实路径是 `omc compute`/datarule type2);`omc next "统计子表行数"` 路由补 `datarule` 阶梯与 `field-compute-subtable-sum` 配方;`org.user.search` 省略关键字时**按部门树列全员**(平台 `wd` 不支持空串,此前只能枚举 0–9);`omc artifact get --jq` 无值回键清单(不再 commander 报错);`usage` 的 `faces` 纳入 `--jq/--summary/--keys/--path-only/--extract`(此前读面投影完全不记账) |
|
|
138
139
|
| 0.4.80 | **省 token 与用量账本**:`omc help <topic>` 默认只回节索引(约 1KB,含每节字节数),`--all` 才读整篇;`omc artifact get` 正文 >8KB 默认只回形状(`--full` 才整包);`omc usage` 默认只回汇总(`byTool`/`redundancy`/`estimatedTokens`),`--records` 才展开;`omc script` 的**每行工具调用单独记账**(`script-call` + `parentId`);未绑定环境的目录回落 `$OMC_CONFIG_HOME/usage/<cwd-hash>.jsonl` |
|
|
139
140
|
| 0.4.79 | README 对齐 0.4.79 实际工具面(142 工具 / 16 能力) |
|
|
@@ -339,6 +339,9 @@
|
|
|
339
339
|
"workflow.bindRule",
|
|
340
340
|
"workflow.setButtons",
|
|
341
341
|
"workflow.delete",
|
|
342
|
+
"workflow.permissions",
|
|
343
|
+
"workflow.addPermission",
|
|
344
|
+
"workflow.removePermission",
|
|
342
345
|
"configure_workflow",
|
|
343
346
|
"workflow.validate",
|
|
344
347
|
"org.role.users",
|
|
@@ -365,6 +368,7 @@
|
|
|
365
368
|
"scopes": [
|
|
366
369
|
"greenfield workflow authoring",
|
|
367
370
|
"workflow node form-button checkboxes",
|
|
371
|
+
"workflow start permission (who may originate)",
|
|
368
372
|
"organization role member read (role/users)",
|
|
369
373
|
"organization role member management scope read (role/get_ouscope)",
|
|
370
374
|
"organization role group create (role/add_role_group)",
|
package/dist/endpoints.js
CHANGED
|
@@ -152,6 +152,18 @@ export const ENDPOINTS = {
|
|
|
152
152
|
workflowValidate: "/api/api/workflow/validate",
|
|
153
153
|
workflowPublish: "/api/api/workflow/publish",
|
|
154
154
|
workflowDelete: "/api/api/workflow/delete",
|
|
155
|
+
// 发起权限 (who may START the flow). Without a row here — or with a role/
|
|
156
|
+
// department row the caller is not in — `data.submit`/`data.save+workflowCode`
|
|
157
|
+
// answers 40009 流程发起权限不足 (WorkflowAspect.validateWorkflowPermission →
|
|
158
|
+
// WorkflowManagementFacadeImpl.getWorkflowTemplateHeadersBySchemaCode). Endpoints
|
|
159
|
+
// from WorkflowTemplateController (cloudpivot-engine 8.6.26; live 1.1.25):
|
|
160
|
+
// GET /workflow/list_permission?workflowCode= → [{id,name,unitType,fullName,corpId,code}]
|
|
161
|
+
// POST /workflow/create_permission body {workflowCode,unitId,unitType,corpId?}
|
|
162
|
+
// unitType is the ENUM INDEX on the wire: 1=部门 2=角色 3=用户 (UnitType @JsonValue)
|
|
163
|
+
// POST/DELETE /workflow/delete_permission?permissionId=
|
|
164
|
+
workflowListPermission: "/api/api/workflow/list_permission",
|
|
165
|
+
workflowCreatePermission: "/api/api/workflow/create_permission",
|
|
166
|
+
workflowDeletePermission: "/api/api/workflow/delete_permission",
|
|
155
167
|
workflowSearchWorkItems: "/api/api/runtime/workflow/search_workitems",
|
|
156
168
|
workflowGetBizObjectByWfInstanceId: "/api/api/runtime/workflow/getBizObjectByWfInstanceId",
|
|
157
169
|
workflowListInstanceActivity: "/api/api/runtime/workflow/list_workflow_instance_activity",
|
|
@@ -699,7 +699,7 @@ export function registerRuntimeDataLane(tools) {
|
|
|
699
699
|
...(replayToken ? { replayToken } : {}),
|
|
700
700
|
bizObject: { schemaCode: args.schemaCode, sheetCode, data: { ...formData, id: objectId, sequenceStatus: "DRAFT" } },
|
|
701
701
|
},
|
|
702
|
-
}), formData);
|
|
702
|
+
}), formData, args.workflowCode);
|
|
703
703
|
const vo = (performed.data ?? {});
|
|
704
704
|
const returnedId = typeof vo.objectId === "string" && vo.objectId ? vo.objectId : typeof vo.bizObjectId === "string" && vo.bizObjectId ? vo.bizObjectId : objectId;
|
|
705
705
|
const workflowInstanceId = typeof vo.workflowInstanceId === "string" ? vo.workflowInstanceId : null;
|
|
@@ -1087,7 +1087,7 @@ export function registerRuntimeDataLane(tools) {
|
|
|
1087
1087
|
path: ENDPOINTS.runtimeFormSubmit,
|
|
1088
1088
|
method: "POST",
|
|
1089
1089
|
body: payload,
|
|
1090
|
-
}));
|
|
1090
|
+
}), undefined, hasWorkflowCode ? explicitWorkflowCode : undefined);
|
|
1091
1091
|
const performedData = (performed.data ?? {});
|
|
1092
1092
|
let readback = { formType };
|
|
1093
1093
|
if (formType === "2") {
|
|
@@ -174,6 +174,18 @@ export const WORKFLOW_LANE_CONTRACT = Object.freeze({
|
|
|
174
174
|
scope: WORKFLOW_SCOPE,
|
|
175
175
|
description: "删除工作流模板(`/api/api/workflow/delete`),回读确认缺失。",
|
|
176
176
|
}),
|
|
177
|
+
"workflow.permissions": Object.freeze({
|
|
178
|
+
scope: "workflow start permission (who may originate)",
|
|
179
|
+
description: "列出谁能发起该流程(`GET /api/api/workflow/list_permission?workflowCode=` → {id,name,unitType,fullName,corpId,code};1=部门 2=角色 3=用户)。**`data.submit` 报 40009 时先看这里**:PART_VISIABLE 且本表无命中行 ⇒ 除平台管理员(`h_perm_admin` 的 ADMIN/SYS_MNG,非登录 isAdmin)外无人能发起;发布流程不会自动建这一行。",
|
|
180
|
+
}),
|
|
181
|
+
"workflow.addPermission": Object.freeze({
|
|
182
|
+
scope: "workflow start permission (who may originate)",
|
|
183
|
+
description: "授予发起权限(`POST /api/api/workflow/create_permission`,body `{workflowCode,unitId,unitType,corpId}`;unitType 取枚举序号 1=部门 2=角色 3=用户)。unitId 用 org.department.list / org.role.list / org.user.search 取。重复授予按 402003 幂等跳过,写后回读确认。",
|
|
184
|
+
}),
|
|
185
|
+
"workflow.removePermission": Object.freeze({
|
|
186
|
+
scope: "workflow start permission (who may originate)",
|
|
187
|
+
description: "撤销发起权限(`POST /api/api/workflow/delete_permission?permissionId=`)。首选传 `permissionId`(`workflow.permissions` 的 id);也可传 `workflowCode`+`unitId`+`unitType`,工具解析该主体的显示名后匹配(`list_permission` 不回 unitId)。缺失即幂等成功。",
|
|
188
|
+
}),
|
|
177
189
|
"workflow.validate": Object.freeze({
|
|
178
190
|
scope: WORKFLOW_SCOPE,
|
|
179
191
|
description: "只读预检(`POST /api/api/workflow/validate`):合成草稿模板(或可选 `template` 按 merge 语义)后返回 ValidateMessage[],拆出 errors/warnings,`valid = 无 ERROR`。用于 publish 前先看平台会不会拒——平台对 publish 常回 errcode:0 但 ValidateMessage[] 里带 ERROR,本工具把它显式暴露,避免「publish 看似成功却没落库」。不写任何状态。",
|
|
@@ -223,6 +235,9 @@ export function assertWorkflowContract() {
|
|
|
223
235
|
assertDescriptionCarries(WORKFLOW_LANE_CONTRACT, "workflow.publish", ["workflow/publish", "template"]);
|
|
224
236
|
assertDescriptionCarries(WORKFLOW_LANE_CONTRACT, "workflow.setButtons", ["buttonPermission", "customBtns", "childTableBtns", "formButtonCodes", "form-button-absent", "publish:false"]);
|
|
225
237
|
assertDescriptionCarries(WORKFLOW_LANE_CONTRACT, "workflow.bindRule", ["startEventHandler", "cancelEventHandler", "activateEventHandler", "endEventHandler", "bizActions", "bizActionType", "BIZ_RULE", "publish:false", "enabledRuleCodes", "workflow-bind-rule-absent", "activityCode", "nodeEvent", "endActivity", "activityApproved"]);
|
|
238
|
+
assertDescriptionCarries(WORKFLOW_LANE_CONTRACT, "workflow.permissions", ["list_permission", "40009", "PART_VISIABLE"]);
|
|
239
|
+
assertDescriptionCarries(WORKFLOW_LANE_CONTRACT, "workflow.addPermission", ["create_permission", "unitType", "unitId"]);
|
|
240
|
+
assertDescriptionCarries(WORKFLOW_LANE_CONTRACT, "workflow.removePermission", ["delete_permission", "permissionId"]);
|
|
226
241
|
assertDescriptionCarries(WORKFLOW_LANE_CONTRACT, "org.role.list", ["organization/role/list", "type", "Originator"]);
|
|
227
242
|
assertDescriptionCarries(WORKFLOW_LANE_CONTRACT, "org.department.list", ["organization/department/tree", "parentId"]);
|
|
228
243
|
assertDescriptionCarries(WORKFLOW_LANE_CONTRACT, "org.user.search", ["organization/user/search", "wd", "type 3"]);
|
|
@@ -18,6 +18,9 @@ export const WORKFLOW_COVERAGE = declareLaneCoverage(WORKFLOW_LANE_CONTRACT.lane
|
|
|
18
18
|
claim("workflow.bindRule", T["workflow.bindRule"].scope),
|
|
19
19
|
claim("workflow.setButtons", T["workflow.setButtons"].scope),
|
|
20
20
|
claim("workflow.delete", T["workflow.delete"].scope),
|
|
21
|
+
claim("workflow.permissions", T["workflow.permissions"].scope),
|
|
22
|
+
claim("workflow.addPermission", T["workflow.addPermission"].scope),
|
|
23
|
+
claim("workflow.removePermission", T["workflow.removePermission"].scope),
|
|
21
24
|
claim("configure_workflow", T["configure_workflow"].scope),
|
|
22
25
|
claim("workflow.validate", T["workflow.validate"].scope),
|
|
23
26
|
// Organization writes (role group / role / members / management scope).
|
package/dist/lanes/workflow.js
CHANGED
|
@@ -2724,6 +2724,194 @@ export function registerWorkflowLane(tools) {
|
|
|
2724
2724
|
};
|
|
2725
2725
|
}),
|
|
2726
2726
|
}));
|
|
2727
|
+
// ── 发起权限 (who may originate) ────────────────────────────────────────────
|
|
2728
|
+
//
|
|
2729
|
+
// 40009 流程发起权限不足 is the single most opaque refusal on this platform:
|
|
2730
|
+
// `data.submit` (and `data.save` with workflowCode) answers it, the message
|
|
2731
|
+
// names no permission, and there is no CLI face to inspect or grant one. r11
|
|
2732
|
+
// (2026-10-24) burned 7 submit attempts and could not deliver the whole
|
|
2733
|
+
// workflow section; the platform source shows the check is concrete:
|
|
2734
|
+
// `WorkflowAspect.validateWorkflowPermission` → template header must exist and
|
|
2735
|
+
// be published → `getWorkflowTemplateHeadersBySchemaCode(userId, schemaCode)`:
|
|
2736
|
+
// • the user's root department must resolve (`getAllParentIds` non-empty),
|
|
2737
|
+
// • `visibleType=ALL_VISIABLE` templates are open to everyone,
|
|
2738
|
+
// • otherwise a `h_workflow_permission` row must match the user, their
|
|
2739
|
+
// department (or any ancestor), or one of their roles,
|
|
2740
|
+
// • platform admins (`h_perm_admin` ADMIN/SYS_MNG) bypass all of it.
|
|
2741
|
+
// Publishing a flow creates NO permission row, so a freshly published flow is
|
|
2742
|
+
// unusable by anyone who is not a platform admin.
|
|
2743
|
+
const permissionRows = async (session, workflowCode) => wireRows((await wireCall(session, { path: ENDPOINTS.workflowListPermission, query: { workflowCode } })).data);
|
|
2744
|
+
// `list_permission` echoes the PERMISSION row id plus the unit's display name
|
|
2745
|
+
// and code — never the unitId (WorkflowTemplateController.getPermissionMap).
|
|
2746
|
+
// So identity for readback/removal is resolved from the unit's own face.
|
|
2747
|
+
const unitIdentity = async (session, unitType, unitId) => {
|
|
2748
|
+
try {
|
|
2749
|
+
if (unitType === 1) {
|
|
2750
|
+
const row = (await wireCall(session, { path: ENDPOINTS.organizationDepartmentGet, query: { deptId: unitId } })).data;
|
|
2751
|
+
return { name: row ? String(row.name ?? "") || null : null, code: row ? (String(row.code ?? "") || null) : null };
|
|
2752
|
+
}
|
|
2753
|
+
if (unitType === 2) {
|
|
2754
|
+
const rows = wireRows((await wireCall(session, { path: ENDPOINTS.organizationRoleList, query: { expandAll: "true" } })).data);
|
|
2755
|
+
const walk = (entries) => {
|
|
2756
|
+
for (const entry of entries) {
|
|
2757
|
+
if (String(entry.id ?? "") === unitId)
|
|
2758
|
+
return entry;
|
|
2759
|
+
const children = Array.isArray(entry.children) ? entry.children : [];
|
|
2760
|
+
const hit = walk(children);
|
|
2761
|
+
if (hit)
|
|
2762
|
+
return hit;
|
|
2763
|
+
}
|
|
2764
|
+
return undefined;
|
|
2765
|
+
};
|
|
2766
|
+
const hit = walk(rows);
|
|
2767
|
+
return { name: hit ? String(hit.name ?? "") || null : null, code: hit ? (String(hit.code ?? "") || null) : null };
|
|
2768
|
+
}
|
|
2769
|
+
const row = (await wireCall(session, { path: ENDPOINTS.organizationUserInfo, query: { id: unitId } })).data;
|
|
2770
|
+
return { name: row ? String(row.name ?? "") || null : null, code: row ? (String(row.username ?? "") || null) : null };
|
|
2771
|
+
}
|
|
2772
|
+
catch {
|
|
2773
|
+
return { name: null, code: null };
|
|
2774
|
+
}
|
|
2775
|
+
};
|
|
2776
|
+
tools.push(defineTool({
|
|
2777
|
+
name: "workflow.permissions",
|
|
2778
|
+
layer: "low",
|
|
2779
|
+
kind: "read",
|
|
2780
|
+
lane: WORKFLOW_LANE,
|
|
2781
|
+
scope: WORKFLOW_TOOLS["workflow.permissions"].scope,
|
|
2782
|
+
description: WORKFLOW_TOOLS["workflow.permissions"].description,
|
|
2783
|
+
inputSchema: {
|
|
2784
|
+
environment: environmentArg,
|
|
2785
|
+
workflowCode: z.string().optional().describe("流程 code;省略则用 schemaCode 解析"),
|
|
2786
|
+
schemaCode: z.string().optional().describe("模型 code:省略 workflowCode 时用它解析该模型的流程"),
|
|
2787
|
+
capability: capabilityArg,
|
|
2788
|
+
},
|
|
2789
|
+
handler: async (ctx, args, self) => governedRead(ctx, self, args, async (session) => {
|
|
2790
|
+
const workflowCode = await resolveWorkflowArg(session, args);
|
|
2791
|
+
if (!workflowCode)
|
|
2792
|
+
return { schemaCode: args.schemaCode, exists: false, permissions: [], note: "该模型没有流程" };
|
|
2793
|
+
const rows = await permissionRows(session, workflowCode);
|
|
2794
|
+
const permissions = rows.map((row) => ({
|
|
2795
|
+
id: row.id ?? null,
|
|
2796
|
+
name: row.name ?? null,
|
|
2797
|
+
unitType: row.unitType ?? null,
|
|
2798
|
+
unitTypeName: row.unitType === 1 || row.unitType === "1" ? "部门" : row.unitType === 2 || row.unitType === "2" ? "角色" : row.unitType === 3 || row.unitType === "3" ? "用户" : null,
|
|
2799
|
+
fullName: row.fullName ?? null,
|
|
2800
|
+
corpId: row.corpId ?? null,
|
|
2801
|
+
code: row.code ?? null,
|
|
2802
|
+
}));
|
|
2803
|
+
return {
|
|
2804
|
+
workflowCode,
|
|
2805
|
+
count: permissions.length,
|
|
2806
|
+
permissions,
|
|
2807
|
+
...(permissions.length === 0
|
|
2808
|
+
? {
|
|
2809
|
+
blocker: "workflow-start-permission-empty",
|
|
2810
|
+
note: "该流程没有任何发起权限行:除平台管理员(h_perm_admin 里 ADMIN/SYS_MNG)外**没有人能发起**,data.submit 会回 40009「流程发起权限不足」。发布流程不会自动建这一行,用 workflow.addPermission 授给部门/角色/用户(或把模板 visibleType 改成 ALL_VISIABLE)。",
|
|
2811
|
+
nextCommand: `omc call workflow.addPermission --env <env> --args '{"workflowCode":"${workflowCode}","unitId":"<部门|角色|用户 id>","unitType":1}'`,
|
|
2812
|
+
}
|
|
2813
|
+
: {}),
|
|
2814
|
+
};
|
|
2815
|
+
}),
|
|
2816
|
+
}));
|
|
2817
|
+
writeTool(tools, "workflow.addPermission", WORKFLOW_LANE, WORKFLOW_TOOLS["workflow.addPermission"].scope, WORKFLOW_TOOLS["workflow.addPermission"].description, {
|
|
2818
|
+
workflowCode: z.string().optional().describe("流程 code;省略则用 schemaCode 解析"),
|
|
2819
|
+
schemaCode: z.string().optional().describe("模型 code:省略 workflowCode 时用它解析该模型的流程"),
|
|
2820
|
+
unitId: z.string().min(1).describe("部门 id / 角色 id / 用户 id(看 unitType)"),
|
|
2821
|
+
unitType: z.union([z.literal(1), z.literal(2), z.literal(3), z.literal("DEPARTMENT"), z.literal("ROLE"), z.literal("USER")]).describe("1=部门 2=角色 3=用户;也接受 DEPARTMENT/ROLE/USER"),
|
|
2822
|
+
corpId: z.string().optional().describe("企业 id;省略则用当前会话企业"),
|
|
2823
|
+
}, async (session, args) => {
|
|
2824
|
+
const workflowCode = await resolveWorkflowArg(session, args);
|
|
2825
|
+
if (!workflowCode)
|
|
2826
|
+
throw new ToolRefusalError({ status: "refused", blocker: "workflow-not-found", message: `模型 ${String(args.schemaCode ?? "")} 没有流程;先 workflow.create` });
|
|
2827
|
+
const unitType = args.unitType === 1 || args.unitType === "DEPARTMENT" ? 1 : args.unitType === 2 || args.unitType === "ROLE" ? 2 : 3;
|
|
2828
|
+
// Idempotent retry: the platform answers 402003 WORKFLOW_PERMISSION_EXIST
|
|
2829
|
+
// for a duplicate unitId, which would read as a failure. `list_permission`
|
|
2830
|
+
// does NOT echo unitId (only id/name/unitType/fullName/code), so identity
|
|
2831
|
+
// is decided by the platform's own refusal rather than a guessed pre-read.
|
|
2832
|
+
const before = await permissionRows(session, workflowCode);
|
|
2833
|
+
try {
|
|
2834
|
+
// Send the unit's own name/code too: `list_permission` reads them back
|
|
2835
|
+
// (it echoes `name`/`code`), which is the only way `workflow.permissions`
|
|
2836
|
+
// can describe a row by more than its opaque permission id.
|
|
2837
|
+
const identity = await unitIdentity(session, unitType, args.unitId);
|
|
2838
|
+
await wireCall(session, {
|
|
2839
|
+
path: ENDPOINTS.workflowCreatePermission,
|
|
2840
|
+
method: "POST",
|
|
2841
|
+
body: {
|
|
2842
|
+
workflowCode,
|
|
2843
|
+
unitId: args.unitId,
|
|
2844
|
+
unitType,
|
|
2845
|
+
corpId: args.corpId ?? session.enterpriseId(),
|
|
2846
|
+
...(identity.name ? { name: identity.name } : {}),
|
|
2847
|
+
...(identity.code ? { code: identity.code } : {}),
|
|
2848
|
+
},
|
|
2849
|
+
});
|
|
2850
|
+
}
|
|
2851
|
+
catch (error) {
|
|
2852
|
+
const errcode = error.details?.errcode ?? error.errcode;
|
|
2853
|
+
if (String(errcode ?? "") !== "402003")
|
|
2854
|
+
throw new ToolRefusalError({
|
|
2855
|
+
status: "refused",
|
|
2856
|
+
blocker: "workflow-permission-write-failed",
|
|
2857
|
+
workflowCode,
|
|
2858
|
+
unitId: args.unitId,
|
|
2859
|
+
unitType,
|
|
2860
|
+
message: `create_permission 被平台拒绝(err=${String(errcode ?? "n/a")})。核对 unitId 是否真实存在(部门用 org.department.list、角色用 org.role.list、用户用 org.user.search),以及发起权限是否被企业策略禁用。原始错误:${error instanceof Error ? error.message : String(error)}`,
|
|
2861
|
+
});
|
|
2862
|
+
return {
|
|
2863
|
+
performed: { skipped: "already-present" },
|
|
2864
|
+
readback: { workflowCode, alreadyPresent: true, unitId: args.unitId, unitType, count: before.length, note: "该主体已有发起权限(平台 402003),未重复写入" },
|
|
2865
|
+
};
|
|
2866
|
+
}
|
|
2867
|
+
const after = await permissionRows(session, workflowCode);
|
|
2868
|
+
const created = after.find((row) => !before.some((old) => String(old.id ?? "") === String(row.id ?? ""))) ?? after[after.length - 1];
|
|
2869
|
+
return {
|
|
2870
|
+
performed: { errcode: 0 },
|
|
2871
|
+
readback: {
|
|
2872
|
+
workflowCode,
|
|
2873
|
+
count: after.length,
|
|
2874
|
+
permissionId: created?.id ?? null,
|
|
2875
|
+
unitType: created?.unitType ?? unitType,
|
|
2876
|
+
name: created?.name ?? null,
|
|
2877
|
+
note: "发起权限已授予。若之前 data.submit 回 40009,现在应可发起;仍失败先 workflow.permissions 确认行存在,并核对发起人所属部门/角色。",
|
|
2878
|
+
},
|
|
2879
|
+
};
|
|
2880
|
+
});
|
|
2881
|
+
writeTool(tools, "workflow.removePermission", WORKFLOW_LANE, WORKFLOW_TOOLS["workflow.removePermission"].scope, WORKFLOW_TOOLS["workflow.removePermission"].description, {
|
|
2882
|
+
permissionId: z.string().min(1).optional().describe("首选:workflow.permissions 返回的 id(唯一确定)"),
|
|
2883
|
+
workflowCode: z.string().optional().describe("配合 unitId+unitType 使用(平台的 list_permission 不回 unitId,需按显示名解析)"),
|
|
2884
|
+
unitId: z.string().min(1).optional().describe("部门/角色/用户 id;按 unitType 解析显示名后匹配权限行"),
|
|
2885
|
+
unitType: z.union([z.literal(1), z.literal(2), z.literal(3), z.literal("DEPARTMENT"), z.literal("ROLE"), z.literal("USER")]).optional().describe("unitId 的类型:1=部门 2=角色 3=用户"),
|
|
2886
|
+
schemaCode: z.string().optional(),
|
|
2887
|
+
}, async (session, args) => {
|
|
2888
|
+
let permissionId = args.permissionId;
|
|
2889
|
+
if (!permissionId) {
|
|
2890
|
+
const workflowCode = await resolveWorkflowArg(session, { workflowCode: args.workflowCode, schemaCode: args.schemaCode });
|
|
2891
|
+
if (!workflowCode || !args.unitId || args.unitType === undefined)
|
|
2892
|
+
throw new ToolInputError({ status: "error", blocker: "tool-input-invalid", tool: "workflow.removePermission", message: "需要 permissionId(推荐),或 workflowCode(/schemaCode)+ unitId + unitType" });
|
|
2893
|
+
const unitType = args.unitType === 1 || args.unitType === "DEPARTMENT" ? 1 : args.unitType === 2 || args.unitType === "ROLE" ? 2 : 3;
|
|
2894
|
+
const identity = await unitIdentity(session, unitType, args.unitId);
|
|
2895
|
+
const rows = await permissionRows(session, workflowCode);
|
|
2896
|
+
const wantedType = String(unitType);
|
|
2897
|
+
const hit = rows.find((row) => String(row.unitType ?? "") === wantedType && (String(row.id ?? "") === args.unitId ||
|
|
2898
|
+
(identity.name !== null && String(row.name ?? "") === identity.name) ||
|
|
2899
|
+
(identity.code !== null && String(row.code ?? "") === identity.code)));
|
|
2900
|
+
if (!hit)
|
|
2901
|
+
return {
|
|
2902
|
+
performed: { skipped: "absent" },
|
|
2903
|
+
readback: {
|
|
2904
|
+
workflowCode,
|
|
2905
|
+
removed: false,
|
|
2906
|
+
note: `没有匹配该主体的权限行(幂等)。注意 list_permission 不回 unitId,匹配靠解析出的显示名「${identity.name ?? "?"}」;不确定时用 workflow.permissions 拿 id 再按 permissionId 删。`,
|
|
2907
|
+
permissions: rows.map((row) => ({ id: row.id ?? null, name: row.name ?? null, unitType: row.unitType ?? null })),
|
|
2908
|
+
},
|
|
2909
|
+
};
|
|
2910
|
+
permissionId = String(hit.id ?? "");
|
|
2911
|
+
}
|
|
2912
|
+
await wireCall(session, { path: ENDPOINTS.workflowDeletePermission, method: "POST", query: { permissionId } });
|
|
2913
|
+
return { performed: { errcode: 0 }, readback: { permissionId, removed: true } };
|
|
2914
|
+
});
|
|
2727
2915
|
tools.push(defineTool({
|
|
2728
2916
|
name: "org.role.list",
|
|
2729
2917
|
layer: "low",
|
package/dist/runtime-support.js
CHANGED
|
@@ -54,11 +54,17 @@ export async function mapUnpublishedAppFailure(session, schemaCode, errcode, roo
|
|
|
54
54
|
* unpublished-app failure. Used by data.save/data.submit (errcode 50000) and
|
|
55
55
|
* datarule.create type2 (errcode 305003) so the try/catch wiring lives once.
|
|
56
56
|
*/
|
|
57
|
-
export async function runtimeWriteWithAppMapping(session, schemaCode, errcode, rootCause, call, formData) {
|
|
57
|
+
export async function runtimeWriteWithAppMapping(session, schemaCode, errcode, rootCause, call, formData, workflowCode) {
|
|
58
58
|
try {
|
|
59
59
|
return await call();
|
|
60
60
|
}
|
|
61
61
|
catch (error) {
|
|
62
|
+
// 40009 is diagnosed FIRST: it names a cause the app/relation mappings
|
|
63
|
+
// cannot see, and it is the one refusal whose fix is a concrete next
|
|
64
|
+
// command (r11: seven blind submit attempts). Guarded by the errcode so
|
|
65
|
+
// every other failure keeps its existing mapping.
|
|
66
|
+
if (error instanceof WireError && error.kind === "business" && error.errcode === 40009)
|
|
67
|
+
await diagnoseWorkflowStartPermission(session, schemaCode, workflowCode, error);
|
|
62
68
|
let mapped;
|
|
63
69
|
try {
|
|
64
70
|
mapped = await mapUnpublishedAppFailure(session, schemaCode, errcode, rootCause, error);
|
|
@@ -131,4 +137,89 @@ export async function diagnoseRelationShapeFailure(session, schemaCode, errcode,
|
|
|
131
137
|
message: `errcode ${errcode}——根因:单选关联字段 ${culprits.join(", ")} 传了数组(WORK_SHEET/propertyType 9 只接受 id 字符串或 {id};数组会稳定 ${errcode})。多选关联(11)与人员/组织(50/51)才用数组`,
|
|
132
138
|
});
|
|
133
139
|
}
|
|
140
|
+
/**
|
|
141
|
+
* 40009 流程发起权限不足 — the most opaque refusal on this platform.
|
|
142
|
+
*
|
|
143
|
+
* r11 (2026-10-24, 1.1.25) could not deliver an entire workflow section: seven
|
|
144
|
+
* `data.submit` attempts all answered the same two words, and neither the
|
|
145
|
+
* message nor any CLI face said WHY. The platform source
|
|
146
|
+
* (`WorkflowAspect.validateWorkflowPermission` →
|
|
147
|
+
* `WorkflowManagementFacadeImpl.getWorkflowTemplateHeadersBySchemaCode`) shows
|
|
148
|
+
* three independent causes, all of which produce the identical code:
|
|
149
|
+
*
|
|
150
|
+
* 1. `h_workflow_template` header missing / not published;
|
|
151
|
+
* 2. the caller's root department does not resolve (`getAllParentIds` empty →
|
|
152
|
+
* the allowed-template list is short-circuited to EMPTY, so a user whose
|
|
153
|
+
* department row was deleted/moved can never start anything);
|
|
154
|
+
* 3. no `h_workflow_permission` row matching the user, their department (or an
|
|
155
|
+
* ancestor), or one of their roles — and `visibleType != ALL_VISIABLE`.
|
|
156
|
+
* Publishing a flow creates NO permission row, so a freshly published flow
|
|
157
|
+
* is unusable by everyone except a `h_perm_admin` ADMIN/SYS_MNG.
|
|
158
|
+
*
|
|
159
|
+
* The diagnosis here reads only the cheap, authoritative faces (permission list
|
|
160
|
+
* + header presence) and names the recovery command instead of leaving the
|
|
161
|
+
* caller to guess. Anything that fails is swallowed: a diagnosis must never
|
|
162
|
+
* replace the original refusal.
|
|
163
|
+
*/
|
|
164
|
+
export async function diagnoseWorkflowStartPermission(session, schemaCode, workflowCode, error) {
|
|
165
|
+
if (!(error instanceof WireError && error.kind === "business" && error.errcode === 40009))
|
|
166
|
+
throw error;
|
|
167
|
+
const resolvedCode = workflowCode ?? (await resolvePublishedWorkflowCodeQuiet(session, schemaCode));
|
|
168
|
+
if (!resolvedCode)
|
|
169
|
+
throw error;
|
|
170
|
+
let permissions = [];
|
|
171
|
+
let headerPresent = true;
|
|
172
|
+
try {
|
|
173
|
+
headerPresent = (await wireCall(session, { path: ENDPOINTS.workflowGetHeader, query: { workflowCode: resolvedCode } })).data != null;
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
headerPresent = true;
|
|
177
|
+
}
|
|
178
|
+
try {
|
|
179
|
+
permissions = wireRows((await wireCall(session, { path: ENDPOINTS.workflowListPermission, query: { workflowCode: resolvedCode } })).data);
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
permissions = [];
|
|
183
|
+
}
|
|
184
|
+
const headerHint = headerPresent ? "" : ` 流程头 ${resolvedCode} 不存在或未发布(先 workflow.publish)。`;
|
|
185
|
+
const message = permissions.length === 0
|
|
186
|
+
? `流程 ${resolvedCode} 没有任何发起权限行。§ 发布流程不会自动建发起权限§:` +
|
|
187
|
+
`除平台管理员(平台 h_perm_admin 里的 ADMIN/SYS_MNG,注意这与登录接口返回的 isAdmin 不是一回事)外,任何人都无法发起,` +
|
|
188
|
+
`data.submit / data.save+workflowCode 一律回 40009。${headerHint}` +
|
|
189
|
+
` 修复:把发起权限授给一个部门/角色/用户 —— ` +
|
|
190
|
+
`omc call workflow.addPermission --env <env> --args '{"workflowCode":"${resolvedCode}","unitId":"<org.department.list|org.role.list|org.user.search 的 id>","unitType":1}'` +
|
|
191
|
+
`(1=部门 2=角色 3=用户;同一行也可用 workflow.permissions 复核)。` +
|
|
192
|
+
`另一种原因同样报 40009 且无法从权限行看出来:发起人所属部门记录已被删除/移动(平台 getRootDeptId 解析为空时直接短路成"无任何可发起模板")——` +
|
|
193
|
+
`用 org.user.search 看自己的 departmentId,再用 org.department.list 确认该部门仍在。`
|
|
194
|
+
: `流程 ${resolvedCode} 已有 ${permissions.length} 条发起权限行(${permissions.map((row) => `${String(row.name ?? row.code ?? "?")}/unitType=${String(row.unitType ?? "?")}`).join("、")}),` +
|
|
195
|
+
`但当前调用者不在其中。${headerHint}` +
|
|
196
|
+
` 核对顺序:① 发起人的 departmentId(org.user.search)是否等于某条 unitType=1 的部门,或是其祖先链上的一级;` +
|
|
197
|
+
`② 发起人是否属于某条 unitType=2 的角色(org.role.users);③ 是否该 unitType=3 的用户本人;` +
|
|
198
|
+
`④ 若部门记录已被删除/移动,平台会把可发起列表短路成空——同样报 40009,但与权限行无关。` +
|
|
199
|
+
` 补授权:omc call workflow.addPermission --env <env> --args '{"workflowCode":"${resolvedCode}","unitId":"<id>","unitType":1}';查看现状用 omc call workflow.permissions。`;
|
|
200
|
+
throw new ToolRefusalError({
|
|
201
|
+
status: "refused",
|
|
202
|
+
blocker: "workflow-start-permission-denied",
|
|
203
|
+
schemaCode,
|
|
204
|
+
workflowCode: resolvedCode,
|
|
205
|
+
errcode: 40009,
|
|
206
|
+
permissionCount: permissions.length,
|
|
207
|
+
permissions,
|
|
208
|
+
message,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
/** The published workflow code for a schema, or undefined (never throws). */
|
|
212
|
+
async function resolvePublishedWorkflowCodeQuiet(session, schemaCode) {
|
|
213
|
+
try {
|
|
214
|
+
const isWorkflow = (await wireCall(session, { path: ENDPOINTS.workflowIsWorkflow, query: { schemaCode } })).data === true;
|
|
215
|
+
if (!isWorkflow)
|
|
216
|
+
return undefined;
|
|
217
|
+
const template = (await wireCall(session, { path: ENDPOINTS.workflowGetBySchema, query: { schemaCode } })).data;
|
|
218
|
+
const code = template && typeof template.workflowCode === "string" && template.workflowCode.length > 0 ? template.workflowCode : null;
|
|
219
|
+
return code ?? undefined;
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
return undefined;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
134
225
|
//# sourceMappingURL=runtime-support.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stalfh233/omc-cli",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.82",
|
|
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.82",
|
|
54
54
|
"commander": "14.0.3",
|
|
55
|
-
"omc-core": "0.4.
|
|
55
|
+
"omc-core": "0.4.82",
|
|
56
56
|
"proper-lockfile": "4.1.2",
|
|
57
57
|
"write-file-atomic": "6.0.0",
|
|
58
58
|
"zod": "^3.23.8",
|
|
@@ -148,7 +148,7 @@ only the section for the capability you need —
|
|
|
148
148
|
## 流程模板 `workflow`
|
|
149
149
|
|
|
150
150
|
- **能做哪些事**:流程模板 get_draft/get_published、create、update、publish、bindRule(事件处理器规则绑定)、delete、workitem.list、workflow.instanceActivity(实例节点状态诊断);参与人用组织对象。
|
|
151
|
-
- **工具**:`workflow.get_draft`、`workflow.setButtons`(节点「表单操作」按钮勾选)、`workflow.get_published`、`workflow.validate`、`workflow.create`、`workflow.update`、`workflow.publish`、`workflow.bindRule`、`workflow.delete`、`configure_workflow`、`workitem.list`、`workflow.instanceActivity`、`org.role.list`、`org.department.list`、`org.user.search`、`org.role.users`、`org.role.userScope`、`org.roleGroup.create`、`org.role.create`、`org.role.update`、`org.role.addUsers`、`org.role.removeUsers`、`org.role.setUserScope`、`org.department.create`、`org.department.update`、`org.department.delete`、`org.department.users`、`org.user.create`、`org.user.update`、`org.user.delete`、`org.user.info`、`org.user.departments`、`org.role.delete`、`org.roleGroup.delete`、`org.user.transferDepartment`
|
|
151
|
+
- **工具**:`workflow.get_draft`、`workflow.setButtons`(节点「表单操作」按钮勾选)、`workflow.get_published`、`workflow.validate`、`workflow.create`、`workflow.update`、`workflow.publish`、`workflow.bindRule`、`workflow.delete`、`workflow.permissions`、`workflow.addPermission`、`workflow.removePermission`(发起权限:发布流程不会自动建这一行,缺它一律 40009)、`configure_workflow`、`workitem.list`、`workflow.instanceActivity`、`org.role.list`、`org.department.list`、`org.user.search`、`org.role.users`、`org.role.userScope`、`org.roleGroup.create`、`org.role.create`、`org.role.update`、`org.role.addUsers`、`org.role.removeUsers`、`org.role.setUserScope`、`org.department.create`、`org.department.update`、`org.department.delete`、`org.department.users`、`org.user.create`、`org.user.update`、`org.user.delete`、`org.user.info`、`org.user.departments`、`org.role.delete`、`org.roleGroup.delete`、`org.user.transferDepartment`
|
|
152
152
|
- **recipe**:`omc describe workflow.bindRule --example workflow-handler-binding`
|
|
153
153
|
- **边界 / 陷阱**:greenfield 首版;`publish`/`bindRule` 不得在 `ValidateMessage[]` 拒绝时报告成功。运行时 workitem(提交/审批)走 `data.submit`;想知道实例停在哪/是否错派,用 `workflow.instanceActivity`(无 status=1=真停,参与人全是发起人=解析为空退回)。
|
|
154
154
|
|