@actiondock/core 2.8.0 → 2.9.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.
@@ -1,13 +1,13 @@
1
1
  import { parseActionRef } from "../../catalog/resolve-action.js";
2
2
  import { filterByIntent } from "../../filter/index.js";
3
- import { ACTION_EXECUTION_ERROR, ACTION_NOT_FOUND, ACTION_START_FAILED, ACTION_TIMEOUT, ACTIONS_LIST_ERROR, IDEMPOTENCY_CONFLICT, INPUT_VALIDATION_FAILED, INVALID_JSON, PACKAGE_FORBIDDEN, PACKAGE_NOT_FOUND, REQUEST_TOO_LARGE, } from "../../errors.js";
3
+ import { ACTION_EXECUTION_ERROR, ACTION_FORBIDDEN, ACTION_NOT_FOUND, ACTION_START_FAILED, ACTION_TIMEOUT, ACTIONS_LIST_ERROR, IDEMPOTENCY_CONFLICT, INPUT_VALIDATION_FAILED, INVALID_JSON, PACKAGE_FORBIDDEN, PACKAGE_NOT_FOUND, REQUEST_TOO_LARGE, } from "../../errors.js";
4
4
  import { InvalidJsonError, readJsonBody, RequestTooLargeError } from "../body.js";
5
- import { getSubPath, jsonResponse } from "./common.js";
5
+ import { filterActionsByPolicy, getSubPath, isActionAllowedByPolicy, isPackageAllowedByPolicy, jsonResponse, } from "./common.js";
6
6
  /**
7
7
  * 处理 Action 相关的 HTTP 路由(列表、规范查询、同步执行与异步启动)。
8
8
  */
9
9
  export async function handleActionsRoutes(ctx) {
10
- const { req, url, pathname, corsHeaders, options, service } = ctx;
10
+ const { req, url, pathname, corsHeaders, options, service, activePolicy: policy } = ctx;
11
11
  const subpath = getSubPath(pathname);
12
12
  // 1. Actions List: GET /api/v2/actions, GET /actions
13
13
  if (subpath === "/actions" && req.method === "GET") {
@@ -25,9 +25,7 @@ export async function handleActionsRoutes(ctx) {
25
25
  if (targetPkg) {
26
26
  actions = actions.filter((a) => a.packageId === targetPkg || a.id.startsWith(`${targetPkg}/`));
27
27
  }
28
- if (options.packageAllowlist && options.packageAllowlist.length > 0) {
29
- actions = actions.filter((a) => a.packageId && options.packageAllowlist.includes(a.packageId));
30
- }
28
+ actions = filterActionsByPolicy(actions, policy);
31
29
  if (intent) {
32
30
  actions = filterByIntent(actions, intent, [(a) => a.id, (a) => a.description || "", (a) => a.packageId || ""], false);
33
31
  }
@@ -45,16 +43,23 @@ export async function handleActionsRoutes(ctx) {
45
43
  if (pkgActionShowMatch && req.method === "GET") {
46
44
  const packageId = decodeURIComponent(pkgActionShowMatch[1]);
47
45
  const actionId = decodeURIComponent(pkgActionShowMatch[2]);
48
- if (options.packageAllowlist && options.packageAllowlist.length > 0) {
49
- if (!options.packageAllowlist.includes(packageId)) {
50
- return jsonResponse({
51
- ok: false,
52
- error: {
53
- code: PACKAGE_FORBIDDEN,
54
- message: `Package '${packageId}' is not in the allowed package list`,
55
- },
56
- }, 403, corsHeaders);
57
- }
46
+ if (!isPackageAllowedByPolicy(packageId, policy)) {
47
+ return jsonResponse({
48
+ ok: false,
49
+ error: {
50
+ code: PACKAGE_FORBIDDEN,
51
+ message: `Package '${packageId}' is not in the allowed package list`,
52
+ },
53
+ }, 403, corsHeaders);
54
+ }
55
+ if (!isActionAllowedByPolicy({ packageId, actionId }, policy)) {
56
+ return jsonResponse({
57
+ ok: false,
58
+ error: {
59
+ code: ACTION_FORBIDDEN,
60
+ message: `Action '${packageId}/${actionId}' is not in the allowed action list`,
61
+ },
62
+ }, 403, corsHeaders);
58
63
  }
59
64
  const ref = `${packageId}/${actionId}`;
60
65
  try {
@@ -77,8 +82,8 @@ export async function handleActionsRoutes(ctx) {
77
82
  const actionId = decodeURIComponent(actionShowMatch[1]);
78
83
  try {
79
84
  const parsed = parseActionRef(actionId);
80
- if (parsed.packageId && options.packageAllowlist && options.packageAllowlist.length > 0) {
81
- if (!options.packageAllowlist.includes(parsed.packageId)) {
85
+ if (parsed.packageId) {
86
+ if (!isPackageAllowedByPolicy(parsed.packageId, policy)) {
82
87
  return jsonResponse({
83
88
  ok: false,
84
89
  error: {
@@ -87,19 +92,35 @@ export async function handleActionsRoutes(ctx) {
87
92
  },
88
93
  }, 403, corsHeaders);
89
94
  }
95
+ if (!isActionAllowedByPolicy({ packageId: parsed.packageId, actionId: parsed.actionId }, policy)) {
96
+ return jsonResponse({
97
+ ok: false,
98
+ error: {
99
+ code: ACTION_FORBIDDEN,
100
+ message: `Action '${actionId}' is not in the allowed action list`,
101
+ },
102
+ }, 403, corsHeaders);
103
+ }
90
104
  }
91
105
  }
92
106
  catch { }
93
107
  try {
94
108
  const spec = await service.discovery.describeAction(actionId);
95
- if (options.packageAllowlist &&
96
- options.packageAllowlist.length > 0 &&
97
- (!spec.packageId || !options.packageAllowlist.includes(spec.packageId))) {
109
+ if (!isPackageAllowedByPolicy(spec.packageId, policy)) {
98
110
  return jsonResponse({
99
111
  ok: false,
100
112
  error: {
101
113
  code: PACKAGE_FORBIDDEN,
102
- message: `Package '${spec.packageId}' is not in the allowed package list`,
114
+ message: `Package '${spec.packageId || "unknown"}' is not in the allowed package list`,
115
+ },
116
+ }, 403, corsHeaders);
117
+ }
118
+ if (!isActionAllowedByPolicy({ packageId: spec.packageId, actionId: spec.id }, policy)) {
119
+ return jsonResponse({
120
+ ok: false,
121
+ error: {
122
+ code: ACTION_FORBIDDEN,
123
+ message: `Action '${spec.packageId ? `${spec.packageId}/${spec.id}` : spec.id}' is not in the allowed action list`,
103
124
  },
104
125
  }, 403, corsHeaders);
105
126
  }
@@ -127,9 +148,10 @@ export async function handleActionsRoutes(ctx) {
127
148
  let actionRef;
128
149
  let endpointMode;
129
150
  let pkgId;
151
+ let actId;
130
152
  if (pkgRunMatch) {
131
153
  pkgId = decodeURIComponent(pkgRunMatch[1]);
132
- const actId = decodeURIComponent(pkgRunMatch[2]);
154
+ actId = decodeURIComponent(pkgRunMatch[2]);
133
155
  endpointMode = pkgRunMatch[3];
134
156
  actionRef = `${pkgId}/${actId}`;
135
157
  }
@@ -139,30 +161,52 @@ export async function handleActionsRoutes(ctx) {
139
161
  try {
140
162
  const parsed = parseActionRef(actionRef);
141
163
  pkgId = parsed.packageId;
164
+ actId = parsed.actionId;
142
165
  }
143
166
  catch { }
144
167
  }
145
- // 校验 Package 允许白名单
146
- if (options.packageAllowlist && options.packageAllowlist.length > 0) {
147
- if (!pkgId) {
148
- try {
149
- const spec = await service.discovery.describeAction(actionRef);
150
- pkgId = spec?.packageId;
151
- }
152
- catch { }
168
+ // 解析并补全包与动作标识(仅在必要时单次查询 spec)
169
+ if ((!pkgId || !actId) && (policy.packageAllowlist?.length || policy.actionAllowlist?.length)) {
170
+ try {
171
+ const spec = await service.discovery.describeAction(actionRef);
172
+ pkgId = pkgId || spec?.packageId;
173
+ actId = actId || spec?.id;
153
174
  }
154
- if (!pkgId || !options.packageAllowlist.includes(pkgId)) {
155
- return jsonResponse({
156
- ok: false,
157
- runId: "",
158
- error: {
159
- code: PACKAGE_FORBIDDEN,
160
- message: pkgId
161
- ? `Package '${pkgId}' is not in the allowed package list`
162
- : `Action '${actionRef}' does not belong to any allowed package`,
163
- },
164
- }, 403, corsHeaders);
175
+ catch { }
176
+ }
177
+ if (!actId) {
178
+ try {
179
+ const parsed = parseActionRef(actionRef);
180
+ actId = parsed.actionId;
181
+ pkgId = pkgId || parsed.packageId;
165
182
  }
183
+ catch {
184
+ actId = actionRef;
185
+ }
186
+ }
187
+ // 校验 Package 允许白名单
188
+ if (!isPackageAllowedByPolicy(pkgId, policy)) {
189
+ return jsonResponse({
190
+ ok: false,
191
+ runId: "",
192
+ error: {
193
+ code: PACKAGE_FORBIDDEN,
194
+ message: pkgId
195
+ ? `Package '${pkgId}' is not in the allowed package list`
196
+ : `Action '${actionRef}' does not belong to any allowed package`,
197
+ },
198
+ }, 403, corsHeaders);
199
+ }
200
+ // 校验 Action 允许白名单
201
+ if (!isActionAllowedByPolicy({ packageId: pkgId, actionId: actId }, policy)) {
202
+ return jsonResponse({
203
+ ok: false,
204
+ runId: "",
205
+ error: {
206
+ code: ACTION_FORBIDDEN,
207
+ message: `Action '${actionRef}' is not in the allowed action list`,
208
+ },
209
+ }, 403, corsHeaders);
166
210
  }
167
211
  let body = {};
168
212
  try {
@@ -1,19 +1,8 @@
1
1
  import type { PackageInfo } from "../../package/types.js";
2
- import type { ActionDockService } from "../../service/types.js";
3
- import type { ServerOptions } from "../types.js";
4
- /**
5
- * 路由处理统一上下文对象。
6
- */
7
- export interface RouteContext {
8
- req: Request;
9
- url: URL;
10
- pathname: string;
11
- corsHeaders: Record<string, string>;
12
- projectRoot: string | null;
13
- customHome?: string;
14
- service: ActionDockService;
15
- options: ServerOptions;
16
- }
2
+ import type { EffectiveServerPolicy, RouteContext, ServerOptions } from "../types.js";
3
+ import { ActionForbiddenError, assertActionAllowedByPolicy, assertPackageAllowedByPolicy, filterActionsByPolicy, filterPackagesByPolicy, isActionAllowed, isActionAllowedByPolicy, isManagementAllowedByPolicy, isPackageAllowedByPolicy, PackageNotAllowedError } from "../policy.js";
4
+ export type { RouteContext };
5
+ export { ActionForbiddenError, PackageNotAllowedError, isActionAllowed, isActionAllowedByPolicy, isPackageAllowedByPolicy, isManagementAllowedByPolicy, filterActionsByPolicy, filterPackagesByPolicy, assertActionAllowedByPolicy, assertPackageAllowedByPolicy, };
17
6
  /**
18
7
  * 构造带 CORS 头的标准 JSON HTTP 响应。
19
8
  */
@@ -22,22 +11,22 @@ export declare function jsonResponse(data: unknown, status?: number, corsHeaders
22
11
  * 剥离标准版本前缀 (/api/v2/),获取相对路由子路径。
23
12
  */
24
13
  export declare function getSubPath(pathname: string): string;
25
- import { ActionDockError } from "../../errors.js";
26
14
  /**
27
- * 包未列入白名单拒绝访问异常。
15
+ * 校验目标 action 是否属于允许动作白名单。
16
+ * 当 options 或 policy 中的 actionAllowlist 存在且目标 action 不在白名单中时抛出 403 ACTION_FORBIDDEN 异常。
28
17
  */
29
- export declare class PackageNotAllowedError extends ActionDockError {
30
- status: number;
31
- statusCode: number;
32
- constructor(packageId: string);
33
- }
18
+ export declare function assertActionAllowed(action: {
19
+ actionId?: string;
20
+ id?: string;
21
+ packageId?: string;
22
+ } | string, options?: ServerOptions | EffectiveServerPolicy): void;
34
23
  /**
35
24
  * 校验目标 packageId 是否属于允许包白名单。
36
- * 当 options.packageAllowlist 存在且目标 packageId 不在白名单中时抛出 403 PACKAGE_NOT_ALLOWED 异常。
25
+ * 当 options 或 policy 中的 packageAllowlist 存在且目标 packageId 不在白名单中时抛出 403 PACKAGE_NOT_ALLOWED 异常。
37
26
  */
38
- export declare function assertPackageAllowed(packageId: string, options?: ServerOptions): void;
27
+ export declare function assertPackageAllowed(packageId: string, options?: ServerOptions | EffectiveServerPolicy): void;
39
28
  /**
40
29
  * 解析并确定生效的目标 packageId。
41
30
  * 遵循严格的解析规则与白名单检查。
42
31
  */
43
- export declare function resolveTargetPackageId(packages: PackageInfo[], requestedPackageId?: string, options?: ServerOptions): string;
32
+ export declare function resolveTargetPackageId(packages: PackageInfo[], requestedPackageId?: string, options?: ServerOptions | EffectiveServerPolicy): string;
@@ -1,4 +1,7 @@
1
1
  import { assertValidPackageId } from "../../utils/index.js";
2
+ import { ActionDockError, PACKAGE_NOT_FOUND } from "../../errors.js";
3
+ import { ActionForbiddenError, assertActionAllowedByPolicy, assertPackageAllowedByPolicy, filterActionsByPolicy, filterPackagesByPolicy, isActionAllowed, isActionAllowedByPolicy, isManagementAllowedByPolicy, isPackageAllowedByPolicy, PackageNotAllowedError, } from "../policy.js";
4
+ export { ActionForbiddenError, PackageNotAllowedError, isActionAllowed, isActionAllowedByPolicy, isPackageAllowedByPolicy, isManagementAllowedByPolicy, filterActionsByPolicy, filterPackagesByPolicy, assertActionAllowedByPolicy, assertPackageAllowedByPolicy, };
2
5
  /**
3
6
  * 构造带 CORS 头的标准 JSON HTTP 响应。
4
7
  */
@@ -20,36 +23,37 @@ export function getSubPath(pathname) {
20
23
  }
21
24
  return pathname;
22
25
  }
23
- import { ActionDockError, PACKAGE_NOT_FOUND, PACKAGE_NOT_ALLOWED } from "../../errors.js";
24
26
  /**
25
- * 包未列入白名单拒绝访问异常。
27
+ * 校验目标 action 是否属于允许动作白名单。
28
+ * 当 options 或 policy 中的 actionAllowlist 存在且目标 action 不在白名单中时抛出 403 ACTION_FORBIDDEN 异常。
26
29
  */
27
- export class PackageNotAllowedError extends ActionDockError {
28
- status = 403;
29
- statusCode = 403;
30
- constructor(packageId) {
31
- super(PACKAGE_NOT_ALLOWED, `Package '${packageId}' is not in the allowed package list`);
32
- this.name = "PackageNotAllowedError";
33
- Object.setPrototypeOf(this, PackageNotAllowedError.prototype);
34
- }
30
+ export function assertActionAllowed(action, options) {
31
+ const policy = options
32
+ ? {
33
+ packageAllowlist: options.packageAllowlist,
34
+ actionAllowlist: options.actionAllowlist,
35
+ }
36
+ : undefined;
37
+ assertActionAllowedByPolicy(action, policy);
35
38
  }
36
39
  /**
37
40
  * 校验目标 packageId 是否属于允许包白名单。
38
- * 当 options.packageAllowlist 存在且目标 packageId 不在白名单中时抛出 403 PACKAGE_NOT_ALLOWED 异常。
41
+ * 当 options 或 policy 中的 packageAllowlist 存在且目标 packageId 不在白名单中时抛出 403 PACKAGE_NOT_ALLOWED 异常。
39
42
  */
40
43
  export function assertPackageAllowed(packageId, options) {
41
- if (options?.packageAllowlist &&
42
- Array.isArray(options.packageAllowlist) &&
43
- options.packageAllowlist.length > 0 &&
44
- !options.packageAllowlist.includes(packageId)) {
45
- throw new PackageNotAllowedError(packageId);
46
- }
44
+ const policy = options
45
+ ? {
46
+ packageAllowlist: options.packageAllowlist,
47
+ }
48
+ : undefined;
49
+ assertPackageAllowedByPolicy(packageId, policy);
47
50
  }
48
51
  /**
49
52
  * 解析并确定生效的目标 packageId。
50
53
  * 遵循严格的解析规则与白名单检查。
51
54
  */
52
55
  export function resolveTargetPackageId(packages, requestedPackageId, options) {
56
+ const allowlist = options?.packageAllowlist;
53
57
  if (requestedPackageId) {
54
58
  if (requestedPackageId === "global") {
55
59
  return "global";
@@ -63,10 +67,10 @@ export function resolveTargetPackageId(packages, requestedPackageId, options) {
63
67
  return matched.id;
64
68
  }
65
69
  let targetId;
66
- if (options?.packageAllowlist &&
67
- Array.isArray(options.packageAllowlist) &&
68
- options.packageAllowlist.length > 0) {
69
- const allowed = packages.find((p) => options.packageAllowlist.includes(p.id));
70
+ if (allowlist &&
71
+ Array.isArray(allowlist) &&
72
+ allowlist.length > 0) {
73
+ const allowed = packages.find((p) => allowlist.includes(p.id));
70
74
  targetId = allowed ? allowed.id : (packages[0]?.id || "");
71
75
  }
72
76
  else if (packages.length > 0) {
@@ -1,6 +1,6 @@
1
1
  import { type RouteContext } from "./common.js";
2
2
  /**
3
3
  * 处理配置元数据与当前值读取、更新及删除接口。
4
- * 在未显式开启管理功能 (options.enableManagement !== true) 时返回 403 拒绝。
4
+ * 在未显式开启管理功能时返回 403 拒绝。
5
5
  */
6
6
  export declare function handleConfigRoutes(ctx: RouteContext): Promise<Response | null>;
@@ -2,7 +2,7 @@ import { CAPABILITY_UNAVAILABLE, CONFIG_DELETE_ERROR, CONFIG_ENV_ERROR, CONFIG_L
2
2
  import { resolveEnvValue } from "../../runtime/index.js";
3
3
  import { isSecretConfigKey, sanitizeConfigDefinitions } from "../../storage/index.js";
4
4
  import { readJsonBody } from "../body.js";
5
- import { getSubPath, jsonResponse, resolveTargetPackageId } from "./common.js";
5
+ import { getSubPath, isManagementAllowedByPolicy, jsonResponse, resolveTargetPackageId, } from "./common.js";
6
6
  function isClientConfigError(err) {
7
7
  return (err?.code === PACKAGE_NOT_FOUND ||
8
8
  err?.code === INVALID_PACKAGE_ID ||
@@ -13,10 +13,10 @@ function isClientConfigError(err) {
13
13
  }
14
14
  /**
15
15
  * 处理配置元数据与当前值读取、更新及删除接口。
16
- * 在未显式开启管理功能 (options.enableManagement !== true) 时返回 403 拒绝。
16
+ * 在未显式开启管理功能时返回 403 拒绝。
17
17
  */
18
18
  export async function handleConfigRoutes(ctx) {
19
- const { req, url, pathname, corsHeaders, options, service } = ctx;
19
+ const { req, url, pathname, corsHeaders, options, service, activePolicy: policy } = ctx;
20
20
  const subpath = getSubPath(pathname);
21
21
  const isConfigRoute = subpath === "/config" ||
22
22
  subpath.startsWith("/config/") ||
@@ -25,7 +25,7 @@ export async function handleConfigRoutes(ctx) {
25
25
  return null;
26
26
  }
27
27
  // 校验管理能力开关
28
- if (options.enableManagement !== true || !service.management?.config) {
28
+ if (!isManagementAllowedByPolicy(policy) || !service.management?.config) {
29
29
  return jsonResponse({
30
30
  ok: false,
31
31
  error: {
@@ -39,7 +39,7 @@ export async function handleConfigRoutes(ctx) {
39
39
  try {
40
40
  const pkgs = await service.discovery.listPackages();
41
41
  const pkgParam = url.searchParams.get("package") || url.searchParams.get("packageId") || undefined;
42
- const targetPackageId = resolveTargetPackageId(pkgs, pkgParam, options);
42
+ const targetPackageId = resolveTargetPackageId(pkgs, pkgParam, policy);
43
43
  const pkg = pkgs.find((p) => p.id === targetPackageId);
44
44
  const declared = pkg?.config || {};
45
45
  const envChecks = [];
@@ -76,7 +76,7 @@ export async function handleConfigRoutes(ctx) {
76
76
  try {
77
77
  const pkgs = await service.discovery.listPackages();
78
78
  const pkgParam = url.searchParams.get("package") || url.searchParams.get("packageId") || undefined;
79
- const targetPackageId = resolveTargetPackageId(pkgs, pkgParam, options);
79
+ const targetPackageId = resolveTargetPackageId(pkgs, pkgParam, policy);
80
80
  const pkg = pkgs.find((p) => p.id === targetPackageId);
81
81
  const rawDeclared = pkg?.config || {};
82
82
  const declared = sanitizeConfigDefinitions(rawDeclared) || {};
@@ -112,7 +112,7 @@ export async function handleConfigRoutes(ctx) {
112
112
  const body = await readJsonBody(req, { maxBytes: options.maxBodyBytes });
113
113
  const pkgParam = url.searchParams.get("package") || url.searchParams.get("packageId") || body.package || undefined;
114
114
  const pkgs = await service.discovery.listPackages();
115
- const targetPackageId = resolveTargetPackageId(pkgs, pkgParam, options);
115
+ const targetPackageId = resolveTargetPackageId(pkgs, pkgParam, policy);
116
116
  const key = body.key;
117
117
  if (!key) {
118
118
  return jsonResponse({ ok: false, error: { code: INVALID_ARGUMENT, message: "Config 'key' is required" } }, 400, corsHeaders);
@@ -141,7 +141,7 @@ export async function handleConfigRoutes(ctx) {
141
141
  const key = decodeURIComponent(configKeyMatch[1]);
142
142
  const pkgParam = url.searchParams.get("package") || url.searchParams.get("packageId") || undefined;
143
143
  const pkgs = await service.discovery.listPackages();
144
- const targetPackageId = resolveTargetPackageId(pkgs, pkgParam, options);
144
+ const targetPackageId = resolveTargetPackageId(pkgs, pkgParam, policy);
145
145
  const deleted = await service.management.config.delete(targetPackageId, key);
146
146
  return jsonResponse({ ok: true, packageId: targetPackageId, key, deleted }, 200, corsHeaders);
147
147
  }
@@ -5,14 +5,14 @@ import { assertPackageAllowed, getSubPath, jsonResponse } from "./common.js";
5
5
  * 处理环境与依赖诊断接口(GET /api/v2/doctor 与 /doctor)。
6
6
  */
7
7
  export async function handleDoctorRoute(ctx) {
8
- const { req, url, pathname, corsHeaders, projectRoot, customHome, service, options } = ctx;
8
+ const { req, url, pathname, corsHeaders, projectRoot, customHome, service, options, activePolicy: policy } = ctx;
9
9
  const subpath = getSubPath(pathname);
10
10
  if (subpath !== "/doctor" || req.method !== "GET") {
11
11
  return null;
12
12
  }
13
13
  try {
14
14
  const targetPkg = url.searchParams.get("package") || url.searchParams.get("packageId") || undefined;
15
- if (options.packageAllowlist && options.packageAllowlist.length > 0) {
15
+ if (policy.packageAllowlist && policy.packageAllowlist.length > 0) {
16
16
  if (!targetPkg) {
17
17
  return jsonResponse({
18
18
  ok: false,
@@ -24,7 +24,7 @@ export async function handleDoctorRoute(ctx) {
24
24
  }
25
25
  }
26
26
  if (targetPkg) {
27
- assertPackageAllowed(targetPkg, options);
27
+ assertPackageAllowed(targetPkg, policy);
28
28
  }
29
29
  let packageRoot;
30
30
  if (targetPkg) {
@@ -36,7 +36,7 @@ export async function handleDoctorRoute(ctx) {
36
36
  cwd: packageRoot || projectRoot || process.cwd(),
37
37
  packageIdOrPath: packageRoot || targetPkg,
38
38
  customHome,
39
- packageAllowlist: options.packageAllowlist,
39
+ packageAllowlist: policy.packageAllowlist,
40
40
  });
41
41
  return jsonResponse({ ok: true, report }, 200, corsHeaders);
42
42
  }
@@ -6,12 +6,12 @@ import { jsonResponse } from "./common.js";
6
6
  * 处理健康检查与就绪状态接口(支持 /api/v2/health 与 /health)。
7
7
  */
8
8
  export async function handleHealthRoute(ctx) {
9
- const { req, pathname, corsHeaders, options, projectRoot } = ctx;
9
+ const { req, pathname, corsHeaders, options, projectRoot, activePolicy: policy } = ctx;
10
10
  if (pathname !== "/api/v2/health" &&
11
11
  pathname !== "/health") {
12
12
  return null;
13
13
  }
14
- if (!verifyBearerToken(req, options.token, options)) {
14
+ if (!verifyBearerToken(req, policy.token, options)) {
15
15
  return jsonResponse({
16
16
  ok: false,
17
17
  error: {
@@ -2,7 +2,7 @@ import { filterWithFallbackInfo } from "../../filter/index.js";
2
2
  import { INFO_ERROR, PACKAGE_NOT_ALLOWED, PACKAGE_NOT_FOUND, PACKAGES_INFO_ERROR } from "../../errors.js";
3
3
  import { ACTIONDOCK_VERSION } from "../../version.js";
4
4
  import { sanitizeConfigDefinitions } from "../../storage/index.js";
5
- import { assertPackageAllowed, getSubPath, jsonResponse } from "./common.js";
5
+ import { assertPackageAllowed, filterPackagesByPolicy, getSubPath, jsonResponse } from "./common.js";
6
6
  function sanitizePackage(pkg, exposeDebugInfo) {
7
7
  if (!pkg)
8
8
  return pkg;
@@ -22,20 +22,17 @@ function sanitizePackage(pkg, exposeDebugInfo) {
22
22
  * - GET /api/v2/packages 与 GET /packages -> 委托 service.discovery.listPackages()
23
23
  */
24
24
  export async function handleInfoRoute(ctx) {
25
- const { req, url, pathname, corsHeaders, options, service, projectRoot } = ctx;
25
+ const { req, url, pathname, corsHeaders, options, service, projectRoot, activePolicy: policy } = ctx;
26
26
  const subpath = getSubPath(pathname);
27
27
  // 1. Packages List: GET /api/v2/packages, /packages
28
28
  if ((subpath === "/packages" || pathname === "/api/v2/packages" || pathname === "/packages") && req.method === "GET") {
29
29
  try {
30
30
  const targetPkg = url.searchParams.get("package") || url.searchParams.get("packageId") || undefined;
31
31
  if (targetPkg) {
32
- assertPackageAllowed(targetPkg, options);
32
+ assertPackageAllowed(targetPkg, policy);
33
33
  }
34
34
  const rawPackages = await service.discovery.listPackages();
35
- let packages = rawPackages.map((p) => sanitizePackage(p, options.exposeDebugInfo));
36
- if (options.packageAllowlist && Array.isArray(options.packageAllowlist) && options.packageAllowlist.length > 0) {
37
- packages = packages.filter((p) => p.id && options.packageAllowlist.includes(p.id));
38
- }
35
+ const packages = filterPackagesByPolicy(rawPackages.map((p) => sanitizePackage(p, options.exposeDebugInfo)), policy);
39
36
  return jsonResponse({
40
37
  ok: true,
41
38
  packages,
@@ -64,13 +61,10 @@ export async function handleInfoRoute(ctx) {
64
61
  const intent = url.searchParams.get("intent") || undefined;
65
62
  const targetPkg = url.searchParams.get("package") || url.searchParams.get("packageId") || undefined;
66
63
  if (targetPkg) {
67
- assertPackageAllowed(targetPkg, options);
64
+ assertPackageAllowed(targetPkg, policy);
68
65
  }
69
66
  const rawPackages = await service.info();
70
- let packages = rawPackages.map((p) => sanitizePackage(p, options.exposeDebugInfo));
71
- if (options.packageAllowlist && Array.isArray(options.packageAllowlist) && options.packageAllowlist.length > 0) {
72
- packages = packages.filter((p) => p.id && options.packageAllowlist.includes(p.id));
73
- }
67
+ const packages = filterPackagesByPolicy(rawPackages.map((p) => sanitizePackage(p, options.exposeDebugInfo)), policy);
74
68
  if (isTree) {
75
69
  return jsonResponse({ ok: true, type: "tree", packages }, 200, corsHeaders);
76
70
  }
@@ -1,11 +1,11 @@
1
1
  import { filterByIntent } from "../../filter/index.js";
2
2
  import { PACKAGE_NOT_ALLOWED, PLAYBOOK_NOT_FOUND, PLAYBOOKS_LIST_ERROR } from "../../errors.js";
3
- import { assertPackageAllowed, getSubPath, jsonResponse } from "./common.js";
3
+ import { assertPackageAllowed, getSubPath, isPackageAllowedByPolicy, jsonResponse } from "./common.js";
4
4
  /**
5
5
  * 处理 Playbook 规程相关的 HTTP 路由(列表与 SOP 详情查询)。
6
6
  */
7
7
  export async function handlePlaybooksRoutes(ctx) {
8
- const { req, url, pathname, corsHeaders, service, options } = ctx;
8
+ const { req, url, pathname, corsHeaders, service, options, activePolicy: policy } = ctx;
9
9
  const subpath = getSubPath(pathname);
10
10
  // 1. Playbooks List: GET /api/v2/playbooks, GET /playbooks
11
11
  if (subpath === "/playbooks" && req.method === "GET") {
@@ -13,14 +13,14 @@ export async function handlePlaybooksRoutes(ctx) {
13
13
  const intent = url.searchParams.get("intent") || undefined;
14
14
  const targetPkg = url.searchParams.get("package") || url.searchParams.get("packageId") || undefined;
15
15
  if (targetPkg) {
16
- assertPackageAllowed(targetPkg, options);
16
+ assertPackageAllowed(targetPkg, policy);
17
17
  }
18
18
  let pbs = await service.discovery.listPlaybooks({ intent, package: targetPkg });
19
19
  if (targetPkg) {
20
20
  pbs = pbs.filter((p) => p.packageId === targetPkg);
21
21
  }
22
- if (options.packageAllowlist && Array.isArray(options.packageAllowlist) && options.packageAllowlist.length > 0) {
23
- pbs = pbs.filter((p) => p.packageId && options.packageAllowlist.includes(p.packageId));
22
+ if (policy.packageAllowlist && Array.isArray(policy.packageAllowlist) && policy.packageAllowlist.length > 0) {
23
+ pbs = pbs.filter((p) => isPackageAllowedByPolicy(p.packageId, policy));
24
24
  }
25
25
  if (intent) {
26
26
  pbs = filterByIntent(pbs, intent, [(p) => p.id, (p) => p.description || "", (p) => p.packageId || "", (p) => (p.actions || []).join(" ")], false);
@@ -46,7 +46,7 @@ export async function handlePlaybooksRoutes(ctx) {
46
46
  const packageId = decodeURIComponent(pkgPlaybookMatch[1]);
47
47
  const playbookId = decodeURIComponent(pkgPlaybookMatch[2]);
48
48
  try {
49
- assertPackageAllowed(packageId, options);
49
+ assertPackageAllowed(packageId, policy);
50
50
  }
51
51
  catch (err) {
52
52
  return jsonResponse({
@@ -79,7 +79,7 @@ export async function handlePlaybooksRoutes(ctx) {
79
79
  if (playbookId.includes("/")) {
80
80
  const pkgFromRef = playbookId.slice(0, playbookId.lastIndexOf("/"));
81
81
  try {
82
- assertPackageAllowed(pkgFromRef, options);
82
+ assertPackageAllowed(pkgFromRef, policy);
83
83
  }
84
84
  catch (err) {
85
85
  return jsonResponse({
@@ -101,7 +101,7 @@ export async function handlePlaybooksRoutes(ctx) {
101
101
  pkgId = matched.packageId;
102
102
  }
103
103
  }
104
- assertPackageAllowed(pkgId, options);
104
+ assertPackageAllowed(pkgId, policy);
105
105
  return jsonResponse(pb, 200, corsHeaders);
106
106
  }
107
107
  catch (err) {