create-windy 0.3.18 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (216) hide show
  1. package/README.md +28 -13
  2. package/dist/cli.js +788 -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/agent-server/src/southwind_agent_server/adk_debug.py +38 -5
  9. package/template/apps/agent-server/tests/test_adk_debug_result_polling.py +139 -0
  10. package/template/apps/server/Dockerfile +1 -0
  11. package/template/apps/server/package.json +1 -0
  12. package/template/apps/server/scripts/validate-module-composition-once.ts +4 -0
  13. package/template/apps/server/scripts/validate-module-composition.ts +28 -0
  14. package/template/apps/server/src/agent/data-scope-routes.test.ts +20 -5
  15. package/template/apps/server/src/agent/routes.test.ts +20 -7
  16. package/template/apps/server/src/agent/routes.ts +14 -0
  17. package/template/apps/server/src/agent/runtime-bootstrap.ts +22 -6
  18. package/template/apps/server/src/agent/sandbox-worker.ts +2 -2
  19. package/template/apps/server/src/agent/unavailable-routes.ts +33 -0
  20. package/template/apps/server/src/app/server-composition.ts +56 -0
  21. package/template/apps/server/src/application-services.ts +2 -0
  22. package/template/apps/server/src/bulk-data/service.ts +14 -0
  23. package/template/apps/server/src/data-governance/export/authorization-port.ts +1 -1
  24. package/template/apps/server/src/data-governance/export/postgres-api-fixture.ts +18 -2
  25. package/template/apps/server/src/data-governance/export/postgres-recovery.integration.test.ts +10 -9
  26. package/template/apps/server/src/data-governance/export/routes-test-fixture.ts +6 -8
  27. package/template/apps/server/src/data-governance/user-plaintext-routes.test.ts +4 -2
  28. package/template/apps/server/src/feature/routes.authorization.test.ts +26 -1
  29. package/template/apps/server/src/feature/routes.ts +33 -15
  30. package/template/apps/server/src/foundation-activation.test.ts +60 -0
  31. package/template/apps/server/src/foundation.ts +64 -5
  32. package/template/apps/server/src/guards.test.ts +23 -0
  33. package/template/apps/server/src/guards.ts +12 -0
  34. package/template/apps/server/src/index.ts +28 -34
  35. package/template/apps/server/src/installed-business-modules.ts +13 -24
  36. package/template/apps/server/src/jobs/legacy-adapter.integration.test.ts +1 -0
  37. package/template/apps/server/src/jobs/legacy-adapter.test.ts +106 -0
  38. package/template/apps/server/src/jobs/legacy-service-runtime.ts +40 -0
  39. package/template/apps/server/src/license/activation.test.ts +30 -0
  40. package/template/apps/server/src/license/activation.ts +26 -0
  41. package/template/apps/server/src/license/restriction.test.ts +16 -0
  42. package/template/apps/server/src/license/routes-offline-code.test.ts +6 -2
  43. package/template/apps/server/src/license/routes.ts +20 -4
  44. package/template/apps/server/src/license/runtime-service.test.ts +10 -2
  45. package/template/apps/server/src/module-bootstrap.ts +14 -23
  46. package/template/apps/server/src/module-composition.test.ts +43 -2
  47. package/template/apps/server/src/module-composition.ts +29 -0
  48. package/template/apps/server/src/module-host/durable-context-guard.test.ts +172 -0
  49. package/template/apps/server/src/module-host/durable-context-unavailable.test.ts +50 -1
  50. package/template/apps/server/src/module-host/durable-context.test.ts +42 -25
  51. package/template/apps/server/src/module-host/durable-context.ts +42 -2
  52. package/template/apps/server/src/module-host/durable-handler-registration.ts +96 -5
  53. package/template/apps/server/src/module-host/implementation-completeness.test.ts +185 -0
  54. package/template/apps/server/src/module-host/implementation-completeness.ts +132 -0
  55. package/template/apps/server/src/module-host/initialize-contracts.ts +14 -0
  56. package/template/apps/server/src/module-host/initialize.test.ts +78 -2
  57. package/template/apps/server/src/module-host/initialize.ts +15 -4
  58. package/template/apps/server/src/module-host/project-attachments.test.ts +92 -0
  59. package/template/apps/server/src/module-host/project-attachments.ts +58 -0
  60. package/template/apps/server/src/module-host/project-composition-package.test.ts +186 -0
  61. package/template/apps/server/src/module-host/project-composition.test.ts +260 -0
  62. package/template/apps/server/src/module-host/project-composition.ts +311 -0
  63. package/template/apps/server/src/module-host/request-context.ts +3 -0
  64. package/template/apps/server/src/module-host/route-declaration.ts +15 -6
  65. package/template/apps/server/src/module-host/route-installer.test.ts +15 -2
  66. package/template/apps/server/src/module-host/route-installer.ts +12 -2
  67. package/template/apps/server/src/module-host/route-registration.test.ts +13 -4
  68. package/template/apps/server/src/module-host/runtime-composition-compatibility.test.ts +63 -0
  69. package/template/apps/server/src/module-host/runtime-composition-compatibility.ts +46 -0
  70. package/template/apps/server/src/module-host/runtime-composition.ts +34 -0
  71. package/template/apps/server/src/module-host/search-provider-registration.test.ts +64 -0
  72. package/template/apps/server/src/module-host/search-provider-registration.ts +35 -0
  73. package/template/apps/server/src/module-host/static-route-authorization.test.ts +99 -0
  74. package/template/apps/server/src/module-host/static-route-authorization.ts +90 -0
  75. package/template/apps/server/src/module-host/task-definitions.ts +1 -0
  76. package/template/apps/server/src/module-host/user-directory-composition.test.ts +4 -1
  77. package/template/apps/server/src/module-host.test.ts +2 -0
  78. package/template/apps/server/src/module-preflight.test.ts +289 -0
  79. package/template/apps/server/src/module-preflight.ts +96 -0
  80. package/template/apps/server/src/module-runtime-validation.ts +1 -0
  81. package/template/apps/server/src/platform-app-bootstrap.ts +50 -0
  82. package/template/apps/server/src/platform-http-runtime.ts +32 -7
  83. package/template/apps/server/src/route-guards.test.ts +22 -0
  84. package/template/apps/server/src/route-guards.ts +10 -3
  85. package/template/apps/server/src/runtime-audit-query.ts +50 -0
  86. package/template/apps/server/src/runtime-feature.test.ts +46 -0
  87. package/template/apps/server/src/runtime-feature.ts +5 -0
  88. package/template/apps/server/src/runtime.ts +10 -49
  89. package/template/apps/server/src/scheduler/audit-action.test.ts +49 -0
  90. package/template/apps/server/src/scheduler/audit.ts +1 -1
  91. package/template/apps/server/src/scheduler/execution-recorder.ts +10 -5
  92. package/template/apps/server/src/scheduler/recovery-service.test.ts +4 -3
  93. package/template/apps/server/src/scheduler/service-idle.test.ts +4 -3
  94. package/template/apps/server/src/scheduler/service.test.ts +1 -0
  95. package/template/apps/server/src/scheduler/service.ts +18 -21
  96. package/template/apps/server/src/scheduler/types.ts +5 -1
  97. package/template/apps/server/src/startup-composition-validation.test.ts +35 -0
  98. package/template/apps/server/src/startup-composition-validation.ts +54 -0
  99. package/template/apps/server/src/startup-preflight.test.ts +36 -0
  100. package/template/apps/server/src/startup-preflight.ts +37 -0
  101. package/template/apps/server/src/system/audit-routes.test.ts +9 -3
  102. package/template/apps/server/src/system/department-routes.test.ts +5 -3
  103. package/template/apps/server/src/system/feature-routes.test.ts +4 -3
  104. package/template/apps/server/src/system/interface-catalog.test.ts +5 -3
  105. package/template/apps/server/src/system/interface-catalog.ts +3 -0
  106. package/template/apps/server/src/system/menu-routes.test.ts +5 -3
  107. package/template/apps/server/src/system/route-test-runtime.ts +24 -0
  108. package/template/apps/server/src/system/routes-validation.test.ts +7 -3
  109. package/template/apps/server/src/system/routes.test.ts +21 -11
  110. package/template/apps/server/src/system/routes.ts +11 -2
  111. package/template/apps/server/src/work-order/foundation-modules.test.ts +6 -4
  112. package/template/apps/server/src/work-order/routes.test.ts +8 -0
  113. package/template/apps/server/src/work-order/task.test.ts +6 -1
  114. package/template/apps/web/Dockerfile +5 -0
  115. package/template/apps/web/package.json +1 -0
  116. package/template/apps/web/scripts/validate-admin-ui-composition.ts +106 -0
  117. package/template/apps/web/src/app/web-composition.ts +24 -0
  118. package/template/apps/web/src/app/web-composition.webtest.ts +15 -0
  119. package/template/apps/web/src/layout/AppSidebar.vue +8 -1
  120. package/template/apps/web/src/layout/NavigationSearchDialog.vue +8 -1
  121. package/template/apps/web/src/layout/navigation.ts +29 -4
  122. package/template/apps/web/src/layout/navigation.webtest.ts +51 -0
  123. package/template/apps/web/src/pages/dashboard/dashboard-presenter.ts +6 -1
  124. package/template/apps/web/src/pages/work-orders/WorkOrderListPage.vue +202 -0
  125. package/template/apps/web/src/pages/work-orders/WorkOrderListPage.webtest.ts +51 -0
  126. package/template/apps/web/src/router/admin-extension-loader.ts +44 -0
  127. package/template/apps/web/src/router/admin-extension-loader.webtest.ts +49 -0
  128. package/template/apps/web/src/router/admin-ui-composition-plugin.ts +33 -0
  129. package/template/apps/web/src/router/admin-ui-composition-plugin.webtest.ts +20 -0
  130. package/template/apps/web/src/router/admin-ui-composition.ts +313 -0
  131. package/template/apps/web/src/router/admin-ui-composition.webtest.ts +225 -0
  132. package/template/apps/web/src/router/admin-ui-module-registry-plugin.ts +55 -0
  133. package/template/apps/web/src/router/admin-ui-module-registry-plugin.webtest.ts +46 -0
  134. package/template/apps/web/src/router/business-routes.ts +32 -45
  135. package/template/apps/web/src/router/business-routes.webtest.ts +40 -44
  136. package/template/apps/web/src/router/index.ts +64 -54
  137. package/template/apps/web/src/router/manifest-routes.ts +27 -7
  138. package/template/apps/web/src/router/manifest-routes.webtest.ts +10 -2
  139. package/template/apps/web/src/router/platform-admin-route-bindings.ts +58 -0
  140. package/template/apps/web/src/router/reserved-route-paths.ts +14 -0
  141. package/template/apps/web/src/router/shared-scaffold-neutrality.webtest.ts +10 -4
  142. package/template/apps/web/src/router/web-composition-host.ts +20 -0
  143. package/template/apps/web/src/services/work-order-api.ts +16 -0
  144. package/template/apps/web/src/services/work-order-api.webtest.ts +21 -0
  145. package/template/apps/web/src/virtual-admin-ui-modules.d.ts +5 -0
  146. package/template/apps/web/vite.config.ts +22 -1
  147. package/template/docs/architecture/migration-bundle.md +10 -7
  148. package/template/docs/architecture/module-manifest-composition.md +85 -3
  149. package/template/docs/architecture/search-provider-protocol.md +6 -1
  150. package/template/docs/architecture/server-module-interface.md +70 -7
  151. package/template/docs/development/business-web-routes.md +53 -50
  152. package/template/docs/development/module-lifecycle.md +48 -54
  153. package/template/docs/platform/agent-runtime.md +2 -0
  154. package/template/docs/platform/navigation-registry.md +5 -4
  155. package/template/package.json +1 -1
  156. package/template/packages/config/package.json +1 -1
  157. package/template/packages/database/package.json +1 -1
  158. package/template/packages/example-work-order/package.json +6 -0
  159. package/template/packages/example-work-order/src/admin-page.ts +78 -0
  160. package/template/packages/example-work-order/src/admin.ts +35 -0
  161. package/template/packages/example-work-order/src/manifest.test.ts +62 -5
  162. package/template/packages/example-work-order/src/manifest.ts +54 -7
  163. package/template/packages/example-work-order/src/migration-bundle.ts +8 -0
  164. package/template/packages/example-work-order/src/server-module.ts +2 -2
  165. package/template/packages/jobs/package.json +1 -1
  166. package/template/packages/modules/index.ts +4 -0
  167. package/template/packages/modules/package.json +3 -2
  168. package/template/packages/modules/src/admin-ui-entry.ts +8 -0
  169. package/template/packages/modules/src/admin-ui-manifest.test.ts +52 -0
  170. package/template/packages/modules/src/admin-ui-manifest.ts +53 -0
  171. package/template/packages/modules/src/capability-audit-validation.test.ts +127 -0
  172. package/template/packages/modules/src/capability-audit-validation.ts +38 -0
  173. package/template/packages/modules/src/capability-guard-validation.ts +148 -0
  174. package/template/packages/modules/src/capability-key-validation.test.ts +150 -0
  175. package/template/packages/modules/src/composition-fixtures.test-support.ts +7 -5
  176. package/template/packages/modules/src/composition.test.ts +74 -9
  177. package/template/packages/modules/src/composition.ts +8 -0
  178. package/template/packages/modules/src/manifest.ts +1 -0
  179. package/template/packages/modules/src/migration-bundle-validation.test.ts +23 -0
  180. package/template/packages/modules/src/module-host-range.test.ts +57 -0
  181. package/template/packages/modules/src/module-host-range.ts +125 -0
  182. package/template/packages/modules/src/project-module-assembly.test.ts +133 -0
  183. package/template/packages/modules/src/project-module-assembly.ts +126 -0
  184. package/template/packages/modules/src/system-admin-routes.ts +6 -5
  185. package/template/packages/modules/src/system-admin-ui-catalog.ts +72 -0
  186. package/template/packages/modules/src/system-agent-tools.ts +2 -2
  187. package/template/packages/modules/src/system-api-binding.ts +43 -9
  188. package/template/packages/modules/src/system-api-control-plane.ts +202 -0
  189. package/template/packages/modules/src/system-api-data-governance.ts +4 -2
  190. package/template/packages/modules/src/system-api-operational.ts +57 -8
  191. package/template/packages/modules/src/system-api-permissions.ts +115 -21
  192. package/template/packages/modules/src/system-api-watermark.ts +3 -0
  193. package/template/packages/modules/src/system-audit-actions.ts +7 -0
  194. package/template/packages/modules/src/system-data-governance.ts +5 -2
  195. package/template/packages/modules/src/system-features.ts +31 -0
  196. package/template/packages/modules/src/system-governed-export-bindings.ts +8 -4
  197. package/template/packages/modules/src/system-module-catalog.test.ts +39 -4
  198. package/template/packages/modules/src/system-module-catalog.ts +68 -7
  199. package/template/packages/modules/src/system-module-tasks.ts +8 -1
  200. package/template/packages/modules/src/system-modules-access.test.ts +4 -0
  201. package/template/packages/modules/src/system-modules.test.ts +10 -2
  202. package/template/packages/modules/src/system-modules.ts +6 -5
  203. package/template/packages/modules/src/system-permissions.ts +4 -0
  204. package/template/packages/modules/user/module.ts +19 -2
  205. package/template/packages/server-sdk/index.ts +8 -0
  206. package/template/packages/server-sdk/package.json +2 -1
  207. package/template/packages/server-sdk/src/durable-handler.ts +16 -1
  208. package/template/packages/server-sdk/src/module-adapter.test.ts +44 -0
  209. package/template/packages/server-sdk/src/module-adapter.ts +59 -0
  210. package/template/packages/server-sdk/src/route-definition.ts +3 -2
  211. package/template/packages/shared/package.json +1 -1
  212. package/template/packages/shared/src/audit.test.ts +7 -0
  213. package/template/packages/shared/src/audit.ts +16 -0
  214. package/template/packages/shared/src/migration-bundle-validation.ts +9 -11
  215. package/template/packages/shared/src/rbac.ts +2 -0
  216. package/template/apps/web/src/app/business-routes.ts +0 -22
@@ -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 { installedBusinessModules } from "./installed-business-modules.js";
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) registry.register(factory);
42
- for (const module of installedBusinessModules) {
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) registry.register(factory);
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: config.license.enabled,
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
  }
@@ -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
  });