@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.
@@ -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
- result = describeToolFull(registry, value ?? "");
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",
@@ -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:"<子表schemaCode>", dataItemCode:"<子表字段code>", formula:"SUM({<子表schemaCode>.<子表字段code>})", calculateType?:.., queryConditions?:[]}(FormulaProcessor.executeMainAggregation + AggregationFormula)');
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