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,6 +14,7 @@ import {
14
14
  type ModuleToolHandlers,
15
15
  type PlatformToolExecutionContext,
16
16
  } from "./tool-registry.js";
17
+ import type { StaticRouteAuthorizer } from "../module-host/static-route-authorization.js";
17
18
 
18
19
  type ServerRuntime = ReturnType<typeof createServerRuntime>;
19
20
 
@@ -85,9 +86,16 @@ export function createSystemToolHandlers(
85
86
  export function registerAgentRoutes(
86
87
  app: RouteApp,
87
88
  registry: GovernedToolRegistry,
89
+ authorizer: StaticRouteAuthorizer,
88
90
  ) {
89
91
  app.get("/agent/tools", async (rawContext) => {
90
92
  const { guardContext } = rawContext as RouteContext;
93
+ const denied = await authorizer.authorize(
94
+ guardContext,
95
+ "GET",
96
+ "/api/agent/tools",
97
+ );
98
+ if (denied) return denied;
91
99
  return Promise.all(
92
100
  registry.definitions().map(async (tool) => ({
93
101
  key: tool.key,
@@ -103,6 +111,12 @@ export function registerAgentRoutes(
103
111
 
104
112
  app.post("/agent/tools/:key/execute", async (rawContext) => {
105
113
  const context = rawContext as RouteContext;
114
+ const denied = await authorizer.authorize(
115
+ context.guardContext,
116
+ "POST",
117
+ "/api/agent/tools/:key/execute",
118
+ );
119
+ if (denied) return denied;
106
120
  try {
107
121
  return await registry.execute(
108
122
  context.params.key ?? "",
@@ -46,6 +46,11 @@ import { AgentBackgroundOperationExecutor } from "./background-operation.js";
46
46
  import type { DeferredAgentBackgroundOperationPortFactory } from "./background-operation-port.js";
47
47
  import { UnixSandboxToolClient } from "./sandbox-client.js";
48
48
  import { createModuleAiOperationPort } from "../module-host/ai-operation-port.js";
49
+ import {
50
+ createManifestAuthorizedRouteApp,
51
+ createStaticRouteAuthorizer,
52
+ } from "../module-host/static-route-authorization.js";
53
+ import { registerUnavailableAiRoutes } from "./unavailable-routes.js";
49
54
 
50
55
  export function registerAgentPlatformRuntime(input: {
51
56
  app: RouteApp;
@@ -79,18 +84,29 @@ export function registerAgentPlatformRuntime(input: {
79
84
  toolHandlers,
80
85
  input.runtime,
81
86
  );
82
- registerAgentRoutes(input.app, toolRegistry);
87
+ const routeAuthorizer = createStaticRouteAuthorizer(
88
+ input.runtime,
89
+ input.modules,
90
+ );
91
+ registerAgentRoutes(input.app, toolRegistry, routeAuthorizer);
83
92
  registerMcpRoutes(
84
93
  input.app,
85
94
  new PlatformMcpAdapter(toolRegistry, input.runtime, features),
86
95
  );
87
96
  if (!input.platformConfig.enableAI) {
97
+ registerUnavailableAiRoutes(
98
+ createManifestAuthorizedRouteApp(input.app, routeAuthorizer),
99
+ );
88
100
  return {
89
101
  toolHandlers,
90
102
  aiArtifacts: undefined,
91
103
  aiOperations: undefined,
92
104
  };
93
105
  }
106
+ const aiRouteApp = createManifestAuthorizedRouteApp(
107
+ input.app,
108
+ routeAuthorizer,
109
+ );
94
110
  const internalToken = requiredEnvironment("AGENT_SERVER_INTERNAL_TOKEN");
95
111
  const aiArtifacts = new AiArtifactRuntime(
96
112
  new AiArtifactCodec(requiredEnvironment("AI_ARTIFACT_ENCRYPTION_SECRET")),
@@ -100,7 +116,7 @@ export function registerAgentPlatformRuntime(input: {
100
116
  input.storage,
101
117
  resolveAgentActor,
102
118
  );
103
- registerAiArtifactRoutes(input.app, aiArtifacts, internalToken);
119
+ registerAiArtifactRoutes(aiRouteApp, aiArtifacts, internalToken);
104
120
  const remote = new RemoteAgentRuntime({
105
121
  baseUrl: process.env.AGENT_SERVER_URL || "http://agent-server:8080",
106
122
  internalToken,
@@ -127,7 +143,7 @@ export function registerAgentPlatformRuntime(input: {
127
143
  aiArtifacts,
128
144
  grants,
129
145
  );
130
- registerDocumentRunRoutes(input.app, documentFacade, documentRemote);
146
+ registerDocumentRunRoutes(aiRouteApp, documentFacade, documentRemote);
131
147
  const speechRemote = process.env.SPEECH_SERVER_URL?.trim()
132
148
  ? new RemoteSpeechRuntime({
133
149
  baseUrl: process.env.SPEECH_SERVER_URL,
@@ -142,7 +158,7 @@ export function registerAgentPlatformRuntime(input: {
142
158
  aiArtifacts,
143
159
  grants,
144
160
  );
145
- registerSpeechRunRoutes(input.app, speechFacade, speechRemote);
161
+ registerSpeechRunRoutes(aiRouteApp, speechFacade, speechRemote);
146
162
  const embeddingRemote = process.env.EMBEDDING_SERVER_URL?.trim()
147
163
  ? new RemoteEmbeddingRuntime({
148
164
  baseUrl: process.env.EMBEDDING_SERVER_URL,
@@ -156,7 +172,7 @@ export function registerAgentPlatformRuntime(input: {
156
172
  embeddingRemote,
157
173
  aiArtifacts,
158
174
  );
159
- registerEmbeddingRoutes(input.app, embeddingFacade);
175
+ registerEmbeddingRoutes(aiRouteApp, embeddingFacade);
160
176
  const agentFacade = new AgentRunFacade(
161
177
  input.modules,
162
178
  input.hosts,
@@ -166,7 +182,7 @@ export function registerAgentPlatformRuntime(input: {
166
182
  aiArtifacts,
167
183
  );
168
184
  registerAgentRunRoutes(
169
- input.app,
185
+ aiRouteApp,
170
186
  agentFacade,
171
187
  remote,
172
188
  new RestrictedAgentToolHost(
@@ -1,6 +1,6 @@
1
- import { installedBusinessModules } from "../installed-business-modules.js";
2
1
  import { SandboxToolRegistry } from "./sandbox-registry.js";
3
2
  import { runSandboxWorkerUntilSignal } from "./sandbox-worker-server.js";
3
+ import { runtimeBusinessModules } from "../module-host/runtime-composition.js";
4
4
 
5
5
  const socketPath =
6
6
  process.env.TOOL_SANDBOX_SOCKET?.trim() ||
@@ -8,5 +8,5 @@ const socketPath =
8
8
 
9
9
  await runSandboxWorkerUntilSignal(
10
10
  socketPath,
11
- new SandboxToolRegistry(installedBusinessModules),
11
+ new SandboxToolRegistry(runtimeBusinessModules),
12
12
  );
@@ -0,0 +1,33 @@
1
+ import type { RouteApp } from "../system/route-types.js";
2
+
3
+ const unavailableRoutes = [
4
+ ["GET", "/internal/ai/artifacts"],
5
+ ["POST", "/document/runs"],
6
+ ["GET", "/document/runs/:runId/events"],
7
+ ["GET", "/document/runs/:runId/result"],
8
+ ["POST", "/document/runs/:runId/cancel"],
9
+ ["POST", "/speech/runs"],
10
+ ["GET", "/speech/runs/:runId/events"],
11
+ ["GET", "/speech/runs/:runId/result"],
12
+ ["GET", "/speech/runs/:runId/artifact"],
13
+ ["POST", "/speech/runs/:runId/cancel"],
14
+ ["POST", "/embeddings"],
15
+ ["POST", "/agent/runs"],
16
+ ["GET", "/agent/runs/:runId/events"],
17
+ ["GET", "/agent/runs/:runId/result"],
18
+ ["POST", "/agent/runs/:runId/cancel"],
19
+ ["POST", "/agent/runs/:runId/attach"],
20
+ ["POST", "/internal/agent/tools/execute"],
21
+ ] as const;
22
+
23
+ export function registerUnavailableAiRoutes(app: RouteApp): void {
24
+ const unavailable = () =>
25
+ Response.json(
26
+ { code: "RUNTIME_UNAVAILABLE", message: "AI Runtime 未启用" },
27
+ { status: 503 },
28
+ );
29
+ for (const [method, path] of unavailableRoutes) {
30
+ if (method === "GET") app.get(path, unavailable);
31
+ else app.post(path, unavailable);
32
+ }
33
+ }
@@ -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.rbac"),
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 { createServerRuntime, type AuditEventWriter } from "../../runtime.js";
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 = createServerRuntime({}, { actorResolver, auditWriter });
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>;
@@ -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 { createServerRuntime, type AuditEventWriter } from "../../runtime.js";
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 = createServerRuntime(
253
- {},
254
- { actorResolver: fixture.actorResolver, auditWriter: reliable },
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 = createServerRuntime(
298
- {},
299
- { actorResolver: fixture.actorResolver, auditWriter: reliable },
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 { createServerRuntime, type AuditEventWriter } from "../../runtime.js";
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 runtime = createServerRuntime(
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 { createServerRuntime, type AuditEventWriter } from "../runtime.js";
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 = createServerRuntime(
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(app, repositories.features, runtime);
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
- runtime,
33
- guardContext,
34
- "read",
35
- undefined,
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
- undefined,
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
- undefined,
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
- undefined,
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
+ });