create-windy 0.3.2 → 0.3.5

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 (343) hide show
  1. package/README.md +27 -9
  2. package/dist/cli.js +825 -117
  3. package/package.json +1 -1
  4. package/template/.agents/skills/windy-business-module/SKILL.md +16 -3
  5. package/template/.windy-template.json +2 -2
  6. package/template/AGENTS.md +4 -0
  7. package/template/README.md +18 -11
  8. package/template/_gitignore +1 -0
  9. package/template/apps/agent-server/README.md +2 -0
  10. package/template/apps/agent-server/pyproject.toml +8 -0
  11. package/template/apps/agent-server/src/southwind_agent_server/adk_adapter.py +87 -10
  12. package/template/apps/agent-server/src/southwind_agent_server/adk_model.py +18 -3
  13. package/template/apps/agent-server/src/southwind_agent_server/adk_tools.py +78 -11
  14. package/template/apps/agent-server/src/southwind_agent_server/app.py +120 -115
  15. package/template/apps/agent-server/src/southwind_agent_server/artifact_input.py +153 -0
  16. package/template/apps/agent-server/src/southwind_agent_server/config.py +29 -0
  17. package/template/apps/agent-server/src/southwind_agent_server/contracts.py +81 -1
  18. package/template/apps/agent-server/src/southwind_agent_server/deterministic.py +8 -0
  19. package/template/apps/agent-server/src/southwind_agent_server/engine.py +28 -0
  20. package/template/apps/agent-server/src/southwind_agent_server/fingerprint.py +41 -0
  21. package/template/apps/agent-server/src/southwind_agent_server/http_helpers.py +62 -0
  22. package/template/apps/agent-server/src/southwind_agent_server/lease.py +15 -0
  23. package/template/apps/agent-server/src/southwind_agent_server/multimodal.py +76 -0
  24. package/template/apps/agent-server/src/southwind_agent_server/openai_engine.py +112 -20
  25. package/template/apps/agent-server/src/southwind_agent_server/openai_provider.py +14 -1
  26. package/template/apps/agent-server/src/southwind_agent_server/postgres_store.py +329 -0
  27. package/template/apps/agent-server/src/southwind_agent_server/postgres_store_backend.py +332 -0
  28. package/template/apps/agent-server/src/southwind_agent_server/provider_governance.py +64 -3
  29. package/template/apps/agent-server/src/southwind_agent_server/runtime_factory.py +68 -0
  30. package/template/apps/agent-server/src/southwind_agent_server/service.py +218 -56
  31. package/template/apps/agent-server/src/southwind_agent_server/signal_events.py +36 -0
  32. package/template/apps/agent-server/src/southwind_agent_server/sqlite_catalog.py +107 -0
  33. package/template/apps/agent-server/src/southwind_agent_server/sqlite_schema.py +65 -0
  34. package/template/apps/agent-server/src/southwind_agent_server/sqlite_serialization.py +16 -0
  35. package/template/apps/agent-server/src/southwind_agent_server/sqlite_store.py +89 -57
  36. package/template/apps/agent-server/src/southwind_agent_server/store.py +105 -2
  37. package/template/apps/agent-server/src/southwind_agent_server/store_cipher.py +40 -0
  38. package/template/apps/agent-server/src/southwind_agent_server/telemetry.py +89 -0
  39. package/template/apps/agent-server/src/southwind_agent_server/tool_host.py +72 -0
  40. package/template/apps/agent-server/tests/test_adk_adapter.py +6 -2
  41. package/template/apps/agent-server/tests/test_api.py +24 -1
  42. package/template/apps/agent-server/tests/test_artifact_input.py +110 -0
  43. package/template/apps/agent-server/tests/test_config.py +4 -0
  44. package/template/apps/agent-server/tests/test_fingerprint.py +10 -0
  45. package/template/apps/agent-server/tests/test_multimodal.py +51 -0
  46. package/template/apps/agent-server/tests/test_openai_provider.py +6 -2
  47. package/template/apps/agent-server/tests/test_postgres_store.py +131 -0
  48. package/template/apps/agent-server/tests/test_provider_governance.py +61 -0
  49. package/template/apps/agent-server/tests/test_sqlite_store.py +95 -10
  50. package/template/apps/agent-server/tests/test_telemetry.py +25 -0
  51. package/template/apps/agent-server/tests/test_tool_approval.py +28 -0
  52. package/template/apps/agent-server/uv.lock +165 -0
  53. package/template/apps/document-server/Dockerfile +19 -0
  54. package/template/apps/document-server/pyproject.toml +43 -0
  55. package/template/apps/document-server/src/southwind_document_server/__init__.py +1 -0
  56. package/template/apps/document-server/src/southwind_document_server/adapter.py +337 -0
  57. package/template/apps/document-server/src/southwind_document_server/app.py +151 -0
  58. package/template/apps/document-server/src/southwind_document_server/config.py +94 -0
  59. package/template/apps/document-server/src/southwind_document_server/contracts.py +103 -0
  60. package/template/apps/document-server/src/southwind_document_server/service.py +149 -0
  61. package/template/apps/document-server/src/southwind_document_server/store.py +185 -0
  62. package/template/apps/document-server/tests/__init__.py +1 -0
  63. package/template/apps/document-server/tests/test_config.py +42 -0
  64. package/template/apps/document-server/tests/test_service.py +140 -0
  65. package/template/apps/document-server/uv.lock +430 -0
  66. package/template/apps/embedding-server/Dockerfile +17 -0
  67. package/template/apps/embedding-server/pyproject.toml +44 -0
  68. package/template/apps/embedding-server/src/southwind_embedding_server/__init__.py +1 -0
  69. package/template/apps/embedding-server/src/southwind_embedding_server/adapter.py +254 -0
  70. package/template/apps/embedding-server/src/southwind_embedding_server/app.py +119 -0
  71. package/template/apps/embedding-server/src/southwind_embedding_server/config.py +120 -0
  72. package/template/apps/embedding-server/src/southwind_embedding_server/contracts.py +106 -0
  73. package/template/apps/embedding-server/src/southwind_embedding_server/error.py +6 -0
  74. package/template/apps/embedding-server/src/southwind_embedding_server/image_input.py +87 -0
  75. package/template/apps/embedding-server/tests/__init__.py +1 -0
  76. package/template/apps/embedding-server/tests/test_adapter.py +92 -0
  77. package/template/apps/embedding-server/tests/test_config.py +38 -0
  78. package/template/apps/embedding-server/uv.lock +449 -0
  79. package/template/apps/server/src/agent/background-operation-port.ts +37 -0
  80. package/template/apps/server/src/agent/background-operation.test.ts +231 -0
  81. package/template/apps/server/src/agent/background-operation.ts +252 -0
  82. package/template/apps/server/src/agent/drizzle-run-binding-repository.ts +114 -0
  83. package/template/apps/server/src/agent/execution-grants.ts +7 -1
  84. package/template/apps/server/src/agent/fake-runtime.test-support.ts +43 -0
  85. package/template/apps/server/src/agent/idempotency-key.test.ts +28 -0
  86. package/template/apps/server/src/agent/idempotency-key.ts +11 -0
  87. package/template/apps/server/src/agent/remote-runtime.test.ts +115 -1
  88. package/template/apps/server/src/agent/remote-runtime.ts +173 -5
  89. package/template/apps/server/src/agent/run-authorization.test-support.ts +9 -0
  90. package/template/apps/server/src/agent/run-authorization.test.ts +126 -0
  91. package/template/apps/server/src/agent/run-authorization.ts +180 -0
  92. package/template/apps/server/src/agent/run-binding-repository.ts +93 -0
  93. package/template/apps/server/src/agent/run-contracts.ts +88 -1
  94. package/template/apps/server/src/agent/run-definition-support.ts +58 -0
  95. package/template/apps/server/src/agent/run-errors.ts +25 -0
  96. package/template/apps/server/src/agent/run-facade.test.ts +57 -46
  97. package/template/apps/server/src/agent/run-facade.ts +185 -65
  98. package/template/apps/server/src/agent/run-release.ts +78 -0
  99. package/template/apps/server/src/agent/run-route-input.ts +169 -0
  100. package/template/apps/server/src/agent/run-routes.ts +63 -76
  101. package/template/apps/server/src/agent/runtime-bootstrap.ts +169 -13
  102. package/template/apps/server/src/agent/sandbox-client.ts +200 -0
  103. package/template/apps/server/src/agent/sandbox-protocol.ts +76 -0
  104. package/template/apps/server/src/agent/sandbox-registry.ts +205 -0
  105. package/template/apps/server/src/agent/sandbox-runtime.test.ts +137 -0
  106. package/template/apps/server/src/agent/sandbox-worker-server.ts +195 -0
  107. package/template/apps/server/src/agent/sandbox-worker.ts +12 -0
  108. package/template/apps/server/src/agent/tool-approval.test.ts +75 -0
  109. package/template/apps/server/src/agent/tool-approval.ts +115 -0
  110. package/template/apps/server/src/agent/tool-audit.ts +62 -0
  111. package/template/apps/server/src/agent/tool-host.test.ts +39 -11
  112. package/template/apps/server/src/agent/tool-host.ts +184 -17
  113. package/template/apps/server/src/agent/tool-registry.ts +52 -47
  114. package/template/apps/server/src/agent/vision-facade.test.ts +256 -0
  115. package/template/apps/server/src/agent/vision-input.ts +120 -0
  116. package/template/apps/server/src/ai-artifact/codec.test.ts +44 -0
  117. package/template/apps/server/src/ai-artifact/codec.ts +137 -0
  118. package/template/apps/server/src/ai-artifact/routes.ts +37 -0
  119. package/template/apps/server/src/ai-artifact/runtime.ts +205 -0
  120. package/template/apps/server/src/application-services.ts +62 -2
  121. package/template/apps/server/src/auth/reauthentication.test.ts +40 -0
  122. package/template/apps/server/src/auth/reauthentication.ts +19 -0
  123. package/template/apps/server/src/auth/runtime.ts +9 -1
  124. package/template/apps/server/src/data-access/context.ts +6 -4
  125. package/template/apps/server/src/data-access/scope-snapshot.ts +27 -0
  126. package/template/apps/server/src/data-governance/export/approval-port.ts +6 -3
  127. package/template/apps/server/src/data-governance/export/authorization-port.test.ts +23 -4
  128. package/template/apps/server/src/data-governance/export/authorization-port.ts +89 -25
  129. package/template/apps/server/src/data-governance/export/drizzle-repository.integration.test.ts +2 -0
  130. package/template/apps/server/src/data-governance/export/drizzle-repository.ts +1 -0
  131. package/template/apps/server/src/data-governance/export/job-runtime.test.ts +15 -4
  132. package/template/apps/server/src/data-governance/export/job-runtime.ts +68 -15
  133. package/template/apps/server/src/data-governance/export/ndjson-stream.ts +25 -6
  134. package/template/apps/server/src/data-governance/export/postgres-recovery.integration.test.ts +4 -2
  135. package/template/apps/server/src/data-governance/export/renderer-job-runtime.test.ts +235 -0
  136. package/template/apps/server/src/data-governance/export/renderer-runtime.ts +293 -0
  137. package/template/apps/server/src/data-governance/export/renderer-selection.test.ts +67 -0
  138. package/template/apps/server/src/data-governance/export/repository.test.ts +2 -0
  139. package/template/apps/server/src/data-governance/export/request-fingerprint.test.ts +13 -0
  140. package/template/apps/server/src/data-governance/export/request-fingerprint.ts +2 -0
  141. package/template/apps/server/src/data-governance/export/routes.ts +11 -1
  142. package/template/apps/server/src/data-governance/export/scope-snapshot.ts +6 -18
  143. package/template/apps/server/src/data-governance/export/service-support.ts +188 -0
  144. package/template/apps/server/src/data-governance/export/service.ts +90 -113
  145. package/template/apps/server/src/data-governance/export/source.ts +101 -0
  146. package/template/apps/server/src/data-governance/export/types.ts +27 -0
  147. package/template/apps/server/src/document/contracts.ts +76 -0
  148. package/template/apps/server/src/document/facade.ts +339 -0
  149. package/template/apps/server/src/document/remote-runtime.test.ts +96 -0
  150. package/template/apps/server/src/document/remote-runtime.ts +223 -0
  151. package/template/apps/server/src/document/routes.ts +193 -0
  152. package/template/apps/server/src/embedding/contracts.ts +83 -0
  153. package/template/apps/server/src/embedding/error.ts +10 -0
  154. package/template/apps/server/src/embedding/facade.ts +217 -0
  155. package/template/apps/server/src/embedding/input.ts +172 -0
  156. package/template/apps/server/src/embedding/remote-runtime.test.ts +66 -0
  157. package/template/apps/server/src/embedding/remote-runtime.ts +218 -0
  158. package/template/apps/server/src/embedding/routes.ts +152 -0
  159. package/template/apps/server/src/index.ts +40 -43
  160. package/template/apps/server/src/jobs/artifact-service.test.ts +14 -2
  161. package/template/apps/server/src/jobs/artifact-service.ts +10 -0
  162. package/template/apps/server/src/jobs/drizzle-execution-repository.ts +17 -18
  163. package/template/apps/server/src/jobs/drizzle-execution.integration.test.ts +19 -1
  164. package/template/apps/server/src/jobs/drizzle-mappers.ts +4 -1
  165. package/template/apps/server/src/jobs/drizzle-recovery-repository.ts +49 -2
  166. package/template/apps/server/src/jobs/drizzle-repository.ts +1 -0
  167. package/template/apps/server/src/jobs/execution-repository.ts +17 -0
  168. package/template/apps/server/src/jobs/execution.test.ts +34 -0
  169. package/template/apps/server/src/jobs/in-memory-control.ts +135 -0
  170. package/template/apps/server/src/jobs/in-memory-execution.ts +34 -65
  171. package/template/apps/server/src/jobs/legacy-service-runtime.ts +5 -2
  172. package/template/apps/server/src/jobs/repository.ts +3 -0
  173. package/template/apps/server/src/jobs/worker.ts +80 -41
  174. package/template/apps/server/src/module-host/ai-operation-port.ts +170 -0
  175. package/template/apps/server/src/module-host/durable-context.test.ts +312 -0
  176. package/template/apps/server/src/module-host/durable-context.ts +124 -0
  177. package/template/apps/server/src/module-host/durable-handler-registration.ts +72 -0
  178. package/template/apps/server/src/module-host/governed-export-route-support.ts +171 -0
  179. package/template/apps/server/src/module-host/governed-export-routes.test.ts +327 -0
  180. package/template/apps/server/src/module-host/governed-export-routes.ts +238 -0
  181. package/template/apps/server/src/module-host/governed-renderer-registration.test.ts +247 -0
  182. package/template/apps/server/src/module-host/governed-resource-routes.ts +331 -0
  183. package/template/apps/server/src/module-host/governed-resources.test.ts +344 -0
  184. package/template/apps/server/src/module-host/governed-resources.ts +203 -0
  185. package/template/apps/server/src/module-host/http-runtime.ts +95 -0
  186. package/template/apps/server/src/module-host/initialize-contracts.ts +14 -0
  187. package/template/apps/server/src/module-host/initialize.test.ts +49 -1
  188. package/template/apps/server/src/module-host/initialize.ts +39 -16
  189. package/template/apps/server/src/module-host/job-port.test.ts +262 -0
  190. package/template/apps/server/src/module-host/job-port.ts +334 -0
  191. package/template/apps/server/src/module-host/request-context.test.ts +88 -2
  192. package/template/apps/server/src/module-host/request-context.ts +56 -22
  193. package/template/apps/server/src/module-host/route-installer.test.ts +10 -0
  194. package/template/apps/server/src/module-host/route-installer.ts +36 -1
  195. package/template/apps/server/src/module-host/sandbox-tool-registration.ts +42 -0
  196. package/template/apps/server/src/module-host/tool-handlers.ts +67 -31
  197. package/template/apps/server/src/module-host.ts +1 -0
  198. package/template/apps/server/src/module-runtime-validation.ts +7 -3
  199. package/template/apps/server/src/speech/contracts.ts +76 -0
  200. package/template/apps/server/src/speech/facade.ts +316 -0
  201. package/template/apps/server/src/speech/remote-runtime.test.ts +88 -0
  202. package/template/apps/server/src/speech/remote-runtime.ts +227 -0
  203. package/template/apps/server/src/speech/routes.ts +180 -0
  204. package/template/apps/server/src/system/actor.ts +39 -0
  205. package/template/apps/server/src/system/route-helpers.ts +1 -0
  206. package/template/apps/server/src/workflow/service.ts +2 -1
  207. package/template/apps/speech-server/Dockerfile +22 -0
  208. package/template/apps/speech-server/pyproject.toml +43 -0
  209. package/template/apps/speech-server/src/southwind_speech_server/__init__.py +1 -0
  210. package/template/apps/speech-server/src/southwind_speech_server/adapter.py +249 -0
  211. package/template/apps/speech-server/src/southwind_speech_server/app.py +183 -0
  212. package/template/apps/speech-server/src/southwind_speech_server/config.py +100 -0
  213. package/template/apps/speech-server/src/southwind_speech_server/contracts.py +103 -0
  214. package/template/apps/speech-server/src/southwind_speech_server/errors.py +6 -0
  215. package/template/apps/speech-server/src/southwind_speech_server/normalizer.py +108 -0
  216. package/template/apps/speech-server/src/southwind_speech_server/output_validation.py +128 -0
  217. package/template/apps/speech-server/src/southwind_speech_server/service.py +162 -0
  218. package/template/apps/speech-server/src/southwind_speech_server/store.py +209 -0
  219. package/template/apps/speech-server/tests/__init__.py +1 -0
  220. package/template/apps/speech-server/tests/test_config.py +33 -0
  221. package/template/apps/speech-server/tests/test_service.py +154 -0
  222. package/template/apps/speech-server/uv.lock +430 -0
  223. package/template/apps/web/src/app/business-routes.ts +22 -0
  224. package/template/apps/web/src/components/common/AccessSnapshotBoundary.vue +112 -0
  225. package/template/apps/web/src/components/common/AccessSnapshotBoundary.webtest.ts +130 -0
  226. package/template/apps/web/src/components/common/AccessSnapshotNotice.vue +117 -0
  227. package/template/apps/web/src/components/common/FormDialog.vue +6 -0
  228. package/template/apps/web/src/components/common/ModalLayout.webtest.ts +13 -0
  229. package/template/apps/web/src/composables/access-snapshot-change.ts +131 -0
  230. package/template/apps/web/src/composables/access-snapshot-change.webtest.ts +103 -0
  231. package/template/apps/web/src/composables/useAccessSnapshot.ts +48 -2
  232. package/template/apps/web/src/composables/useAccessSnapshot.webtest.ts +54 -0
  233. package/template/apps/web/src/composables/useAccessSnapshotMonitor.ts +42 -0
  234. package/template/apps/web/src/layout/AppShell.vue +28 -25
  235. package/template/apps/web/src/layout/AppShell.webtest.ts +1 -0
  236. package/template/apps/web/src/router/business-routes.ts +68 -0
  237. package/template/apps/web/src/router/business-routes.webtest.ts +85 -0
  238. package/template/apps/web/src/router/index.ts +7 -0
  239. package/template/apps/web/src/router/shared-scaffold-neutrality.webtest.ts +12 -0
  240. package/template/apps/web/src/services/access-api.ts +5 -1
  241. package/template/apps/web/src/services/access-api.webtest.ts +35 -0
  242. package/template/docker-compose.yml +127 -0
  243. package/template/docs/architecture/ai-egress.md +12 -9
  244. package/template/docs/architecture/ai-runtime.md +23 -11
  245. package/template/docs/architecture/durable-jobs.md +25 -3
  246. package/template/docs/architecture/foundation-infrastructure.md +163 -0
  247. package/template/docs/architecture/module-host.md +115 -0
  248. package/template/docs/architecture/server-module-interface.md +39 -0
  249. package/template/docs/architecture/web-access-snapshot-resilience.md +60 -0
  250. package/template/docs/development/business-web-routes.md +66 -0
  251. package/template/docs/development/custom-login-page.md +3 -3
  252. package/template/docs/development/module-lifecycle.md +71 -0
  253. package/template/docs/operations/observability.md +5 -1
  254. package/template/docs/platform/agent-runtime.md +21 -11
  255. package/template/docs/platform/agent-tool-sandbox.md +58 -0
  256. package/template/docs/platform/agent-tools.md +5 -0
  257. package/template/docs/platform/document-ai-runtime.md +204 -0
  258. package/template/docs/platform/governed-export-renderers.md +132 -0
  259. package/template/docs/platform/navigation-registry.md +5 -0
  260. package/template/docs/platform/organization-membership.md +2 -1
  261. package/template/docs/platform/speech-ai-runtime.md +156 -0
  262. package/template/docs/platform/system-crud-api.md +7 -1
  263. package/template/docs/platform/vision-ai-runtime.md +97 -0
  264. package/template/docs/platform/web-system-crud.md +6 -0
  265. package/template/package.json +16 -2
  266. package/template/packages/ai-client/index.ts +4 -0
  267. package/template/packages/ai-client/package.json +5 -2
  268. package/template/packages/ai-client/src/agent/client/artifact.ts +26 -0
  269. package/template/packages/ai-client/src/agent/client/attach.test.ts +124 -0
  270. package/template/packages/ai-client/src/agent/client/client.ts +119 -7
  271. package/template/packages/ai-client/src/agent/client/run.ts +14 -0
  272. package/template/packages/ai-client/src/agent/client/types.ts +18 -0
  273. package/template/packages/ai-client/src/agent/client/vision.test.ts +53 -0
  274. package/template/packages/ai-client/src/agent/contracts/contracts.test.ts +1 -0
  275. package/template/packages/ai-client/src/agent/contracts/events.ts +33 -0
  276. package/template/packages/ai-client/src/agent/contracts/runs.ts +71 -0
  277. package/template/packages/ai-client/src/agent/index.ts +1 -0
  278. package/template/packages/ai-client/src/artifact-ref.ts +8 -0
  279. package/template/packages/ai-client/src/document/client.test.ts +114 -0
  280. package/template/packages/ai-client/src/document/client.ts +134 -0
  281. package/template/packages/ai-client/src/document/contracts.ts +152 -0
  282. package/template/packages/ai-client/src/document/error.ts +43 -0
  283. package/template/packages/ai-client/src/document/index.ts +3 -0
  284. package/template/packages/ai-client/src/document/run.ts +104 -0
  285. package/template/packages/ai-client/src/document/validation.ts +115 -0
  286. package/template/packages/ai-client/src/embedding/client.test.ts +77 -0
  287. package/template/packages/ai-client/src/embedding/client.ts +96 -0
  288. package/template/packages/ai-client/src/embedding/contracts.ts +46 -0
  289. package/template/packages/ai-client/src/embedding/error.ts +61 -0
  290. package/template/packages/ai-client/src/embedding/index.ts +3 -0
  291. package/template/packages/ai-client/src/embedding/validation.ts +117 -0
  292. package/template/packages/ai-client/src/speech/client.test.ts +135 -0
  293. package/template/packages/ai-client/src/speech/client.ts +104 -0
  294. package/template/packages/ai-client/src/speech/contracts.ts +129 -0
  295. package/template/packages/ai-client/src/speech/error.ts +43 -0
  296. package/template/packages/ai-client/src/speech/index.ts +3 -0
  297. package/template/packages/ai-client/src/speech/run.ts +133 -0
  298. package/template/packages/ai-client/src/speech/validation.ts +132 -0
  299. package/template/packages/config/package.json +2 -2
  300. package/template/packages/database/drizzle/0039_early_ezekiel_stane.sql +1 -0
  301. package/template/packages/database/drizzle/0040_colossal_baron_strucker.sql +63 -0
  302. package/template/packages/database/drizzle/0041_yummy_prowler.sql +21 -0
  303. package/template/packages/database/drizzle/0042_puzzling_rumiko_fujikawa.sql +27 -0
  304. package/template/packages/database/drizzle/meta/0039_snapshot.json +7755 -0
  305. package/template/packages/database/drizzle/meta/0040_snapshot.json +8150 -0
  306. package/template/packages/database/drizzle/meta/0041_snapshot.json +8323 -0
  307. package/template/packages/database/drizzle/meta/0042_snapshot.json +8541 -0
  308. package/template/packages/database/drizzle/meta/_journal.json +28 -0
  309. package/template/packages/database/package.json +2 -2
  310. package/template/packages/database/src/agent-runtime-schema.test.ts +54 -0
  311. package/template/packages/database/src/schema/agent-runtime.ts +149 -0
  312. package/template/packages/database/src/schema/common.ts +2 -14
  313. package/template/packages/database/src/schema/durable-jobs.ts +5 -0
  314. package/template/packages/database/src/schema/governed-exports.ts +17 -5
  315. package/template/packages/database/src/schema/index.ts +3 -0
  316. package/template/packages/database/src/schema-durable-recovery.test.ts +31 -0
  317. package/template/packages/database/src/schema-governed-exports.test.ts +21 -1
  318. package/template/packages/database/src/schema.test.ts +6 -23
  319. package/template/packages/jobs/package.json +3 -2
  320. package/template/packages/jobs/src/handlers.test.ts +13 -0
  321. package/template/packages/jobs/src/handlers.ts +7 -1
  322. package/template/packages/jobs/src/types.ts +4 -0
  323. package/template/packages/modules/package.json +2 -2
  324. package/template/packages/modules/src/ai-operation-composition.test-support.ts +165 -0
  325. package/template/packages/modules/src/ai-operation-composition.test.ts +91 -127
  326. package/template/packages/modules/src/ai-operation-composition.ts +59 -1
  327. package/template/packages/modules/src/manifest.ts +25 -1
  328. package/template/packages/modules/src/tool-composition.ts +18 -0
  329. package/template/packages/server-sdk/index.ts +50 -0
  330. package/template/packages/server-sdk/package.json +10 -4
  331. package/template/packages/server-sdk/src/ai-artifact-port.ts +34 -0
  332. package/template/packages/server-sdk/src/ai-operation-port.ts +65 -0
  333. package/template/packages/server-sdk/src/ai-operation-registration.ts +42 -1
  334. package/template/packages/server-sdk/src/background-ai-port.ts +43 -0
  335. package/template/packages/server-sdk/src/data-governance-registration.ts +116 -0
  336. package/template/packages/server-sdk/src/durable-handler.ts +29 -0
  337. package/template/packages/server-sdk/src/jobs-port.ts +69 -0
  338. package/template/packages/server-sdk/src/module-host.ts +16 -2
  339. package/template/packages/server-sdk/src/request-context.ts +6 -2
  340. package/template/packages/server-sdk/src/sandbox-tool.ts +21 -0
  341. package/template/packages/server-sdk/src/tool-registration.ts +2 -0
  342. package/template/packages/shared/package.json +1 -1
  343. package/template/packages/shared/src/rbac.ts +30 -0
@@ -0,0 +1,137 @@
1
+ import {
2
+ createCipheriv,
3
+ createDecipheriv,
4
+ createHash,
5
+ randomBytes,
6
+ } from "node:crypto";
7
+ import type { ModuleResourceFileRequest } from "@southwind-ai/server-sdk";
8
+
9
+ export interface AiArtifactClaims {
10
+ readonly version: 1;
11
+ readonly moduleName: string;
12
+ readonly actorId: string;
13
+ readonly source: ModuleResourceFileRequest;
14
+ readonly mediaType: string;
15
+ readonly byteSize: number;
16
+ readonly sha256: string;
17
+ readonly issuedAt: string;
18
+ readonly expiresAt: string;
19
+ }
20
+
21
+ const aad = Buffer.from("southwind-ai-artifact-v1");
22
+
23
+ export class AiArtifactCodec {
24
+ readonly #key: Buffer;
25
+
26
+ constructor(secret: string) {
27
+ if (secret.trim().length < 32) {
28
+ throw new Error("AI Artifact 加密 Secret 至少需要 32 个字符");
29
+ }
30
+ this.#key = createHash("sha256").update(secret).digest();
31
+ }
32
+
33
+ encode(claims: AiArtifactClaims): string {
34
+ const iv = randomBytes(12);
35
+ const cipher = createCipheriv("aes-256-gcm", this.#key, iv);
36
+ cipher.setAAD(aad);
37
+ const encrypted = Buffer.concat([
38
+ cipher.update(JSON.stringify(claims), "utf8"),
39
+ cipher.final(),
40
+ ]);
41
+ return [
42
+ "v1",
43
+ iv.toString("base64url"),
44
+ encrypted.toString("base64url"),
45
+ cipher.getAuthTag().toString("base64url"),
46
+ ].join(".");
47
+ }
48
+
49
+ decode(token: string): AiArtifactClaims {
50
+ try {
51
+ if (token.length > 4_096) throw new Error("token too large");
52
+ const [version, ivValue, encryptedValue, tagValue, extra] =
53
+ token.split(".");
54
+ if (
55
+ version !== "v1" ||
56
+ !ivValue ||
57
+ !encryptedValue ||
58
+ !tagValue ||
59
+ extra
60
+ ) {
61
+ throw new Error("token shape");
62
+ }
63
+ const decipher = createDecipheriv(
64
+ "aes-256-gcm",
65
+ this.#key,
66
+ decodeCanonicalBase64url(ivValue, 12),
67
+ );
68
+ decipher.setAAD(aad);
69
+ decipher.setAuthTag(decodeCanonicalBase64url(tagValue, 16));
70
+ const plaintext = Buffer.concat([
71
+ decipher.update(decodeCanonicalBase64url(encryptedValue)),
72
+ decipher.final(),
73
+ ]);
74
+ return parseClaims(JSON.parse(plaintext.toString("utf8")));
75
+ } catch {
76
+ throw new AiArtifactCodecError();
77
+ }
78
+ }
79
+ }
80
+
81
+ export class AiArtifactCodecError extends Error {
82
+ constructor() {
83
+ super("AI Artifact 引用无效");
84
+ this.name = "AiArtifactCodecError";
85
+ }
86
+ }
87
+
88
+ function decodeCanonicalBase64url(value: string, byteSize?: number): Buffer {
89
+ if (!/^[A-Za-z0-9_-]+$/.test(value)) {
90
+ throw new Error("base64url invalid");
91
+ }
92
+ const decoded = Buffer.from(value, "base64url");
93
+ if (
94
+ decoded.toString("base64url") !== value ||
95
+ (byteSize !== undefined && decoded.byteLength !== byteSize)
96
+ ) {
97
+ throw new Error("base64url non-canonical");
98
+ }
99
+ return decoded;
100
+ }
101
+
102
+ function parseClaims(value: unknown): AiArtifactClaims {
103
+ if (!isRecord(value) || value.version !== 1 || !isRecord(value.source)) {
104
+ throw new Error("claims invalid");
105
+ }
106
+ const claims = value as unknown as AiArtifactClaims;
107
+ for (const field of [
108
+ claims.moduleName,
109
+ claims.actorId,
110
+ claims.source.fileId,
111
+ claims.source.purpose,
112
+ claims.source.resourceType,
113
+ claims.source.resourceId,
114
+ claims.mediaType,
115
+ claims.sha256,
116
+ claims.issuedAt,
117
+ claims.expiresAt,
118
+ ]) {
119
+ if (typeof field !== "string" || !field.trim()) {
120
+ throw new Error("claims field invalid");
121
+ }
122
+ }
123
+ if (
124
+ !Number.isSafeInteger(claims.byteSize) ||
125
+ claims.byteSize < 0 ||
126
+ !/^[a-f0-9]{64}$/.test(claims.sha256) ||
127
+ !Number.isFinite(Date.parse(claims.issuedAt)) ||
128
+ !Number.isFinite(Date.parse(claims.expiresAt))
129
+ ) {
130
+ throw new Error("claims metadata invalid");
131
+ }
132
+ return claims;
133
+ }
134
+
135
+ function isRecord(value: unknown): value is Record<string, unknown> {
136
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
137
+ }
@@ -0,0 +1,37 @@
1
+ import type { RouteApp } from "../system/route-types.js";
2
+ import { validInternalToken } from "../agent/run-routes.js";
3
+ import type { AiArtifactRuntime } from "./runtime.js";
4
+
5
+ interface RouteContext {
6
+ readonly request: Request;
7
+ }
8
+
9
+ export function registerAiArtifactRoutes(
10
+ app: RouteApp,
11
+ artifacts: AiArtifactRuntime,
12
+ internalToken: string,
13
+ ) {
14
+ app.get("/internal/ai/artifacts", async (raw) => {
15
+ const context = raw as RouteContext;
16
+ if (!validInternalToken(context.request, internalToken)) {
17
+ return Response.json({ code: "FORBIDDEN" }, { status: 403 });
18
+ }
19
+ const token = context.request.headers.get("x-ai-artifact-ref");
20
+ if (!token) {
21
+ return Response.json({ code: "ARTIFACT_UNAVAILABLE" }, { status: 404 });
22
+ }
23
+ try {
24
+ const opened = await artifacts.openToken(token, context.request.signal);
25
+ return new Response(opened.body, {
26
+ headers: {
27
+ "cache-control": "private, no-store",
28
+ "content-type": opened.mimeType,
29
+ "content-length": String(opened.byteSize),
30
+ "x-content-sha256": opened.sha256,
31
+ },
32
+ });
33
+ } catch {
34
+ return Response.json({ code: "ARTIFACT_UNAVAILABLE" }, { status: 404 });
35
+ }
36
+ });
37
+ }
@@ -0,0 +1,205 @@
1
+ import {
2
+ ModuleAiArtifactError,
3
+ type ModuleAiArtifactPort,
4
+ type ModuleAiArtifactRef,
5
+ type ModuleResourceFileRequest,
6
+ } from "@southwind-ai/server-sdk";
7
+ import type { FeatureFlagDefinition } from "@southwind-ai/shared";
8
+ import type { RequestActor, RequestGuardContext } from "../guards.js";
9
+ import type { InitializedModuleHosts } from "../module-host.js";
10
+ import {
11
+ createModuleStoragePort,
12
+ type ModuleStorageServices,
13
+ } from "../module-host/storage-port.js";
14
+ import { createModuleStorageScope } from "../module-host/storage-scope.js";
15
+ import type { createServerRuntime } from "../runtime.js";
16
+ import { AiArtifactCodec, type AiArtifactClaims } from "./codec.js";
17
+
18
+ export class AiArtifactRuntime {
19
+ constructor(
20
+ private readonly codec: AiArtifactCodec,
21
+ private readonly hosts: InitializedModuleHosts,
22
+ private readonly runtime: ReturnType<typeof createServerRuntime>,
23
+ private readonly features: readonly FeatureFlagDefinition[],
24
+ private readonly storage: ModuleStorageServices,
25
+ private readonly resolveActor: (
26
+ actorId: string,
27
+ ) => Promise<RequestActor | undefined>,
28
+ private readonly now: () => Date = () => new Date(),
29
+ ) {}
30
+
31
+ createModulePort(
32
+ moduleName: string,
33
+ guardContext: RequestGuardContext,
34
+ signal: AbortSignal,
35
+ ): ModuleAiArtifactPort {
36
+ return {
37
+ issue: (source, options) =>
38
+ this.issue(
39
+ moduleName,
40
+ guardContext,
41
+ source,
42
+ options?.ttlSeconds,
43
+ signal,
44
+ ),
45
+ };
46
+ }
47
+
48
+ async rotate(
49
+ token: string,
50
+ actorId: string,
51
+ moduleName: string,
52
+ ttlSeconds: number,
53
+ signal: AbortSignal,
54
+ ): Promise<ModuleAiArtifactRef> {
55
+ const claims = this.requireClaims(token, actorId, moduleName);
56
+ const opened = await this.open(claims, signal);
57
+ await opened.body.cancel().catch(() => undefined);
58
+ return this.reference(claims, opened, boundedTtl(ttlSeconds, 60, 3_600));
59
+ }
60
+
61
+ async openToken(token: string, signal: AbortSignal) {
62
+ return this.open(this.requireClaims(token), signal);
63
+ }
64
+
65
+ private async issue(
66
+ moduleName: string,
67
+ guardContext: RequestGuardContext,
68
+ source: ModuleResourceFileRequest,
69
+ ttlSeconds = 300,
70
+ signal: AbortSignal,
71
+ ) {
72
+ const port = this.storagePort(moduleName, guardContext, signal);
73
+ try {
74
+ const opened = await port.openResourceFile(source);
75
+ await opened.body.cancel().catch(() => undefined);
76
+ return this.reference(
77
+ {
78
+ version: 1,
79
+ moduleName,
80
+ actorId: guardContext.actor.id,
81
+ source,
82
+ mediaType: opened.mimeType,
83
+ byteSize: opened.byteSize,
84
+ sha256: opened.sha256,
85
+ issuedAt: this.now().toISOString(),
86
+ expiresAt: this.now().toISOString(),
87
+ },
88
+ opened,
89
+ boundedTtl(ttlSeconds, 60, 900),
90
+ );
91
+ } catch (error) {
92
+ if (error instanceof ModuleAiArtifactError) throw error;
93
+ throw new ModuleAiArtifactError(
94
+ "AI_ARTIFACT_FORBIDDEN",
95
+ "业务资源不能转换为 AI Artifact",
96
+ );
97
+ }
98
+ }
99
+
100
+ private reference(
101
+ claims: AiArtifactClaims,
102
+ opened: {
103
+ mimeType: string;
104
+ byteSize: number;
105
+ sha256: string;
106
+ },
107
+ ttlSeconds: number,
108
+ ): ModuleAiArtifactRef {
109
+ const issuedAt = this.now();
110
+ const expiresAt = new Date(
111
+ issuedAt.getTime() + ttlSeconds * 1_000,
112
+ ).toISOString();
113
+ const refreshed: AiArtifactClaims = {
114
+ ...claims,
115
+ mediaType: opened.mimeType,
116
+ byteSize: opened.byteSize,
117
+ sha256: opened.sha256,
118
+ issuedAt: issuedAt.toISOString(),
119
+ expiresAt,
120
+ };
121
+ return {
122
+ value: this.codec.encode(refreshed),
123
+ mediaType: refreshed.mediaType,
124
+ byteSize: refreshed.byteSize,
125
+ sha256: refreshed.sha256,
126
+ expiresAt,
127
+ };
128
+ }
129
+
130
+ private requireClaims(token: string, actorId?: string, moduleName?: string) {
131
+ const claims = this.codec.decode(token);
132
+ if (
133
+ claims.expiresAt <= this.now().toISOString() ||
134
+ (actorId && claims.actorId !== actorId) ||
135
+ (moduleName && claims.moduleName !== moduleName)
136
+ ) {
137
+ throw new ModuleAiArtifactError(
138
+ "AI_ARTIFACT_FORBIDDEN",
139
+ "AI Artifact 已过期或不属于当前调用方",
140
+ );
141
+ }
142
+ return claims;
143
+ }
144
+
145
+ private async open(claims: AiArtifactClaims, signal: AbortSignal) {
146
+ const actor = await this.resolveActor(claims.actorId);
147
+ if (!actor) throw forbidden();
148
+ const request = new Request("http://ai-artifact.internal/open", { signal });
149
+ const guardContext = await this.runtime.createGuardContextForActor(
150
+ request,
151
+ actor,
152
+ );
153
+ const opened = await this.storagePort(
154
+ claims.moduleName,
155
+ guardContext,
156
+ signal,
157
+ ).openResourceFile(claims.source);
158
+ if (
159
+ opened.mimeType !== claims.mediaType ||
160
+ opened.byteSize !== claims.byteSize ||
161
+ opened.sha256 !== claims.sha256
162
+ ) {
163
+ await opened.body.cancel().catch(() => undefined);
164
+ throw forbidden();
165
+ }
166
+ return opened;
167
+ }
168
+
169
+ private storagePort(
170
+ moduleName: string,
171
+ guardContext: RequestGuardContext,
172
+ signal: AbortSignal,
173
+ ) {
174
+ return createModuleStoragePort(
175
+ this.storage,
176
+ createModuleStorageScope({
177
+ moduleName,
178
+ purposes: this.hosts.uploadPurposesOf(moduleName),
179
+ resourceFileAccessPolicies:
180
+ this.hosts.resourceFileAccessPoliciesOf(moduleName),
181
+ guardContext,
182
+ runtime: this.runtime,
183
+ features: this.features,
184
+ signal,
185
+ }),
186
+ );
187
+ }
188
+ }
189
+
190
+ function boundedTtl(value: number, minimum: number, maximum: number) {
191
+ if (!Number.isSafeInteger(value) || value < minimum || value > maximum) {
192
+ throw new ModuleAiArtifactError(
193
+ "AI_ARTIFACT_INVALID",
194
+ `AI Artifact TTL 必须在 ${minimum}–${maximum} 秒之间`,
195
+ );
196
+ }
197
+ return value;
198
+ }
199
+
200
+ function forbidden() {
201
+ return new ModuleAiArtifactError(
202
+ "AI_ARTIFACT_FORBIDDEN",
203
+ "AI Artifact 当前授权已失效",
204
+ );
205
+ }
@@ -5,7 +5,10 @@ import { GovernedExportJobRuntime } from "./data-governance/export/job-runtime.j
5
5
  import { SystemGovernedExportAuthorization } from "./data-governance/export/authorization-port.js";
6
6
  import { InMemoryGovernedExportRepository } from "./data-governance/export/repository.js";
7
7
  import { GovernedExportService } from "./data-governance/export/service.js";
8
- import { GovernedUserExportSource } from "./data-governance/export/source.js";
8
+ import {
9
+ GovernedUserExportSource,
10
+ ModuleGovernedExportSource,
11
+ } from "./data-governance/export/source.js";
9
12
  import { DrizzleBulkDataRepository } from "./bulk-data/drizzle-repository.js";
10
13
  import { InMemoryBulkDataRepository } from "./bulk-data/repository.js";
11
14
  import type { createFoundationSnapshot } from "./foundation.js";
@@ -45,6 +48,12 @@ import {
45
48
  import { SystemNotificationRecipientDirectory } from "./notification/recipient-directory.js";
46
49
  import { NotificationService } from "./notification/service.js";
47
50
  import { SmtpNotificationChannelAdapter } from "./notification/smtp-adapter.js";
51
+ import type { InitializedModuleHosts } from "./module-host.js";
52
+ import {
53
+ createModuleDurableContextualizer,
54
+ type ModuleDurableContextServices,
55
+ } from "./module-host/durable-context.js";
56
+ import { resolveCurrentSystemActor } from "./system/actor.js";
48
57
 
49
58
  interface ApplicationServicesInput {
50
59
  persistence: ReturnType<typeof createServerPersistence>;
@@ -56,6 +65,10 @@ interface ApplicationServicesInput {
56
65
  bulkRetentionDays: number;
57
66
  mailConfig?: PlatformConfig["mail"];
58
67
  installModuleDurableHandlers?: (registry: DurableHandlerRegistry) => void;
68
+ moduleHosts: InitializedModuleHosts;
69
+ // @windy-module system.agent begin
70
+ backgroundAi?: ModuleDurableContextServices["backgroundAi"];
71
+ // @windy-module system.agent end
59
72
  }
60
73
 
61
74
  export async function createApplicationServices(
@@ -155,6 +168,17 @@ export async function createApplicationServices(
155
168
  runtime: input.runtime,
156
169
  workflow,
157
170
  installModuleDurableHandlers: input.installModuleDurableHandlers,
171
+ durableContextualize: createModuleDurableContextualizer({
172
+ hosts: input.moduleHosts,
173
+ runtime: input.runtime,
174
+ features: input.foundation.features,
175
+ storage: input.storage,
176
+ // @windy-module system.agent begin
177
+ backgroundAi: input.backgroundAi,
178
+ // @windy-module system.agent end
179
+ resolveActor: (actorId) =>
180
+ resolveCurrentSystemActor(input.systemRepositories, actorId),
181
+ }),
158
182
  });
159
183
  const governedExportRequests = persistence
160
184
  ? new DrizzleGovernedExportRepository(persistence.db)
@@ -162,6 +186,33 @@ export async function createApplicationServices(
162
186
  const governedExportApprovals = new WorkflowGovernedExportApproval(workflow, [
163
187
  { code: "super-admin", name: "超级管理员" },
164
188
  ]);
189
+ const moduleGovernedExportSources = input.moduleHosts.governedResources
190
+ .filter(({ registration }) => registration.export)
191
+ .map(({ moduleName, registration }) => {
192
+ const policy = input.moduleHosts
193
+ .manifestOf(moduleName)
194
+ .dataPolicies?.find(({ key }) => key === registration.policyKey);
195
+ if (!policy) {
196
+ throw new Error(`模块数据治理策略不存在:${registration.policyKey}`);
197
+ }
198
+ return new ModuleGovernedExportSource(registration, policy, (actorId) =>
199
+ resolveCurrentSystemActor(input.systemRepositories, actorId),
200
+ );
201
+ });
202
+ const moduleGovernedExportRenderers = input.moduleHosts.governedResources.flatMap(
203
+ ({ moduleName, registration }) => {
204
+ const policy = input.moduleHosts
205
+ .manifestOf(moduleName)
206
+ .dataPolicies?.find(({ key }) => key === registration.policyKey);
207
+ if (!policy) {
208
+ throw new Error(`模块数据治理策略不存在:${registration.policyKey}`);
209
+ }
210
+ return (registration.export?.renderers ?? []).map((renderer) => ({
211
+ resourceType: policy.resourceType,
212
+ renderer,
213
+ }));
214
+ },
215
+ );
165
216
  const governedExports = new GovernedExportJobRuntime(
166
217
  governedExportRequests,
167
218
  services.durable.service,
@@ -177,9 +228,18 @@ export async function createApplicationServices(
177
228
  persistence?.authRepository,
178
229
  ).users,
179
230
  ),
231
+ ...moduleGovernedExportSources,
180
232
  ],
181
233
  (request) => governedExportApprovals.isApproved(request),
182
- new SystemGovernedExportAuthorization(input.systemRepositories),
234
+ new SystemGovernedExportAuthorization(input.systemRepositories, {
235
+ policies: input.moduleHosts.manifests.flatMap(
236
+ (manifest) => manifest.dataPolicies ?? [],
237
+ ),
238
+ features: input.foundation.features,
239
+ runtime: input.runtime,
240
+ }),
241
+ undefined,
242
+ moduleGovernedExportRenderers,
183
243
  );
184
244
  const governedExportService = new GovernedExportService(
185
245
  governedExportRequests,
@@ -0,0 +1,40 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { InMemoryAuthRepository } from "./repository.js";
3
+ import { ReauthenticationService } from "./reauthentication.js";
4
+
5
+ describe("AuthService reauthenticate", () => {
6
+ test("只接受当前启用用户的正确密码", async () => {
7
+ const repository = new InMemoryAuthRepository([
8
+ {
9
+ id: "user-active",
10
+ username: "active",
11
+ displayName: "启用用户",
12
+ passwordHash: "stored-hash",
13
+ credentialState: "active",
14
+ status: "active",
15
+ roleCodes: [],
16
+ permissionKeys: [],
17
+ },
18
+ {
19
+ id: "user-disabled",
20
+ username: "disabled",
21
+ displayName: "停用用户",
22
+ passwordHash: "stored-hash",
23
+ credentialState: "active",
24
+ status: "disabled",
25
+ roleCodes: [],
26
+ permissionKeys: [],
27
+ },
28
+ ]);
29
+ const service = new ReauthenticationService(repository, {
30
+ hash: async () => "stored-hash",
31
+ verify: async (hash, password) =>
32
+ hash === "stored-hash" && password === "correct",
33
+ });
34
+
35
+ expect(await service.verify("user-active", "correct")).toBe(true);
36
+ expect(await service.verify("user-active", "wrong")).toBe(false);
37
+ expect(await service.verify("user-disabled", "correct")).toBe(false);
38
+ expect(await service.verify("missing", "correct")).toBe(false);
39
+ });
40
+ });
@@ -0,0 +1,19 @@
1
+ import type { PasswordHasher } from "./password.js";
2
+ import type { AuthRepository } from "./repository.js";
3
+
4
+ export class ReauthenticationService {
5
+ constructor(
6
+ private readonly repository: AuthRepository,
7
+ private readonly passwordHasher: PasswordHasher,
8
+ ) {}
9
+
10
+ async verify(actorId: string, password: string): Promise<boolean> {
11
+ if (!actorId.trim() || !password || password.length > 512) return false;
12
+ const user = await this.repository.findUserByReference(actorId);
13
+ return Boolean(
14
+ user?.status === "active" &&
15
+ user.passwordHash &&
16
+ (await this.passwordHasher.verify(user.passwordHash, password)),
17
+ );
18
+ }
19
+ }
@@ -2,6 +2,8 @@ import type { AuditEventReader, AuditEventWriter } from "../runtime.js";
2
2
  import type { PasswordPolicy } from "@southwind-ai/shared";
3
3
  import { InMemoryAuthRepository, type AuthRepository } from "./repository.js";
4
4
  import { AuthService } from "./service.js";
5
+ import { createArgon2PasswordHasher } from "./password.js";
6
+ import { ReauthenticationService } from "./reauthentication.js";
5
7
 
6
8
  interface AuthRuntimeInput {
7
9
  repository?: AuthRepository;
@@ -13,11 +15,17 @@ interface AuthRuntimeInput {
13
15
 
14
16
  export function createAuthRuntime(input: AuthRuntimeInput) {
15
17
  const authRepository = input.repository ?? new InMemoryAuthRepository();
18
+ const passwordHasher = createArgon2PasswordHasher(input.passwordPolicy);
16
19
  const authService = new AuthService(authRepository, {
17
20
  sessionTtlSeconds: input.sessionTtlSeconds,
18
21
  passwordPolicy: input.passwordPolicy,
19
22
  auditWriter: { write: input.auditWriter },
20
23
  auditReader: input.auditReader,
24
+ passwordHasher,
21
25
  });
22
- return { authRepository, authService };
26
+ const reauthentication = new ReauthenticationService(
27
+ authRepository,
28
+ passwordHasher,
29
+ );
30
+ return { authRepository, authService, reauthentication };
23
31
  }
@@ -1,5 +1,7 @@
1
- import type { DataScope } from "@southwind-ai/shared";
2
- import type { GovernedExportScopeSnapshot } from "@southwind-ai/database";
1
+ import type {
2
+ AuthorizationScopeSnapshot,
3
+ DataScope,
4
+ } from "@southwind-ai/shared";
3
5
 
4
6
  export interface DataAccessRolePolicy {
5
7
  id: string;
@@ -96,7 +98,7 @@ export function isServerScopeContext(value: unknown): value is ScopeContext {
96
98
  */
97
99
  export function snapshotServerScopeContext(
98
100
  value: unknown,
99
- ): GovernedExportScopeSnapshot {
101
+ ): AuthorizationScopeSnapshot {
100
102
  if (!isServerScopeContext(value)) {
101
103
  throw new Error("治理任务缺少服务端 ScopeContext");
102
104
  }
@@ -126,7 +128,7 @@ export function snapshotServerScopeContext(
126
128
 
127
129
  /** Worker 只从受控 Repository 读取投影并在服务端恢复 nominal context。 */
128
130
  export function restoreServerScopeContext(
129
- snapshot: GovernedExportScopeSnapshot,
131
+ snapshot: AuthorizationScopeSnapshot,
130
132
  ): ScopeContext {
131
133
  if (
132
134
  snapshot.tenantId !== "single-tenant" ||
@@ -0,0 +1,27 @@
1
+ import { createHash } from "node:crypto";
2
+ import type { AuthorizationScopeSnapshot } from "@southwind-ai/shared";
3
+ import { snapshotServerScopeContext } from "./context.js";
4
+
5
+ export function captureAuthorizationScope(value: unknown) {
6
+ const snapshot = snapshotServerScopeContext(value);
7
+ return { snapshot, hash: hashAuthorizationScope(snapshot) };
8
+ }
9
+
10
+ export function hashAuthorizationScope(
11
+ snapshot: AuthorizationScopeSnapshot,
12
+ ): string {
13
+ return createHash("sha256").update(stableJson(snapshot)).digest("hex");
14
+ }
15
+
16
+ function stableJson(value: unknown): string {
17
+ if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
18
+ if (value && typeof value === "object") {
19
+ const entries = Object.entries(value as Record<string, unknown>)
20
+ .filter(([, item]) => item !== undefined)
21
+ .sort(([left], [right]) => left.localeCompare(right));
22
+ return `{${entries
23
+ .map(([key, item]) => `${JSON.stringify(key)}:${stableJson(item)}`)
24
+ .join(",")}}`;
25
+ }
26
+ return JSON.stringify(value);
27
+ }
@@ -26,13 +26,16 @@ export class WorkflowGovernedExportApproval implements GovernedExportApprovalPor
26
26
  async create(request: GovernedExportRequest, actor: RequestActor) {
27
27
  const item = await this.workflow.create(
28
28
  {
29
- title: `敏感用户数据导出:${request.reason}`,
29
+ title: `敏感数据导出:${request.reason}`,
30
30
  candidateRoles: [...this.candidateRoles],
31
31
  dueAt: request.expiresAt,
32
32
  target: {
33
- type: "system.user.governed-export",
33
+ type: `${request.resourceType}.governed-export`,
34
34
  id: request.id,
35
- route: `/system/users/exports/${request.id}`,
35
+ route:
36
+ request.resourceType === "system.user"
37
+ ? `/system/users/exports/${request.id}`
38
+ : "/system/workflow",
36
39
  },
37
40
  idempotencyKey: `governed-export:${request.id}`,
38
41
  },