create-windy 0.2.33 → 0.3.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 (284) hide show
  1. package/dist/cli.js +173 -17
  2. package/package.json +1 -1
  3. package/template/.agents/skills/windy-business-module/SKILL.md +10 -1
  4. package/template/.windy-template.json +2 -2
  5. package/template/AGENTS.md +9 -0
  6. package/template/README.md +3 -1
  7. package/template/apps/agent-server/Dockerfile +1 -0
  8. package/template/apps/agent-server/README.md +28 -5
  9. package/template/apps/agent-server/adk_web_agents/__init__.py +1 -0
  10. package/template/apps/agent-server/adk_web_agents/safe_debug/__init__.py +3 -0
  11. package/template/apps/agent-server/adk_web_agents/safe_debug/agent.py +40 -0
  12. package/template/apps/agent-server/pyproject.toml +1 -1
  13. package/template/apps/agent-server/src/southwind_agent_server/adk_adapter.py +191 -16
  14. package/template/apps/agent-server/src/southwind_agent_server/adk_failure.py +13 -0
  15. package/template/apps/agent-server/src/southwind_agent_server/adk_model.py +225 -0
  16. package/template/apps/agent-server/src/southwind_agent_server/adk_tools.py +76 -0
  17. package/template/apps/agent-server/src/southwind_agent_server/app.py +63 -3
  18. package/template/apps/agent-server/src/southwind_agent_server/contracts.py +35 -0
  19. package/template/apps/agent-server/src/southwind_agent_server/engine.py +2 -0
  20. package/template/apps/agent-server/src/southwind_agent_server/openai_engine.py +31 -3
  21. package/template/apps/agent-server/src/southwind_agent_server/openai_provider.py +22 -9
  22. package/template/apps/agent-server/src/southwind_agent_server/provider_audit.py +144 -0
  23. package/template/apps/agent-server/src/southwind_agent_server/provider_governance.py +227 -0
  24. package/template/apps/agent-server/src/southwind_agent_server/service.py +1 -0
  25. package/template/apps/agent-server/tests/test_adk_adapter.py +305 -0
  26. package/template/apps/agent-server/tests/test_adk_web_agent.py +12 -0
  27. package/template/apps/agent-server/tests/test_api.py +16 -1
  28. package/template/apps/agent-server/tests/test_openai_provider.py +79 -0
  29. package/template/apps/agent-server/tests/test_provider_governance.py +215 -0
  30. package/template/apps/server/package.json +4 -1
  31. package/template/apps/server/src/agent/provider-input-governance.test.ts +82 -0
  32. package/template/apps/server/src/agent/provider-input-governance.ts +140 -0
  33. package/template/apps/server/src/agent/remote-runtime.test.ts +22 -0
  34. package/template/apps/server/src/agent/remote-runtime.ts +1 -0
  35. package/template/apps/server/src/agent/run-contracts.ts +2 -0
  36. package/template/apps/server/src/agent/run-facade.test.ts +52 -2
  37. package/template/apps/server/src/agent/run-facade.ts +38 -9
  38. package/template/apps/server/src/agent/tool-registry.ts +2 -6
  39. package/template/apps/server/src/application-services.ts +50 -0
  40. package/template/apps/server/src/audit/drizzle-log-store.ts +25 -0
  41. package/template/apps/server/src/audit/search-summary.ts +2 -6
  42. package/template/apps/server/src/auth/credential-restriction.ts +1 -0
  43. package/template/apps/server/src/auth/password.ts +30 -21
  44. package/template/apps/server/src/auth/routes.test.ts +44 -3
  45. package/template/apps/server/src/auth/routes.ts +6 -1
  46. package/template/apps/server/src/auth/runtime.ts +4 -1
  47. package/template/apps/server/src/auth/service.ts +25 -7
  48. package/template/apps/server/src/configuration/bootstrap.ts +20 -1
  49. package/template/apps/server/src/configuration/definition.test.ts +71 -0
  50. package/template/apps/server/src/configuration/definition.ts +242 -12
  51. package/template/apps/server/src/configuration/drizzle-repository.ts +36 -2
  52. package/template/apps/server/src/configuration/repository.ts +27 -2
  53. package/template/apps/server/src/configuration/routes.ts +4 -1
  54. package/template/apps/server/src/configuration/service.test.ts +100 -0
  55. package/template/apps/server/src/configuration/service.ts +50 -10
  56. package/template/apps/server/src/data-access/scoped-repository.integration.test.ts +3 -2
  57. package/template/apps/server/src/data-governance/export/service.ts +2 -6
  58. package/template/apps/server/src/example-modules.ts +4 -1
  59. package/template/apps/server/src/guards.ts +18 -5
  60. package/template/apps/server/src/http/request-source.test.ts +85 -0
  61. package/template/apps/server/src/http/request-source.ts +90 -0
  62. package/template/apps/server/src/index.ts +45 -45
  63. package/template/apps/server/src/license/routes.ts +2 -0
  64. package/template/apps/server/src/license/runtime-service.ts +10 -11
  65. package/template/apps/server/src/module-bootstrap.ts +2 -2
  66. package/template/apps/server/src/module-host/initialize-contracts.ts +6 -0
  67. package/template/apps/server/src/module-host/initialize.ts +30 -44
  68. package/template/apps/server/src/module-host/request-context.ts +29 -23
  69. package/template/apps/server/src/module-host/resource-file-policies.test.ts +197 -0
  70. package/template/apps/server/src/module-host/resource-file-policies.ts +108 -0
  71. package/template/apps/server/src/module-host/route-declaration.ts +45 -0
  72. package/template/apps/server/src/module-host/route-installer.ts +27 -3
  73. package/template/apps/server/src/module-host/storage-port.test.ts +170 -1
  74. package/template/apps/server/src/module-host/storage-port.ts +134 -1
  75. package/template/apps/server/src/module-host/storage-scope.ts +89 -0
  76. package/template/apps/server/src/module-host/tool-handlers.test.ts +305 -0
  77. package/template/apps/server/src/module-host/tool-handlers.ts +38 -4
  78. package/template/apps/server/src/module-host/user-directory-composition.test.ts +126 -0
  79. package/template/apps/server/src/module-host/user-directory-port.test.ts +238 -0
  80. package/template/apps/server/src/module-host/user-directory-port.ts +139 -0
  81. package/template/apps/server/src/module-host.ts +4 -0
  82. package/template/apps/server/src/notification/channel-adapter.ts +46 -0
  83. package/template/apps/server/src/notification/delivery-policy.test.ts +59 -0
  84. package/template/apps/server/src/notification/delivery-policy.ts +37 -0
  85. package/template/apps/server/src/notification/delivery-repository.ts +198 -0
  86. package/template/apps/server/src/notification/delivery-worker.test.ts +87 -0
  87. package/template/apps/server/src/notification/delivery-worker.ts +88 -0
  88. package/template/apps/server/src/notification/drizzle-management.ts +180 -0
  89. package/template/apps/server/src/notification/drizzle-query.ts +160 -0
  90. package/template/apps/server/src/notification/drizzle-repository.integration.test.ts +301 -0
  91. package/template/apps/server/src/notification/drizzle-repository.ts +156 -90
  92. package/template/apps/server/src/notification/extension-routes.test.ts +119 -0
  93. package/template/apps/server/src/notification/extension-routes.ts +111 -0
  94. package/template/apps/server/src/notification/memory-delivery-repository.ts +137 -0
  95. package/template/apps/server/src/notification/memory-support.ts +72 -0
  96. package/template/apps/server/src/notification/preference-repository.ts +95 -0
  97. package/template/apps/server/src/notification/recipient-directory.ts +104 -0
  98. package/template/apps/server/src/notification/repository.test.ts +171 -0
  99. package/template/apps/server/src/notification/repository.ts +179 -32
  100. package/template/apps/server/src/notification/revision-routes.test.ts +186 -0
  101. package/template/apps/server/src/notification/route-support.ts +225 -0
  102. package/template/apps/server/src/notification/routes.test.ts +182 -0
  103. package/template/apps/server/src/notification/routes.ts +225 -132
  104. package/template/apps/server/src/notification/service.test.ts +104 -0
  105. package/template/apps/server/src/notification/service.ts +140 -0
  106. package/template/apps/server/src/notification/smtp-adapter.test.ts +65 -0
  107. package/template/apps/server/src/notification/smtp-adapter.ts +68 -0
  108. package/template/apps/server/src/persistence.integration.test.ts +41 -2
  109. package/template/apps/server/src/route-guards.ts +1 -0
  110. package/template/apps/server/src/runtime-audit-list.test.ts +39 -0
  111. package/template/apps/server/src/runtime-development.ts +2 -0
  112. package/template/apps/server/src/runtime.test.ts +23 -9
  113. package/template/apps/server/src/runtime.ts +39 -2
  114. package/template/apps/server/src/scheduler/audit.ts +2 -11
  115. package/template/apps/server/src/scheduler/definitions.test.ts +6 -0
  116. package/template/apps/server/src/scheduler/definitions.ts +7 -0
  117. package/template/apps/server/src/scheduler/recovery-service.ts +2 -11
  118. package/template/apps/server/src/scheduler/routes.ts +2 -7
  119. package/template/apps/server/src/search/opensearch-client.test.ts +45 -0
  120. package/template/apps/server/src/search/opensearch-client.ts +145 -0
  121. package/template/apps/server/src/search/service.ts +2 -6
  122. package/template/apps/server/src/settings/routes.ts +15 -3
  123. package/template/apps/server/src/settings/runtime.ts +10 -1
  124. package/template/apps/server/src/storage/runtime.ts +13 -0
  125. package/template/apps/server/src/system/audit-query.ts +33 -4
  126. package/template/apps/server/src/system/audit-routes.test.ts +58 -4
  127. package/template/apps/server/src/system/audit-routes.ts +6 -5
  128. package/template/apps/server/src/system/built-in-roles.ts +1 -0
  129. package/template/apps/server/src/system/department-organization.ts +320 -0
  130. package/template/apps/server/src/system/department-routes.test.ts +207 -48
  131. package/template/apps/server/src/system/department-routes.ts +114 -33
  132. package/template/apps/server/src/system/drizzle-repository.ts +15 -7
  133. package/template/apps/server/src/system/drizzle-scoped-repository.ts +5 -3
  134. package/template/apps/server/src/system/drizzle-system.ts +37 -9
  135. package/template/apps/server/src/system/drizzle-transaction-context.ts +14 -0
  136. package/template/apps/server/src/system/identity-route-config.ts +7 -2
  137. package/template/apps/server/src/system/mutation-runner.ts +23 -0
  138. package/template/apps/server/src/system/organization-postgres.integration.test.ts +176 -0
  139. package/template/apps/server/src/system/resource-access-response.ts +12 -6
  140. package/template/apps/server/src/system/role-department-scope-policy.ts +55 -0
  141. package/template/apps/server/src/system/route-helpers.ts +2 -5
  142. package/template/apps/server/src/system/route-types.ts +12 -3
  143. package/template/apps/server/src/system/routes-validation.test.ts +26 -0
  144. package/template/apps/server/src/system/routes.test.ts +10 -2
  145. package/template/apps/server/src/system/routes.ts +70 -36
  146. package/template/apps/server/src/system/seed.ts +7 -0
  147. package/template/apps/server/src/work-order/data-scope.integration.test.ts +9 -2
  148. package/template/apps/server/src/work-order/opensearch-index.test.ts +97 -0
  149. package/template/apps/server/src/work-order/opensearch-index.ts +313 -0
  150. package/template/apps/server/src/work-order/search-provider.ts +60 -1
  151. package/template/apps/server/src/work-order/service.ts +42 -10
  152. package/template/apps/web/src/app/error-pages.ts +9 -0
  153. package/template/apps/web/src/components/auth/PasswordChangeForm.vue +16 -11
  154. package/template/apps/web/src/components/auth/PasswordChangeForm.webtest.ts +14 -0
  155. package/template/apps/web/src/components/common/FormDialog.vue +16 -2
  156. package/template/apps/web/src/components/common/ModalLayout.webtest.ts +20 -1
  157. package/template/apps/web/src/composables/useGlobalSearch.ts +68 -0
  158. package/template/apps/web/src/composables/usePlatformSettings.ts +8 -7
  159. package/template/apps/web/src/layout/GlobalSearchResults.vue +58 -0
  160. package/template/apps/web/src/layout/NavigationSearchDialog.vue +42 -6
  161. package/template/apps/web/src/layout/NavigationSearchDialog.webtest.ts +71 -0
  162. package/template/apps/web/src/layout/NotificationMenu.vue +73 -31
  163. package/template/apps/web/src/layout/NotificationMenu.webtest.ts +44 -1
  164. package/template/apps/web/src/layout/NotificationMenuItem.vue +61 -0
  165. package/template/apps/web/src/layout/NotificationPreferencesDialog.vue +181 -0
  166. package/template/apps/web/src/layout/NotificationPreferencesDialog.webtest.ts +80 -0
  167. package/template/apps/web/src/lib/date-time.ts +22 -10
  168. package/template/apps/web/src/lib/date-time.webtest.ts +9 -1
  169. package/template/apps/web/src/main.ts +14 -1
  170. package/template/apps/web/src/pages/auth/ChangePasswordPage.vue +16 -1
  171. package/template/apps/web/src/pages/configuration/components/ModuleConfigVersionList.vue +1 -0
  172. package/template/apps/web/src/pages/configuration/components/ModuleConfigurationEditor.vue +10 -0
  173. package/template/apps/web/src/pages/configuration/components/ModuleConfigurationWorkbench.vue +37 -6
  174. package/template/apps/web/src/pages/errors/PlatformErrorPage.vue +115 -0
  175. package/template/apps/web/src/pages/errors/PlatformErrorPage.webtest.ts +44 -0
  176. package/template/apps/web/src/pages/errors/error-page-extension.ts +26 -0
  177. package/template/apps/web/src/pages/system/SystemNotificationsPage.vue +136 -68
  178. package/template/apps/web/src/pages/system/SystemNotificationsPage.webtest.ts +224 -12
  179. package/template/apps/web/src/pages/system/audit-log-presenter.ts +86 -0
  180. package/template/apps/web/src/pages/system/audit-time-range.ts +28 -0
  181. package/template/apps/web/src/pages/system/components/AuditActionCell.vue +10 -69
  182. package/template/apps/web/src/pages/system/components/AuditActionCell.webtest.ts +20 -27
  183. package/template/apps/web/src/pages/system/components/AuditLogDetailDialog.vue +134 -0
  184. package/template/apps/web/src/pages/system/components/AuditLogDetailDialog.webtest.ts +76 -0
  185. package/template/apps/web/src/pages/system/components/AuditLogFilters.vue +116 -52
  186. package/template/apps/web/src/pages/system/components/AuditLogFilters.webtest.ts +52 -15
  187. package/template/apps/web/src/pages/system/components/NotificationHistoryDialog.vue +129 -0
  188. package/template/apps/web/src/pages/system/components/NotificationManagementCard.vue +91 -0
  189. package/template/apps/web/src/pages/system/components/NotificationPublishDialog.vue +121 -0
  190. package/template/apps/web/src/pages/system/components/NotificationPublishForm.vue +162 -1
  191. package/template/apps/web/src/pages/system/components/SystemResourceActions.vue +68 -0
  192. package/template/apps/web/src/pages/system/components/SystemResourceEditor.vue +74 -0
  193. package/template/apps/web/src/pages/system/components/SystemResourceFilters.vue +3 -4
  194. package/template/apps/web/src/pages/system/components/SystemResourcePage.vue +91 -98
  195. package/template/apps/web/src/pages/system/components/SystemResourcePage.webtest.ts +68 -0
  196. package/template/apps/web/src/pages/system/components/SystemResourceTable.vue +20 -1
  197. package/template/apps/web/src/pages/system/components/SystemResourceTable.webtest.ts +28 -7
  198. package/template/apps/web/src/pages/system/components/department/DepartmentOrganizationForm.vue +210 -0
  199. package/template/apps/web/src/pages/system/components/department/DepartmentTransitionDialog.vue +112 -0
  200. package/template/apps/web/src/pages/system/components/user-account/UserAccountForm.vue +17 -4
  201. package/template/apps/web/src/pages/system/composables/useDepartmentTransitions.ts +79 -0
  202. package/template/apps/web/src/pages/system/composables/useNotificationPermissions.ts +14 -0
  203. package/template/apps/web/src/pages/system/composables/useSystemResource.ts +2 -0
  204. package/template/apps/web/src/pages/system/composables/useSystemResource.webtest.ts +15 -2
  205. package/template/apps/web/src/pages/system/resource-config.ts +12 -4
  206. package/template/apps/web/src/router/error-navigation.ts +50 -0
  207. package/template/apps/web/src/router/error-navigation.webtest.ts +106 -0
  208. package/template/apps/web/src/router/index.ts +71 -0
  209. package/template/apps/web/src/router/index.webtest.ts +29 -0
  210. package/template/apps/web/src/router/manifest-routes.webtest.ts +4 -0
  211. package/template/apps/web/src/router/shared-scaffold-neutrality.webtest.ts +12 -0
  212. package/template/apps/web/src/services/auth-api.ts +5 -0
  213. package/template/apps/web/src/services/http.ts +43 -6
  214. package/template/apps/web/src/services/http.webtest.ts +70 -0
  215. package/template/apps/web/src/services/notification-api.ts +85 -2
  216. package/template/apps/web/src/services/notification-api.webtest.ts +110 -0
  217. package/template/apps/web/src/services/search-api.ts +39 -0
  218. package/template/apps/web/src/services/system-api.ts +18 -0
  219. package/template/apps/web/vitest.config.ts +1 -0
  220. package/template/docker-compose.yml +68 -0
  221. package/template/docs/architecture/ai-egress.md +49 -7
  222. package/template/docs/architecture/ai-runtime.md +34 -6
  223. package/template/docs/architecture/data-scope-query-enforcement.md +4 -0
  224. package/template/docs/architecture/object-storage.md +22 -2
  225. package/template/docs/architecture/search-provider-protocol.md +38 -2
  226. package/template/docs/development/custom-error-pages.md +78 -0
  227. package/template/docs/development/custom-login-page.md +3 -3
  228. package/template/docs/platform/agent-runtime.md +76 -7
  229. package/template/docs/platform/audit-reliability.md +46 -2
  230. package/template/docs/platform/business-user-directory.md +82 -0
  231. package/template/docs/platform/module-configuration.md +13 -4
  232. package/template/docs/platform/notifications.md +67 -11
  233. package/template/docs/platform/organization-membership.md +17 -3
  234. package/template/docs/platform/system-crud-api.md +16 -0
  235. package/template/docs/platform/web-system-crud.md +6 -2
  236. package/template/package.json +4 -0
  237. package/template/packages/config/index.test.ts +45 -0
  238. package/template/packages/config/package.json +1 -1
  239. package/template/packages/config/src/platform.ts +169 -48
  240. package/template/packages/database/drizzle/0035_material_nightshade.sql +10 -0
  241. package/template/packages/database/drizzle/0036_hesitant_speed.sql +35 -0
  242. package/template/packages/database/drizzle/0037_absent_nick_fury.sql +2 -0
  243. package/template/packages/database/drizzle/0038_uneven_wasp.sql +38 -0
  244. package/template/packages/database/drizzle/meta/0035_snapshot.json +7331 -0
  245. package/template/packages/database/drizzle/meta/0036_snapshot.json +7436 -0
  246. package/template/packages/database/drizzle/meta/0037_snapshot.json +7457 -0
  247. package/template/packages/database/drizzle/meta/0038_snapshot.json +7749 -0
  248. package/template/packages/database/drizzle/meta/_journal.json +28 -0
  249. package/template/packages/database/package.json +1 -1
  250. package/template/packages/database/src/schema/identity.ts +7 -0
  251. package/template/packages/database/src/schema/notifications.ts +135 -0
  252. package/template/packages/database/src/schema-notification-delivery.test.ts +54 -0
  253. package/template/packages/database/src/schema-workflow-notification.test.ts +40 -1
  254. package/template/packages/modules/package.json +1 -1
  255. package/template/packages/modules/src/ai-operation-composition.test.ts +68 -0
  256. package/template/packages/modules/src/ai-operation-composition.ts +115 -0
  257. package/template/packages/modules/src/manifest.ts +26 -0
  258. package/template/packages/modules/src/provider-data-policy-composition.test.ts +74 -0
  259. package/template/packages/modules/src/system-api-permissions.ts +54 -0
  260. package/template/packages/modules/src/system-audit-actions.ts +15 -0
  261. package/template/packages/modules/src/system-features.ts +6 -1
  262. package/template/packages/modules/src/system-module-catalog.test.ts +16 -0
  263. package/template/packages/modules/src/system-module-catalog.ts +28 -4
  264. package/template/packages/modules/src/system-modules.test.ts +13 -1
  265. package/template/packages/modules/src/system-modules.ts +12 -0
  266. package/template/packages/modules/src/system-permissions.ts +11 -5
  267. package/template/packages/server-sdk/index.ts +11 -0
  268. package/template/packages/server-sdk/package.json +3 -2
  269. package/template/packages/server-sdk/src/ai-operation-registration.ts +2 -1
  270. package/template/packages/server-sdk/src/module-host.ts +8 -0
  271. package/template/packages/server-sdk/src/request-context.ts +2 -0
  272. package/template/packages/server-sdk/src/storage-port.ts +66 -0
  273. package/template/packages/server-sdk/src/tool-registration.ts +6 -0
  274. package/template/packages/server-sdk/src/user-directory-port.ts +42 -0
  275. package/template/packages/shared/package.json +1 -1
  276. package/template/packages/shared/src/audit.ts +25 -0
  277. package/template/packages/shared/src/module-configuration.ts +5 -0
  278. package/template/packages/shared/src/notification.ts +92 -1
  279. package/template/packages/shared/src/password.ts +7 -2
  280. package/template/packages/shared/src/platform-settings.ts +2 -0
  281. package/template/packages/storage/package.json +1 -1
  282. package/template/packages/storage/src/upload-service.test.ts +16 -0
  283. package/template/packages/storage/src/upload-service.ts +5 -1
  284. package/template/scripts/notification-postgres-regression.ts +27 -0
@@ -17,6 +17,7 @@ import { createWorkOrderRepository } from "./work-order/repository.js";
17
17
  import { createDrizzleWorkOrderRepository } from "./work-order/drizzle-repository.js";
18
18
  import { createWorkOrderRouteDefinitions } from "./work-order/routes.js";
19
19
  import { WorkOrderSearchProvider } from "./work-order/search-provider.js";
20
+ import { createWorkOrderOpenSearchIndex } from "./work-order/opensearch-index.js";
20
21
  import { WorkOrderService } from "./work-order/service.js";
21
22
 
22
23
  export interface WorkOrderModuleDependencies {
@@ -42,6 +43,7 @@ export interface WorkOrderModule {
42
43
  */
43
44
  export function createWorkOrderModule(): WorkOrderModule {
44
45
  let service: WorkOrderService | undefined;
46
+ const searchIndex = createWorkOrderOpenSearchIndex(process.env);
45
47
  const requireService = () => {
46
48
  if (!service) throw new Error("示例工单模块尚未挂载");
47
49
  return service;
@@ -52,7 +54,7 @@ export function createWorkOrderModule(): WorkOrderModule {
52
54
  requireService().operationalSnapshot(
53
55
  platformDataActor("task_work_order_snapshot", "工单运营快照任务"),
54
56
  ),
55
- searchProvider: new WorkOrderSearchProvider(requireService),
57
+ searchProvider: new WorkOrderSearchProvider(requireService, searchIndex),
56
58
  });
57
59
  return {
58
60
  ...serverModule,
@@ -67,6 +69,7 @@ export function createWorkOrderModule(): WorkOrderModule {
67
69
  ? createDrizzleWorkOrderRepository(dependencies.db)
68
70
  : createWorkOrderRepository()),
69
71
  dependencies.departments,
72
+ searchIndex,
70
73
  );
71
74
  },
72
75
  };
@@ -1,5 +1,6 @@
1
1
  import type {
2
2
  ApiPermissionBinding,
3
+ AuditActor,
3
4
  AuditEvent,
4
5
  EntitlementKey,
5
6
  FeatureFlagDefinition,
@@ -33,6 +34,7 @@ export interface RequestGuardContext {
33
34
  actor: RequestActor;
34
35
  startedAt: string;
35
36
  clientIp?: string;
37
+ userAgent?: string;
36
38
  license?: {
37
39
  versionKey?: LicenseVersionKey;
38
40
  /** V1 兼容字段。新 License 不再包含 Entitlement。 */
@@ -61,6 +63,7 @@ export interface GuardDecision {
61
63
 
62
64
  export interface RequestGuardContextOptions {
63
65
  clientIp?: string;
66
+ userAgent?: string;
64
67
  resolveActor?: (request: Request) => RequestActor | undefined;
65
68
  resolveLicense?: (
66
69
  request: Request,
@@ -86,10 +89,24 @@ export function createRequestGuardContext(
86
89
  actor: options.resolveActor?.(request) || anonymousActor(),
87
90
  startedAt: new Date().toISOString(),
88
91
  clientIp: options.clientIp,
92
+ userAgent: options.userAgent,
89
93
  license: options.resolveLicense?.(request),
90
94
  };
91
95
  }
92
96
 
97
+ export function auditActor(
98
+ context: Pick<RequestGuardContext, "actor" | "clientIp" | "userAgent">,
99
+ ): AuditActor {
100
+ return {
101
+ id: context.actor.id,
102
+ type: context.actor.type === "anonymous" ? "user" : context.actor.type,
103
+ name: context.actor.name,
104
+ roleCodes: context.actor.roleCodes,
105
+ ip: context.clientIp,
106
+ userAgent: context.userAgent,
107
+ };
108
+ }
109
+
93
110
  export function evaluatePermissionGuard(
94
111
  context: RequestGuardContext,
95
112
  binding: PermissionGuardRequirement,
@@ -207,11 +224,7 @@ function deniedDecision(
207
224
  id: randomUUIDv7(),
208
225
  action: "security.denied",
209
226
  outcome: "denied",
210
- actor: {
211
- id: context.actor.id,
212
- type: context.actor.type === "anonymous" ? "user" : context.actor.type,
213
- name: context.actor.name,
214
- },
227
+ actor: auditActor(context),
215
228
  target: {
216
229
  type: "guard",
217
230
  id: targetId,
@@ -0,0 +1,85 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import {
3
+ requestSourcePolicyFromEnv,
4
+ resolveRequestSource,
5
+ } from "./request-source.js";
6
+
7
+ describe("平台请求来源信任边界", () => {
8
+ test("默认使用直接对端并忽略客户端伪造的转发头", () => {
9
+ const source = resolveRequestSource(
10
+ request("203.0.113.66"),
11
+ "198.51.100.10",
12
+ requestSourcePolicyFromEnv({}),
13
+ );
14
+
15
+ expect(source).toEqual({
16
+ clientIp: "198.51.100.10",
17
+ userAgent: "Windy-Test/1.0",
18
+ });
19
+ });
20
+
21
+ test("仅显式启用且直接对端受信时采用单一合法转发来源", () => {
22
+ const policy = requestSourcePolicyFromEnv({
23
+ WINDY_TRUST_PROXY: "true",
24
+ WINDY_TRUSTED_PROXY_IPS: "10.0.0.8",
25
+ });
26
+
27
+ expect(
28
+ resolveRequestSource(request("203.0.113.66"), "::1", policy).clientIp,
29
+ ).toBe("203.0.113.66");
30
+ expect(
31
+ resolveRequestSource(request("203.0.113.67"), "10.0.0.8", policy)
32
+ .clientIp,
33
+ ).toBe("203.0.113.67");
34
+ expect(
35
+ resolveRequestSource(request("203.0.113.68"), "10.0.0.9", policy)
36
+ .clientIp,
37
+ ).toBe("10.0.0.9");
38
+ });
39
+
40
+ test("多段或非法转发来源退回直接对端", () => {
41
+ const policy = requestSourcePolicyFromEnv({
42
+ WINDY_TRUST_PROXY: "true",
43
+ });
44
+ for (const forwardedFor of [
45
+ "203.0.113.66, 198.51.100.9",
46
+ "not-an-ip",
47
+ "203.0.113.66:443",
48
+ ]) {
49
+ expect(
50
+ resolveRequestSource(request(forwardedFor), "127.0.0.1", policy)
51
+ .clientIp,
52
+ ).toBe("127.0.0.1");
53
+ }
54
+ });
55
+
56
+ test("代理配置默认关闭且错误配置 fail closed", () => {
57
+ expect(requestSourcePolicyFromEnv({})).toEqual({
58
+ trustProxy: false,
59
+ trustedProxyIps: [],
60
+ });
61
+ expect(() =>
62
+ requestSourcePolicyFromEnv({
63
+ WINDY_TRUSTED_PROXY_IPS: "10.0.0.8",
64
+ }),
65
+ ).toThrow("WINDY_TRUST_PROXY=true");
66
+ expect(() =>
67
+ requestSourcePolicyFromEnv({ WINDY_TRUST_PROXY: "yes" }),
68
+ ).toThrow("只允许 true 或 false");
69
+ expect(() =>
70
+ requestSourcePolicyFromEnv({
71
+ WINDY_TRUST_PROXY: "true",
72
+ WINDY_TRUSTED_PROXY_IPS: "10.0.0.999",
73
+ }),
74
+ ).toThrow("无效 IP");
75
+ });
76
+ });
77
+
78
+ function request(forwardedFor: string): Request {
79
+ return new Request("https://windy.example.test/api/system/users", {
80
+ headers: {
81
+ "user-agent": "Windy-Test/1.0",
82
+ "x-forwarded-for": forwardedFor,
83
+ },
84
+ });
85
+ }
@@ -0,0 +1,90 @@
1
+ import { isIP } from "node:net";
2
+
3
+ export interface RequestSourcePolicy {
4
+ trustProxy: boolean;
5
+ trustedProxyIps: readonly string[];
6
+ }
7
+
8
+ export interface RequestSource {
9
+ clientIp?: string;
10
+ userAgent?: string;
11
+ }
12
+
13
+ export function requestSourcePolicyFromEnv(
14
+ env: Record<string, string | undefined>,
15
+ ): RequestSourcePolicy {
16
+ const trustProxy = readBoolean(env.WINDY_TRUST_PROXY);
17
+ const configuredIps = (env.WINDY_TRUSTED_PROXY_IPS || "")
18
+ .split(",")
19
+ .map((value) => value.trim())
20
+ .filter(Boolean);
21
+ if (!trustProxy && configuredIps.length) {
22
+ throw new Error(
23
+ "配置 WINDY_TRUSTED_PROXY_IPS 时必须显式设置 WINDY_TRUST_PROXY=true",
24
+ );
25
+ }
26
+ return {
27
+ trustProxy,
28
+ trustedProxyIps: configuredIps.map((value) => {
29
+ const normalized = normalizeIp(value);
30
+ if (!normalized) {
31
+ throw new Error(`WINDY_TRUSTED_PROXY_IPS 包含无效 IP:${value}`);
32
+ }
33
+ return normalized;
34
+ }),
35
+ };
36
+ }
37
+
38
+ export function resolveRequestSource(
39
+ request: Request,
40
+ peerIp: string | undefined,
41
+ policy: RequestSourcePolicy,
42
+ ): RequestSource {
43
+ const normalizedPeerIp = normalizeIp(peerIp);
44
+ const forwardedIp = resolveForwardedIp(request, normalizedPeerIp, policy);
45
+ return {
46
+ clientIp: forwardedIp || normalizedPeerIp,
47
+ userAgent: normalizeUserAgent(request.headers.get("user-agent")),
48
+ };
49
+ }
50
+
51
+ function resolveForwardedIp(
52
+ request: Request,
53
+ peerIp: string | undefined,
54
+ policy: RequestSourcePolicy,
55
+ ): string | undefined {
56
+ if (!policy.trustProxy || !peerIp || !isTrustedProxy(peerIp, policy)) {
57
+ return undefined;
58
+ }
59
+ const header = request.headers.get("x-forwarded-for")?.trim();
60
+ if (!header || header.includes(",")) return undefined;
61
+ return normalizeIp(header);
62
+ }
63
+
64
+ function isTrustedProxy(peerIp: string, policy: RequestSourcePolicy): boolean {
65
+ if (isLoopback(peerIp)) return true;
66
+ return policy.trustedProxyIps.some((value) => normalizeIp(value) === peerIp);
67
+ }
68
+
69
+ function isLoopback(ip: string): boolean {
70
+ return ip === "::1" || (isIP(ip) === 4 && ip.startsWith("127."));
71
+ }
72
+
73
+ function normalizeIp(value: string | undefined): string | undefined {
74
+ const candidate = value?.trim().toLowerCase();
75
+ if (!candidate) return undefined;
76
+ const mapped = candidate.match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/)?.[1];
77
+ const normalized = mapped && isIP(mapped) === 4 ? mapped : candidate;
78
+ return isIP(normalized) ? normalized : undefined;
79
+ }
80
+
81
+ function normalizeUserAgent(value: string | null): string | undefined {
82
+ const normalized = value?.trim();
83
+ return normalized ? normalized.slice(0, 1_024) : undefined;
84
+ }
85
+
86
+ function readBoolean(value: string | undefined): boolean {
87
+ if (value === undefined || value === "" || value === "false") return false;
88
+ if (value === "true") return true;
89
+ throw new Error("WINDY_TRUST_PROXY 只允许 true 或 false");
90
+ }
@@ -104,6 +104,7 @@ const {
104
104
  moduleHosts,
105
105
  systemRepositories,
106
106
  } = await bootstrapServerModules(platformConfig, persistence);
107
+ const features = foundationSnapshot.features;
107
108
  // @windy-module system.license begin
108
109
  const platformLicenseOptions = createPlatformLicenseRuntimeOptions(process.env);
109
110
  const platformLicenseRuntime = new PlatformLicenseRuntime(
@@ -121,7 +122,7 @@ const runtime = createServerRuntime(process.env, {
121
122
  : persistence?.licenseResolver,
122
123
  // @windy-module system.license end
123
124
  featureResolver: persistence ? systemRepositories.features : undefined,
124
- featureCatalog: foundationSnapshot.features,
125
+ featureCatalog: features,
125
126
  // @windy-module system.audit begin
126
127
  auditReader: persistence?.auditReader,
127
128
  auditWriter: persistence?.auditWriter,
@@ -140,7 +141,11 @@ platformLicenseRuntime.setAuditSink(runtime.auditSink);
140
141
  // @windy-module system.license end
141
142
  const { authRepository, authService } = createAuthRuntime({
142
143
  repository: persistence?.authRepository,
143
- sessionTtlSeconds: platformConfig.security.sessionTtlSeconds,
144
+ sessionTtlSeconds: () => platformConfig.security.sessionTtlSeconds,
145
+ passwordPolicy: () => ({
146
+ minLength: platformConfig.security.passwordMinLength,
147
+ maxLength: platformConfig.security.passwordMaxLength,
148
+ }),
144
149
  auditWriter: (event) => runtime.auditSink.recordCriticalEvent(event),
145
150
  // @windy-module system.audit begin
146
151
  auditReader: persistence?.auditReader,
@@ -152,12 +157,17 @@ const storageRuntime = createStorageRuntime(
152
157
  persistence?.db,
153
158
  moduleHosts.uploadPolicies,
154
159
  );
160
+ configurationRuntime.bindStorage(storageRuntime.uploads);
155
161
  // @windy-module system.storage end
156
162
  // @windy-module system.settings begin
157
- const platformSettings = createPlatformSettingsRuntime(persistence?.db, {
158
- softwareName: platformConfig.name,
159
- logoUrl: platformConfig.logoUrl,
160
- });
163
+ const platformSettings = createPlatformSettingsRuntime(
164
+ persistence?.db,
165
+ {
166
+ softwareName: platformConfig.name,
167
+ logoUrl: platformConfig.logoUrl,
168
+ },
169
+ () => ({ timeZone: platformConfig.regional.timeZone }),
170
+ );
161
171
  // @windy-module system.settings end
162
172
  // @windy-module system.search begin
163
173
  const searchHost = createSearchHost(
@@ -169,13 +179,14 @@ const searchHost = createSearchHost(
169
179
  // @windy-module system.scheduler begin
170
180
  const moduleTaskDefinitions = createModuleTaskDefinitions(
171
181
  moduleHosts,
172
- foundationSnapshot.features,
182
+ features,
173
183
  );
174
184
  const {
175
185
  bulkDataService,
176
186
  scheduler,
177
187
  taskDefinitions: scheduledTaskDefinitions,
178
188
  notifications: notificationRepository,
189
+ notificationService,
179
190
  workflow: workflowService,
180
191
  governedExportService,
181
192
  durable: durableJobRuntime,
@@ -187,6 +198,7 @@ const {
187
198
  storage: storageRuntime,
188
199
  moduleTasks: moduleTaskDefinitions,
189
200
  bulkRetentionDays: readPositiveInt(process.env.BULK_DATA_RETENTION_DAYS, 7),
201
+ mailConfig: platformConfig.mail,
190
202
  installModuleDurableHandlers: moduleHosts.installDurableHandlers,
191
203
  });
192
204
  const schedulerWorker = new SchedulerWorker(
@@ -209,33 +221,29 @@ registerAuthRoutes(app, authService, {
209
221
  sessionCookieName: platformConfig.security.sessionCookieName,
210
222
  secureCookie: process.env.NODE_ENV === "production",
211
223
  });
212
- registerProfileRoutes(
213
- app,
214
- authRepository,
215
- runtime,
216
- foundationSnapshot.features,
217
- );
224
+ registerProfileRoutes(app, authRepository, runtime, features);
218
225
 
219
226
  // @windy-module system.license begin
220
227
  registerPlatformLicenseRoutes(app, platformLicenseRuntime);
221
228
  // @windy-module system.license end
222
229
 
223
230
  // @windy-module system.settings begin
224
- platformSettings.register(app, runtime, foundationSnapshot.features);
231
+ platformSettings.register(app, runtime, features);
225
232
  // @windy-module system.settings end
226
233
  // @windy-module system.notification begin
227
234
  registerNotificationRoutes(
228
235
  app,
229
236
  notificationRepository,
230
237
  runtime,
231
- foundationSnapshot.features,
238
+ features,
239
+ notificationService,
232
240
  );
233
241
  // @windy-module system.notification end
234
242
  // @windy-module system.workflow begin
235
243
  registerWorkflowRoutes(app, workflowService, runtime);
236
244
  // @windy-module system.workflow end
237
245
  // @windy-module system.configuration begin
238
- configurationRuntime.register(app, runtime, foundationSnapshot.features);
246
+ configurationRuntime.register(app, runtime, features);
239
247
  // @windy-module system.configuration end
240
248
  // @windy-module system.storage begin
241
249
  registerStorageRoutes(
@@ -248,7 +256,7 @@ registerStorageRoutes(
248
256
  `/api/platform/files/${fileId}`,
249
257
  },
250
258
  runtime,
251
- foundationSnapshot.features,
259
+ features,
252
260
  );
253
261
  // @windy-module system.storage end
254
262
  // @windy-module system.search begin
@@ -260,27 +268,18 @@ registerAdminBootstrapRoute(app, runtime, {
260
268
  snapshot: foundationSnapshot,
261
269
  loadMenus: () => systemRepositories.menuRegistry.tree(),
262
270
  });
263
- registerSessionContextRoute(app, runtime, foundationSnapshot.features);
271
+ registerSessionContextRoute(app, runtime, features);
264
272
 
265
273
  registerFoundationRoute(app, runtime, foundationSnapshot, systemRepositories);
266
274
 
267
275
  // @windy-module system.data-governance begin
268
- registerGovernedUserExportRoutes(
269
- app,
270
- governedExportService,
271
- runtime,
272
- foundationSnapshot.features,
273
- );
276
+ registerGovernedUserExportRoutes(app, governedExportService, runtime, features);
274
277
  // @windy-module system.data-governance end
275
- registerSystemRoutes(
276
- app,
277
- systemRepositories,
278
- runtime,
279
- foundationSnapshot.features,
280
- { authRepository: persistence?.authRepository },
281
- );
278
+ registerSystemRoutes(app, systemRepositories, runtime, features, {
279
+ authRepository: persistence?.authRepository,
280
+ });
282
281
  // @windy-module system.operations begin
283
- registerSystemOperationRoutes(app, runtime, foundationSnapshot.features, {
282
+ registerSystemOperationRoutes(app, runtime, features, {
284
283
  redisHealthProbe: configurationRuntime.redisHealthProbe,
285
284
  onlineUserProvider: () =>
286
285
  persistence?.authRepository.listActiveSessions(
@@ -293,7 +292,13 @@ registerSystemOperationRoutes(app, runtime, foundationSnapshot.features, {
293
292
  // @windy-module system.agent begin
294
293
  const toolHandlers = new Map([
295
294
  ...createSystemToolHandlers(systemRepositories, runtime),
296
- ...createModuleToolHandlerMap(moduleHosts),
295
+ ...createModuleToolHandlerMap(moduleHosts, {
296
+ runtime,
297
+ features: foundationSnapshot.features,
298
+ // @windy-module system.storage begin
299
+ storage: storageRuntime,
300
+ // @windy-module system.storage end
301
+ }),
297
302
  ]);
298
303
  const toolRegistry = new GovernedToolRegistry(
299
304
  foundationModules,
@@ -301,10 +306,7 @@ const toolRegistry = new GovernedToolRegistry(
301
306
  runtime,
302
307
  );
303
308
  registerAgentRoutes(app, toolRegistry);
304
- registerMcpRoutes(
305
- app,
306
- new PlatformMcpAdapter(toolRegistry, runtime, foundationSnapshot.features),
307
- );
309
+ registerMcpRoutes(app, new PlatformMcpAdapter(toolRegistry, runtime, features));
308
310
  registerAgentPlatformRuntime({
309
311
  app,
310
312
  platformConfig,
@@ -315,19 +317,14 @@ registerAgentPlatformRuntime({
315
317
  });
316
318
  // @windy-module system.agent end
317
319
  // @windy-module system.scheduler begin
318
- registerSchedulerRoutes(app, scheduler, runtime, foundationSnapshot.features);
320
+ registerSchedulerRoutes(app, scheduler, runtime, features);
319
321
  // @windy-module system.scheduler end
320
322
  // @windy-module system.bulk-data begin
321
- registerBulkDataRoutes(
322
- app,
323
- bulkDataService,
324
- runtime,
325
- foundationSnapshot.features,
326
- );
323
+ registerBulkDataRoutes(app, bulkDataService, runtime, features);
327
324
  // @windy-module system.bulk-data end
328
325
  installModuleRoutes(moduleHosts, app, {
329
326
  runtime,
330
- features: foundationSnapshot.features,
327
+ features,
331
328
  // @windy-module system.scheduler begin
332
329
  jobs: durableJobRuntime.service,
333
330
  // @windy-module system.scheduler end
@@ -337,6 +334,9 @@ installModuleRoutes(moduleHosts, app, {
337
334
  files: storageRuntime.files,
338
335
  },
339
336
  // @windy-module system.storage end
337
+ userDirectory: {
338
+ repositories: systemRepositories,
339
+ },
340
340
  });
341
341
 
342
342
  validateInstalledRuntime({
@@ -52,6 +52,8 @@ export function registerPlatformLicenseRoutes(
52
52
  const installed = await licenseRuntime.install(rawLicense, {
53
53
  actor: context.guardContext.actor,
54
54
  requestId: context.guardContext.requestId,
55
+ clientIp: context.guardContext.clientIp,
56
+ userAgent: context.guardContext.userAgent,
55
57
  });
56
58
  if (installed.result.status === "rejected") {
57
59
  return Response.json(
@@ -17,7 +17,11 @@ import type {
17
17
  LicenseFailureReason,
18
18
  SignedLicense,
19
19
  } from "@southwind-ai/shared";
20
- import type { RequestActor } from "../guards.js";
20
+ import {
21
+ auditActor,
22
+ type RequestActor,
23
+ type RequestGuardContext,
24
+ } from "../guards.js";
21
25
  import type { InMemoryAuditSink } from "../runtime.js";
22
26
  import type {
23
27
  LicenseEntitlementResolver,
@@ -57,10 +61,10 @@ export interface PlatformLicenseRuntimeOptions {
57
61
  now?: () => Date;
58
62
  }
59
63
 
60
- export interface LicenseInstallActorContext {
61
- actor: RequestActor;
62
- requestId: string;
63
- }
64
+ export type LicenseInstallActorContext = Pick<
65
+ RequestGuardContext,
66
+ "actor" | "requestId" | "clientIp" | "userAgent"
67
+ >;
64
68
 
65
69
  export class PlatformLicenseRuntime implements LicenseEntitlementResolver {
66
70
  private readonly stateStorage: LicenseStorage;
@@ -285,12 +289,7 @@ function installAuditEvent(
285
289
  id: crypto.randomUUID(),
286
290
  action: "license.update",
287
291
  outcome: success ? "success" : "failure",
288
- actor: {
289
- id: context.actor.id,
290
- type: "user",
291
- name: context.actor.name,
292
- roleCodes: context.actor.roleCodes,
293
- },
292
+ actor: auditActor(context),
294
293
  target: {
295
294
  type: "runtime-license",
296
295
  id: result.license?.claims.licenseId || candidate?.claims.licenseId,
@@ -17,7 +17,7 @@ import {
17
17
  } from "./module-host.js";
18
18
  import type { ServerPersistence } from "./persistence.js";
19
19
  // @windy-module system.storage begin
20
- import { platformUploadPolicies } from "./storage/runtime.js";
20
+ import { createPlatformUploadPolicies } from "./storage/runtime.js";
21
21
  // @windy-module system.storage end
22
22
  import {
23
23
  createSystemRepositories,
@@ -51,7 +51,7 @@ export async function bootstrapServerModules(
51
51
  installations: installedBusinessModules,
52
52
  baseUploadPolicies: [
53
53
  // @windy-module system.storage begin
54
- ...platformUploadPolicies,
54
+ ...createPlatformUploadPolicies(platformConfig),
55
55
  // @windy-module system.storage end
56
56
  ],
57
57
  });
@@ -5,6 +5,7 @@ import type {
5
5
  ModuleAiOperationHandlerRegistration,
6
6
  ModuleInitializer,
7
7
  ModuleRolePreset,
8
+ ModuleResourceFileAccessPolicy,
8
9
  ModuleRouteDefinition,
9
10
  ModuleSearchProvider,
10
11
  ModuleTaskHandlerRegistration,
@@ -54,6 +55,7 @@ export interface InitializeModuleHostsInput {
54
55
  export interface InitializedModuleHosts {
55
56
  readonly manifests: readonly ModuleManifest[];
56
57
  readonly uploadPolicies: readonly ModuleUploadPolicy[];
58
+ readonly resourceFileAccessPolicies: readonly ModuleResourceFileAccessPolicy[];
57
59
  readonly routes: readonly RegisteredModuleRoute[];
58
60
  readonly taskHandlers: readonly RegisteredModuleTaskHandler[];
59
61
  readonly toolHandlers: readonly RegisteredModuleToolHandler[];
@@ -63,5 +65,9 @@ export interface InitializedModuleHosts {
63
65
  manifestOf(moduleName: string): ModuleManifest;
64
66
  /** 该模块经 Host 注册的上传用途集合(不含平台基础用途)。 */
65
67
  uploadPurposesOf(moduleName: string): ReadonlySet<string>;
68
+ /** 该模块注册的资源文件读取策略。 */
69
+ resourceFileAccessPoliciesOf(
70
+ moduleName: string,
71
+ ): readonly ModuleResourceFileAccessPolicy[];
66
72
  installDurableHandlers(registry: DurableHandlerRegistry): void;
67
73
  }