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.
Files changed (213) hide show
  1. package/README.md +28 -13
  2. package/dist/cli.js +783 -280
  3. package/package.json +1 -1
  4. package/template/.agents/skills/windy-business-module/SKILL.md +34 -10
  5. package/template/.windy-template.json +2 -2
  6. package/template/AGENTS.md +18 -8
  7. package/template/README.md +32 -22
  8. package/template/apps/server/Dockerfile +1 -0
  9. package/template/apps/server/package.json +1 -0
  10. package/template/apps/server/scripts/validate-module-composition-once.ts +4 -0
  11. package/template/apps/server/scripts/validate-module-composition.ts +28 -0
  12. package/template/apps/server/src/agent/data-scope-routes.test.ts +20 -5
  13. package/template/apps/server/src/agent/routes.test.ts +20 -7
  14. package/template/apps/server/src/agent/routes.ts +14 -0
  15. package/template/apps/server/src/agent/runtime-bootstrap.ts +22 -6
  16. package/template/apps/server/src/agent/sandbox-worker.ts +2 -2
  17. package/template/apps/server/src/agent/unavailable-routes.ts +33 -0
  18. package/template/apps/server/src/app/server-composition.ts +56 -0
  19. package/template/apps/server/src/application-services.ts +2 -0
  20. package/template/apps/server/src/bulk-data/service.ts +14 -0
  21. package/template/apps/server/src/data-governance/export/authorization-port.ts +1 -1
  22. package/template/apps/server/src/data-governance/export/postgres-api-fixture.ts +18 -2
  23. package/template/apps/server/src/data-governance/export/postgres-recovery.integration.test.ts +10 -9
  24. package/template/apps/server/src/data-governance/export/routes-test-fixture.ts +6 -8
  25. package/template/apps/server/src/data-governance/user-plaintext-routes.test.ts +4 -2
  26. package/template/apps/server/src/feature/routes.authorization.test.ts +26 -1
  27. package/template/apps/server/src/feature/routes.ts +33 -15
  28. package/template/apps/server/src/foundation-activation.test.ts +60 -0
  29. package/template/apps/server/src/foundation.ts +64 -5
  30. package/template/apps/server/src/guards.test.ts +23 -0
  31. package/template/apps/server/src/guards.ts +12 -0
  32. package/template/apps/server/src/index.ts +28 -34
  33. package/template/apps/server/src/installed-business-modules.ts +13 -24
  34. package/template/apps/server/src/jobs/legacy-adapter.integration.test.ts +1 -0
  35. package/template/apps/server/src/jobs/legacy-adapter.test.ts +106 -0
  36. package/template/apps/server/src/jobs/legacy-service-runtime.ts +40 -0
  37. package/template/apps/server/src/license/activation.test.ts +30 -0
  38. package/template/apps/server/src/license/activation.ts +26 -0
  39. package/template/apps/server/src/license/restriction.test.ts +16 -0
  40. package/template/apps/server/src/license/routes-offline-code.test.ts +6 -2
  41. package/template/apps/server/src/license/routes.ts +20 -4
  42. package/template/apps/server/src/license/runtime-service.test.ts +10 -2
  43. package/template/apps/server/src/module-bootstrap.ts +14 -23
  44. package/template/apps/server/src/module-composition.test.ts +43 -2
  45. package/template/apps/server/src/module-composition.ts +29 -0
  46. package/template/apps/server/src/module-host/durable-context-guard.test.ts +172 -0
  47. package/template/apps/server/src/module-host/durable-context-unavailable.test.ts +50 -1
  48. package/template/apps/server/src/module-host/durable-context.test.ts +42 -25
  49. package/template/apps/server/src/module-host/durable-context.ts +42 -2
  50. package/template/apps/server/src/module-host/durable-handler-registration.ts +96 -5
  51. package/template/apps/server/src/module-host/implementation-completeness.test.ts +185 -0
  52. package/template/apps/server/src/module-host/implementation-completeness.ts +132 -0
  53. package/template/apps/server/src/module-host/initialize-contracts.ts +14 -0
  54. package/template/apps/server/src/module-host/initialize.test.ts +78 -2
  55. package/template/apps/server/src/module-host/initialize.ts +15 -4
  56. package/template/apps/server/src/module-host/project-attachments.test.ts +92 -0
  57. package/template/apps/server/src/module-host/project-attachments.ts +58 -0
  58. package/template/apps/server/src/module-host/project-composition-package.test.ts +186 -0
  59. package/template/apps/server/src/module-host/project-composition.test.ts +260 -0
  60. package/template/apps/server/src/module-host/project-composition.ts +311 -0
  61. package/template/apps/server/src/module-host/request-context.ts +3 -0
  62. package/template/apps/server/src/module-host/route-declaration.ts +15 -6
  63. package/template/apps/server/src/module-host/route-installer.test.ts +15 -2
  64. package/template/apps/server/src/module-host/route-installer.ts +12 -2
  65. package/template/apps/server/src/module-host/route-registration.test.ts +13 -4
  66. package/template/apps/server/src/module-host/runtime-composition-compatibility.test.ts +63 -0
  67. package/template/apps/server/src/module-host/runtime-composition-compatibility.ts +46 -0
  68. package/template/apps/server/src/module-host/runtime-composition.ts +34 -0
  69. package/template/apps/server/src/module-host/search-provider-registration.test.ts +64 -0
  70. package/template/apps/server/src/module-host/search-provider-registration.ts +35 -0
  71. package/template/apps/server/src/module-host/static-route-authorization.test.ts +99 -0
  72. package/template/apps/server/src/module-host/static-route-authorization.ts +90 -0
  73. package/template/apps/server/src/module-host/task-definitions.ts +1 -0
  74. package/template/apps/server/src/module-host/user-directory-composition.test.ts +4 -1
  75. package/template/apps/server/src/module-host.test.ts +2 -0
  76. package/template/apps/server/src/module-preflight.test.ts +289 -0
  77. package/template/apps/server/src/module-preflight.ts +96 -0
  78. package/template/apps/server/src/module-runtime-validation.ts +1 -0
  79. package/template/apps/server/src/platform-app-bootstrap.ts +50 -0
  80. package/template/apps/server/src/platform-http-runtime.ts +32 -7
  81. package/template/apps/server/src/route-guards.test.ts +22 -0
  82. package/template/apps/server/src/route-guards.ts +10 -3
  83. package/template/apps/server/src/runtime-audit-query.ts +50 -0
  84. package/template/apps/server/src/runtime-feature.test.ts +46 -0
  85. package/template/apps/server/src/runtime-feature.ts +5 -0
  86. package/template/apps/server/src/runtime.ts +10 -49
  87. package/template/apps/server/src/scheduler/audit-action.test.ts +49 -0
  88. package/template/apps/server/src/scheduler/audit.ts +1 -1
  89. package/template/apps/server/src/scheduler/execution-recorder.ts +10 -5
  90. package/template/apps/server/src/scheduler/recovery-service.test.ts +4 -3
  91. package/template/apps/server/src/scheduler/service-idle.test.ts +4 -3
  92. package/template/apps/server/src/scheduler/service.test.ts +1 -0
  93. package/template/apps/server/src/scheduler/service.ts +18 -21
  94. package/template/apps/server/src/scheduler/types.ts +5 -1
  95. package/template/apps/server/src/startup-composition-validation.test.ts +35 -0
  96. package/template/apps/server/src/startup-composition-validation.ts +54 -0
  97. package/template/apps/server/src/startup-preflight.test.ts +36 -0
  98. package/template/apps/server/src/startup-preflight.ts +37 -0
  99. package/template/apps/server/src/system/audit-routes.test.ts +9 -3
  100. package/template/apps/server/src/system/department-routes.test.ts +5 -3
  101. package/template/apps/server/src/system/feature-routes.test.ts +4 -3
  102. package/template/apps/server/src/system/interface-catalog.test.ts +5 -3
  103. package/template/apps/server/src/system/interface-catalog.ts +3 -0
  104. package/template/apps/server/src/system/menu-routes.test.ts +5 -3
  105. package/template/apps/server/src/system/route-test-runtime.ts +24 -0
  106. package/template/apps/server/src/system/routes-validation.test.ts +7 -3
  107. package/template/apps/server/src/system/routes.test.ts +21 -11
  108. package/template/apps/server/src/system/routes.ts +11 -2
  109. package/template/apps/server/src/work-order/foundation-modules.test.ts +6 -4
  110. package/template/apps/server/src/work-order/routes.test.ts +8 -0
  111. package/template/apps/server/src/work-order/task.test.ts +6 -1
  112. package/template/apps/web/Dockerfile +5 -0
  113. package/template/apps/web/package.json +1 -0
  114. package/template/apps/web/scripts/validate-admin-ui-composition.ts +106 -0
  115. package/template/apps/web/src/app/web-composition.ts +24 -0
  116. package/template/apps/web/src/app/web-composition.webtest.ts +15 -0
  117. package/template/apps/web/src/layout/AppSidebar.vue +8 -1
  118. package/template/apps/web/src/layout/NavigationSearchDialog.vue +8 -1
  119. package/template/apps/web/src/layout/navigation.ts +29 -4
  120. package/template/apps/web/src/layout/navigation.webtest.ts +51 -0
  121. package/template/apps/web/src/pages/dashboard/dashboard-presenter.ts +6 -1
  122. package/template/apps/web/src/pages/work-orders/WorkOrderListPage.vue +202 -0
  123. package/template/apps/web/src/pages/work-orders/WorkOrderListPage.webtest.ts +51 -0
  124. package/template/apps/web/src/router/admin-extension-loader.ts +44 -0
  125. package/template/apps/web/src/router/admin-extension-loader.webtest.ts +49 -0
  126. package/template/apps/web/src/router/admin-ui-composition-plugin.ts +33 -0
  127. package/template/apps/web/src/router/admin-ui-composition-plugin.webtest.ts +20 -0
  128. package/template/apps/web/src/router/admin-ui-composition.ts +313 -0
  129. package/template/apps/web/src/router/admin-ui-composition.webtest.ts +225 -0
  130. package/template/apps/web/src/router/admin-ui-module-registry-plugin.ts +55 -0
  131. package/template/apps/web/src/router/admin-ui-module-registry-plugin.webtest.ts +46 -0
  132. package/template/apps/web/src/router/business-routes.ts +32 -45
  133. package/template/apps/web/src/router/business-routes.webtest.ts +40 -44
  134. package/template/apps/web/src/router/index.ts +64 -54
  135. package/template/apps/web/src/router/manifest-routes.ts +27 -7
  136. package/template/apps/web/src/router/manifest-routes.webtest.ts +10 -2
  137. package/template/apps/web/src/router/platform-admin-route-bindings.ts +58 -0
  138. package/template/apps/web/src/router/reserved-route-paths.ts +14 -0
  139. package/template/apps/web/src/router/shared-scaffold-neutrality.webtest.ts +10 -4
  140. package/template/apps/web/src/router/web-composition-host.ts +20 -0
  141. package/template/apps/web/src/services/work-order-api.ts +16 -0
  142. package/template/apps/web/src/services/work-order-api.webtest.ts +21 -0
  143. package/template/apps/web/src/virtual-admin-ui-modules.d.ts +5 -0
  144. package/template/apps/web/vite.config.ts +22 -1
  145. package/template/docs/architecture/migration-bundle.md +10 -7
  146. package/template/docs/architecture/module-manifest-composition.md +85 -3
  147. package/template/docs/architecture/search-provider-protocol.md +6 -1
  148. package/template/docs/architecture/server-module-interface.md +70 -7
  149. package/template/docs/development/business-web-routes.md +53 -50
  150. package/template/docs/development/module-lifecycle.md +48 -54
  151. package/template/docs/platform/navigation-registry.md +5 -4
  152. package/template/package.json +1 -1
  153. package/template/packages/config/package.json +1 -1
  154. package/template/packages/database/package.json +1 -1
  155. package/template/packages/example-work-order/package.json +6 -0
  156. package/template/packages/example-work-order/src/admin-page.ts +78 -0
  157. package/template/packages/example-work-order/src/admin.ts +35 -0
  158. package/template/packages/example-work-order/src/manifest.test.ts +62 -5
  159. package/template/packages/example-work-order/src/manifest.ts +54 -7
  160. package/template/packages/example-work-order/src/migration-bundle.ts +8 -0
  161. package/template/packages/example-work-order/src/server-module.ts +2 -2
  162. package/template/packages/jobs/package.json +1 -1
  163. package/template/packages/modules/index.ts +4 -0
  164. package/template/packages/modules/package.json +3 -2
  165. package/template/packages/modules/src/admin-ui-entry.ts +8 -0
  166. package/template/packages/modules/src/admin-ui-manifest.test.ts +52 -0
  167. package/template/packages/modules/src/admin-ui-manifest.ts +53 -0
  168. package/template/packages/modules/src/capability-audit-validation.test.ts +127 -0
  169. package/template/packages/modules/src/capability-audit-validation.ts +38 -0
  170. package/template/packages/modules/src/capability-guard-validation.ts +148 -0
  171. package/template/packages/modules/src/capability-key-validation.test.ts +150 -0
  172. package/template/packages/modules/src/composition-fixtures.test-support.ts +7 -5
  173. package/template/packages/modules/src/composition.test.ts +74 -9
  174. package/template/packages/modules/src/composition.ts +8 -0
  175. package/template/packages/modules/src/manifest.ts +1 -0
  176. package/template/packages/modules/src/migration-bundle-validation.test.ts +23 -0
  177. package/template/packages/modules/src/module-host-range.test.ts +57 -0
  178. package/template/packages/modules/src/module-host-range.ts +125 -0
  179. package/template/packages/modules/src/project-module-assembly.test.ts +133 -0
  180. package/template/packages/modules/src/project-module-assembly.ts +126 -0
  181. package/template/packages/modules/src/system-admin-routes.ts +6 -5
  182. package/template/packages/modules/src/system-admin-ui-catalog.ts +72 -0
  183. package/template/packages/modules/src/system-agent-tools.ts +2 -2
  184. package/template/packages/modules/src/system-api-binding.ts +43 -9
  185. package/template/packages/modules/src/system-api-control-plane.ts +202 -0
  186. package/template/packages/modules/src/system-api-data-governance.ts +4 -2
  187. package/template/packages/modules/src/system-api-operational.ts +57 -8
  188. package/template/packages/modules/src/system-api-permissions.ts +115 -21
  189. package/template/packages/modules/src/system-api-watermark.ts +3 -0
  190. package/template/packages/modules/src/system-audit-actions.ts +7 -0
  191. package/template/packages/modules/src/system-data-governance.ts +5 -2
  192. package/template/packages/modules/src/system-features.ts +31 -0
  193. package/template/packages/modules/src/system-governed-export-bindings.ts +8 -4
  194. package/template/packages/modules/src/system-module-catalog.test.ts +39 -4
  195. package/template/packages/modules/src/system-module-catalog.ts +68 -7
  196. package/template/packages/modules/src/system-module-tasks.ts +8 -1
  197. package/template/packages/modules/src/system-modules-access.test.ts +4 -0
  198. package/template/packages/modules/src/system-modules.test.ts +10 -2
  199. package/template/packages/modules/src/system-modules.ts +6 -5
  200. package/template/packages/modules/src/system-permissions.ts +4 -0
  201. package/template/packages/modules/user/module.ts +19 -2
  202. package/template/packages/server-sdk/index.ts +8 -0
  203. package/template/packages/server-sdk/package.json +2 -1
  204. package/template/packages/server-sdk/src/durable-handler.ts +16 -1
  205. package/template/packages/server-sdk/src/module-adapter.test.ts +44 -0
  206. package/template/packages/server-sdk/src/module-adapter.ts +59 -0
  207. package/template/packages/server-sdk/src/route-definition.ts +3 -2
  208. package/template/packages/shared/package.json +1 -1
  209. package/template/packages/shared/src/audit.test.ts +7 -0
  210. package/template/packages/shared/src/audit.ts +16 -0
  211. package/template/packages/shared/src/migration-bundle-validation.ts +9 -11
  212. package/template/packages/shared/src/rbac.ts +2 -0
  213. package/template/apps/web/src/app/business-routes.ts +0 -22
@@ -1,5 +1,4 @@
1
1
  import { registerAdminBootstrapRoute } from "./admin/routes.js";
2
- import { registerSessionContextRoute } from "./session/routes.js";
3
2
  import { registerFoundationRoute } from "./foundation-route.js";
4
3
  // @windy-module system.scheduler begin
5
4
  import { createApplicationServices } from "./application-services.js";
@@ -21,12 +20,7 @@ import { resolveCurrentSystemActor } from "./system/actor.js";
21
20
  // @windy-module system.agent end
22
21
  import { createAuthRuntime } from "./auth/runtime.js";
23
22
  // @windy-module system.license begin
24
- import { registerPlatformLicenseRoutes } from "./license/routes.js";
25
- import {
26
- createPlatformLicenseRuntimeOptions,
27
- hasConfiguredLicenseKeys,
28
- PlatformLicenseRuntime,
29
- } from "./license/runtime-service.js";
23
+ import { createLicenseRuntimeActivation } from "./license/activation.js";
30
24
  // @windy-module system.license end
31
25
  // @windy-module system.operations begin
32
26
  import { registerSystemOperationRoutes } from "./system/operations.js";
@@ -64,15 +58,15 @@ import { registerWorkflowRoutes } from "./workflow/routes.js";
64
58
  import { bootstrapServerModules } from "./module-bootstrap.js";
65
59
  import {
66
60
  createHttpObservability,
67
- createPlatformServerApp,
68
61
  listenPlatformServer,
69
62
  logServerStartup,
70
63
  } from "./platform-http-runtime.js";
71
- import { createPlatformBootstrap } from "./platform-bootstrap.js";
72
- const { logger, persistence, platformConfig } = createPlatformBootstrap();
73
- // @windy-module system.audit begin
74
- await persistence?.auditWriter.recover();
75
- // @windy-module system.audit end
64
+ import { bootstrapPlatformApp } from "./platform-app-bootstrap.js";
65
+ import { createPreparedPlatformBootstrap } from "./startup-preflight.js";
66
+ import { validateCompositionBeforeStartup } from "./startup-composition-validation.js";
67
+ validateCompositionBeforeStartup();
68
+ const { logger, persistence, platformConfig, preparedModules } =
69
+ await createPreparedPlatformBootstrap();
76
70
  const {
77
71
  // @windy-module system.configuration begin
78
72
  configurationRuntime,
@@ -82,16 +76,14 @@ const {
82
76
  foundationSnapshot,
83
77
  moduleHosts,
84
78
  systemRepositories,
85
- } = await bootstrapServerModules(platformConfig, persistence);
79
+ } = await bootstrapServerModules(platformConfig, persistence, preparedModules);
86
80
  const features = foundationSnapshot.features;
87
81
  // @windy-module system.license begin
88
- const platformLicenseOptions = createPlatformLicenseRuntimeOptions(process.env);
89
- const platformLicenseRuntime = new PlatformLicenseRuntime(
90
- platformLicenseOptions,
82
+ const licenseActivation = createLicenseRuntimeActivation(
83
+ process.env,
84
+ foundationSnapshot.platform.licenseEnabled,
85
+ persistence?.licenseResolver,
91
86
  );
92
- const useInstalledLicense =
93
- Boolean(platformLicenseOptions.enforceRestrictions) ||
94
- hasConfiguredLicenseKeys(platformLicenseOptions);
95
87
  // @windy-module system.license end
96
88
  const runtime = createServerRuntime(process.env, {
97
89
  actorResolver: persistence?.actorResolver,
@@ -100,12 +92,12 @@ const runtime = createServerRuntime(process.env, {
100
92
  resolveCurrentSystemActor(systemRepositories, actorId),
101
93
  // @windy-module system.agent end
102
94
  // @windy-module system.license begin
103
- licenseResolver: useInstalledLicense
104
- ? platformLicenseRuntime
105
- : persistence?.licenseResolver,
95
+ licenseResolver: licenseActivation.resolver,
96
+ licenseEnforced: licenseActivation.enforced,
106
97
  // @windy-module system.license end
107
98
  featureResolver: persistence ? systemRepositories.features : undefined,
108
99
  featureCatalog: features,
100
+ disabledModuleNames: new Set(foundationSnapshot.disabledModuleNames),
109
101
  // @windy-module system.audit begin
110
102
  auditReader: persistence?.auditReader,
111
103
  auditWriter: persistence?.auditWriter,
@@ -113,7 +105,7 @@ const runtime = createServerRuntime(process.env, {
113
105
  });
114
106
  const observability = createHttpObservability(runtime, persistence, logger);
115
107
  // @windy-module system.license begin
116
- platformLicenseRuntime.setAuditSink(runtime.auditSink);
108
+ licenseActivation.runtime.setAuditSink(runtime.auditSink);
117
109
  // @windy-module system.license end
118
110
  const {
119
111
  authRepository,
@@ -213,7 +205,7 @@ const schedulerWorker = new SchedulerWorker(
213
205
  (result) => observability.metrics.observeSchedulerTick(result),
214
206
  );
215
207
  // @windy-module system.scheduler end
216
- const app = await createPlatformServerApp({
208
+ const app = await bootstrapPlatformApp({
217
209
  runtime,
218
210
  persistence,
219
211
  platformConfig,
@@ -223,10 +215,11 @@ const app = await createPlatformServerApp({
223
215
  features,
224
216
  logger,
225
217
  observability,
218
+ modules: foundationModules,
219
+ // @windy-module system.license begin
220
+ licenseRuntime: licenseActivation.runtime,
221
+ // @windy-module system.license end
226
222
  });
227
- // @windy-module system.license begin
228
- registerPlatformLicenseRoutes(app, platformLicenseRuntime);
229
- // @windy-module system.license end
230
223
  // @windy-module system.settings begin
231
224
  platformSettings.register(app, runtime, features);
232
225
  // @windy-module system.settings end
@@ -267,7 +260,6 @@ registerAdminBootstrapRoute(app, runtime, {
267
260
  snapshot: foundationSnapshot,
268
261
  loadMenus: () => systemRepositories.menuRegistry.tree(),
269
262
  });
270
- registerSessionContextRoute(app, runtime, features);
271
263
  registerFoundationRoute(app, runtime, foundationSnapshot, systemRepositories);
272
264
  // @windy-module system.data-governance begin
273
265
  registerGovernedUserExportRoutes(app, governedExportService, runtime, features);
@@ -342,8 +334,10 @@ validateInstalledRuntime({
342
334
  // @windy-module system.search end
343
335
  hosts: moduleHosts,
344
336
  });
345
- const port = listenPlatformServer(app);
346
- // @windy-module system.scheduler begin
347
- schedulerWorker.start();
348
- // @windy-module system.scheduler end
349
- logServerStartup(logger, featureSync, Boolean(persistence), port);
337
+ if (!process.argv.includes("--validate-module-composition")) {
338
+ const port = listenPlatformServer(app);
339
+ // @windy-module system.scheduler begin
340
+ schedulerWorker.start();
341
+ // @windy-module system.scheduler end
342
+ logServerStartup(logger, featureSync, Boolean(persistence), port);
343
+ }
@@ -1,29 +1,18 @@
1
- // @windy-module work-order begin
2
- import { createWorkOrderModule } from "./example-modules.js";
3
- // @windy-module work-order end
4
1
  import type { ModuleManifest } from "@southwind-ai/modules";
5
2
  import type { InstallableServerModule } from "@southwind-ai/server-sdk";
6
- import type { NodePgDatabase } from "drizzle-orm/node-postgres";
7
- import type { ManagedDepartment } from "./system/entities.js";
8
- import type { EntityRepository } from "./system/repository.js";
9
3
 
10
- export interface BusinessModuleRuntime {
11
- readonly db?: NodePgDatabase;
12
- readonly departments: EntityRepository<ManagedDepartment>;
13
- }
4
+ export type { ProjectBusinessModuleRuntime as BusinessModuleRuntime } from "./app/server-composition.js";
5
+ import type { ProjectBusinessModuleRuntime } from "./app/server-composition.js";
14
6
 
15
- interface InstalledBusinessModule extends InstallableServerModule<ModuleManifest> {
16
- attach(runtime: BusinessModuleRuntime): void;
17
- }
7
+ /**
8
+ * 0.3.17 起保留的 project-owned 兼容装配缝。
9
+ *
10
+ * 新项目使用 app/server-composition.ts;升级项目可以继续在这里维护旧模块,受管 Host
11
+ * 会合并两种来源且不会要求客户文件新增导出。
12
+ */
13
+ export const installedBusinessModules: readonly InstallableServerModule<ModuleManifest>[] =
14
+ [];
18
15
 
19
- export const installedBusinessModules: readonly InstalledBusinessModule[] = [
20
- // @windy-module work-order begin
21
- createWorkOrderModule(),
22
- // @windy-module work-order end
23
- ];
24
-
25
- export function attachInstalledBusinessModules(
26
- runtime: BusinessModuleRuntime,
27
- ): void {
28
- for (const module of installedBusinessModules) module.attach(runtime);
29
- }
16
+ export async function attachInstalledBusinessModules(
17
+ _runtime: ProjectBusinessModuleRuntime,
18
+ ): Promise<void> {}
@@ -121,6 +121,7 @@ describePostgres("PostgreSQL legacy durable adapter", () => {
121
121
  () => now,
122
122
  undefined,
123
123
  adapter,
124
+ async () => undefined,
124
125
  );
125
126
  executor.attach(({ legacyId, generation }) =>
126
127
  restarted.resumeDurable(legacyId, generation),
@@ -73,6 +73,7 @@ describe("legacy durable adapters", () => {
73
73
  () => now,
74
74
  undefined,
75
75
  adapter,
76
+ async () => undefined,
76
77
  );
77
78
  executor.attach(({ legacyId, generation }) =>
78
79
  restarted.resumeDurable(legacyId, generation),
@@ -110,6 +111,44 @@ describe("legacy durable adapters", () => {
110
111
  processedRows: 1,
111
112
  });
112
113
  });
114
+
115
+ test("Bulk durable 恢复会重新校验当前 Actor 与 Module 激活状态", async () => {
116
+ const now = new Date("2026-07-16T00:00:00.000Z");
117
+ const repository = new InMemoryBulkDataRepository();
118
+ let executions = 0;
119
+ const service = new BulkDataService(
120
+ repository,
121
+ [
122
+ {
123
+ ...testBulkHandler(),
124
+ exportRows: async () => {
125
+ executions += 1;
126
+ return [{ id: "row-1" }];
127
+ },
128
+ },
129
+ ],
130
+ {},
131
+ () => now,
132
+ undefined,
133
+ undefined,
134
+ async () => "feature-disabled",
135
+ );
136
+ await service.createExport(
137
+ "fixtures",
138
+ "durable:bulk:guard",
139
+ actor("user_bulk"),
140
+ );
141
+ const running = await repository.claimNext(now);
142
+ if (!running) throw new Error("应创建 running Bulk 任务");
143
+
144
+ await service.resumeDurable(running.id, running.attempt);
145
+
146
+ expect(executions).toBe(0);
147
+ expect(await repository.getJob(running.id)).toMatchObject({
148
+ status: "failed",
149
+ error: "feature-disabled",
150
+ });
151
+ });
113
152
  // @windy-module system.bulk-data end
114
153
 
115
154
  test("Scheduler 采用未关联 running 记录且重放已完成记录为空操作", async () => {
@@ -142,6 +181,7 @@ describe("legacy durable adapters", () => {
142
181
  maxAttempts: 3,
143
182
  retryDelaySeconds: 10,
144
183
  permissionKey: "system.scheduler.manage",
184
+ auditAction: "task.execute",
145
185
  feature,
146
186
  handler: () => {
147
187
  executions += 1;
@@ -152,6 +192,13 @@ describe("legacy durable adapters", () => {
152
192
  createServerRuntime(),
153
193
  clock,
154
194
  adapter,
195
+ async (actorId) => ({
196
+ id: actorId,
197
+ type: "user",
198
+ name: actorId,
199
+ roleCodes: [],
200
+ permissionKeys: ["system.scheduler.manage"],
201
+ }),
155
202
  );
156
203
  executor.attach(({ legacyId, generation }) =>
157
204
  scheduler.resumeDurable(legacyId, generation),
@@ -181,6 +228,65 @@ describe("legacy durable adapters", () => {
181
228
  expect(durableJob?.id).toBe(completed.durableJobId);
182
229
  expect((await durable.get(durableJob!.id))?.attempts).toHaveLength(1);
183
230
  });
231
+
232
+ test("Scheduler durable 恢复会重新校验当前 Actor 与 Module 激活状态", async () => {
233
+ const now = new Date("2026-07-16T00:00:00.000Z");
234
+ const feature = createFoundationSnapshot().features.find(
235
+ ({ key }) => key === "system.scheduler",
236
+ );
237
+ if (!feature) throw new Error("测试缺少 Scheduler Feature");
238
+ const repository = new InMemorySchedulerRepository();
239
+ let executions = 0;
240
+ const scheduler = new SchedulerService(
241
+ repository,
242
+ [
243
+ {
244
+ key: "test.durable-guard",
245
+ label: "持久执行恢复门禁",
246
+ intervalSeconds: 3_600,
247
+ maxAttempts: 3,
248
+ retryDelaySeconds: 10,
249
+ permissionKey: "system.scheduler.manage",
250
+ auditAction: "task.execute",
251
+ feature,
252
+ handler: () => {
253
+ executions += 1;
254
+ },
255
+ },
256
+ ],
257
+ createServerRuntime(
258
+ {},
259
+ {
260
+ featureCatalog: [feature],
261
+ disabledModuleNames: new Set(["system.scheduler"]),
262
+ },
263
+ ),
264
+ { now: () => now },
265
+ undefined,
266
+ async (actorId) => ({
267
+ id: actorId,
268
+ type: "user",
269
+ name: actorId,
270
+ roleCodes: [],
271
+ permissionKeys: ["system.scheduler.manage"],
272
+ }),
273
+ );
274
+ await scheduler.initialize();
275
+ const run = await repository.claimManual(
276
+ "test.durable-guard",
277
+ "user_scheduler",
278
+ now,
279
+ );
280
+ if (!run) throw new Error("应创建 Scheduler run");
281
+
282
+ await scheduler.resumeDurable(run.id, run.attempt);
283
+
284
+ expect(executions).toBe(0);
285
+ expect(await repository.getRun(run.id)).toMatchObject({
286
+ status: "blocked",
287
+ error: "feature-disabled",
288
+ });
289
+ });
184
290
  });
185
291
 
186
292
  // @windy-module system.bulk-data begin
@@ -10,6 +10,10 @@ import type { BulkDataRepository } from "../bulk-data/repository.js";
10
10
  import type { BulkResourceHandler } from "../bulk-data/resource-handler.js";
11
11
  // @windy-module system.bulk-data end
12
12
  import type { createServerRuntime } from "../runtime.js";
13
+ import type { RequestActor } from "../guards.js";
14
+ // @windy-module system.bulk-data begin
15
+ import { evaluateAccessGuards } from "../access-guards.js";
16
+ // @windy-module system.bulk-data end
13
17
  import type { SchedulerRepository } from "../scheduler/repository.js";
14
18
  import { SchedulerService } from "../scheduler/service.js";
15
19
  import type { ScheduledTaskDefinition } from "../scheduler/types.js";
@@ -38,6 +42,7 @@ interface LegacyServiceRuntimeInput {
38
42
  schedulerRepository: SchedulerRepository;
39
43
  taskDefinitions: ScheduledTaskDefinition[];
40
44
  runtime: ReturnType<typeof createServerRuntime>;
45
+ resolveActor?: (actorId: string) => Promise<RequestActor | undefined>;
41
46
  // @windy-module system.workflow begin
42
47
  workflow: WorkflowService;
43
48
  // @windy-module system.workflow end
@@ -80,6 +85,7 @@ export function createLegacyServiceRuntime(input: LegacyServiceRuntimeInput) {
80
85
  workerId: `server-${process.pid}-bulk`,
81
86
  execute: (payload) => bulkExecutor.execute(payload),
82
87
  }),
88
+ createBulkResumeAuthorization(input),
83
89
  );
84
90
  bulkExecutor.attach(({ legacyId, generation }) =>
85
91
  bulkDataService.resumeDurable(legacyId, generation),
@@ -99,6 +105,7 @@ export function createLegacyServiceRuntime(input: LegacyServiceRuntimeInput) {
99
105
  workerId: `server-${process.pid}-scheduler`,
100
106
  execute: (payload) => schedulerExecutor.execute(payload),
101
107
  }),
108
+ input.resolveActor,
102
109
  );
103
110
  schedulerExecutor.attach(({ legacyId, generation }) =>
104
111
  scheduler.resumeDurable(legacyId, generation),
@@ -116,3 +123,36 @@ export function createLegacyServiceRuntime(input: LegacyServiceRuntimeInput) {
116
123
  durable: { facts, service, registry, worker },
117
124
  };
118
125
  }
126
+
127
+ // @windy-module system.bulk-data begin
128
+ function createBulkResumeAuthorization(input: LegacyServiceRuntimeInput) {
129
+ const feature = input.taskDefinitions.find(
130
+ ({ key }) => key === "system.bulk-data.dispatch",
131
+ )?.feature;
132
+ return async (job: { requestedBy: string }) => {
133
+ if (!feature) return "feature-definition-missing";
134
+ const actor =
135
+ (await input.resolveActor?.(job.requestedBy)) ??
136
+ deniedResumeActor(job.requestedBy);
137
+ const context = await input.runtime.createGuardContextForActor(
138
+ new Request("http://jobs.local/bulk-data/resume"),
139
+ actor,
140
+ );
141
+ const guarded = await evaluateAccessGuards(input.runtime, context, {
142
+ permission: { permissionKey: "system.bulk-data.manage" },
143
+ feature,
144
+ });
145
+ return guarded.decision.allowed ? undefined : guarded.decision.reason;
146
+ };
147
+ }
148
+
149
+ function deniedResumeActor(actorId: string): RequestActor {
150
+ return {
151
+ id: actorId,
152
+ type: "user",
153
+ name: actorId,
154
+ roleCodes: [],
155
+ permissionKeys: [],
156
+ };
157
+ }
158
+ // @windy-module system.bulk-data end
@@ -0,0 +1,30 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { createLicenseRuntimeActivation } from "./activation.js";
3
+
4
+ describe("License Module 激活", () => {
5
+ test("Module 未启用时不创建授权限制边界", () => {
6
+ const fallback = { resolve: () => ({ status: "valid" as const }) };
7
+ const activation = createLicenseRuntimeActivation(
8
+ { NODE_ENV: "production" },
9
+ false,
10
+ fallback,
11
+ );
12
+
13
+ expect(activation.enforced).toBe(false);
14
+ expect(activation.resolver).toBeUndefined();
15
+ });
16
+
17
+ test("Module 启用后按环境选择官方或持久化 Resolver", () => {
18
+ const fallback = { resolve: () => ({ status: "valid" as const }) };
19
+ const production = createLicenseRuntimeActivation(
20
+ { NODE_ENV: "production" },
21
+ true,
22
+ fallback,
23
+ );
24
+ const development = createLicenseRuntimeActivation({}, true, fallback);
25
+
26
+ expect(production.resolver).toBe(production.runtime);
27
+ expect(development.resolver).toBe(fallback);
28
+ expect(production.enforced).toBe(true);
29
+ });
30
+ });
@@ -0,0 +1,26 @@
1
+ import type { LicenseEntitlementResolver } from "../runtime-license.js";
2
+ import {
3
+ createPlatformLicenseRuntimeOptions,
4
+ hasConfiguredLicenseKeys,
5
+ PlatformLicenseRuntime,
6
+ } from "./runtime-service.js";
7
+
8
+ export function createLicenseRuntimeActivation(
9
+ env: Record<string, string | undefined>,
10
+ enabled: boolean,
11
+ fallbackResolver?: LicenseEntitlementResolver,
12
+ ) {
13
+ const options = createPlatformLicenseRuntimeOptions(env);
14
+ const runtime = new PlatformLicenseRuntime(options);
15
+ const useOfficialRuntime =
16
+ Boolean(options.enforceRestrictions) || hasConfiguredLicenseKeys(options);
17
+ return {
18
+ enforced: enabled,
19
+ runtime,
20
+ resolver: enabled
21
+ ? useOfficialRuntime
22
+ ? runtime
23
+ : fallbackResolver
24
+ : undefined,
25
+ };
26
+ }
@@ -51,4 +51,20 @@ describe("License 限制模式 HTTP 白名单", () => {
51
51
  );
52
52
  }
53
53
  });
54
+
55
+ test("License Module 未启用时不进入全局限制模式", async () => {
56
+ const runtime = createServerRuntime(
57
+ {},
58
+ {
59
+ licenseEnforced: false,
60
+ licenseResolver: {
61
+ resolve: () => ({ status: "expired", restricted: true }),
62
+ },
63
+ },
64
+ );
65
+ const request = new Request("http://localhost/api/system/users");
66
+ const context = await runtime.createGuardContext(request);
67
+
68
+ expect(evaluateRestrictedHttpRequest(context, request).allowed).toBe(true);
69
+ });
54
70
  });
@@ -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
- registerPlatformLicenseRoutes(app, licenseRuntime);
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
- loadSystemModules(),
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
- attachInstalledBusinessModules,
12
- installedBusinessModules,
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 foundationModules = loadFoundationModules();
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
- attachInstalledBusinessModules({
68
+ await attachRuntimeBusinessModules({
78
69
  db: persistence?.db,
79
70
  departments: systemRepositories.departments,
80
71
  });