@stalfh233/omc-cli 0.0.0-stage → 0.4.10
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/LICENSE +21 -0
- package/README.md +293 -3
- package/coverage/m1-coverage-manifest-v1.json +1278 -0
- package/dist/approval-token.js +102 -0
- package/dist/args.js +25 -0
- package/dist/artifacts.js +119 -0
- package/dist/bench/call-face-eval.js +256 -0
- package/dist/bench/context-attribution.js +151 -0
- package/dist/bench/discovery-cost-eval.js +230 -0
- package/dist/bench/driver.js +81 -0
- package/dist/bench/evals.js +236 -0
- package/dist/bench/fake-http-server.js +65 -0
- package/dist/bench/instrument.js +87 -0
- package/dist/bench/intent-face-eval.js +343 -0
- package/dist/bench/run.js +166 -0
- package/dist/bench/scenario.js +343 -0
- package/dist/bench/types.js +76 -0
- package/dist/bi-wire.js +41 -0
- package/dist/bizservice-config.js +581 -0
- package/dist/call.js +153 -0
- package/dist/capability-absences.js +23 -0
- package/dist/capability-overview.js +492 -0
- package/dist/capability-shape.js +154 -0
- package/dist/cli-contract.js +70 -0
- package/dist/cli-output.js +73 -0
- package/dist/cli.js +1418 -0
- package/dist/code-rules.js +69 -0
- package/dist/command-transport.js +155 -0
- package/dist/config-store.js +195 -0
- package/dist/context.js +21 -0
- package/dist/contract-consistency.js +66 -0
- package/dist/contract-resources.js +62 -0
- package/dist/coverage-consistency.js +62 -0
- package/dist/coverage-registry.js +76 -0
- package/dist/coverage.js +130 -0
- package/dist/data-list-filter.js +114 -0
- package/dist/discovery.js +390 -0
- package/dist/endpoints.js +154 -0
- package/dist/environment-policy.js +26 -0
- package/dist/execution-metadata.js +1092 -0
- package/dist/fake/app.js +45 -0
- package/dist/fake/b2-registration.js +594 -0
- package/dist/fake/businessrule.js +242 -0
- package/dist/fake/datarule.js +72 -0
- package/dist/fake/dictionary.js +108 -0
- package/dist/fake/environment.js +30 -0
- package/dist/fake/field.js +167 -0
- package/dist/fake/form.js +101 -0
- package/dist/fake/index.js +121 -0
- package/dist/fake/list-view.js +289 -0
- package/dist/fake/model.js +136 -0
- package/dist/fake/online-js.js +18 -0
- package/dist/fake/report.js +253 -0
- package/dist/fake/routes.js +47 -0
- package/dist/fake/rule-lifecycle.js +37 -0
- package/dist/fake/runtime-data.js +367 -0
- package/dist/fake/state.js +67 -0
- package/dist/fake/workflow.js +364 -0
- package/dist/field-change.js +200 -0
- package/dist/field-families.js +896 -0
- package/dist/form-layout.js +111 -0
- package/dist/form-support.js +821 -0
- package/dist/goal-routes.js +468 -0
- package/dist/governed-execution.js +87 -0
- package/dist/human-summary.js +212 -0
- package/dist/identity.js +62 -0
- package/dist/intent/baseline.js +57 -0
- package/dist/intent/capabilities/bizservice.js +274 -0
- package/dist/intent/capabilities/businessrule.js +493 -0
- package/dist/intent/capabilities/datarule.js +187 -0
- package/dist/intent/capabilities/field.js +545 -0
- package/dist/intent/capabilities/form.js +136 -0
- package/dist/intent/capabilities/index.js +64 -0
- package/dist/intent/capabilities/listview.js +157 -0
- package/dist/intent/capabilities/model.js +100 -0
- package/dist/intent/capabilities/onlinejs.js +108 -0
- package/dist/intent/capabilities/report.js +355 -0
- package/dist/intent/capabilities/workflow.js +458 -0
- package/dist/intent/capability.js +6 -0
- package/dist/intent/cli.js +91 -0
- package/dist/intent/compare.js +56 -0
- package/dist/intent/compiler.js +79 -0
- package/dist/intent/dsl.js +129 -0
- package/dist/intent/plan-file.js +63 -0
- package/dist/intent/readback.js +65 -0
- package/dist/intent/schema.js +158 -0
- package/dist/intent/validation.js +30 -0
- package/dist/intent/yaml.js +315 -0
- package/dist/json-column.js +68 -0
- package/dist/lanes/app-contract.js +95 -0
- package/dist/lanes/app-coverage.js +16 -0
- package/dist/lanes/app.js +174 -0
- package/dist/lanes/apply-changes.js +231 -0
- package/dist/lanes/b2-registration-contract.js +292 -0
- package/dist/lanes/b2-registration-coverage.js +48 -0
- package/dist/lanes/b2-registration.js +1187 -0
- package/dist/lanes/businessrule-contract.js +232 -0
- package/dist/lanes/businessrule-coverage.js +16 -0
- package/dist/lanes/businessrule.js +221 -0
- package/dist/lanes/contract-support.js +65 -0
- package/dist/lanes/coverage-declaration.js +9 -0
- package/dist/lanes/datarule-contract.js +155 -0
- package/dist/lanes/datarule-coverage.js +19 -0
- package/dist/lanes/datarule-protocol.js +308 -0
- package/dist/lanes/datarule.js +818 -0
- package/dist/lanes/dictionary-contract.js +78 -0
- package/dist/lanes/dictionary-coverage.js +24 -0
- package/dist/lanes/dictionary.js +235 -0
- package/dist/lanes/environment-contract.js +70 -0
- package/dist/lanes/environment-coverage.js +14 -0
- package/dist/lanes/environment.js +163 -0
- package/dist/lanes/field-contract.js +223 -0
- package/dist/lanes/field-coverage.js +27 -0
- package/dist/lanes/field.js +374 -0
- package/dist/lanes/form-contract.js +97 -0
- package/dist/lanes/form-coverage.js +16 -0
- package/dist/lanes/form.js +185 -0
- package/dist/lanes/lane-ids.js +34 -0
- package/dist/lanes/list-view-contract.js +175 -0
- package/dist/lanes/list-view-coverage.js +20 -0
- package/dist/lanes/list-view-shapes.js +1207 -0
- package/dist/lanes/list-view.js +578 -0
- package/dist/lanes/meta-contract.js +85 -0
- package/dist/lanes/meta.js +255 -0
- package/dist/lanes/model-contract.js +168 -0
- package/dist/lanes/model-coverage.js +20 -0
- package/dist/lanes/model.js +986 -0
- package/dist/lanes/online-js-contract.js +99 -0
- package/dist/lanes/online-js-coverage.js +28 -0
- package/dist/lanes/online-js.js +127 -0
- package/dist/lanes/report-contract.js +144 -0
- package/dist/lanes/report-coverage.js +21 -0
- package/dist/lanes/report.js +476 -0
- package/dist/lanes/rule-graph.js +1846 -0
- package/dist/lanes/rule-lifecycle-contract.js +92 -0
- package/dist/lanes/rule-lifecycle-coverage.js +20 -0
- package/dist/lanes/rule-lifecycle.js +176 -0
- package/dist/lanes/runtime-data-contract.js +264 -0
- package/dist/lanes/runtime-data-coverage.js +25 -0
- package/dist/lanes/runtime-data.js +1054 -0
- package/dist/lanes/workflow-contract.js +223 -0
- package/dist/lanes/workflow-coverage.js +40 -0
- package/dist/lanes/workflow.js +1813 -0
- package/dist/online-js-layout.js +58 -0
- package/dist/online-js-source.js +276 -0
- package/dist/package-tool.js +51 -0
- package/dist/package.js +73 -0
- package/dist/plan.js +73 -0
- package/dist/read.js +144 -0
- package/dist/redact.js +28 -0
- package/dist/rule-support.js +587 -0
- package/dist/runtime-support.js +134 -0
- package/dist/server.js +92 -0
- package/dist/session-manager.js +30 -0
- package/dist/session.js +149 -0
- package/dist/skills.js +112 -0
- package/dist/support.js +98 -0
- package/dist/tool-types.js +127 -0
- package/dist/tools.js +59 -0
- package/dist/usage-log.js +197 -0
- package/dist/wire.js +287 -0
- package/dist/workflow-support.js +99 -0
- package/dist/write-lease.js +26 -0
- package/dist/write-lock.js +109 -0
- package/dist/write.js +176 -0
- package/dist/zip.js +156 -0
- package/docs/tool-surface-map.md +42 -0
- package/package.json +71 -6
- package/skills/omc-acceptance-criteria.md +47 -0
- package/skills/omc-business-configuration.md +257 -0
- package/skills/omc-capabilities.md +197 -0
- package/skills/omc-capability-scouting.md +55 -0
- package/skills/omc-config-draft-review.md +171 -0
- package/skills/omc-five-piece-flow.md +35 -0
- package/skills/omc-glossary.md +86 -0
- package/skills/omc-refusals.md +110 -0
- package/skills/omc-requirement-analysis.md +161 -0
- package/skills/omc-requirement-vocabulary.md +51 -0
- package/skills/omc-start-here.md +82 -0
- package/skills/omc-tool-selection.md +119 -0
- package/skills/omc-write-hazards.md +87 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { assertDescriptionCarries, assertLaneContract, behaviorBinding, CONTRACT_FAMILY, CONTRACT_REFERENCE_VERSION, emitterBinding } from "./contract-support.js";
|
|
2
|
+
import { LANES } from "./lane-ids.js";
|
|
3
|
+
import { RULE_PLATFORM_QUIRK } from "./businessrule-contract.js";
|
|
4
|
+
/**
|
|
5
|
+
* The live-proven enable chain (2026-09-11): `publish` creates a new version
|
|
6
|
+
* (the server runs its enableability check) and `enable` flips the DRAFT row's
|
|
7
|
+
* switch. The runtime dispatcher reads the DRAFT row's `enabled` plus the
|
|
8
|
+
* latest PUBLISH content (getExecutableBySchemaCodeAndCode), so publishing
|
|
9
|
+
* without enabling silently does nothing.
|
|
10
|
+
*/
|
|
11
|
+
export const RULE_ENABLE_CHAIN = Object.freeze({
|
|
12
|
+
publish: "publish 以完整规则模型出新版本(平台内部跑可启用了性校验)",
|
|
13
|
+
enable: "enable 以 {schemaCode, ruleCode} 翻转 DRAFT 行的启用开关",
|
|
14
|
+
dispatch: "运行时分发读的是 DRAFT 行 enabled + 最新 PUBLISH 内容(getExecutableBySchemaCodeAndCode)",
|
|
15
|
+
conclusion: "只 publish 不 enable 时规则静默不生效",
|
|
16
|
+
readbackRule: "Readback must reflect the platform's `enabled` field directly — never infer from `status`.",
|
|
17
|
+
idempotentErrcode: 550014,
|
|
18
|
+
});
|
|
19
|
+
export const RULE_SCHEMA_GATE = Object.freeze({
|
|
20
|
+
probe: "bizmodels/get_all (GET ?schemaCode) — BizSchemaController.java:615-633, miss answers SCHEMA_NOT_FOUNT_ERR 10011 业务模型不存在",
|
|
21
|
+
reason: "list_by_schemacode 对任意 schemaCode 自动补建 7 条预置规则并自动 publish(BusinessRuleFacadeImpl.listViewsBySchemaCode:179-185 → BusinessRuleServiceImpl.checkAndFillDefaultBusinessRule:315-344,不校验模型存在;publish 才拦 601003)——幻影 schema 的规则写入会在 API 不可删除的预置行上留下残留,故 rule.save/enable/disable/delete 写前一律先探测模型存在,缺失即 refused `rule-schema-unresolved`",
|
|
22
|
+
presetDelete: "预置 ruleCode(Create/Update/Delete/GetList/Load/Available/Cancel)删除不可达:list 自动重灌缺失预置,删除后下一次 list 复活且版本行增殖——rule.delete 对预置 code 直接 refused `rule-preset-delete-unreachable`",
|
|
23
|
+
});
|
|
24
|
+
export function ruleEnableDescription() {
|
|
25
|
+
const chain = RULE_ENABLE_CHAIN;
|
|
26
|
+
return `启用业务规则(businessrule/publish + businessrule/enable)。两步缺一不可(2026-09-11 live 取证):${chain.publish},${chain.enable}——${chain.dispatch},${chain.conclusion}。${chain.readbackRule}。${RULE_SCHEMA_GATE.reason}。`;
|
|
27
|
+
}
|
|
28
|
+
export function ruleDisableDescription() {
|
|
29
|
+
return `停用业务规则(businessrule/update)。读回规则行后以整行 + \`enabled:false\` 作为请求体提交。${RULE_ENABLE_CHAIN.readbackRule}。${RULE_SCHEMA_GATE.reason}。`;
|
|
30
|
+
}
|
|
31
|
+
export const RULE_LIFECYCLE_LANE_CONTRACT = Object.freeze({
|
|
32
|
+
lane: LANES.ruleLifecycle,
|
|
33
|
+
family: CONTRACT_FAMILY,
|
|
34
|
+
referenceVersion: CONTRACT_REFERENCE_VERSION,
|
|
35
|
+
endpoints: Object.freeze({
|
|
36
|
+
list: "businessRuleListBySchemaCode",
|
|
37
|
+
publish: "businessRulePublish",
|
|
38
|
+
enable: "businessRuleEnable",
|
|
39
|
+
update: "businessRuleUpdate",
|
|
40
|
+
delete: "businessRuleDelete",
|
|
41
|
+
}),
|
|
42
|
+
wire: Object.freeze({
|
|
43
|
+
chain: Object.freeze(["publish", "enable"]),
|
|
44
|
+
enableBodyKeys: Object.freeze(["schemaCode", "ruleCode"]),
|
|
45
|
+
idempotentErrcode: RULE_ENABLE_CHAIN.idempotentErrcode,
|
|
46
|
+
platformQuirk: RULE_PLATFORM_QUIRK,
|
|
47
|
+
}),
|
|
48
|
+
wireBindings: Object.freeze({
|
|
49
|
+
chain: Object.freeze([behaviorBinding("test/rule-list.test.ts#rule-lifecycle lane contract pins the publish+enable chain", assertRuleLifecycleContract)]),
|
|
50
|
+
enableBodyKeys: Object.freeze([behaviorBinding("test/rule-list.test.ts#rule-lifecycle lane contract pins the publish+enable chain", assertRuleLifecycleContract)]),
|
|
51
|
+
idempotentErrcode: Object.freeze([emitterBinding("lanes/rule-lifecycle-contract.RULE_ENABLE_CHAIN", assertRuleLifecycleContract)]),
|
|
52
|
+
platformQuirk: Object.freeze([emitterBinding("lanes/businessrule-contract.RULE_PLATFORM_QUIRK", assertRuleLifecycleContract)]),
|
|
53
|
+
}),
|
|
54
|
+
evidence: Object.freeze([
|
|
55
|
+
"ticket 02 (2026-09-11): only-publish leaves the rule silently ineffective; publish then enable is required",
|
|
56
|
+
"getExecutableBySchemaCodeAndCode reads the DRAFT row enabled + latest PUBLISH content",
|
|
57
|
+
"re-enabling an already-enabled rule answers 550014 业务规则当前是启用状态 (idempotent)",
|
|
58
|
+
]),
|
|
59
|
+
tools: Object.freeze({
|
|
60
|
+
"rule.enable": Object.freeze({ scope: "enable invoke disable delete", description: ruleEnableDescription() }),
|
|
61
|
+
"rule.disable": Object.freeze({ scope: "enable invoke disable delete", description: ruleDisableDescription() }),
|
|
62
|
+
"rule.delete": Object.freeze({
|
|
63
|
+
scope: "enable invoke disable delete",
|
|
64
|
+
description: `删除业务规则(businessrule/delete,id 先经 list 解析)并精确回读缺失。${RULE_SCHEMA_GATE.presetDelete}。${RULE_SCHEMA_GATE.reason}。`,
|
|
65
|
+
}),
|
|
66
|
+
"rule.disableMany": Object.freeze({
|
|
67
|
+
scope: "enable invoke disable delete",
|
|
68
|
+
description: "批量停用某模型的业务规则(一次调用,逐条 businessrule/update enabled:false)。**默认停用全部匹配规则(含平台预置触发码 Create/Update/…)**——用户逻辑常写进预置行,跳过会静默漏停;`customOnly:true` 只停自定义、跳过预置,`ruleCodes` 限定子集。返回 disabled/skipped 与仍在启用的兜底核对。",
|
|
69
|
+
}),
|
|
70
|
+
configure_rule: Object.freeze({
|
|
71
|
+
scope: "enable invoke disable delete",
|
|
72
|
+
description: "High-level: save a business rule and enable it (rule.save → rule.enable → readback), subject to the write permission gate.",
|
|
73
|
+
}),
|
|
74
|
+
}),
|
|
75
|
+
});
|
|
76
|
+
export function assertRuleLifecycleContract() {
|
|
77
|
+
assertLaneContract(RULE_LIFECYCLE_LANE_CONTRACT);
|
|
78
|
+
if (RULE_ENABLE_CHAIN.idempotentErrcode !== 550014)
|
|
79
|
+
throw new Error("rule-lifecycle contract pins the idempotent re-enable errcode to 550014");
|
|
80
|
+
assertDescriptionCarries(RULE_LIFECYCLE_LANE_CONTRACT, "rule.enable", ["businessrule/publish", "businessrule/enable", "DRAFT", "PUBLISH", "`enabled`", "`status`"]);
|
|
81
|
+
assertDescriptionCarries(RULE_LIFECYCLE_LANE_CONTRACT, "rule.disable", ["businessrule/update", "`enabled:false`", "`enabled`", "`status`"]);
|
|
82
|
+
assertDescriptionCarries(RULE_LIFECYCLE_LANE_CONTRACT, "rule.disableMany", ["businessrule/update", "enabled:false", "customOnly", "ruleCodes", "disabled"]);
|
|
83
|
+
assertDescriptionCarries(RULE_LIFECYCLE_LANE_CONTRACT, "rule.delete", ["businessrule/delete", "rule-preset-delete-unreachable", "预置", "rule-schema-unresolved"]);
|
|
84
|
+
assertDescriptionCarries(RULE_LIFECYCLE_LANE_CONTRACT, "configure_rule", ["rule.save", "rule.enable"]);
|
|
85
|
+
if (JSON.stringify(RULE_LIFECYCLE_LANE_CONTRACT.wire.chain) !== JSON.stringify(["publish", "enable"]))
|
|
86
|
+
throw new Error("rule-lifecycle contract pins the enable chain to publish+enable");
|
|
87
|
+
if (JSON.stringify(RULE_LIFECYCLE_LANE_CONTRACT.wire.enableBodyKeys) !== JSON.stringify(["schemaCode", "ruleCode"]))
|
|
88
|
+
throw new Error("rule-lifecycle contract pins the enable body keys");
|
|
89
|
+
if (!RULE_PLATFORM_QUIRK.includes("status"))
|
|
90
|
+
throw new Error("rule-lifecycle contract must pin the enabled/status quirk");
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=rule-lifecycle-contract.js.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { claim, declareLaneCoverage } from "./coverage-declaration.js";
|
|
2
|
+
import { RULE_LIFECYCLE_LANE_CONTRACT } from "./rule-lifecycle-contract.js";
|
|
3
|
+
const T = RULE_LIFECYCLE_LANE_CONTRACT.tools;
|
|
4
|
+
export const RULE_LIFECYCLE_COVERAGE = declareLaneCoverage(RULE_LIFECYCLE_LANE_CONTRACT.lane, [
|
|
5
|
+
{
|
|
6
|
+
capability: "S25 business-rule enable/invoke/disable/delete lifecycle",
|
|
7
|
+
evidence: [
|
|
8
|
+
".scratch/omc-business-rules/acceptance/s25-enable-20260910001115307.md",
|
|
9
|
+
".scratch/omc-business-rules/acceptance/s25-cleanup-20260910001140227.md",
|
|
10
|
+
],
|
|
11
|
+
tools: [
|
|
12
|
+
claim("rule.enable", T["rule.enable"].scope),
|
|
13
|
+
claim("rule.disable", T["rule.disable"].scope),
|
|
14
|
+
claim("rule.delete", T["rule.delete"].scope),
|
|
15
|
+
claim("rule.disableMany", T["rule.disableMany"].scope),
|
|
16
|
+
claim("configure_rule", T["configure_rule"].scope),
|
|
17
|
+
],
|
|
18
|
+
},
|
|
19
|
+
]);
|
|
20
|
+
//# sourceMappingURL=rule-lifecycle-coverage.js.map
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { ENDPOINTS } from "../endpoints.js";
|
|
3
|
+
import { writeTool } from "../write.js";
|
|
4
|
+
import { registerPackageTool } from "../package-tool.js";
|
|
5
|
+
import { writeSummary } from "../human-summary.js";
|
|
6
|
+
import { executeRuleSaveWrite, ruleNodeGraphArg, assertRuleSchemaExists } from "../rule-support.js";
|
|
7
|
+
import { ToolRefusalError } from "../tool-types.js";
|
|
8
|
+
import { prepareRuleGraph, validateRuleNodeGraph, PLATFORM_TRIGGER_RULE_CODES } from "./rule-graph.js";
|
|
9
|
+
import { wireCall, wireRows, WireError } from "../wire.js";
|
|
10
|
+
import { RULE_ENABLE_CHAIN, RULE_LIFECYCLE_LANE_CONTRACT } from "./rule-lifecycle-contract.js";
|
|
11
|
+
const RULE_LIFECYCLE_LANE = RULE_LIFECYCLE_LANE_CONTRACT.lane;
|
|
12
|
+
const RULE_LIFECYCLE_TOOLS = RULE_LIFECYCLE_LANE_CONTRACT.tools;
|
|
13
|
+
export async function executeRuleEnableWrite(session, args) {
|
|
14
|
+
await assertRuleSchemaExists(session, args.schemaCode);
|
|
15
|
+
const list = await wireCall(session, { path: ENDPOINTS.businessRuleListBySchemaCode, query: { schemaCode: args.schemaCode } });
|
|
16
|
+
const row = wireRows(list.data).find((item) => item.code === args.ruleCode);
|
|
17
|
+
if (!row?.id)
|
|
18
|
+
throw new WireError("business", `rule ${args.ruleCode} not found for enable`, 302024);
|
|
19
|
+
// Publish must carry the FULL model, not the list-projection row. The list
|
|
20
|
+
// face (`list_by_schemacode`) omits `node`/`route`/`schedulerSetting`; for a
|
|
21
|
+
// TIMING_TASK rule that makes `BusinessRuleServiceImpl.updateTimerJob` see a
|
|
22
|
+
// null scheduler and early-return — the quartz job is NEVER created, so the
|
|
23
|
+
// rule reads back "enabled" but never fires (2026-10-06 live: rule.enable from
|
|
24
|
+
// the list row → no `create Job`, `qrtz_job_details` empty). Fetch the
|
|
25
|
+
// authoritative model via `businessrule/get?id` (same face `rule.get` uses),
|
|
26
|
+
// stamp enabled+status, then publish — which creates/reschedules the job.
|
|
27
|
+
const full = (await wireCall(session, { path: ENDPOINTS.businessRuleGet, query: { id: String(row.id) } })).data;
|
|
28
|
+
if (!full)
|
|
29
|
+
throw new WireError("business", `rule ${args.ruleCode} 完整模型读回为空(businessrule/get id ${row.id}),拒绝用列表投影发布`, 302024);
|
|
30
|
+
const published = await wireCall(session, { path: ENDPOINTS.businessRulePublish, method: "POST", body: { ...full, enabled: true, status: "ENABLED" } });
|
|
31
|
+
let enableErrcode = 0;
|
|
32
|
+
let alreadyEnabled = false;
|
|
33
|
+
try {
|
|
34
|
+
const enabled = await wireCall(session, { path: ENDPOINTS.businessRuleEnable, method: "POST", body: { schemaCode: args.schemaCode, ruleCode: args.ruleCode } });
|
|
35
|
+
enableErrcode = enabled.errcode;
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
if (error instanceof WireError && error.kind === "business" && error.errcode === RULE_ENABLE_CHAIN.idempotentErrcode)
|
|
39
|
+
alreadyEnabled = true;
|
|
40
|
+
else
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
|
+
const readback = await wireCall(session, { path: ENDPOINTS.businessRuleListBySchemaCode, query: { schemaCode: args.schemaCode } });
|
|
44
|
+
const after = wireRows(readback.data).find((item) => item.code === args.ruleCode);
|
|
45
|
+
if (after?.enabled !== true)
|
|
46
|
+
throw new ToolRefusalError({ status: "refused", blocker: "rule-enable-readback-failed", message: `rule ${args.ruleCode} 发布后未启用;enabled 必须为 true` });
|
|
47
|
+
return { performed: { errcode: published.errcode, enableErrcode, ...(alreadyEnabled ? { alreadyEnabled: true } : {}) }, readback: { enabled: true } };
|
|
48
|
+
}
|
|
49
|
+
export function registerRuleLifecycleLane(tools) {
|
|
50
|
+
writeTool(tools, "rule.enable", RULE_LIFECYCLE_LANE, RULE_LIFECYCLE_TOOLS["rule.enable"].scope, RULE_LIFECYCLE_TOOLS["rule.enable"].description, { schemaCode: z.string().min(1), ruleCode: z.string().min(1) }, async (session, args) => executeRuleEnableWrite(session, args));
|
|
51
|
+
writeTool(tools, "rule.disable", RULE_LIFECYCLE_LANE, RULE_LIFECYCLE_TOOLS["rule.disable"].scope, RULE_LIFECYCLE_TOOLS["rule.disable"].description, { schemaCode: z.string().min(1), ruleCode: z.string().min(1) }, async (session, args) => {
|
|
52
|
+
await assertRuleSchemaExists(session, args.schemaCode);
|
|
53
|
+
const list = await wireCall(session, { path: ENDPOINTS.businessRuleListBySchemaCode, query: { schemaCode: args.schemaCode } });
|
|
54
|
+
const row = wireRows(list.data).find((item) => item.code === args.ruleCode);
|
|
55
|
+
if (!row?.id)
|
|
56
|
+
throw new WireError("business", `rule ${args.ruleCode} not found for disable`, 302024);
|
|
57
|
+
const performed = await wireCall(session, { path: ENDPOINTS.businessRuleUpdate, method: "POST", body: { ...row, enabled: false, status: "DISABLED" } });
|
|
58
|
+
const readback = await wireCall(session, { path: ENDPOINTS.businessRuleListBySchemaCode, query: { schemaCode: args.schemaCode } });
|
|
59
|
+
const after = wireRows(readback.data).find((item) => item.code === args.ruleCode);
|
|
60
|
+
return { performed: { errcode: performed.errcode }, readback: { enabled: after?.enabled === true } };
|
|
61
|
+
});
|
|
62
|
+
writeTool(tools, "rule.delete", RULE_LIFECYCLE_LANE, RULE_LIFECYCLE_TOOLS["rule.delete"].scope, RULE_LIFECYCLE_TOOLS["rule.delete"].description, { schemaCode: z.string().min(1), ruleCode: z.string().min(1) }, async (session, args) => {
|
|
63
|
+
// Ticket 96 finding: the preset rule codes are platform-managed —
|
|
64
|
+
// listViewsBySchemaCode re-fills any missing preset for the schema, so
|
|
65
|
+
// deleting one via the API is unreachable (it resurrects on the next
|
|
66
|
+
// list). Refuse up front instead of "succeeding" and multiplying rows.
|
|
67
|
+
if (PLATFORM_TRIGGER_RULE_CODES.has(args.ruleCode))
|
|
68
|
+
throw new ToolRefusalError({
|
|
69
|
+
status: "refused",
|
|
70
|
+
blocker: "rule-preset-delete-unreachable",
|
|
71
|
+
message: `ruleCode "${args.ruleCode}" 是平台预置触发规则——list_by_schemacode 会自动重灌缺失预置(BusinessRuleFacadeImpl.listViewsBySchemaCode:179-185),API 删除不可达且会增殖版本行;预置规则请用 rule.save 覆盖图或 rule.disable 停用`,
|
|
72
|
+
});
|
|
73
|
+
await assertRuleSchemaExists(session, args.schemaCode);
|
|
74
|
+
const list = await wireCall(session, { path: ENDPOINTS.businessRuleListBySchemaCode, query: { schemaCode: args.schemaCode } });
|
|
75
|
+
const row = wireRows(list.data).find((item) => item.code === args.ruleCode);
|
|
76
|
+
if (!row?.id)
|
|
77
|
+
throw new WireError("business", `rule ${args.ruleCode} not found for delete`, 302024);
|
|
78
|
+
const performed = await wireCall(session, { path: ENDPOINTS.businessRuleDelete, method: "POST", query: { id: String(row.id) } });
|
|
79
|
+
const readback = await wireCall(session, { path: ENDPOINTS.businessRuleListBySchemaCode, query: { schemaCode: args.schemaCode } });
|
|
80
|
+
return { performed: { errcode: performed.errcode }, readback: { absent: !wireRows(readback.data).some((item) => item.code === args.ruleCode) } };
|
|
81
|
+
});
|
|
82
|
+
// Round 10 issue 05 — lifecycle batch. Disabling a model's rules one by one was
|
|
83
|
+
// N calls; this flips `enabled:false` on every matching rule in one tool. Only
|
|
84
|
+
// CUSTOM rules by default (platform preset trigger rows must not be disabled —
|
|
85
|
+
// they are platform-managed); `includePresets:true` opts in explicitly.
|
|
86
|
+
writeTool(tools, "rule.disableMany", RULE_LIFECYCLE_LANE, RULE_LIFECYCLE_TOOLS["rule.disableMany"].scope, RULE_LIFECYCLE_TOOLS["rule.disableMany"].description, {
|
|
87
|
+
schemaCode: z.string().min(1),
|
|
88
|
+
ruleCodes: z.array(z.string().min(1)).optional().describe("只停用这些 code;省略=该模型全部规则"),
|
|
89
|
+
customOnly: z.boolean().optional().describe("默认 false:停用全部匹配规则(含平台预置触发码 Create/Update/…,因为用户逻辑常写在其中);true 只停用自定义规则、跳过预置。"),
|
|
90
|
+
}, async (session, args) => {
|
|
91
|
+
await assertRuleSchemaExists(session, args.schemaCode);
|
|
92
|
+
const list = await wireCall(session, { path: ENDPOINTS.businessRuleListBySchemaCode, query: { schemaCode: args.schemaCode } });
|
|
93
|
+
const wanted = args.ruleCodes ? new Set(args.ruleCodes) : undefined;
|
|
94
|
+
const targets = wireRows(list.data).filter((row) => {
|
|
95
|
+
const code = String(row.code ?? "");
|
|
96
|
+
if (wanted && !wanted.has(code))
|
|
97
|
+
return false;
|
|
98
|
+
// NOTE: presets are INCLUDED by default — user logic is frequently
|
|
99
|
+
// authored INTO preset trigger rows (Create/Update/…), so skipping them
|
|
100
|
+
// would silently leave the user's rules enabled. customOnly:true opts out.
|
|
101
|
+
if (args.customOnly === true && PLATFORM_TRIGGER_RULE_CODES.has(code))
|
|
102
|
+
return false;
|
|
103
|
+
return true;
|
|
104
|
+
});
|
|
105
|
+
const disabled = [];
|
|
106
|
+
const skipped = [];
|
|
107
|
+
for (const row of targets) {
|
|
108
|
+
const code = String(row.code ?? "");
|
|
109
|
+
try {
|
|
110
|
+
await wireCall(session, { path: ENDPOINTS.businessRuleUpdate, method: "POST", body: { ...row, enabled: false, status: "DISABLED" } });
|
|
111
|
+
disabled.push(code);
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
skipped.push({ ruleCode: code, reason: error instanceof Error ? error.message : String(error) });
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
const after = wireRows((await wireCall(session, { path: ENDPOINTS.businessRuleListBySchemaCode, query: { schemaCode: args.schemaCode } })).data);
|
|
118
|
+
const stillEnabled = after.filter((row) => row.enabled === true && disabled.includes(String(row.code ?? ""))).map((row) => String(row.code));
|
|
119
|
+
return { performed: { disabled }, readback: { disabledCount: disabled.length, disabled, skipped, stillEnabled } };
|
|
120
|
+
});
|
|
121
|
+
registerPackageTool(tools, {
|
|
122
|
+
name: "configure_rule",
|
|
123
|
+
lane: RULE_LIFECYCLE_LANE,
|
|
124
|
+
scope: RULE_LIFECYCLE_TOOLS.configure_rule.scope,
|
|
125
|
+
description: RULE_LIFECYCLE_TOOLS.configure_rule.description,
|
|
126
|
+
inputSchema: {
|
|
127
|
+
schemaCode: z.string().min(1),
|
|
128
|
+
ruleCode: z.string().min(1),
|
|
129
|
+
name: z.string().min(1),
|
|
130
|
+
nodeGraph: ruleNodeGraphArg,
|
|
131
|
+
customTrigger: z.boolean().optional().describe("默认开:自定义 ruleCode + bizRuleType 0 会在 START 后合成 CUSTOM_NODE 系统触发节点;显式 false 退回(见 rule.save 描述)"),
|
|
132
|
+
},
|
|
133
|
+
// CFG-02/CFG-26: lint the SAME normalized graph the executor sends, before
|
|
134
|
+
// the plan is built — a bad graph must refuse here, not after the
|
|
135
|
+
// human has already approved a package that cannot run.
|
|
136
|
+
preflight: (args) => {
|
|
137
|
+
const prepared = prepareRuleGraph(args.ruleCode, (args.nodeGraph ?? {}), args.customTrigger, args.schemaCode);
|
|
138
|
+
const blockers = [...prepared.warnings, ...validateRuleNodeGraph(prepared.graph, args.ruleCode, typeof args.schemaCode === "string" ? args.schemaCode : undefined)];
|
|
139
|
+
// Ticket 113: warning-severity findings ride the payload but never gate.
|
|
140
|
+
if (blockers.some((blocker) => blocker.severity !== "warning"))
|
|
141
|
+
throw new ToolRefusalError({ status: "refused", blocker: "rule-graph-validation-failed", message: "nodeGraph 未过出门前校验(dry lint);按 validation 逐项修正,校验器不猜不改", validation: blockers });
|
|
142
|
+
},
|
|
143
|
+
plan: (args) => ({
|
|
144
|
+
summary: writeSummary("configure_rule", args, undefined, "plan"),
|
|
145
|
+
actions: [
|
|
146
|
+
{ tool: "rule.save", args: { schemaCode: args.schemaCode, ruleCode: args.ruleCode, name: args.name, nodeGraph: args.nodeGraph, ...(args.customTrigger === true ? { customTrigger: true } : {}) } },
|
|
147
|
+
{ tool: "rule.enable", args: { schemaCode: args.schemaCode, ruleCode: args.ruleCode } },
|
|
148
|
+
],
|
|
149
|
+
}),
|
|
150
|
+
execute: async (session, args) => {
|
|
151
|
+
const results = [];
|
|
152
|
+
// configure_rule MUST delegate to rule.save's upsert (which can also
|
|
153
|
+
// update the platform's preset trigger rules like Create/Update) —
|
|
154
|
+
// never bypass with a direct create-only call.
|
|
155
|
+
const saveOutcome = await executeRuleSaveWrite(session, {
|
|
156
|
+
schemaCode: args.schemaCode,
|
|
157
|
+
ruleCode: args.ruleCode,
|
|
158
|
+
name: args.name,
|
|
159
|
+
nodeGraph: args.nodeGraph,
|
|
160
|
+
...(args.customTrigger === true ? { customTrigger: true } : {}),
|
|
161
|
+
});
|
|
162
|
+
results.push({ step: "rule.save", errcode: saveOutcome.performed.errcode, mode: saveOutcome.performed.mode });
|
|
163
|
+
try {
|
|
164
|
+
const enableOutcome = await executeRuleEnableWrite(session, args);
|
|
165
|
+
results.push({ step: "rule.enable", ...enableOutcome });
|
|
166
|
+
return { steps: results, readback: { rulePresent: true, enabled: true } };
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
const envelope = error instanceof ToolRefusalError ? { ...error.payload } : error instanceof WireError ? { blocker: `wire-${error.kind}`, errcode: error.errcode, message: error.message } : { blocker: "configure-rule-step-failed", message: String(error) };
|
|
170
|
+
return { status: "refused", steps: results, failedStep: { index: results.length, tool: "rule.enable" }, envelope };
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
summarize: (args, _outcome, phase) => writeSummary("configure_rule", args, undefined, phase),
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
//# sourceMappingURL=rule-lifecycle.js.map
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { assertDescriptionCarries, assertLaneContract, behaviorBinding, CONTRACT_FAMILY, CONTRACT_REFERENCE_VERSION, describeTool, emitterBinding } from "./contract-support.js";
|
|
3
|
+
import { LANES } from "./lane-ids.js";
|
|
4
|
+
/**
|
|
5
|
+
* M2 runtime-data wire facts (live-proven). The submit chain, the replayToken
|
|
6
|
+
* gate, the three-layer queryCondition, and the propertyType injection are the
|
|
7
|
+
* single source for the handler, the arg schemas, and the fake.
|
|
8
|
+
*/
|
|
9
|
+
export const RUNTIME_DATA_WIRE = Object.freeze({
|
|
10
|
+
queryConditionShape: "List<List<List<FilterModel>>>(level 1 AND across groups, level 2 OR across and-groups, level 3 AND across leaves)",
|
|
11
|
+
dslShape: "or>and>叶子",
|
|
12
|
+
queryBodyCanon: "filters:[], queryVersion:1, queryCode=schemaCode, showTotal",
|
|
13
|
+
typeCastErrcode: 10009,
|
|
14
|
+
emptyModelErrcode: 301005,
|
|
15
|
+
absenceErrcode: 304001,
|
|
16
|
+
formTypeNoWorkflow: "2",
|
|
17
|
+
formTypeWorkflow: "1",
|
|
18
|
+
replayTokenEndpoint: "/api/api/runtime/form/getReplayToken",
|
|
19
|
+
replayTokenErrcode: 100005,
|
|
20
|
+
approvalNpeErrcode: 50000,
|
|
21
|
+
workflowStartDuplicateErrcode: 40000,
|
|
22
|
+
submitChain: "submit 自持久化(ticket 18 源码证据):form/submit 持久化 bizObject.data——formType=2 submitBizSheet→saveBizObject(置 COMPLETED,mergeObject 合并);formType=1 workItem→SubmitDataChangeHandlerImpl.updateBizObject;payload {id,schemaCode,sheetCode,ownerId?,data}+actionCode:\"submit\",owner 服务端以库内行回填,data 剥 engine 键(sequenceStatus/sequenceNo)",
|
|
23
|
+
submitInPlace: "无流程且 bizObjectId 有值时先 form/load 预检(缺席 304001 拒绝),再以预检行+formData 合成最小对象直提 runtime/form/submit(submit 自持久化,无预 save/构建 load);submit 返回 objectId 必须仍是目标 id,否则拒绝;仅无 workflowCode/workItemId/workflowInstanceId 的无 id 便捷链先 form/save 建稿再直提(新建记录);带 workItemId 或 workflowInstanceId 而缺 bizObjectId 时 fail-closed 拒绝(submit-biz-object-id-required);workflowCode + 既有无实例 bizObjectId 在 8.6.26 无 REST 就地面(见 workflowStartCreateOnly),持续拒绝并指向 workflowDraftSave 两段式",
|
|
24
|
+
workflowDraftSave: "门户暂存形状(ticket 13):POST runtime/form/save 顶层带 workflowCode(无 workflowInstanceId/workItemId)→ FormRuntimeController.saveWorkflow→startWorkflow(finishStart=FALSE):同 id 建 DRAFT 行 + DRAFT 流程实例 + 发起 workItem,FormSaveVO 返回 {objectId,workflowInstanceId,workItem:{id}};必须先 GET getReplayToken(isStartWf 分支 verifyReplay,否则 100005),且 data 显式 sequenceStatus:\"DRAFT\"(createAndGetBizObject 空缺省按 PROCESSING 建实例);提交该草稿 = form/submit formType=1 + workItemId + workflowInstanceId + bizObject.id(submitWorkflowByInstanceIdAndItemId),同 id 就地 PROCESSING,绝不走 workflowCode 分支",
|
|
25
|
+
workflowStartCreateOnly: "form/submit formType=1 + workflowCode(无 workItemId)→ submitWorkflowByWorkflowCode→startWorkflowInstance(create)→BizObjectFacadeImpl.createAndGetBizObject 纯 INSERT(BizObjectInsertModel 无存在性检查)→ 既有 id 必 40000 Duplicate entry(TID 771f64ee 全栈:ExecutorBase INSERT→BizObjectServiceImplCompanion:275→40000);8.6.26 唯一 load+update 型就地发起是 WorkflowInstanceFacade 5 参重载(908 行,facade 层供二开扩展消费),无 REST 面",
|
|
26
|
+
sheetResolution: "explicit caller arg > default-form sheetCode from bizsheet/get > schemaCode fallback",
|
|
27
|
+
updateBranch: "POST runtime/form/save 更新既有记录:目标 id 必须放进 bizObject.data[\"id\"](平台 BizObjectModel.getId() 读 data 的 id 键;顶层 bizObject.id 会被忽略并退化为新建);命中既有记录时 FormRuntimeController.saveBizSheet→BizObjectFacadeImpl.updateBizObject,未命中则 createBizObject 插入",
|
|
28
|
+
updateStatusRule: "既有记录的 DRAFT/COMPLETED 状态在未显式传 sequenceStatus 时原样保留(saveBizSheet 分支),COMPLETED 记录可直接 update,无需流程",
|
|
29
|
+
updateMergeRule: "updateBizObject.mergeObject 以库内旧值打底、以本次 data 覆盖 → 部分字段(patch)更新,未传字段保持不变",
|
|
30
|
+
childRowStatusNote: "子表行(CHILD_TABLE(8))在 form-save 路径按每行 `rowStatus` 分派(BizObjectUpdateModel.convertBizObjectOpItem):Added=插入 / Modified=更新 / Deleted=删除;**缺失或其它值=immutable(静默忽略)**。门户前端会为改动行标 `rowStatus:\"Modified\"`;手拼载荷漏了它,子表改动不落库、但写仍返回 errcode 0(真机 2026-10-08:data.update 改子表行无 rowStatus,process_name/计划时间读回全不变)。OMC 自动补:带自身 id 的行→\"Modified\",无 id 的行→\"Added\",显式 rowStatus 原样保留。",
|
|
31
|
+
updateAbsentGuard: "平台对\"带 id 但库内不存在\"不报错而是静默插入(createBizObject),故 data.update 先 form/load 预检,命中缺席则以 304001 拒绝,绝不静默转新增",
|
|
32
|
+
projectionHint: "所选列在默认视图未配置(值可能存在但列表投影为 null);先用 listview.configure 配置列,或用 data.load 读单条权威值",
|
|
33
|
+
formulaFieldNote: "计算字段(FORMULA / 表单「计算」控件、子表计算规则)**不在服务端 API 路径回填/重算**:门户是**前端**算好再提交,API save/update/submit 都不会替它算(真机 2026-09-25:子表计算规则字段落库仍 null;纯主表表达式会算)。OMC 的定位是「字段配好即默认它在前台会算」——**写测试数据时应在 formData 里显式带上预算好的计算值**(让数据与预期自洽),**不要指望平台在 API 保存时替它算,也不要用 API 回读值去断言公式/计算是否正确**。**这类纯前台行为最终由人工在前台(配表单→填表)确认。** 另:带**子表计算字段**的模型,API data.save/data.update 可能因服务端重算(CalculateHandler→FormulaProcessor)NPE 而 50000(真机 2026-10-07,前台路径正常)——此类模型请改用门户录入或避开该字段,别据此判定配置错误。",
|
|
34
|
+
});
|
|
35
|
+
function encodeScalar(value) {
|
|
36
|
+
if (value === null || value === undefined)
|
|
37
|
+
return "";
|
|
38
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean")
|
|
39
|
+
return String(value);
|
|
40
|
+
if (value instanceof Date)
|
|
41
|
+
return value.toISOString();
|
|
42
|
+
return JSON.stringify(value);
|
|
43
|
+
}
|
|
44
|
+
function encodeList(value) {
|
|
45
|
+
if (Array.isArray(value))
|
|
46
|
+
return value.map(encodeScalar).join(";");
|
|
47
|
+
if (typeof value === "string")
|
|
48
|
+
return value;
|
|
49
|
+
return encodeScalar(value);
|
|
50
|
+
}
|
|
51
|
+
const numeric = (value) => Number(value);
|
|
52
|
+
function betweenBounds(expected) {
|
|
53
|
+
const [low, high] = expected.split(";").map(Number);
|
|
54
|
+
return [low ?? 0, high ?? 0];
|
|
55
|
+
}
|
|
56
|
+
export const RUNTIME_QUERY_OPERATORS = Object.freeze({
|
|
57
|
+
Eq: { encode: encodeScalar, matches: (actual, expected) => String(actual ?? "") === expected },
|
|
58
|
+
NotEq: { encode: encodeScalar, matches: (actual, expected) => String(actual ?? "") !== expected },
|
|
59
|
+
Gt: { encode: encodeScalar, matches: (actual, expected) => numeric(actual) > numeric(expected) },
|
|
60
|
+
Lt: { encode: encodeScalar, matches: (actual, expected) => numeric(actual) < numeric(expected) },
|
|
61
|
+
Gte: { encode: encodeScalar, matches: (actual, expected) => numeric(actual) >= numeric(expected) },
|
|
62
|
+
Lte: { encode: encodeScalar, matches: (actual, expected) => numeric(actual) <= numeric(expected) },
|
|
63
|
+
Like: { encode: encodeScalar, matches: (actual, expected) => String(actual ?? "").includes(expected) },
|
|
64
|
+
NotLike: { encode: encodeScalar, matches: (actual, expected) => !String(actual ?? "").includes(expected) },
|
|
65
|
+
In: { encode: encodeList, matches: (actual, expected) => expected.split(";").includes(String(actual ?? "")) },
|
|
66
|
+
NotIn: { encode: encodeList, matches: (actual, expected) => !expected.split(";").includes(String(actual ?? "")) },
|
|
67
|
+
Between: { encode: encodeList, matches: (actual, expected) => { const [low, high] = betweenBounds(expected); return numeric(actual) >= low && numeric(actual) <= high; } },
|
|
68
|
+
NotBetween: { encode: encodeList, matches: (actual, expected) => { const [low, high] = betweenBounds(expected); return !(numeric(actual) >= low && numeric(actual) <= high); } },
|
|
69
|
+
IsNull: { encode: () => "", matches: (actual) => actual === null || actual === undefined },
|
|
70
|
+
IsNotNull: { encode: () => "", matches: (actual) => actual !== null && actual !== undefined },
|
|
71
|
+
});
|
|
72
|
+
export const ENGINE_OWNED_KEYS = Object.freeze(["sequenceStatus", "2147483647_sequenceNo"]);
|
|
73
|
+
export const SUBMIT_STATUS_TRANSITIONS = Object.freeze({
|
|
74
|
+
/** Start submit / start-item finish moves the row DRAFT -> PROCESSING. */
|
|
75
|
+
workflowStart: "PROCESSING",
|
|
76
|
+
/** Approval submit with agree!==false completes the row. */
|
|
77
|
+
approvalAgree: "COMPLETED",
|
|
78
|
+
/** Approval submit with agree===false cancels the row. */
|
|
79
|
+
approvalReject: "CANCELED",
|
|
80
|
+
/** formType=2 submitBizSheet -> saveBizObject completes in place. */
|
|
81
|
+
inPlaceSubmit: "COMPLETED",
|
|
82
|
+
});
|
|
83
|
+
export function dataListDescription() {
|
|
84
|
+
const wire = RUNTIME_DATA_WIRE;
|
|
85
|
+
return `按简化过滤 DSL 查询业务对象(runtime/query/list);DSL 在 CLI 内翻译为平台 queryCondition(${wire.dslShape}),并按发布字段元数据为每个条件注入 propertyType(未知过滤字段将被拒绝,可用 rawCondition 逃生);响应仅返回选定 fields 与分页元数据;空模型返回空列表;未配列的自定义字段投影为 null——这是显示面缺列配置、不是数据缺失(data.load 有值),显式选列在结果中全为 null 而发布字段元数据显示其存在时,readback 追加 projectionHint(先用 listview.configure 配置该模型的默认视图列,或用 data.load 读单条权威值)。`;
|
|
86
|
+
}
|
|
87
|
+
export function dataUpdateDescription() {
|
|
88
|
+
const wire = RUNTIME_DATA_WIRE;
|
|
89
|
+
return describeTool("M2 runtime data UPDATE (runtime/form/save with data.id, bizObjectId arg).", [
|
|
90
|
+
`编辑既有业务对象:${wire.updateBranch}。`,
|
|
91
|
+
`状态语义 ${wire.updateStatusRule}。`,
|
|
92
|
+
`${wire.updateMergeRule}。`,
|
|
93
|
+
`缺席保护:${wire.updateAbsentGuard}。`,
|
|
94
|
+
`子表行:${wire.childRowStatusNote}`,
|
|
95
|
+
`计算字段:${wire.formulaFieldNote}`,
|
|
96
|
+
"成功后以 form/load 权威读回变更字段;审批门/生产拒绝/租约与其他 M2 写一致(governedWrite)。",
|
|
97
|
+
]);
|
|
98
|
+
}
|
|
99
|
+
export function dataSaveDescription() {
|
|
100
|
+
const wire = RUNTIME_DATA_WIRE;
|
|
101
|
+
return describeTool("M2 runtime data write (runtime/form/save) — returns bizObjectId for authoritative readback via data.load.", [
|
|
102
|
+
`带可选 workflowCode 时按门户暂存形状创建流程草稿:${wire.workflowDraftSave}。`,
|
|
103
|
+
"formData 禁止携带 id(带 id 的就地发起在 8.6.26 无 REST 面,会被拒绝;既有无实例草稿请改用两段式或 data.submit 的 workItemId 审批链)。",
|
|
104
|
+
"关联值形状(live 2026-09-24):**单选关联(propertyType 9)传 id 字符串或 { id };传数组会 50000**;多选关联(11)才用数组。**人员/组织(50/51/60/61)传选择对象数组 `[{\"id\":\"<id>\",\"type\":3,\"name\":\"<名>\"}]`——`type` 必填**(BizField.parseFromClient 把 `type` 强转 Integer 并 intValue(),缺 `type` 稳定 50000);`type` 取 UnitType 索引:用户=3、部门=1。字段 `mappings` 由表单运行时在用户选择时套用,**服务端 data.save 不会自动回填映射目标字段**——API 侧调用需自行写入被映射字段,否则读回为空(不要误判为映射配置失败)。",
|
|
105
|
+
`不带 workflowCode 走纯表单保存(新行 DRAFT);formType 语义与 replayToken 门见 data.submit 契约(${wire.replayTokenErrcode})。`,
|
|
106
|
+
"根因提示(环境相关,ticket 78 观测):**某些环境**里模型所在应用未发布时运行时写稳定 50000(同调用在已发布应用成功),工具会反推并拒 app-not-published。注意该根因并非普遍规律——2026-09-19 实测另有一类环境未发布应用也能写运行时数据(详见 docs/issues/omc-cli-findings-2026-09-19.md B17)。遇 50000 时先用只读数据核对,不要仅凭“应用未发布”就放弃运行时验证。",
|
|
107
|
+
`子表行:${wire.childRowStatusNote}`,
|
|
108
|
+
`计算字段:${wire.formulaFieldNote}`,
|
|
109
|
+
"Pass `sheetCode` explicitly when the model has multiple sheets; when omitted, the model's default-form sheetCode is resolved from bizsheet/get.",
|
|
110
|
+
]);
|
|
111
|
+
}
|
|
112
|
+
export function dataSubmitDescription() {
|
|
113
|
+
const wire = RUNTIME_DATA_WIRE;
|
|
114
|
+
return describeTool(`M2 runtime data SUBMIT (runtime/form/submit, WorkItemSubmitVO).`, [
|
|
115
|
+
"Distinct from data.save: data.save persists as DRAFT; data.submit advances the record to COMPLETED (no workflow) or drives the workflow instance.",
|
|
116
|
+
"**审批提交(workItemId+agree)时 `formData` 也必填,且其值会写回业务对象——勿把期望结果(如审批级别)预写进 formData,否则规则回写的真伪无法区分(2026-09-22 实测返工案例)**;只传身份参数会被拒 formData: Required。",
|
|
117
|
+
`就地语义:${wire.submitInPlace};无流程 readback 必须确认 objectId 与传入 bizObjectId 相同;无 bizObjectId 的 workflowCode 新建单 → PROCESSING + workflowInstanceId,无流程 → 该行 COMPLETED。`,
|
|
118
|
+
`既有草稿发起流程的唯一支持形状是两段式:先 data.save 带 workflowCode 暂存(${wire.workflowDraftSave}),再以返回的 workItemId 提交。`,
|
|
119
|
+
`仅无 workflowCode/workItemId/workflowInstanceId 且无 bizObjectId 时保留 save→submit 便捷链(新建记录);只要存在 workItemId 或 workflowInstanceId 就 fail-closed 拒绝(blocker submit-biz-object-id-required),零 runtime/form/submit wire。`,
|
|
120
|
+
`formType 自动判定:无任何 workflowCode/workItemId/workflowInstanceId 时**先按模型自动判定**——GET workflow/isworkflow?schemaCode=true 则用 workflow/getWorkflow 解析已发布流程码并**发起流程**(formType="${wire.formTypeWorkflow}",自动先 GET getReplayToken 注入 replayToken,否则 ${wire.replayTokenErrcode} 频繁重复提交);模型无流程 → "${wire.formTypeNoWorkflow}"(无流程提交,走 live 已证链 ${wire.submitChain};单发新建对象会 ${wire.typeCastErrcode},bizObject 勿带字符串 status)或 workItemId+workflowInstanceId(审批待办,agree 通过/驳回,**必须带 bizObjectId 指向在途单据**,否则引擎 NPE ${wire.approvalNpeErrcode})→ "${wire.formTypeWorkflow}"。即「无流程直接提交、有流程发起流程」,不再把有流程模型的新建数据落成游离 sheet 行。`,
|
|
121
|
+
"根因提示(环境相关,ticket 78 观测):**某些环境**里模型所在应用未发布时运行时提交稳定 50000,工具会反推并拒 app-not-published。该根因非普遍规律(见 data.save 的同条提示与 docs/issues/omc-cli-findings-2026-09-19.md B17)——遇 50000 先只读核对再判断。",
|
|
122
|
+
"Pass `sheetCode` explicitly when the model has multiple sheets; otherwise the default-form sheetCode is resolved from bizsheet/get.",
|
|
123
|
+
`计算字段:${wire.formulaFieldNote}`,
|
|
124
|
+
]);
|
|
125
|
+
}
|
|
126
|
+
/** Runtime business-object data keyed by field code. */
|
|
127
|
+
export const runtimeFormDataArg = z
|
|
128
|
+
.record(z.string(), z.unknown())
|
|
129
|
+
.describe('business-object data keyed by field code, e.g. { name:"...", omc_x_amount:123, omc_x_rel:"<target objectId>" }. Relation value SHAPE depends on the field type (live 2026-09-24): a SINGLE relation (WORK_SHEET, propertyType 9) takes the target object id string or an object { id, name? } — an ARRAY here makes runtime/form/save answer 50000; a MULTI relation (propertyType 11) takes an array. STAFF/ORG fields (propertyType 50/51/60/61) take an array of selection objects `[{ id, type, name }]` where `type` is MANDATORY (BizField.parseFromClient casts `type` to Integer and calls intValue(); omitting it answers 50000) — `type` is the UnitType index: USER=3, DEPARTMENT=1. When unsure, prefer "id" or { id }. Field `mappings` are applied by the form RUNTIME when a user picks a value — a server-side data.save does NOT copy source→target, so API callers must set the mapped fields themselves.');
|
|
130
|
+
export const RUNTIME_DATA_LANE_CONTRACT = Object.freeze({
|
|
131
|
+
lane: LANES.runtimeData,
|
|
132
|
+
family: CONTRACT_FAMILY,
|
|
133
|
+
referenceVersion: CONTRACT_REFERENCE_VERSION,
|
|
134
|
+
endpoints: Object.freeze({
|
|
135
|
+
queryList: "runtimeQueryList",
|
|
136
|
+
formSave: "runtimeFormSave",
|
|
137
|
+
formLoad: "runtimeFormLoad",
|
|
138
|
+
formDelete: "runtimeFormDelete",
|
|
139
|
+
formSubmit: "runtimeFormSubmit",
|
|
140
|
+
replayToken: "runtimeFormReplayToken",
|
|
141
|
+
sheetGet: "sheetGet",
|
|
142
|
+
fieldList: "fieldList",
|
|
143
|
+
}),
|
|
144
|
+
wire: Object.freeze({
|
|
145
|
+
queryConditionShape: RUNTIME_DATA_WIRE.queryConditionShape,
|
|
146
|
+
dslShape: RUNTIME_DATA_WIRE.dslShape,
|
|
147
|
+
queryBodyCanon: RUNTIME_DATA_WIRE.queryBodyCanon,
|
|
148
|
+
typeCastErrcode: RUNTIME_DATA_WIRE.typeCastErrcode,
|
|
149
|
+
emptyModelErrcode: RUNTIME_DATA_WIRE.emptyModelErrcode,
|
|
150
|
+
absenceErrcode: RUNTIME_DATA_WIRE.absenceErrcode,
|
|
151
|
+
replayTokenErrcode: RUNTIME_DATA_WIRE.replayTokenErrcode,
|
|
152
|
+
approvalNpeErrcode: RUNTIME_DATA_WIRE.approvalNpeErrcode,
|
|
153
|
+
submitChain: RUNTIME_DATA_WIRE.submitChain,
|
|
154
|
+
submitInPlace: RUNTIME_DATA_WIRE.submitInPlace,
|
|
155
|
+
workflowDraftSave: RUNTIME_DATA_WIRE.workflowDraftSave,
|
|
156
|
+
workflowStartCreateOnly: RUNTIME_DATA_WIRE.workflowStartCreateOnly,
|
|
157
|
+
sheetResolution: RUNTIME_DATA_WIRE.sheetResolution,
|
|
158
|
+
updateBranch: RUNTIME_DATA_WIRE.updateBranch,
|
|
159
|
+
updateStatusRule: RUNTIME_DATA_WIRE.updateStatusRule,
|
|
160
|
+
updateMergeRule: RUNTIME_DATA_WIRE.updateMergeRule,
|
|
161
|
+
updateAbsentGuard: RUNTIME_DATA_WIRE.updateAbsentGuard,
|
|
162
|
+
childRowStatusNote: RUNTIME_DATA_WIRE.childRowStatusNote,
|
|
163
|
+
projectionHint: RUNTIME_DATA_WIRE.projectionHint,
|
|
164
|
+
}),
|
|
165
|
+
wireBindings: Object.freeze({
|
|
166
|
+
queryConditionShape: Object.freeze([emitterBinding("data-list-filter.toQueryCondition", assertRuntimeDataContract)]),
|
|
167
|
+
dslShape: Object.freeze([emitterBinding("data-list-filter.SIMPLE_OP_MAP", assertRuntimeDataContract)]),
|
|
168
|
+
queryBodyCanon: Object.freeze([behaviorBinding("test/data-list.test.ts#data.list translates simplified filters", assertRuntimeDataContract)]),
|
|
169
|
+
typeCastErrcode: Object.freeze([emitterBinding("data-list-filter.injectPropertyTypes", assertRuntimeDataContract)]),
|
|
170
|
+
emptyModelErrcode: Object.freeze([emitterBinding("lanes/runtime-data-contract.RUNTIME_DATA_WIRE", assertRuntimeDataContract)]),
|
|
171
|
+
absenceErrcode: Object.freeze([emitterBinding("lanes/runtime-data-contract.RUNTIME_DATA_WIRE", assertRuntimeDataContract)]),
|
|
172
|
+
replayTokenErrcode: Object.freeze([emitterBinding("lanes/runtime-data-contract.RUNTIME_DATA_WIRE", assertRuntimeDataContract)]),
|
|
173
|
+
approvalNpeErrcode: Object.freeze([emitterBinding("lanes/runtime-data-contract.RUNTIME_DATA_WIRE", assertRuntimeDataContract)]),
|
|
174
|
+
submitChain: Object.freeze([behaviorBinding("test/issue-13-data-submit.test.ts#data.submit with formType=2 issues a WorkItemSubmitVO", assertRuntimeDataContract)]),
|
|
175
|
+
submitInPlace: Object.freeze([behaviorBinding("test/issue-13-data-submit.test.ts#data.submit with bizObjectId submits an existing no-workflow record in place", assertRuntimeDataContract)]),
|
|
176
|
+
workflowDraftSave: Object.freeze([behaviorBinding("test/issue-13-data-submit.test.ts#data.save with workflowCode creates a DRAFT row", assertRuntimeDataContract)]),
|
|
177
|
+
workflowStartCreateOnly: Object.freeze([behaviorBinding("test/issue-13-data-submit.test.ts#data.submit refuses existing-id workflow starts", assertRuntimeDataContract)]),
|
|
178
|
+
sheetResolution: Object.freeze([behaviorBinding("test/issue-13-data-submit.test.ts#data.submit forwards an explicit sheetCode", assertRuntimeDataContract)]),
|
|
179
|
+
updateBranch: Object.freeze([behaviorBinding("test/data-update.test.ts#data.update edits an existing record in place", assertRuntimeDataContract)]),
|
|
180
|
+
updateStatusRule: Object.freeze([behaviorBinding("test/data-update.test.ts#data.update updates a COMPLETED record directly", assertRuntimeDataContract)]),
|
|
181
|
+
updateMergeRule: Object.freeze([behaviorBinding("test/data-update.test.ts#data.update edits an existing record in place", assertRuntimeDataContract)]),
|
|
182
|
+
updateAbsentGuard: Object.freeze([behaviorBinding("test/data-update.test.ts#data.update does NOT silently insert", assertRuntimeDataContract)]),
|
|
183
|
+
childRowStatusNote: Object.freeze([behaviorBinding("test/data-update.test.ts#data.update stamps rowStatus on child-table rows", assertRuntimeDataContract)]),
|
|
184
|
+
projectionHint: Object.freeze([behaviorBinding("test/data-list.test.ts#data.list appends projectionHint", assertRuntimeDataContract)]),
|
|
185
|
+
}),
|
|
186
|
+
evidence: Object.freeze([
|
|
187
|
+
"ticket 01 B (2026-09-11): queryCondition must be List<List<List<FilterModel>>>; a 2-level array answers 10009 类型转换异常",
|
|
188
|
+
"BizObjectFacadeImpl.getFiltersByQueryParam / buildFilter: level1 AND groups, level2 OR and-groups, level3 AND leaves",
|
|
189
|
+
"each leaf requires propertyType injected from bizproperty/list isPublish=true field metadata (cpdocs filter-condition contract)",
|
|
190
|
+
"ticket 02/03 (2026-09-11): submit chain save(DRAFT)→load→submit; workflow start requires getReplayToken or 100005; workItem approval requires bizObjectId or the engine NPEs 50000",
|
|
191
|
+
"ticket 07 (2026-09-11): a model's unconfigured default list view projects business columns as null though values exist; data.list appends projectionHint when explicit columns are all-null and published metadata lists them",
|
|
192
|
+
"ticket 13 (2026-09-12): FormRuntimeController.saveWorkflow finishStart=FALSE creates DRAFT instance + start workItem (portal 暂存 shape); WorkflowInstanceFacadeImpl:908 5-arg overload is load+update in-place start but has no REST surface; TID 771f64ee pins create-path INSERT duplicate (40000)",
|
|
193
|
+
"ticket 18 (2026-09-12): AbstractFormRuntimeController.submitBizSheet sets COMPLETED then saveBizObject (submit persists data itself); SubmitDataChangeHandlerImpl.updateBizObject persists the workItem submit's bizObject; BizObjectHelper.initSubmitBizObject.setOwner re-reads the STORED row's owner — the pre-submit form/save + form/load round-trip is redundant wire",
|
|
194
|
+
]),
|
|
195
|
+
tools: Object.freeze({
|
|
196
|
+
"data.list": Object.freeze({ scope: "filtered list field projection", description: dataListDescription() }),
|
|
197
|
+
"data.load": Object.freeze({ scope: "authoritative readback form load", description: "M2 authoritative readback: form/load one business object by id. **返回形状:业务字段值在 `result.data`(非 readback.authoritative);大结果 stdout 只回 detailArtifactId + fetch 提示,用 `omc artifact get <id>` 取回;`fields:[...]` 可投影只取所需键**。" }),
|
|
198
|
+
"data.save": Object.freeze({
|
|
199
|
+
scope: "write authoritative readback form save",
|
|
200
|
+
description: dataSaveDescription(),
|
|
201
|
+
}),
|
|
202
|
+
"data.update": Object.freeze({
|
|
203
|
+
scope: "write authoritative readback form save",
|
|
204
|
+
description: dataUpdateDescription(),
|
|
205
|
+
}),
|
|
206
|
+
"data.delete": Object.freeze({
|
|
207
|
+
scope: "write authoritative readback form save",
|
|
208
|
+
description: desc("M2 runtime data delete (runtime/form/delete) with absence readback."),
|
|
209
|
+
}),
|
|
210
|
+
"data.submit": Object.freeze({ scope: "write authoritative readback form save", description: dataSubmitDescription() }),
|
|
211
|
+
}),
|
|
212
|
+
});
|
|
213
|
+
function desc(lead) {
|
|
214
|
+
return `${lead} Pass \`sheetCode\` explicitly when the model has multiple sheets; when omitted, the model's default-form sheetCode is resolved from bizsheet/get.`;
|
|
215
|
+
}
|
|
216
|
+
export function assertRuntimeDataContract() {
|
|
217
|
+
assertLaneContract(RUNTIME_DATA_LANE_CONTRACT);
|
|
218
|
+
assertDescriptionCarries(RUNTIME_DATA_LANE_CONTRACT, "data.list", ["queryCondition", "propertyType", "rawCondition", "projectionHint", "listview.configure", "data.load"]);
|
|
219
|
+
assertDescriptionCarries(RUNTIME_DATA_LANE_CONTRACT, "data.save", ["bizObjectId", "sheetCode", "bizsheet/get", "workflowCode", "sequenceStatus", "workItem", "propertyType 9", "50000", "mappings", "rowStatus"]);
|
|
220
|
+
assertDescriptionCarries(RUNTIME_DATA_LANE_CONTRACT, "data.update", ["runtime/form/save", "bizObjectId", "COMPLETED", "304001", "form/load", "rowStatus"]);
|
|
221
|
+
assertDescriptionCarries(RUNTIME_DATA_LANE_CONTRACT, "data.delete", ["absence", "sheetCode"]);
|
|
222
|
+
assertDescriptionCarries(RUNTIME_DATA_LANE_CONTRACT, "data.submit", ["getReplayToken", "replayToken", "bizObjectId", "objectId", "DRAFT", "COMPLETED", "10009", "100005", "NPE", "两段式", "finishStart=FALSE"]);
|
|
223
|
+
if (!RUNTIME_DATA_WIRE.workflowDraftSave.includes("finishStart=FALSE"))
|
|
224
|
+
throw new Error("runtime-data contract must pin the portal draft-save chain (finishStart=FALSE)");
|
|
225
|
+
if (!RUNTIME_DATA_WIRE.workflowStartCreateOnly.includes("40000"))
|
|
226
|
+
throw new Error("runtime-data contract must pin the create-only workflow start duplicate failure");
|
|
227
|
+
if (RUNTIME_DATA_WIRE.typeCastErrcode !== 10009)
|
|
228
|
+
throw new Error("runtime-data contract pins the type-cast errcode to 10009");
|
|
229
|
+
if (RUNTIME_DATA_WIRE.approvalNpeErrcode !== 50000)
|
|
230
|
+
throw new Error("runtime-data contract pins the approval NPE errcode to 50000");
|
|
231
|
+
if (RUNTIME_DATA_WIRE.workflowStartDuplicateErrcode !== 40000)
|
|
232
|
+
throw new Error("runtime-data contract pins the create-only workflow start duplicate errcode to 40000");
|
|
233
|
+
if (!RUNTIME_DATA_WIRE.queryConditionShape.includes("level 3 AND across leaves"))
|
|
234
|
+
throw new Error("runtime-data contract must pin the three-layer queryCondition shape");
|
|
235
|
+
if (!RUNTIME_DATA_WIRE.dslShape.includes("or>and"))
|
|
236
|
+
throw new Error("runtime-data contract must pin the or>and>leaf DSL shape");
|
|
237
|
+
if (!RUNTIME_DATA_WIRE.queryBodyCanon.includes("queryVersion:1") || !RUNTIME_DATA_WIRE.queryBodyCanon.includes("queryCode=schemaCode"))
|
|
238
|
+
throw new Error("runtime-data contract must pin the query body canon (queryVersion:1, queryCode=schemaCode)");
|
|
239
|
+
if (RUNTIME_DATA_WIRE.emptyModelErrcode !== 301005)
|
|
240
|
+
throw new Error("runtime-data contract pins the empty-model errcode to 301005");
|
|
241
|
+
if (RUNTIME_DATA_WIRE.absenceErrcode !== 304001)
|
|
242
|
+
throw new Error("runtime-data contract pins the absent-object errcode to 304001");
|
|
243
|
+
if (RUNTIME_DATA_WIRE.replayTokenErrcode !== 100005)
|
|
244
|
+
throw new Error("runtime-data contract pins the replay-token errcode to 100005");
|
|
245
|
+
if (!RUNTIME_DATA_WIRE.submitChain.includes("formType=2") || !RUNTIME_DATA_WIRE.submitChain.includes("formType=1"))
|
|
246
|
+
throw new Error("runtime-data contract must pin the submit chain formType branches");
|
|
247
|
+
if (!RUNTIME_DATA_WIRE.submitInPlace.includes("submit-biz-object-id-required"))
|
|
248
|
+
throw new Error("runtime-data contract must pin the workItem in-place fail-closed blocker");
|
|
249
|
+
if (!RUNTIME_DATA_WIRE.sheetResolution.includes("bizsheet/get"))
|
|
250
|
+
throw new Error("runtime-data contract must pin the default-form sheet resolution");
|
|
251
|
+
if (!RUNTIME_DATA_WIRE.updateBranch.includes('bizObject.data["id"]'))
|
|
252
|
+
throw new Error("runtime-data contract must pin the update-branch id placement");
|
|
253
|
+
if (!RUNTIME_DATA_WIRE.updateStatusRule.includes("sequenceStatus"))
|
|
254
|
+
throw new Error("runtime-data contract must pin the update sequenceStatus preservation");
|
|
255
|
+
if (!RUNTIME_DATA_WIRE.updateMergeRule.includes("mergeObject"))
|
|
256
|
+
throw new Error("runtime-data contract must pin the update merge semantics");
|
|
257
|
+
if (!RUNTIME_DATA_WIRE.childRowStatusNote.includes("rowStatus") || !RUNTIME_DATA_WIRE.childRowStatusNote.includes("immutable"))
|
|
258
|
+
throw new Error("runtime-data contract must pin the child-row rowStatus dispatch semantics");
|
|
259
|
+
if (!RUNTIME_DATA_WIRE.projectionHint.includes("listview.configure"))
|
|
260
|
+
throw new Error("runtime-data contract must pin the projectionHint remediation");
|
|
261
|
+
if (!RUNTIME_DATA_WIRE.updateAbsentGuard.includes(String(RUNTIME_DATA_WIRE.absenceErrcode)))
|
|
262
|
+
throw new Error("runtime-data contract must pin the update absent-target guard to the absence errcode");
|
|
263
|
+
}
|
|
264
|
+
//# sourceMappingURL=runtime-data-contract.js.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { claim, declareLaneCoverage } from "./coverage-declaration.js";
|
|
2
|
+
import { RUNTIME_DATA_LANE_CONTRACT } from "./runtime-data-contract.js";
|
|
3
|
+
const T = RUNTIME_DATA_LANE_CONTRACT.tools;
|
|
4
|
+
export const RUNTIME_DATA_COVERAGE = declareLaneCoverage(RUNTIME_DATA_LANE_CONTRACT.lane, [
|
|
5
|
+
{
|
|
6
|
+
capability: "M2 runtime data write and authoritative readback (form/save + form/load)",
|
|
7
|
+
evidence: [
|
|
8
|
+
".scratch/omc-route-wayfinder/leg2-closeout.md",
|
|
9
|
+
".scratch/omc-route-wayfinder/research/m2-null-rootcause.md",
|
|
10
|
+
],
|
|
11
|
+
tools: [
|
|
12
|
+
claim("data.load", T["data.load"].scope),
|
|
13
|
+
claim("data.save", T["data.save"].scope),
|
|
14
|
+
claim("data.update", T["data.update"].scope),
|
|
15
|
+
claim("data.delete", T["data.delete"].scope),
|
|
16
|
+
claim("data.submit", T["data.submit"].scope),
|
|
17
|
+
],
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
capability: "data.list filtered business-object listing",
|
|
21
|
+
evidence: ["packages/omc-cli/src/tools.ts", "packages/omc-cli/test/data-list.test.ts"],
|
|
22
|
+
tools: [claim("data.list", T["data.list"].scope)],
|
|
23
|
+
},
|
|
24
|
+
]);
|
|
25
|
+
//# sourceMappingURL=runtime-data-coverage.js.map
|