@actiondock/core 2.18.0 → 2.20.0
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/client/config.d.ts +17 -14
- package/dist/client/config.js +112 -14
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.js +1 -1
- package/dist/client/runs.d.ts +9 -0
- package/dist/client/runs.js +7 -0
- package/dist/errors.d.ts +3 -1
- package/dist/errors.js +2 -0
- package/dist/execution/service.js +8 -5
- package/dist/execution/types.d.ts +3 -3
- package/dist/host/host.d.ts +9 -1
- package/dist/host/host.js +266 -45
- package/dist/host/types.d.ts +3 -0
- package/dist/index.d.ts +4 -3
- package/dist/index.js +1 -1
- package/dist/input/config-override.d.ts +5 -0
- package/dist/input/config-override.js +23 -0
- package/dist/input/input-resolver.d.ts +24 -7
- package/dist/input/input-resolver.js +73 -5
- package/dist/invocation/bindings.d.ts +53 -0
- package/dist/invocation/bindings.js +248 -0
- package/dist/invocation/policy.d.ts +5 -1
- package/dist/invocation/policy.js +16 -9
- package/dist/invocation/remote.d.ts +40 -0
- package/dist/invocation/remote.js +489 -0
- package/dist/invocation/types.d.ts +2 -0
- package/dist/invocation/types.js +1 -0
- package/dist/package/index.d.ts +1 -1
- package/dist/package/runtime.js +105 -15
- package/dist/platform/types.d.ts +48 -1
- package/dist/project/config-declarations.d.ts +61 -0
- package/dist/project/config-declarations.js +297 -0
- package/dist/project/index.d.ts +5 -2
- package/dist/project/index.js +5 -2
- package/dist/project/init.js +2 -2
- package/dist/project/loader.js +4 -0
- package/dist/project/manifest.js +10 -0
- package/dist/project/types.d.ts +30 -0
- package/dist/runtime/context.d.ts +6 -3
- package/dist/runtime/context.js +60 -13
- package/dist/runtime/env.js +15 -1
- package/dist/runtime/standalone-commands.d.ts +2 -2
- package/dist/runtime/standalone-commands.js +113 -47
- package/dist/runtime/standalone-help.d.ts +1 -0
- package/dist/runtime/standalone-help.js +15 -9
- package/dist/runtime/standalone.js +2 -2
- package/dist/server/policy.d.ts +24 -107
- package/dist/server/policy.js +134 -291
- package/dist/server/routes/actions.js +10 -8
- package/dist/server/routes/common.d.ts +5 -20
- package/dist/server/routes/common.js +22 -58
- package/dist/server/routes/config.js +65 -7
- package/dist/server/routes/doctor.d.ts +1 -0
- package/dist/server/routes/doctor.js +4 -3
- package/dist/server/routes/info.js +100 -13
- package/dist/server/routes/playbooks.js +1 -3
- package/dist/server/routes/runs.js +112 -18
- package/dist/server/server.d.ts +2 -1
- package/dist/server/server.js +38 -55
- package/dist/server/types.d.ts +6 -6
- package/dist/service/factory.js +1 -0
- package/dist/service/remote.js +28 -29
- package/dist/service/types.d.ts +34 -5
- package/dist/service/types.js +8 -0
- package/dist/storage/mask.d.ts +33 -6
- package/dist/storage/mask.js +298 -16
- package/dist/storage/sqlite-runs.d.ts +5 -17
- package/dist/storage/sqlite-runs.js +127 -5
- package/dist/storage/sqlite.d.ts +5 -17
- package/dist/storage/sqlite.js +2 -2
- package/dist/storage/types.d.ts +31 -16
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -2
package/dist/storage/mask.d.ts
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
|
+
import type { JsonValue } from "@actiondock/sdk";
|
|
1
2
|
import type { ConfigItemDefinition } from "../project/types.js";
|
|
2
3
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* 判定逻辑:
|
|
6
|
-
* 严格依据配置项元数据属性判定:若声明了 `secret: true`,直接判定为敏感数据;否则不视为敏感数据。
|
|
7
|
-
* 不进行基于正则或键名的自动探测,由开发者显式配置驱动。
|
|
4
|
+
* 检查指定配置项是否为敏感数据(顶层 secret: true 或模式中嵌套 secret: true)。
|
|
5
|
+
* 不进行基于正则或键名的自动探测,调用方若涉及模板应先解析具体声明项。
|
|
8
6
|
*
|
|
9
7
|
* @param _key 配置键名(保留入参以兼容契约)
|
|
10
8
|
* @param declaredItem 项目中声明的配置元数据(可选)
|
|
@@ -17,9 +15,38 @@ export declare function isSecretConfigKey(_key?: string, declaredItem?: ConfigIt
|
|
|
17
15
|
* @returns 掩码脱敏后的安全展示字符串
|
|
18
16
|
*/
|
|
19
17
|
export declare function maskSecretValue(value: unknown): string;
|
|
18
|
+
/**
|
|
19
|
+
* 配置值脱敏安全视图产物。
|
|
20
|
+
*/
|
|
21
|
+
export interface RedactResult {
|
|
22
|
+
/** 脱敏后的数据值(顶层敏感时为 undefined) */
|
|
23
|
+
value: JsonValue | undefined;
|
|
24
|
+
/** 是否包含敏感信息或发生了保守脱敏 */
|
|
25
|
+
secret: boolean;
|
|
26
|
+
/** 发生脱敏替换的字段路径集合(标准 JSON Pointer 格式) */
|
|
27
|
+
redactedPaths: string[];
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* 依据单项配置声明对配置值执行纯函数脱敏处理。
|
|
31
|
+
*
|
|
32
|
+
* @param value 待脱敏的配置原始数据值
|
|
33
|
+
* @param item 对应的单项配置定义元数据
|
|
34
|
+
*/
|
|
35
|
+
export declare function redactConfigValue(value: unknown, item?: ConfigItemDefinition): RedactResult;
|
|
36
|
+
/** 根据可信隐藏路径应用幂等掩码,绝不把远端提供的路径本身当成已脱敏的证据。 */
|
|
37
|
+
export declare function redactConfigValueAtPaths(value: unknown, paths: string[]): RedactResult;
|
|
38
|
+
/**
|
|
39
|
+
* 依据跨包多重声明对配置值执行合并脱敏处理(取脱敏路径并集,祖先路径覆盖子路径)。
|
|
40
|
+
*
|
|
41
|
+
* @param value 待脱敏的配置原始数据值
|
|
42
|
+
* @param items 各相关包分别解析得到的配置声明集合
|
|
43
|
+
*/
|
|
44
|
+
export declare function redactConfigValueForDeclarations(value: unknown, items: ConfigItemDefinition[]): RedactResult;
|
|
20
45
|
/**
|
|
21
46
|
* 对配置定义字典进行脱敏处理:
|
|
22
|
-
*
|
|
47
|
+
* - 针对外层 secret: true 或嵌套包含敏感字段的项,剥离其 default 默认值,避免敏感默认值泄露或误用于执行;
|
|
48
|
+
* - 针对模式中敏感节点的 enum 约束列表进行清洗,杜绝约束文本泄露秘密;
|
|
49
|
+
* - 仅返回安全副本,严禁修改清单原始数据。
|
|
23
50
|
*
|
|
24
51
|
* @param config 声明的配置定义字典
|
|
25
52
|
*/
|
package/dist/storage/mask.js
CHANGED
|
@@ -1,15 +1,45 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
* 递归检查 Schema 是否包含任何标记为 secret: true 的节点。
|
|
3
|
+
*/
|
|
4
|
+
function schemaHasSecret(schema) {
|
|
5
|
+
if (!schema || typeof schema !== "object") {
|
|
6
|
+
return false;
|
|
7
|
+
}
|
|
8
|
+
if (schema.secret === true) {
|
|
9
|
+
return true;
|
|
10
|
+
}
|
|
11
|
+
if (schema.properties) {
|
|
12
|
+
for (const propSchema of Object.values(schema.properties)) {
|
|
13
|
+
if (schemaHasSecret(propSchema)) {
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
if (schema.items && schemaHasSecret(schema.items)) {
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
if (schema.additionalProperties &&
|
|
22
|
+
typeof schema.additionalProperties === "object" &&
|
|
23
|
+
schemaHasSecret(schema.additionalProperties)) {
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* 检查指定配置项是否为敏感数据(顶层 secret: true 或模式中嵌套 secret: true)。
|
|
30
|
+
* 不进行基于正则或键名的自动探测,调用方若涉及模板应先解析具体声明项。
|
|
7
31
|
*
|
|
8
32
|
* @param _key 配置键名(保留入参以兼容契约)
|
|
9
33
|
* @param declaredItem 项目中声明的配置元数据(可选)
|
|
10
34
|
*/
|
|
11
35
|
export function isSecretConfigKey(_key, declaredItem) {
|
|
12
|
-
|
|
36
|
+
if (declaredItem?.secret === true) {
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
if (declaredItem?.schema && schemaHasSecret(declaredItem.schema)) {
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
return false;
|
|
13
43
|
}
|
|
14
44
|
/**
|
|
15
45
|
* 对敏感数据的值进行掩码脱敏处理(返回 "********")。
|
|
@@ -24,25 +54,277 @@ export function maskSecretValue(value) {
|
|
|
24
54
|
return "null";
|
|
25
55
|
return "********";
|
|
26
56
|
}
|
|
57
|
+
function escapePointerToken(token) {
|
|
58
|
+
return token.replace(/~/g, "~0").replace(/\//g, "~1");
|
|
59
|
+
}
|
|
60
|
+
function unescapePointerToken(token) {
|
|
61
|
+
return token.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
62
|
+
}
|
|
63
|
+
function isTypeMatch(val, expectedType) {
|
|
64
|
+
if (!expectedType)
|
|
65
|
+
return true;
|
|
66
|
+
switch (expectedType) {
|
|
67
|
+
case "string":
|
|
68
|
+
return typeof val === "string";
|
|
69
|
+
case "number":
|
|
70
|
+
return typeof val === "number" && Number.isFinite(val);
|
|
71
|
+
case "integer":
|
|
72
|
+
return typeof val === "number" && Number.isInteger(val);
|
|
73
|
+
case "boolean":
|
|
74
|
+
return typeof val === "boolean";
|
|
75
|
+
case "array":
|
|
76
|
+
return Array.isArray(val);
|
|
77
|
+
case "object":
|
|
78
|
+
return val !== null && typeof val === "object" && !Array.isArray(val);
|
|
79
|
+
default:
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function redactNode(val, schema, path) {
|
|
84
|
+
// 模式显式标记敏感节点:直接掩码并终止下钻
|
|
85
|
+
if (schema?.secret === true) {
|
|
86
|
+
return {
|
|
87
|
+
value: "********",
|
|
88
|
+
redacted: true,
|
|
89
|
+
paths: [path],
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
// 即使省略 type,结构关键字也不能让标量绕过子字段的秘密保护。
|
|
93
|
+
const objectShape = schema?.properties !== undefined || schema?.additionalProperties !== undefined || schema?.required !== undefined;
|
|
94
|
+
const arrayShape = schema?.items !== undefined;
|
|
95
|
+
const incompatibleShape = !schema?.type && (objectShape || arrayShape) &&
|
|
96
|
+
!(objectShape && val !== null && typeof val === "object" && !Array.isArray(val)) &&
|
|
97
|
+
!(arrayShape && Array.isArray(val));
|
|
98
|
+
// 结构与声明类型不符:保守隐藏该节点
|
|
99
|
+
if (incompatibleShape || (schema?.type && !isTypeMatch(val, schema.type))) {
|
|
100
|
+
return {
|
|
101
|
+
value: "********",
|
|
102
|
+
redacted: true,
|
|
103
|
+
paths: [path],
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
// 数组节点处理
|
|
107
|
+
if (Array.isArray(val)) {
|
|
108
|
+
const newArr = [];
|
|
109
|
+
const childPaths = [];
|
|
110
|
+
let childRedacted = false;
|
|
111
|
+
if (schema?.items && typeof schema.items === "object") {
|
|
112
|
+
for (let i = 0; i < val.length; i++) {
|
|
113
|
+
const elemPath = path === "" ? `/${i}` : `${path}/${i}`;
|
|
114
|
+
const res = redactNode(val[i], schema.items, elemPath);
|
|
115
|
+
newArr.push(res.value);
|
|
116
|
+
if (res.redacted)
|
|
117
|
+
childRedacted = true;
|
|
118
|
+
childPaths.push(...res.paths);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
// 数组按 items 遍历,无对应声明则元素隐藏
|
|
123
|
+
for (let i = 0; i < val.length; i++) {
|
|
124
|
+
const elemPath = path === "" ? `/${i}` : `${path}/${i}`;
|
|
125
|
+
newArr.push("********");
|
|
126
|
+
childPaths.push(elemPath);
|
|
127
|
+
}
|
|
128
|
+
if (val.length > 0) {
|
|
129
|
+
childRedacted = true;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
value: newArr,
|
|
134
|
+
redacted: childRedacted,
|
|
135
|
+
paths: childPaths,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
// 普通对象节点处理
|
|
139
|
+
if (val !== null && typeof val === "object") {
|
|
140
|
+
const entries = [];
|
|
141
|
+
const childPaths = [];
|
|
142
|
+
let childRedacted = false;
|
|
143
|
+
for (const k of Object.keys(val)) {
|
|
144
|
+
const childVal = val[k];
|
|
145
|
+
const token = escapePointerToken(k);
|
|
146
|
+
const propPath = path === "" ? `/${token}` : `${path}/${token}`;
|
|
147
|
+
if (schema?.properties && Object.prototype.hasOwnProperty.call(schema.properties, k)) {
|
|
148
|
+
const res = redactNode(childVal, schema.properties[k], propPath);
|
|
149
|
+
entries.push([k, res.value]);
|
|
150
|
+
if (res.redacted)
|
|
151
|
+
childRedacted = true;
|
|
152
|
+
childPaths.push(...res.paths);
|
|
153
|
+
}
|
|
154
|
+
else if (schema?.additionalProperties && typeof schema.additionalProperties === "object") {
|
|
155
|
+
// additionalProperties 声明为模式时继续递归
|
|
156
|
+
const res = redactNode(childVal, schema.additionalProperties, propPath);
|
|
157
|
+
entries.push([k, res.value]);
|
|
158
|
+
if (res.redacted)
|
|
159
|
+
childRedacted = true;
|
|
160
|
+
childPaths.push(...res.paths);
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
// 对象未知属性值隐藏
|
|
164
|
+
entries.push([k, "********"]);
|
|
165
|
+
childRedacted = true;
|
|
166
|
+
childPaths.push(propPath);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return {
|
|
170
|
+
value: Object.fromEntries(entries),
|
|
171
|
+
redacted: childRedacted,
|
|
172
|
+
paths: childPaths,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
// 标量基元节点
|
|
176
|
+
return {
|
|
177
|
+
value: val,
|
|
178
|
+
redacted: false,
|
|
179
|
+
paths: [],
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* 依据单项配置声明对配置值执行纯函数脱敏处理。
|
|
184
|
+
*
|
|
185
|
+
* @param value 待脱敏的配置原始数据值
|
|
186
|
+
* @param item 对应的单项配置定义元数据
|
|
187
|
+
*/
|
|
188
|
+
export function redactConfigValue(value, item) {
|
|
189
|
+
// 外层 secret: true 判定
|
|
190
|
+
if (item?.secret === true) {
|
|
191
|
+
if (value === undefined) {
|
|
192
|
+
return { value: undefined, secret: true, redactedPaths: [] };
|
|
193
|
+
}
|
|
194
|
+
return { value: undefined, secret: true, redactedPaths: [""] };
|
|
195
|
+
}
|
|
196
|
+
// 未声明 schema 且非顶层 secret 维持旧行为
|
|
197
|
+
if (!item?.schema) {
|
|
198
|
+
return { value: value, secret: false, redactedPaths: [] };
|
|
199
|
+
}
|
|
200
|
+
// 数据值未配置时,不生成虚假脱敏路径,但反映声明敏感性
|
|
201
|
+
if (value === undefined) {
|
|
202
|
+
return {
|
|
203
|
+
value: undefined,
|
|
204
|
+
secret: schemaHasSecret(item.schema),
|
|
205
|
+
redactedPaths: [],
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
const res = redactNode(value, item.schema, "");
|
|
209
|
+
const declarationHasSecret = schemaHasSecret(item.schema);
|
|
210
|
+
const secret = declarationHasSecret || res.redacted;
|
|
211
|
+
return {
|
|
212
|
+
value: res.value,
|
|
213
|
+
secret,
|
|
214
|
+
redactedPaths: res.paths,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
function isAncestorPath(parent, child) {
|
|
218
|
+
return parent === "" ? child !== "" : child.startsWith(parent + "/");
|
|
219
|
+
}
|
|
220
|
+
/** 根据可信隐藏路径应用幂等掩码,绝不把远端提供的路径本身当成已脱敏的证据。 */
|
|
221
|
+
export function redactConfigValueAtPaths(value, paths) {
|
|
222
|
+
const hideRoot = () => ({ value: undefined, secret: true, redactedPaths: [""] });
|
|
223
|
+
if (paths.some((path) => typeof path !== "string" || (path !== "" && (!path.startsWith("/") || /~(?![01])/.test(path))))) {
|
|
224
|
+
return hideRoot();
|
|
225
|
+
}
|
|
226
|
+
if (paths.includes(""))
|
|
227
|
+
return hideRoot();
|
|
228
|
+
const unique = [...new Set(paths)];
|
|
229
|
+
const minimal = unique.filter((path) => !unique.some((parent) => parent !== path && isAncestorPath(parent, path))).sort();
|
|
230
|
+
const copy = structuredClone(value);
|
|
231
|
+
for (const path of minimal) {
|
|
232
|
+
const tokens = path.slice(1).split("/").map(unescapePointerToken);
|
|
233
|
+
let node = copy;
|
|
234
|
+
for (const token of tokens.slice(0, -1)) {
|
|
235
|
+
if (node === null || typeof node !== "object")
|
|
236
|
+
return hideRoot();
|
|
237
|
+
if (!Object.hasOwn(node, token)) {
|
|
238
|
+
node = undefined;
|
|
239
|
+
break;
|
|
240
|
+
}
|
|
241
|
+
node = node[token];
|
|
242
|
+
}
|
|
243
|
+
if (node === undefined)
|
|
244
|
+
continue;
|
|
245
|
+
if (node === null || typeof node !== "object")
|
|
246
|
+
return hideRoot();
|
|
247
|
+
const leaf = tokens[tokens.length - 1];
|
|
248
|
+
if (Object.hasOwn(node, leaf)) {
|
|
249
|
+
Object.defineProperty(node, leaf, { value: "********", enumerable: true, writable: true, configurable: true });
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return { value: copy, secret: minimal.length > 0, redactedPaths: minimal };
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* 依据跨包多重声明对配置值执行合并脱敏处理(取脱敏路径并集,祖先路径覆盖子路径)。
|
|
256
|
+
*
|
|
257
|
+
* @param value 待脱敏的配置原始数据值
|
|
258
|
+
* @param items 各相关包分别解析得到的配置声明集合
|
|
259
|
+
*/
|
|
260
|
+
export function redactConfigValueForDeclarations(value, items) {
|
|
261
|
+
if (!items || items.length === 0) {
|
|
262
|
+
return { value: value, secret: false, redactedPaths: [] };
|
|
263
|
+
}
|
|
264
|
+
// 任何声明要求整项敏感,直接隐藏整个根节点
|
|
265
|
+
if (items.some((item) => item.secret === true)) {
|
|
266
|
+
if (value === undefined) {
|
|
267
|
+
return { value: undefined, secret: true, redactedPaths: [] };
|
|
268
|
+
}
|
|
269
|
+
return { value: undefined, secret: true, redactedPaths: [""] };
|
|
270
|
+
}
|
|
271
|
+
if (value === undefined) {
|
|
272
|
+
const hasSecret = items.some((item) => isSecretConfigKey("", item));
|
|
273
|
+
return { value: undefined, secret: hasSecret, redactedPaths: [] };
|
|
274
|
+
}
|
|
275
|
+
let anySecret = false;
|
|
276
|
+
const allPaths = new Set();
|
|
277
|
+
for (const item of items) {
|
|
278
|
+
const res = redactConfigValue(value, item);
|
|
279
|
+
if (res.secret) {
|
|
280
|
+
anySecret = true;
|
|
281
|
+
}
|
|
282
|
+
for (const p of res.redactedPaths) {
|
|
283
|
+
allPaths.add(p);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
const merged = redactConfigValueAtPaths(value, [...allPaths]);
|
|
287
|
+
return { ...merged, secret: anySecret || merged.secret };
|
|
288
|
+
}
|
|
289
|
+
function sanitizeSchemaNode(schema, inheritedSecret = false) {
|
|
290
|
+
const copy = { ...schema };
|
|
291
|
+
const hidden = inheritedSecret || schema.secret === true;
|
|
292
|
+
// 祖先枚举也可能携带子字段秘密,未知结构同样不可借约束回显。
|
|
293
|
+
if (hidden || schemaHasSecret(schema) || schema.enum?.some((value) => redactNode(value, schema, "").redacted)) {
|
|
294
|
+
delete copy.enum;
|
|
295
|
+
}
|
|
296
|
+
if (copy.properties) {
|
|
297
|
+
copy.properties = Object.fromEntries(Object.entries(copy.properties).map(([key, child]) => [key, sanitizeSchemaNode(child, hidden)]));
|
|
298
|
+
}
|
|
299
|
+
if (copy.additionalProperties && typeof copy.additionalProperties === "object") {
|
|
300
|
+
copy.additionalProperties = sanitizeSchemaNode(copy.additionalProperties, hidden);
|
|
301
|
+
}
|
|
302
|
+
if (copy.items && typeof copy.items === "object") {
|
|
303
|
+
copy.items = sanitizeSchemaNode(copy.items, hidden);
|
|
304
|
+
}
|
|
305
|
+
return copy;
|
|
306
|
+
}
|
|
27
307
|
/**
|
|
28
308
|
* 对配置定义字典进行脱敏处理:
|
|
29
|
-
*
|
|
309
|
+
* - 针对外层 secret: true 或嵌套包含敏感字段的项,剥离其 default 默认值,避免敏感默认值泄露或误用于执行;
|
|
310
|
+
* - 针对模式中敏感节点的 enum 约束列表进行清洗,杜绝约束文本泄露秘密;
|
|
311
|
+
* - 仅返回安全副本,严禁修改清单原始数据。
|
|
30
312
|
*
|
|
31
313
|
* @param config 声明的配置定义字典
|
|
32
314
|
*/
|
|
33
315
|
export function sanitizeConfigDefinitions(config) {
|
|
34
316
|
if (!config)
|
|
35
317
|
return config;
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
318
|
+
return Object.fromEntries(Object.entries(config).map(([key, item]) => {
|
|
319
|
+
if (!item)
|
|
320
|
+
return [key, item];
|
|
321
|
+
const copy = { ...item };
|
|
322
|
+
if (isSecretConfigKey(key, item) || redactConfigValue(item.default, item).redactedPaths.length > 0) {
|
|
40
323
|
delete copy.default;
|
|
41
|
-
result[key] = copy;
|
|
42
324
|
}
|
|
43
|
-
|
|
44
|
-
|
|
325
|
+
if (copy.schema) {
|
|
326
|
+
copy.schema = sanitizeSchemaNode(copy.schema, copy.secret === true);
|
|
45
327
|
}
|
|
46
|
-
|
|
47
|
-
|
|
328
|
+
return [key, copy];
|
|
329
|
+
}));
|
|
48
330
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Logger, RuntimeError, RunRecord } from "@actiondock/sdk";
|
|
2
|
-
import type { SqliteDriver, TerminalRunStatus, RunsRetentionPolicy, IdempotencyRecord, IdempotencyCheckResult } from "./types.js";
|
|
2
|
+
import type { SqliteDriver, TerminalRunStatus, RunsRetentionPolicy, IdempotencyRecord, IdempotencyCheckResult, RunsQueryFilterOptions } from "./types.js";
|
|
3
3
|
import type { Clock } from "./clock.js";
|
|
4
4
|
import { type ProcessLivenessProbe } from "./run-liveness.js";
|
|
5
5
|
export declare class SqliteRunsStore {
|
|
@@ -52,28 +52,16 @@ export declare class SqliteRunsStore {
|
|
|
52
52
|
* 查询单条运行记录并补齐幂等请求标识关联(详情路径专用)。
|
|
53
53
|
*/
|
|
54
54
|
getRunWithRequestId(id: string, isClosed: boolean): RunRecord | null;
|
|
55
|
-
listRuns(options?:
|
|
56
|
-
actionId?: string;
|
|
57
|
-
status?: string;
|
|
58
|
-
limit?: number;
|
|
59
|
-
offset?: number;
|
|
60
|
-
}): RunRecord[];
|
|
55
|
+
listRuns(options?: RunsQueryFilterOptions): RunRecord[];
|
|
61
56
|
/**
|
|
62
57
|
* 统计符合过滤条件的真实运行记录总数。
|
|
63
58
|
* 支持按 actionId、status 以及包含 requestIds 的反查场景统计总数。
|
|
64
59
|
*/
|
|
65
|
-
countRuns(options?:
|
|
66
|
-
actionId?: string;
|
|
67
|
-
status?: string;
|
|
68
|
-
requestIds?: string[];
|
|
69
|
-
}, isClosed?: boolean): number;
|
|
60
|
+
countRuns(options?: RunsQueryFilterOptions, isClosed?: boolean): number;
|
|
70
61
|
/**
|
|
71
62
|
* 按客户端幂等请求标识反查关联运行记录总数。
|
|
72
63
|
*/
|
|
73
|
-
countRunsByRequestIds(isClosed: boolean, requestIds: string[], options?:
|
|
74
|
-
actionId?: string;
|
|
75
|
-
status?: string;
|
|
76
|
-
}): number;
|
|
64
|
+
countRunsByRequestIds(isClosed: boolean, requestIds: string[], options?: RunsQueryFilterOptions): number;
|
|
77
65
|
/**
|
|
78
66
|
* 构造 IN (…) 占位符列表。
|
|
79
67
|
*/
|
|
@@ -85,7 +73,7 @@ export declare class SqliteRunsStore {
|
|
|
85
73
|
* 当前包,保证多包共库(全局库)时不越界读取其他包的运行记录。
|
|
86
74
|
* 不按 owner_id 过滤:写入方与查询方分属不同宿主进程,ownerId 必然不同。
|
|
87
75
|
*/
|
|
88
|
-
listRunsByRequestIds(isClosed: boolean, requestIds: string[]): RunRecord[];
|
|
76
|
+
listRunsByRequestIds(isClosed: boolean, requestIds: string[], options?: RunsQueryFilterOptions): RunRecord[];
|
|
89
77
|
/**
|
|
90
78
|
* 反查当前包范围内运行记录关联的幂等请求标识。
|
|
91
79
|
*
|
|
@@ -244,6 +244,30 @@ export class SqliteRunsStore {
|
|
|
244
244
|
sql += " AND status = ?";
|
|
245
245
|
params.push(options.status);
|
|
246
246
|
}
|
|
247
|
+
if (options.channel) {
|
|
248
|
+
sql += " AND json_extract(origin_json, '$.channel') = ?";
|
|
249
|
+
params.push(options.channel);
|
|
250
|
+
}
|
|
251
|
+
if (options.view) {
|
|
252
|
+
sql += " AND json_extract(origin_json, '$.viewName') = ?";
|
|
253
|
+
params.push(options.view);
|
|
254
|
+
}
|
|
255
|
+
if (options.since) {
|
|
256
|
+
sql += " AND started_at >= ?";
|
|
257
|
+
params.push(options.since);
|
|
258
|
+
}
|
|
259
|
+
if (options.until) {
|
|
260
|
+
sql += " AND started_at <= ?";
|
|
261
|
+
params.push(options.until);
|
|
262
|
+
}
|
|
263
|
+
if (options.rootRunId) {
|
|
264
|
+
sql += " AND root_run_id = ?";
|
|
265
|
+
params.push(options.rootRunId);
|
|
266
|
+
}
|
|
267
|
+
if (options.parentRunId) {
|
|
268
|
+
sql += " AND parent_run_id = ?";
|
|
269
|
+
params.push(options.parentRunId);
|
|
270
|
+
}
|
|
247
271
|
sql += " ORDER BY started_at DESC LIMIT ?";
|
|
248
272
|
params.push(limit);
|
|
249
273
|
if (typeof options.offset === "number" && options.offset > 0) {
|
|
@@ -281,6 +305,30 @@ export class SqliteRunsStore {
|
|
|
281
305
|
sql += " AND runs.status = ?";
|
|
282
306
|
params.push(options.status);
|
|
283
307
|
}
|
|
308
|
+
if (options.channel) {
|
|
309
|
+
sql += " AND json_extract(runs.origin_json, '$.channel') = ?";
|
|
310
|
+
params.push(options.channel);
|
|
311
|
+
}
|
|
312
|
+
if (options.view) {
|
|
313
|
+
sql += " AND json_extract(runs.origin_json, '$.viewName') = ?";
|
|
314
|
+
params.push(options.view);
|
|
315
|
+
}
|
|
316
|
+
if (options.since) {
|
|
317
|
+
sql += " AND runs.started_at >= ?";
|
|
318
|
+
params.push(options.since);
|
|
319
|
+
}
|
|
320
|
+
if (options.until) {
|
|
321
|
+
sql += " AND runs.started_at <= ?";
|
|
322
|
+
params.push(options.until);
|
|
323
|
+
}
|
|
324
|
+
if (options.rootRunId) {
|
|
325
|
+
sql += " AND runs.root_run_id = ?";
|
|
326
|
+
params.push(options.rootRunId);
|
|
327
|
+
}
|
|
328
|
+
if (options.parentRunId) {
|
|
329
|
+
sql += " AND runs.parent_run_id = ?";
|
|
330
|
+
params.push(options.parentRunId);
|
|
331
|
+
}
|
|
284
332
|
const stmt = this.getStatement(sql);
|
|
285
333
|
const row = stmt.get(...params);
|
|
286
334
|
return row?.count ?? 0;
|
|
@@ -295,6 +343,30 @@ export class SqliteRunsStore {
|
|
|
295
343
|
sql += " AND status = ?";
|
|
296
344
|
params.push(options.status);
|
|
297
345
|
}
|
|
346
|
+
if (options.channel) {
|
|
347
|
+
sql += " AND json_extract(origin_json, '$.channel') = ?";
|
|
348
|
+
params.push(options.channel);
|
|
349
|
+
}
|
|
350
|
+
if (options.view) {
|
|
351
|
+
sql += " AND json_extract(origin_json, '$.viewName') = ?";
|
|
352
|
+
params.push(options.view);
|
|
353
|
+
}
|
|
354
|
+
if (options.since) {
|
|
355
|
+
sql += " AND started_at >= ?";
|
|
356
|
+
params.push(options.since);
|
|
357
|
+
}
|
|
358
|
+
if (options.until) {
|
|
359
|
+
sql += " AND started_at <= ?";
|
|
360
|
+
params.push(options.until);
|
|
361
|
+
}
|
|
362
|
+
if (options.rootRunId) {
|
|
363
|
+
sql += " AND root_run_id = ?";
|
|
364
|
+
params.push(options.rootRunId);
|
|
365
|
+
}
|
|
366
|
+
if (options.parentRunId) {
|
|
367
|
+
sql += " AND parent_run_id = ?";
|
|
368
|
+
params.push(options.parentRunId);
|
|
369
|
+
}
|
|
298
370
|
const stmt = this.getStatement(sql);
|
|
299
371
|
const row = stmt.get(...params);
|
|
300
372
|
return row?.count ?? 0;
|
|
@@ -318,23 +390,56 @@ export class SqliteRunsStore {
|
|
|
318
390
|
* 当前包,保证多包共库(全局库)时不越界读取其他包的运行记录。
|
|
319
391
|
* 不按 owner_id 过滤:写入方与查询方分属不同宿主进程,ownerId 必然不同。
|
|
320
392
|
*/
|
|
321
|
-
listRunsByRequestIds(isClosed, requestIds) {
|
|
393
|
+
listRunsByRequestIds(isClosed, requestIds, options = {}) {
|
|
322
394
|
if (isClosed || requestIds.length === 0)
|
|
323
395
|
return [];
|
|
324
396
|
const uniqueIds = Array.from(new Set(requestIds.filter((id) => typeof id === "string" && id.length > 0)));
|
|
325
397
|
if (uniqueIds.length === 0)
|
|
326
398
|
return [];
|
|
327
|
-
|
|
399
|
+
let sql = `
|
|
328
400
|
SELECT runs.*, ik.request_id AS request_id
|
|
329
401
|
FROM runs
|
|
330
402
|
INNER JOIN idempotency_keys AS ik ON ik.run_id = runs.id
|
|
331
403
|
WHERE runs.package_id = ?
|
|
332
404
|
AND ik.request_id IN (${SqliteRunsStore.placeholders(uniqueIds.length)})
|
|
333
|
-
ORDER BY runs.started_at DESC
|
|
334
405
|
`;
|
|
406
|
+
const params = [this.packageId, ...uniqueIds];
|
|
407
|
+
if (options.actionId) {
|
|
408
|
+
sql += " AND runs.action_id = ?";
|
|
409
|
+
params.push(options.actionId);
|
|
410
|
+
}
|
|
411
|
+
if (options.status) {
|
|
412
|
+
sql += " AND runs.status = ?";
|
|
413
|
+
params.push(options.status);
|
|
414
|
+
}
|
|
415
|
+
if (options.channel) {
|
|
416
|
+
sql += " AND json_extract(runs.origin_json, '$.channel') = ?";
|
|
417
|
+
params.push(options.channel);
|
|
418
|
+
}
|
|
419
|
+
if (options.view) {
|
|
420
|
+
sql += " AND json_extract(runs.origin_json, '$.viewName') = ?";
|
|
421
|
+
params.push(options.view);
|
|
422
|
+
}
|
|
423
|
+
if (options.since) {
|
|
424
|
+
sql += " AND runs.started_at >= ?";
|
|
425
|
+
params.push(options.since);
|
|
426
|
+
}
|
|
427
|
+
if (options.until) {
|
|
428
|
+
sql += " AND runs.started_at <= ?";
|
|
429
|
+
params.push(options.until);
|
|
430
|
+
}
|
|
431
|
+
if (options.rootRunId) {
|
|
432
|
+
sql += " AND runs.root_run_id = ?";
|
|
433
|
+
params.push(options.rootRunId);
|
|
434
|
+
}
|
|
435
|
+
if (options.parentRunId) {
|
|
436
|
+
sql += " AND runs.parent_run_id = ?";
|
|
437
|
+
params.push(options.parentRunId);
|
|
438
|
+
}
|
|
439
|
+
sql += " ORDER BY runs.started_at DESC";
|
|
335
440
|
const stmt = this.getStatement(sql);
|
|
336
|
-
const rows = stmt.all(
|
|
337
|
-
|
|
441
|
+
const rows = stmt.all(...params);
|
|
442
|
+
let records = rows.map((r) => this.mapRunRecord(r));
|
|
338
443
|
// requestId 来自联查列,直接补齐(mapRunRecord 无法从 runs 行还原)
|
|
339
444
|
const requestIdByRunId = new Map();
|
|
340
445
|
for (const row of rows) {
|
|
@@ -347,6 +452,23 @@ export class SqliteRunsStore {
|
|
|
347
452
|
if (requestId)
|
|
348
453
|
record.requestId = requestId;
|
|
349
454
|
}
|
|
455
|
+
// 内存过滤适配
|
|
456
|
+
if (options.actionId)
|
|
457
|
+
records = records.filter((r) => r.actionId === options.actionId);
|
|
458
|
+
if (options.status)
|
|
459
|
+
records = records.filter((r) => r.status === options.status);
|
|
460
|
+
if (options.channel)
|
|
461
|
+
records = records.filter((r) => r.origin?.channel === options.channel);
|
|
462
|
+
if (options.view)
|
|
463
|
+
records = records.filter((r) => r.origin?.viewName === options.view);
|
|
464
|
+
if (options.since)
|
|
465
|
+
records = records.filter((r) => r.startedAt >= options.since);
|
|
466
|
+
if (options.until)
|
|
467
|
+
records = records.filter((r) => r.startedAt <= options.until);
|
|
468
|
+
if (options.rootRunId)
|
|
469
|
+
records = records.filter((r) => r.rootRunId === options.rootRunId);
|
|
470
|
+
if (options.parentRunId)
|
|
471
|
+
records = records.filter((r) => r.parentRunId === options.parentRunId);
|
|
350
472
|
return records;
|
|
351
473
|
}
|
|
352
474
|
/**
|
package/dist/storage/sqlite.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { decodeStateKey, encodeStateKey, escapeStateSegment, unescapeStateSegment, type Logger, type RuntimeError, type RunRecord } from "@actiondock/sdk";
|
|
2
|
-
import { type IdempotencyCheckResult, type IdempotencyRecord, type RunsRetentionPolicy, type RuntimeStorage, type StateEntry, type StorageOptions, type TerminalRunStatus } from "./types.js";
|
|
2
|
+
import { type IdempotencyCheckResult, type IdempotencyRecord, type RunsQueryFilterOptions, type RunsRetentionPolicy, type RuntimeStorage, type StateEntry, type StorageOptions, type TerminalRunStatus } from "./types.js";
|
|
3
3
|
export { decodeStateKey, encodeStateKey, escapeStateSegment, unescapeStateSegment, };
|
|
4
4
|
export interface SqliteStorageOptions extends StorageOptions {
|
|
5
5
|
logger?: Logger;
|
|
@@ -48,17 +48,8 @@ export declare class SqliteRuntimeStorage implements RuntimeStorage {
|
|
|
48
48
|
createRun(record: RunRecord | any): void;
|
|
49
49
|
updateRun(id: string, status: TerminalRunStatus, output?: unknown, error?: RuntimeError, finishedAt?: string): void;
|
|
50
50
|
getRun(id: string): RunRecord | null;
|
|
51
|
-
listRuns(options?:
|
|
52
|
-
|
|
53
|
-
status?: string;
|
|
54
|
-
limit?: number;
|
|
55
|
-
offset?: number;
|
|
56
|
-
}): RunRecord[];
|
|
57
|
-
countRuns(options?: {
|
|
58
|
-
actionId?: string;
|
|
59
|
-
status?: string;
|
|
60
|
-
requestIds?: string[];
|
|
61
|
-
}): number;
|
|
51
|
+
listRuns(options?: RunsQueryFilterOptions): RunRecord[];
|
|
52
|
+
countRuns(options?: RunsQueryFilterOptions): number;
|
|
62
53
|
clearRuns(options?: {
|
|
63
54
|
actionId?: string;
|
|
64
55
|
status?: string;
|
|
@@ -69,11 +60,8 @@ export declare class SqliteRuntimeStorage implements RuntimeStorage {
|
|
|
69
60
|
touchRunHeartbeat(runIds: string[]): number;
|
|
70
61
|
checkAndRecordIdempotency(record: IdempotencyRecord): IdempotencyCheckResult;
|
|
71
62
|
getIdempotencyRecord(ownerId: string, actionRef: string, requestId: string): IdempotencyRecord | undefined;
|
|
72
|
-
listRunsByRequestIds(requestIds: string[]): RunRecord[];
|
|
73
|
-
countRunsByRequestIds(requestIds: string[], options?:
|
|
74
|
-
actionId?: string;
|
|
75
|
-
status?: string;
|
|
76
|
-
}): number;
|
|
63
|
+
listRunsByRequestIds(requestIds: string[], options?: RunsQueryFilterOptions): RunRecord[];
|
|
64
|
+
countRunsByRequestIds(requestIds: string[], options?: RunsQueryFilterOptions): number;
|
|
77
65
|
getRunRequestIds(runIds: string[]): Record<string, string>;
|
|
78
66
|
getRunWithRequestId(id: string): RunRecord | null;
|
|
79
67
|
appendEvent(eventType: string, payload: unknown): void;
|
package/dist/storage/sqlite.js
CHANGED
|
@@ -159,8 +159,8 @@ export class SqliteRuntimeStorage {
|
|
|
159
159
|
getIdempotencyRecord(ownerId, actionRef, requestId) {
|
|
160
160
|
return this.runsStore.getIdempotencyRecord(this.isClosed, ownerId, actionRef, requestId);
|
|
161
161
|
}
|
|
162
|
-
listRunsByRequestIds(requestIds) {
|
|
163
|
-
return this.runsStore.listRunsByRequestIds(this.isClosed, requestIds);
|
|
162
|
+
listRunsByRequestIds(requestIds, options = {}) {
|
|
163
|
+
return this.runsStore.listRunsByRequestIds(this.isClosed, requestIds, options);
|
|
164
164
|
}
|
|
165
165
|
countRunsByRequestIds(requestIds, options = {}) {
|
|
166
166
|
return this.runsStore.countRunsByRequestIds(this.isClosed, requestIds, options);
|