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
@@ -6,7 +6,7 @@ import type { ModuleManifest } from "@southwind-ai/modules";
6
6
  import {
7
7
  requireModuleNamespace,
8
8
  type ModuleHost,
9
- type ModuleRouteDefinition,
9
+ type ModuleResourceFileAccessPolicy,
10
10
  type ModuleUploadPolicy,
11
11
  } from "@southwind-ai/server-sdk";
12
12
  import type {
@@ -19,6 +19,11 @@ import type {
19
19
  RegisteredModuleTaskHandler,
20
20
  RegisteredModuleToolHandler,
21
21
  } from "./initialize-contracts.js";
22
+ import {
23
+ registerResourceFileAccessPolicies,
24
+ validateResourceFileAccessPurposes,
25
+ } from "./resource-file-policies.js";
26
+ import { validateRouteDeclaration } from "./route-declaration.js";
22
27
 
23
28
  export type {
24
29
  InitializedModuleHosts,
@@ -53,6 +58,15 @@ export async function initializeModuleHosts(
53
58
  uploadPolicies,
54
59
  uploadPurposes,
55
60
  modulePurposes: new Map<string, Set<string>>(),
61
+ moduleDependencies: new Map(
62
+ input.modules.map((module) => [module.name, module.dependencies ?? []]),
63
+ ),
64
+ resourceFileAccessPolicies: [],
65
+ moduleResourceFileAccessPolicies: new Map<
66
+ string,
67
+ ModuleResourceFileAccessPolicy[]
68
+ >(),
69
+ resourceFileAccessPolicyKeys: new Set<string>(),
56
70
  handlerKeys: new Set<string>(),
57
71
  handlerInstallers: [],
58
72
  routes: [],
@@ -104,10 +118,12 @@ export async function initializeModuleHosts(
104
118
  }
105
119
  await installation.setup(createScopedHost(manifest, state));
106
120
  }
121
+ validateResourceFileAccessPurposes(state);
107
122
 
108
123
  return {
109
124
  manifests: input.modules,
110
125
  uploadPolicies: [...state.uploadPolicies],
126
+ resourceFileAccessPolicies: [...state.resourceFileAccessPolicies],
111
127
  routes: [...state.routes],
112
128
  taskHandlers: [...state.taskHandlers],
113
129
  toolHandlers: [...state.toolHandlers],
@@ -122,6 +138,9 @@ export async function initializeModuleHosts(
122
138
  uploadPurposesOf(moduleName) {
123
139
  return state.modulePurposes.get(moduleName) ?? new Set<string>();
124
140
  },
141
+ resourceFileAccessPoliciesOf(moduleName) {
142
+ return state.moduleResourceFileAccessPolicies.get(moduleName) ?? [];
143
+ },
125
144
  installDurableHandlers(registry) {
126
145
  for (const install of state.handlerInstallers) install(registry);
127
146
  },
@@ -132,6 +151,13 @@ interface HostCollections {
132
151
  uploadPolicies: ModuleUploadPolicy[];
133
152
  uploadPurposes: Set<string>;
134
153
  modulePurposes: Map<string, Set<string>>;
154
+ moduleDependencies: ReadonlyMap<string, readonly string[]>;
155
+ resourceFileAccessPolicies: ModuleResourceFileAccessPolicy[];
156
+ moduleResourceFileAccessPolicies: Map<
157
+ string,
158
+ ModuleResourceFileAccessPolicy[]
159
+ >;
160
+ resourceFileAccessPolicyKeys: Set<string>;
135
161
  handlerKeys: Set<string>;
136
162
  handlerInstallers: HandlerInstaller[];
137
163
  routes: RegisteredModuleRoute[];
@@ -167,6 +193,9 @@ function createScopedHost(
167
193
  purposes.add(policy.purpose);
168
194
  state.modulePurposes.set(moduleName, purposes);
169
195
  },
196
+ registerResourceFileAccessPolicies(policies) {
197
+ registerResourceFileAccessPolicies(manifest, state, policies);
198
+ },
170
199
  registerDurableHandler<T>(definition: DurableHandlerDefinition<T>) {
171
200
  requireModuleNamespace("任务 handler", definition.key, moduleName);
172
201
  if (state.handlerKeys.has(definition.key)) {
@@ -283,46 +312,3 @@ function createScopedHost(
283
312
  },
284
313
  };
285
314
  }
286
-
287
- function validateRouteDeclaration(
288
- manifest: ModuleManifest,
289
- definition: ModuleRouteDefinition,
290
- ): void {
291
- const binding = manifest.apiPermissions.find(
292
- (item) =>
293
- item.method === definition.method && item.path === definition.path,
294
- );
295
- const route = `${definition.method} ${definition.path}`;
296
- if (!binding) {
297
- throw new Error(`模块路由未在 Manifest 声明:${route}`);
298
- }
299
- if (binding.permissionKey !== definition.permissionKey) {
300
- throw new Error(`模块路由权限与 Manifest 声明不一致:${route}`);
301
- }
302
- if (
303
- definition.featureKey !== undefined &&
304
- binding.featureKey !== definition.featureKey
305
- ) {
306
- throw new Error(`模块路由 Feature 与 Manifest 声明不一致:${route}`);
307
- }
308
- if (
309
- binding.featureKey &&
310
- !manifest.features.some(({ key }) => key === binding.featureKey)
311
- ) {
312
- throw new Error(
313
- `模块路由 Feature 未在 Manifest 注册:${binding.featureKey}`,
314
- );
315
- }
316
- if (
317
- definition.auditAction &&
318
- !manifest.auditActions.some((action) =>
319
- typeof action === "string"
320
- ? action === definition.auditAction
321
- : action.key === definition.auditAction,
322
- )
323
- ) {
324
- throw new Error(
325
- `模块路由审计动作未在 Manifest 注册:${definition.auditAction}`,
326
- );
327
- }
328
- }
@@ -7,12 +7,21 @@ import type {
7
7
  ModuleJobSubmitter,
8
8
  ModuleRequestContext,
9
9
  ModuleStoragePort,
10
+ ModuleUserDirectoryPort,
11
+ } from "@southwind-ai/server-sdk";
12
+ import {
13
+ ModuleUserDirectoryError,
14
+ requireModuleNamespace,
10
15
  } from "@southwind-ai/server-sdk";
11
- import { requireModuleNamespace } from "@southwind-ai/server-sdk";
12
16
  import type { AuditEvent } from "@southwind-ai/shared";
13
17
  import type { ModuleManifest } from "@southwind-ai/modules";
14
- import type { RequestActor, RequestGuardContext } from "../guards.js";
18
+ import {
19
+ auditActor,
20
+ type RequestActor,
21
+ type RequestGuardContext,
22
+ } from "../guards.js";
15
23
  import type { createServerRuntime } from "../runtime.js";
24
+ import { unavailableModuleStoragePort } from "./storage-port.js";
16
25
 
17
26
  type ServerRuntime = ReturnType<typeof createServerRuntime>;
18
27
 
@@ -58,6 +67,7 @@ export interface ModuleRequestContextInput {
58
67
  runtime: ServerRuntime;
59
68
  jobs?: DurableJobs;
60
69
  storage?: ModuleStoragePort;
70
+ userDirectory?: ModuleUserDirectoryPort;
61
71
  }
62
72
 
63
73
  export function buildModuleRequestContext(
@@ -71,7 +81,22 @@ export function buildModuleRequestContext(
71
81
  body: input.body,
72
82
  audit: createModuleAuditEmitter(input),
73
83
  jobs: createModuleJobSubmitter(input),
74
- storage: input.storage ?? unavailableModuleStorage(input.moduleName),
84
+ storage: input.storage ?? unavailableModuleStoragePort(input.moduleName),
85
+ userDirectory:
86
+ input.userDirectory ?? unavailableModuleUserDirectory(input.moduleName),
87
+ };
88
+ }
89
+
90
+ function unavailableModuleUserDirectory(
91
+ moduleName: string,
92
+ ): ModuleUserDirectoryPort {
93
+ return {
94
+ async list() {
95
+ throw new ModuleUserDirectoryError(
96
+ "USER_DIRECTORY_UNAVAILABLE",
97
+ `模块 ${moduleName} 的用户目录 Port 未挂载`,
98
+ );
99
+ },
75
100
  };
76
101
  }
77
102
 
@@ -109,11 +134,7 @@ function buildAuditEvent(
109
134
  id: randomUUIDv7(),
110
135
  action,
111
136
  outcome: "success",
112
- actor: {
113
- id: context.actor.id,
114
- type: context.actor.type === "anonymous" ? "user" : context.actor.type,
115
- name: context.actor.name,
116
- },
137
+ actor: auditActor(context),
117
138
  target: input.target,
118
139
  requestId: context.requestId,
119
140
  metadata: input.metadata,
@@ -139,18 +160,3 @@ function createModuleJobSubmitter(
139
160
  },
140
161
  };
141
162
  }
142
-
143
- /**
144
- * 存储 Port 未挂载时的防御性实现:任何访问都以明确错误失败,
145
- * 而不是让模块悄悄绕过 purpose / owner 约束。
146
- */
147
- function unavailableModuleStorage(moduleName: string): ModuleStoragePort {
148
- const fail = (): never => {
149
- throw new Error(`模块 ${moduleName} 的存储 Port 未挂载`);
150
- };
151
- return {
152
- createUploadSession: () => fail(),
153
- openFile: () => fail(),
154
- openPublicFile: () => fail(),
155
- };
156
- }
@@ -0,0 +1,197 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { featureKey } from "@southwind-ai/shared";
3
+ import type {
4
+ ModuleInitializer,
5
+ ModuleResourceFileAccessPolicy,
6
+ } from "@southwind-ai/server-sdk";
7
+ import { initializeModuleHosts } from "./initialize.js";
8
+ import { manifest } from "./test-fixtures.js";
9
+
10
+ describe("模块资源文件访问策略注册", () => {
11
+ test("组合保留同 Manifest 的 purpose、Feature、Permission 与 Audit 契约", async () => {
12
+ const declared = declaredManifest();
13
+ const policy = accessPolicy();
14
+ const hosts = await initializeModuleHosts({
15
+ modules: [declared],
16
+ initializers: [
17
+ initializer((host) => {
18
+ host.registerResourceFileAccessPolicies([policy]);
19
+ host.registerUploadPolicy(uploadPolicy());
20
+ }),
21
+ ],
22
+ });
23
+
24
+ expect(hosts.resourceFileAccessPoliciesOf("evidence")).toEqual([policy]);
25
+ });
26
+
27
+ test("未注册 purpose、未知 Manifest 引用与重复策略均 fail-fast", async () => {
28
+ await expect(
29
+ initializeModuleHosts({
30
+ modules: [declaredManifest()],
31
+ initializers: [
32
+ initializer((host) => {
33
+ host.registerResourceFileAccessPolicies([accessPolicy()]);
34
+ }),
35
+ ],
36
+ }),
37
+ ).rejects.toThrow(
38
+ "资源文件访问策略用途未由模块 evidence 或其声明依赖注册:evidence.original",
39
+ );
40
+
41
+ await expect(
42
+ initializeModuleHosts({
43
+ modules: [declaredManifest()],
44
+ initializers: [
45
+ initializer((host) => {
46
+ host.registerUploadPolicy(uploadPolicy());
47
+ host.registerResourceFileAccessPolicies([
48
+ { ...accessPolicy(), permissionKey: "evidence.unknown" },
49
+ ]);
50
+ }),
51
+ ],
52
+ }),
53
+ ).rejects.toThrow("资源文件访问权限未在 Manifest 声明:evidence.unknown");
54
+
55
+ await expect(
56
+ initializeModuleHosts({
57
+ modules: [declaredManifest()],
58
+ initializers: [
59
+ initializer((host) => {
60
+ host.registerUploadPolicy(uploadPolicy());
61
+ host.registerResourceFileAccessPolicies([
62
+ accessPolicy(),
63
+ accessPolicy(),
64
+ ]);
65
+ }),
66
+ ],
67
+ }),
68
+ ).rejects.toThrow("资源文件访问策略重复注册");
69
+ });
70
+
71
+ test("资源类型、用途和治理 Key 不能越过模块命名空间", async () => {
72
+ await expect(
73
+ initializeModuleHosts({
74
+ modules: [declaredManifest()],
75
+ initializers: [
76
+ initializer((host) => {
77
+ host.registerUploadPolicy(uploadPolicy());
78
+ host.registerResourceFileAccessPolicies([
79
+ { ...accessPolicy(), resourceType: "other.file" },
80
+ ]);
81
+ }),
82
+ ],
83
+ }),
84
+ ).rejects.toThrow("资源文件类型不属于模块 evidence:other.file");
85
+ });
86
+
87
+ test("消费者可显式读取 Manifest 依赖模块拥有的 purpose", async () => {
88
+ const evidence = manifest("evidence");
89
+ const media = manifest("media", {
90
+ dependencies: ["evidence"],
91
+ permissions: [
92
+ {
93
+ key: "media.read",
94
+ label: "读取媒体",
95
+ module: "media",
96
+ moduleLabel: "媒体",
97
+ resource: "file",
98
+ resourceLabel: "文件",
99
+ action: "read",
100
+ },
101
+ ],
102
+ features: [
103
+ {
104
+ key: featureKey("media.catalog"),
105
+ label: "媒体目录",
106
+ module: "media",
107
+ visible: "visible",
108
+ enabled: true,
109
+ stage: "stable",
110
+ },
111
+ ],
112
+ auditActions: ["media.file.read"],
113
+ });
114
+ const policy: ModuleResourceFileAccessPolicy = {
115
+ purpose: "evidence.original",
116
+ resourceType: "media.evidence-file",
117
+ featureKey: "media.catalog",
118
+ permissionKey: "media.read",
119
+ auditAction: "media.file.read",
120
+ authorize: () => true,
121
+ };
122
+ const hosts = await initializeModuleHosts({
123
+ modules: [evidence, media],
124
+ installations: [
125
+ {
126
+ manifest: evidence,
127
+ setup(host) {
128
+ host.registerUploadPolicy(uploadPolicy());
129
+ },
130
+ },
131
+ {
132
+ manifest: media,
133
+ setup(host) {
134
+ host.registerResourceFileAccessPolicies([policy]);
135
+ },
136
+ },
137
+ ],
138
+ });
139
+
140
+ expect(hosts.resourceFileAccessPoliciesOf("media")).toEqual([policy]);
141
+ });
142
+ });
143
+
144
+ function initializer(
145
+ initialize: ModuleInitializer["initialize"],
146
+ ): ModuleInitializer {
147
+ return { moduleName: "evidence", initialize };
148
+ }
149
+
150
+ function declaredManifest() {
151
+ return manifest("evidence", {
152
+ permissions: [
153
+ {
154
+ key: "evidence.read",
155
+ label: "读取物证",
156
+ module: "evidence",
157
+ moduleLabel: "物证",
158
+ resource: "file",
159
+ resourceLabel: "文件",
160
+ action: "read",
161
+ },
162
+ ],
163
+ features: [
164
+ {
165
+ key: featureKey("evidence.catalog"),
166
+ label: "物证目录",
167
+ module: "evidence",
168
+ visible: "visible",
169
+ enabled: true,
170
+ stage: "stable",
171
+ },
172
+ ],
173
+ auditActions: ["evidence.file.read"],
174
+ });
175
+ }
176
+
177
+ function accessPolicy(): ModuleResourceFileAccessPolicy {
178
+ return {
179
+ purpose: "evidence.original",
180
+ resourceType: "evidence.file",
181
+ featureKey: "evidence.catalog",
182
+ permissionKey: "evidence.read",
183
+ auditAction: "evidence.file.read",
184
+ authorize: () => true,
185
+ };
186
+ }
187
+
188
+ function uploadPolicy() {
189
+ return {
190
+ purpose: "evidence.original",
191
+ maxByteSize: 1024,
192
+ maxPartByteSize: 512,
193
+ sessionTtlMs: 60_000,
194
+ allowedTypes: { "image/jpeg": ["jpg"] },
195
+ signature: "none" as const,
196
+ };
197
+ }
@@ -0,0 +1,108 @@
1
+ import type { ModuleManifest } from "@southwind-ai/modules";
2
+ import {
3
+ requireModuleNamespace,
4
+ type ModuleResourceFileAccessPolicy,
5
+ } from "@southwind-ai/server-sdk";
6
+
7
+ export interface ResourceFilePolicyCollections {
8
+ readonly modulePurposes: Map<string, Set<string>>;
9
+ readonly moduleDependencies: ReadonlyMap<string, readonly string[]>;
10
+ readonly resourceFileAccessPolicies: ModuleResourceFileAccessPolicy[];
11
+ readonly moduleResourceFileAccessPolicies: Map<
12
+ string,
13
+ ModuleResourceFileAccessPolicy[]
14
+ >;
15
+ readonly resourceFileAccessPolicyKeys: Set<string>;
16
+ }
17
+
18
+ export function registerResourceFileAccessPolicies(
19
+ manifest: ModuleManifest,
20
+ state: ResourceFilePolicyCollections,
21
+ policies: readonly ModuleResourceFileAccessPolicy[],
22
+ ): void {
23
+ for (const policy of policies) {
24
+ validateNamespace(manifest, policy);
25
+ validateManifest(manifest, policy);
26
+ const key = `${manifest.name}:${policy.purpose}:${policy.resourceType}`;
27
+ if (state.resourceFileAccessPolicyKeys.has(key)) {
28
+ throw new Error(
29
+ `资源文件访问策略重复注册:${policy.purpose} / ${policy.resourceType}`,
30
+ );
31
+ }
32
+ state.resourceFileAccessPolicyKeys.add(key);
33
+ state.resourceFileAccessPolicies.push(policy);
34
+ const modulePolicies =
35
+ state.moduleResourceFileAccessPolicies.get(manifest.name) ?? [];
36
+ modulePolicies.push(policy);
37
+ state.moduleResourceFileAccessPolicies.set(manifest.name, modulePolicies);
38
+ }
39
+ }
40
+
41
+ export function validateResourceFileAccessPurposes(
42
+ state: ResourceFilePolicyCollections,
43
+ ): void {
44
+ for (const [moduleName, policies] of state.moduleResourceFileAccessPolicies) {
45
+ for (const policy of policies) {
46
+ const owner = [...state.modulePurposes].find(([, purposes]) =>
47
+ purposes.has(policy.purpose),
48
+ )?.[0];
49
+ const allowedOwners = new Set([
50
+ moduleName,
51
+ ...(state.moduleDependencies.get(moduleName) ?? []),
52
+ ]);
53
+ if (!owner || !allowedOwners.has(owner)) {
54
+ throw new Error(
55
+ `资源文件访问策略用途未由模块 ${moduleName} 或其声明依赖注册:${policy.purpose}`,
56
+ );
57
+ }
58
+ }
59
+ }
60
+ }
61
+
62
+ function validateNamespace(
63
+ manifest: ModuleManifest,
64
+ policy: ModuleResourceFileAccessPolicy,
65
+ ): void {
66
+ const moduleName = manifest.name;
67
+ if (
68
+ ![moduleName, ...(manifest.dependencies ?? [])].some(
69
+ (owner) =>
70
+ policy.purpose === owner || policy.purpose.startsWith(`${owner}.`),
71
+ )
72
+ ) {
73
+ throw new Error(
74
+ `资源文件用途不属于模块 ${moduleName} 或其声明依赖:${policy.purpose}`,
75
+ );
76
+ }
77
+ requireModuleNamespace("资源文件类型", policy.resourceType, moduleName);
78
+ requireModuleNamespace("资源文件权限", policy.permissionKey, moduleName);
79
+ requireModuleNamespace("资源文件 Feature", policy.featureKey, moduleName);
80
+ requireModuleNamespace("资源文件审计动作", policy.auditAction, moduleName);
81
+ }
82
+
83
+ function validateManifest(
84
+ manifest: ModuleManifest,
85
+ policy: ModuleResourceFileAccessPolicy,
86
+ ): void {
87
+ if (!manifest.permissions.some(({ key }) => key === policy.permissionKey)) {
88
+ throw new Error(
89
+ `资源文件访问权限未在 Manifest 声明:${policy.permissionKey}`,
90
+ );
91
+ }
92
+ if (!manifest.features.some(({ key }) => key === policy.featureKey)) {
93
+ throw new Error(
94
+ `资源文件访问 Feature 未在 Manifest 声明:${policy.featureKey}`,
95
+ );
96
+ }
97
+ if (
98
+ !manifest.auditActions.some((action) =>
99
+ typeof action === "string"
100
+ ? action === policy.auditAction
101
+ : action.key === policy.auditAction,
102
+ )
103
+ ) {
104
+ throw new Error(
105
+ `资源文件访问审计动作未在 Manifest 声明:${policy.auditAction}`,
106
+ );
107
+ }
108
+ }
@@ -0,0 +1,45 @@
1
+ import type { ModuleManifest } from "@southwind-ai/modules";
2
+ import type { ModuleRouteDefinition } from "@southwind-ai/server-sdk";
3
+
4
+ export function validateRouteDeclaration(
5
+ manifest: ModuleManifest,
6
+ definition: ModuleRouteDefinition,
7
+ ): void {
8
+ const binding = manifest.apiPermissions.find(
9
+ (item) =>
10
+ item.method === definition.method && item.path === definition.path,
11
+ );
12
+ const route = `${definition.method} ${definition.path}`;
13
+ if (!binding) {
14
+ throw new Error(`模块路由未在 Manifest 声明:${route}`);
15
+ }
16
+ if (binding.permissionKey !== definition.permissionKey) {
17
+ throw new Error(`模块路由权限与 Manifest 声明不一致:${route}`);
18
+ }
19
+ if (
20
+ definition.featureKey !== undefined &&
21
+ binding.featureKey !== definition.featureKey
22
+ ) {
23
+ throw new Error(`模块路由 Feature 与 Manifest 声明不一致:${route}`);
24
+ }
25
+ if (
26
+ binding.featureKey &&
27
+ !manifest.features.some(({ key }) => key === binding.featureKey)
28
+ ) {
29
+ throw new Error(
30
+ `模块路由 Feature 未在 Manifest 注册:${binding.featureKey}`,
31
+ );
32
+ }
33
+ if (
34
+ definition.auditAction &&
35
+ !manifest.auditActions.some((action) =>
36
+ typeof action === "string"
37
+ ? action === definition.auditAction
38
+ : action.key === definition.auditAction,
39
+ )
40
+ ) {
41
+ throw new Error(
42
+ `模块路由审计动作未在 Manifest 注册:${definition.auditAction}`,
43
+ );
44
+ }
45
+ }
@@ -10,6 +10,11 @@ import {
10
10
  createModuleStoragePort,
11
11
  type ModuleStorageServices,
12
12
  } from "./storage-port.js";
13
+ import {
14
+ createModuleUserDirectoryPort,
15
+ type ModuleUserDirectoryServices,
16
+ } from "./user-directory-port.js";
17
+ import { createModuleStorageScope } from "./storage-scope.js";
13
18
 
14
19
  type ServerRuntime = ReturnType<typeof createServerRuntime>;
15
20
 
@@ -21,6 +26,8 @@ export interface ModuleRouteInstallContext {
21
26
  jobs?: DurableJobs;
22
27
  /** 平台存储服务;提供后模块获得受限 Upload/BlobRead Port。 */
23
28
  storage?: ModuleStorageServices;
29
+ /** 平台身份目录;Adapter 会绑定当前请求 Actor 与服务端数据范围。 */
30
+ userDirectory?: Omit<ModuleUserDirectoryServices, "runtime" | "features">;
24
31
  }
25
32
 
26
33
  /**
@@ -69,11 +76,28 @@ export function installModuleRoutes(
69
76
  jobs: context.jobs,
70
77
  storage: context.storage
71
78
  ? createModuleStoragePort(context.storage, {
72
- moduleName,
73
- ownerId: route.guardContext.actor.id,
74
- purposes: hosts.uploadPurposesOf(moduleName),
79
+ ...createModuleStorageScope({
80
+ moduleName,
81
+ purposes: hosts.uploadPurposesOf(moduleName),
82
+ resourceFileAccessPolicies:
83
+ hosts.resourceFileAccessPoliciesOf(moduleName),
84
+ guardContext: route.guardContext,
85
+ runtime: context.runtime,
86
+ features: context.features,
87
+ signal: new AbortController().signal,
88
+ }),
75
89
  })
76
90
  : undefined,
91
+ userDirectory: context.userDirectory
92
+ ? createModuleUserDirectoryPort(
93
+ {
94
+ ...context.userDirectory,
95
+ runtime: context.runtime,
96
+ features: context.features,
97
+ },
98
+ route.guardContext,
99
+ )
100
+ : undefined,
77
101
  });
78
102
  const result = await definition.handler(requestContext);
79
103
  if (definition.auditAction) {