@stalfh233/omc-cli 0.4.55 → 0.4.56
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/dist/bizservice-config.js +69 -19
- package/dist/cli.js +37 -1
- package/dist/lanes/b2-registration-contract.js +2 -2
- package/dist/lanes/b2-registration.js +20 -2
- package/dist/lanes/businessrule.js +5 -2
- package/dist/recipes/b2-registration.js +69 -0
- package/package.json +3 -3
- package/skills/omc-business-configuration.md +2 -1
- package/skills/omc-refusals.md +6 -5
|
@@ -319,6 +319,14 @@ export function compileMethodConfigPlain(adapterCode, methodConfig) {
|
|
|
319
319
|
config.requestBodyType = encodeEnumIndex(config.requestBodyType, RESTFUL_REQUEST_BODY_TYPES, "requestBodyType");
|
|
320
320
|
if (typeof config.url !== "string" || !config.url)
|
|
321
321
|
refuse(BIZSERVICE_CONFIG_BLOCKERS.methodConfigInvalid, "RESTFUL 方法 url 必须是非空字符串(相对路径与服务 commonlist.url 拼接)", { key: "url" });
|
|
322
|
+
// Prefix × auth matrix (live 2026-10-18, SubCon): `/openapi/**` is spliced
|
|
323
|
+
// to `/api/openapi/**` (single layer) and authenticates with the OAuth2
|
|
324
|
+
// client (`xclient`); `/api/**` becomes `/api/api/**` (double layer) and
|
|
325
|
+
// authenticates as the USER — mixing them answers 401 权限不够. Two hard
|
|
326
|
+
// rules are unambiguous on their own: an unauthenticated prefix must never
|
|
327
|
+
// carry a 二开 interface, and externalLink is never a business endpoint.
|
|
328
|
+
if (/^\/?externalLink/i.test(config.url))
|
|
329
|
+
refuse(BIZSERVICE_CONFIG_BLOCKERS.methodConfigInvalid, "禁止用 /externalLink 等免鉴权前缀承载二开接口(能通但方向错误);二开接口走 /openapi/** + xclient", { key: "url", url: config.url });
|
|
322
330
|
// Designer parity: the rule designer's RESTFUL method config also carries
|
|
323
331
|
// `connectTime` / `retryTimes` (string seconds/count, designer defaults
|
|
324
332
|
// "10"/"3"); the 绑定业务方法 dialog and the runtime read them, so a
|
|
@@ -401,7 +409,19 @@ export function compileParameterFace(adapterCode, parameters, label) {
|
|
|
401
409
|
if (!Array.isArray(parameters))
|
|
402
410
|
refuse(BIZSERVICE_CONFIG_BLOCKERS.parameterFaceInvalid, `${label} 必须是结构化参数数组(ParameterModel[])`, { label });
|
|
403
411
|
const defaultRequired = isOutputFace(label) ? false : undefined;
|
|
404
|
-
|
|
412
|
+
// Live 2026-10-18 (SubCon): a 7-method registration failed one parameter at a
|
|
413
|
+
// time across two rounds — the whole batch was rewritten for a single enum
|
|
414
|
+
// typo, then again for a missing output position. Collect EVERY violation and
|
|
415
|
+
// refuse once with the full list, so one pass fixes the whole face.
|
|
416
|
+
const violations = [];
|
|
417
|
+
const normalized = parameters.map((parameter, index) => compileParameter(adapterCode, parameter, label, `${label}[${index}]`, index, defaultRequired, violations));
|
|
418
|
+
if (violations.length > 0)
|
|
419
|
+
throw new ToolRefusalError({
|
|
420
|
+
status: "refused",
|
|
421
|
+
blocker: BIZSERVICE_CONFIG_BLOCKERS.parameterFaceInvalid,
|
|
422
|
+
message: `${label} 参数面有 ${violations.length} 处非法项(一次性列出,逐项修正后一次重试):` + violations.map((v) => `${v.path}${v.code ? ` (${v.code})` : ""}: ${v.message}`).join(";"),
|
|
423
|
+
issues: violations,
|
|
424
|
+
});
|
|
405
425
|
return JSON.stringify(normalized);
|
|
406
426
|
}
|
|
407
427
|
/** True when `label` names the RETURN parameter face (outputParametersJson). */
|
|
@@ -417,32 +437,51 @@ function isOutputFace(label) {
|
|
|
417
437
|
* leaf → 1000 PROPERTY, a node with subParameters → 1002 OBJECT) and
|
|
418
438
|
* `description`. RESTFUL additionally requires configJson.position.
|
|
419
439
|
*/
|
|
420
|
-
function compileParameter(adapterCode, parameter, label, path, index, defaultRequired) {
|
|
421
|
-
|
|
422
|
-
|
|
440
|
+
function compileParameter(adapterCode, parameter, label, path, index, defaultRequired, violations) {
|
|
441
|
+
const collect = (message, code) => {
|
|
442
|
+
if (violations)
|
|
443
|
+
violations.push({ path, ...(code ? { code } : {}), message });
|
|
444
|
+
else
|
|
445
|
+
refuse(BIZSERVICE_CONFIG_BLOCKERS.parameterFaceInvalid, `${path}${code ? ` (${code})` : ""}: ${message}`, { label, index, ...(code ? { code } : {}) });
|
|
446
|
+
};
|
|
447
|
+
if (!isRecord(parameter)) {
|
|
448
|
+
collect("必须是对象");
|
|
449
|
+
return {};
|
|
450
|
+
}
|
|
423
451
|
if (typeof parameter.code !== "string" || !parameter.code)
|
|
424
|
-
|
|
452
|
+
collect("缺少 code(绑定校验按 code 对账)");
|
|
425
453
|
if (parameter.required !== undefined && typeof parameter.required !== "boolean")
|
|
426
|
-
|
|
454
|
+
collect("required 必须是布尔(缺席=未声明必填,不会被补成 false)");
|
|
427
455
|
const position = parameterPosition(parameter);
|
|
456
|
+
const validPositions = Object.values(RESTFUL_PARAMETER_POSITIONS);
|
|
457
|
+
const positionIsValid = (typeof position === "number" && Number.isInteger(position) && position in RESTFUL_PARAMETER_POSITIONS) ||
|
|
458
|
+
(typeof position === "string" && validPositions.includes(position));
|
|
428
459
|
if (adapterCode === "RESTFUL") {
|
|
429
460
|
if (position === undefined)
|
|
430
|
-
|
|
431
|
-
if (
|
|
432
|
-
|
|
461
|
+
collect("缺 configJson.position —— RESTFUL 参数无 position 在 invoke 时 NPE(RestProtocolAdapterRuntimeServiceImpl.java:197)", typeof parameter.code === "string" ? parameter.code : undefined);
|
|
462
|
+
else if (!positionIsValid)
|
|
463
|
+
collect(`position 枚举名 ${String(position)} 不在已取证集合 ${validPositions.join("|")} 内(REQUEST_PARAM 不存在)`, typeof parameter.code === "string" ? parameter.code : undefined);
|
|
464
|
+
else if (position === 1 || position === "URL_VARIABLE")
|
|
465
|
+
collect("position=URL_VARIABLE 被运行时拒绝(501007,无对应分支);用 QUERY_PARAMS/REQUEST_HEADER/REQUEST_BODY/PATH_PARAMS", typeof parameter.code === "string" ? parameter.code : undefined);
|
|
433
466
|
}
|
|
434
467
|
// Platform canonical: `ParameterModel.configJson` is a JSON STRING column
|
|
435
468
|
// whose content is the nested config OBJECT with the WIRE INTEGER position
|
|
436
469
|
// (platform built-ins: "configJson":{"position":3}). Encode the enum and
|
|
437
470
|
// emit the object form; a string input is parsed so we never double-encode.
|
|
438
471
|
const configInput = isRecord(parameter.configJson) ? parameter.configJson : typeof parameter.configJson === "string" ? safeParse(parameter.configJson) : undefined;
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
472
|
+
let configJson = parameter.configJson;
|
|
473
|
+
if (adapterCode === "RESTFUL") {
|
|
474
|
+
const usable = positionIsValid && position !== 1 && position !== "URL_VARIABLE";
|
|
475
|
+
configJson = usable
|
|
476
|
+
? { ...(isRecord(configInput) ? configInput : {}), position: encodeEnumIndex(position, RESTFUL_PARAMETER_POSITIONS, "position") }
|
|
477
|
+
: isRecord(configInput)
|
|
478
|
+
? configInput
|
|
479
|
+
: parameter.configJson;
|
|
480
|
+
}
|
|
481
|
+
const bizPropertyType = resolveBizPropertyType(parameter, label, index, path, collect);
|
|
482
|
+
const externParameterType = resolveExternParameterType(parameter, label, index, collect);
|
|
444
483
|
const subParameters = Array.isArray(parameter.subParameters)
|
|
445
|
-
? parameter.subParameters.map((sub, subIndex) => compileParameter(adapterCode, sub, label, `${path}.subParameters[${subIndex}]`, subIndex, defaultRequired))
|
|
484
|
+
? parameter.subParameters.map((sub, subIndex) => compileParameter(adapterCode, sub, label, `${path}.subParameters[${subIndex}]`, subIndex, defaultRequired, violations))
|
|
446
485
|
: parameter.subParameters;
|
|
447
486
|
return {
|
|
448
487
|
...parameter,
|
|
@@ -457,24 +496,35 @@ function compileParameter(adapterCode, parameter, label, path, index, defaultReq
|
|
|
457
496
|
};
|
|
458
497
|
}
|
|
459
498
|
/** Resolve/validate a parameter's `bizPropertyType` (int index or enum name). Absent → refuse (fail-closed). */
|
|
460
|
-
function resolveBizPropertyType(parameter, label, index, path) {
|
|
499
|
+
function resolveBizPropertyType(parameter, label, index, path, collect) {
|
|
461
500
|
const raw = parameter.bizPropertyType;
|
|
501
|
+
const fail = (message) => {
|
|
502
|
+
if (collect) {
|
|
503
|
+
collect(message);
|
|
504
|
+
return BIZ_PROPERTY_TYPE_NAMES.SHORT_TEXT;
|
|
505
|
+
}
|
|
506
|
+
refuse(BIZSERVICE_CONFIG_BLOCKERS.parameterFaceInvalid, `${path} (${parameter.code}) ${message}`, { label, index, code: parameter.code });
|
|
507
|
+
};
|
|
462
508
|
if (raw === undefined || raw === null)
|
|
463
|
-
|
|
509
|
+
return fail("缺 bizPropertyType(参数类型)——业务集成参数必须有类型,否则业务规则按类型绑定失败、参数无法映射;用 BIZ_PROPERTY_TYPES 索引或枚举名(SHORT_TEXT/NUMERICAL/RADIO/DROPDOWN_BOX…)");
|
|
464
510
|
if (typeof raw === "number" && Number.isInteger(raw) && raw in BIZ_PROPERTY_TYPES)
|
|
465
511
|
return raw;
|
|
466
512
|
if (typeof raw === "string" && raw in BIZ_PROPERTY_TYPE_NAMES)
|
|
467
513
|
return BIZ_PROPERTY_TYPE_NAMES[raw];
|
|
468
|
-
|
|
514
|
+
return fail("bizPropertyType 必须是合法字段类型(整数索引或枚举名,如 SHORT_TEXT/NUMERICAL/RADIO/DROPDOWN_BOX)——缺失或有误会破坏业务规则按类型绑定");
|
|
469
515
|
}
|
|
470
516
|
/** Resolve/validate a parameter's `externParameterType` (int index or name); default leaf=PROPERTY(1000)/parent=OBJECT(1002). */
|
|
471
|
-
function resolveExternParameterType(parameter, label, index) {
|
|
517
|
+
function resolveExternParameterType(parameter, label, index, collect) {
|
|
472
518
|
const raw = parameter.externParameterType;
|
|
473
519
|
if (raw !== undefined) {
|
|
474
520
|
if (typeof raw === "number" && Number.isInteger(raw) && raw in EXTERN_PARAMETER_TYPES)
|
|
475
521
|
return raw;
|
|
476
522
|
if (typeof raw === "string" && raw in EXTERN_PARAMETER_NAMES)
|
|
477
523
|
return EXTERN_PARAMETER_NAMES[raw];
|
|
524
|
+
if (collect) {
|
|
525
|
+
collect("externParameterType 必须是 PROPERTY(1000)/LIST(1001)/OBJECT(1002) 的整数索引或枚举名");
|
|
526
|
+
return EXTERN_PARAMETER_NAMES.PROPERTY;
|
|
527
|
+
}
|
|
478
528
|
refuse(BIZSERVICE_CONFIG_BLOCKERS.parameterFaceInvalid, `${label}[${index}] (${parameter.code}) externParameterType 必须是 PROPERTY(1000)/LIST(1001)/OBJECT(1002) 的整数索引或枚举名`, { label, index, code: parameter.code });
|
|
479
529
|
}
|
|
480
530
|
const hasChildren = Array.isArray(parameter.subParameters) && parameter.subParameters.length > 0;
|
package/dist/cli.js
CHANGED
|
@@ -209,7 +209,24 @@ async function cmdDoctor(store, environment, serviceCode) {
|
|
|
209
209
|
}
|
|
210
210
|
catch (error) {
|
|
211
211
|
ok = false;
|
|
212
|
-
|
|
212
|
+
// Distinguish "the project has not pinned an environment" (usable once you
|
|
213
|
+
// name one) from "the platform is unreachable" — conflating them sent a
|
|
214
|
+
// sandbox probe guessing environment names five times (r18).
|
|
215
|
+
const message = redactText(error.message, []);
|
|
216
|
+
let configured = "";
|
|
217
|
+
try {
|
|
218
|
+
configured = store.listEnvironments().map((item) => item.name).join("、");
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
configured = "";
|
|
222
|
+
}
|
|
223
|
+
const kind = /no default environment|not configured|unknown environment|not configured in omc\.config\.json/i.test(message)
|
|
224
|
+
? "env-not-pinned"
|
|
225
|
+
: "env-unreachable";
|
|
226
|
+
steps.push(`自检 FAIL ${message}`);
|
|
227
|
+
steps.push(kind === "env-not-pinned"
|
|
228
|
+
? `环境 ${kind} 已配置环境:${configured || "(none)"};用 omc env list 查看,再以 --env <name> 指定或 omc setup 绑定;对象不存在(modelPresent:false)不是环境错误`
|
|
229
|
+
: `环境 ${kind} 环境已配置但平台不可达;先查网络/服务,不要改环境名试错(已配置:${configured || "(none)"})`);
|
|
213
230
|
}
|
|
214
231
|
for (const line of steps)
|
|
215
232
|
console.error(line);
|
|
@@ -935,6 +952,25 @@ async function main() {
|
|
|
935
952
|
command.command("setup").option("--list").option("--edit <environment>").option("--remove <environment>").action(async (options) => {
|
|
936
953
|
commandExitCode = await cmdSetup(new ConfigStore(defaultPaths()), [...(options.list ? ["--list"] : []), ...(options.edit ? ["--edit", options.edit] : []), ...(options.remove ? ["--remove", options.remove] : [])]);
|
|
937
954
|
});
|
|
955
|
+
command.command("env [action]").option("--json").action(async (action, options) => {
|
|
956
|
+
// Read-only inventory of configured environment names. r18: the sandbox
|
|
957
|
+
// guessed names instead of listing them; doctor's wording now points here.
|
|
958
|
+
const store = new ConfigStore(defaultPaths());
|
|
959
|
+
if (action !== undefined && action !== "list") {
|
|
960
|
+
console.error("用法:omc env list [--json]");
|
|
961
|
+
commandExitCode = CLI_EXIT_CODES.invalidInput;
|
|
962
|
+
return;
|
|
963
|
+
}
|
|
964
|
+
const rows = store.listEnvironments().map((item) => ({ name: item.name, baseUrl: item.profile.baseUrl, applicationCode: item.profile.applicationCode, isDefault: item.profile.isDefault === true, writable: item.profile.writable !== false }));
|
|
965
|
+
if (options.json)
|
|
966
|
+
process.stdout.write(JSON.stringify({ ok: true, environments: rows }, null, 2) + "\n");
|
|
967
|
+
else if (rows.length === 0)
|
|
968
|
+
console.error("(none configured; run omc setup)");
|
|
969
|
+
else
|
|
970
|
+
for (const row of rows)
|
|
971
|
+
console.error(`${row.name}${row.isDefault ? " (default)" : ""} ${row.baseUrl} app=${row.applicationCode}${row.writable ? "" : " [read-only]"}`);
|
|
972
|
+
commandExitCode = CLI_EXIT_CODES.ok;
|
|
973
|
+
});
|
|
938
974
|
command.command("doctor").option("--environment <environment>").option("--env <environment>").option("--service <serviceCode>", "可选:体检单个业务集成服务的鉴权参数形状(掩码/占位/全 0 检出,WARN 非致命)").action(async (options) => { commandExitCode = await cmdDoctor(new ConfigStore(defaultPaths()), options.environment ?? options.env, options.service); });
|
|
939
975
|
command.command("call <tool>").option("--env <environment>").option("--environment <environment>").option("--args <json>").option("--args-file <path>").option("--max-output-bytes <bytes>").option("--request-id <requestId>").option("--timeout-ms <milliseconds>").option("--wait <milliseconds>").option("--dry-run").addHelpText("after", `\n${onboardingHelp()}\n`).action(async (tool, options) => {
|
|
940
976
|
const rest = [tool, ...(options.env ? ["--env", options.env] : []), ...(options.environment ? ["--environment", options.environment] : []), ...(options.args !== undefined ? ["--args", options.args] : []), ...(options.argsFile !== undefined ? ["--args-file", options.argsFile] : []), ...(options.maxOutputBytes !== undefined ? ["--max-output-bytes", options.maxOutputBytes] : []), ...(options.requestId ? ["--request-id", options.requestId] : []), ...(options.timeoutMs !== undefined ? ["--timeout-ms", options.timeoutMs] : []), ...(options.wait !== undefined ? ["--wait", options.wait] : []), ...(options.dryRun ? ["--dry-run"] : [])];
|
|
@@ -129,11 +129,11 @@ export const B2_LANE_CONTRACT = Object.freeze({
|
|
|
129
129
|
}),
|
|
130
130
|
"method.update": Object.freeze({
|
|
131
131
|
scope: "method update readback",
|
|
132
|
-
description: "原地更新业务集成方法(`/bizservice/method/update
|
|
132
|
+
description: "原地更新业务集成方法(`/bizservice/method/update`),RESTFUL 参数 position 仅 QUERY_PARAMS|URL_VARIABLE|REQUEST_HEADER|REQUEST_BODY|PATH_PARAMS(写入名字、读回整数索引),非法项一次性全列。方法 configJson 由工具 AES 加密,参数面结构化序列化并回读;保留方法身份与规则引用。",
|
|
133
133
|
}),
|
|
134
134
|
"method.create": Object.freeze({
|
|
135
135
|
scope: "method create readback",
|
|
136
|
-
description: "在**已存在的业务服务**下新增方法(`/bizservice/method/create
|
|
136
|
+
description: "在**已存在的业务服务**下新增方法(`/bizservice/method/create`),RESTFUL 参数 position 仅 QUERY_PARAMS|URL_VARIABLE|REQUEST_HEADER|REQUEST_BODY|PATH_PARAMS,入参与出参都必带,常量头 required:false,非法项一次性全列。新写一个扩展接口通常不是新建服务,而是往已有服务里加方法。服务不存在 → refused(新增服务用 `service.register`);方法已存在 → refused(改用 `method.update`)。configJson 由工具 AES 加密,参数面结构化序列化并回读。**RESTFUL 参数面**:入参与出参的每个参数都要 `configJson.position`,合法值只有 `QUERY_PARAMS | URL_VARIABLE | REQUEST_HEADER | REQUEST_BODY | PATH_PARAMS`(`REQUEST_PARAM` 不存在;`URL_VARIABLE` 运行时 501007 被拒)。平台注入的常量头(如 X-LowCode-Corpid)标 `required:false`,否则 method.test 会按缺参 fail-closed。写入用枚举名,读回是整数索引(0 QUERY_PARAMS / 1 URL_VARIABLE / 2 REQUEST_HEADER / 3 REQUEST_BODY / 4 PATH_PARAMS)。",
|
|
137
137
|
}),
|
|
138
138
|
"method.delete": Object.freeze({
|
|
139
139
|
scope: "method delete readback",
|
|
@@ -61,9 +61,10 @@ function safeParseArray(value) {
|
|
|
61
61
|
function parseDeclaredParameterCodes(inputParametersJson) {
|
|
62
62
|
const parsed = typeof inputParametersJson === "string" ? safeParseArray(inputParametersJson) : Array.isArray(inputParametersJson) ? inputParametersJson : undefined;
|
|
63
63
|
if (!parsed)
|
|
64
|
-
return { all: [], required: [] };
|
|
64
|
+
return { all: [], required: [], constants: [] };
|
|
65
65
|
const all = [];
|
|
66
66
|
const required = [];
|
|
67
|
+
const constants = [];
|
|
67
68
|
for (const entry of parsed) {
|
|
68
69
|
if (!entry || typeof entry !== "object")
|
|
69
70
|
continue;
|
|
@@ -73,8 +74,12 @@ function parseDeclaredParameterCodes(inputParametersJson) {
|
|
|
73
74
|
all.push(code);
|
|
74
75
|
if (entry.required === true)
|
|
75
76
|
required.push(code);
|
|
77
|
+
// codeType 0 = CONST: the platform injects the value; a null/blank test
|
|
78
|
+
// value is a declaration problem, not a missing business input.
|
|
79
|
+
if (entry.codeType === 0 || entry.codeType === "0")
|
|
80
|
+
constants.push(code);
|
|
76
81
|
}
|
|
77
|
-
return { all, required };
|
|
82
|
+
return { all, required, constants };
|
|
78
83
|
}
|
|
79
84
|
/**
|
|
80
85
|
* Ticket T08 — readback face for a service LIST row: an explicit field
|
|
@@ -1280,6 +1285,18 @@ export function registerB2Lane(tools) {
|
|
|
1280
1285
|
const missing = face.required.filter((code) => !(code in args.testInputParametersMap));
|
|
1281
1286
|
if (missing.length > 0)
|
|
1282
1287
|
throw new ToolRefusalError({ status: "refused", blocker: "bizservice-test-param-missing", message: `试跑缺必填入参:${missing.join("、")}(按方法声明参数面 fail-closed,零试跑调用)`, missing });
|
|
1288
|
+
// Live 2026-10-18 (SubCon): the test readback echoed
|
|
1289
|
+
// {"x-lowcode-enginecode":null,...} and the caller only understood it
|
|
1290
|
+
// after eating a 401. Flag declared CONST parameters supplied as
|
|
1291
|
+
// null/blank BEFORE the wire: the platform injects them, so a null here
|
|
1292
|
+
// means "the declaration/injection is wrong", not "the test input is
|
|
1293
|
+
// missing".
|
|
1294
|
+
const blankConstants = face.constants.filter((code) => {
|
|
1295
|
+
if (!(code in args.testInputParametersMap))
|
|
1296
|
+
return true;
|
|
1297
|
+
const value = args.testInputParametersMap[code];
|
|
1298
|
+
return value === null || value === undefined || (typeof value === "string" && value.trim() === "");
|
|
1299
|
+
});
|
|
1283
1300
|
const unknown = supplied.filter((code) => !face.all.includes(code));
|
|
1284
1301
|
if (unknown.length > 0)
|
|
1285
1302
|
throw new ToolRefusalError({ status: "refused", blocker: "bizservice-test-param-unknown", message: `试跑携带未声明参数:${unknown.join("、")}(方法参数面只有 ${face.all.join("、") || "(空)"};不猜不透传,零试跑调用)`, unknown });
|
|
@@ -1329,6 +1346,7 @@ export function registerB2Lane(tools) {
|
|
|
1329
1346
|
result: performed.data,
|
|
1330
1347
|
configUnchanged,
|
|
1331
1348
|
externalSuccess,
|
|
1349
|
+
...(blankConstants.length ? { constantHeaderWarnings: blankConstants, constantHeaderHint: "平台注入的常量参数在试跑回显为 null/空——先确认参数声明 codeType:0 + bizCode(并为它们标 required:false);出现 401/EngineCode 空头时优先查这里,而不是改业务入参" } : {}),
|
|
1332
1350
|
...(resultBodyErrcode === undefined ? {} : { resultBodyErrcode }),
|
|
1333
1351
|
...(externalSuccess ? {} : { externalFailure }),
|
|
1334
1352
|
},
|
|
@@ -74,7 +74,8 @@ export function registerBusinessRuleLane(tools) {
|
|
|
74
74
|
inputSchema: {
|
|
75
75
|
environment: environmentArg,
|
|
76
76
|
schemaCode: z.string().min(1),
|
|
77
|
-
|
|
77
|
+
onlyCustom: z.boolean().optional().describe("只回自建规则(defaultRule!==true),滤掉平台预置触发行——读面收窄,省 token"),
|
|
78
|
+
full: z.boolean().optional().describe("行内联完整字段(id/createdTime/creator/…,并跳过 detailFile 外置);默认行只有 code/name/enabled/status/ruleVersion/defaultRule/latestPublished"),
|
|
78
79
|
capability: capabilityArg,
|
|
79
80
|
},
|
|
80
81
|
handler: async (ctx, args, self) => governedRead(ctx, self, args, async (session) => {
|
|
@@ -85,9 +86,11 @@ export function registerBusinessRuleLane(tools) {
|
|
|
85
86
|
// with full:true (the old 14-key row). rule.get resolves by business
|
|
86
87
|
// key (schemaCode+ruleCode), so the row id is rarely needed.
|
|
87
88
|
const fullRows = args.full === true;
|
|
89
|
+
const visible = args.onlyCustom === true ? rows.filter((row) => row.defaultRule !== true) : rows;
|
|
88
90
|
return {
|
|
89
91
|
schemaCode: args.schemaCode,
|
|
90
|
-
|
|
92
|
+
...(args.onlyCustom === true ? { filtered: { onlyCustom: true, total: rows.length, returned: visible.length } } : {}),
|
|
93
|
+
rules: visible.map((row) => ({
|
|
91
94
|
code: row.code,
|
|
92
95
|
name: row.name,
|
|
93
96
|
enabled: row.enabled,
|
|
@@ -80,8 +80,77 @@ const bizserviceSelfCallCorp = Object.freeze({
|
|
|
80
80
|
],
|
|
81
81
|
requiresEnvironmentDerivedIds: false,
|
|
82
82
|
});
|
|
83
|
+
const methodCreateRestfulGet = Object.freeze({
|
|
84
|
+
id: "method-create-restful-get",
|
|
85
|
+
title: "给既有服务加一个 RESTFUL GET 方法(query 参数 + 常量头 + 出参 position)",
|
|
86
|
+
status: "offline-valid",
|
|
87
|
+
tool: "method.create",
|
|
88
|
+
args: {
|
|
89
|
+
environment,
|
|
90
|
+
serviceCode: "<service_code>",
|
|
91
|
+
methodCode: "querySupplier",
|
|
92
|
+
name: "供应商查询",
|
|
93
|
+
methodConfig: { httpType: "Get", requestBodyType: "JSON", url: "/openapi/supplier/query", frontEndExtensionEnable: true },
|
|
94
|
+
methodInputParameters: [
|
|
95
|
+
{ code: "keyword", name: "关键字", required: true, configJson: { position: "QUERY_PARAMS" } },
|
|
96
|
+
{ code: "x-lowcode-corpId", name: "企业ID(平台注入常量)", required: false, codeType: 0, bizCode: "<corpId>", configJson: { position: "REQUEST_HEADER" } },
|
|
97
|
+
],
|
|
98
|
+
methodOutputParameters: [{ code: "name", name: "供应商名称", configJson: { position: "REQUEST_BODY" } }],
|
|
99
|
+
},
|
|
100
|
+
preconditions: ["服务已存在(method.create 只加方法);methodCode 未存在;RESTFUL 每个入参与出参都带 configJson.position"],
|
|
101
|
+
conditionalRequirements: [
|
|
102
|
+
"position 仅 QUERY_PARAMS/URL_VARIABLE/REQUEST_HEADER/REQUEST_BODY/PATH_PARAMS;URL_VARIABLE 运行时 501007",
|
|
103
|
+
"平台注入常量头标 required:false,否则 method.test 缺参 fail-closed;常量值放 codeType:0 + bizCode",
|
|
104
|
+
"方法 url 走 /openapi/**(单层 + xclient);/api/** 是双层用户态,混用鉴权即 401",
|
|
105
|
+
],
|
|
106
|
+
priorToolReferences: [
|
|
107
|
+
{ tool: "service.get", args: { environment, serviceCode: "<service_code>" }, purpose: "确认服务存在、adapterCode=RESTFUL,并照抄既有方法的 url/auth 形状" },
|
|
108
|
+
],
|
|
109
|
+
dryRun: { supported: true, command: `omc call method.create --env ${environment} --dry-run --args '<recipe.args>'`, notes: "dry-run 只做参数面本地预检,零 wire" },
|
|
110
|
+
expectedReadback: { performed: { errcode: 0 }, readback: { methodPresent: true }, next: ["method.get 复核参数面(position 读回为整数索引)"] },
|
|
111
|
+
refusalRecovery: [
|
|
112
|
+
{ code: "bizservice-parameter-face-invalid", action: "按 issues 一次性修完全部非法项;position 枚举名拼写见 summary" },
|
|
113
|
+
{ code: "duplicate-method", action: "方法已存在;改用 method.update" },
|
|
114
|
+
...commonRecovery,
|
|
115
|
+
],
|
|
116
|
+
requiresEnvironmentDerivedIds: true,
|
|
117
|
+
prerequisiteData: { source: "service.get", fields: ["serviceCode", "adapterCode"] },
|
|
118
|
+
});
|
|
119
|
+
const methodCreateRestfulPost = Object.freeze({
|
|
120
|
+
id: "method-create-restful-post",
|
|
121
|
+
title: "给既有服务加一个 RESTFUL POST 方法(JSON body)",
|
|
122
|
+
status: "offline-valid",
|
|
123
|
+
tool: "method.create",
|
|
124
|
+
args: {
|
|
125
|
+
environment,
|
|
126
|
+
serviceCode: "<service_code>",
|
|
127
|
+
methodCode: "saveSupplier",
|
|
128
|
+
name: "供应商保存",
|
|
129
|
+
methodConfig: { httpType: "Post", requestBodyType: "JSON", url: "/openapi/supplier/save", frontEndExtensionEnable: true },
|
|
130
|
+
methodInputParameters: [{ code: "body", name: "供应商对象", required: true, configJson: { position: "REQUEST_BODY" } }],
|
|
131
|
+
methodOutputParameters: [{ code: "id", name: "主键", configJson: { position: "REQUEST_BODY" } }],
|
|
132
|
+
},
|
|
133
|
+
preconditions: ["服务已存在;methodCode 未存在;POST 入参 position=REQUEST_BODY,requestBodyType=JSON"],
|
|
134
|
+
conditionalRequirements: [
|
|
135
|
+
"出参也必须带 position(缺它会写成功但 invoke NPE)",
|
|
136
|
+
"方法 url 走 /openapi/** + xclient;不要用 /externalLink 免鉴权前缀",
|
|
137
|
+
],
|
|
138
|
+
priorToolReferences: [
|
|
139
|
+
{ tool: "service.get", args: { environment, serviceCode: "<service_code>" }, purpose: "照抄同 adapter 既有方法的 url 前缀与鉴权" },
|
|
140
|
+
],
|
|
141
|
+
dryRun: { supported: true, command: `omc call method.create --env ${environment} --dry-run --args '<recipe.args>'`, notes: "dry-run 零 wire" },
|
|
142
|
+
expectedReadback: { performed: { errcode: 0 }, readback: { methodPresent: true }, next: ["method.test 跑一条最小实参"] },
|
|
143
|
+
refusalRecovery: [
|
|
144
|
+
{ code: "bizservice-parameter-face-invalid", action: "按 issues 一次性修全" },
|
|
145
|
+
{ code: "duplicate-method", action: "改用 method.update" },
|
|
146
|
+
...commonRecovery,
|
|
147
|
+
],
|
|
148
|
+
requiresEnvironmentDerivedIds: true,
|
|
149
|
+
prerequisiteData: { source: "service.get", fields: ["serviceCode", "adapterCode"] },
|
|
150
|
+
});
|
|
83
151
|
export const B2_METADATA = Object.freeze({
|
|
84
152
|
"service.register": Object.freeze({ recipes: [restfulQueryService], relatedTools: ["service.category.list", "service.adapter_config", "service.get", "method.get"] }),
|
|
85
153
|
"method.update": Object.freeze({ recipes: [bizserviceSelfCallCorp], relatedTools: ["method.get", "method.create", "service.get"] }),
|
|
154
|
+
"method.create": Object.freeze({ recipes: [methodCreateRestfulGet, methodCreateRestfulPost], relatedTools: ["method.get", "method.update", "service.get", "method.test"] }),
|
|
86
155
|
});
|
|
87
156
|
//# sourceMappingURL=b2-registration.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stalfh233/omc-cli",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.56",
|
|
4
4
|
"description": "CloudPivot 8.6 configuration capability CLI — governed, fail-closed, coverage-manifest bounded.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -50,9 +50,9 @@
|
|
|
50
50
|
"access": "public"
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
|
-
"@stalfh233/omc-fake-platform": "0.4.
|
|
53
|
+
"@stalfh233/omc-fake-platform": "0.4.56",
|
|
54
54
|
"commander": "14.0.3",
|
|
55
|
-
"omc-core": "0.4.
|
|
55
|
+
"omc-core": "0.4.56",
|
|
56
56
|
"proper-lockfile": "4.1.2",
|
|
57
57
|
"write-file-atomic": "6.0.0",
|
|
58
58
|
"zod": "^3.23.8",
|
|
@@ -238,7 +238,8 @@ omc call service.get --env local --args '{"serviceCode":"omc_demo_restful"}'
|
|
|
238
238
|
- **写侧用名字、读侧是整数索引**(`{"position":2}` = `REQUEST_HEADER`)——核对时不要因类型变化误判"没写进去"。映射以写入名为准。
|
|
239
239
|
- **常量头(平台注入,如 `X-LowCode-Corpid`/`X-LowCode-EngineCode`)标 `required:false`**:标成必填会让 `method.test` 判缺参而 fail-closed;常量值放 `codeType:0` + `bizCode`。
|
|
240
240
|
- `method.test` 缺常量头时拒跑是设计:要么先补齐实参,要么把常量声明为 `required:false`。
|
|
241
|
-
-
|
|
241
|
+
- 一次性把**全部**非法参数项改完再重试;0.4.56 起校验会一次列出全部非法项(`issues[]`)。
|
|
242
|
+
- **请求头与查询参数都写在方法参数面**(`REQUEST_HEADER` / `QUERY_PARAMS`),不要另外写代码拦截器注入;平台按参数面注入,声明处就是事实源。
|
|
242
243
|
|
|
243
244
|
### 方法 URL 前缀 × 鉴权方式(混用即 401)
|
|
244
245
|
|
package/skills/omc-refusals.md
CHANGED
|
@@ -105,11 +105,12 @@ process exits), then retry. This guards concurrency, not approval.
|
|
|
105
105
|
|
|
106
106
|
## 8. Runtime / gateway errors (not tool refusals, same discipline)
|
|
107
107
|
|
|
108
|
-
- `401
|
|
109
|
-
- `500`
|
|
110
|
-
- `404
|
|
111
|
-
- `position`
|
|
112
|
-
-
|
|
108
|
+
- `401 权限不够`(业务集成)→ 先查**前缀×鉴权**:`/openapi/**` 用 `xclient`,`/api/api/**` 用用户 token;混用必 401。对照同 adapter 既有成功服务(`service.get` 的方法 url)。
|
|
109
|
+
- `500` 且仅 `X-LowCode-EngineCode should not be empty` → 补多租户头,不是业务错误。
|
|
110
|
+
- `404 openresty` / `API 在服务端找不到` → url 层数不对(双层/三层 `/api`),按矩阵改一次,别反复试。
|
|
111
|
+
- `position` 读回整数、写入枚举名 → 读侧编码,不是没写进去。
|
|
112
|
+
- 版本证据缺失:先分 `pcServerUrl` 缺失(读取耦合,0.4.54 已修)与 `systemVersion` 真缺失(fail-closed 正确);不要伪造 8.6。
|
|
113
|
+
- `401/403` 且 body 是 HTML/空 → 网关层,不是业务拒绝;停手索要一条可运行样例,别继续改载荷。
|
|
113
114
|
|
|
114
115
|
## 9. Input / target
|
|
115
116
|
|