create-windy 0.3.18 → 0.4.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/README.md +28 -13
- package/dist/cli.js +788 -280
- package/package.json +1 -1
- package/template/.agents/skills/windy-business-module/SKILL.md +34 -10
- package/template/.windy-template.json +2 -2
- package/template/AGENTS.md +18 -8
- package/template/README.md +32 -22
- package/template/apps/agent-server/src/southwind_agent_server/adk_debug.py +38 -5
- package/template/apps/agent-server/tests/test_adk_debug_result_polling.py +139 -0
- package/template/apps/server/Dockerfile +1 -0
- package/template/apps/server/package.json +1 -0
- package/template/apps/server/scripts/validate-module-composition-once.ts +4 -0
- package/template/apps/server/scripts/validate-module-composition.ts +28 -0
- package/template/apps/server/src/agent/data-scope-routes.test.ts +20 -5
- package/template/apps/server/src/agent/routes.test.ts +20 -7
- package/template/apps/server/src/agent/routes.ts +14 -0
- package/template/apps/server/src/agent/runtime-bootstrap.ts +22 -6
- package/template/apps/server/src/agent/sandbox-worker.ts +2 -2
- package/template/apps/server/src/agent/unavailable-routes.ts +33 -0
- package/template/apps/server/src/app/server-composition.ts +56 -0
- package/template/apps/server/src/application-services.ts +2 -0
- package/template/apps/server/src/bulk-data/service.ts +14 -0
- package/template/apps/server/src/data-governance/export/authorization-port.ts +1 -1
- package/template/apps/server/src/data-governance/export/postgres-api-fixture.ts +18 -2
- package/template/apps/server/src/data-governance/export/postgres-recovery.integration.test.ts +10 -9
- package/template/apps/server/src/data-governance/export/routes-test-fixture.ts +6 -8
- package/template/apps/server/src/data-governance/user-plaintext-routes.test.ts +4 -2
- package/template/apps/server/src/feature/routes.authorization.test.ts +26 -1
- package/template/apps/server/src/feature/routes.ts +33 -15
- package/template/apps/server/src/foundation-activation.test.ts +60 -0
- package/template/apps/server/src/foundation.ts +64 -5
- package/template/apps/server/src/guards.test.ts +23 -0
- package/template/apps/server/src/guards.ts +12 -0
- package/template/apps/server/src/index.ts +28 -34
- package/template/apps/server/src/installed-business-modules.ts +13 -24
- package/template/apps/server/src/jobs/legacy-adapter.integration.test.ts +1 -0
- package/template/apps/server/src/jobs/legacy-adapter.test.ts +106 -0
- package/template/apps/server/src/jobs/legacy-service-runtime.ts +40 -0
- package/template/apps/server/src/license/activation.test.ts +30 -0
- package/template/apps/server/src/license/activation.ts +26 -0
- package/template/apps/server/src/license/restriction.test.ts +16 -0
- package/template/apps/server/src/license/routes-offline-code.test.ts +6 -2
- package/template/apps/server/src/license/routes.ts +20 -4
- package/template/apps/server/src/license/runtime-service.test.ts +10 -2
- package/template/apps/server/src/module-bootstrap.ts +14 -23
- package/template/apps/server/src/module-composition.test.ts +43 -2
- package/template/apps/server/src/module-composition.ts +29 -0
- package/template/apps/server/src/module-host/durable-context-guard.test.ts +172 -0
- package/template/apps/server/src/module-host/durable-context-unavailable.test.ts +50 -1
- package/template/apps/server/src/module-host/durable-context.test.ts +42 -25
- package/template/apps/server/src/module-host/durable-context.ts +42 -2
- package/template/apps/server/src/module-host/durable-handler-registration.ts +96 -5
- package/template/apps/server/src/module-host/implementation-completeness.test.ts +185 -0
- package/template/apps/server/src/module-host/implementation-completeness.ts +132 -0
- package/template/apps/server/src/module-host/initialize-contracts.ts +14 -0
- package/template/apps/server/src/module-host/initialize.test.ts +78 -2
- package/template/apps/server/src/module-host/initialize.ts +15 -4
- package/template/apps/server/src/module-host/project-attachments.test.ts +92 -0
- package/template/apps/server/src/module-host/project-attachments.ts +58 -0
- package/template/apps/server/src/module-host/project-composition-package.test.ts +186 -0
- package/template/apps/server/src/module-host/project-composition.test.ts +260 -0
- package/template/apps/server/src/module-host/project-composition.ts +311 -0
- package/template/apps/server/src/module-host/request-context.ts +3 -0
- package/template/apps/server/src/module-host/route-declaration.ts +15 -6
- package/template/apps/server/src/module-host/route-installer.test.ts +15 -2
- package/template/apps/server/src/module-host/route-installer.ts +12 -2
- package/template/apps/server/src/module-host/route-registration.test.ts +13 -4
- package/template/apps/server/src/module-host/runtime-composition-compatibility.test.ts +63 -0
- package/template/apps/server/src/module-host/runtime-composition-compatibility.ts +46 -0
- package/template/apps/server/src/module-host/runtime-composition.ts +34 -0
- package/template/apps/server/src/module-host/search-provider-registration.test.ts +64 -0
- package/template/apps/server/src/module-host/search-provider-registration.ts +35 -0
- package/template/apps/server/src/module-host/static-route-authorization.test.ts +99 -0
- package/template/apps/server/src/module-host/static-route-authorization.ts +90 -0
- package/template/apps/server/src/module-host/task-definitions.ts +1 -0
- package/template/apps/server/src/module-host/user-directory-composition.test.ts +4 -1
- package/template/apps/server/src/module-host.test.ts +2 -0
- package/template/apps/server/src/module-preflight.test.ts +289 -0
- package/template/apps/server/src/module-preflight.ts +96 -0
- package/template/apps/server/src/module-runtime-validation.ts +1 -0
- package/template/apps/server/src/platform-app-bootstrap.ts +50 -0
- package/template/apps/server/src/platform-http-runtime.ts +32 -7
- package/template/apps/server/src/route-guards.test.ts +22 -0
- package/template/apps/server/src/route-guards.ts +10 -3
- package/template/apps/server/src/runtime-audit-query.ts +50 -0
- package/template/apps/server/src/runtime-feature.test.ts +46 -0
- package/template/apps/server/src/runtime-feature.ts +5 -0
- package/template/apps/server/src/runtime.ts +10 -49
- package/template/apps/server/src/scheduler/audit-action.test.ts +49 -0
- package/template/apps/server/src/scheduler/audit.ts +1 -1
- package/template/apps/server/src/scheduler/execution-recorder.ts +10 -5
- package/template/apps/server/src/scheduler/recovery-service.test.ts +4 -3
- package/template/apps/server/src/scheduler/service-idle.test.ts +4 -3
- package/template/apps/server/src/scheduler/service.test.ts +1 -0
- package/template/apps/server/src/scheduler/service.ts +18 -21
- package/template/apps/server/src/scheduler/types.ts +5 -1
- package/template/apps/server/src/startup-composition-validation.test.ts +35 -0
- package/template/apps/server/src/startup-composition-validation.ts +54 -0
- package/template/apps/server/src/startup-preflight.test.ts +36 -0
- package/template/apps/server/src/startup-preflight.ts +37 -0
- package/template/apps/server/src/system/audit-routes.test.ts +9 -3
- package/template/apps/server/src/system/department-routes.test.ts +5 -3
- package/template/apps/server/src/system/feature-routes.test.ts +4 -3
- package/template/apps/server/src/system/interface-catalog.test.ts +5 -3
- package/template/apps/server/src/system/interface-catalog.ts +3 -0
- package/template/apps/server/src/system/menu-routes.test.ts +5 -3
- package/template/apps/server/src/system/route-test-runtime.ts +24 -0
- package/template/apps/server/src/system/routes-validation.test.ts +7 -3
- package/template/apps/server/src/system/routes.test.ts +21 -11
- package/template/apps/server/src/system/routes.ts +11 -2
- package/template/apps/server/src/work-order/foundation-modules.test.ts +6 -4
- package/template/apps/server/src/work-order/routes.test.ts +8 -0
- package/template/apps/server/src/work-order/task.test.ts +6 -1
- package/template/apps/web/Dockerfile +5 -0
- package/template/apps/web/package.json +1 -0
- package/template/apps/web/scripts/validate-admin-ui-composition.ts +106 -0
- package/template/apps/web/src/app/web-composition.ts +24 -0
- package/template/apps/web/src/app/web-composition.webtest.ts +15 -0
- package/template/apps/web/src/layout/AppSidebar.vue +8 -1
- package/template/apps/web/src/layout/NavigationSearchDialog.vue +8 -1
- package/template/apps/web/src/layout/navigation.ts +29 -4
- package/template/apps/web/src/layout/navigation.webtest.ts +51 -0
- package/template/apps/web/src/pages/dashboard/dashboard-presenter.ts +6 -1
- package/template/apps/web/src/pages/work-orders/WorkOrderListPage.vue +202 -0
- package/template/apps/web/src/pages/work-orders/WorkOrderListPage.webtest.ts +51 -0
- package/template/apps/web/src/router/admin-extension-loader.ts +44 -0
- package/template/apps/web/src/router/admin-extension-loader.webtest.ts +49 -0
- package/template/apps/web/src/router/admin-ui-composition-plugin.ts +33 -0
- package/template/apps/web/src/router/admin-ui-composition-plugin.webtest.ts +20 -0
- package/template/apps/web/src/router/admin-ui-composition.ts +313 -0
- package/template/apps/web/src/router/admin-ui-composition.webtest.ts +225 -0
- package/template/apps/web/src/router/admin-ui-module-registry-plugin.ts +55 -0
- package/template/apps/web/src/router/admin-ui-module-registry-plugin.webtest.ts +46 -0
- package/template/apps/web/src/router/business-routes.ts +32 -45
- package/template/apps/web/src/router/business-routes.webtest.ts +40 -44
- package/template/apps/web/src/router/index.ts +64 -54
- package/template/apps/web/src/router/manifest-routes.ts +27 -7
- package/template/apps/web/src/router/manifest-routes.webtest.ts +10 -2
- package/template/apps/web/src/router/platform-admin-route-bindings.ts +58 -0
- package/template/apps/web/src/router/reserved-route-paths.ts +14 -0
- package/template/apps/web/src/router/shared-scaffold-neutrality.webtest.ts +10 -4
- package/template/apps/web/src/router/web-composition-host.ts +20 -0
- package/template/apps/web/src/services/work-order-api.ts +16 -0
- package/template/apps/web/src/services/work-order-api.webtest.ts +21 -0
- package/template/apps/web/src/virtual-admin-ui-modules.d.ts +5 -0
- package/template/apps/web/vite.config.ts +22 -1
- package/template/docs/architecture/migration-bundle.md +10 -7
- package/template/docs/architecture/module-manifest-composition.md +85 -3
- package/template/docs/architecture/search-provider-protocol.md +6 -1
- package/template/docs/architecture/server-module-interface.md +70 -7
- package/template/docs/development/business-web-routes.md +53 -50
- package/template/docs/development/module-lifecycle.md +48 -54
- package/template/docs/platform/agent-runtime.md +2 -0
- package/template/docs/platform/navigation-registry.md +5 -4
- package/template/package.json +1 -1
- package/template/packages/config/package.json +1 -1
- package/template/packages/database/package.json +1 -1
- package/template/packages/example-work-order/package.json +6 -0
- package/template/packages/example-work-order/src/admin-page.ts +78 -0
- package/template/packages/example-work-order/src/admin.ts +35 -0
- package/template/packages/example-work-order/src/manifest.test.ts +62 -5
- package/template/packages/example-work-order/src/manifest.ts +54 -7
- package/template/packages/example-work-order/src/migration-bundle.ts +8 -0
- package/template/packages/example-work-order/src/server-module.ts +2 -2
- package/template/packages/jobs/package.json +1 -1
- package/template/packages/modules/index.ts +4 -0
- package/template/packages/modules/package.json +3 -2
- package/template/packages/modules/src/admin-ui-entry.ts +8 -0
- package/template/packages/modules/src/admin-ui-manifest.test.ts +52 -0
- package/template/packages/modules/src/admin-ui-manifest.ts +53 -0
- package/template/packages/modules/src/capability-audit-validation.test.ts +127 -0
- package/template/packages/modules/src/capability-audit-validation.ts +38 -0
- package/template/packages/modules/src/capability-guard-validation.ts +148 -0
- package/template/packages/modules/src/capability-key-validation.test.ts +150 -0
- package/template/packages/modules/src/composition-fixtures.test-support.ts +7 -5
- package/template/packages/modules/src/composition.test.ts +74 -9
- package/template/packages/modules/src/composition.ts +8 -0
- package/template/packages/modules/src/manifest.ts +1 -0
- package/template/packages/modules/src/migration-bundle-validation.test.ts +23 -0
- package/template/packages/modules/src/module-host-range.test.ts +57 -0
- package/template/packages/modules/src/module-host-range.ts +125 -0
- package/template/packages/modules/src/project-module-assembly.test.ts +133 -0
- package/template/packages/modules/src/project-module-assembly.ts +126 -0
- package/template/packages/modules/src/system-admin-routes.ts +6 -5
- package/template/packages/modules/src/system-admin-ui-catalog.ts +72 -0
- package/template/packages/modules/src/system-agent-tools.ts +2 -2
- package/template/packages/modules/src/system-api-binding.ts +43 -9
- package/template/packages/modules/src/system-api-control-plane.ts +202 -0
- package/template/packages/modules/src/system-api-data-governance.ts +4 -2
- package/template/packages/modules/src/system-api-operational.ts +57 -8
- package/template/packages/modules/src/system-api-permissions.ts +115 -21
- package/template/packages/modules/src/system-api-watermark.ts +3 -0
- package/template/packages/modules/src/system-audit-actions.ts +7 -0
- package/template/packages/modules/src/system-data-governance.ts +5 -2
- package/template/packages/modules/src/system-features.ts +31 -0
- package/template/packages/modules/src/system-governed-export-bindings.ts +8 -4
- package/template/packages/modules/src/system-module-catalog.test.ts +39 -4
- package/template/packages/modules/src/system-module-catalog.ts +68 -7
- package/template/packages/modules/src/system-module-tasks.ts +8 -1
- package/template/packages/modules/src/system-modules-access.test.ts +4 -0
- package/template/packages/modules/src/system-modules.test.ts +10 -2
- package/template/packages/modules/src/system-modules.ts +6 -5
- package/template/packages/modules/src/system-permissions.ts +4 -0
- package/template/packages/modules/user/module.ts +19 -2
- package/template/packages/server-sdk/index.ts +8 -0
- package/template/packages/server-sdk/package.json +2 -1
- package/template/packages/server-sdk/src/durable-handler.ts +16 -1
- package/template/packages/server-sdk/src/module-adapter.test.ts +44 -0
- package/template/packages/server-sdk/src/module-adapter.ts +59 -0
- package/template/packages/server-sdk/src/route-definition.ts +3 -2
- package/template/packages/shared/package.json +1 -1
- package/template/packages/shared/src/audit.test.ts +7 -0
- package/template/packages/shared/src/audit.ts +16 -0
- package/template/packages/shared/src/migration-bundle-validation.ts +9 -11
- package/template/packages/shared/src/rbac.ts +2 -0
- package/template/apps/web/src/app/business-routes.ts +0 -22
|
@@ -42,7 +42,7 @@ describe("客户 License 离线激活码路由", () => {
|
|
|
42
42
|
requestId: "request_1",
|
|
43
43
|
},
|
|
44
44
|
}));
|
|
45
|
-
registerPlatformLicenseRoutes(app, runtime);
|
|
45
|
+
registerPlatformLicenseRoutes(app, runtime, allowRoutes);
|
|
46
46
|
|
|
47
47
|
const accepted = await app.handle(post({ activationCode }));
|
|
48
48
|
expect(accepted.status).toBe(200);
|
|
@@ -150,10 +150,14 @@ function createApp(runtime: PlatformLicenseRuntime) {
|
|
|
150
150
|
requestId: "request_1",
|
|
151
151
|
},
|
|
152
152
|
}));
|
|
153
|
-
registerPlatformLicenseRoutes(app, runtime);
|
|
153
|
+
registerPlatformLicenseRoutes(app, runtime, allowRoutes);
|
|
154
154
|
return app;
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
+
const allowRoutes = {
|
|
158
|
+
authorize: async () => undefined,
|
|
159
|
+
};
|
|
160
|
+
|
|
157
161
|
function fixedLicense(): SignedLicenseV1 {
|
|
158
162
|
return {
|
|
159
163
|
formatVersion: 1,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { RequestGuardContext } from "../guards.js";
|
|
2
2
|
import type { RouteApp } from "../system/routes.js";
|
|
3
3
|
import type { PlatformLicenseRuntime } from "./runtime-service.js";
|
|
4
|
+
import type { StaticRouteAuthorizer } from "../module-host/static-route-authorization.js";
|
|
4
5
|
import {
|
|
5
6
|
boundedJsonBodyErrorResponse,
|
|
6
7
|
decodeOfflineActivationCode,
|
|
@@ -17,10 +18,18 @@ interface LicenseRouteContext {
|
|
|
17
18
|
export function registerPlatformLicenseRoutes(
|
|
18
19
|
app: Pick<RouteApp, "get" | "post">,
|
|
19
20
|
licenseRuntime: PlatformLicenseRuntime,
|
|
21
|
+
authorizer: StaticRouteAuthorizer,
|
|
20
22
|
) {
|
|
21
23
|
app.get("/license/runtime", async (rawContext) => {
|
|
22
24
|
const context = rawContext as LicenseRouteContext;
|
|
23
25
|
if (context.guardContext.actor.type !== "user") return unauthorized();
|
|
26
|
+
const denied = await authorizer.authorize(
|
|
27
|
+
context.guardContext,
|
|
28
|
+
"GET",
|
|
29
|
+
"/api/license/runtime",
|
|
30
|
+
{ restrictedModeAccess: "admin-admission" },
|
|
31
|
+
);
|
|
32
|
+
if (denied) return denied;
|
|
24
33
|
return licenseRuntime.snapshotForActor(context.guardContext.actor);
|
|
25
34
|
});
|
|
26
35
|
|
|
@@ -29,6 +38,13 @@ export function registerPlatformLicenseRoutes(
|
|
|
29
38
|
async (rawContext) => {
|
|
30
39
|
const context = rawContext as LicenseRouteContext;
|
|
31
40
|
if (context.guardContext.actor.type !== "user") return unauthorized();
|
|
41
|
+
const denied = await authorizer.authorize(
|
|
42
|
+
context.guardContext,
|
|
43
|
+
"POST",
|
|
44
|
+
"/api/license/runtime/install",
|
|
45
|
+
{ restrictedModeAccess: "admin-admission" },
|
|
46
|
+
);
|
|
47
|
+
if (denied) return denied;
|
|
32
48
|
let rawLicense: string;
|
|
33
49
|
try {
|
|
34
50
|
rawLicense = readInstallCandidate(
|
|
@@ -82,6 +98,10 @@ export function registerPlatformLicenseRoutes(
|
|
|
82
98
|
return app;
|
|
83
99
|
}
|
|
84
100
|
|
|
101
|
+
function unauthorized(): Response {
|
|
102
|
+
return Response.json({ error: "UNAUTHORIZED" }, { status: 401 });
|
|
103
|
+
}
|
|
104
|
+
|
|
85
105
|
function readInstallCandidate(body: unknown): string {
|
|
86
106
|
if (!body || typeof body !== "object" || Array.isArray(body)) return "";
|
|
87
107
|
const record = body as Record<string, unknown>;
|
|
@@ -95,7 +115,3 @@ function readInstallCandidate(body: unknown): string {
|
|
|
95
115
|
? record.license
|
|
96
116
|
: "";
|
|
97
117
|
}
|
|
98
|
-
|
|
99
|
-
function unauthorized(): Response {
|
|
100
|
-
return Response.json({ error: "UNAUTHORIZED" }, { status: 401 });
|
|
101
|
-
}
|
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
requestStatus as status,
|
|
34
34
|
} from "./runtime-service-test-http.js";
|
|
35
35
|
import { buildScopeContext } from "../data-access/context.js";
|
|
36
|
+
import { createStaticRouteAuthorizer } from "../module-host/static-route-authorization.js";
|
|
36
37
|
|
|
37
38
|
const NOW = new Date("2026-08-01T00:00:00.000Z");
|
|
38
39
|
|
|
@@ -240,6 +241,7 @@ function createFixture(
|
|
|
240
241
|
{
|
|
241
242
|
actorResolver: { resolve: (current) => actorFrom(current) },
|
|
242
243
|
licenseResolver: licenseRuntime,
|
|
244
|
+
featureCatalog: snapshot.features,
|
|
243
245
|
featureResolver: {
|
|
244
246
|
resolve: (key) => ({ key, enabled: true, visible: "visible" }),
|
|
245
247
|
},
|
|
@@ -258,15 +260,18 @@ function createFixture(
|
|
|
258
260
|
}
|
|
259
261
|
})
|
|
260
262
|
.get("/health", () => ({ status: "ok" }));
|
|
261
|
-
|
|
263
|
+
const modules = loadSystemModules();
|
|
264
|
+
const routeAuthorizer = createStaticRouteAuthorizer(runtime, modules);
|
|
265
|
+
registerPlatformLicenseRoutes(app, licenseRuntime, routeAuthorizer);
|
|
262
266
|
registerSystemRoutes(app, repositories, runtime, snapshot.features);
|
|
263
267
|
registerAgentRoutes(
|
|
264
268
|
app,
|
|
265
269
|
new GovernedToolRegistry(
|
|
266
|
-
|
|
270
|
+
modules,
|
|
267
271
|
createSystemToolHandlers(repositories, runtime),
|
|
268
272
|
runtime,
|
|
269
273
|
),
|
|
274
|
+
routeAuthorizer,
|
|
270
275
|
);
|
|
271
276
|
return { app, runtime, licenseRuntime };
|
|
272
277
|
}
|
|
@@ -283,6 +288,9 @@ function actorFrom(current: Request) {
|
|
|
283
288
|
permissionKeys: [
|
|
284
289
|
"system.user.read",
|
|
285
290
|
"system.audit.read",
|
|
291
|
+
"system.agent.read",
|
|
292
|
+
"system.license.read",
|
|
293
|
+
"system.license.manage",
|
|
286
294
|
"system.menu.read",
|
|
287
295
|
],
|
|
288
296
|
scopeContext: buildScopeContext("user_1", undefined, [
|
|
@@ -1,20 +1,13 @@
|
|
|
1
1
|
import type { PlatformConfig } from "@southwind-ai/config";
|
|
2
|
+
import type { ModuleUploadPolicy } from "@southwind-ai/server-sdk";
|
|
2
3
|
// @windy-module system.configuration begin
|
|
3
4
|
import { bootstrapModuleConfiguration } from "./configuration/bootstrap.js";
|
|
4
5
|
// @windy-module system.configuration end
|
|
5
|
-
import {
|
|
6
|
-
createFoundationSnapshot,
|
|
7
|
-
loadFoundationModules,
|
|
8
|
-
} from "./foundation.js";
|
|
6
|
+
import { createFoundationSnapshot } from "./foundation.js";
|
|
9
7
|
import { runInstalledModuleMigrations } from "./module-migrations.js";
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
} from "./installed-business-modules.js";
|
|
14
|
-
import {
|
|
15
|
-
initializeModuleHosts,
|
|
16
|
-
synchronizeModuleRolePresets,
|
|
17
|
-
} from "./module-host.js";
|
|
8
|
+
import { attachRuntimeBusinessModules } from "./module-host/runtime-composition.js";
|
|
9
|
+
import { synchronizeModuleRolePresets } from "./module-host.js";
|
|
10
|
+
import { prepareInstalledServerModules } from "./module-preflight.js";
|
|
18
11
|
import type { ServerPersistence } from "./persistence.js";
|
|
19
12
|
// @windy-module system.storage begin
|
|
20
13
|
import { createPlatformUploadPolicies } from "./storage/runtime.js";
|
|
@@ -27,8 +20,15 @@ import {
|
|
|
27
20
|
export async function bootstrapServerModules(
|
|
28
21
|
platformConfig: PlatformConfig,
|
|
29
22
|
persistence: ServerPersistence | undefined,
|
|
23
|
+
prepared?: Awaited<ReturnType<typeof prepareInstalledServerModules>>,
|
|
30
24
|
) {
|
|
31
|
-
const
|
|
25
|
+
const baseUploadPolicies: ModuleUploadPolicy[] = [
|
|
26
|
+
// @windy-module system.storage begin
|
|
27
|
+
...createPlatformUploadPolicies(platformConfig),
|
|
28
|
+
// @windy-module system.storage end
|
|
29
|
+
];
|
|
30
|
+
const { foundationModules, moduleHosts } =
|
|
31
|
+
prepared ?? (await prepareInstalledServerModules(baseUploadPolicies));
|
|
32
32
|
const foundationSnapshot = createFoundationSnapshot(
|
|
33
33
|
platformConfig,
|
|
34
34
|
foundationModules,
|
|
@@ -46,15 +46,6 @@ export async function bootstrapServerModules(
|
|
|
46
46
|
// @windy-module system.configuration begin
|
|
47
47
|
configurationRuntime.bindFoundation(foundationSnapshot);
|
|
48
48
|
// @windy-module system.configuration end
|
|
49
|
-
const moduleHosts = await initializeModuleHosts({
|
|
50
|
-
modules: foundationModules,
|
|
51
|
-
installations: installedBusinessModules,
|
|
52
|
-
baseUploadPolicies: [
|
|
53
|
-
// @windy-module system.storage begin
|
|
54
|
-
...createPlatformUploadPolicies(platformConfig),
|
|
55
|
-
// @windy-module system.storage end
|
|
56
|
-
],
|
|
57
|
-
});
|
|
58
49
|
const systemRepositories = createSystemRepositories(foundationSnapshot, {
|
|
59
50
|
db: persistence?.db,
|
|
60
51
|
});
|
|
@@ -74,7 +65,7 @@ export async function bootstrapServerModules(
|
|
|
74
65
|
foundationSnapshot,
|
|
75
66
|
moduleHosts.rolePresets,
|
|
76
67
|
);
|
|
77
|
-
|
|
68
|
+
await attachRuntimeBusinessModules({
|
|
78
69
|
db: persistence?.db,
|
|
79
70
|
departments: systemRepositories.departments,
|
|
80
71
|
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
2
|
import type { ModuleManifest } from "@southwind-ai/modules";
|
|
3
|
+
import { featureKey } from "@southwind-ai/shared";
|
|
3
4
|
import { Elysia } from "elysia";
|
|
4
5
|
import { validateServerModuleComposition } from "./module-composition.js";
|
|
5
6
|
|
|
@@ -83,6 +84,35 @@ describe("Server Module Composition", () => {
|
|
|
83
84
|
"AI Operation Handler已注册但未在 Manifest 声明:system.unknown-operation",
|
|
84
85
|
);
|
|
85
86
|
});
|
|
87
|
+
|
|
88
|
+
test("平台静态 HTTP Route 也必须在 Manifest 声明", () => {
|
|
89
|
+
expect(() =>
|
|
90
|
+
validateServerModuleComposition({
|
|
91
|
+
modules: [fixture()],
|
|
92
|
+
routes: [
|
|
93
|
+
{
|
|
94
|
+
method: "POST",
|
|
95
|
+
path: "/api/auth/users/:id/password",
|
|
96
|
+
},
|
|
97
|
+
{ method: "GET", path: "/api/demo/ghost" },
|
|
98
|
+
],
|
|
99
|
+
}),
|
|
100
|
+
).toThrow("HTTP Route 已注册但未在 Manifest 声明:GET /api/demo/ghost");
|
|
101
|
+
|
|
102
|
+
expect(() =>
|
|
103
|
+
validateServerModuleComposition({
|
|
104
|
+
modules: [fixture()],
|
|
105
|
+
routes: [
|
|
106
|
+
{
|
|
107
|
+
method: "POST",
|
|
108
|
+
path: "/api/auth/users/:id/password",
|
|
109
|
+
},
|
|
110
|
+
{ method: "GET", path: "/api/openapi.json" },
|
|
111
|
+
],
|
|
112
|
+
frameworkRoutes: ["GET /api/openapi.json"],
|
|
113
|
+
}),
|
|
114
|
+
).not.toThrow();
|
|
115
|
+
});
|
|
86
116
|
});
|
|
87
117
|
|
|
88
118
|
function fixture(): ModuleManifest {
|
|
@@ -110,14 +140,25 @@ function fixture(): ModuleManifest {
|
|
|
110
140
|
method: "POST",
|
|
111
141
|
path: "/api/auth/users/:id/password",
|
|
112
142
|
permissionKey: "system.auth.manage",
|
|
143
|
+
featureKey: "system.auth",
|
|
144
|
+
auditAction: "auth.password.update",
|
|
145
|
+
},
|
|
146
|
+
],
|
|
147
|
+
features: [
|
|
148
|
+
{
|
|
149
|
+
key: featureKey("system.auth"),
|
|
150
|
+
label: "账号认证",
|
|
151
|
+
module: "system",
|
|
152
|
+
visible: "visible",
|
|
153
|
+
enabled: true,
|
|
154
|
+
stage: "stable",
|
|
113
155
|
},
|
|
114
156
|
],
|
|
115
|
-
features: [],
|
|
116
157
|
migrations: [],
|
|
117
158
|
schemaExports: [],
|
|
118
159
|
tasks: [],
|
|
119
160
|
tools: [],
|
|
120
161
|
providers: [],
|
|
121
|
-
auditActions: [],
|
|
162
|
+
auditActions: ["auth.password.update"],
|
|
122
163
|
};
|
|
123
164
|
}
|
|
@@ -23,6 +23,11 @@ export interface ModuleImplementationRegistrations {
|
|
|
23
23
|
export interface ServerCompositionInput {
|
|
24
24
|
modules: readonly ModuleManifest[];
|
|
25
25
|
routes: readonly RegisteredHttpRoute[];
|
|
26
|
+
/**
|
|
27
|
+
* 仅用于框架生成且不属于产品 API 的路由,例如 OpenAPI 文档。
|
|
28
|
+
* 产品、运维、认证与恢复入口仍必须进入 Manifest。
|
|
29
|
+
*/
|
|
30
|
+
frameworkRoutes?: readonly string[];
|
|
26
31
|
tasks?: readonly { key: string }[];
|
|
27
32
|
tools?: ReadonlyMap<string, unknown>;
|
|
28
33
|
aiOperations?: readonly string[];
|
|
@@ -33,6 +38,11 @@ export interface ServerCompositionInput {
|
|
|
33
38
|
export function validateServerModuleComposition(
|
|
34
39
|
input: ServerCompositionInput,
|
|
35
40
|
): void {
|
|
41
|
+
validateHttpRouteRegistration(
|
|
42
|
+
input.modules,
|
|
43
|
+
input.routes.map(apiRouteKey),
|
|
44
|
+
input.frameworkRoutes ?? [],
|
|
45
|
+
);
|
|
36
46
|
validateModuleComposition(input.modules, {
|
|
37
47
|
apiRoutes: input.routes.map(apiRouteKey),
|
|
38
48
|
tasks: (input.tasks || []).map(({ key }) => key),
|
|
@@ -45,6 +55,25 @@ export function validateServerModuleComposition(
|
|
|
45
55
|
}
|
|
46
56
|
}
|
|
47
57
|
|
|
58
|
+
function validateHttpRouteRegistration(
|
|
59
|
+
modules: readonly ModuleManifest[],
|
|
60
|
+
registeredRoutes: readonly string[],
|
|
61
|
+
frameworkRoutes: readonly string[],
|
|
62
|
+
): void {
|
|
63
|
+
const declared = new Set(
|
|
64
|
+
modules.flatMap((module) => module.apiPermissions.map(apiRouteKey)),
|
|
65
|
+
);
|
|
66
|
+
const exemptions = new Set(frameworkRoutes);
|
|
67
|
+
const unknown = registeredRoutes.filter(
|
|
68
|
+
(route) => !declared.has(route) && !exemptions.has(route),
|
|
69
|
+
);
|
|
70
|
+
if (unknown.length) {
|
|
71
|
+
throw new Error(
|
|
72
|
+
`HTTP Route 已注册但未在 Manifest 声明:${[...new Set(unknown)].join("、")}`,
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
48
77
|
function validateRegistrationConsistency(
|
|
49
78
|
modules: readonly ModuleManifest[],
|
|
50
79
|
registrations: ModuleImplementationRegistrations,
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import type { DurableHandlerContext } from "@southwind-ai/jobs";
|
|
3
|
+
import { featureKey } from "@southwind-ai/shared";
|
|
4
|
+
import { buildActorScopeContexts } from "../data-access/context.js";
|
|
5
|
+
import { captureAuthorizationScope } from "../data-access/scope-snapshot.js";
|
|
6
|
+
import type { RequestActor } from "../guards.js";
|
|
7
|
+
import { createServerRuntime } from "../runtime.js";
|
|
8
|
+
import { createModuleDurableContextualizer } from "./durable-context.js";
|
|
9
|
+
import { initializeModuleHosts } from "./initialize.js";
|
|
10
|
+
import { manifest } from "./test-fixtures.js";
|
|
11
|
+
|
|
12
|
+
const feature = {
|
|
13
|
+
key: featureKey("evidence.catalog"),
|
|
14
|
+
label: "电子物证",
|
|
15
|
+
module: "evidence",
|
|
16
|
+
visible: "visible" as const,
|
|
17
|
+
enabled: true,
|
|
18
|
+
stage: "stable" as const,
|
|
19
|
+
};
|
|
20
|
+
const moduleManifest = manifest("evidence", {
|
|
21
|
+
features: [feature],
|
|
22
|
+
permissions: [
|
|
23
|
+
{
|
|
24
|
+
key: "evidence.read",
|
|
25
|
+
label: "读取物证",
|
|
26
|
+
module: "evidence",
|
|
27
|
+
moduleLabel: "物证",
|
|
28
|
+
resource: "file",
|
|
29
|
+
resourceLabel: "文件",
|
|
30
|
+
action: "read",
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
tasks: [
|
|
34
|
+
{
|
|
35
|
+
key: "evidence.refresh",
|
|
36
|
+
label: "刷新物证",
|
|
37
|
+
description: "刷新物证目录。",
|
|
38
|
+
permissionKey: "evidence.read",
|
|
39
|
+
featureKey: "evidence.catalog",
|
|
40
|
+
auditAction: "evidence.file.read",
|
|
41
|
+
intervalSeconds: 60,
|
|
42
|
+
maxAttempts: 3,
|
|
43
|
+
retryDelaySeconds: 5,
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
auditActions: ["evidence.file.read"],
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe("模块 Durable Handler 逐次 Guard", () => {
|
|
50
|
+
test("重试与恢复会重新读取当前 Feature 和 License 状态", async () => {
|
|
51
|
+
let featureEnabled = true;
|
|
52
|
+
const featureChanged = await fixture({
|
|
53
|
+
featureResolver: {
|
|
54
|
+
resolve: (key) => ({
|
|
55
|
+
key,
|
|
56
|
+
enabled: featureEnabled,
|
|
57
|
+
visible: "visible",
|
|
58
|
+
}),
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
const persistedJob = job(featureChanged.actor);
|
|
62
|
+
await featureChanged.contextualize({
|
|
63
|
+
claim: { job: persistedJob },
|
|
64
|
+
moduleName: "evidence",
|
|
65
|
+
context: baseContext(1),
|
|
66
|
+
});
|
|
67
|
+
featureEnabled = false;
|
|
68
|
+
await expect(
|
|
69
|
+
featureChanged.contextualize({
|
|
70
|
+
claim: { job: persistedJob },
|
|
71
|
+
moduleName: "evidence",
|
|
72
|
+
context: baseContext(2),
|
|
73
|
+
}),
|
|
74
|
+
).rejects.toMatchObject({
|
|
75
|
+
code: "MODULE_DURABLE_GUARD_DENIED",
|
|
76
|
+
details: { reason: "feature-disabled" },
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
const restricted = await fixture({
|
|
80
|
+
licenseEnforced: true,
|
|
81
|
+
licenseResolver: {
|
|
82
|
+
resolve: () => ({ restricted: true, status: "invalid" }),
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
await expect(
|
|
86
|
+
restricted.contextualize({
|
|
87
|
+
claim: { job: job(restricted.actor) },
|
|
88
|
+
moduleName: "evidence",
|
|
89
|
+
context: baseContext(3),
|
|
90
|
+
}),
|
|
91
|
+
).rejects.toMatchObject({
|
|
92
|
+
code: "MODULE_DURABLE_GUARD_DENIED",
|
|
93
|
+
details: { reason: "license-restricted" },
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
async function fixture(
|
|
99
|
+
runtimeOptions: NonNullable<Parameters<typeof createServerRuntime>[1]>,
|
|
100
|
+
) {
|
|
101
|
+
const hosts = await initializeModuleHosts({
|
|
102
|
+
modules: [moduleManifest],
|
|
103
|
+
installations: [
|
|
104
|
+
{
|
|
105
|
+
manifest: moduleManifest,
|
|
106
|
+
setup(host) {
|
|
107
|
+
host.registerDurableHandler({
|
|
108
|
+
key: "evidence.refresh",
|
|
109
|
+
featureKey: "evidence.catalog",
|
|
110
|
+
permissionKey: "evidence.read",
|
|
111
|
+
auditAction: "evidence.file.read",
|
|
112
|
+
payload: { parse: () => ({}) },
|
|
113
|
+
execute: () => undefined,
|
|
114
|
+
});
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
],
|
|
118
|
+
});
|
|
119
|
+
const runtime = createServerRuntime(
|
|
120
|
+
{ NODE_ENV: "test" },
|
|
121
|
+
{ ...runtimeOptions, featureCatalog: [feature] },
|
|
122
|
+
);
|
|
123
|
+
const actor = currentActor();
|
|
124
|
+
return {
|
|
125
|
+
actor,
|
|
126
|
+
contextualize: createModuleDurableContextualizer({
|
|
127
|
+
hosts,
|
|
128
|
+
runtime,
|
|
129
|
+
features: [feature],
|
|
130
|
+
resolveActor: async () => actor,
|
|
131
|
+
}),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function currentActor(): RequestActor {
|
|
136
|
+
return {
|
|
137
|
+
id: "user-reader",
|
|
138
|
+
type: "user",
|
|
139
|
+
name: "物证读取者",
|
|
140
|
+
roleCodes: ["investigator"],
|
|
141
|
+
permissionKeys: ["evidence.read"],
|
|
142
|
+
...buildActorScopeContexts("user-reader", "dept-case", [
|
|
143
|
+
{
|
|
144
|
+
id: "role-investigator",
|
|
145
|
+
code: "investigator",
|
|
146
|
+
dataScope: "all",
|
|
147
|
+
departmentIds: [],
|
|
148
|
+
},
|
|
149
|
+
]),
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function job(actor: RequestActor) {
|
|
154
|
+
return {
|
|
155
|
+
id: "job-evidence",
|
|
156
|
+
handlerKey: "evidence.refresh",
|
|
157
|
+
requestedBy: actor.id,
|
|
158
|
+
authorizationScope: captureAuthorizationScope(actor.scopeContext).snapshot,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function baseContext(attemptNumber: number): DurableHandlerContext {
|
|
163
|
+
return {
|
|
164
|
+
jobId: "job-evidence",
|
|
165
|
+
stageKey: "main",
|
|
166
|
+
attemptNumber,
|
|
167
|
+
signal: new AbortController().signal,
|
|
168
|
+
heartbeat: async () => undefined,
|
|
169
|
+
progress: async () => undefined,
|
|
170
|
+
publishArtifact: async () => undefined,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
@@ -6,6 +6,8 @@ import { captureAuthorizationScope } from "../data-access/scope-snapshot.js";
|
|
|
6
6
|
import type { RequestActor } from "../guards.js";
|
|
7
7
|
import { createServerRuntime } from "../runtime.js";
|
|
8
8
|
import { createModuleDurableContextualizer } from "./durable-context.js";
|
|
9
|
+
import { initializeModuleHosts } from "./initialize.js";
|
|
10
|
+
import { manifest } from "./test-fixtures.js";
|
|
9
11
|
|
|
10
12
|
const feature = {
|
|
11
13
|
key: featureKey("evidence.catalog"),
|
|
@@ -19,8 +21,54 @@ const feature = {
|
|
|
19
21
|
describe("模块 Durable Handler 可选端口", () => {
|
|
20
22
|
test("未挂载 Storage 时仍重建 Actor,并对文件读取 fail-closed", async () => {
|
|
21
23
|
const actor = currentActor();
|
|
24
|
+
const moduleManifest = manifest("evidence", {
|
|
25
|
+
features: [feature],
|
|
26
|
+
permissions: [
|
|
27
|
+
{
|
|
28
|
+
key: "evidence.read",
|
|
29
|
+
label: "读取物证",
|
|
30
|
+
module: "evidence",
|
|
31
|
+
moduleLabel: "物证",
|
|
32
|
+
resource: "file",
|
|
33
|
+
resourceLabel: "文件",
|
|
34
|
+
action: "read",
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
tasks: [
|
|
38
|
+
{
|
|
39
|
+
key: "evidence.refresh",
|
|
40
|
+
label: "刷新物证",
|
|
41
|
+
description: "刷新物证目录。",
|
|
42
|
+
permissionKey: "evidence.read",
|
|
43
|
+
featureKey: "evidence.catalog",
|
|
44
|
+
auditAction: "evidence.file.read",
|
|
45
|
+
intervalSeconds: 60,
|
|
46
|
+
maxAttempts: 3,
|
|
47
|
+
retryDelaySeconds: 5,
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
auditActions: ["evidence.file.read"],
|
|
51
|
+
});
|
|
52
|
+
const hosts = await initializeModuleHosts({
|
|
53
|
+
modules: [moduleManifest],
|
|
54
|
+
installations: [
|
|
55
|
+
{
|
|
56
|
+
manifest: moduleManifest,
|
|
57
|
+
setup(host) {
|
|
58
|
+
host.registerDurableHandler({
|
|
59
|
+
key: "evidence.refresh",
|
|
60
|
+
featureKey: "evidence.catalog",
|
|
61
|
+
permissionKey: "evidence.read",
|
|
62
|
+
auditAction: "evidence.file.read",
|
|
63
|
+
payload: { parse: () => ({}) },
|
|
64
|
+
execute: () => undefined,
|
|
65
|
+
});
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
});
|
|
22
70
|
const contextualize = createModuleDurableContextualizer({
|
|
23
|
-
hosts
|
|
71
|
+
hosts,
|
|
24
72
|
runtime: createServerRuntime(
|
|
25
73
|
{ NODE_ENV: "test" },
|
|
26
74
|
{ featureCatalog: [feature] },
|
|
@@ -66,6 +114,7 @@ function currentActor(): RequestActor {
|
|
|
66
114
|
function job(actor: RequestActor) {
|
|
67
115
|
return {
|
|
68
116
|
id: "job-evidence",
|
|
117
|
+
handlerKey: "evidence.refresh",
|
|
69
118
|
requestedBy: actor.id,
|
|
70
119
|
authorizationScope: captureAuthorizationScope(actor.scopeContext).snapshot,
|
|
71
120
|
};
|
|
@@ -34,6 +34,19 @@ const evidenceManifest = manifest("evidence", {
|
|
|
34
34
|
},
|
|
35
35
|
],
|
|
36
36
|
auditActions: ["evidence.file.read"],
|
|
37
|
+
tasks: [
|
|
38
|
+
{
|
|
39
|
+
key: "evidence.refresh",
|
|
40
|
+
label: "刷新物证",
|
|
41
|
+
description: "刷新物证目录。",
|
|
42
|
+
permissionKey: "evidence.read",
|
|
43
|
+
featureKey: "evidence.catalog",
|
|
44
|
+
auditAction: "evidence.file.read",
|
|
45
|
+
intervalSeconds: 60,
|
|
46
|
+
maxAttempts: 3,
|
|
47
|
+
retryDelaySeconds: 5,
|
|
48
|
+
},
|
|
49
|
+
],
|
|
37
50
|
});
|
|
38
51
|
|
|
39
52
|
describe("模块 Durable Handler 授权上下文", () => {
|
|
@@ -137,7 +150,7 @@ describe("模块 Durable Handler 授权上下文", () => {
|
|
|
137
150
|
// @windy-module system.storage end
|
|
138
151
|
|
|
139
152
|
// @windy-module system.storage begin
|
|
140
|
-
test("Scope
|
|
153
|
+
test("Scope 变化与权限撤销均在执行前 fail-closed", async () => {
|
|
141
154
|
const stable = currentActor();
|
|
142
155
|
const scopeChanged = currentActor("self");
|
|
143
156
|
const setupScopeChanged = await setup(() => scopeChanged);
|
|
@@ -152,37 +165,32 @@ describe("模块 Durable Handler 授权上下文", () => {
|
|
|
152
165
|
retryable: false,
|
|
153
166
|
});
|
|
154
167
|
|
|
155
|
-
|
|
156
|
-
const revoked = await setup(() =>
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
168
|
+
let permissionGranted = true;
|
|
169
|
+
const revoked = await setup(() => ({
|
|
170
|
+
...stable,
|
|
171
|
+
permissionKeys: permissionGranted ? ["evidence.read"] : [],
|
|
172
|
+
}));
|
|
173
|
+
const persistedJob = job(stable);
|
|
174
|
+
await revoked.contextualize({
|
|
175
|
+
claim: { job: persistedJob },
|
|
160
176
|
moduleName: "evidence",
|
|
161
177
|
context: baseContext(1),
|
|
162
178
|
});
|
|
163
|
-
|
|
164
|
-
context as typeof context & {
|
|
165
|
-
storage: {
|
|
166
|
-
openResourceFile(input: {
|
|
167
|
-
fileId: string;
|
|
168
|
-
purpose: string;
|
|
169
|
-
resourceType: string;
|
|
170
|
-
resourceId: string;
|
|
171
|
-
}): Promise<unknown>;
|
|
172
|
-
};
|
|
173
|
-
}
|
|
174
|
-
).storage;
|
|
179
|
+
permissionGranted = false;
|
|
175
180
|
await expect(
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
resourceId: "evidence-1",
|
|
181
|
+
revoked.contextualize({
|
|
182
|
+
claim: { job: persistedJob },
|
|
183
|
+
moduleName: "evidence",
|
|
184
|
+
context: baseContext(2),
|
|
181
185
|
}),
|
|
182
|
-
).rejects.
|
|
186
|
+
).rejects.toMatchObject({
|
|
187
|
+
code: "MODULE_DURABLE_GUARD_DENIED",
|
|
188
|
+
retryable: false,
|
|
189
|
+
details: { reason: "missing-permission" },
|
|
190
|
+
});
|
|
183
191
|
expect(revoked.runtime.auditSink.list()).toContainEqual(
|
|
184
192
|
expect.objectContaining({
|
|
185
|
-
action: "
|
|
193
|
+
action: "security.denied",
|
|
186
194
|
outcome: "denied",
|
|
187
195
|
}),
|
|
188
196
|
);
|
|
@@ -222,6 +230,14 @@ async function setup(
|
|
|
222
230
|
context.actor.id === "user-reader",
|
|
223
231
|
},
|
|
224
232
|
]);
|
|
233
|
+
host.registerDurableHandler({
|
|
234
|
+
key: "evidence.refresh",
|
|
235
|
+
featureKey: "evidence.catalog",
|
|
236
|
+
permissionKey: "evidence.read",
|
|
237
|
+
auditAction: "evidence.file.read",
|
|
238
|
+
payload: { parse: () => ({}) },
|
|
239
|
+
execute: () => undefined,
|
|
240
|
+
});
|
|
225
241
|
},
|
|
226
242
|
},
|
|
227
243
|
],
|
|
@@ -278,6 +294,7 @@ function currentActor(scope: "all" | "self" = "all"): RequestActor {
|
|
|
278
294
|
function job(actor: RequestActor) {
|
|
279
295
|
return {
|
|
280
296
|
id: "job-evidence",
|
|
297
|
+
handlerKey: "evidence.refresh",
|
|
281
298
|
requestedBy: actor.id,
|
|
282
299
|
authorizationScope: captureAuthorizationScope(actor.scopeContext).snapshot,
|
|
283
300
|
};
|