create-windy 0.3.19 → 0.4.1
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/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/settings/drizzle-watermark-repository.integration.test.ts +4 -0
- package/template/apps/server/src/settings/drizzle-watermark-repository.ts +11 -3
- package/template/apps/server/src/settings/watermark-routes.test.ts +54 -1
- package/template/apps/server/src/settings/watermark-routes.ts +29 -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/composables/useWatermarkSettings.webtest.ts +14 -0
- package/template/apps/web/src/composables/watermark-settings.ts +40 -11
- package/template/apps/web/src/composables/watermark-settings.webtest.ts +19 -6
- package/template/apps/web/src/layout/AppSidebar.vue +8 -1
- package/template/apps/web/src/layout/GlobalWatermark.layering.webtest.ts +2 -0
- package/template/apps/web/src/layout/GlobalWatermark.vue +4 -8
- package/template/apps/web/src/layout/GlobalWatermark.webtest.ts +12 -5
- 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/settings/WatermarkPreview.vue +72 -0
- package/template/apps/web/src/pages/settings/WatermarkPreview.webtest.ts +55 -0
- package/template/apps/web/src/pages/settings/WatermarkSettingsSection.vue +80 -24
- package/template/apps/web/src/pages/settings/WatermarkSettingsSection.webtest.ts +115 -0
- 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/docs/platform/platform-shell-settings.md +15 -10
- package/template/docs/platform/web-system-crud.md +1 -1
- package/template/package.json +1 -1
- package/template/packages/config/package.json +1 -1
- package/template/packages/database/drizzle/0044_sweet_tarantula.sql +6 -0
- package/template/packages/database/drizzle/meta/0044_snapshot.json +8349 -0
- package/template/packages/database/drizzle/meta/_journal.json +7 -0
- package/template/packages/database/package.json +1 -1
- package/template/packages/database/src/schema/platform-settings.ts +3 -0
- 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/platform-settings.ts +17 -0
- package/template/packages/shared/src/rbac.ts +2 -0
- package/template/apps/web/src/app/business-routes.ts +0 -22
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import moduleAssembly from "../../../../windy.modules.json";
|
|
2
|
+
import {
|
|
3
|
+
defineWindyServerComposition,
|
|
4
|
+
type WindyServerModuleAdapter,
|
|
5
|
+
type WindyServerModuleAttachment,
|
|
6
|
+
} from "@southwind-ai/server-sdk";
|
|
7
|
+
import type { ModuleManifest } from "@southwind-ai/modules";
|
|
8
|
+
import type { NodePgDatabase } from "drizzle-orm/node-postgres";
|
|
9
|
+
import type { ManagedDepartment } from "../system/entities.js";
|
|
10
|
+
import type { EntityRepository } from "../system/repository.js";
|
|
11
|
+
// @windy-module work-order begin
|
|
12
|
+
import { createWorkOrderModule } from "../example-modules.js";
|
|
13
|
+
// @windy-module work-order end
|
|
14
|
+
|
|
15
|
+
export interface ProjectBusinessModuleRuntime {
|
|
16
|
+
readonly db?: NodePgDatabase;
|
|
17
|
+
readonly departments: EntityRepository<ManagedDepartment>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// @windy-module work-order begin
|
|
21
|
+
const workOrderModule = createWorkOrderModule();
|
|
22
|
+
// @windy-module work-order end
|
|
23
|
+
|
|
24
|
+
const projectAdapters: readonly WindyServerModuleAdapter<ModuleManifest>[] = [
|
|
25
|
+
// @windy-module work-order begin
|
|
26
|
+
{
|
|
27
|
+
name: "work-order",
|
|
28
|
+
hostRange: "^0.1.0",
|
|
29
|
+
runtimeMode: "project-attachment",
|
|
30
|
+
create: () => workOrderModule,
|
|
31
|
+
},
|
|
32
|
+
// @windy-module work-order end
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
const projectAttachments: readonly WindyServerModuleAttachment<ProjectBusinessModuleRuntime>[] =
|
|
36
|
+
[
|
|
37
|
+
// @windy-module work-order begin
|
|
38
|
+
{
|
|
39
|
+
name: "work-order",
|
|
40
|
+
attach: (runtime) => workOrderModule.attach(runtime),
|
|
41
|
+
},
|
|
42
|
+
// @windy-module work-order end
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* 客户拥有的 Server 组合声明。
|
|
47
|
+
*
|
|
48
|
+
* 标准 package Adapter 由 Host 动态加载;只有需要客户 Repository 的本地 Adapter
|
|
49
|
+
* 才在这里覆盖,并通过显式 attachment 获得项目运行时。动态 package 永远不会收到
|
|
50
|
+
* 数据库或平台内部 Repository。
|
|
51
|
+
*/
|
|
52
|
+
export const serverCompositionDeclaration = defineWindyServerComposition({
|
|
53
|
+
assembly: moduleAssembly,
|
|
54
|
+
adapters: projectAdapters,
|
|
55
|
+
attachments: projectAttachments,
|
|
56
|
+
});
|
|
@@ -219,6 +219,8 @@ export async function createApplicationServices(
|
|
|
219
219
|
: new InMemorySchedulerRepository(),
|
|
220
220
|
taskDefinitions,
|
|
221
221
|
runtime: input.runtime,
|
|
222
|
+
resolveActor: (actorId) =>
|
|
223
|
+
resolveCurrentSystemActor(input.systemRepositories, actorId),
|
|
222
224
|
// @windy-module system.workflow begin
|
|
223
225
|
workflow,
|
|
224
226
|
// @windy-module system.workflow end
|
|
@@ -28,6 +28,10 @@ import type {
|
|
|
28
28
|
} from "./types.js";
|
|
29
29
|
export * from "./input-validation.js";
|
|
30
30
|
|
|
31
|
+
export type BulkResumeAuthorization = (
|
|
32
|
+
job: BulkDataJob,
|
|
33
|
+
) => Promise<string | undefined>;
|
|
34
|
+
|
|
31
35
|
export class BulkDataService {
|
|
32
36
|
private readonly handlers = new Map<string, BulkResourceHandler>();
|
|
33
37
|
private readonly limits: BulkDataLimits;
|
|
@@ -40,6 +44,7 @@ export class BulkDataService {
|
|
|
40
44
|
private readonly now: () => Date = () => new Date(),
|
|
41
45
|
blobStore?: BlobStore,
|
|
42
46
|
private readonly durable?: LegacyDurableAdapter,
|
|
47
|
+
private readonly authorizeResume?: BulkResumeAuthorization,
|
|
43
48
|
) {
|
|
44
49
|
this.limits = { ...defaultBulkDataLimits, ...limits };
|
|
45
50
|
this.artifacts = new BulkArtifactStore(repository, blobStore);
|
|
@@ -155,6 +160,15 @@ export class BulkDataService {
|
|
|
155
160
|
if (!job || job.status !== "running" || job.attempt !== generation) {
|
|
156
161
|
return;
|
|
157
162
|
}
|
|
163
|
+
const denial = this.authorizeResume
|
|
164
|
+
? await this.authorizeResume(job)
|
|
165
|
+
: this.durable
|
|
166
|
+
? "resume-authorization-unavailable"
|
|
167
|
+
: undefined;
|
|
168
|
+
if (denial) {
|
|
169
|
+
await this.repository.retryOrFail(job.id, denial, this.now());
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
158
172
|
await this.processClaimed(job);
|
|
159
173
|
}
|
|
160
174
|
|
|
@@ -77,7 +77,7 @@ export class SystemGovernedExportAuthorization implements GovernedExportAuthoriz
|
|
|
77
77
|
key: request.policyKey,
|
|
78
78
|
resourceType: request.resourceType,
|
|
79
79
|
version: request.policyVersion,
|
|
80
|
-
featureKey: featureKey("system.
|
|
80
|
+
featureKey: featureKey("system.user"),
|
|
81
81
|
exportPermissionKey: "system.user.export",
|
|
82
82
|
}
|
|
83
83
|
: undefined;
|
|
@@ -14,7 +14,11 @@ import { DrizzleDurableJobRepository } from "../../jobs/drizzle-repository.js";
|
|
|
14
14
|
import { DurableJobService } from "../../jobs/service.js";
|
|
15
15
|
import { DurableJobWorker } from "../../jobs/worker.js";
|
|
16
16
|
import { createPlatformObservability } from "../../observability/bootstrap.js";
|
|
17
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
createServerRuntime,
|
|
19
|
+
type AuditEventWriter,
|
|
20
|
+
type ServerRuntimeOptions,
|
|
21
|
+
} from "../../runtime.js";
|
|
18
22
|
import { identityRouteConfigs } from "../../system/identity-route-config.js";
|
|
19
23
|
import { registerSystemRoutes } from "../../system/routes.js";
|
|
20
24
|
import { createSystemRepositories } from "../../system/seed.js";
|
|
@@ -67,7 +71,7 @@ export async function createPostgresApiFixture(
|
|
|
67
71
|
const actors = governanceActors();
|
|
68
72
|
const actorResolver = bearerActorResolver(actors);
|
|
69
73
|
const logs: Array<Record<string, unknown>> = [];
|
|
70
|
-
const runtime =
|
|
74
|
+
const runtime = createGovernanceRuntime({ actorResolver, auditWriter });
|
|
71
75
|
const stack = createGovernanceStack({
|
|
72
76
|
db,
|
|
73
77
|
runtime,
|
|
@@ -94,6 +98,18 @@ export async function createPostgresApiFixture(
|
|
|
94
98
|
};
|
|
95
99
|
}
|
|
96
100
|
|
|
101
|
+
export function createGovernanceRuntime(
|
|
102
|
+
options: Pick<ServerRuntimeOptions, "actorResolver" | "auditWriter">,
|
|
103
|
+
) {
|
|
104
|
+
return createServerRuntime(
|
|
105
|
+
{},
|
|
106
|
+
{
|
|
107
|
+
...options,
|
|
108
|
+
featureCatalog: createFoundationSnapshot().features,
|
|
109
|
+
},
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
97
113
|
export function createGovernanceStack(input: {
|
|
98
114
|
db: NodePgDatabase;
|
|
99
115
|
runtime: ReturnType<typeof createServerRuntime>;
|
package/template/apps/server/src/data-governance/export/postgres-recovery.integration.test.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { Pool } from "pg";
|
|
|
5
5
|
import { FileAuditRecoveryQueue } from "../../audit/recovery-queue.js";
|
|
6
6
|
import { ReliableAuditWriter } from "../../audit/reliable-writer.js";
|
|
7
7
|
import { DrizzleAuditLogWriter } from "../../audit/drizzle-log-store.js";
|
|
8
|
-
import {
|
|
8
|
+
import type { AuditEventWriter } from "../../runtime.js";
|
|
9
9
|
import {
|
|
10
10
|
assertNoCanaries,
|
|
11
11
|
createExport,
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
import {
|
|
18
18
|
apiRequest,
|
|
19
19
|
approveExport,
|
|
20
|
+
createGovernanceRuntime,
|
|
20
21
|
createGovernanceStack,
|
|
21
22
|
createPostgresApiFixture,
|
|
22
23
|
ownerCanaries,
|
|
@@ -249,10 +250,10 @@ async function exerciseRecoveryQueue(
|
|
|
249
250
|
join(fixture.storageRoot, "audit-recovery.json"),
|
|
250
251
|
);
|
|
251
252
|
const reliable = reliableWriter(primary, queue);
|
|
252
|
-
const runtime =
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
);
|
|
253
|
+
const runtime = createGovernanceRuntime({
|
|
254
|
+
actorResolver: fixture.actorResolver,
|
|
255
|
+
auditWriter: reliable,
|
|
256
|
+
});
|
|
256
257
|
const stack = createGovernanceStack({
|
|
257
258
|
db: fixture.db,
|
|
258
259
|
runtime,
|
|
@@ -294,10 +295,10 @@ async function exerciseDoubleFault(
|
|
|
294
295
|
},
|
|
295
296
|
},
|
|
296
297
|
);
|
|
297
|
-
const runtime =
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
);
|
|
298
|
+
const runtime = createGovernanceRuntime({
|
|
299
|
+
actorResolver: fixture.actorResolver,
|
|
300
|
+
auditWriter: reliable,
|
|
301
|
+
});
|
|
301
302
|
const stack = createGovernanceStack({
|
|
302
303
|
db: fixture.db,
|
|
303
304
|
runtime,
|
|
@@ -11,7 +11,8 @@ import { DurableArtifactService } from "../../jobs/artifact-service.js";
|
|
|
11
11
|
import { InMemoryDurableExecutionRepository } from "../../jobs/in-memory-execution.js";
|
|
12
12
|
import { DurableJobService } from "../../jobs/service.js";
|
|
13
13
|
import { DurableJobWorker } from "../../jobs/worker.js";
|
|
14
|
-
import {
|
|
14
|
+
import type { AuditEventWriter } from "../../runtime.js";
|
|
15
|
+
import { createRouteTestRuntime } from "../../system/route-test-runtime.js";
|
|
15
16
|
import { FakeRouteApp } from "../../system/route-test-app.js";
|
|
16
17
|
import type { GovernedExportApprovalPort } from "./approval-port.js";
|
|
17
18
|
import { GovernedExportJobRuntime } from "./job-runtime.js";
|
|
@@ -34,7 +35,9 @@ export async function createFixture(
|
|
|
34
35
|
await auditWriter?.write(event, policy);
|
|
35
36
|
},
|
|
36
37
|
};
|
|
37
|
-
const
|
|
38
|
+
const foundation = createFoundationSnapshot();
|
|
39
|
+
const runtime = createRouteTestRuntime(
|
|
40
|
+
foundation,
|
|
38
41
|
{ WINDY_DEV_ADMIN_TOKEN: "token" },
|
|
39
42
|
{ auditWriter: writer },
|
|
40
43
|
);
|
|
@@ -86,12 +89,7 @@ export async function createFixture(
|
|
|
86
89
|
{ approvalRequired: true, retentionSeconds: 3600 },
|
|
87
90
|
);
|
|
88
91
|
const app = new FakeRouteApp();
|
|
89
|
-
registerGovernedUserExportRoutes(
|
|
90
|
-
app,
|
|
91
|
-
service,
|
|
92
|
-
runtime,
|
|
93
|
-
createFoundationSnapshot().features,
|
|
94
|
-
);
|
|
92
|
+
registerGovernedUserExportRoutes(app, service, runtime, foundation.features);
|
|
95
93
|
return {
|
|
96
94
|
app,
|
|
97
95
|
approval,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
2
|
import { createFoundationSnapshot } from "../foundation.js";
|
|
3
|
-
import {
|
|
3
|
+
import type { AuditEventWriter } from "../runtime.js";
|
|
4
|
+
import { createRouteTestRuntime } from "../system/route-test-runtime.js";
|
|
4
5
|
import { FakeRouteApp } from "../system/route-test-app.js";
|
|
5
6
|
import { registerSystemRoutes } from "../system/routes.js";
|
|
6
7
|
import { createSystemRepositories } from "../system/seed.js";
|
|
@@ -249,7 +250,8 @@ describe("用户敏感字段 API", () => {
|
|
|
249
250
|
async function fixture(auditWriter?: AuditEventWriter) {
|
|
250
251
|
const snapshot = createFoundationSnapshot();
|
|
251
252
|
const repositories = createSystemRepositories(snapshot);
|
|
252
|
-
const runtime =
|
|
253
|
+
const runtime = createRouteTestRuntime(
|
|
254
|
+
snapshot,
|
|
253
255
|
{ WINDY_DEV_ADMIN_TOKEN: "token" },
|
|
254
256
|
{ auditWriter },
|
|
255
257
|
);
|
|
@@ -95,6 +95,26 @@ describe("Feature Flag 管理授权", () => {
|
|
|
95
95
|
expect(item).toMatchObject({ key: "system.rbac", enabled: true });
|
|
96
96
|
});
|
|
97
97
|
|
|
98
|
+
test("Feature 管理核心能力不能关闭或隐藏", async () => {
|
|
99
|
+
const fixture = await createFixture(actor(["developer"], true));
|
|
100
|
+
|
|
101
|
+
for (const body of [
|
|
102
|
+
{ enabled: false },
|
|
103
|
+
{ visible: "hidden" },
|
|
104
|
+
{ licenseVersionOverride: ["advanced"] },
|
|
105
|
+
]) {
|
|
106
|
+
const response = (await updateFeature(
|
|
107
|
+
fixture,
|
|
108
|
+
body,
|
|
109
|
+
"system.feature",
|
|
110
|
+
)) as Response;
|
|
111
|
+
expect(response.status).toBe(409);
|
|
112
|
+
await expect(response.json()).resolves.toMatchObject({
|
|
113
|
+
error: "FEATURE_CONTROL_IMMUTABLE",
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
98
118
|
test("Feature Flag 更新明确使用关键审计策略", async () => {
|
|
99
119
|
const policies: string[] = [];
|
|
100
120
|
const fixture = await createFixture(actor(["developer"], true), {
|
|
@@ -166,7 +186,12 @@ async function createFixture(
|
|
|
166
186
|
},
|
|
167
187
|
);
|
|
168
188
|
const app = new FakeRouteApp();
|
|
169
|
-
registerFeatureFlagRoutes(
|
|
189
|
+
registerFeatureFlagRoutes(
|
|
190
|
+
app,
|
|
191
|
+
repositories.features,
|
|
192
|
+
runtime,
|
|
193
|
+
snapshot.features.find(({ key }) => key === "system.feature")!,
|
|
194
|
+
);
|
|
170
195
|
const guardContext = await runtime.createGuardContext(
|
|
171
196
|
new Request("http://localhost/api/system/features/system.profile"),
|
|
172
197
|
);
|
|
@@ -25,21 +25,16 @@ export function registerFeatureFlagRoutes(
|
|
|
25
25
|
app: RouteApp,
|
|
26
26
|
repository: ManifestFeatureRepository,
|
|
27
27
|
runtime: ServerRuntime,
|
|
28
|
+
feature: FeatureFlagDefinition,
|
|
28
29
|
) {
|
|
29
30
|
app.get("/system/features", async (rawContext) => {
|
|
30
31
|
const { guardContext, query } = routeContext(rawContext);
|
|
31
|
-
return withFeaturePermission(
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
const listQuery = parseListQuery(query);
|
|
38
|
-
return listQuery instanceof Response
|
|
39
|
-
? listQuery
|
|
40
|
-
: repository.list(listQuery, ["key", "label", "module"]);
|
|
41
|
-
},
|
|
42
|
-
);
|
|
32
|
+
return withFeaturePermission(runtime, guardContext, "read", feature, () => {
|
|
33
|
+
const listQuery = parseListQuery(query);
|
|
34
|
+
return listQuery instanceof Response
|
|
35
|
+
? listQuery
|
|
36
|
+
: repository.list(listQuery, ["key", "label", "module"]);
|
|
37
|
+
});
|
|
43
38
|
});
|
|
44
39
|
|
|
45
40
|
app.get("/system/features/export", async (rawContext) => {
|
|
@@ -48,7 +43,7 @@ export function registerFeatureFlagRoutes(
|
|
|
48
43
|
runtime,
|
|
49
44
|
guardContext,
|
|
50
45
|
"read",
|
|
51
|
-
|
|
46
|
+
feature,
|
|
52
47
|
async () => {
|
|
53
48
|
const listQuery = parseListQuery(query);
|
|
54
49
|
if (listQuery instanceof Response) return listQuery;
|
|
@@ -78,7 +73,7 @@ export function registerFeatureFlagRoutes(
|
|
|
78
73
|
runtime,
|
|
79
74
|
guardContext,
|
|
80
75
|
"read",
|
|
81
|
-
|
|
76
|
+
feature,
|
|
82
77
|
async () => {
|
|
83
78
|
const id = requiredId(params);
|
|
84
79
|
return (await repository.get(id)) || notFound("features", id);
|
|
@@ -92,11 +87,21 @@ export function registerFeatureFlagRoutes(
|
|
|
92
87
|
runtime,
|
|
93
88
|
guardContext,
|
|
94
89
|
"manage",
|
|
95
|
-
|
|
90
|
+
feature,
|
|
96
91
|
async () => {
|
|
97
92
|
const id = requiredId(params);
|
|
98
93
|
const input = readStatePatch(body);
|
|
99
94
|
if (input instanceof Response) return input;
|
|
95
|
+
if (id === feature.key && wouldDisableFeatureControl(input)) {
|
|
96
|
+
return Response.json(
|
|
97
|
+
{
|
|
98
|
+
error: "FEATURE_CONTROL_IMMUTABLE",
|
|
99
|
+
message:
|
|
100
|
+
"功能开关管理是核心恢复能力,不能关闭、隐藏或限制 License。",
|
|
101
|
+
},
|
|
102
|
+
{ status: 409 },
|
|
103
|
+
);
|
|
104
|
+
}
|
|
100
105
|
const item = await repository.updateState(
|
|
101
106
|
id,
|
|
102
107
|
input,
|
|
@@ -156,6 +161,19 @@ function readStatePatch(
|
|
|
156
161
|
};
|
|
157
162
|
}
|
|
158
163
|
|
|
164
|
+
function wouldDisableFeatureControl(
|
|
165
|
+
input: Partial<
|
|
166
|
+
Pick<ManagedFeature, "enabled" | "visible" | "licenseVersionOverride">
|
|
167
|
+
>,
|
|
168
|
+
): boolean {
|
|
169
|
+
return (
|
|
170
|
+
input.enabled === false ||
|
|
171
|
+
input.visible === "hidden" ||
|
|
172
|
+
(input.licenseVersionOverride !== undefined &&
|
|
173
|
+
input.licenseVersionOverride !== null)
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
159
177
|
function readLicenseVersionOverride(
|
|
160
178
|
record: Record<string, unknown>,
|
|
161
179
|
): LicenseVersionKey[] | null | undefined | Response {
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import {
|
|
3
|
+
// @windy-module system.settings begin
|
|
4
|
+
composeModuleMenus,
|
|
5
|
+
validateModuleComposition,
|
|
6
|
+
// @windy-module system.settings end
|
|
7
|
+
systemModuleManifests,
|
|
8
|
+
} from "@southwind-ai/modules";
|
|
9
|
+
import { applyPlatformModuleActivation } from "./foundation.js";
|
|
10
|
+
|
|
11
|
+
describe("平台 Module 运行时启停", () => {
|
|
12
|
+
// @windy-module system.operations begin
|
|
13
|
+
test("停用只关闭能力与入口,保留静态实现声明用于一致性校验", () => {
|
|
14
|
+
const source = systemModuleManifests().find(
|
|
15
|
+
({ name }) => name === "system.operations",
|
|
16
|
+
)!;
|
|
17
|
+
const disabled = applyPlatformModuleActivation(source, false);
|
|
18
|
+
|
|
19
|
+
expect(disabled.features.every(({ enabled }) => !enabled)).toBe(true);
|
|
20
|
+
expect(disabled.features.every(({ visible }) => visible === "hidden")).toBe(
|
|
21
|
+
true,
|
|
22
|
+
);
|
|
23
|
+
expect(
|
|
24
|
+
disabled.menus
|
|
25
|
+
.flatMap(({ children }) => children ?? [])
|
|
26
|
+
.every(({ visible }) => !visible),
|
|
27
|
+
).toBe(true);
|
|
28
|
+
expect(
|
|
29
|
+
disabled.menus
|
|
30
|
+
.filter(({ type }) => type === "catalog")
|
|
31
|
+
.every(({ visible }) => visible),
|
|
32
|
+
).toBe(true);
|
|
33
|
+
expect(disabled.apiPermissions).toEqual(source.apiPermissions);
|
|
34
|
+
expect(disabled.adminRoutes).toEqual(source.adminRoutes);
|
|
35
|
+
});
|
|
36
|
+
// @windy-module system.operations end
|
|
37
|
+
|
|
38
|
+
// @windy-module system.settings begin
|
|
39
|
+
test("停用一个平台 Module 不改变共享菜单目录壳", () => {
|
|
40
|
+
const modules = systemModuleManifests().map((manifest) =>
|
|
41
|
+
applyPlatformModuleActivation(
|
|
42
|
+
manifest,
|
|
43
|
+
manifest.name !== "system.settings",
|
|
44
|
+
),
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
expect(() => validateModuleComposition(modules)).not.toThrow();
|
|
48
|
+
expect(
|
|
49
|
+
composeModuleMenus(modules)
|
|
50
|
+
.find(({ key }) => key === "system.configuration")
|
|
51
|
+
?.children?.find(({ key }) => key === "system.settings")?.visible,
|
|
52
|
+
).toBe(false);
|
|
53
|
+
});
|
|
54
|
+
// @windy-module system.settings end
|
|
55
|
+
|
|
56
|
+
test("启用时保留原始 Manifest 身份", () => {
|
|
57
|
+
const source = systemModuleManifests()[0]!;
|
|
58
|
+
expect(applyPlatformModuleActivation(source, true)).toBe(source);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
@@ -14,7 +14,10 @@ import type {
|
|
|
14
14
|
PermissionDefinition,
|
|
15
15
|
} from "@southwind-ai/shared";
|
|
16
16
|
import { resolveLicenseCatalog } from "@southwind-ai/shared";
|
|
17
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
runtimeBusinessModules,
|
|
19
|
+
serverComposition,
|
|
20
|
+
} from "./module-host/runtime-composition.js";
|
|
18
21
|
|
|
19
22
|
export interface FoundationSnapshot {
|
|
20
23
|
platform: {
|
|
@@ -34,12 +37,21 @@ export interface FoundationSnapshot {
|
|
|
34
37
|
auditActions: string[];
|
|
35
38
|
auditActionCatalog: readonly AuditActionDefinition[];
|
|
36
39
|
licenseCatalog: readonly LicenseCatalogDefinition[];
|
|
40
|
+
disabledModuleNames: readonly string[];
|
|
37
41
|
}
|
|
38
42
|
|
|
39
43
|
export function loadFoundationModules(): ModuleManifest[] {
|
|
40
44
|
const registry = createModuleRegistry();
|
|
41
|
-
for (const factory of systemModuleFactories)
|
|
42
|
-
|
|
45
|
+
for (const factory of systemModuleFactories) {
|
|
46
|
+
const manifest = factory();
|
|
47
|
+
registry.register(() =>
|
|
48
|
+
applyPlatformModuleActivation(
|
|
49
|
+
manifest,
|
|
50
|
+
serverComposition.enabledModuleNames.has(manifest.name),
|
|
51
|
+
),
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
for (const module of runtimeBusinessModules) {
|
|
43
55
|
registry.register(() => module.manifest);
|
|
44
56
|
}
|
|
45
57
|
return registry.list();
|
|
@@ -47,10 +59,48 @@ export function loadFoundationModules(): ModuleManifest[] {
|
|
|
47
59
|
|
|
48
60
|
export function loadSystemModules(): ModuleManifest[] {
|
|
49
61
|
const registry = createModuleRegistry();
|
|
50
|
-
for (const factory of systemModuleFactories)
|
|
62
|
+
for (const factory of systemModuleFactories) {
|
|
63
|
+
const manifest = factory();
|
|
64
|
+
registry.register(() =>
|
|
65
|
+
applyPlatformModuleActivation(
|
|
66
|
+
manifest,
|
|
67
|
+
serverComposition.enabledModuleNames.has(manifest.name),
|
|
68
|
+
),
|
|
69
|
+
);
|
|
70
|
+
}
|
|
51
71
|
return registry.list();
|
|
52
72
|
}
|
|
53
73
|
|
|
74
|
+
export function applyPlatformModuleActivation(
|
|
75
|
+
manifest: ModuleManifest,
|
|
76
|
+
enabled: boolean,
|
|
77
|
+
): ModuleManifest {
|
|
78
|
+
if (enabled) return manifest;
|
|
79
|
+
return {
|
|
80
|
+
...manifest,
|
|
81
|
+
metadata: {
|
|
82
|
+
...manifest.metadata,
|
|
83
|
+
runtimeActivation: "disabled",
|
|
84
|
+
},
|
|
85
|
+
features: manifest.features.map((feature) => ({
|
|
86
|
+
...feature,
|
|
87
|
+
enabled: false,
|
|
88
|
+
visible: "hidden",
|
|
89
|
+
})),
|
|
90
|
+
menus: manifest.menus.map(hideMenu),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function hideMenu(menu: MenuNode): MenuNode {
|
|
95
|
+
return {
|
|
96
|
+
...menu,
|
|
97
|
+
visible: menu.type === "catalog" ? menu.visible : false,
|
|
98
|
+
...(menu.children
|
|
99
|
+
? { children: menu.children.map((child) => hideMenu(child)) }
|
|
100
|
+
: {}),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
54
104
|
function collectMenus(modules: ModuleManifest[]): MenuNode[] {
|
|
55
105
|
return composeModuleMenus(modules);
|
|
56
106
|
}
|
|
@@ -89,7 +139,13 @@ export function createFoundationSnapshot(
|
|
|
89
139
|
name: config.name,
|
|
90
140
|
enableAI: config.enableAI,
|
|
91
141
|
databaseDialect: config.database.dialect,
|
|
92
|
-
licenseEnabled:
|
|
142
|
+
licenseEnabled:
|
|
143
|
+
config.license.enabled &&
|
|
144
|
+
modules.some(
|
|
145
|
+
({ name, metadata }) =>
|
|
146
|
+
name === "system.license" &&
|
|
147
|
+
metadata?.runtimeActivation !== "disabled",
|
|
148
|
+
),
|
|
93
149
|
auditEnabled: config.audit.enabled,
|
|
94
150
|
ruoyiCompatibilityEnabled: config.compatibility.ruoyiVue.enabled,
|
|
95
151
|
},
|
|
@@ -105,5 +161,8 @@ export function createFoundationSnapshot(
|
|
|
105
161
|
auditActions: collectAuditActions(modules),
|
|
106
162
|
auditActionCatalog: catalogs.auditActions,
|
|
107
163
|
licenseCatalog: catalogs.licenseVersions,
|
|
164
|
+
disabledModuleNames: modules
|
|
165
|
+
.filter(({ metadata }) => metadata?.runtimeActivation === "disabled")
|
|
166
|
+
.map(({ name }) => name),
|
|
108
167
|
};
|
|
109
168
|
}
|
|
@@ -8,6 +8,8 @@ import {
|
|
|
8
8
|
import {
|
|
9
9
|
evaluateEntitlementGuard,
|
|
10
10
|
evaluateFeatureGuard,
|
|
11
|
+
evaluateLicenseRestrictionGuard,
|
|
12
|
+
evaluateLicenseVersionGuard,
|
|
11
13
|
evaluatePermissionGuard,
|
|
12
14
|
type RequestGuardContext,
|
|
13
15
|
} from "./guards.js";
|
|
@@ -72,6 +74,27 @@ describe("guards", () => {
|
|
|
72
74
|
).toBe("entitlement-not-granted");
|
|
73
75
|
});
|
|
74
76
|
|
|
77
|
+
test("License 未启用时跳过版本、Entitlement 与限制模式", () => {
|
|
78
|
+
const unrestricted = {
|
|
79
|
+
...context,
|
|
80
|
+
licenseEnforced: false,
|
|
81
|
+
license: { restricted: true as const, status: "expired" as const },
|
|
82
|
+
};
|
|
83
|
+
const licensedFeature = {
|
|
84
|
+
...feature,
|
|
85
|
+
requiredEntitlement: entitlementKey("capability.audit"),
|
|
86
|
+
allowedLicenseVersions: [],
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
expect(
|
|
90
|
+
evaluateEntitlementGuard(unrestricted, licensedFeature).allowed,
|
|
91
|
+
).toBe(true);
|
|
92
|
+
expect(
|
|
93
|
+
evaluateLicenseVersionGuard(unrestricted, licensedFeature).allowed,
|
|
94
|
+
).toBe(true);
|
|
95
|
+
expect(evaluateLicenseRestrictionGuard(unrestricted).allowed).toBe(true);
|
|
96
|
+
});
|
|
97
|
+
|
|
75
98
|
test("运行时解析出依赖缺失时拒绝并记录 security.denied", () => {
|
|
76
99
|
const denied = evaluateFeatureGuard(context, {
|
|
77
100
|
...feature,
|
|
@@ -58,6 +58,7 @@ export interface RequestGuardContext {
|
|
|
58
58
|
status?: "valid" | "expired" | "clock-rollback" | "invalid" | "missing";
|
|
59
59
|
restricted?: boolean;
|
|
60
60
|
};
|
|
61
|
+
licenseEnforced?: boolean;
|
|
61
62
|
}
|
|
62
63
|
|
|
63
64
|
export type GuardDecisionReason =
|
|
@@ -84,6 +85,7 @@ export interface RequestGuardContextOptions {
|
|
|
84
85
|
resolveLicense?: (
|
|
85
86
|
request: Request,
|
|
86
87
|
) => RequestGuardContext["license"] | undefined;
|
|
88
|
+
licenseEnforced?: boolean;
|
|
87
89
|
}
|
|
88
90
|
|
|
89
91
|
export type PermissionGuardRequirement = Pick<
|
|
@@ -107,6 +109,7 @@ export function createRequestGuardContext(
|
|
|
107
109
|
clientIp: options.clientIp,
|
|
108
110
|
userAgent: options.userAgent,
|
|
109
111
|
license: options.resolveLicense?.(request),
|
|
112
|
+
licenseEnforced: options.licenseEnforced,
|
|
110
113
|
};
|
|
111
114
|
}
|
|
112
115
|
|
|
@@ -176,6 +179,9 @@ export function evaluateEntitlementGuard(
|
|
|
176
179
|
context: RequestGuardContext,
|
|
177
180
|
feature: FeatureFlagDefinition,
|
|
178
181
|
): GuardDecision {
|
|
182
|
+
if (context.licenseEnforced === false) {
|
|
183
|
+
return { allowed: true, reason: "allowed" };
|
|
184
|
+
}
|
|
179
185
|
if (
|
|
180
186
|
!feature.requiredEntitlement ||
|
|
181
187
|
context.license?.entitlements?.includes(feature.requiredEntitlement)
|
|
@@ -194,6 +200,9 @@ export function evaluateLicenseVersionGuard(
|
|
|
194
200
|
context: RequestGuardContext,
|
|
195
201
|
feature: FeatureFlagDefinition,
|
|
196
202
|
): GuardDecision {
|
|
203
|
+
if (context.licenseEnforced === false) {
|
|
204
|
+
return { allowed: true, reason: "allowed" };
|
|
205
|
+
}
|
|
197
206
|
if (isLicenseVersionAllowed(feature, context.license?.versionKey)) {
|
|
198
207
|
return { allowed: true, reason: "allowed" };
|
|
199
208
|
}
|
|
@@ -208,6 +217,9 @@ export function evaluateLicenseRestrictionGuard(
|
|
|
208
217
|
context: RequestGuardContext,
|
|
209
218
|
explicitlyAllowed = false,
|
|
210
219
|
): GuardDecision {
|
|
220
|
+
if (context.licenseEnforced === false) {
|
|
221
|
+
return { allowed: true, reason: "allowed" };
|
|
222
|
+
}
|
|
211
223
|
if (!context.license?.restricted || explicitlyAllowed) {
|
|
212
224
|
return { allowed: true, reason: "allowed" };
|
|
213
225
|
}
|