@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,581 @@
|
|
|
1
|
+
import { ToolRefusalError } from "./tool-types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Ticket 92 — business-integration write-face codec + preflight (pure functions,
|
|
4
|
+
* zero DSH/wire dependencies). Reused by service.register (92), service.update
|
|
5
|
+
* (93), method.get/update/delete (94/95/97).
|
|
6
|
+
*
|
|
7
|
+
* Evidence:
|
|
8
|
+
* - research/bizservice-integration-backend-face.md (8.6.26 source, file:line):
|
|
9
|
+
* service configJson is PLAINTEXT (BizServiceController create path); method
|
|
10
|
+
* configJson is AES-128-ECB/Base64 on entry and persisted as plaintext
|
|
11
|
+
* (BizServiceMethodController.java:104-105,546-550; AESUtils.java:18-51) — a
|
|
12
|
+
* plaintext payload silently decrypts to NULL and is stored NULL.
|
|
13
|
+
* - evidence/bizservice-rule-binding-fwb-20260914.md (fwb 8.6.39 readback):
|
|
14
|
+
* adapterCode RESTFUL/DATA_SQL; service readback configJson plaintext;
|
|
15
|
+
* method readback enum INTEGERS (httpType:0, requestBodyType:1, line 122/136).
|
|
16
|
+
* - CORRECTION (live 8.6.26 round19): the WIRE representation is the INTEGER
|
|
17
|
+
* index on BOTH sides. `HttpType`/`RequestBodyType`/`BizServiceLocationType`
|
|
18
|
+
* are `@JsonValue int getIndex()` + `@JsonCreator get(int)`; platform built-in
|
|
19
|
+
* rows store `"httpType":0` and `"position":3`; `method/list_http_type`
|
|
20
|
+
* returns `{name,index}`. Writing the NAME leaves the field unparseable and
|
|
21
|
+
* the designer shows 接口类型 未配置. The tool takes names (tool-speak) but
|
|
22
|
+
* ENCODES to the integer index on the wire, and DECODES both on readback.
|
|
23
|
+
* - RestProtocolAdapterRuntimeServiceImpl.java:185-200: URL_VARIABLE(1) has no
|
|
24
|
+
* runtime branch (501007) — the preflight refuses it.
|
|
25
|
+
*/
|
|
26
|
+
/** Source-evidenced native write adapters. INTERNAL method creation remains extension-package import, not this tool face. */
|
|
27
|
+
export const BIZSERVICE_WRITE_ADAPTERS = Object.freeze(["RESTFUL", "DATA_SQL", "DATA_TABLE", "SAP", "KAFKA", "SOAP"]);
|
|
28
|
+
/**
|
|
29
|
+
* HttpType enum (HttpType.java:13-43). The platform enum is `@JsonValue int
|
|
30
|
+
* getIndex()` + `@JsonCreator get(int)` — i.e. the WIRE representation is the
|
|
31
|
+
* INTEGER index (platform built-in methods store `"httpType":0`; the designer
|
|
32
|
+
* dropdown endpoint `bizservice/method/list_http_type` returns `{name,index}`).
|
|
33
|
+
* The tool SPEAKS names to the caller but MUST write the integer index, or the
|
|
34
|
+
* platform stores an unparseable string and the UI shows 接口类型 未配置
|
|
35
|
+
* (live round19: `"httpType":"Get"` broke `method/get`).
|
|
36
|
+
*/
|
|
37
|
+
export const RESTFUL_HTTP_TYPES = Object.freeze({ 0: "Post", 1: "Get", 2: "Put", 3: "Patch", 4: "Delete" });
|
|
38
|
+
/** RequestBodyType enum (RequestBodyType.java:13-40). Wire = integer index. */
|
|
39
|
+
export const RESTFUL_REQUEST_BODY_TYPES = Object.freeze({ 0: "Form-urlencoded", 1: "JSON", 2: "Text" });
|
|
40
|
+
/** BizServiceLocationType (BizServiceLocationType.java:13-43). Wire = integer index. */
|
|
41
|
+
export const RESTFUL_PARAMETER_POSITIONS = Object.freeze({ 0: "QUERY_PARAMS", 1: "URL_VARIABLE", 2: "REQUEST_HEADER", 3: "REQUEST_BODY", 4: "PATH_PARAMS" });
|
|
42
|
+
/**
|
|
43
|
+
* ExternalParameterType (platform `ParameterModel.externParameterType`): the
|
|
44
|
+
* wire is an integer index (1000/1001/1002), names are PROPERTY/LIST/OBJECT
|
|
45
|
+
* (`ExternalParameterType.java`). The rule designer emits it on EVERY
|
|
46
|
+
* parameter of a published BizMethod (leaf → 1000 PROPERTY; a parameter that
|
|
47
|
+
* carries `subParameters` → 1002 OBJECT, its children → 1001 LIST); a literal
|
|
48
|
+
* `deviceNo`-style BO leaf without it is the signature of a hand-rolled
|
|
49
|
+
* method face (params silently drop in the 绑定业务方法 dialog).
|
|
50
|
+
*/
|
|
51
|
+
export const EXTERN_PARAMETER_TYPES = Object.freeze({ 1000: "PROPERTY", 1001: "LIST", 1002: "OBJECT" });
|
|
52
|
+
const EXTERN_PARAMETER_NAMES = Object.freeze({ PROPERTY: 1000, LIST: 1001, OBJECT: 1002 });
|
|
53
|
+
/**
|
|
54
|
+
* BizPropertyType (platform `ParameterModel.bizPropertyType` / `MetadataPropertyType`):
|
|
55
|
+
* wire = the fourth constructor arg (`BizPropertyType.java`), NOT the enum
|
|
56
|
+
* ordinal. A published method parameter MUST carry this (designer's 参数类型);
|
|
57
|
+
* without it the parameter has no type and the 业务规则的绑定业务方法 dialog
|
|
58
|
+
* cannot match it to a target field (matching is by type — 短文本/下拉单选
|
|
59
|
+
* families interoperate, but a type-less or mismatched parameter cannot be
|
|
60
|
+
* mapped). Evidence: designer-written method faces always carry
|
|
61
|
+
* `bizPropertyType` (e.g. `{"code":"changeFormId",...,"bizPropertyType":0,...}`).
|
|
62
|
+
*/
|
|
63
|
+
export const BIZ_PROPERTY_TYPES = Object.freeze({
|
|
64
|
+
0: "SHORT_TEXT", 1: "LONG_TEXT", 2: "NUMERICAL", 3: "DATE", 4: "LOGICAL", 5: "SELECTION",
|
|
65
|
+
6: "ATTACHMENT", 7: "COMMENT", 8: "CHILD_TABLE", 9: "WORK_SHEET", 10: "ADDRESS", 11: "MULT_WORK_SHEET",
|
|
66
|
+
12: "RADIO", 13: "CHECKBOX", 14: "DROPDOWN_BOX", 15: "DROPDOWN_MULTI_BOX", 16: "RELEVANCE_DATA",
|
|
67
|
+
17: "HYPER_LINK", 20: "ID", 28: "TIME", 50: "STAFF_SELECTOR", 51: "STAFF_MULTI_SELECTOR",
|
|
68
|
+
60: "DEPARTMENT_SELECTOR", 61: "DEPARTMENT_MULTI_SELECTOR", 100: "FORMULA", 250: "OCR",
|
|
69
|
+
251: "ELECTRONIC_SIGN", 254: "APPROVAL_INFO",
|
|
70
|
+
});
|
|
71
|
+
const BIZ_PROPERTY_TYPE_NAMES = Object.freeze(Object.fromEntries(Object.entries(BIZ_PROPERTY_TYPES).map(([index, name]) => [name, Number(index)])));
|
|
72
|
+
/** MethodReturnType (MethodReturnType.java:13-40). */
|
|
73
|
+
export const DATA_RETURN_TYPES = Object.freeze({ 0: "Void", 1: "SingleObject", 2: "List" });
|
|
74
|
+
export const RESTFUL_AUTH_PARAMETER_CODES = Object.freeze({
|
|
75
|
+
customer: Object.freeze([]),
|
|
76
|
+
basic: Object.freeze(["username", "password"]),
|
|
77
|
+
oauth_password: Object.freeze(["client_id", "client_secret", "username", "password", "oauth_path"]),
|
|
78
|
+
oauth_client: Object.freeze(["client_id", "client_secret", "oauth_path"]),
|
|
79
|
+
});
|
|
80
|
+
export const RESTFUL_AUTH_PARAMETER_DEFINITIONS = Object.freeze({
|
|
81
|
+
customer: Object.freeze([]),
|
|
82
|
+
basic: Object.freeze([
|
|
83
|
+
{ code: "username", name: "用户名", description: "请输入用户名", required: true },
|
|
84
|
+
{ code: "password", name: "密码", description: "请输入密码", required: true },
|
|
85
|
+
]),
|
|
86
|
+
oauth_password: Object.freeze([
|
|
87
|
+
{ code: "client_id", name: "客户端ID", description: "", required: true },
|
|
88
|
+
{ code: "client_secret", name: "客户端凭证", description: "", required: true },
|
|
89
|
+
{ code: "username", name: "用户名", description: "请输入用户名", required: true },
|
|
90
|
+
{ code: "password", name: "密码", description: "请输入密码", required: true },
|
|
91
|
+
{ code: "oauth_path", name: "获取token地址", description: "", required: true },
|
|
92
|
+
]),
|
|
93
|
+
oauth_client: Object.freeze([
|
|
94
|
+
{ code: "client_id", name: "客户端ID", description: "", required: true },
|
|
95
|
+
{ code: "client_secret", name: "客户端凭证", description: "", required: true },
|
|
96
|
+
{ code: "oauth_path", name: "获取token地址", description: "", required: true },
|
|
97
|
+
]),
|
|
98
|
+
});
|
|
99
|
+
export function validateRestfulAuth(auth) {
|
|
100
|
+
if (!isRecord(auth))
|
|
101
|
+
refuse(BIZSERVICE_CONFIG_BLOCKERS.serviceConfigInvalid, "RESTFUL 服务 auth 必须是对象", { key: "auth" });
|
|
102
|
+
if (typeof auth.code !== "string" || !auth.code || !(auth.code in RESTFUL_AUTH_PARAMETER_CODES))
|
|
103
|
+
refuse(BIZSERVICE_CONFIG_BLOCKERS.serviceConfigInvalid, `RESTFUL auth.code 必须是 customer/basic/oauth_password/oauth_client,动态扩展鉴权不在已证实写面`, { key: "auth.code" });
|
|
104
|
+
if (typeof auth.name !== "undefined" && typeof auth.name !== "string")
|
|
105
|
+
refuse(BIZSERVICE_CONFIG_BLOCKERS.serviceConfigInvalid, "RESTFUL auth.name 必须是字符串", { key: "auth.name" });
|
|
106
|
+
if (typeof auth.description !== "undefined" && typeof auth.description !== "string")
|
|
107
|
+
refuse(BIZSERVICE_CONFIG_BLOCKERS.serviceConfigInvalid, "RESTFUL auth.description 必须是字符串", { key: "auth.description" });
|
|
108
|
+
if (!Array.isArray(auth.parameterlist))
|
|
109
|
+
refuse(BIZSERVICE_CONFIG_BLOCKERS.serviceConfigInvalid, "RESTFUL auth.parameterlist 必须是数组", { key: "auth.parameterlist" });
|
|
110
|
+
const expected = RESTFUL_AUTH_PARAMETER_CODES[auth.code];
|
|
111
|
+
const items = auth.parameterlist;
|
|
112
|
+
const seen = new Set();
|
|
113
|
+
for (const [index, item] of items.entries()) {
|
|
114
|
+
if (!isRecord(item) || typeof item.code !== "string" || !item.code || typeof item.value !== "string" || !item.value)
|
|
115
|
+
refuse(BIZSERVICE_CONFIG_BLOCKERS.serviceConfigInvalid, `RESTFUL auth.parameterlist[${index}] 必须含 code/name?/value 非空字符串;敏感 value 只透传不记录`, { key: `auth.parameterlist[${index}]` });
|
|
116
|
+
if (item.name !== undefined && typeof item.name !== "string")
|
|
117
|
+
refuse(BIZSERVICE_CONFIG_BLOCKERS.serviceConfigInvalid, `RESTFUL auth.parameterlist[${index}].name 必须是字符串`, { key: `auth.parameterlist[${index}].name` });
|
|
118
|
+
if (item.description !== undefined && typeof item.description !== "string")
|
|
119
|
+
refuse(BIZSERVICE_CONFIG_BLOCKERS.serviceConfigInvalid, `RESTFUL auth.parameterlist[${index}].description 必须是字符串`, { key: `auth.parameterlist[${index}].description` });
|
|
120
|
+
if (item.required !== undefined && item.required !== true)
|
|
121
|
+
refuse(BIZSERVICE_CONFIG_BLOCKERS.serviceConfigInvalid, `RESTFUL auth.parameterlist[${index}].required 必须为 true(前端 AdapterConfigParam.Param 定义)`, { key: `auth.parameterlist[${index}].required` });
|
|
122
|
+
if (seen.has(item.code))
|
|
123
|
+
refuse(BIZSERVICE_CONFIG_BLOCKERS.serviceConfigInvalid, `RESTFUL auth.parameterlist 不允许重复参数 ${item.code}`, { key: "auth.parameterlist" });
|
|
124
|
+
seen.add(item.code);
|
|
125
|
+
if (!expected.includes(item.code))
|
|
126
|
+
refuse(BIZSERVICE_CONFIG_BLOCKERS.serviceConfigInvalid, `RESTFUL auth=${auth.code} 不允许参数 ${item.code};期望参数:${expected.join(",") || "无"}`, { key: `auth.parameterlist[${index}].code` });
|
|
127
|
+
}
|
|
128
|
+
const missing = expected.filter((code) => !seen.has(code));
|
|
129
|
+
if (missing.length > 0)
|
|
130
|
+
refuse(BIZSERVICE_CONFIG_BLOCKERS.serviceConfigInvalid, `RESTFUL auth=${auth.code} 缺少参数:${missing.join(",")}`, { key: "auth.parameterlist" });
|
|
131
|
+
}
|
|
132
|
+
export const BIZSERVICE_CONFIG_BLOCKERS = Object.freeze({
|
|
133
|
+
adapterNotWhitelisted: "bizservice-adapter-not-whitelisted",
|
|
134
|
+
rawConfigJson: "bizservice-raw-config-json",
|
|
135
|
+
serviceConfigInvalid: "bizservice-service-config-invalid",
|
|
136
|
+
methodConfigInvalid: "bizservice-method-config-invalid",
|
|
137
|
+
parameterFaceInvalid: "bizservice-parameter-face-invalid",
|
|
138
|
+
});
|
|
139
|
+
function refuse(blocker, message, details) {
|
|
140
|
+
throw new ToolRefusalError({ status: "refused", blocker, message, ...(details ?? {}) });
|
|
141
|
+
}
|
|
142
|
+
function isRecord(value) {
|
|
143
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
144
|
+
}
|
|
145
|
+
/** Commonlist lookup: ServiceConfigVO reads commonlist[], not a top-level field (BizServiceController save path). */
|
|
146
|
+
function commonlistValue(serviceConfig, code) {
|
|
147
|
+
const list = serviceConfig.commonlist;
|
|
148
|
+
if (!Array.isArray(list))
|
|
149
|
+
return undefined;
|
|
150
|
+
for (const item of list)
|
|
151
|
+
if (isRecord(item) && item.code === code && typeof item.value === "string" && item.value)
|
|
152
|
+
return item.value;
|
|
153
|
+
return undefined;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* The adapter gate for every structured write. Non-whitelisted adapterCodes
|
|
157
|
+
* are refused at local dry-lint with the support face named (ADR 0001).
|
|
158
|
+
*/
|
|
159
|
+
export function assertBizServiceAdapterWhitelisted(adapterCode) {
|
|
160
|
+
if (typeof adapterCode !== "string" || !adapterCode)
|
|
161
|
+
refuse(BIZSERVICE_CONFIG_BLOCKERS.adapterNotWhitelisted, "adapterCode 是必需的(结构化写面)", { adapterCode });
|
|
162
|
+
if (!BIZSERVICE_WRITE_ADAPTERS.includes(adapterCode))
|
|
163
|
+
refuse(BIZSERVICE_CONFIG_BLOCKERS.adapterNotWhitelisted, `adapterCode ${adapterCode} 不在写面支持范围内;本写面仅支持 ${BIZSERVICE_WRITE_ADAPTERS.join("/")}。` +
|
|
164
|
+
`**自定义 Java 方法体的常规做法是:在扩展模块写一个接口,然后用 RESTFUL 适配器注册一个服务指向它**(service.register,adapterCode:RESTFUL,methodConfig.url 指向扩展端点);不要用 INTERNAL/custom_protocol 适配器(那是平台内部/自定义协议机制,不是二开接口的常规登记方式,也不是本工具的支持面)`, { adapterCode, supported: BIZSERVICE_WRITE_ADAPTERS });
|
|
165
|
+
return adapterCode;
|
|
166
|
+
}
|
|
167
|
+
/** The AES boundary is not bypassable: a raw configJson string is refused before any wire call. */
|
|
168
|
+
export function assertStructuredConfig(value, label) {
|
|
169
|
+
if (typeof value === "string")
|
|
170
|
+
refuse(BIZSERVICE_CONFIG_BLOCKERS.rawConfigJson, `拒绝原始 ${label} 字符串:传结构化对象,工具负责序列化/加密(方法 configJson 的 AES 边界唯一,不可绕过)`, { label });
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* r6-onlinejs 2026-10-10(全 0 悬案根因链的起点)——鉴权凭据的掩码/占位值。
|
|
174
|
+
* 平台 service/create 不校验凭据内容,掩码/占位值注册成功但运行时自调必败
|
|
175
|
+
* (401/500),且因"服务端不校验+读回掩码"极难排查。凭据值若匹配:
|
|
176
|
+
* 读回掩码 ***(读面按 ticket 115 掩码,不可抄写)、自造 RELEVANCE-xxx 占位、
|
|
177
|
+
* 全 0 串,则拒绝并要求从任务环境事实取真值。
|
|
178
|
+
*/
|
|
179
|
+
const MASKED_SECRET_RE = /^\*{3,}$/;
|
|
180
|
+
const PLACEHOLDER_CORP_RE = /^RELEVANCE-[A-Za-z0-9]+$/;
|
|
181
|
+
const ALL_ZERO_CREDENTIAL_RE = /^0{16,}$/;
|
|
182
|
+
function scanAuthParameterlistForPlaceholders(parameterlist, adapterCode) {
|
|
183
|
+
for (const entry of parameterlist) {
|
|
184
|
+
if (!isRecord(entry))
|
|
185
|
+
continue;
|
|
186
|
+
const value = entry.value;
|
|
187
|
+
if (typeof value !== "string")
|
|
188
|
+
continue;
|
|
189
|
+
const t = value.trim();
|
|
190
|
+
let why = "";
|
|
191
|
+
if (MASKED_SECRET_RE.test(t))
|
|
192
|
+
why = "读回掩码 ***(读面按 ticket 115 掩码,不可抄写)";
|
|
193
|
+
else if (PLACEHOLDER_CORP_RE.test(t))
|
|
194
|
+
why = "自造 RELEVANCE-xxx 占位(沙盒真实 corpId 才是 RELEVANCE-<真实id>)";
|
|
195
|
+
else if (ALL_ZERO_CREDENTIAL_RE.test(t))
|
|
196
|
+
why = "全 0 串(脱敏显示值,非真实凭据)";
|
|
197
|
+
if (why)
|
|
198
|
+
refuse(BIZSERVICE_CONFIG_BLOCKERS.serviceConfigInvalid, `${adapterCode} 服务鉴权凭据包含${why}——平台 service/create 不校验凭据内容,注册成功但运行时自调必败(r6-onlinejs 真机:全 0 corpId 注册成功、自调 401/500)。凭据必须从任务环境事实取真值(如 doctor 显示的 corpId、任务提供的 client_secret)。`, { key: `auth.parameterlist.${String(entry.code ?? "")}`, value: t });
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
function requireAuthAndUrl(serviceConfig, adapterCode) {
|
|
202
|
+
const auth = serviceConfig.auth;
|
|
203
|
+
if (adapterCode === "RESTFUL" || adapterCode === "SOAP")
|
|
204
|
+
validateRestfulAuth(auth);
|
|
205
|
+
if (!isRecord(auth) || typeof auth.code !== "string" || !auth.code || !Array.isArray(auth.parameterlist))
|
|
206
|
+
refuse(BIZSERVICE_CONFIG_BLOCKERS.serviceConfigInvalid, `${adapterCode} 服务 configJson 需要 auth {code, parameterlist[]}`, { key: "auth" });
|
|
207
|
+
if (!commonlistValue(serviceConfig, "url"))
|
|
208
|
+
refuse(BIZSERVICE_CONFIG_BLOCKERS.serviceConfigInvalid, `${adapterCode} 服务 configJson 需要 commonlist [{code:"url", value}]`, { key: "commonlist.url" });
|
|
209
|
+
scanAuthParameterlistForPlaceholders(auth.parameterlist, adapterCode);
|
|
210
|
+
}
|
|
211
|
+
function requireCommonlist(serviceConfig, key, adapterCode) {
|
|
212
|
+
if (!commonlistValue(serviceConfig, key))
|
|
213
|
+
refuse(BIZSERVICE_CONFIG_BLOCKERS.serviceConfigInvalid, `${adapterCode} 服务 configJson 需要 commonlist [{code:"${key}", value}]`, { key: `commonlist.${key}` });
|
|
214
|
+
}
|
|
215
|
+
function requireEmptyAuth(serviceConfig, adapterCode) {
|
|
216
|
+
const auth = serviceConfig.auth;
|
|
217
|
+
if (auth !== undefined && (!isRecord(auth) || (auth.code !== undefined && auth.code !== "") || (auth.parameterlist !== undefined && (!Array.isArray(auth.parameterlist) || auth.parameterlist.length !== 0))))
|
|
218
|
+
refuse(BIZSERVICE_CONFIG_BLOCKERS.serviceConfigInvalid, `${adapterCode} 服务不使用鉴权,auth 应为空对象或省略`, { key: "auth" });
|
|
219
|
+
}
|
|
220
|
+
function validateSapMethod(methodConfig) {
|
|
221
|
+
for (const key of ["functionName", "returnType"])
|
|
222
|
+
if (methodConfig[key] === undefined)
|
|
223
|
+
refuse(BIZSERVICE_CONFIG_BLOCKERS.methodConfigInvalid, `SAP 方法 configJson 缺少必需键 ${key}`, { key });
|
|
224
|
+
if (typeof methodConfig.functionName !== "string" || !methodConfig.functionName)
|
|
225
|
+
refuse(BIZSERVICE_CONFIG_BLOCKERS.methodConfigInvalid, "SAP functionName 必须是非空字符串", { key: "functionName" });
|
|
226
|
+
methodConfig.returnType = encodeEnumIndex(methodConfig.returnType, DATA_RETURN_TYPES, "returnType");
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Validate a structured service config against the adapter's source-evidenced
|
|
230
|
+
* required keys. Service configJson is plaintext on the wire for every adapter.
|
|
231
|
+
*/
|
|
232
|
+
export function compileServiceConfig(adapterCode, serviceConfig) {
|
|
233
|
+
assertStructuredConfig(serviceConfig, "serviceConfig");
|
|
234
|
+
if (!isRecord(serviceConfig))
|
|
235
|
+
refuse(BIZSERVICE_CONFIG_BLOCKERS.serviceConfigInvalid, "serviceConfig 必须是结构化对象", { adapterCode });
|
|
236
|
+
if (adapterCode === "RESTFUL" || adapterCode === "INTERNAL" || adapterCode === "SOAP")
|
|
237
|
+
requireAuthAndUrl(serviceConfig, adapterCode);
|
|
238
|
+
if (adapterCode === "DATA_SQL" || adapterCode === "DATA_TABLE") {
|
|
239
|
+
requireEmptyAuth(serviceConfig, adapterCode);
|
|
240
|
+
requireCommonlist(serviceConfig, "databaseConnectionPoolCode", adapterCode);
|
|
241
|
+
}
|
|
242
|
+
if (adapterCode === "SAP") {
|
|
243
|
+
requireEmptyAuth(serviceConfig, adapterCode);
|
|
244
|
+
requireCommonlist(serviceConfig, "sapConnectionPoolCode", adapterCode);
|
|
245
|
+
}
|
|
246
|
+
if (adapterCode === "KAFKA") {
|
|
247
|
+
requireEmptyAuth(serviceConfig, adapterCode);
|
|
248
|
+
requireCommonlist(serviceConfig, "kafkaConnectionPoolCode", adapterCode);
|
|
249
|
+
}
|
|
250
|
+
return JSON.stringify(serviceConfig);
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Encode an enum to the platform WIRE INTEGER INDEX. The platform enums
|
|
254
|
+
* (`HttpType`/`RequestBodyType`/`BizServiceLocationType`) are `@JsonValue int
|
|
255
|
+
* getIndex()` + `@JsonCreator get(int)`, and the designer dropdown endpoint
|
|
256
|
+
* returns `{name,index}`; platform built-in rows store the integer
|
|
257
|
+
* (`"httpType":0`, `"position":3`). Accept either the name (tool-speak) or an
|
|
258
|
+
* index, and RETURN the index — writing the name leaves the platform field
|
|
259
|
+
* unparseable and the UI shows 接口类型 未配置 (live round19).
|
|
260
|
+
*/
|
|
261
|
+
function encodeEnumIndex(value, ints, label) {
|
|
262
|
+
const names = Object.values(ints);
|
|
263
|
+
if (typeof value === "number" && Number.isInteger(value)) {
|
|
264
|
+
if (!(value in ints))
|
|
265
|
+
refuse(BIZSERVICE_CONFIG_BLOCKERS.methodConfigInvalid, `${label} 枚举值 ${value} 不在已取证集合内`, { key: label });
|
|
266
|
+
return value;
|
|
267
|
+
}
|
|
268
|
+
if (typeof value === "string") {
|
|
269
|
+
const index = Object.entries(ints).find(([, name]) => name === value)?.[0];
|
|
270
|
+
if (index === undefined)
|
|
271
|
+
refuse(BIZSERVICE_CONFIG_BLOCKERS.methodConfigInvalid, `${label} 枚举名 ${value} 不在已取证集合 ${names.join("|")} 内`, { key: label });
|
|
272
|
+
return Number(index);
|
|
273
|
+
}
|
|
274
|
+
refuse(BIZSERVICE_CONFIG_BLOCKERS.methodConfigInvalid, `${label} 必须是已取证枚举名或整数索引`, { key: label });
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Validate a structured method config against the adapter's required keys and
|
|
278
|
+
* return the plain JSON string (the caller encrypts it for the wire). The tool
|
|
279
|
+
* speaks enum NAMES to the caller but ENCODES them to the platform WIRE INTEGER
|
|
280
|
+
* index (never mutating the caller's object).
|
|
281
|
+
*/
|
|
282
|
+
export function compileMethodConfigPlain(adapterCode, methodConfig) {
|
|
283
|
+
assertStructuredConfig(methodConfig, "methodConfig");
|
|
284
|
+
if (!isRecord(methodConfig))
|
|
285
|
+
refuse(BIZSERVICE_CONFIG_BLOCKERS.methodConfigInvalid, "methodConfig 必须是结构化对象", { adapterCode });
|
|
286
|
+
const config = { ...methodConfig };
|
|
287
|
+
if (adapterCode === "SOAP")
|
|
288
|
+
refuse(BIZSERVICE_CONFIG_BLOCKERS.methodConfigInvalid, "SOAP 方法由 WSDL 动态发现,没有固定 methodConfig;请使用平台原生 SOAP refresh/方法发现接口,不伪造方法体", { adapterCode });
|
|
289
|
+
if (adapterCode === "RESTFUL") {
|
|
290
|
+
for (const key of ["httpType", "requestBodyType", "url", "frontEndExtensionEnable"])
|
|
291
|
+
if (config[key] === undefined)
|
|
292
|
+
refuse(BIZSERVICE_CONFIG_BLOCKERS.methodConfigInvalid, `RESTFUL 方法 configJson 缺少必需键 ${key}(BaseMethodConfigVO/HttpMethodConfigVO)`, { key });
|
|
293
|
+
config.httpType = encodeEnumIndex(config.httpType, RESTFUL_HTTP_TYPES, "httpType");
|
|
294
|
+
config.requestBodyType = encodeEnumIndex(config.requestBodyType, RESTFUL_REQUEST_BODY_TYPES, "requestBodyType");
|
|
295
|
+
if (typeof config.url !== "string" || !config.url)
|
|
296
|
+
refuse(BIZSERVICE_CONFIG_BLOCKERS.methodConfigInvalid, "RESTFUL 方法 url 必须是非空字符串(相对路径与服务 commonlist.url 拼接)", { key: "url" });
|
|
297
|
+
// Designer parity: the rule designer's RESTFUL method config also carries
|
|
298
|
+
// `connectTime` / `retryTimes` (string seconds/count, designer defaults
|
|
299
|
+
// "10"/"3"); the 绑定业务方法 dialog and the runtime read them, so a
|
|
300
|
+
// hand-rolled config that omits them is normalized to the designer
|
|
301
|
+
// defaults. Caller values pass through (string or number).
|
|
302
|
+
if (config.connectTime === undefined)
|
|
303
|
+
config.connectTime = "10";
|
|
304
|
+
else if (typeof config.connectTime !== "string" && typeof config.connectTime !== "number")
|
|
305
|
+
refuse(BIZSERVICE_CONFIG_BLOCKERS.methodConfigInvalid, "RESTFUL connectTime 必须是字符串或数字(秒)", { key: "connectTime" });
|
|
306
|
+
if (config.retryTimes === undefined)
|
|
307
|
+
config.retryTimes = "3";
|
|
308
|
+
else if (typeof config.retryTimes !== "string" && typeof config.retryTimes !== "number")
|
|
309
|
+
refuse(BIZSERVICE_CONFIG_BLOCKERS.methodConfigInvalid, "RESTFUL retryTimes 必须是字符串或数字(次)", { key: "retryTimes" });
|
|
310
|
+
}
|
|
311
|
+
if (adapterCode === "DATA_SQL" || adapterCode === "DATA_TABLE") {
|
|
312
|
+
for (const key of ["sql", "returnType"])
|
|
313
|
+
if (config[key] === undefined)
|
|
314
|
+
refuse(BIZSERVICE_CONFIG_BLOCKERS.methodConfigInvalid, `${adapterCode} 方法 configJson 缺少必需键 ${key}`, { key });
|
|
315
|
+
if (typeof config.sql !== "string" || !config.sql)
|
|
316
|
+
refuse(BIZSERVICE_CONFIG_BLOCKERS.methodConfigInvalid, `${adapterCode} 方法 sql 必须是非空字符串`, { key: "sql" });
|
|
317
|
+
config.returnType = encodeEnumIndex(config.returnType, DATA_RETURN_TYPES, "returnType");
|
|
318
|
+
if (adapterCode === "DATA_TABLE") {
|
|
319
|
+
if (config.sqlType !== undefined && (typeof config.sqlType !== "string" || !["SELECT", "UPDATE", "DELETE", "INSERT", "UPSERT"].includes(config.sqlType)))
|
|
320
|
+
refuse(BIZSERVICE_CONFIG_BLOCKERS.methodConfigInvalid, "DATA_TABLE sqlType 必须是 SELECT/UPDATE/DELETE/INSERT/UPSERT", { key: "sqlType" });
|
|
321
|
+
if (config.dataTable !== undefined && typeof config.dataTable !== "string")
|
|
322
|
+
refuse(BIZSERVICE_CONFIG_BLOCKERS.methodConfigInvalid, "DATA_TABLE dataTable 必须是字符串", { key: "dataTable" });
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
if (adapterCode === "SAP") {
|
|
326
|
+
validateSapMethod(config);
|
|
327
|
+
}
|
|
328
|
+
if (adapterCode === "KAFKA") {
|
|
329
|
+
for (const key of ["sendMode", "connectTime", "retryTimes", "topic"])
|
|
330
|
+
if (config[key] === undefined)
|
|
331
|
+
refuse(BIZSERVICE_CONFIG_BLOCKERS.methodConfigInvalid, `KAFKA 方法 configJson 缺少必需键 ${key}`, { key });
|
|
332
|
+
if (!["SYNC_SEND", "ASYNC_SEND", "FORGOTTEN_SEND"].includes(String(config.sendMode)))
|
|
333
|
+
refuse(BIZSERVICE_CONFIG_BLOCKERS.methodConfigInvalid, "KAFKA sendMode 必须是 SYNC_SEND/ASYNC_SEND/FORGOTTEN_SEND", { key: "sendMode" });
|
|
334
|
+
if (typeof config.topic !== "string" || !config.topic)
|
|
335
|
+
refuse(BIZSERVICE_CONFIG_BLOCKERS.methodConfigInvalid, "KAFKA topic 必须是非空字符串", { key: "topic" });
|
|
336
|
+
if (typeof config.connectTime !== "number" && typeof config.connectTime !== "string")
|
|
337
|
+
refuse(BIZSERVICE_CONFIG_BLOCKERS.methodConfigInvalid, "KAFKA connectTime 必须是数字或字符串", { key: "connectTime" });
|
|
338
|
+
if (typeof config.retryTimes !== "number" && typeof config.retryTimes !== "string")
|
|
339
|
+
refuse(BIZSERVICE_CONFIG_BLOCKERS.methodConfigInvalid, "KAFKA retryTimes 必须是数字或字符串", { key: "retryTimes" });
|
|
340
|
+
}
|
|
341
|
+
if (config.frontEndExtensionEnable !== undefined && typeof config.frontEndExtensionEnable !== "boolean")
|
|
342
|
+
refuse(BIZSERVICE_CONFIG_BLOCKERS.methodConfigInvalid, "frontEndExtensionEnable 必须是布尔值(/runtime/bizbus/invoke 的门槛,false → 500113)", { key: "frontEndExtensionEnable" });
|
|
343
|
+
return JSON.stringify(config);
|
|
344
|
+
}
|
|
345
|
+
function parameterPosition(parameter) {
|
|
346
|
+
const raw = parameter.configJson;
|
|
347
|
+
const parsed = typeof raw === "string" ? safeParse(raw) : raw;
|
|
348
|
+
return isRecord(parsed) ? parsed.position : undefined;
|
|
349
|
+
}
|
|
350
|
+
function safeParse(text) {
|
|
351
|
+
try {
|
|
352
|
+
return JSON.parse(text);
|
|
353
|
+
}
|
|
354
|
+
catch {
|
|
355
|
+
return undefined;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Validate a structured parameter face and serialize it to
|
|
360
|
+
* input/outputParametersJson. `required` is PASSTHROUGH THREE-STATE: true /
|
|
361
|
+
* false / absent stay exactly as declared (an absent key is "必填未声明",
|
|
362
|
+
* never coerced to false — fwb §2 evidence).
|
|
363
|
+
*
|
|
364
|
+
* EXCEPTION (output face): RETURN parameters default to `required:false`
|
|
365
|
+
* when absent. Outputs are values the caller receives — marking them required
|
|
366
|
+
* buys nothing and, because the rule designer mirrors each method parameter's
|
|
367
|
+
* `required` onto the BIZ_ACTION mapping element, a required OUTPUT forces
|
|
368
|
+
* every output mapping to be required too, which is a common source of
|
|
369
|
+
* binding friction. Inputs keep the three-state passthrough: mark them
|
|
370
|
+
* required per actual need.
|
|
371
|
+
*
|
|
372
|
+
* For RESTFUL every parameter must carry configJson.position, and
|
|
373
|
+
* URL_VARIABLE is refused because the runtime has no branch for it (501007).
|
|
374
|
+
*/
|
|
375
|
+
export function compileParameterFace(adapterCode, parameters, label) {
|
|
376
|
+
if (!Array.isArray(parameters))
|
|
377
|
+
refuse(BIZSERVICE_CONFIG_BLOCKERS.parameterFaceInvalid, `${label} 必须是结构化参数数组(ParameterModel[])`, { label });
|
|
378
|
+
const defaultRequired = isOutputFace(label) ? false : undefined;
|
|
379
|
+
const normalized = parameters.map((parameter, index) => compileParameter(adapterCode, parameter, label, `${label}[${index}]`, index, defaultRequired));
|
|
380
|
+
return JSON.stringify(normalized);
|
|
381
|
+
}
|
|
382
|
+
/** True when `label` names the RETURN parameter face (outputParametersJson). */
|
|
383
|
+
function isOutputFace(label) {
|
|
384
|
+
return /output/i.test(label);
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* Compile ONE parameter element (and recurse into `subParameters`). Platform
|
|
388
|
+
* canonical shape (designer-written BizMethod face): every element carries
|
|
389
|
+
* `bizPropertyType` (参数类型, int index) — mandatory, since the 业务规则的
|
|
390
|
+
* 绑定业务方法 dialog matches parameters to target fields BY TYPE and a
|
|
391
|
+
* type-less parameter cannot be mapped — plus `externParameterType` (int;
|
|
392
|
+
* leaf → 1000 PROPERTY, a node with subParameters → 1002 OBJECT) and
|
|
393
|
+
* `description`. RESTFUL additionally requires configJson.position.
|
|
394
|
+
*/
|
|
395
|
+
function compileParameter(adapterCode, parameter, label, path, index, defaultRequired) {
|
|
396
|
+
if (!isRecord(parameter))
|
|
397
|
+
refuse(BIZSERVICE_CONFIG_BLOCKERS.parameterFaceInvalid, `${path} 必须是对象`, { label, index });
|
|
398
|
+
if (typeof parameter.code !== "string" || !parameter.code)
|
|
399
|
+
refuse(BIZSERVICE_CONFIG_BLOCKERS.parameterFaceInvalid, `${path} 缺少 code(绑定校验按 code 对账)`, { label, index });
|
|
400
|
+
if (parameter.required !== undefined && typeof parameter.required !== "boolean")
|
|
401
|
+
refuse(BIZSERVICE_CONFIG_BLOCKERS.parameterFaceInvalid, `${path}.required 必须是布尔(缺席=未声明必填,不会被补成 false)`, { label, index });
|
|
402
|
+
const position = parameterPosition(parameter);
|
|
403
|
+
if (adapterCode === "RESTFUL") {
|
|
404
|
+
if (position === undefined)
|
|
405
|
+
refuse(BIZSERVICE_CONFIG_BLOCKERS.parameterFaceInvalid, `${path} (${parameter.code}) 缺 configJson.position —— RESTFUL 参数无 position 在 invoke 时 NPE(RestProtocolAdapterRuntimeServiceImpl.java:197)`, { label, index, code: parameter.code });
|
|
406
|
+
if (position === 1 || position === "URL_VARIABLE")
|
|
407
|
+
refuse(BIZSERVICE_CONFIG_BLOCKERS.parameterFaceInvalid, `${path} (${parameter.code}) position=URL_VARIABLE 被运行时拒绝(501007,无对应分支);用 QUERY_PARAMS/REQUEST_HEADER/REQUEST_BODY/PATH_PARAMS`, { label, index, code: parameter.code });
|
|
408
|
+
}
|
|
409
|
+
// Platform canonical: `ParameterModel.configJson` is a JSON STRING column
|
|
410
|
+
// whose content is the nested config OBJECT with the WIRE INTEGER position
|
|
411
|
+
// (platform built-ins: "configJson":{"position":3}). Encode the enum and
|
|
412
|
+
// emit the object form; a string input is parsed so we never double-encode.
|
|
413
|
+
const configInput = isRecord(parameter.configJson) ? parameter.configJson : typeof parameter.configJson === "string" ? safeParse(parameter.configJson) : undefined;
|
|
414
|
+
const configJson = adapterCode === "RESTFUL"
|
|
415
|
+
? { ...(isRecord(configInput) ? configInput : {}), position: encodeEnumIndex(position, RESTFUL_PARAMETER_POSITIONS, "position") }
|
|
416
|
+
: parameter.configJson;
|
|
417
|
+
const bizPropertyType = resolveBizPropertyType(parameter, label, index, path);
|
|
418
|
+
const externParameterType = resolveExternParameterType(parameter, label, index);
|
|
419
|
+
const subParameters = Array.isArray(parameter.subParameters)
|
|
420
|
+
? parameter.subParameters.map((sub, subIndex) => compileParameter(adapterCode, sub, label, `${path}.subParameters[${subIndex}]`, subIndex, defaultRequired))
|
|
421
|
+
: parameter.subParameters;
|
|
422
|
+
return {
|
|
423
|
+
...parameter,
|
|
424
|
+
...(configJson === undefined ? {} : { configJson }),
|
|
425
|
+
description: typeof parameter.description === "string" ? parameter.description : "",
|
|
426
|
+
bizPropertyType,
|
|
427
|
+
externParameterType,
|
|
428
|
+
// Output face defaults absent `required` to false (see compileParameterFace).
|
|
429
|
+
...(parameter.required === undefined && defaultRequired !== undefined ? { required: defaultRequired } : {}),
|
|
430
|
+
...(subParameters === undefined ? {} : { subParameters }),
|
|
431
|
+
...(parameter.name === undefined ? { name: parameter.code } : {}),
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
/** Resolve/validate a parameter's `bizPropertyType` (int index or enum name). Absent → refuse (fail-closed). */
|
|
435
|
+
function resolveBizPropertyType(parameter, label, index, path) {
|
|
436
|
+
const raw = parameter.bizPropertyType;
|
|
437
|
+
if (raw === undefined || raw === null)
|
|
438
|
+
refuse(BIZSERVICE_CONFIG_BLOCKERS.parameterFaceInvalid, `${path} (${parameter.code}) 缺 bizPropertyType(参数类型)——业务集成参数必须有类型,否则业务规则按类型绑定失败、参数无法映射;用 BIZ_PROPERTY_TYPES 索引或枚举名(SHORT_TEXT/NUMERICAL/RADIO/DROPDOWN_BOX…)`, { label, index, code: parameter.code });
|
|
439
|
+
if (typeof raw === "number" && Number.isInteger(raw) && raw in BIZ_PROPERTY_TYPES)
|
|
440
|
+
return raw;
|
|
441
|
+
if (typeof raw === "string" && raw in BIZ_PROPERTY_TYPE_NAMES)
|
|
442
|
+
return BIZ_PROPERTY_TYPE_NAMES[raw];
|
|
443
|
+
refuse(BIZSERVICE_CONFIG_BLOCKERS.parameterFaceInvalid, `${path} (${parameter.code}) bizPropertyType 必须是合法字段类型(整数索引或枚举名,如 SHORT_TEXT/NUMERICAL/RADIO/DROPDOWN_BOX)——缺失或有误会破坏业务规则按类型绑定`, { label, index, code: parameter.code });
|
|
444
|
+
}
|
|
445
|
+
/** Resolve/validate a parameter's `externParameterType` (int index or name); default leaf=PROPERTY(1000)/parent=OBJECT(1002). */
|
|
446
|
+
function resolveExternParameterType(parameter, label, index) {
|
|
447
|
+
const raw = parameter.externParameterType;
|
|
448
|
+
if (raw !== undefined) {
|
|
449
|
+
if (typeof raw === "number" && Number.isInteger(raw) && raw in EXTERN_PARAMETER_TYPES)
|
|
450
|
+
return raw;
|
|
451
|
+
if (typeof raw === "string" && raw in EXTERN_PARAMETER_NAMES)
|
|
452
|
+
return EXTERN_PARAMETER_NAMES[raw];
|
|
453
|
+
refuse(BIZSERVICE_CONFIG_BLOCKERS.parameterFaceInvalid, `${label}[${index}] (${parameter.code}) externParameterType 必须是 PROPERTY(1000)/LIST(1001)/OBJECT(1002) 的整数索引或枚举名`, { label, index, code: parameter.code });
|
|
454
|
+
}
|
|
455
|
+
const hasChildren = Array.isArray(parameter.subParameters) && parameter.subParameters.length > 0;
|
|
456
|
+
return hasChildren ? EXTERN_PARAMETER_NAMES.OBJECT : EXTERN_PARAMETER_NAMES.PROPERTY;
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Read-side parameter-face normalization: the platform stores each parameter's
|
|
460
|
+
* `configJson.position` as the WIRE INTEGER index (platform built-ins:
|
|
461
|
+
* `{"position":3}`). Decode it back to the canonical NAME for a readable
|
|
462
|
+
* readback (mirrors `normalizeMethodConfigReadback`). Non-RESTFUL faces pass
|
|
463
|
+
* through untouched.
|
|
464
|
+
*/
|
|
465
|
+
export function normalizeParameterFaceReadback(adapterCode, parameters) {
|
|
466
|
+
if (adapterCode !== "RESTFUL" || !Array.isArray(parameters))
|
|
467
|
+
return parameters;
|
|
468
|
+
return parameters.map((parameter) => {
|
|
469
|
+
if (!isRecord(parameter))
|
|
470
|
+
return parameter;
|
|
471
|
+
const config = isRecord(parameter.configJson) ? parameter.configJson : typeof parameter.configJson === "string" ? safeParse(parameter.configJson) : undefined;
|
|
472
|
+
if (!isRecord(config) || !(typeof config.position === "number" && Number.isInteger(config.position) && config.position in RESTFUL_PARAMETER_POSITIONS))
|
|
473
|
+
return parameter;
|
|
474
|
+
return { ...parameter, configJson: { ...config, position: RESTFUL_PARAMETER_POSITIONS[config.position] } };
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Read-side enum normalization: the wire stores INTEGER indexes (8.6.26 and
|
|
479
|
+
* fwb alike). Decode them to the canonical NAME form so a readback reads like a
|
|
480
|
+
* name; unknown values pass through untouched (never guessed). A name already
|
|
481
|
+
* present passes through unchanged, so the projection is idempotent.
|
|
482
|
+
*/
|
|
483
|
+
export function normalizeMethodConfigReadback(adapterCode, config) {
|
|
484
|
+
if (!isRecord(config))
|
|
485
|
+
return config;
|
|
486
|
+
if (adapterCode !== "RESTFUL" && adapterCode !== "DATA_SQL" && adapterCode !== "DATA_TABLE" && adapterCode !== "SAP" && adapterCode !== "KAFKA")
|
|
487
|
+
return config;
|
|
488
|
+
const normalized = { ...config };
|
|
489
|
+
const mapEntry = (key, ints) => {
|
|
490
|
+
if (typeof normalized[key] === "number" && Number.isInteger(normalized[key]) && normalized[key] in ints)
|
|
491
|
+
normalized[key] = ints[normalized[key]];
|
|
492
|
+
};
|
|
493
|
+
if (adapterCode === "RESTFUL") {
|
|
494
|
+
mapEntry("httpType", RESTFUL_HTTP_TYPES);
|
|
495
|
+
mapEntry("requestBodyType", RESTFUL_REQUEST_BODY_TYPES);
|
|
496
|
+
}
|
|
497
|
+
else if (adapterCode === "DATA_SQL" || adapterCode === "DATA_TABLE" || adapterCode === "SAP") {
|
|
498
|
+
mapEntry("returnType", DATA_RETURN_TYPES);
|
|
499
|
+
}
|
|
500
|
+
return normalized;
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
503
|
+
* Ticket 115 — READ-face credential masking. A RESTFUL service config's
|
|
504
|
+
* `auth.parameterlist[].value` carries cleartext credentials (basic
|
|
505
|
+
* password, oauth client_secret…); handoff §5 says method-config secrets
|
|
506
|
+
* never echo. Applied ONLY to readback projections (service.get /
|
|
507
|
+
* method.get / method update readback): the VALUES become "***" while the
|
|
508
|
+
* code/name structure stays readable, so ticket 94's shape-readback
|
|
509
|
+
* semantics survive. NEVER apply this on a write/echo path — the update
|
|
510
|
+
* echo must carry the original config verbatim or the mask would be
|
|
511
|
+
* persisted over the stored credential.
|
|
512
|
+
*/
|
|
513
|
+
export function maskServiceConfigSecrets(config) {
|
|
514
|
+
if (!isRecord(config))
|
|
515
|
+
return config;
|
|
516
|
+
const auth = config.auth;
|
|
517
|
+
if (!isRecord(auth) || !Array.isArray(auth.parameterlist))
|
|
518
|
+
return config;
|
|
519
|
+
const maskedParameterlist = auth.parameterlist.map((entry) => {
|
|
520
|
+
if (!isRecord(entry))
|
|
521
|
+
return entry;
|
|
522
|
+
return { ...entry, ...(entry.value !== undefined ? { value: "***" } : {}) };
|
|
523
|
+
});
|
|
524
|
+
return { ...config, auth: { ...auth, parameterlist: maskedParameterlist } };
|
|
525
|
+
}
|
|
526
|
+
/** Decode a manifest.json payload; anything off-shape is an error (fail closed). */
|
|
527
|
+
export function parseIeManifest(value) {
|
|
528
|
+
const record = value;
|
|
529
|
+
if (!record || typeof record !== "object" || Array.isArray(record))
|
|
530
|
+
throw new Error("manifest.json 不是对象");
|
|
531
|
+
const servicesRaw = record.services;
|
|
532
|
+
if (!Array.isArray(servicesRaw) || servicesRaw.length === 0)
|
|
533
|
+
throw new Error("manifest.services 缺失或为空");
|
|
534
|
+
const services = [];
|
|
535
|
+
const methodsByService = {};
|
|
536
|
+
for (const entry of servicesRaw) {
|
|
537
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry))
|
|
538
|
+
throw new Error("manifest.services 存在非对象元素");
|
|
539
|
+
const row = entry;
|
|
540
|
+
if (typeof row.code !== "string" || !row.code.trim())
|
|
541
|
+
throw new Error("manifest.services 存在为空的集成服务编码");
|
|
542
|
+
services.push({
|
|
543
|
+
code: row.code,
|
|
544
|
+
name: row.name,
|
|
545
|
+
adapterCode: row.adapterCode,
|
|
546
|
+
configJson: typeof row.configJson === "string" ? row.configJson : undefined,
|
|
547
|
+
raw: row,
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
const methodsMap = record.methodsMap;
|
|
551
|
+
if (methodsMap !== undefined) {
|
|
552
|
+
if (!methodsMap || typeof methodsMap !== "object" || Array.isArray(methodsMap))
|
|
553
|
+
throw new Error("manifest.methodsMap 不是对象");
|
|
554
|
+
for (const [serviceCode, list] of Object.entries(methodsMap)) {
|
|
555
|
+
if (!Array.isArray(list))
|
|
556
|
+
throw new Error(`manifest.methodsMap[${serviceCode}] 不是数组`);
|
|
557
|
+
methodsByService[serviceCode] = list.map((method) => {
|
|
558
|
+
if (!method || typeof method !== "object" || Array.isArray(method))
|
|
559
|
+
throw new Error(`manifest.methodsMap[${serviceCode}] 存在非对象元素`);
|
|
560
|
+
const row = method;
|
|
561
|
+
if (typeof row.code !== "string" || !row.code.trim())
|
|
562
|
+
throw new Error(`manifest.methodsMap[${serviceCode}] 存在为空的方法编码`);
|
|
563
|
+
return { code: row.code, raw: row };
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
return { systemVersion: typeof record.systemVersion === "string" ? record.systemVersion : undefined, services, methodsByService };
|
|
568
|
+
}
|
|
569
|
+
/** Secret-free package summary for tool responses (codes/faces only, never config bodies). */
|
|
570
|
+
export function iePackageSummary(manifest) {
|
|
571
|
+
return {
|
|
572
|
+
systemVersion: manifest.systemVersion,
|
|
573
|
+
services: manifest.services.map((service) => ({
|
|
574
|
+
code: service.code,
|
|
575
|
+
name: service.name,
|
|
576
|
+
adapterCode: service.adapterCode,
|
|
577
|
+
methods: (manifest.methodsByService[service.code] ?? []).map((method) => method.code),
|
|
578
|
+
})),
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
//# sourceMappingURL=bizservice-config.js.map
|