@stalfh233/omc-cli 0.6.11 → 0.6.13
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 +2 -0
- package/dist/approval-flow.js +84 -3
- package/dist/batch-call.js +32 -2
- package/dist/bench/call-face-eval.js +2 -2
- package/dist/call.js +35 -2
- package/dist/capability-overview.js +21 -7
- package/dist/cli-contract.js +34 -4
- package/dist/cli-output.js +6 -2
- package/dist/cli.js +303 -69
- package/dist/describe-cache.js +8 -4
- package/dist/environment-policy.js +20 -0
- package/dist/field-families.js +62 -4
- package/dist/form-support.js +293 -6
- package/dist/goal-routes.js +137 -22
- package/dist/identity.js +63 -11
- package/dist/lanes/app-contract.js +4 -4
- package/dist/lanes/app.js +19 -1
- package/dist/lanes/dictionary-contract.js +16 -5
- package/dist/lanes/dictionary-coverage.js +1 -0
- package/dist/lanes/dictionary.js +200 -8
- package/dist/lanes/environment-contract.js +18 -6
- package/dist/lanes/environment.js +129 -16
- package/dist/lanes/field-contract.js +2 -1
- package/dist/lanes/field.js +707 -59
- package/dist/lanes/form.js +52 -6
- package/dist/lanes/list-view-contract.js +2 -2
- package/dist/lanes/list-view.js +98 -2
- package/dist/lanes/meta.js +36 -10
- package/dist/lanes/model-contract.js +7 -6
- package/dist/lanes/model.js +154 -10
- package/dist/lanes/organization.js +67 -7
- package/dist/lanes/runtime-data-contract.js +4 -2
- package/dist/lanes/runtime-data.js +395 -41
- package/dist/lanes/workflow-contract.js +2 -2
- package/dist/lanes/workflow.js +381 -11
- package/dist/model-scaffold.js +11 -3
- package/dist/path-projection.js +192 -32
- package/dist/read-shapes.js +31 -11
- package/dist/recipes/field.js +16 -3
- package/dist/recipes/form.js +2 -1
- package/dist/recipes/runtime-data.js +58 -8
- package/dist/recipes/workflow.js +86 -1
- package/dist/refusal-guidance.js +18 -6
- package/dist/rollback-spec.js +41 -2
- package/dist/rollback.js +49 -11
- package/dist/script.js +72 -12
- package/dist/skills.js +91 -3
- package/dist/tools.js +113 -1
- package/dist/wire.js +13 -1
- package/dist/write-lock.js +92 -4
- package/dist/write.js +129 -16
- package/package.json +5 -3
- package/skills/omc-business-configuration.md +1 -1
- package/skills/omc-capabilities.md +5 -4
- package/skills/omc-playbook.md +41 -0
- package/skills/omc-refusals.md +58 -6
- package/skills/omc-tool-selection.md +2 -0
- package/skills/omc-write-hazards.md +3 -2
package/dist/goal-routes.js
CHANGED
|
@@ -16,11 +16,56 @@ const READ_CUES = ["查看", "读回", "盘点", "现状", "列出", "有哪些"
|
|
|
16
16
|
const BUILD_CUES = ["新建", "创建", "搭建", "从零", "从无到有", "做", "建", "加", "实现", "生成", "新增", "保存", "启用", "录入", "添加", "造", "填报", "打卡", "录"];
|
|
17
17
|
/** Whole-application asks — a single-model atom is the wrong shape for these. */
|
|
18
18
|
const WHOLE_APP_CUES = ["应用", "系统", "整套", "从零", "从无到有", "端到端", "一条龙", "一整套", "业务应用", "平台"];
|
|
19
|
+
/** From-scratch cues — firstAnswer must not point at a schema that does not exist yet. */
|
|
20
|
+
const GREENFIELD_CUES = ["从零", "从无到有", "做一个", "做一套", "新建应用", "搭建应用", "端到端", "一条龙"];
|
|
19
21
|
function looksWholePackage(goal, capabilityCount) {
|
|
20
22
|
if (capabilityCount >= 4)
|
|
21
23
|
return true;
|
|
22
24
|
return WHOLE_APP_CUES.some((cue) => goal.includes(cue));
|
|
23
25
|
}
|
|
26
|
+
function looksGreenfield(goal) {
|
|
27
|
+
return GREENFIELD_CUES.some((cue) => goal.includes(cue));
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* r31c F1: multi-face asks must NOT be stolen by the first matching composer.
|
|
31
|
+
* Conservative: only when ≥2 capability faces score AND the ask is whole-package
|
|
32
|
+
* shaped (WHOLE_APP cue or ≥4 faces). Single-face composer goals (G1/G2/G5) stay
|
|
33
|
+
* single-point answers.
|
|
34
|
+
*/
|
|
35
|
+
function shouldPreferMultiFace(mode, goal, scoredCapabilityIds) {
|
|
36
|
+
if (mode !== "build" && mode !== "modify")
|
|
37
|
+
return false;
|
|
38
|
+
if (scoredCapabilityIds.length < 2)
|
|
39
|
+
return false;
|
|
40
|
+
return looksWholePackage(goal, scoredCapabilityIds.length);
|
|
41
|
+
}
|
|
42
|
+
/** Short why-line for a capability step in a multi-face ladder (r31c F1). */
|
|
43
|
+
function capabilityWhy(capabilityId, goal) {
|
|
44
|
+
const cues = CAPABILITY_CUES[capabilityId] ?? [];
|
|
45
|
+
const hits = cues.filter((cue) => goal.includes(cue)).slice(0, 3);
|
|
46
|
+
if (hits.length > 0)
|
|
47
|
+
return hits.join(" + ");
|
|
48
|
+
switch (capabilityId) {
|
|
49
|
+
case "model":
|
|
50
|
+
return "建模";
|
|
51
|
+
case "field":
|
|
52
|
+
return "字段 / 子表";
|
|
53
|
+
case "form":
|
|
54
|
+
return "表单";
|
|
55
|
+
case "listview":
|
|
56
|
+
return "列表 / 按钮";
|
|
57
|
+
case "datarule":
|
|
58
|
+
return "字段计算 / 校验";
|
|
59
|
+
case "businessrule":
|
|
60
|
+
return "业务规则 / 按钮";
|
|
61
|
+
case "workflow":
|
|
62
|
+
return "审批流程";
|
|
63
|
+
case "onlinejs":
|
|
64
|
+
return "在线 JS";
|
|
65
|
+
default:
|
|
66
|
+
return capabilityId;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
24
69
|
function detectMode(goal) {
|
|
25
70
|
if (SCOUT_CUES.some((cue) => goal.includes(cue)))
|
|
26
71
|
return "scout";
|
|
@@ -419,7 +464,8 @@ const COMPOSER_ROUTES = Object.freeze([
|
|
|
419
464
|
command: "omc approval-flow --schema <模型code> --steps '<节点名>@role:<角色code>' --apply",
|
|
420
465
|
usage: "omc approval-flow --help",
|
|
421
466
|
// F1(r27):边界前置——非发起人审批无法在单会话 CLI 内推进,别等 submit 失败才说。
|
|
422
|
-
|
|
467
|
+
// r31c F5:明说线性-only;条件分支退回 workflow.update;行级可见范围不支持。
|
|
468
|
+
summary: "线性审批流一条命令:建流程 → 模板+字段只读权限 → 发布(多级用重复 --steps)。⚠️ 只做线性流程,无 --condition;条件分支用 omc describe workflow.update --example workflow-conditional-branch。边界:非发起人身份的审批无法在单会话 CLI 内推进(单 token);若要验跨账号审批,用第二账号另建环境,或把参与人设为 {Originator}。workflow.permissions 不是行级数据权限(申请人只看自己的单据当前工具面不支持)",
|
|
423
469
|
groups: [["审批", "流程"], ["建", "创建", "新建", "配置", "加", "搭"]],
|
|
424
470
|
// Branching / countersign shapes are NOT this composer's business.
|
|
425
471
|
avoid: ["条件分支", "分支", "会签", "并行", "或签", "网关", "子流程", "动态审批人", "清理", "删除", "移除", "purge", "teardown"],
|
|
@@ -502,17 +548,30 @@ function matchComposer(goal, mode) {
|
|
|
502
548
|
function isWholePackageWrite(mode, goal, capabilityCount) {
|
|
503
549
|
return (mode === "build" || mode === "modify") && looksWholePackage(goal, capabilityCount);
|
|
504
550
|
}
|
|
505
|
-
function wholePackageRoute(registry, goal, index) {
|
|
551
|
+
function wholePackageRoute(registry, goal, index, hitCapabilities) {
|
|
506
552
|
const routeById = new Map(index.routes.map((route) => [route.id, route]));
|
|
507
|
-
|
|
553
|
+
// r31c F1: prefer the faces the ask actually named; fall back to the full
|
|
554
|
+
// package set when the ask only said "做应用" without naming faces.
|
|
555
|
+
const seed = hitCapabilities !== undefined && hitCapabilities.length >= 2
|
|
556
|
+
? [...new Set([...hitCapabilities, "model", "field"])]
|
|
557
|
+
: [...WHOLE_PACKAGE_CAPABILITIES];
|
|
558
|
+
const ordered = orderedCapabilities(seed, index).filter((id) => routeById.has(id));
|
|
508
559
|
// Round 16b (dogfood R1): the supplement used to repeat every capability's
|
|
509
560
|
// full tool map (model.create/delete/…, field.create/…), ~60% of a 2.8KB
|
|
510
561
|
// response the caller never used — intent already covers these. Keep it a
|
|
511
562
|
// POINTER (capability + lane + one command), not a second ladder.
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
563
|
+
// r31c F1: multi-face asks must SEE every face they named (not just the first
|
|
564
|
+
// composer hit). But this must not be called `ladder` — round16 settled that
|
|
565
|
+
// whole-package has ONE primary path (intent) and `route.ladder` is the
|
|
566
|
+
// atomic execution ladder, which for whole-package is deliberately absent
|
|
567
|
+
// (bench40: two co-equal paths made agents wander). So the face list rides
|
|
568
|
+
// `route.faces`: same information, no second execution path.
|
|
569
|
+
const faces = ordered.map((id, indexInFaces) => ({
|
|
570
|
+
step: indexInFaces + 1,
|
|
571
|
+
capability: id,
|
|
572
|
+
why: capabilityWhy(id, goal),
|
|
573
|
+
}));
|
|
574
|
+
const greenfield = looksGreenfield(goal);
|
|
516
575
|
return {
|
|
517
576
|
status: "ok",
|
|
518
577
|
goal,
|
|
@@ -521,21 +580,34 @@ function wholePackageRoute(registry, goal, index) {
|
|
|
521
580
|
match: { kind: "whole-package", ref: ordered.join("→"), capabilities: ordered },
|
|
522
581
|
route: {
|
|
523
582
|
primary: "intent",
|
|
524
|
-
|
|
525
|
-
|
|
583
|
+
faceCount: faces.length,
|
|
584
|
+
atomicSupplement: {
|
|
585
|
+
note: "intent 已覆盖下列能力的创建;原子工具只用于 intent 未覆盖面(删除/生命周期/运行数据/精确 patch)",
|
|
586
|
+
capabilities: ordered,
|
|
587
|
+
// round16b: 这是**指针**(capability + why),不是第二套执行阶梯——
|
|
588
|
+
// 每步都不带 tools,否则就等于把 atomic ladder 又内联一遍。
|
|
589
|
+
ladder: faces,
|
|
590
|
+
},
|
|
591
|
+
...routeReads(sectionPointersFor(ordered.slice(0, 3))),
|
|
526
592
|
// r19 ticket 04: readiness (discover_environment) left firstCommands —
|
|
527
593
|
// the probe executed it 0 times in 184 calls; it rides its own field now.
|
|
528
594
|
readiness: DISCOVERY_AUTHORITY.readiness,
|
|
529
|
-
firstCommands: ["omc intent schema"],
|
|
595
|
+
firstCommands: ["omc intent schema", "omc help omc-playbook --section 采购申请"],
|
|
530
596
|
write: true,
|
|
531
597
|
},
|
|
532
598
|
write: true,
|
|
533
599
|
intent: intentGuidance(),
|
|
534
|
-
firstAnswer:
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
600
|
+
firstAnswer: greenfield
|
|
601
|
+
? {
|
|
602
|
+
state: "ready",
|
|
603
|
+
action: "omc intent schema",
|
|
604
|
+
why: "从零整包:模型还不存在——先看 intent 键位/示例,再按 starter 写 YAML(validate → plan → apply);不要先问已有 schemaCode",
|
|
605
|
+
}
|
|
606
|
+
: {
|
|
607
|
+
state: "ready",
|
|
608
|
+
action: "omc intent schema",
|
|
609
|
+
why: "整包需求走声明式意图:先看 intent YAML 的键位与可运行示例,再按 starter 写文件(validate → plan → apply)",
|
|
610
|
+
},
|
|
539
611
|
};
|
|
540
612
|
}
|
|
541
613
|
/** A configuration read-back ask that names no capability ("给我一份现状说明"). */
|
|
@@ -571,8 +643,17 @@ export function goalRoute(registry, goal) {
|
|
|
571
643
|
// matched to a nearby capability and handed the write ladder — that is the
|
|
572
644
|
// "invent a capability for a blocked face" anti-pattern. Return a boundary
|
|
573
645
|
// result pointing at capability-scouting + the (read-only) coverage proof.
|
|
646
|
+
//
|
|
647
|
+
// r31c F27 exception: a WHOLE-PACKAGE ask that merely MENTIONS an unsupported
|
|
648
|
+
// face (e.g. 「申请人只看自己」 inside an 8-clause purchase ask) must not
|
|
649
|
+
// steal the entire multi-face route. Soft-attach the boundary note and keep
|
|
650
|
+
// routing; pure boundary asks (only that face) stay blocked.
|
|
574
651
|
const boundary = boundaryOf(goal);
|
|
575
|
-
|
|
652
|
+
const earlyScores = capabilityScores(registry, goal);
|
|
653
|
+
const earlyScoredIds = [...earlyScores.entries()].filter((entry) => entry[1] >= 2).map((entry) => entry[0]);
|
|
654
|
+
const multiFaceMentionsBoundary = boundary !== undefined
|
|
655
|
+
&& shouldPreferMultiFace(mode, goal, earlyScoredIds);
|
|
656
|
+
if (boundary && !multiFaceMentionsBoundary) {
|
|
576
657
|
return {
|
|
577
658
|
status: "ok",
|
|
578
659
|
goal,
|
|
@@ -728,9 +809,41 @@ export function goalRoute(registry, goal) {
|
|
|
728
809
|
};
|
|
729
810
|
}
|
|
730
811
|
}
|
|
812
|
+
// Score capabilities BEFORE composer capture so a multi-face ask (r31c F1)
|
|
813
|
+
// is not stolen by the first matching composer (e.g. 「合计」→ compute).
|
|
814
|
+
const scoreMap = capabilityScores(registry, goal);
|
|
815
|
+
const scores = [...scoreMap.entries()].sort((left, right) => right[1] - left[1]);
|
|
816
|
+
const scoredCapabilityIds = scores.filter((entry) => entry[1] >= 2).map((entry) => entry[0]);
|
|
731
817
|
const composer = matchComposer(goal, mode);
|
|
818
|
+
if (composer && shouldPreferMultiFace(mode, goal, scoredCapabilityIds)) {
|
|
819
|
+
// Multi-face + whole-package shaped → surface the ordered ladder, not a
|
|
820
|
+
// single composer. Intent remains the primary path. Soft-attach any
|
|
821
|
+
// mentioned unsupported face (r31c F27) so the gap is named without
|
|
822
|
+
// blocking the rest of the package.
|
|
823
|
+
const routed = wholePackageRoute(registry, goal, index, scoredCapabilityIds);
|
|
824
|
+
if (boundary) {
|
|
825
|
+
return {
|
|
826
|
+
...routed,
|
|
827
|
+
boundary: {
|
|
828
|
+
id: boundary.id,
|
|
829
|
+
status: boundary.status,
|
|
830
|
+
note: boundary.note,
|
|
831
|
+
soft: true,
|
|
832
|
+
verify: "omc help omc-capabilities --section \"边界\"",
|
|
833
|
+
},
|
|
834
|
+
};
|
|
835
|
+
}
|
|
836
|
+
return routed;
|
|
837
|
+
}
|
|
732
838
|
if (composer) {
|
|
733
839
|
const topics = (composer.topics ?? []).filter((topic) => availableTopics.has(topic));
|
|
840
|
+
// r31c F3: if the ask is greenfield, do not point firstAnswer at a schema
|
|
841
|
+
// that does not exist yet — rewrite the clarifying question to create-first.
|
|
842
|
+
const baseQuestion = COMPOSER_QUESTIONS[composer.id] ?? "补齐命令里的尖括号占位符后运行(形状见 pendingCommand)";
|
|
843
|
+
const greenfield = looksGreenfield(goal);
|
|
844
|
+
const question = greenfield
|
|
845
|
+
? `模型还不存在:先创建模型(omc call model.create --args '{"code":"<模型code>","name":"<名称>","applicationCode":"<应用>"}' 或 omc model-scaffold …),再回答:${baseQuestion}`
|
|
846
|
+
: baseQuestion;
|
|
734
847
|
return {
|
|
735
848
|
status: "ok",
|
|
736
849
|
goal,
|
|
@@ -753,15 +866,17 @@ export function goalRoute(registry, goal) {
|
|
|
753
866
|
...(topics.length > 0 ? { topics } : {}),
|
|
754
867
|
firstAnswer: {
|
|
755
868
|
state: "needs-input",
|
|
756
|
-
question
|
|
757
|
-
pendingCommand: composer.
|
|
758
|
-
|
|
759
|
-
|
|
869
|
+
question,
|
|
870
|
+
pendingCommand: greenfield && composer.id !== "model-scaffold" && composer.id !== "model-purge"
|
|
871
|
+
? `omc call model.create --args '{"code":"<模型code>","name":"<名称>","applicationCode":"<应用>"}'`
|
|
872
|
+
: composer.command,
|
|
873
|
+
...(composer.id === "model-purge" || composer.id === "compute" || greenfield ? { action: LIST_MODELS_ACTION } : {}),
|
|
874
|
+
why: greenfield
|
|
875
|
+
? `从零需求:先确认/创建模型,再跑 ${composer.id}(${composer.summary})`
|
|
876
|
+
: composer.summary,
|
|
760
877
|
},
|
|
761
878
|
};
|
|
762
879
|
}
|
|
763
|
-
const scoreMap = capabilityScores(registry, goal);
|
|
764
|
-
const scores = [...scoreMap.entries()].sort((left, right) => right[1] - left[1]);
|
|
765
880
|
const fallback = {
|
|
766
881
|
status: "ok",
|
|
767
882
|
goal,
|
package/dist/identity.js
CHANGED
|
@@ -1,18 +1,70 @@
|
|
|
1
1
|
import { ENDPOINTS } from "./endpoints.js";
|
|
2
2
|
import { wireCall, wireRows } from "./wire.js";
|
|
3
|
+
import { wireString } from "./workflow-support.js";
|
|
4
|
+
export const PLATFORM_ADMIN_START_BYPASS = "platform-admin bypasses start-permission whitelist";
|
|
3
5
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* its business key is `(schemaCode, ruleCode)`, a workflow is `workflowCode`
|
|
9
|
-
* while the caller only knows the model's `schemaCode`. Today that forces a
|
|
10
|
-
* `list → get` dance and per-lane key memorisation.
|
|
11
|
-
*
|
|
12
|
-
* These resolvers let a tool accept the BUSINESS key and resolve the wire
|
|
13
|
-
* identity internally. They are read-only and return `null` for a genuine miss,
|
|
14
|
-
* so the caller can fail closed with a precise message.
|
|
6
|
+
* Resolve who is operating this session. Best-effort: never throws.
|
|
7
|
+
* `isAdmin` comes from login().administrator (info_login permissions contains
|
|
8
|
+
* ADMIN/SYS_MNG) — that is the platform start-permission bypass, not the
|
|
9
|
+
* login API's isAdmin boolean.
|
|
15
10
|
*/
|
|
11
|
+
export async function resolvePermissionActor(session, options = {}) {
|
|
12
|
+
let userId = null;
|
|
13
|
+
let name = null;
|
|
14
|
+
let isAdmin = false;
|
|
15
|
+
try {
|
|
16
|
+
const login = await session.login();
|
|
17
|
+
isAdmin = login.administrator === true;
|
|
18
|
+
const identity = login.accountIdentity;
|
|
19
|
+
if (identity !== undefined && identity.length > 0) {
|
|
20
|
+
const searchData = (await wireCall(session, {
|
|
21
|
+
path: ENDPOINTS.organizationUserSearch,
|
|
22
|
+
method: "POST",
|
|
23
|
+
body: { wd: identity, page: 0, size: 20 },
|
|
24
|
+
})).data;
|
|
25
|
+
const found = Array.isArray(searchData?.content)
|
|
26
|
+
? searchData.content
|
|
27
|
+
: (() => {
|
|
28
|
+
try {
|
|
29
|
+
return wireRows(searchData);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return [];
|
|
33
|
+
}
|
|
34
|
+
})();
|
|
35
|
+
const exact = found.find((row) => [row.username, row.userId, row.name].some((value) => String(value ?? "") === identity)) ??
|
|
36
|
+
found[0];
|
|
37
|
+
if (exact) {
|
|
38
|
+
userId = wireString(exact.id) ?? wireString(exact.userId);
|
|
39
|
+
name = wireString(exact.name) ?? identity;
|
|
40
|
+
if (userId) {
|
|
41
|
+
try {
|
|
42
|
+
const info = (await wireCall(session, { path: ENDPOINTS.organizationUserInfo, query: { id: userId } })).data;
|
|
43
|
+
if (info) {
|
|
44
|
+
userId = wireString(info.id) ?? userId;
|
|
45
|
+
name = wireString(info.name) ?? name;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
// keep search hit
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
name = identity;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
// best-effort actor — never poison the calling receipt
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
userId,
|
|
63
|
+
name,
|
|
64
|
+
isAdmin,
|
|
65
|
+
...(options.bypassReason ? { bypassReason: options.bypassReason } : {}),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
16
68
|
/** Resolve a rule ROW id from `(schemaCode, ruleCode)`. Reuses the list face. */
|
|
17
69
|
export async function resolveRuleRowId(session, schemaCode, ruleCode) {
|
|
18
70
|
const rows = wireRows((await wireCall(session, { path: ENDPOINTS.businessRuleListBySchemaCode, query: { schemaCode } })).data);
|
|
@@ -56,15 +56,15 @@ export const APP_LANE_CONTRACT = Object.freeze({
|
|
|
56
56
|
tools: Object.freeze({
|
|
57
57
|
"app.list": Object.freeze({
|
|
58
58
|
scope: "app package list",
|
|
59
|
-
description: "列出应用(apppackage/list
|
|
59
|
+
description: "列出应用(apppackage/list)及分组;回执含 applicationsCount/omitted。用于跨应用解析与 app.create/delete 前后读回。",
|
|
60
60
|
}),
|
|
61
61
|
"app.create": Object.freeze({
|
|
62
62
|
scope: "app package create",
|
|
63
|
-
description: "
|
|
63
|
+
description: "创建设计时应用包(apppackage/create,非目录)。groupId 可选:缺省用配置应用所在分组;否则首个具名非兜底分组(auto-first-group / resolved-to-first-group)。落到 code=all → resolved-to-fallback-group; portalHidden:true。读回 apppackage/list;平台自动建默认分组(defaultFolder.isDefault)。发布属边界外,需设计器人工发布。",
|
|
64
64
|
}),
|
|
65
65
|
"app.update": Object.freeze({
|
|
66
66
|
scope: "app package update",
|
|
67
|
-
description: "
|
|
67
|
+
description: "更新应用(apppackage/update,管理员面;源码证据 AppPackageController.update → AppIntegrationFacadeImpl.updateAppPackage):按记录 id 整体提交;显式 groupId 生效,缺省会强制回默认分组,enabled≠true 清 workflow 收藏——故只改 groupId 并原样保留 enabled。不补 i18nMessage;其它字段读回原样回传。读回 apppackage/list。",
|
|
68
68
|
}),
|
|
69
69
|
"app.delete": Object.freeze({
|
|
70
70
|
scope: "app package delete absence",
|
|
@@ -76,7 +76,7 @@ export function assertAppContract() {
|
|
|
76
76
|
assertLaneContract(APP_LANE_CONTRACT);
|
|
77
77
|
if (!APP_WIRE.defaultGroupResolution.includes("resolved-to-first-group"))
|
|
78
78
|
throw new Error("app contract pins the first-group fallback warning token");
|
|
79
|
-
assertDescriptionCarries(APP_LANE_CONTRACT, "app.list", ["apppackage/list", "跨应用"]);
|
|
79
|
+
assertDescriptionCarries(APP_LANE_CONTRACT, "app.list", ["apppackage/list", "跨应用", "applicationsCount"]);
|
|
80
80
|
assertDescriptionCarries(APP_LANE_CONTRACT, "app.create", ["apppackage/create", "resolved-to-first-group", "边界外", "defaultFolder"]);
|
|
81
81
|
if (!APP_WIRE.createReadiness.includes("zero"))
|
|
82
82
|
throw new Error("app contract must pin the zero-wire prerequisite refusal");
|
package/dist/lanes/app.js
CHANGED
|
@@ -53,7 +53,25 @@ export function registerAppLane(tools) {
|
|
|
53
53
|
name: row.appName ?? row.name,
|
|
54
54
|
groupId: row.groupId,
|
|
55
55
|
}));
|
|
56
|
-
|
|
56
|
+
// r29f F2:与 discover_environment 同构——一眼判断「内联是否全集」。
|
|
57
|
+
// 应用通常个位数,默认内联 + omitted:false;大租户由 governedRead 外置时
|
|
58
|
+
// 仍保留 applicationsCount,并由 diet 路径标 omitted。
|
|
59
|
+
return {
|
|
60
|
+
applications,
|
|
61
|
+
applicationsCount: applications.length,
|
|
62
|
+
omitted: false,
|
|
63
|
+
};
|
|
64
|
+
}, {
|
|
65
|
+
target: "app.list",
|
|
66
|
+
compact: (result) => {
|
|
67
|
+
const apps = Array.isArray(result.applications) ? result.applications : [];
|
|
68
|
+
return {
|
|
69
|
+
applicationsCount: apps.length,
|
|
70
|
+
omitted: true,
|
|
71
|
+
applications: apps.slice(0, 20),
|
|
72
|
+
tablesHint: "applications 已截断内联前 20 条(omitted:true);全集在 detailArtifactId,用 --jq applications[].code 或 artifact get 取回。",
|
|
73
|
+
};
|
|
74
|
+
},
|
|
57
75
|
}),
|
|
58
76
|
}));
|
|
59
77
|
writeTool({
|
|
@@ -50,12 +50,22 @@ export const DICTIONARY_LANE_CONTRACT = Object.freeze({
|
|
|
50
50
|
]),
|
|
51
51
|
tools: Object.freeze({
|
|
52
52
|
"dictionary.list": Object.freeze({ scope: "dictionary list", description: "分页读取数据字典(data_dictionary/searchDictionary),返回字典元数据。" }),
|
|
53
|
-
"dictionary.get": Object.freeze({ scope: "dictionary read", description: "按 id 或 code
|
|
54
|
-
"dictionary.records": Object.freeze({
|
|
53
|
+
"dictionary.get": Object.freeze({ scope: "dictionary read", description: "按 id 或 code 读取数据字典头;附 recordsCount(明细仍走 dictionary.records)。" }),
|
|
54
|
+
"dictionary.records": Object.freeze({
|
|
55
|
+
scope: "dictionary records read",
|
|
56
|
+
description: "读取字典记录(id/code/enabledOnly/parentId,保留 children)。回执恒 {records:{totalElements,content[]}, total}。",
|
|
57
|
+
}),
|
|
55
58
|
"dictionary.save": Object.freeze({ scope: "dictionary save", description: "创建或更新数据字典及记录(data_dictionary/saveDictionary);KEY_VALUE=0,TREE=1,写入前需审批。records 是增量 upsert:传 [] 不会清空已有记录,平台也没有字典/记录删除端点——停用请用 dictionary.status,清理需维护者数据库操作。" }),
|
|
56
59
|
"dictionary.status": Object.freeze({ scope: "dictionary status", description: "启用或停用字典或字典记录;停用字典时运行时不会返回选项。" }),
|
|
57
60
|
"dictionary.class": Object.freeze({ scope: "dictionary classification", description: "读取、新建、删除字典分类并维护分类关联字典。" }),
|
|
58
|
-
"dictionary.usage": Object.freeze({
|
|
61
|
+
"dictionary.usage": Object.freeze({
|
|
62
|
+
scope: "dictionary record usage",
|
|
63
|
+
description: "写:批量设置字典记录 initialUsed 使用标记(不是引用查询)。查谁引用了字典请用 dictionary.references。",
|
|
64
|
+
}),
|
|
65
|
+
"dictionary.references": Object.freeze({
|
|
66
|
+
scope: "dictionary refs",
|
|
67
|
+
description: "只读:谁引用了该字典(上限50+truncated)。",
|
|
68
|
+
}),
|
|
59
69
|
"dictionary.type": Object.freeze({ scope: "dictionary type read", description: "按 dictionaryType 读取字典。" }),
|
|
60
70
|
"dictionary.names": Object.freeze({ scope: "dictionary names read", description: "读取字典 id/name 摘要。" }),
|
|
61
71
|
"dictionary.parent": Object.freeze({ scope: "dictionary parent records read", description: "读取树型字典指定父节点的直接子记录。" }),
|
|
@@ -67,9 +77,10 @@ export const DICTIONARY_LANE_CONTRACT = Object.freeze({
|
|
|
67
77
|
export function assertDictionaryContract() {
|
|
68
78
|
assertLaneContract(DICTIONARY_LANE_CONTRACT);
|
|
69
79
|
assertDescriptionCarries(DICTIONARY_LANE_CONTRACT, "dictionary.save", ["saveDictionary", "KEY_VALUE=0", "TREE=1"]);
|
|
70
|
-
assertDescriptionCarries(DICTIONARY_LANE_CONTRACT, "dictionary.records", ["children"]);
|
|
80
|
+
assertDescriptionCarries(DICTIONARY_LANE_CONTRACT, "dictionary.records", ["children", "totalElements"]);
|
|
71
81
|
assertDescriptionCarries(DICTIONARY_LANE_CONTRACT, "dictionary.class", ["分类"]);
|
|
72
|
-
assertDescriptionCarries(DICTIONARY_LANE_CONTRACT, "dictionary.usage", ["initialUsed"]);
|
|
82
|
+
assertDescriptionCarries(DICTIONARY_LANE_CONTRACT, "dictionary.usage", ["initialUsed", "不是引用查询"]);
|
|
83
|
+
assertDescriptionCarries(DICTIONARY_LANE_CONTRACT, "dictionary.references", ["truncated"]);
|
|
73
84
|
if (!String(DICTIONARY_LANE_CONTRACT.wire.selectionDictionary).includes("checkedDictionary"))
|
|
74
85
|
throw new Error("dictionary contract must pin dictionary option keys");
|
|
75
86
|
if (!String(DICTIONARY_LANE_CONTRACT.wire.selectionBusinessModel).includes("sheetDataItem"))
|
|
@@ -15,6 +15,7 @@ export const DICTIONARY_COVERAGE = declareLaneCoverage(DICTIONARY_LANE_CONTRACT.
|
|
|
15
15
|
claim("dictionary.save", T["dictionary.save"].scope),
|
|
16
16
|
claim("dictionary.status", T["dictionary.status"].scope),
|
|
17
17
|
claim("dictionary.usage", T["dictionary.usage"].scope),
|
|
18
|
+
claim("dictionary.references", T["dictionary.references"].scope),
|
|
18
19
|
claim("dictionary.classes", T["dictionary.classes"].scope),
|
|
19
20
|
claim("dictionary.class.save", T["dictionary.class.save"].scope),
|
|
20
21
|
claim("dictionary.class.delete", T["dictionary.class.delete"].scope),
|