create-windy 0.3.19 → 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 +783 -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/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/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
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import {
|
|
2
|
+
validateModuleComposition,
|
|
3
|
+
type ModuleManifest,
|
|
4
|
+
} from "@southwind-ai/modules";
|
|
5
|
+
import type {
|
|
6
|
+
InstallableServerModule,
|
|
7
|
+
ModuleUploadPolicy,
|
|
8
|
+
} from "@southwind-ai/server-sdk";
|
|
9
|
+
import { loadFoundationModules } from "./foundation.js";
|
|
10
|
+
import { serverCompositionDeclaration } from "./app/server-composition.js";
|
|
11
|
+
import { initializeModuleHosts } from "./module-host.js";
|
|
12
|
+
import { validateHostedModuleImplementations } from "./module-host/implementation-completeness.js";
|
|
13
|
+
import { validateProjectServerAttachments } from "./module-host/project-attachments.js";
|
|
14
|
+
import {
|
|
15
|
+
runtimeBusinessModules,
|
|
16
|
+
serverComposition,
|
|
17
|
+
} from "./module-host/runtime-composition.js";
|
|
18
|
+
import type {
|
|
19
|
+
WindyServerModuleAdapter,
|
|
20
|
+
WindyServerModuleAttachment,
|
|
21
|
+
} from "@southwind-ai/server-sdk";
|
|
22
|
+
|
|
23
|
+
export async function prepareServerModules(input: {
|
|
24
|
+
readonly modules: readonly ModuleManifest[];
|
|
25
|
+
readonly installations: readonly InstallableServerModule<ModuleManifest>[];
|
|
26
|
+
readonly baseUploadPolicies?: readonly ModuleUploadPolicy[];
|
|
27
|
+
readonly adapters?: readonly WindyServerModuleAdapter<ModuleManifest>[];
|
|
28
|
+
readonly attachments?: readonly WindyServerModuleAttachment<unknown>[];
|
|
29
|
+
readonly enabledModuleNames?: ReadonlySet<string>;
|
|
30
|
+
}) {
|
|
31
|
+
const foundationModules = [...input.modules];
|
|
32
|
+
const canonicalInstallations = input.installations.map((installation) => ({
|
|
33
|
+
source: installation,
|
|
34
|
+
manifest: installation.manifest,
|
|
35
|
+
fingerprint: JSON.stringify(installation.manifest),
|
|
36
|
+
installation: {
|
|
37
|
+
manifest: installation.manifest,
|
|
38
|
+
setup: installation.setup,
|
|
39
|
+
setupSandbox: installation.setupSandbox,
|
|
40
|
+
} satisfies InstallableServerModule<ModuleManifest>,
|
|
41
|
+
}));
|
|
42
|
+
validateModuleComposition(foundationModules);
|
|
43
|
+
validateProjectServerAttachments({
|
|
44
|
+
attachments: input.attachments ?? [],
|
|
45
|
+
adapters: input.adapters ?? [],
|
|
46
|
+
enabledModuleNames: input.enabledModuleNames ?? new Set(),
|
|
47
|
+
});
|
|
48
|
+
const moduleHosts = await initializeModuleHosts({
|
|
49
|
+
modules: foundationModules,
|
|
50
|
+
installations: canonicalInstallations.map(
|
|
51
|
+
({ installation }) => installation,
|
|
52
|
+
),
|
|
53
|
+
baseUploadPolicies: input.baseUploadPolicies,
|
|
54
|
+
});
|
|
55
|
+
assertCanonicalManifestIntegrity(canonicalInstallations);
|
|
56
|
+
validateModuleComposition(foundationModules);
|
|
57
|
+
validateHostedModuleImplementations(
|
|
58
|
+
canonicalInstallations.map(({ installation }) => installation),
|
|
59
|
+
moduleHosts,
|
|
60
|
+
);
|
|
61
|
+
return { foundationModules, moduleHosts };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function assertCanonicalManifestIntegrity(
|
|
65
|
+
installations: readonly {
|
|
66
|
+
readonly source: InstallableServerModule<ModuleManifest>;
|
|
67
|
+
readonly manifest: ModuleManifest;
|
|
68
|
+
readonly fingerprint: string;
|
|
69
|
+
}[],
|
|
70
|
+
): void {
|
|
71
|
+
for (const installation of installations) {
|
|
72
|
+
if (installation.source.manifest !== installation.manifest) {
|
|
73
|
+
throw new Error(
|
|
74
|
+
`Server Module 在 setup 期间替换了 canonical Manifest:${installation.manifest.name}`,
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
if (JSON.stringify(installation.manifest) !== installation.fingerprint) {
|
|
78
|
+
throw new Error(
|
|
79
|
+
`Server Module 在 setup 期间修改了 canonical Manifest:${installation.manifest.name}`,
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function prepareInstalledServerModules(
|
|
86
|
+
baseUploadPolicies: readonly ModuleUploadPolicy[],
|
|
87
|
+
) {
|
|
88
|
+
return prepareServerModules({
|
|
89
|
+
modules: loadFoundationModules(),
|
|
90
|
+
installations: runtimeBusinessModules,
|
|
91
|
+
baseUploadPolicies,
|
|
92
|
+
adapters: serverCompositionDeclaration.adapters,
|
|
93
|
+
attachments: serverCompositionDeclaration.attachments,
|
|
94
|
+
enabledModuleNames: serverComposition.enabledModuleNames,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
@@ -16,6 +16,7 @@ export function validateInstalledRuntime(input: {
|
|
|
16
16
|
validateServerModuleComposition({
|
|
17
17
|
modules: input.modules,
|
|
18
18
|
routes: input.routes,
|
|
19
|
+
frameworkRoutes: ["GET /api/swagger", "GET /api/swagger/json"],
|
|
19
20
|
tasks: input.tasks,
|
|
20
21
|
tools: input.tools,
|
|
21
22
|
aiOperations,
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { ModuleManifest } from "@southwind-ai/modules";
|
|
2
|
+
import type { FeatureFlagDefinition } from "@southwind-ai/shared";
|
|
3
|
+
import type { AuthRepository } from "./auth/repository.js";
|
|
4
|
+
import type { AuthService } from "./auth/service.js";
|
|
5
|
+
// @windy-module system.license begin
|
|
6
|
+
import type { PlatformLicenseRuntime } from "./license/runtime-service.js";
|
|
7
|
+
import { registerPlatformLicenseRoutes } from "./license/routes.js";
|
|
8
|
+
// @windy-module system.license end
|
|
9
|
+
import {
|
|
10
|
+
createManifestAuthorizedRouteApp,
|
|
11
|
+
createStaticRouteAuthorizer,
|
|
12
|
+
} from "./module-host/static-route-authorization.js";
|
|
13
|
+
import type { createServerPersistence } from "./persistence.js";
|
|
14
|
+
import {
|
|
15
|
+
createPlatformServerApp,
|
|
16
|
+
type PlatformServerAppInput,
|
|
17
|
+
} from "./platform-http-runtime.js";
|
|
18
|
+
import type { createServerRuntime } from "./runtime.js";
|
|
19
|
+
import { registerSessionContextRoute } from "./session/routes.js";
|
|
20
|
+
|
|
21
|
+
interface PlatformAppBootstrapInput extends Omit<
|
|
22
|
+
PlatformServerAppInput,
|
|
23
|
+
"runtime" | "persistence" | "features"
|
|
24
|
+
> {
|
|
25
|
+
runtime: ReturnType<typeof createServerRuntime>;
|
|
26
|
+
persistence: ReturnType<typeof createServerPersistence>;
|
|
27
|
+
authRepository: AuthRepository;
|
|
28
|
+
authService: AuthService;
|
|
29
|
+
features: FeatureFlagDefinition[];
|
|
30
|
+
modules: readonly ModuleManifest[];
|
|
31
|
+
// @windy-module system.license begin
|
|
32
|
+
licenseRuntime: PlatformLicenseRuntime;
|
|
33
|
+
// @windy-module system.license end
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function bootstrapPlatformApp(input: PlatformAppBootstrapInput) {
|
|
37
|
+
const app = await createPlatformServerApp(input);
|
|
38
|
+
const authorizer = createStaticRouteAuthorizer(input.runtime, input.modules);
|
|
39
|
+
// @windy-module system.license begin
|
|
40
|
+
registerPlatformLicenseRoutes(app, input.licenseRuntime, authorizer);
|
|
41
|
+
// @windy-module system.license end
|
|
42
|
+
registerSessionContextRoute(
|
|
43
|
+
createManifestAuthorizedRouteApp(app, authorizer, () => ({
|
|
44
|
+
immutableCoreAccess: true,
|
|
45
|
+
})),
|
|
46
|
+
input.runtime,
|
|
47
|
+
input.features,
|
|
48
|
+
);
|
|
49
|
+
return app;
|
|
50
|
+
}
|
|
@@ -15,8 +15,13 @@ import { registerProfileRoutes } from "./profile/routes.js";
|
|
|
15
15
|
import type { createServerRuntime } from "./runtime.js";
|
|
16
16
|
import type { SystemRepositories } from "./system/seed.js";
|
|
17
17
|
import type { FeatureManifestSyncResult } from "./feature/manifest-feature-repository.js";
|
|
18
|
+
import type { ModuleManifest } from "@southwind-ai/modules";
|
|
19
|
+
import {
|
|
20
|
+
createManifestAuthorizedRouteApp,
|
|
21
|
+
createStaticRouteAuthorizer,
|
|
22
|
+
} from "./module-host/static-route-authorization.js";
|
|
18
23
|
|
|
19
|
-
interface
|
|
24
|
+
export interface PlatformServerAppInput {
|
|
20
25
|
runtime: ReturnType<typeof createServerRuntime>;
|
|
21
26
|
persistence: ReturnType<typeof createServerPersistence>;
|
|
22
27
|
platformConfig: PlatformConfig;
|
|
@@ -26,6 +31,7 @@ interface CreatePlatformServerAppInput {
|
|
|
26
31
|
features: FeatureFlagDefinition[];
|
|
27
32
|
logger: StructuredRequestLogger;
|
|
28
33
|
observability: ReturnType<typeof createPlatformObservability>;
|
|
34
|
+
modules: readonly ModuleManifest[];
|
|
29
35
|
}
|
|
30
36
|
|
|
31
37
|
export function createHttpObservability(
|
|
@@ -44,9 +50,7 @@ export function createHttpObservability(
|
|
|
44
50
|
});
|
|
45
51
|
}
|
|
46
52
|
|
|
47
|
-
export async function createPlatformServerApp(
|
|
48
|
-
input: CreatePlatformServerAppInput,
|
|
49
|
-
) {
|
|
53
|
+
export async function createPlatformServerApp(input: PlatformServerAppInput) {
|
|
50
54
|
await bootstrapAdminPassword({
|
|
51
55
|
authService: input.authService,
|
|
52
56
|
authRepository: input.persistence?.authRepository,
|
|
@@ -54,9 +58,22 @@ export async function createPlatformServerApp(
|
|
|
54
58
|
password: process.env.WINDY_BOOTSTRAP_ADMIN_PASSWORD,
|
|
55
59
|
});
|
|
56
60
|
const app = createPlatformHttpApp(input.runtime, input.observability);
|
|
57
|
-
input.
|
|
58
|
-
|
|
59
|
-
|
|
61
|
+
const authorizer = createStaticRouteAuthorizer(input.runtime, input.modules);
|
|
62
|
+
const guardedApp = createManifestAuthorizedRouteApp(
|
|
63
|
+
app,
|
|
64
|
+
authorizer,
|
|
65
|
+
(method, path) =>
|
|
66
|
+
immutableCoreRoutes.has(`${method} /api${path}`)
|
|
67
|
+
? { immutableCoreAccess: true }
|
|
68
|
+
: undefined,
|
|
69
|
+
);
|
|
70
|
+
input.observability.register(
|
|
71
|
+
createManifestAuthorizedRouteApp(app, authorizer, () => ({
|
|
72
|
+
immutableCoreAccess: true,
|
|
73
|
+
})),
|
|
74
|
+
);
|
|
75
|
+
registerAuthSessionRoute(guardedApp);
|
|
76
|
+
registerAuthRoutes(guardedApp, input.authService, {
|
|
60
77
|
sessionCookieName: input.platformConfig.security.sessionCookieName,
|
|
61
78
|
secureCookie: process.env.NODE_ENV === "production",
|
|
62
79
|
});
|
|
@@ -69,6 +86,14 @@ export async function createPlatformServerApp(
|
|
|
69
86
|
return app;
|
|
70
87
|
}
|
|
71
88
|
|
|
89
|
+
const immutableCoreRoutes = new Set([
|
|
90
|
+
"GET /api/auth/session",
|
|
91
|
+
"POST /api/auth/login",
|
|
92
|
+
"POST /api/auth/api/login",
|
|
93
|
+
"GET /api/auth/password/policy",
|
|
94
|
+
"POST /api/auth/logout",
|
|
95
|
+
]);
|
|
96
|
+
|
|
72
97
|
export function listenPlatformServer(
|
|
73
98
|
app: ReturnType<typeof createPlatformHttpApp>,
|
|
74
99
|
): number {
|
|
@@ -5,6 +5,10 @@ import {
|
|
|
5
5
|
licenseVersionKey,
|
|
6
6
|
} from "@southwind-ai/shared";
|
|
7
7
|
import { evaluateAccessGuards } from "./access-guards.js";
|
|
8
|
+
import {
|
|
9
|
+
systemFeatureKeyForPermission,
|
|
10
|
+
systemFeatureKeyForResource,
|
|
11
|
+
} from "./route-guards.js";
|
|
8
12
|
import { createServerRuntime } from "./runtime.js";
|
|
9
13
|
|
|
10
14
|
const commercialFeature = {
|
|
@@ -18,6 +22,24 @@ const commercialFeature = {
|
|
|
18
22
|
};
|
|
19
23
|
|
|
20
24
|
describe("access guard pipeline", () => {
|
|
25
|
+
test("Permission 默认映射到同基础 Key Feature", () => {
|
|
26
|
+
expect(systemFeatureKeyForPermission("system.user.read")).toBe(
|
|
27
|
+
"system.user",
|
|
28
|
+
);
|
|
29
|
+
expect(systemFeatureKeyForPermission("business.report.manage")).toBe(
|
|
30
|
+
"business.report",
|
|
31
|
+
);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("系统资源 Route 使用资源级 Feature", () => {
|
|
35
|
+
expect(systemFeatureKeyForResource("user")).toBe("system.user");
|
|
36
|
+
expect(systemFeatureKeyForResource("role")).toBe("system.role");
|
|
37
|
+
expect(systemFeatureKeyForResource("department")).toBe("system.department");
|
|
38
|
+
expect(systemFeatureKeyForResource("menu")).toBe("system.menu");
|
|
39
|
+
expect(systemFeatureKeyForResource("dict")).toBe("system.dict");
|
|
40
|
+
expect(systemFeatureKeyForResource("ops")).toBe("system.ops");
|
|
41
|
+
});
|
|
42
|
+
|
|
21
43
|
test.each([
|
|
22
44
|
{
|
|
23
45
|
name: "缺少 Entitlement 优先拒绝",
|
|
@@ -40,15 +40,22 @@ export function findFeature(
|
|
|
40
40
|
|
|
41
41
|
export function systemFeatureKeyForResource(resourceName: string): string {
|
|
42
42
|
const resourceFeatures: Record<string, string> = {
|
|
43
|
+
admin: "system.admin",
|
|
43
44
|
agent: "system.agent",
|
|
44
45
|
audit: "system.audit",
|
|
46
|
+
department: "system.department",
|
|
47
|
+
dict: "system.dict",
|
|
48
|
+
feature: "system.feature",
|
|
45
49
|
license: "system.license",
|
|
50
|
+
menu: "system.menu",
|
|
46
51
|
ops: "system.ops",
|
|
47
52
|
notification: "system.notification",
|
|
53
|
+
role: "system.role",
|
|
48
54
|
scheduler: "system.scheduler",
|
|
49
55
|
settings: "system.settings",
|
|
56
|
+
user: "system.user",
|
|
50
57
|
};
|
|
51
|
-
return resourceFeatures[resourceName] ||
|
|
58
|
+
return resourceFeatures[resourceName] || `system.${resourceName}`;
|
|
52
59
|
}
|
|
53
60
|
|
|
54
61
|
export function findSystemFeature(
|
|
@@ -59,6 +66,6 @@ export function findSystemFeature(
|
|
|
59
66
|
}
|
|
60
67
|
|
|
61
68
|
export function systemFeatureKeyForPermission(permissionKey: string): string {
|
|
62
|
-
const
|
|
63
|
-
return
|
|
69
|
+
const separator = permissionKey.lastIndexOf(".");
|
|
70
|
+
return separator > 0 ? permissionKey.slice(0, separator) : "";
|
|
64
71
|
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { AuditEvent } from "@southwind-ai/shared";
|
|
2
|
+
|
|
3
|
+
export interface AuditEventListQuery {
|
|
4
|
+
page: number;
|
|
5
|
+
pageSize: number;
|
|
6
|
+
q?: string;
|
|
7
|
+
actorId?: string;
|
|
8
|
+
action?: string;
|
|
9
|
+
resource?: string;
|
|
10
|
+
resourceId?: string;
|
|
11
|
+
occurredAfter?: string;
|
|
12
|
+
occurredBefore?: string;
|
|
13
|
+
includeScheduler?: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function matchesAuditQuery(
|
|
17
|
+
event: AuditEvent,
|
|
18
|
+
query: AuditEventListQuery,
|
|
19
|
+
): boolean {
|
|
20
|
+
return (
|
|
21
|
+
(!query.q || matchesAuditKeyword(event, query.q)) &&
|
|
22
|
+
(!query.actorId || event.actor.id === query.actorId) &&
|
|
23
|
+
(!query.action || event.action === query.action) &&
|
|
24
|
+
(!query.resource || event.target?.type === query.resource) &&
|
|
25
|
+
(!query.resourceId || event.target?.id === query.resourceId) &&
|
|
26
|
+
(query.includeScheduler || event.actor.id !== "system_scheduler") &&
|
|
27
|
+
(!query.occurredAfter || event.occurredAt >= query.occurredAfter) &&
|
|
28
|
+
(!query.occurredBefore || event.occurredAt <= query.occurredBefore)
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function matchesAuditKeyword(event: AuditEvent, query: string): boolean {
|
|
33
|
+
const keyword = query.toLowerCase();
|
|
34
|
+
const values = [
|
|
35
|
+
event.action,
|
|
36
|
+
event.actor.id,
|
|
37
|
+
event.actor.name,
|
|
38
|
+
event.target?.type,
|
|
39
|
+
event.target?.id,
|
|
40
|
+
event.target?.label,
|
|
41
|
+
event.actor.ip,
|
|
42
|
+
event.message,
|
|
43
|
+
event.module,
|
|
44
|
+
event.requestId,
|
|
45
|
+
event.metadata ? JSON.stringify(event.metadata) : undefined,
|
|
46
|
+
];
|
|
47
|
+
return values.some((value) =>
|
|
48
|
+
value === undefined ? false : String(value).toLowerCase().includes(keyword),
|
|
49
|
+
);
|
|
50
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { featureKey, type FeatureFlagDefinition } from "@southwind-ai/shared";
|
|
3
|
+
import { createRequestGuardContext } from "./guards.js";
|
|
4
|
+
import { RuntimeFeatureCatalog } from "./runtime-feature.js";
|
|
5
|
+
|
|
6
|
+
describe("运行时 Feature 的 Module 激活上限", () => {
|
|
7
|
+
test("数据库状态不能重新启用装配清单已停用的 Module", async () => {
|
|
8
|
+
const manifest = feature("system.ops", "system.operations");
|
|
9
|
+
const catalog = new RuntimeFeatureCatalog(
|
|
10
|
+
[manifest],
|
|
11
|
+
{
|
|
12
|
+
resolve: () => ({
|
|
13
|
+
key: manifest.key,
|
|
14
|
+
enabled: true,
|
|
15
|
+
visible: "visible",
|
|
16
|
+
}),
|
|
17
|
+
},
|
|
18
|
+
true,
|
|
19
|
+
new Set(["system.operations"]),
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
await expect(catalog.resolve(context(), manifest)).resolves.toMatchObject({
|
|
23
|
+
enabled: false,
|
|
24
|
+
visible: "hidden",
|
|
25
|
+
disabledReason: "disabled",
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
function feature(key: string, module: string): FeatureFlagDefinition {
|
|
31
|
+
return {
|
|
32
|
+
key: featureKey(key),
|
|
33
|
+
module,
|
|
34
|
+
label: key,
|
|
35
|
+
enabled: true,
|
|
36
|
+
visible: "visible",
|
|
37
|
+
stage: "stable",
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function context() {
|
|
42
|
+
return createRequestGuardContext(new Request("http://localhost/api/test"), {
|
|
43
|
+
resolveActor: () => undefined,
|
|
44
|
+
resolveLicense: () => undefined,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
@@ -38,6 +38,7 @@ export class RuntimeFeatureCatalog {
|
|
|
38
38
|
definitions: readonly FeatureFlagDefinition[],
|
|
39
39
|
private readonly resolver: FeatureFlagRuntimeResolver | undefined,
|
|
40
40
|
private readonly production: boolean,
|
|
41
|
+
private readonly disabledModuleNames: ReadonlySet<string> = new Set(),
|
|
41
42
|
) {
|
|
42
43
|
for (const definition of definitions) {
|
|
43
44
|
this.definitions.set(definition.key, definition);
|
|
@@ -107,6 +108,9 @@ export class RuntimeFeatureCatalog {
|
|
|
107
108
|
private async resolveOwnState(
|
|
108
109
|
manifestFeature: FeatureFlagDefinition,
|
|
109
110
|
): Promise<FeatureFlagDefinition> {
|
|
111
|
+
if (this.disabledModuleNames.has(manifestFeature.module)) {
|
|
112
|
+
return disabledFeature(manifestFeature);
|
|
113
|
+
}
|
|
110
114
|
if (!this.resolver) {
|
|
111
115
|
return this.production
|
|
112
116
|
? disabledFeature(manifestFeature)
|
|
@@ -144,6 +148,7 @@ function isFeatureLicensedForContext(
|
|
|
144
148
|
feature: FeatureFlagDefinition,
|
|
145
149
|
context: RequestGuardContext,
|
|
146
150
|
): boolean {
|
|
151
|
+
if (context.licenseEnforced === false) return true;
|
|
147
152
|
return (
|
|
148
153
|
isLicenseVersionAllowed(feature, context.license?.versionKey) &&
|
|
149
154
|
(!feature.requiredEntitlement ||
|
|
@@ -32,6 +32,10 @@ import {
|
|
|
32
32
|
resolveRequestSource,
|
|
33
33
|
} from "./http/request-source.js";
|
|
34
34
|
import { resolveRequestActor } from "./runtime-request-actor.js";
|
|
35
|
+
import {
|
|
36
|
+
matchesAuditQuery,
|
|
37
|
+
type AuditEventListQuery,
|
|
38
|
+
} from "./runtime-audit-query.js";
|
|
35
39
|
// @windy-module system.agent begin
|
|
36
40
|
import {
|
|
37
41
|
createAdkDebugRuntime,
|
|
@@ -44,6 +48,7 @@ export type {
|
|
|
44
48
|
FeatureFlagRuntimeState,
|
|
45
49
|
ResolvedFeatureFlag,
|
|
46
50
|
} from "./runtime-feature.js";
|
|
51
|
+
export type { AuditEventListQuery } from "./runtime-audit-query.js";
|
|
47
52
|
|
|
48
53
|
export interface SessionRecord {
|
|
49
54
|
token: string;
|
|
@@ -61,19 +66,6 @@ export interface AuditEventWriter {
|
|
|
61
66
|
status?(): AuditWriteStatus;
|
|
62
67
|
}
|
|
63
68
|
|
|
64
|
-
export interface AuditEventListQuery {
|
|
65
|
-
page: number;
|
|
66
|
-
pageSize: number;
|
|
67
|
-
q?: string;
|
|
68
|
-
actorId?: string;
|
|
69
|
-
action?: string;
|
|
70
|
-
resource?: string;
|
|
71
|
-
resourceId?: string;
|
|
72
|
-
occurredAfter?: string;
|
|
73
|
-
occurredBefore?: string;
|
|
74
|
-
includeScheduler?: boolean;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
69
|
export interface AuditEventReader {
|
|
78
70
|
list(query: AuditEventListQuery): Promise<PageResult<AuditEvent>>;
|
|
79
71
|
}
|
|
@@ -81,8 +73,10 @@ export interface AuditEventReader {
|
|
|
81
73
|
export interface ServerRuntimeOptions {
|
|
82
74
|
actorResolver?: RequestActorResolver;
|
|
83
75
|
licenseResolver?: LicenseEntitlementResolver;
|
|
76
|
+
licenseEnforced?: boolean;
|
|
84
77
|
featureResolver?: FeatureFlagRuntimeResolver;
|
|
85
78
|
featureCatalog?: readonly FeatureFlagDefinition[];
|
|
79
|
+
disabledModuleNames?: ReadonlySet<string>;
|
|
86
80
|
auditWriter?: AuditEventWriter;
|
|
87
81
|
auditReader?: AuditEventReader;
|
|
88
82
|
// @windy-module system.agent begin
|
|
@@ -225,6 +219,7 @@ export function createServerRuntime(
|
|
|
225
219
|
options.featureCatalog || [],
|
|
226
220
|
options.featureResolver,
|
|
227
221
|
env.NODE_ENV === "production",
|
|
222
|
+
options.disabledModuleNames ?? new Set(),
|
|
228
223
|
);
|
|
229
224
|
const requestSourcePolicy = requestSourcePolicyFromEnv(env);
|
|
230
225
|
|
|
@@ -254,6 +249,7 @@ export function createServerRuntime(
|
|
|
254
249
|
...requestSource,
|
|
255
250
|
resolveActor: () => actor,
|
|
256
251
|
resolveLicense: () => license,
|
|
252
|
+
licenseEnforced: options.licenseEnforced,
|
|
257
253
|
});
|
|
258
254
|
},
|
|
259
255
|
// @windy-module system.agent begin
|
|
@@ -276,6 +272,7 @@ export function createServerRuntime(
|
|
|
276
272
|
...requestSource,
|
|
277
273
|
resolveActor: () => actor,
|
|
278
274
|
resolveLicense: () => license,
|
|
275
|
+
licenseEnforced: options.licenseEnforced,
|
|
279
276
|
});
|
|
280
277
|
},
|
|
281
278
|
async resolveFeature(
|
|
@@ -311,39 +308,3 @@ export function createServerRuntime(
|
|
|
311
308
|
},
|
|
312
309
|
};
|
|
313
310
|
}
|
|
314
|
-
|
|
315
|
-
function matchesAuditQuery(
|
|
316
|
-
event: AuditEvent,
|
|
317
|
-
query: AuditEventListQuery,
|
|
318
|
-
): boolean {
|
|
319
|
-
return (
|
|
320
|
-
(!query.q || matchesAuditKeyword(event, query.q)) &&
|
|
321
|
-
(!query.actorId || event.actor.id === query.actorId) &&
|
|
322
|
-
(!query.action || event.action === query.action) &&
|
|
323
|
-
(!query.resource || event.target?.type === query.resource) &&
|
|
324
|
-
(!query.resourceId || event.target?.id === query.resourceId) &&
|
|
325
|
-
(query.includeScheduler || event.actor.id !== "system_scheduler") &&
|
|
326
|
-
(!query.occurredAfter || event.occurredAt >= query.occurredAfter) &&
|
|
327
|
-
(!query.occurredBefore || event.occurredAt <= query.occurredBefore)
|
|
328
|
-
);
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
function matchesAuditKeyword(event: AuditEvent, query: string): boolean {
|
|
332
|
-
const keyword = query.toLowerCase();
|
|
333
|
-
const values = [
|
|
334
|
-
event.action,
|
|
335
|
-
event.actor.id,
|
|
336
|
-
event.actor.name,
|
|
337
|
-
event.target?.type,
|
|
338
|
-
event.target?.id,
|
|
339
|
-
event.target?.label,
|
|
340
|
-
event.actor.ip,
|
|
341
|
-
event.message,
|
|
342
|
-
event.module,
|
|
343
|
-
event.requestId,
|
|
344
|
-
event.metadata ? JSON.stringify(event.metadata) : undefined,
|
|
345
|
-
];
|
|
346
|
-
return values.some((value) =>
|
|
347
|
-
value === undefined ? false : String(value).toLowerCase().includes(keyword),
|
|
348
|
-
);
|
|
349
|
-
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { expect, test } from "bun:test";
|
|
2
|
+
import { createFoundationSnapshot } from "../foundation.js";
|
|
3
|
+
import { createServerRuntime } from "../runtime.js";
|
|
4
|
+
import { InMemorySchedulerRepository } from "./repository.js";
|
|
5
|
+
import { SchedulerService } from "./service.js";
|
|
6
|
+
|
|
7
|
+
test("任务执行审计使用 Manifest 投影的动作", async () => {
|
|
8
|
+
const feature = createFoundationSnapshot().features.find(
|
|
9
|
+
({ key }) => key === "system.workflow",
|
|
10
|
+
);
|
|
11
|
+
if (!feature) throw new Error("测试缺少 system.workflow Feature");
|
|
12
|
+
const runtime = createServerRuntime({
|
|
13
|
+
WINDY_DEV_ADMIN_TOKEN: "token",
|
|
14
|
+
});
|
|
15
|
+
const scheduler = new SchedulerService(
|
|
16
|
+
new InMemorySchedulerRepository(),
|
|
17
|
+
[
|
|
18
|
+
{
|
|
19
|
+
key: "system.workflow.timeout-test",
|
|
20
|
+
label: "审批超时测试",
|
|
21
|
+
intervalSeconds: 60,
|
|
22
|
+
maxAttempts: 3,
|
|
23
|
+
retryDelaySeconds: 10,
|
|
24
|
+
permissionKey: "system.workflow.manage",
|
|
25
|
+
auditAction: "workflow.timeout",
|
|
26
|
+
feature,
|
|
27
|
+
handler: () => undefined,
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
runtime,
|
|
31
|
+
);
|
|
32
|
+
await scheduler.initialize();
|
|
33
|
+
const context = await runtime.createGuardContext(
|
|
34
|
+
new Request(
|
|
35
|
+
"http://localhost/api/system/scheduled-tasks/system.workflow.timeout-test/trigger",
|
|
36
|
+
{ headers: { authorization: "Bearer token" } },
|
|
37
|
+
),
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
expect(
|
|
41
|
+
await scheduler.triggerManual("system.workflow.timeout-test", context),
|
|
42
|
+
).toMatchObject({ ok: true });
|
|
43
|
+
expect(runtime.auditSink.list()).toContainEqual(
|
|
44
|
+
expect.objectContaining({
|
|
45
|
+
action: "workflow.timeout",
|
|
46
|
+
outcome: "success",
|
|
47
|
+
}),
|
|
48
|
+
);
|
|
49
|
+
});
|
|
@@ -6,7 +6,7 @@ import type { ScheduledTaskRun } from "./types.js";
|
|
|
6
6
|
export function schedulerAuditEvent(
|
|
7
7
|
context: RequestGuardContext,
|
|
8
8
|
run: ScheduledTaskRun,
|
|
9
|
-
action: "
|
|
9
|
+
action: AuditEvent["action"],
|
|
10
10
|
outcome: AuditEvent["outcome"],
|
|
11
11
|
occurredAt: Date,
|
|
12
12
|
): AuditEvent {
|
|
@@ -38,7 +38,7 @@ export class SchedulerExecutionRecorder {
|
|
|
38
38
|
await this.repository.discardRun(run.id);
|
|
39
39
|
return completed;
|
|
40
40
|
}
|
|
41
|
-
this.record(context, completed,
|
|
41
|
+
this.record(context, completed, definition.auditAction, "success");
|
|
42
42
|
return completed;
|
|
43
43
|
}
|
|
44
44
|
|
|
@@ -59,14 +59,19 @@ export class SchedulerExecutionRecorder {
|
|
|
59
59
|
: undefined,
|
|
60
60
|
error: sanitizeExecutionError(error),
|
|
61
61
|
});
|
|
62
|
-
this.record(context, completed,
|
|
62
|
+
this.record(context, completed, definition.auditAction, "failure");
|
|
63
63
|
return completed;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
denied(
|
|
66
|
+
denied(
|
|
67
|
+
context: RequestGuardContext,
|
|
68
|
+
run: ScheduledTaskRun,
|
|
69
|
+
definition: ScheduledTaskDefinition,
|
|
70
|
+
reason: string,
|
|
71
|
+
) {
|
|
67
72
|
if (this.denialReasons.get(run.taskKey) === reason) return;
|
|
68
73
|
this.denialReasons.set(run.taskKey, reason);
|
|
69
|
-
this.record(context, run,
|
|
74
|
+
this.record(context, run, definition.auditAction, "denied");
|
|
70
75
|
}
|
|
71
76
|
|
|
72
77
|
trigger(context: RequestGuardContext, run: ScheduledTaskRun) {
|
|
@@ -91,7 +96,7 @@ export class SchedulerExecutionRecorder {
|
|
|
91
96
|
private record(
|
|
92
97
|
context: RequestGuardContext,
|
|
93
98
|
run: ScheduledTaskRun,
|
|
94
|
-
action: "
|
|
99
|
+
action: AuditEvent["action"],
|
|
95
100
|
outcome: AuditEvent["outcome"],
|
|
96
101
|
) {
|
|
97
102
|
this.runtime.auditSink.recordEvent(
|
|
@@ -183,9 +183,10 @@ async function createFixture(options: FixtureOptions = {}) {
|
|
|
183
183
|
label: "测试任务",
|
|
184
184
|
intervalSeconds: 100,
|
|
185
185
|
maxAttempts: 3,
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
186
|
+
retryDelaySeconds: 10,
|
|
187
|
+
permissionKey: "system.scheduler.manage",
|
|
188
|
+
auditAction: "task.execute",
|
|
189
|
+
feature: {
|
|
189
190
|
...feature,
|
|
190
191
|
requiredEntitlement: entitlementKey("capability.scheduler"),
|
|
191
192
|
},
|
|
@@ -24,9 +24,10 @@ test("自动轮询没有领取任务时不写历史和审计,人工触发仍
|
|
|
24
24
|
label: "测试任务",
|
|
25
25
|
intervalSeconds: 100,
|
|
26
26
|
maxAttempts: 3,
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
retryDelaySeconds: 10,
|
|
28
|
+
permissionKey: "system.scheduler.manage",
|
|
29
|
+
auditAction: "task.execute",
|
|
30
|
+
feature: {
|
|
30
31
|
...feature,
|
|
31
32
|
requiredEntitlement: entitlementKey("capability.scheduler"),
|
|
32
33
|
},
|