@stalfh233/omc-cli 0.4.72 → 0.4.74
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/coverage/m1-coverage-manifest-v1.json +42 -2
- package/dist/cli.js +8 -2
- package/dist/describe-cache.js +91 -0
- package/dist/endpoints.js +22 -0
- package/dist/field-families.js +59 -1
- package/dist/lanes/organization-coverage.js +27 -0
- package/dist/lanes/organization.js +628 -0
- package/dist/lanes/rule-graph.js +87 -1
- package/dist/lanes/rule-lifecycle.js +20 -0
- package/dist/lanes/workflow-contract.js +1 -1
- package/dist/lanes/workflow-coverage.js +21 -0
- package/dist/lanes/workflow.js +55 -6
- package/dist/rule-support.js +132 -1
- package/dist/tools.js +2 -0
- package/dist/usage-log.js +62 -0
- package/package.json +3 -3
- package/skills/omc-business-configuration.md +16 -1
- package/skills/omc-capabilities.md +1 -1
|
@@ -339,10 +339,50 @@
|
|
|
339
339
|
"workflow.bindRule",
|
|
340
340
|
"workflow.delete",
|
|
341
341
|
"configure_workflow",
|
|
342
|
-
"workflow.validate"
|
|
342
|
+
"workflow.validate",
|
|
343
|
+
"org.role.users",
|
|
344
|
+
"org.role.userScope",
|
|
345
|
+
"org.roleGroup.create",
|
|
346
|
+
"org.role.create",
|
|
347
|
+
"org.role.update",
|
|
348
|
+
"org.role.addUsers",
|
|
349
|
+
"org.role.removeUsers",
|
|
350
|
+
"org.role.setUserScope",
|
|
351
|
+
"org.role.delete",
|
|
352
|
+
"org.roleGroup.delete",
|
|
353
|
+
"org.user.transferDepartment",
|
|
354
|
+
"org.department.create",
|
|
355
|
+
"org.department.update",
|
|
356
|
+
"org.department.delete",
|
|
357
|
+
"org.department.users",
|
|
358
|
+
"org.user.create",
|
|
359
|
+
"org.user.update",
|
|
360
|
+
"org.user.delete",
|
|
361
|
+
"org.user.info",
|
|
362
|
+
"org.user.departments"
|
|
343
363
|
],
|
|
344
364
|
"scopes": [
|
|
345
|
-
"greenfield workflow authoring"
|
|
365
|
+
"greenfield workflow authoring",
|
|
366
|
+
"organization role member read (role/users)",
|
|
367
|
+
"organization role member management scope read (role/get_ouscope)",
|
|
368
|
+
"organization role group create (role/add_role_group)",
|
|
369
|
+
"organization role create (role/add_role)",
|
|
370
|
+
"organization role update (role/modify_role)",
|
|
371
|
+
"organization role member add (role/add_role_user)",
|
|
372
|
+
"organization role member remove (role/strike_out_role_user)",
|
|
373
|
+
"organization role member scope update (role/update_ouscope)",
|
|
374
|
+
"organization role delete (role/strike_out_role)",
|
|
375
|
+
"organization role group delete (role/strike_out_role_group)",
|
|
376
|
+
"organization user department transfer (user/dept_transfer)",
|
|
377
|
+
"organization department create (department/add)",
|
|
378
|
+
"organization department update (department/modefied)",
|
|
379
|
+
"organization department delete (department/strike_out)",
|
|
380
|
+
"organization department member read (department/list_user)",
|
|
381
|
+
"organization user create (user/add)",
|
|
382
|
+
"organization user update (user/modify)",
|
|
383
|
+
"organization user delete (user/strike_out)",
|
|
384
|
+
"organization user read (user/info)",
|
|
385
|
+
"organization user department read (user/departments)"
|
|
346
386
|
]
|
|
347
387
|
},
|
|
348
388
|
{
|
package/dist/cli.js
CHANGED
|
@@ -28,6 +28,7 @@ import { cmdFormDefault } from "./form-default.js";
|
|
|
28
28
|
if (process.argv.includes("--utf8"))
|
|
29
29
|
process.env.OMC_ASCII_STDOUT = "0";
|
|
30
30
|
import { commandIndex, commandInventory, describeContract, describeContractFull, describeContractTool, describeExample, describeParam, describeSchemaOnly, describeTool, describeToolFull } from "./discovery.js";
|
|
31
|
+
import { repeatFullDescribe } from "./describe-cache.js";
|
|
31
32
|
import { buildCapabilityOverview, capabilityDetail, compactCapabilities, executableCapabilityBundle, resolveCapabilityId, DISCOVERY_AUTHORITY } from "./capability-overview.js";
|
|
32
33
|
import { goalRoute } from "./goal-routes.js";
|
|
33
34
|
import { absencePointer } from "./capability-absences.js";
|
|
@@ -630,8 +631,13 @@ function cmdDiscovery(kind, transport, value, options = {}) {
|
|
|
630
631
|
result = describeParam(registry, value ?? "", options.param);
|
|
631
632
|
else if (options.example !== undefined)
|
|
632
633
|
result = describeExample(registry, value ?? "", typeof options.example === "string" ? options.example : undefined);
|
|
633
|
-
else if (options.full)
|
|
634
|
-
|
|
634
|
+
else if (options.full) {
|
|
635
|
+
const full = describeToolFull(registry, value ?? "");
|
|
636
|
+
// r9: the same `--full` contract was re-shipped 8 times (134 KB) while the
|
|
637
|
+
// caller only wanted to re-check a shape. Within a project session the
|
|
638
|
+
// second identical read returns an artifact reference instead of the body.
|
|
639
|
+
result = full ? (repeatFullDescribe(value ?? "", full) ?? full) : full;
|
|
640
|
+
}
|
|
635
641
|
else
|
|
636
642
|
result = describeTool(registry, value ?? "");
|
|
637
643
|
if (!result) {
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
|
+
import { artifactRoot, joinConfigRoot } from "./command-transport.js";
|
|
5
|
+
import { createArtifact, readArtifact } from "./artifacts.js";
|
|
6
|
+
import { projectSessionActive } from "./usage-log.js";
|
|
7
|
+
/**
|
|
8
|
+
* Repeat-read suppression for `describe <tool> --full`.
|
|
9
|
+
*
|
|
10
|
+
* r9 (2026-10-22) spent 134 KB on EIGHT `describe rule.save --full` calls: the
|
|
11
|
+
* same contract, unchanged, re-shipped every time the caller re-checked a shape.
|
|
12
|
+
* The hint at the bottom of the response ("先试 --example/--param") did not help
|
|
13
|
+
* because a caller editing a graph wants the authoritative schema again, and
|
|
14
|
+
* `--full` had no memory.
|
|
15
|
+
*
|
|
16
|
+
* The first `--full` in a project still returns the whole contract and spills a
|
|
17
|
+
* copy to an artifact. A later identical read returns `{artifactId, sha256,
|
|
18
|
+
* bytes, fetch}` instead: the caller can project the one key it needs with
|
|
19
|
+
* `omc artifact get <id> --jq <path>` instead of paying for the whole body
|
|
20
|
+
* again. Identity is the CONTENT hash, so a changed contract (new version, new
|
|
21
|
+
* field) always returns the full body again.
|
|
22
|
+
*
|
|
23
|
+
* Scoped to a project session by construction: the cache only exists where the
|
|
24
|
+
* usage log does (`.omc-env` in the working directory, or OMC_USAGE_LOG). Repo
|
|
25
|
+
* tests and the golden master run without it, so their bytes stay stable.
|
|
26
|
+
*/
|
|
27
|
+
const CACHE_FILE = "describe-full-cache.json";
|
|
28
|
+
const CACHE_TTL_MS = 6 * 60 * 60 * 1000;
|
|
29
|
+
function cachePath() {
|
|
30
|
+
return resolve(joinConfigRoot(), ".omc", CACHE_FILE);
|
|
31
|
+
}
|
|
32
|
+
function readCache() {
|
|
33
|
+
try {
|
|
34
|
+
const parsed = JSON.parse(readFileSync(cachePath(), "utf8"));
|
|
35
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return {};
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function writeCache(cache) {
|
|
42
|
+
try {
|
|
43
|
+
const path = cachePath();
|
|
44
|
+
mkdirSync(join(path, ".."), { recursive: true });
|
|
45
|
+
writeFileSync(path, `${JSON.stringify(cache)}\n`, { mode: 0o600 });
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
// Best-effort: a cache write must never fail the command.
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Return the compact repeat-read face for `tool`, or `undefined` when this is
|
|
53
|
+
* the first read (or the contract changed). Records the new entry as a side
|
|
54
|
+
* effect so the NEXT read is compact.
|
|
55
|
+
*/
|
|
56
|
+
export function repeatFullDescribe(tool, result) {
|
|
57
|
+
// No project session (repo tests, golden master, one-off runs) ⇒ no cache:
|
|
58
|
+
// those callers need byte-stable output, and a cache write would be litter.
|
|
59
|
+
if (!projectSessionActive())
|
|
60
|
+
return undefined;
|
|
61
|
+
const body = `${JSON.stringify(result, null, 2)}\n`;
|
|
62
|
+
const bytes = Buffer.byteLength(body, "utf8");
|
|
63
|
+
const digest = createHash("sha256").update(body).digest("hex");
|
|
64
|
+
const cache = readCache();
|
|
65
|
+
const previous = cache[tool];
|
|
66
|
+
if (previous && previous.sha256 === digest && Date.now() - previous.at < CACHE_TTL_MS) {
|
|
67
|
+
// A pruned artifact would turn the summary into a dead end; re-ship instead.
|
|
68
|
+
if (existsSync(join(artifactRoot(), `${previous.artifactId}.artifact`))) {
|
|
69
|
+
try {
|
|
70
|
+
readArtifact(artifactRoot(), previous.artifactId);
|
|
71
|
+
return {
|
|
72
|
+
name: tool,
|
|
73
|
+
repeated: true,
|
|
74
|
+
artifactId: previous.artifactId,
|
|
75
|
+
sha256: digest,
|
|
76
|
+
bytes,
|
|
77
|
+
fetch: `omc artifact get ${previous.artifactId} --summary(再 --keys/--jq 取需要的键)`,
|
|
78
|
+
note: `本项目的会话里已读过同一个契约(${bytes}B,内容 sha256 未变),本次不再重发全文;用 fetch 里的命令按需投影`,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
// fall through to a full re-ship
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const artifact = createArtifact(artifactRoot(), body);
|
|
87
|
+
cache[tool] = { tool, sha256: digest, bytes, artifactId: artifact.artifactId, at: Date.now() };
|
|
88
|
+
writeCache(cache);
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=describe-cache.js.map
|
package/dist/endpoints.js
CHANGED
|
@@ -120,6 +120,28 @@ export const ENDPOINTS = {
|
|
|
120
120
|
organizationRoleList: "/api/api/organization/role/list",
|
|
121
121
|
organizationDepartmentTree: "/api/api/organization/department/tree",
|
|
122
122
|
organizationUserSearch: "/api/api/organization/user/search",
|
|
123
|
+
organizationRoleUsers: "/api/api/organization/role/users",
|
|
124
|
+
organizationRoleGetOuScope: "/api/api/organization/role/get_ouscope",
|
|
125
|
+
organizationRoleUpdateOuScope: "/api/api/organization/role/update_ouscope",
|
|
126
|
+
organizationRoleAddGroup: "/api/api/organization/role/add_role_group",
|
|
127
|
+
organizationRoleAdd: "/api/api/organization/role/add_role",
|
|
128
|
+
organizationRoleModify: "/api/api/organization/role/modify_role",
|
|
129
|
+
organizationRoleGet: "/api/api/organization/role/get",
|
|
130
|
+
organizationDepartmentAdd: "/api/api/organization/department/add",
|
|
131
|
+
organizationDepartmentModify: "/api/api/organization/department/modefied",
|
|
132
|
+
organizationDepartmentDelete: "/api/api/organization/department/strike_out",
|
|
133
|
+
organizationDepartmentGet: "/api/api/organization/department/get_dept_info",
|
|
134
|
+
organizationDepartmentUsers: "/api/api/organization/department/list_user",
|
|
135
|
+
organizationUserAdd: "/api/api/organization/user/add",
|
|
136
|
+
organizationUserModify: "/api/api/organization/user/modify",
|
|
137
|
+
organizationUserDelete: "/api/api/organization/user/strike_out",
|
|
138
|
+
organizationUserInfo: "/api/api/organization/user/info",
|
|
139
|
+
organizationUserDepartments: "/api/api/organization/user/departments",
|
|
140
|
+
organizationRoleAddUser: "/api/api/organization/role/add_role_user",
|
|
141
|
+
organizationRoleRemoveUser: "/api/api/organization/role/strike_out_role_user",
|
|
142
|
+
organizationRoleDelete: "/api/api/organization/role/strike_out_role",
|
|
143
|
+
organizationRoleGroupDelete: "/api/api/organization/role/strike_out_role_group",
|
|
144
|
+
organizationUserDeptTransfer: "/api/api/organization/user/dept_transfer",
|
|
123
145
|
listQueryCreate: "/api/api/app/query/create",
|
|
124
146
|
listQueryGet: "/api/api/app/query/get",
|
|
125
147
|
listQueryUpdate: "/api/api/app/query/update",
|
package/dist/field-families.js
CHANGED
|
@@ -672,6 +672,59 @@ function refuseFieldOptions(reason, message) {
|
|
|
672
672
|
};
|
|
673
673
|
throw new ToolRefusalError({ status: "refused", blocker: "capability-out-of-coverage", citation });
|
|
674
674
|
}
|
|
675
|
+
/**
|
|
676
|
+
* Aggregation query conditions are deserialized by Jackson into Java enums that
|
|
677
|
+
* are FIXED-ORDINAL (`BizPropertyType`, `OperatorType`): both must be written as
|
|
678
|
+
* their INTEGER code. A name (“NUMERICAL”/“GTEQ”) makes bizproperty/publish
|
|
679
|
+
* answer 50000 with `InvalidFormatException: Cannot deserialize value of type
|
|
680
|
+
* int from String` (live 2026-10-22, 1.1.25 scratch model). The portal UI writes
|
|
681
|
+
* ints, so accept the readable names and normalize them to the wire code —
|
|
682
|
+
* exactly the kind of adapter this lane already performs for `name_i18n`.
|
|
683
|
+
*/
|
|
684
|
+
const AGGREGATION_OPERATOR_CODES = Object.freeze({
|
|
685
|
+
EQ: 1, NEQ: 2, GT: 3, IT: 4, GTEQ: 5, ITEQ: 6,
|
|
686
|
+
CONTAINS: 7, NOT_CONTAINS: 8, IS_NULL: 9, IS_NOT_NULL: 10,
|
|
687
|
+
OF: 11, NOT_OF: 12, HAVE: 13, NOT_HAVE: 14,
|
|
688
|
+
IN: 15, NOT_IN: 16, BETWEEN: 17, NOT_BETWEEN: 18,
|
|
689
|
+
CONTAINS_ALL: 19, NOT_CONTAINS_ALL: 20, CONTAINS_ANY: 21,
|
|
690
|
+
NOT_CONTAINS_ANY: 22, EQ_ANY: 23, NOT_EQ_ANY: 24,
|
|
691
|
+
});
|
|
692
|
+
function normalizeAggregationFormula(agg) {
|
|
693
|
+
const groups = agg.queryConditions;
|
|
694
|
+
if (groups === undefined)
|
|
695
|
+
return agg;
|
|
696
|
+
if (!Array.isArray(groups) || groups.some((group) => !Array.isArray(group)))
|
|
697
|
+
refuseFieldOptions("AGGREGATION aggregation.queryConditions is not an array of arrays", "rejected: aggregation.queryConditions 形状必须是 [[{propertyCode,propertyType(int),operatorType(int),propertyValue,valueType}],…](条件组)");
|
|
698
|
+
const normalized = groups.map((group) => group.map((leaf) => {
|
|
699
|
+
if (!leaf || typeof leaf !== "object" || Array.isArray(leaf))
|
|
700
|
+
refuseFieldOptions("AGGREGATION queryConditions leaf is not an object", "rejected: aggregation.queryConditions 每个条件必须是对象");
|
|
701
|
+
const record = { ...leaf };
|
|
702
|
+
for (const key of ["propertyType", "currentPropertyType"]) {
|
|
703
|
+
const value = record[key];
|
|
704
|
+
if (value === undefined || value === null)
|
|
705
|
+
continue;
|
|
706
|
+
const code = typeof value === "number" ? value : FIELD_PROPERTY_TYPES[String(value).toUpperCase()];
|
|
707
|
+
if (typeof code !== "number" || !Number.isInteger(code))
|
|
708
|
+
refuseFieldOptions(`AGGREGATION queryConditions.${key} ${JSON.stringify(value)} is not a numeric BizPropertyType code`, `rejected: 聚合条件的 ${key} 必须是**整数**属性类型编码(Jackson 按 int 反序列化,传 "NUMERICAL" 会让 publish 50000);可用名字:${Object.keys(FIELD_PROPERTY_TYPES).join("、")}`);
|
|
709
|
+
record[key] = code;
|
|
710
|
+
}
|
|
711
|
+
const operator = record.operatorType;
|
|
712
|
+
if (operator !== undefined && operator !== null) {
|
|
713
|
+
const code = typeof operator === "number" ? operator : AGGREGATION_OPERATOR_CODES[String(operator).toUpperCase()];
|
|
714
|
+
if (typeof code !== "number" || !Number.isInteger(code) || code < 1 || code > 24)
|
|
715
|
+
refuseFieldOptions(`AGGREGATION queryConditions.operatorType ${JSON.stringify(operator)} is not an OperatorType code`, `rejected: 聚合条件的 operatorType 必须是整数 1–24(或名字:${Object.keys(AGGREGATION_OPERATOR_CODES).join("、")});传 "GTEQ" 会让 publish 50000`);
|
|
716
|
+
record.operatorType = code;
|
|
717
|
+
}
|
|
718
|
+
if (record.valueType === undefined)
|
|
719
|
+
record.valueType = "FIXED";
|
|
720
|
+
if (!["FIXED", "DYNAMIC"].includes(String(record.valueType)))
|
|
721
|
+
refuseFieldOptions(`AGGREGATION queryConditions.valueType ${JSON.stringify(record.valueType)} is not FIXED/DYNAMIC`, 'rejected: 聚合条件的 valueType 只能是 "FIXED" 或 "DYNAMIC"');
|
|
722
|
+
if (record.propertyValue !== undefined && typeof record.propertyValue !== "string")
|
|
723
|
+
record.propertyValue = String(record.propertyValue);
|
|
724
|
+
return record;
|
|
725
|
+
}));
|
|
726
|
+
return { ...agg, queryConditions: normalized };
|
|
727
|
+
}
|
|
675
728
|
/** FORMULA formulaConfig decode; policy: string-only face, must parse to a non-array object. */
|
|
676
729
|
function parseOptionsObject(value) {
|
|
677
730
|
return decodeJsonObjectColumn(value, { invalid: "undefined", array: "reject", object: "reject" });
|
|
@@ -843,7 +896,12 @@ export function assertFieldOptionsAllowed(type, options, schemaCode) {
|
|
|
843
896
|
else {
|
|
844
897
|
const agg = parseOptionsObject(config.aggregation);
|
|
845
898
|
if (!agg || !isNonEmptyString(agg.schemaCode) || !isNonEmptyString(agg.dataItemCode) || !isNonEmptyString(agg.formula))
|
|
846
|
-
refuseFieldOptions("AGGREGATION aggregation is missing schemaCode/dataItemCode/formula", 'rejected: aggregation 形状 {schemaCode:"
|
|
899
|
+
refuseFieldOptions("AGGREGATION aggregation is missing schemaCode/dataItemCode/formula", 'rejected: aggregation 形状 {schemaCode:"<目标模型schemaCode>", dataItemCode:"<目标字段code>", formula:"SUM({<目标模型>.<字段>})", calculateType:"SUM", queryConditions:[[…]]}(FormulaProcessor.executeMainAggregation + AggregationFormula)');
|
|
900
|
+
const normalizedAgg = normalizeAggregationFormula(agg);
|
|
901
|
+
if (normalizedAgg !== agg) {
|
|
902
|
+
config.aggregation = JSON.stringify(normalizedAgg);
|
|
903
|
+
options.formulaConfig = JSON.stringify(config);
|
|
904
|
+
}
|
|
847
905
|
}
|
|
848
906
|
}
|
|
849
907
|
else if (!isNonEmptyString(config.expression)) {
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { claim, declareLaneCoverage } from "./coverage-declaration.js";
|
|
2
|
+
import { LANES } from "./lane-ids.js";
|
|
3
|
+
/**
|
|
4
|
+
* Organization writes (role group / role / members / management scope) sit on
|
|
5
|
+
* the same live-proven template lane; the scopes below are claimed here so the
|
|
6
|
+
* runtime gate can resolve them without a second manifest item.
|
|
7
|
+
*/
|
|
8
|
+
export const ORGANIZATION_COVERAGE = declareLaneCoverage(LANES.workflow, [
|
|
9
|
+
{
|
|
10
|
+
capability: "W7 S29 greenfield workflow authoring",
|
|
11
|
+
evidence: [
|
|
12
|
+
"packages/omc-cli/src/lanes/organization.ts",
|
|
13
|
+
"OpenAPI /api/v3/api-docs (1.1.25): organization/role/add_role, add_role_user, update_ouscope",
|
|
14
|
+
],
|
|
15
|
+
tools: [
|
|
16
|
+
claim("org.role.users", "organization role member read (role/users)"),
|
|
17
|
+
claim("org.role.userScope", "organization role member management scope read (role/get_ouscope)"),
|
|
18
|
+
claim("org.roleGroup.create", "organization role group create (role/add_role_group)"),
|
|
19
|
+
claim("org.role.create", "organization role create (role/add_role)"),
|
|
20
|
+
claim("org.role.update", "organization role update (role/modify_role)"),
|
|
21
|
+
claim("org.role.addUsers", "organization role member add (role/add_role_user)"),
|
|
22
|
+
claim("org.role.removeUsers", "organization role member remove (role/strike_out_role_user)"),
|
|
23
|
+
claim("org.role.setUserScope", "organization role member scope update (role/update_ouscope)"),
|
|
24
|
+
],
|
|
25
|
+
},
|
|
26
|
+
]);
|
|
27
|
+
//# sourceMappingURL=organization-coverage.js.map
|