create-windy 0.5.2 → 0.6.1

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 (589) hide show
  1. package/dist/cli.js +370 -156
  2. package/package.json +2 -1
  3. package/template/.windy-template.json +2 -2
  4. package/template/apps/agent-server/README.md +25 -6
  5. package/template/apps/agent-server/src/southwind_agent_server/adk_adapter.py +15 -2
  6. package/template/apps/agent-server/src/southwind_agent_server/app.py +13 -34
  7. package/template/apps/agent-server/src/southwind_agent_server/config.py +49 -0
  8. package/template/apps/agent-server/src/southwind_agent_server/contracts.py +13 -0
  9. package/template/apps/agent-server/src/southwind_agent_server/engine_capacity.py +31 -0
  10. package/template/apps/agent-server/src/southwind_agent_server/fingerprint.py +5 -0
  11. package/template/apps/agent-server/src/southwind_agent_server/health_routes.py +50 -0
  12. package/template/apps/agent-server/src/southwind_agent_server/openai_engine.py +11 -2
  13. package/template/apps/agent-server/src/southwind_agent_server/openai_provider.py +3 -9
  14. package/template/apps/agent-server/src/southwind_agent_server/postgres_store.py +5 -0
  15. package/template/apps/agent-server/src/southwind_agent_server/readiness_pin.py +37 -0
  16. package/template/apps/agent-server/src/southwind_agent_server/run_admission.py +64 -0
  17. package/template/apps/agent-server/src/southwind_agent_server/run_capacity.py +59 -0
  18. package/template/apps/agent-server/src/southwind_agent_server/service.py +42 -43
  19. package/template/apps/agent-server/src/southwind_agent_server/service_errors.py +4 -0
  20. package/template/apps/agent-server/src/southwind_agent_server/service_execution.py +31 -0
  21. package/template/apps/agent-server/src/southwind_agent_server/service_tasks.py +34 -0
  22. package/template/apps/agent-server/src/southwind_agent_server/sqlite_catalog.py +11 -0
  23. package/template/apps/agent-server/src/southwind_agent_server/store.py +6 -0
  24. package/template/apps/agent-server/src/southwind_agent_server/vision_probe.py +87 -0
  25. package/template/apps/agent-server/src/southwind_agent_server/vision_requirement.py +28 -0
  26. package/template/apps/agent-server/src/southwind_agent_server/vlm_readiness.py +247 -0
  27. package/template/apps/agent-server/tests/test_api.py +16 -3
  28. package/template/apps/agent-server/tests/test_openai_provider.py +21 -4
  29. package/template/apps/agent-server/tests/test_postgres_store.py +3 -0
  30. package/template/apps/agent-server/tests/test_run_capacity.py +110 -0
  31. package/template/apps/agent-server/tests/test_runtime_factory_readiness.py +37 -0
  32. package/template/apps/agent-server/tests/test_sqlite_store.py +3 -0
  33. package/template/apps/agent-server/tests/test_vision_readiness_enforcement.py +72 -0
  34. package/template/apps/agent-server/tests/test_vlm_readiness.py +106 -0
  35. package/template/apps/document-server/src/southwind_document_server/adapter.py +117 -205
  36. package/template/apps/document-server/src/southwind_document_server/adapter_errors.py +13 -0
  37. package/template/apps/document-server/src/southwind_document_server/app.py +62 -14
  38. package/template/apps/document-server/src/southwind_document_server/artifacts.py +27 -0
  39. package/template/apps/document-server/src/southwind_document_server/config.py +96 -2
  40. package/template/apps/document-server/src/southwind_document_server/contracts.py +168 -13
  41. package/template/apps/document-server/src/southwind_document_server/fingerprint.py +128 -0
  42. package/template/apps/document-server/src/southwind_document_server/grants.py +81 -0
  43. package/template/apps/document-server/src/southwind_document_server/multipart_output.py +265 -0
  44. package/template/apps/document-server/src/southwind_document_server/output_validation.py +202 -0
  45. package/template/apps/document-server/src/southwind_document_server/progress.py +27 -0
  46. package/template/apps/document-server/src/southwind_document_server/readiness.py +280 -0
  47. package/template/apps/document-server/src/southwind_document_server/readiness_contracts.py +280 -0
  48. package/template/apps/document-server/src/southwind_document_server/readiness_http.py +87 -0
  49. package/template/apps/document-server/src/southwind_document_server/readiness_support.py +120 -0
  50. package/template/apps/document-server/src/southwind_document_server/request_validation.py +48 -0
  51. package/template/apps/document-server/src/southwind_document_server/service.py +297 -96
  52. package/template/apps/document-server/src/southwind_document_server/service_recovery.py +183 -0
  53. package/template/apps/document-server/src/southwind_document_server/sink.py +316 -0
  54. package/template/apps/document-server/src/southwind_document_server/store.py +301 -141
  55. package/template/apps/document-server/src/southwind_document_server/store_events.py +40 -0
  56. package/template/apps/document-server/src/southwind_document_server/store_models.py +62 -0
  57. package/template/apps/document-server/src/southwind_document_server/store_recovery.py +247 -0
  58. package/template/apps/document-server/src/southwind_document_server/store_schema.py +50 -0
  59. package/template/apps/document-server/tests/recovery_support.py +230 -0
  60. package/template/apps/document-server/tests/support.py +210 -0
  61. package/template/apps/document-server/tests/test_adapter.py +251 -0
  62. package/template/apps/document-server/tests/test_config.py +43 -0
  63. package/template/apps/document-server/tests/test_contracts.py +80 -0
  64. package/template/apps/document-server/tests/test_fingerprint.py +125 -0
  65. package/template/apps/document-server/tests/test_grants.py +46 -0
  66. package/template/apps/document-server/tests/test_handshake.py +154 -0
  67. package/template/apps/document-server/tests/test_integration.py +192 -0
  68. package/template/apps/document-server/tests/test_multipart_security.py +259 -0
  69. package/template/apps/document-server/tests/test_readiness.py +311 -0
  70. package/template/apps/document-server/tests/test_readiness_contracts.py +131 -0
  71. package/template/apps/document-server/tests/test_recovery.py +222 -0
  72. package/template/apps/document-server/tests/test_recovery_faults.py +101 -0
  73. package/template/apps/document-server/tests/test_recovery_races.py +283 -0
  74. package/template/apps/document-server/tests/test_service.py +16 -13
  75. package/template/apps/document-server/tests/test_service_artifacts.py +296 -0
  76. package/template/apps/document-server/tests/test_sink.py +253 -0
  77. package/template/apps/document-server/tests/test_store.py +199 -0
  78. package/template/apps/embedding-server/pyproject.toml +5 -0
  79. package/template/apps/embedding-server/src/southwind_embedding_server/adapter.py +38 -2
  80. package/template/apps/embedding-server/src/southwind_embedding_server/app.py +135 -20
  81. package/template/apps/embedding-server/src/southwind_embedding_server/config.py +138 -4
  82. package/template/apps/embedding-server/src/southwind_embedding_server/contracts.py +13 -0
  83. package/template/apps/embedding-server/src/southwind_embedding_server/image_input.py +16 -5
  84. package/template/apps/embedding-server/src/southwind_embedding_server/pin_cli.py +62 -0
  85. package/template/apps/embedding-server/src/southwind_embedding_server/readiness.py +259 -0
  86. package/template/apps/embedding-server/src/southwind_embedding_server/readiness_contracts.py +221 -0
  87. package/template/apps/embedding-server/src/southwind_embedding_server/readiness_http.py +91 -0
  88. package/template/apps/embedding-server/src/southwind_embedding_server/run_cipher.py +31 -0
  89. package/template/apps/embedding-server/src/southwind_embedding_server/run_service.py +150 -0
  90. package/template/apps/embedding-server/src/southwind_embedding_server/run_store.py +250 -0
  91. package/template/apps/embedding-server/tests/test_adapter.py +25 -0
  92. package/template/apps/embedding-server/tests/test_config.py +13 -0
  93. package/template/apps/embedding-server/tests/test_readiness.py +202 -0
  94. package/template/apps/embedding-server/tests/test_run_http.py +257 -0
  95. package/template/apps/embedding-server/tests/test_run_service.py +202 -0
  96. package/template/apps/embedding-server/uv.lock +71 -0
  97. package/template/apps/server/Dockerfile +13 -0
  98. package/template/apps/server/package.json +1 -0
  99. package/template/apps/server/src/agent/drizzle-screenshot-intake-repository.integration.test.ts +286 -0
  100. package/template/apps/server/src/agent/drizzle-screenshot-intake-repository.ts +175 -0
  101. package/template/apps/server/src/agent/persistent-screenshot-intake-producer.ts +320 -0
  102. package/template/apps/server/src/agent/remote-runtime.ts +8 -1
  103. package/template/apps/server/src/agent/required-environment.ts +5 -0
  104. package/template/apps/server/src/agent/run-attach.ts +41 -0
  105. package/template/apps/server/src/agent/run-audit.ts +49 -0
  106. package/template/apps/server/src/agent/run-contracts.ts +6 -1
  107. package/template/apps/server/src/agent/run-facade.ts +67 -68
  108. package/template/apps/server/src/agent/run-route-input.ts +26 -1
  109. package/template/apps/server/src/agent/run-route-screenshot-context.test.ts +13 -0
  110. package/template/apps/server/src/agent/run-routes.ts +1 -0
  111. package/template/apps/server/src/agent/runtime-bootstrap-report.test.ts +1 -1
  112. package/template/apps/server/src/agent/runtime-bootstrap.ts +112 -60
  113. package/template/apps/server/src/agent/runtime-readiness-sources.test.ts +27 -0
  114. package/template/apps/server/src/agent/runtime-readiness-sources.ts +31 -0
  115. package/template/apps/server/src/agent/runtime-reporting-bootstrap.ts +40 -0
  116. package/template/apps/server/src/agent/screenshot-context-admission.test.ts +249 -0
  117. package/template/apps/server/src/agent/screenshot-context-attestation.ts +60 -0
  118. package/template/apps/server/src/agent/screenshot-intake-authorization.ts +29 -0
  119. package/template/apps/server/src/agent/screenshot-intake-bootstrap.ts +59 -0
  120. package/template/apps/server/src/agent/screenshot-intake-durable.ts +53 -0
  121. package/template/apps/server/src/agent/screenshot-intake-errors.ts +9 -0
  122. package/template/apps/server/src/agent/screenshot-intake-producer.ts +41 -0
  123. package/template/apps/server/src/agent/screenshot-intake-record.ts +44 -0
  124. package/template/apps/server/src/agent/screenshot-intake-recovery.ts +106 -0
  125. package/template/apps/server/src/agent/screenshot-intake-repository.ts +64 -0
  126. package/template/apps/server/src/agent/screenshot-intake-reservation.ts +24 -0
  127. package/template/apps/server/src/agent/screenshot-intake-routes.ts +171 -0
  128. package/template/apps/server/src/agent/screenshot-intake-runtime-types.ts +6 -0
  129. package/template/apps/server/src/agent/screenshot-intake-sweep.ts +26 -0
  130. package/template/apps/server/src/agent/screenshot-intake-test-support.ts +91 -0
  131. package/template/apps/server/src/agent/screenshot-intake-v2-validation.ts +176 -0
  132. package/template/apps/server/src/agent/screenshot-intake-v2.test.ts +345 -0
  133. package/template/apps/server/src/agent/screenshot-intake-v2.ts +348 -0
  134. package/template/apps/server/src/agent/screenshot-png.ts +15 -0
  135. package/template/apps/server/src/agent/screenshot-run-binding.ts +49 -0
  136. package/template/apps/server/src/agent/unavailable-routes.ts +9 -0
  137. package/template/apps/server/src/agent/vision-facade.test.ts +98 -8
  138. package/template/apps/server/src/agent/vision-input.ts +5 -2
  139. package/template/apps/server/src/agent/vision-readiness-pin.ts +32 -0
  140. package/template/apps/server/src/agent/vision-readiness.ts +58 -0
  141. package/template/apps/server/src/ai-artifact/runtime-reference.test.ts +135 -0
  142. package/template/apps/server/src/ai-artifact/runtime.ts +134 -5
  143. package/template/apps/server/src/ai-output-artifact/cleanup.ts +214 -0
  144. package/template/apps/server/src/ai-output-artifact/contracts.ts +152 -0
  145. package/template/apps/server/src/ai-output-artifact/drizzle-cleanup.ts +37 -0
  146. package/template/apps/server/src/ai-output-artifact/drizzle-complete.ts +71 -0
  147. package/template/apps/server/src/ai-output-artifact/drizzle-grant-cleanup.ts +45 -0
  148. package/template/apps/server/src/ai-output-artifact/drizzle-grants.ts +109 -0
  149. package/template/apps/server/src/ai-output-artifact/drizzle-mapping.ts +77 -0
  150. package/template/apps/server/src/ai-output-artifact/drizzle-readiness.ts +46 -0
  151. package/template/apps/server/src/ai-output-artifact/drizzle-repository.ts +350 -0
  152. package/template/apps/server/src/ai-output-artifact/drizzle-seal.ts +64 -0
  153. package/template/apps/server/src/ai-output-artifact/grant.ts +30 -0
  154. package/template/apps/server/src/ai-output-artifact/host-registry.ts +53 -0
  155. package/template/apps/server/src/ai-output-artifact/memory-cleanup.ts +47 -0
  156. package/template/apps/server/src/ai-output-artifact/memory-grants.ts +98 -0
  157. package/template/apps/server/src/ai-output-artifact/memory-readiness.ts +26 -0
  158. package/template/apps/server/src/ai-output-artifact/memory-repository.ts +348 -0
  159. package/template/apps/server/src/ai-output-artifact/policy.test.ts +67 -0
  160. package/template/apps/server/src/ai-output-artifact/policy.ts +125 -0
  161. package/template/apps/server/src/ai-output-artifact/postgres-test-support.ts +147 -0
  162. package/template/apps/server/src/ai-output-artifact/postgres.integration.test.ts +349 -0
  163. package/template/apps/server/src/ai-output-artifact/reference-codec.ts +128 -0
  164. package/template/apps/server/src/ai-output-artifact/repository-grant-invariants.test-support.ts +165 -0
  165. package/template/apps/server/src/ai-output-artifact/repository-invariants.test-support.ts +239 -0
  166. package/template/apps/server/src/ai-output-artifact/repository-invariants.test.ts +18 -0
  167. package/template/apps/server/src/ai-output-artifact/repository.ts +107 -0
  168. package/template/apps/server/src/ai-output-artifact/route-input.ts +173 -0
  169. package/template/apps/server/src/ai-output-artifact/routes-delete.test.ts +287 -0
  170. package/template/apps/server/src/ai-output-artifact/routes-grant.test.ts +116 -0
  171. package/template/apps/server/src/ai-output-artifact/routes-nonblocking-body.test.ts +136 -0
  172. package/template/apps/server/src/ai-output-artifact/routes-security.test.ts +176 -0
  173. package/template/apps/server/src/ai-output-artifact/routes-storage-error.test.ts +105 -0
  174. package/template/apps/server/src/ai-output-artifact/routes.test-support.ts +84 -0
  175. package/template/apps/server/src/ai-output-artifact/routes.test.ts +198 -0
  176. package/template/apps/server/src/ai-output-artifact/routes.ts +280 -0
  177. package/template/apps/server/src/ai-output-artifact/service-completion.ts +60 -0
  178. package/template/apps/server/src/ai-output-artifact/service-grants.test.ts +169 -0
  179. package/template/apps/server/src/ai-output-artifact/service-grants.ts +217 -0
  180. package/template/apps/server/src/ai-output-artifact/service-input.ts +175 -0
  181. package/template/apps/server/src/ai-output-artifact/service-read.ts +89 -0
  182. package/template/apps/server/src/ai-output-artifact/service-recovery.test.ts +238 -0
  183. package/template/apps/server/src/ai-output-artifact/service-support.ts +240 -0
  184. package/template/apps/server/src/ai-output-artifact/service.test.ts +283 -0
  185. package/template/apps/server/src/ai-output-artifact/service.ts +334 -0
  186. package/template/apps/server/src/ai-output-artifact/storage-recovery.ts +33 -0
  187. package/template/apps/server/src/ai-output-artifact/storage-writer.ts +103 -0
  188. package/template/apps/server/src/ai-output-artifact/test-support.ts +161 -0
  189. package/template/apps/server/src/ai-runtime-readiness/deployment-pin.ts +69 -0
  190. package/template/apps/server/src/ai-runtime-readiness/health-response.ts +92 -0
  191. package/template/apps/server/src/ai-runtime-readiness/operation-port-trust.test.ts +66 -0
  192. package/template/apps/server/src/ai-runtime-readiness/operation-port.test-support.ts +143 -0
  193. package/template/apps/server/src/ai-runtime-readiness/operation-port.test.ts +193 -0
  194. package/template/apps/server/src/ai-runtime-readiness/operation-port.ts +172 -0
  195. package/template/apps/server/src/ai-runtime-readiness/trust-store.ts +61 -0
  196. package/template/apps/server/src/app/ai-screenshot-intake-binding.ts +22 -0
  197. package/template/apps/server/src/app/document-output-artifact-bindings.ts +5 -0
  198. package/template/apps/server/src/app/speech-output-artifact-bindings.ts +5 -0
  199. package/template/apps/server/src/application-services.ts +9 -3
  200. package/template/apps/server/src/audit/redaction.ts +1 -0
  201. package/template/apps/server/src/audit/reliable-writer.test.ts +8 -0
  202. package/template/apps/server/src/auth/password-change-license-restriction.test.ts +156 -0
  203. package/template/apps/server/src/document/cancel-response.ts +33 -0
  204. package/template/apps/server/src/document/contracts.ts +6 -0
  205. package/template/apps/server/src/document/errors.ts +11 -0
  206. package/template/apps/server/src/document/facade-cancel.test.ts +226 -0
  207. package/template/apps/server/src/document/facade-cancel.ts +110 -0
  208. package/template/apps/server/src/document/facade-grant-handoff.test.ts +113 -0
  209. package/template/apps/server/src/document/facade-idempotency.test.ts +195 -0
  210. package/template/apps/server/src/document/facade-invocation.ts +46 -0
  211. package/template/apps/server/src/document/facade-output-artifact.test.ts +310 -0
  212. package/template/apps/server/src/document/facade-read-response.ts +116 -0
  213. package/template/apps/server/src/document/facade-readiness.test.ts +95 -0
  214. package/template/apps/server/src/document/facade-support.ts +297 -0
  215. package/template/apps/server/src/document/facade.ts +164 -173
  216. package/template/apps/server/src/document/output-artifact-composition.test.ts +64 -0
  217. package/template/apps/server/src/document/output-artifact-composition.ts +78 -0
  218. package/template/apps/server/src/document/output-artifact-fingerprint.contract.test.ts +110 -0
  219. package/template/apps/server/src/document/output-artifact-fingerprint.ts +150 -0
  220. package/template/apps/server/src/document/output-artifact-host-registry.ts +44 -0
  221. package/template/apps/server/src/document/output-artifact-host.test.ts +332 -0
  222. package/template/apps/server/src/document/output-artifact-host.ts +331 -0
  223. package/template/apps/server/src/document/output-artifact-manifest.test-support.ts +71 -0
  224. package/template/apps/server/src/document/output-artifact-publish.test-support.ts +48 -0
  225. package/template/apps/server/src/document/output-artifact-readiness-probe.ts +347 -0
  226. package/template/apps/server/src/document/output-artifact-readiness-timeout.ts +30 -0
  227. package/template/apps/server/src/document/output-artifact-readiness.test.ts +264 -0
  228. package/template/apps/server/src/document/output-artifact-readiness.ts +87 -0
  229. package/template/apps/server/src/document/output-artifact-route-composition.test.ts +27 -0
  230. package/template/apps/server/src/document/output-artifact-route-composition.ts +14 -0
  231. package/template/apps/server/src/document/output-artifact-runtime.test-support.ts +8 -0
  232. package/template/apps/server/src/document/output-artifact-runtime.test.ts +124 -0
  233. package/template/apps/server/src/document/output-artifact-runtime.ts +275 -0
  234. package/template/apps/server/src/document/output-artifact.test-support.ts +350 -0
  235. package/template/apps/server/src/document/remote-response.ts +162 -0
  236. package/template/apps/server/src/document/remote-runtime.test.ts +100 -4
  237. package/template/apps/server/src/document/remote-runtime.ts +37 -85
  238. package/template/apps/server/src/document/routes-read-response.test.ts +267 -0
  239. package/template/apps/server/src/document/routes.test.ts +109 -0
  240. package/template/apps/server/src/document/routes.ts +23 -14
  241. package/template/apps/server/src/embedding/audit.ts +46 -0
  242. package/template/apps/server/src/embedding/contracts.ts +19 -1
  243. package/template/apps/server/src/embedding/drizzle-run-binding-repository.integration.test.ts +100 -0
  244. package/template/apps/server/src/embedding/drizzle-run-binding-repository.ts +102 -0
  245. package/template/apps/server/src/embedding/facade-readiness.test.ts +307 -0
  246. package/template/apps/server/src/embedding/facade-types.ts +18 -0
  247. package/template/apps/server/src/embedding/facade.ts +248 -115
  248. package/template/apps/server/src/embedding/operation-authorization.ts +23 -0
  249. package/template/apps/server/src/embedding/readiness-admission.ts +26 -0
  250. package/template/apps/server/src/embedding/remote-runtime.test.ts +103 -0
  251. package/template/apps/server/src/embedding/remote-runtime.ts +98 -0
  252. package/template/apps/server/src/embedding/routes.test.ts +118 -0
  253. package/template/apps/server/src/embedding/routes.ts +98 -0
  254. package/template/apps/server/src/embedding/run-artifact.ts +18 -0
  255. package/template/apps/server/src/embedding/run-authorization.test.ts +133 -0
  256. package/template/apps/server/src/embedding/run-authorization.ts +91 -0
  257. package/template/apps/server/src/embedding/run-binding-repository.ts +75 -0
  258. package/template/apps/server/src/embedding/run-fingerprint.test.ts +78 -0
  259. package/template/apps/server/src/embedding/run-fingerprint.ts +48 -0
  260. package/template/apps/server/src/embedding/terminal-observer.test.ts +26 -0
  261. package/template/apps/server/src/embedding/terminal-observer.ts +44 -0
  262. package/template/apps/server/src/index.ts +38 -31
  263. package/template/apps/server/src/module-host/ai-operation-port.test.ts +72 -0
  264. package/template/apps/server/src/module-host/ai-operation-port.ts +46 -3
  265. package/template/apps/server/src/module-host/ai-operation-speech-normalize.test.ts +186 -0
  266. package/template/apps/server/src/module-host/durable-context.ts +194 -16
  267. package/template/apps/server/src/module-host/durable-handler-registration.ts +1 -0
  268. package/template/apps/server/src/module-host/durable-storage-filesystem-restart.test.ts +265 -0
  269. package/template/apps/server/src/module-host/durable-storage-write.test.ts +345 -0
  270. package/template/apps/server/src/module-host/http-runtime.ts +25 -0
  271. package/template/apps/server/src/module-host/initialize-contracts.ts +18 -1
  272. package/template/apps/server/src/module-host/initialize.test.ts +18 -0
  273. package/template/apps/server/src/module-host/initialize.ts +42 -2
  274. package/template/apps/server/src/module-host/module-secret-audit-version.test.ts +73 -0
  275. package/template/apps/server/src/module-host/module-secret-binding-resolution.ts +60 -0
  276. package/template/apps/server/src/module-host/module-secret-durable.test.ts +206 -0
  277. package/template/apps/server/src/module-host/module-secret-port.test-support.ts +125 -0
  278. package/template/apps/server/src/module-host/module-secret-port.test.ts +161 -0
  279. package/template/apps/server/src/module-host/module-secret-port.ts +249 -0
  280. package/template/apps/server/src/module-host/module-secret-result.test.ts +324 -0
  281. package/template/apps/server/src/module-host/module-secret-result.ts +274 -0
  282. package/template/apps/server/src/module-host/module-secret-tool-boundary.test.ts +143 -0
  283. package/template/apps/server/src/module-host/project-composition.ts +1 -0
  284. package/template/apps/server/src/module-host/report-staging-port.ts +55 -0
  285. package/template/apps/server/src/module-host/report-staging-registration.test.ts +104 -0
  286. package/template/apps/server/src/module-host/request-context.ts +58 -1
  287. package/template/apps/server/src/module-host/route-declaration.ts +11 -4
  288. package/template/apps/server/src/module-host/route-installer.test-fixture.ts +246 -0
  289. package/template/apps/server/src/module-host/route-installer.test.ts +47 -211
  290. package/template/apps/server/src/module-host/route-installer.ts +192 -21
  291. package/template/apps/server/src/module-host/route-registration.test.ts +44 -4
  292. package/template/apps/server/src/module-host/runtime-composition.ts +2 -0
  293. package/template/apps/server/src/module-host/scoped-host.ts +84 -0
  294. package/template/apps/server/src/module-host/screenshot-artifact-ref.ts +91 -0
  295. package/template/apps/server/src/module-host/screenshot-intake-composition.test.ts +292 -0
  296. package/template/apps/server/src/module-host/screenshot-intake-deadline.ts +38 -0
  297. package/template/apps/server/src/module-host/screenshot-intake-plan.ts +75 -0
  298. package/template/apps/server/src/module-host/screenshot-intake-port.ts +297 -0
  299. package/template/apps/server/src/module-host/screenshot-intake-security.test.ts +311 -0
  300. package/template/apps/server/src/module-host/screenshot-intake-store.ts +76 -0
  301. package/template/apps/server/src/module-host/screenshot-intake-test-support.ts +68 -0
  302. package/template/apps/server/src/module-host/secret-bindings.test.ts +167 -0
  303. package/template/apps/server/src/module-host/secret-bindings.ts +184 -0
  304. package/template/apps/server/src/module-host/storage-authorization-binding.test.ts +242 -0
  305. package/template/apps/server/src/module-host/storage-port.test.ts +8 -1
  306. package/template/apps/server/src/module-host/storage-port.ts +84 -8
  307. package/template/apps/server/src/module-host/storage-restart-fixture.test-support.ts +242 -0
  308. package/template/apps/server/src/module-host/storage-restart-postgres.integration.test.ts +280 -0
  309. package/template/apps/server/src/module-host/storage-scope-drift-http.test.ts +68 -0
  310. package/template/apps/server/src/module-host/storage-scope.ts +52 -1
  311. package/template/apps/server/src/module-host/storage-stream-route.test.ts +282 -0
  312. package/template/apps/server/src/module-host/storage-write-error.test.ts +174 -0
  313. package/template/apps/server/src/module-host/storage-write-error.ts +62 -0
  314. package/template/apps/server/src/module-host/storage-write-port.test.ts +323 -0
  315. package/template/apps/server/src/module-host/storage-write-port.ts +320 -0
  316. package/template/apps/server/src/module-host/storage-write-unavailable.ts +21 -0
  317. package/template/apps/server/src/module-host/storage-writer-retention.test.ts +109 -0
  318. package/template/apps/server/src/module-host/user-directory-composition.test.ts +1 -0
  319. package/template/apps/server/src/module-preflight-secret.test.ts +79 -0
  320. package/template/apps/server/src/module-preflight.ts +5 -0
  321. package/template/apps/server/src/platform-http-runtime.ts +16 -4
  322. package/template/apps/server/src/report-staging/archive-frame-stream.test.ts +209 -0
  323. package/template/apps/server/src/report-staging/archive-frame-stream.ts +341 -0
  324. package/template/apps/server/src/report-staging/archive-process.test.ts +302 -0
  325. package/template/apps/server/src/report-staging/archive-process.ts +257 -0
  326. package/template/apps/server/src/report-staging/archive-volumes.ts +30 -0
  327. package/template/apps/server/src/report-staging/cancellation.ts +13 -0
  328. package/template/apps/server/src/report-staging/contracts.ts +94 -0
  329. package/template/apps/server/src/report-staging/drizzle-lifecycle.ts +278 -0
  330. package/template/apps/server/src/report-staging/drizzle-mapping.ts +140 -0
  331. package/template/apps/server/src/report-staging/drizzle-repository.integration.test.ts +346 -0
  332. package/template/apps/server/src/report-staging/drizzle-repository.ts +255 -0
  333. package/template/apps/server/src/report-staging/drizzle-seal.integration.test.ts +267 -0
  334. package/template/apps/server/src/report-staging/drizzle-seal.ts +177 -0
  335. package/template/apps/server/src/report-staging/filesystem-cleanup.test.ts +68 -0
  336. package/template/apps/server/src/report-staging/filesystem-cleanup.ts +102 -0
  337. package/template/apps/server/src/report-staging/filesystem-errors.ts +15 -0
  338. package/template/apps/server/src/report-staging/filesystem-identifiers.ts +19 -0
  339. package/template/apps/server/src/report-staging/filesystem-io.test.ts +85 -0
  340. package/template/apps/server/src/report-staging/filesystem-io.ts +206 -0
  341. package/template/apps/server/src/report-staging/filesystem-scan.ts +85 -0
  342. package/template/apps/server/src/report-staging/filesystem-store.ts +346 -0
  343. package/template/apps/server/src/report-staging/heartbeat.test.ts +47 -0
  344. package/template/apps/server/src/report-staging/heartbeat.ts +53 -0
  345. package/template/apps/server/src/report-staging/manifest-stream.ts +46 -0
  346. package/template/apps/server/src/report-staging/memory-repository.ts +256 -0
  347. package/template/apps/server/src/report-staging/metadata-catalog.ts +89 -0
  348. package/template/apps/server/src/report-staging/metadata-spool-paths.ts +195 -0
  349. package/template/apps/server/src/report-staging/metadata-spool-schema.ts +54 -0
  350. package/template/apps/server/src/report-staging/metadata-spool.test.ts +214 -0
  351. package/template/apps/server/src/report-staging/metadata-spool.ts +305 -0
  352. package/template/apps/server/src/report-staging/native/archive-frame-helper.c +309 -0
  353. package/template/apps/server/src/report-staging/path-policy.test.ts +127 -0
  354. package/template/apps/server/src/report-staging/path-policy.ts +263 -0
  355. package/template/apps/server/src/report-staging/repository-fence.test.ts +114 -0
  356. package/template/apps/server/src/report-staging/repository.ts +75 -0
  357. package/template/apps/server/src/report-staging/runtime.ts +31 -0
  358. package/template/apps/server/src/report-staging/sealed-file.test.ts +34 -0
  359. package/template/apps/server/src/report-staging/sealed-file.ts +137 -0
  360. package/template/apps/server/src/report-staging/service-input-limits.test.ts +62 -0
  361. package/template/apps/server/src/report-staging/service-input.ts +204 -0
  362. package/template/apps/server/src/report-staging/service-lifecycle.test.ts +132 -0
  363. package/template/apps/server/src/report-staging/service-read-fence.test.ts +37 -0
  364. package/template/apps/server/src/report-staging/service-read.ts +172 -0
  365. package/template/apps/server/src/report-staging/service-support.ts +149 -0
  366. package/template/apps/server/src/report-staging/service.test-fixture.ts +226 -0
  367. package/template/apps/server/src/report-staging/service.test.ts +314 -0
  368. package/template/apps/server/src/report-staging/service.ts +255 -0
  369. package/template/apps/server/src/report-staging/supervised-process.ts +194 -0
  370. package/template/apps/server/src/speech/contracts.ts +8 -0
  371. package/template/apps/server/src/speech/errors.ts +10 -0
  372. package/template/apps/server/src/speech/facade-contract.ts +49 -0
  373. package/template/apps/server/src/speech/facade-output.test.ts +299 -0
  374. package/template/apps/server/src/speech/facade-output.ts +298 -0
  375. package/template/apps/server/src/speech/facade-validation.ts +127 -0
  376. package/template/apps/server/src/speech/facade.ts +185 -151
  377. package/template/apps/server/src/speech/fingerprint.test.ts +42 -0
  378. package/template/apps/server/src/speech/fingerprint.ts +28 -0
  379. package/template/apps/server/src/speech/output-artifact-binding.test.ts +11 -0
  380. package/template/apps/server/src/speech/output-artifact-binding.ts +30 -0
  381. package/template/apps/server/src/speech/output-artifact-error.ts +9 -0
  382. package/template/apps/server/src/speech/output-artifact-host.test-support.ts +73 -0
  383. package/template/apps/server/src/speech/output-artifact-host.test.ts +302 -0
  384. package/template/apps/server/src/speech/output-artifact-host.ts +279 -0
  385. package/template/apps/server/src/speech/output-artifact-policy.ts +114 -0
  386. package/template/apps/server/src/speech/output-artifact-replay.ts +91 -0
  387. package/template/apps/server/src/speech/remote-response.ts +158 -0
  388. package/template/apps/server/src/speech/remote-runtime.test.ts +90 -0
  389. package/template/apps/server/src/speech/remote-runtime.ts +36 -78
  390. package/template/apps/server/src/speech/routes.ts +60 -16
  391. package/template/apps/server/src/startup-finalization.ts +52 -0
  392. package/template/apps/server/src/startup-preflight.ts +2 -0
  393. package/template/apps/server/src/startup-shutdown.test.ts +69 -0
  394. package/template/apps/server/src/startup-shutdown.ts +61 -0
  395. package/template/apps/server/src/storage/drizzle-upload-purge.ts +61 -0
  396. package/template/apps/server/src/storage/drizzle-upload-references.ts +23 -0
  397. package/template/apps/server/src/storage/drizzle-upload-repository.integration.test.ts +103 -0
  398. package/template/apps/server/src/storage/drizzle-upload-repository.ts +109 -14
  399. package/template/apps/server/src/storage/host-runtime.ts +15 -0
  400. package/template/apps/server/src/storage/routes-stream-cancellation.test.ts +154 -0
  401. package/template/apps/server/src/storage/routes.test.ts +13 -2
  402. package/template/apps/server/src/storage/routes.ts +57 -18
  403. package/template/apps/server/src/storage/runtime.ts +17 -4
  404. package/template/apps/server/src/system/route-types.ts +5 -1
  405. package/template/apps/server/src/system/system-resource-monitoring.test.ts +47 -0
  406. package/template/apps/server/src/system/system-resource-monitoring.ts +60 -10
  407. package/template/apps/server/src/work-order/routes.test.ts +1 -0
  408. package/template/apps/speech-server/src/southwind_speech_server/adapter.py +58 -17
  409. package/template/apps/speech-server/src/southwind_speech_server/app.py +24 -17
  410. package/template/apps/speech-server/src/southwind_speech_server/bounded_json.py +60 -0
  411. package/template/apps/speech-server/src/southwind_speech_server/config.py +122 -2
  412. package/template/apps/speech-server/src/southwind_speech_server/contracts.py +58 -0
  413. package/template/apps/speech-server/src/southwind_speech_server/errors.py +8 -0
  414. package/template/apps/speech-server/src/southwind_speech_server/normalizer.py +14 -0
  415. package/template/apps/speech-server/src/southwind_speech_server/output_artifact.py +190 -0
  416. package/template/apps/speech-server/src/southwind_speech_server/output_validation.py +54 -0
  417. package/template/apps/speech-server/src/southwind_speech_server/pin_cli.py +83 -0
  418. package/template/apps/speech-server/src/southwind_speech_server/readiness.py +266 -0
  419. package/template/apps/speech-server/src/southwind_speech_server/readiness_contracts.py +268 -0
  420. package/template/apps/speech-server/src/southwind_speech_server/readiness_http.py +91 -0
  421. package/template/apps/speech-server/src/southwind_speech_server/readiness_local.py +161 -0
  422. package/template/apps/speech-server/src/southwind_speech_server/readiness_support.py +106 -0
  423. package/template/apps/speech-server/src/southwind_speech_server/service.py +118 -13
  424. package/template/apps/speech-server/src/southwind_speech_server/store.py +7 -0
  425. package/template/apps/speech-server/tests/test_bounded_json.py +55 -0
  426. package/template/apps/speech-server/tests/test_config.py +22 -0
  427. package/template/apps/speech-server/tests/test_output_artifact.py +60 -0
  428. package/template/apps/speech-server/tests/test_output_validation.py +44 -0
  429. package/template/apps/speech-server/tests/test_readiness.py +315 -0
  430. package/template/apps/speech-server/tests/test_readiness_http.py +48 -0
  431. package/template/apps/speech-server/tests/test_readiness_local.py +43 -0
  432. package/template/apps/speech-server/tests/test_service.py +211 -36
  433. package/template/apps/web/src/pages/system/components/OperationGpuMonitor.vue +2 -1
  434. package/template/apps/web/src/pages/system/components/OperationGpuMonitor.webtest.ts +29 -0
  435. package/template/apps/web/src/pages/system/components/OperationServiceMonitor.vue +2 -1
  436. package/template/apps/web/src/pages/system/components/OperationServiceMonitor.webtest.ts +4 -1
  437. package/template/docker-compose.yml +49 -1
  438. package/template/docs/architecture/ai-runtime-readiness.md +504 -0
  439. package/template/docs/architecture/ai-runtime.md +20 -4
  440. package/template/docs/architecture/durable-jobs.md +54 -5
  441. package/template/docs/architecture/module-host.md +72 -9
  442. package/template/docs/architecture/module-manifest-composition.md +5 -2
  443. package/template/docs/architecture/module-secret-resolution.md +250 -0
  444. package/template/docs/architecture/object-storage.md +144 -3
  445. package/template/docs/architecture/report-staging.md +158 -0
  446. package/template/docs/architecture/server-module-interface.md +29 -0
  447. package/template/docs/architecture/speech-variants.md +45 -0
  448. package/template/docs/platform/agent-tools.md +16 -0
  449. package/template/docs/platform/document-ai-runtime.md +199 -17
  450. package/template/docs/platform/module-configuration.md +5 -0
  451. package/template/docs/platform/speech-ai-runtime.md +34 -5
  452. package/template/docs/platform/vision-ai-runtime.md +46 -1
  453. package/template/package.json +4 -1
  454. package/template/packages/ai-client/package.json +1 -1
  455. package/template/packages/ai-client/src/agent/client/client.ts +3 -0
  456. package/template/packages/ai-client/src/agent/client/index.ts +1 -0
  457. package/template/packages/ai-client/src/agent/client/screenshot.test.ts +40 -0
  458. package/template/packages/ai-client/src/agent/client/screenshot.ts +146 -0
  459. package/template/packages/ai-client/src/agent/client/types.ts +2 -0
  460. package/template/packages/ai-client/src/agent/contracts/runs.ts +1 -0
  461. package/template/packages/ai-client/src/embedding/client.test.ts +52 -0
  462. package/template/packages/ai-client/src/embedding/client.ts +69 -0
  463. package/template/packages/ai-client/src/embedding/contracts.ts +47 -0
  464. package/template/packages/ai-client/src/embedding/index.ts +1 -0
  465. package/template/packages/ai-client/src/embedding/run.ts +130 -0
  466. package/template/packages/ai-client/src/speech/client.test.ts +26 -0
  467. package/template/packages/ai-client/src/speech/client.ts +25 -1
  468. package/template/packages/ai-client/src/speech/contracts.ts +19 -0
  469. package/template/packages/config/package.json +2 -2
  470. package/template/packages/database/drizzle/0048_illegal_magneto.sql +11 -0
  471. package/template/packages/database/drizzle/0049_right_lenny_balinger.sql +79 -0
  472. package/template/packages/database/drizzle/0050_greedy_sister_grimm.sql +92 -0
  473. package/template/packages/database/drizzle/0051_lame_shockwave.sql +19 -0
  474. package/template/packages/database/drizzle/0052_tiresome_komodo.sql +1 -0
  475. package/template/packages/database/drizzle/0053_calm_victor_mancha.sql +1 -0
  476. package/template/packages/database/drizzle/0054_tough_thanos.sql +29 -0
  477. package/template/packages/database/drizzle/meta/0048_snapshot.json +8852 -0
  478. package/template/packages/database/drizzle/meta/0049_snapshot.json +9410 -0
  479. package/template/packages/database/drizzle/meta/0050_snapshot.json +10115 -0
  480. package/template/packages/database/drizzle/meta/0051_snapshot.json +10257 -0
  481. package/template/packages/database/drizzle/meta/0052_snapshot.json +10263 -0
  482. package/template/packages/database/drizzle/meta/0053_snapshot.json +10270 -0
  483. package/template/packages/database/drizzle/meta/0054_snapshot.json +10468 -0
  484. package/template/packages/database/drizzle/meta/_journal.json +49 -0
  485. package/template/packages/database/package.json +2 -2
  486. package/template/packages/database/src/agent-runtime-schema.test.ts +15 -0
  487. package/template/packages/database/src/ai-output-artifacts.test.ts +57 -0
  488. package/template/packages/database/src/schema/agent-runtime.ts +43 -0
  489. package/template/packages/database/src/schema/ai-output-artifacts.ts +182 -0
  490. package/template/packages/database/src/schema/embedding-runtime.ts +62 -0
  491. package/template/packages/database/src/schema/file-storage.ts +18 -0
  492. package/template/packages/database/src/schema/index.ts +3 -0
  493. package/template/packages/database/src/schema/report-staging.ts +212 -0
  494. package/template/packages/database/src/schema.test.ts +19 -0
  495. package/template/packages/jobs/package.json +2 -2
  496. package/template/packages/modules/index.ts +2 -0
  497. package/template/packages/modules/package.json +1 -1
  498. package/template/packages/modules/src/ai-operation-composition.test.ts +37 -0
  499. package/template/packages/modules/src/ai-operation-composition.ts +4 -0
  500. package/template/packages/modules/src/ai-operation-readiness-contract.test.ts +38 -0
  501. package/template/packages/modules/src/ai-operation-readiness-contract.ts +34 -0
  502. package/template/packages/modules/src/ai-screenshot-context.test.ts +72 -0
  503. package/template/packages/modules/src/ai-screenshot-context.ts +50 -0
  504. package/template/packages/modules/src/capability-audit-validation.ts +20 -0
  505. package/template/packages/modules/src/capability-guard-validation.ts +41 -0
  506. package/template/packages/modules/src/composition.ts +11 -0
  507. package/template/packages/modules/src/manifest.ts +48 -0
  508. package/template/packages/modules/src/secret-composition.test.ts +308 -0
  509. package/template/packages/modules/src/secret-composition.ts +176 -0
  510. package/template/packages/modules/src/system-api-control-plane.ts +17 -1
  511. package/template/packages/modules/src/system-module-catalog.test.ts +52 -0
  512. package/template/packages/modules/src/system-module-catalog.ts +1 -1
  513. package/template/packages/server-sdk/contracts/ai-runtime-readiness/v1/document-python-parity.json +12 -0
  514. package/template/packages/server-sdk/contracts/ai-runtime-readiness/v1/document.json +70 -0
  515. package/template/packages/server-sdk/contracts/ai-runtime-readiness/v1/embedding.json +68 -0
  516. package/template/packages/server-sdk/contracts/ai-runtime-readiness/v1/speech.json +80 -0
  517. package/template/packages/server-sdk/contracts/ai-runtime-readiness/v1/vlm.json +70 -0
  518. package/template/packages/server-sdk/contracts/document-runtime/v1/fingerprint-fixture.json +101 -0
  519. package/template/packages/server-sdk/index.ts +109 -0
  520. package/template/packages/server-sdk/package.json +23 -4
  521. package/template/packages/server-sdk/src/ai-runtime-readiness-capture.ts +168 -0
  522. package/template/packages/server-sdk/src/ai-runtime-readiness-digest.ts +49 -0
  523. package/template/packages/server-sdk/src/ai-runtime-readiness-identity.ts +16 -0
  524. package/template/packages/server-sdk/src/ai-runtime-readiness-keys.ts +23 -0
  525. package/template/packages/server-sdk/src/ai-runtime-readiness-limits.ts +141 -0
  526. package/template/packages/server-sdk/src/ai-runtime-readiness-projection.ts +326 -0
  527. package/template/packages/server-sdk/src/ai-runtime-readiness-requirement.ts +31 -0
  528. package/template/packages/server-sdk/src/ai-runtime-readiness-time.ts +16 -0
  529. package/template/packages/server-sdk/src/ai-runtime-readiness-trust.ts +84 -0
  530. package/template/packages/server-sdk/src/ai-runtime-readiness-validation-values.ts +147 -0
  531. package/template/packages/server-sdk/src/ai-runtime-readiness-validation.ts +299 -0
  532. package/template/packages/server-sdk/src/ai-runtime-readiness-values.ts +42 -0
  533. package/template/packages/server-sdk/src/ai-runtime-readiness.adversarial-input.contract.test.ts +146 -0
  534. package/template/packages/server-sdk/src/ai-runtime-readiness.contract-support.ts +139 -0
  535. package/template/packages/server-sdk/src/ai-runtime-readiness.contract.test.ts +314 -0
  536. package/template/packages/server-sdk/src/ai-runtime-readiness.digest-safety.contract.test.ts +131 -0
  537. package/template/packages/server-sdk/src/ai-runtime-readiness.fail-closed.contract.test.ts +215 -0
  538. package/template/packages/server-sdk/src/ai-runtime-readiness.identity.contract.test.ts +138 -0
  539. package/template/packages/server-sdk/src/ai-runtime-readiness.input-safety.contract.test.ts +314 -0
  540. package/template/packages/server-sdk/src/ai-runtime-readiness.public-api.test.ts +18 -0
  541. package/template/packages/server-sdk/src/ai-runtime-readiness.python-parity.contract.test.ts +160 -0
  542. package/template/packages/server-sdk/src/ai-runtime-readiness.trust.contract.test.ts +135 -0
  543. package/template/packages/server-sdk/src/ai-runtime-readiness.ts +198 -0
  544. package/template/packages/server-sdk/src/ai-screenshot-intake-port.ts +58 -0
  545. package/template/packages/server-sdk/src/data-governance-registration.ts +1 -3
  546. package/template/packages/server-sdk/src/durable-handler.ts +12 -0
  547. package/template/packages/server-sdk/src/module-adapter.test.ts +10 -0
  548. package/template/packages/server-sdk/src/module-adapter.ts +3 -0
  549. package/template/packages/server-sdk/src/module-host.ts +8 -0
  550. package/template/packages/server-sdk/src/report-staging-context-compatibility.test.ts +25 -0
  551. package/template/packages/server-sdk/src/report-staging-port.ts +190 -0
  552. package/template/packages/server-sdk/src/request-context.ts +27 -1
  553. package/template/packages/server-sdk/src/route-definition.ts +6 -0
  554. package/template/packages/server-sdk/src/secret-binding.test.ts +66 -0
  555. package/template/packages/server-sdk/src/secret-binding.ts +54 -0
  556. package/template/packages/server-sdk/src/secret-context-compatibility.test.ts +77 -0
  557. package/template/packages/server-sdk/src/secret-port.ts +80 -0
  558. package/template/packages/server-sdk/src/storage-port.ts +129 -9
  559. package/template/packages/server-sdk/src/storage-v052-source-compatibility.test.ts +61 -0
  560. package/template/packages/shared/index.ts +5 -0
  561. package/template/packages/shared/package.json +4 -1
  562. package/template/packages/shared/src/ai-operation-readiness.test.ts +75 -0
  563. package/template/packages/shared/src/ai-operation-readiness.ts +170 -0
  564. package/template/packages/shared/src/license-catalog.ts +14 -9
  565. package/template/packages/shared/src/screenshot-context-digest.test.ts +46 -0
  566. package/template/packages/shared/src/screenshot-context-digest.ts +72 -0
  567. package/template/packages/shared/src/stable-key.ts +26 -0
  568. package/template/packages/shared/tsconfig.json +16 -0
  569. package/template/packages/storage/index.ts +2 -0
  570. package/template/packages/storage/package.json +1 -1
  571. package/template/packages/storage/src/in-memory-upload-repository.ts +105 -2
  572. package/template/packages/storage/src/local-file-blob-store.test.ts +58 -0
  573. package/template/packages/storage/src/local-file-blob-store.ts +5 -71
  574. package/template/packages/storage/src/local-file-blob-writer.ts +96 -0
  575. package/template/packages/storage/src/upload-abort-recovery.test.ts +234 -0
  576. package/template/packages/storage/src/upload-abort.ts +130 -0
  577. package/template/packages/storage/src/upload-cleanup-race.test.ts +207 -0
  578. package/template/packages/storage/src/upload-cleanup.ts +85 -0
  579. package/template/packages/storage/src/upload-completion.ts +52 -0
  580. package/template/packages/storage/src/upload-purge.test.ts +163 -0
  581. package/template/packages/storage/src/upload-purge.ts +261 -0
  582. package/template/packages/storage/src/upload-repository-v052-compatibility.test.ts +31 -0
  583. package/template/packages/storage/src/upload-repository.ts +61 -0
  584. package/template/packages/storage/src/upload-service-recovery.test.ts +296 -0
  585. package/template/packages/storage/src/upload-service.ts +138 -150
  586. package/template/packages/storage/src/upload-streams.ts +126 -30
  587. package/template/packages/storage/src/upload-types.ts +13 -0
  588. package/template/packages/storage/src/upload-validation.ts +127 -0
  589. package/template/scripts/provision-speech-readiness.ts +147 -0
@@ -100,16 +100,27 @@ interface CommandResult {
100
100
  timedOut?: boolean;
101
101
  }
102
102
 
103
+ interface KillableProcess {
104
+ readonly exitCode: number | null;
105
+ readonly exited: Promise<number>;
106
+ kill(signal?: number): void;
107
+ }
108
+
103
109
  export type NvidiaSmiRunner = (args: string[]) => Promise<CommandResult>;
104
110
 
105
111
  export function createGpuMonitorProbe(
106
112
  run: NvidiaSmiRunner = runNvidiaSmi,
107
113
  ): GpuMonitorProbe {
108
114
  return async () => {
109
- const deviceResult = await run([
110
- "--query-gpu=index,uuid,name,utilization.gpu,memory.used,memory.total,temperature.gpu",
111
- "--format=csv,noheader,nounits",
112
- ]);
115
+ let deviceResult: CommandResult;
116
+ try {
117
+ deviceResult = await run([
118
+ "--query-gpu=index,uuid,name,utilization.gpu,memory.used,memory.total,temperature.gpu",
119
+ "--format=csv,noheader,nounits",
120
+ ]);
121
+ } catch {
122
+ return unavailableGpuSnapshot();
123
+ }
113
124
  if (deviceResult.missing) {
114
125
  return {
115
126
  status: "not-detected",
@@ -138,10 +149,15 @@ export function createGpuMonitorProbe(
138
149
  message: "nvidia-smi 未返回可用的 NVIDIA GPU。",
139
150
  };
140
151
  }
141
- const processResult = await run([
142
- "--query-compute-apps=gpu_uuid,pid,process_name,used_gpu_memory",
143
- "--format=csv,noheader,nounits",
144
- ]);
152
+ let processResult: CommandResult;
153
+ try {
154
+ processResult = await run([
155
+ "--query-compute-apps=gpu_uuid,pid,process_name,used_gpu_memory",
156
+ "--format=csv,noheader,nounits",
157
+ ]);
158
+ } catch {
159
+ processResult = { exitCode: null, stdout: "" };
160
+ }
145
161
  if (processResult.exitCode === 0) {
146
162
  attachGpuProcesses(devices, processResult.stdout);
147
163
  }
@@ -157,6 +173,15 @@ export function createGpuMonitorProbe(
157
173
  };
158
174
  }
159
175
 
176
+ function unavailableGpuSnapshot(): GpuMonitorSnapshot {
177
+ return {
178
+ status: "unavailable",
179
+ provider: "NVIDIA",
180
+ devices: [],
181
+ message: "nvidia-smi 查询失败,无法读取 GPU 指标。",
182
+ };
183
+ }
184
+
160
185
  function systemCpuSample(): CpuSample {
161
186
  const cores = cpus();
162
187
  const totals = cores.reduce(
@@ -203,8 +228,7 @@ async function runNvidiaSmi(args: string[]): Promise<CommandResult> {
203
228
  ]);
204
229
  if (timeout) clearTimeout(timeout);
205
230
  if (result.timedOut) {
206
- child.kill();
207
- await child.exited;
231
+ await terminateTimedOutProcess(child);
208
232
  return { exitCode: null, stdout: "", timedOut: true };
209
233
  }
210
234
  return {
@@ -213,6 +237,32 @@ async function runNvidiaSmi(args: string[]): Promise<CommandResult> {
213
237
  };
214
238
  }
215
239
 
240
+ export async function terminateTimedOutProcess(
241
+ child: KillableProcess,
242
+ delay: (milliseconds: number) => Promise<unknown> = Bun.sleep,
243
+ graceMilliseconds = 250,
244
+ ): Promise<void> {
245
+ try {
246
+ child.kill();
247
+ } catch {
248
+ // Optional telemetry must still return when the soft termination fails.
249
+ }
250
+ await Promise.race([
251
+ child.exited.then(
252
+ () => undefined,
253
+ () => undefined,
254
+ ),
255
+ delay(graceMilliseconds).then(() => undefined),
256
+ ]);
257
+ if (child.exitCode === null) {
258
+ try {
259
+ child.kill(9);
260
+ } catch {
261
+ // The caller has already observed a timeout; never rethrow kill errors.
262
+ }
263
+ }
264
+ }
265
+
216
266
  function parseGpuDevices(output: string): GpuDeviceSnapshot[] {
217
267
  return output
218
268
  .split(/\r?\n/)
@@ -145,6 +145,7 @@ async function createFixture(options: FixtureOptions = {}) {
145
145
  invoke(method: string, path: string, params = {}, body?: unknown) {
146
146
  return app.invoke(method, path, {
147
147
  guardContext,
148
+ request: new Request("http://localhost/api/work-orders"),
148
149
  params,
149
150
  query: {},
150
151
  body,
@@ -8,10 +8,12 @@ from pathlib import Path
8
8
 
9
9
  import httpx
10
10
 
11
+ from .bounded_json import read_bounded_json
11
12
  from .config import SpeechRuntimeConfig
12
13
  from .contracts import JsonValue, StartSpeechRunRequest
13
14
  from .errors import SafeSpeechError
14
15
  from .normalizer import AudioNormalizer, NormalizationError
16
+ from .output_artifact import SpeechOutputArtifactClient
15
17
  from .output_validation import canonicalize_output
16
18
 
17
19
  SUPPORTED_MEDIA_TYPES = frozenset(
@@ -37,13 +39,12 @@ class SpeechAdapter:
37
39
  ) -> None:
38
40
  self.config = config
39
41
  self.normalizer = normalizer or AudioNormalizer()
42
+ self.output_artifacts = SpeechOutputArtifactClient(config)
40
43
 
41
- @property
42
- def available(self) -> bool:
43
- return self.config.adapter_url is not None and self.normalizer.available
44
-
45
- async def execute(self, request: StartSpeechRunRequest) -> JsonValue:
46
- if not self.config.adapter_url:
44
+ async def execute(
45
+ self, request: StartSpeechRunRequest, output_grant: str | None = None
46
+ ) -> JsonValue:
47
+ if request.effective_variant != "normalize" and not self.config.adapter_url:
47
48
  raise SafeSpeechError(
48
49
  "CAPABILITY_UNAVAILABLE",
49
50
  "Speech Adapter 未配置",
@@ -67,12 +68,27 @@ class SpeechAdapter:
67
68
  source = await self._download(request)
68
69
  normalized = Path(f"{source}.wav")
69
70
  try:
71
+ output_limit: int | None = None
72
+ if request.effective_variant == "normalize":
73
+ sink = request.output_artifact_sink
74
+ if not sink or not output_grant:
75
+ raise SafeSpeechError(
76
+ "CAPABILITY_UNAVAILABLE",
77
+ "normalize 需要 Host 分配 Output Artifact Sink",
78
+ True,
79
+ )
80
+ output_limit = min(
81
+ sink.limits.max_artifact_bytes,
82
+ sink.limits.max_total_bytes,
83
+ self.config.max_output_artifact_bytes,
84
+ )
70
85
  try:
71
86
  audio = await self.normalizer.normalize(
72
87
  source,
73
88
  normalized,
74
89
  request.limits.max_media_duration_seconds,
75
90
  request.limits.max_duration_seconds,
91
+ output_limit,
76
92
  )
77
93
  except NormalizationError as error:
78
94
  raise SafeSpeechError(
@@ -80,6 +96,16 @@ class SpeechAdapter:
80
96
  str(error),
81
97
  error.code == "CAPABILITY_UNAVAILABLE",
82
98
  ) from error
99
+ if request.effective_variant == "normalize":
100
+ assert request.output_artifact_sink and output_grant
101
+ artifact = await self.output_artifacts.publish(
102
+ request.output_artifact_sink, output_grant, audio.path
103
+ )
104
+ return {
105
+ "durationMs": audio.duration_ms,
106
+ "normalizedFormat": "pcm_s16le/16000/mono",
107
+ "artifacts": [artifact],
108
+ }
83
109
  return await self._invoke(request, audio.path, audio.duration_ms)
84
110
  finally:
85
111
  await remove(source)
@@ -141,6 +167,16 @@ class SpeechAdapter:
141
167
  )
142
168
  return path
143
169
 
170
+ async def accept_output_grant(
171
+ self, request: StartSpeechRunRequest, grant: str | None
172
+ ) -> None:
173
+ await self.output_artifacts.accept_grant(request, grant)
174
+
175
+ async def abort_open_output(
176
+ self, request: StartSpeechRunRequest, grant: str | None
177
+ ) -> None:
178
+ await self.output_artifacts.abort_open(request, grant)
179
+
144
180
  async def _invoke(
145
181
  self,
146
182
  request: StartSpeechRunRequest,
@@ -165,8 +201,9 @@ class SpeechAdapter:
165
201
  )
166
202
  ) as client:
167
203
  with path.open("rb") as source:
168
- response = await client.post(
169
- f"{adapter_url.rstrip('/')}/v1/speech/transcribe",
204
+ async with client.stream(
205
+ "POST",
206
+ f"{adapter_url.rstrip('/')}/v1/speech/{request.effective_variant}",
170
207
  headers=headers,
171
208
  files={"file": ("source.wav", source, "audio/wav")},
172
209
  data={
@@ -186,19 +223,19 @@ class SpeechAdapter:
186
223
  ),
187
224
  "limits": request.limits.model_dump_json(by_alias=True),
188
225
  },
189
- )
190
- response.raise_for_status()
191
- if len(response.content) > request.limits.max_output_bytes:
192
- raise SafeSpeechError(
193
- "BUDGET_EXCEEDED",
194
- "Speech 输出超过上限",
195
- False,
196
- )
197
- value: JsonValue = response.json()
226
+ ) as response:
227
+ value = await read_bounded_json(
228
+ response,
229
+ min(
230
+ request.limits.max_output_bytes,
231
+ self.config.max_inline_output_bytes,
232
+ ),
233
+ )
198
234
  return canonicalize_output(
199
235
  value,
200
236
  duration_ms,
201
237
  request.limits.max_units,
238
+ request.effective_variant,
202
239
  )
203
240
  except SafeSpeechError:
204
241
  raise
@@ -235,6 +272,10 @@ def enforce_runtime_quotas(
235
272
  ) -> None:
236
273
  if request.source.byte_size > request.limits.max_input_bytes:
237
274
  raise SafeSpeechError("BUDGET_EXCEEDED", "Speech Artifact 超过输入上限", False)
275
+ if request.source.byte_size > config.max_input_artifact_bytes:
276
+ raise SafeSpeechError(
277
+ "BUDGET_EXCEEDED", "Speech Artifact 超过 Runtime 上限", False
278
+ )
238
279
  if request.limits.max_units > config.max_units:
239
280
  raise SafeSpeechError("BUDGET_EXCEEDED", "转写分段上限超过 Runtime 配置", False)
240
281
  if request.limits.max_media_duration_seconds > config.max_media_duration_seconds:
@@ -13,7 +13,8 @@ from fastapi.responses import JSONResponse, Response, StreamingResponse
13
13
  from .adapter import SpeechAdapter
14
14
  from .config import SpeechRuntimeConfig
15
15
  from .contracts import ErrorResponse, StartRunResponse, StartSpeechRunRequest
16
- from .errors import SafeSpeechError
16
+ from .errors import SafeSpeechError, safe_speech_status
17
+ from .readiness import SpeechReadinessProbe
17
18
  from .service import SpeechRunService
18
19
  from .store import RunConflictError, SpeechRunStore
19
20
 
@@ -22,6 +23,7 @@ def create_app(config: SpeechRuntimeConfig) -> FastAPI:
22
23
  app = FastAPI(title="Southwind Speech Runtime", docs_url=None, redoc_url=None)
23
24
  store = SpeechRunStore(config.store_path)
24
25
  service = SpeechRunService(SpeechAdapter(config), store)
26
+ readiness = SpeechReadinessProbe(config)
25
27
 
26
28
  def authorize(authorization: str | None = Header(default=None)) -> None:
27
29
  provided = (authorization or "").removeprefix("Bearer ")
@@ -30,32 +32,33 @@ def create_app(config: SpeechRuntimeConfig) -> FastAPI:
30
32
 
31
33
  @app.get("/health/ready")
32
34
  async def ready(_: None = Depends(authorize)) -> JSONResponse:
33
- if not service.adapter.available:
34
- return JSONResponse(
35
- {"status": "unavailable", "capabilities": []},
36
- status_code=503,
37
- )
35
+ inspection = await readiness.inspect()
38
36
  return JSONResponse(
39
- {
40
- "status": "ready",
41
- "capabilities": list(config.capabilities),
42
- "limits": {
43
- "maxUnits": config.max_units,
44
- "maxMediaDurationSeconds": config.max_media_duration_seconds,
45
- },
46
- }
37
+ inspection.snapshot, status_code=200 if inspection.ready else 503
47
38
  )
48
39
 
49
40
  @app.post("/api/speech/runs", status_code=202)
50
41
  async def start(
51
42
  request: StartSpeechRunRequest,
52
43
  idempotency_key: str = Header(alias="idempotency-key"),
44
+ output_artifact_grant: str | None = Header(
45
+ default=None, alias="x-ai-output-artifact-grant"
46
+ ),
53
47
  _: None = Depends(authorize),
54
48
  ) -> StartRunResponse:
55
49
  if not idempotency_key.strip() or len(idempotency_key) > 160:
56
50
  raise HTTPException(status_code=400, detail="INVALID_INPUT")
51
+ inspection = await readiness.inspect()
52
+ if not readiness.supports(inspection, request):
53
+ raise SafeSpeechError(
54
+ "CAPABILITY_UNAVAILABLE",
55
+ "Speech Runtime 当前没有匹配的原子 offer",
56
+ True,
57
+ )
57
58
  try:
58
- record = await service.start(request, idempotency_key)
59
+ record = await service.start(
60
+ request, idempotency_key, output_artifact_grant
61
+ )
59
62
  except RunConflictError:
60
63
  raise HTTPException(status_code=409, detail="RUN_CONFLICT") from None
61
64
  return StartRunResponse(
@@ -155,8 +158,12 @@ def create_app(config: SpeechRuntimeConfig) -> FastAPI:
155
158
  async def safe_speech_error(
156
159
  _request: object, error: SafeSpeechError
157
160
  ) -> JSONResponse:
158
- status = 403 if error.code == "FORBIDDEN" else 400
159
- return safe_error(error.code, error.safe_message, status, error.retryable)
161
+ return safe_error(
162
+ error.code,
163
+ error.safe_message,
164
+ safe_speech_status(error),
165
+ error.retryable,
166
+ )
160
167
 
161
168
  return app
162
169
 
@@ -0,0 +1,60 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from typing import cast
5
+
6
+ import httpx
7
+
8
+ from .contracts import JsonValue
9
+
10
+
11
+ class BoundedJsonError(Exception):
12
+ pass
13
+
14
+
15
+ async def read_bounded_json(
16
+ response: httpx.Response,
17
+ max_bytes: int,
18
+ ) -> JsonValue:
19
+ media_type = response.headers.get("content-type", "").split(";", 1)[0]
20
+ if (
21
+ response.status_code < 200
22
+ or response.status_code >= 300
23
+ or media_type.strip().lower() != "application/json"
24
+ or response.headers.get("content-encoding")
25
+ ):
26
+ raise BoundedJsonError
27
+ declared = response.headers.get("content-length")
28
+ if declared is not None and (not declared.isdigit() or int(declared) > max_bytes):
29
+ raise BoundedJsonError
30
+ body = bytearray()
31
+ async for chunk in response.aiter_bytes():
32
+ if len(chunk) > max_bytes - len(body):
33
+ raise BoundedJsonError
34
+ body.extend(chunk)
35
+ if declared is not None and int(declared) != len(body):
36
+ raise BoundedJsonError
37
+ try:
38
+ return cast(
39
+ JsonValue,
40
+ json.loads(
41
+ body.decode("utf-8"),
42
+ object_pairs_hook=unique_object,
43
+ parse_constant=reject_constant,
44
+ ),
45
+ )
46
+ except (UnicodeDecodeError, json.JSONDecodeError, ValueError, RecursionError):
47
+ raise BoundedJsonError from None
48
+
49
+
50
+ def unique_object(pairs: list[tuple[str, JsonValue]]) -> dict[str, JsonValue]:
51
+ result: dict[str, JsonValue] = {}
52
+ for key, value in pairs:
53
+ if key in result:
54
+ raise ValueError("duplicate key")
55
+ result[key] = value
56
+ return result
57
+
58
+
59
+ def reject_constant(_value: str) -> JsonValue:
60
+ raise ValueError("non-finite number")
@@ -1,24 +1,52 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import ipaddress
4
+ import re
4
5
  from dataclasses import dataclass
5
6
  from urllib.parse import urlparse
6
7
 
8
+ _STABLE_KEY = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$")
9
+ _DEPLOYMENT_IDENTITY = re.compile(
10
+ r"^windy-deployment-v1_[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-"
11
+ r"[89ab][a-f0-9]{3}-[a-f0-9]{12}$"
12
+ )
13
+ _DIGEST = re.compile(r"^sha256:[a-f0-9]{64}$")
14
+
7
15
 
8
16
  @dataclass(frozen=True)
9
17
  class SpeechRuntimeConfig:
10
18
  internal_token: str
11
19
  store_path: str
12
20
  artifact_url: str
21
+ output_artifact_url: str
13
22
  adapter_url: str | None
14
23
  adapter_token: str | None
15
24
  deployment_key: str | None
25
+ deployment_identity: str | None
26
+ release_digest: str | None
27
+ offers_digest: str | None
16
28
  capabilities: tuple[str, ...]
17
29
  max_units: int = 100_000
18
30
  max_media_duration_seconds: int = 24 * 60 * 60
31
+ max_input_artifact_bytes: int = 2 * 1024 * 1024 * 1024
32
+ max_inline_output_bytes: int = 64 * 1024 * 1024
33
+ max_output_artifact_bytes: int = 512 * 1024 * 1024
34
+ max_concurrent_runs: int = 1
35
+ max_queue_depth: int = 0
36
+ readiness_timeout_seconds: float = 5
37
+ readiness_ttl_seconds: int = 60
19
38
  connect_timeout_seconds: float = 5
20
39
  request_timeout_seconds: float = 3600
21
40
 
41
+ @property
42
+ def readiness_configured(self) -> bool:
43
+ return bool(
44
+ self.deployment_key
45
+ and self.deployment_identity
46
+ and self.release_digest
47
+ and self.offers_digest
48
+ )
49
+
22
50
  @classmethod
23
51
  def from_environment(cls, environment: dict[str, str]) -> SpeechRuntimeConfig:
24
52
  artifact_url = environment.get(
@@ -26,6 +54,11 @@ class SpeechRuntimeConfig:
26
54
  "http://server:9747/api/internal/ai/artifacts",
27
55
  ).strip()
28
56
  validate_intranet_url(artifact_url)
57
+ output_artifact_url = environment.get(
58
+ "SPEECH_OUTPUT_ARTIFACT_URL",
59
+ "http://server:9747/api/internal/ai/output-artifacts",
60
+ ).strip()
61
+ validate_intranet_url(output_artifact_url)
29
62
  adapter_url = environment.get("SPEECH_ADAPTER_URL", "").strip() or None
30
63
  if adapter_url:
31
64
  validate_intranet_url(adapter_url)
@@ -36,7 +69,7 @@ class SpeechRuntimeConfig:
36
69
  for item in environment.get(
37
70
  "SPEECH_ADAPTER_CAPABILITIES",
38
71
  (
39
- "speech.transcribe.batch,speech.normalize,"
72
+ "speech.transcribe.batch,speech.translate,"
40
73
  "speech.vad,speech.timestamps"
41
74
  ),
42
75
  ).split(",")
@@ -44,15 +77,35 @@ class SpeechRuntimeConfig:
44
77
  }
45
78
  )
46
79
  )
80
+ if any(not _STABLE_KEY.fullmatch(item) for item in capabilities):
81
+ raise ValueError("SPEECH_ADAPTER_CAPABILITIES 无效")
82
+ deployment_key = optional(environment, "SPEECH_DEPLOYMENT_KEY")
83
+ if deployment_key and not _STABLE_KEY.fullmatch(deployment_key):
84
+ raise ValueError("SPEECH_DEPLOYMENT_KEY 无效")
85
+ deployment_identity = optional(environment, "SPEECH_DEPLOYMENT_IDENTITY")
86
+ if deployment_identity and not _DEPLOYMENT_IDENTITY.fullmatch(
87
+ deployment_identity
88
+ ):
89
+ raise ValueError("SPEECH_DEPLOYMENT_IDENTITY 无效")
90
+ release_digest = optional(environment, "SPEECH_RELEASE_DIGEST")
91
+ if release_digest and not _DIGEST.fullmatch(release_digest):
92
+ raise ValueError("SPEECH_RELEASE_DIGEST 无效")
93
+ offers_digest = optional(environment, "SPEECH_OFFERS_DIGEST")
94
+ if offers_digest and not _DIGEST.fullmatch(offers_digest):
95
+ raise ValueError("SPEECH_OFFERS_DIGEST 无效")
47
96
  return cls(
48
97
  internal_token=required(environment, "SPEECH_SERVER_INTERNAL_TOKEN"),
49
98
  store_path=environment.get(
50
99
  "SPEECH_RUN_STORE_PATH", "/app/data/speech/runs.sqlite3"
51
100
  ).strip(),
52
101
  artifact_url=artifact_url,
102
+ output_artifact_url=output_artifact_url,
53
103
  adapter_url=adapter_url,
54
104
  adapter_token=environment.get("SPEECH_ADAPTER_TOKEN", "").strip() or None,
55
- deployment_key=environment.get("SPEECH_DEPLOYMENT_KEY", "").strip() or None,
105
+ deployment_key=deployment_key,
106
+ deployment_identity=deployment_identity,
107
+ release_digest=release_digest,
108
+ offers_digest=offers_digest,
56
109
  capabilities=capabilities,
57
110
  max_units=positive_int(environment, "SPEECH_MAX_UNITS", 100_000),
58
111
  max_media_duration_seconds=positive_int(
@@ -60,6 +113,25 @@ class SpeechRuntimeConfig:
60
113
  "SPEECH_MAX_MEDIA_DURATION_SECONDS",
61
114
  24 * 60 * 60,
62
115
  ),
116
+ max_input_artifact_bytes=positive_int(
117
+ environment, "SPEECH_MAX_INPUT_ARTIFACT_BYTES", 2 * 1024**3
118
+ ),
119
+ max_inline_output_bytes=positive_int(
120
+ environment, "SPEECH_MAX_INLINE_OUTPUT_BYTES", 64 * 1024**2
121
+ ),
122
+ max_output_artifact_bytes=positive_int(
123
+ environment, "SPEECH_MAX_OUTPUT_ARTIFACT_BYTES", 512 * 1024**2
124
+ ),
125
+ max_concurrent_runs=positive_int(
126
+ environment, "SPEECH_MAX_CONCURRENT_RUNS", 1
127
+ ),
128
+ max_queue_depth=non_negative_int(environment, "SPEECH_MAX_QUEUE_DEPTH", 0),
129
+ readiness_timeout_seconds=bounded_positive_float(
130
+ environment, "SPEECH_READINESS_TIMEOUT_SECONDS", 5, 30
131
+ ),
132
+ readiness_ttl_seconds=bounded_positive_int(
133
+ environment, "SPEECH_READINESS_TTL_SECONDS", 60, 300
134
+ ),
63
135
  )
64
136
 
65
137
 
@@ -70,6 +142,10 @@ def required(environment: dict[str, str], key: str) -> str:
70
142
  return value
71
143
 
72
144
 
145
+ def optional(environment: dict[str, str], key: str) -> str | None:
146
+ return environment.get(key, "").strip() or None
147
+
148
+
73
149
  def positive_int(environment: dict[str, str], key: str, default: int) -> int:
74
150
  raw = environment.get(key, str(default)).strip()
75
151
  try:
@@ -81,6 +157,50 @@ def positive_int(environment: dict[str, str], key: str, default: int) -> int:
81
157
  return value
82
158
 
83
159
 
160
+ def non_negative_int(environment: dict[str, str], key: str, default: int) -> int:
161
+ value = integer(environment, key, default)
162
+ if value < 0:
163
+ raise ValueError(f"{key} 必须是非负整数")
164
+ return value
165
+
166
+
167
+ def integer(environment: dict[str, str], key: str, default: int) -> int:
168
+ raw = environment.get(key, str(default)).strip()
169
+ try:
170
+ return int(raw)
171
+ except ValueError:
172
+ raise ValueError(f"{key} 必须是整数") from None
173
+
174
+
175
+ def positive_float(environment: dict[str, str], key: str, default: float) -> float:
176
+ raw = environment.get(key, str(default)).strip()
177
+ try:
178
+ value = float(raw)
179
+ except ValueError:
180
+ raise ValueError(f"{key} 必须是正数") from None
181
+ if value <= 0:
182
+ raise ValueError(f"{key} 必须是正数")
183
+ return value
184
+
185
+
186
+ def bounded_positive_int(
187
+ environment: dict[str, str], key: str, default: int, maximum: int
188
+ ) -> int:
189
+ value = positive_int(environment, key, default)
190
+ if value > maximum:
191
+ raise ValueError(f"{key} 不能超过 {maximum}")
192
+ return value
193
+
194
+
195
+ def bounded_positive_float(
196
+ environment: dict[str, str], key: str, default: float, maximum: float
197
+ ) -> float:
198
+ value = positive_float(environment, key, default)
199
+ if value > maximum:
200
+ raise ValueError(f"{key} 不能超过 {maximum:g}")
201
+ return value
202
+
203
+
84
204
  def validate_intranet_url(raw: str) -> None:
85
205
  parsed = urlparse(raw)
86
206
  if parsed.scheme not in {"http", "https"} or not parsed.hostname:
@@ -38,6 +38,37 @@ class RuntimeLimits(CanonicalModel):
38
38
  max_media_duration_seconds: int = Field(ge=1, le=24 * 60 * 60)
39
39
 
40
40
 
41
+ class OutputArtifactLimits(CanonicalModel):
42
+ max_artifact_bytes: int = Field(ge=1, le=512 * 1024 * 1024)
43
+ max_artifacts: Literal[1]
44
+ max_total_bytes: int = Field(ge=1, le=512 * 1024 * 1024)
45
+
46
+
47
+ class OutputArtifactSink(CanonicalModel):
48
+ contract_version: Literal[1]
49
+ scope: str = Field(pattern=r"^outscope_[a-f0-9]{32}$")
50
+ output_kind: Literal["normalized-audio"]
51
+ deployment_identity: str = Field(
52
+ pattern=(
53
+ r"^windy-deployment-v1_[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-"
54
+ r"[89ab][a-f0-9]{3}-[a-f0-9]{12}$"
55
+ )
56
+ )
57
+ release_digest: str = Field(pattern=r"^sha256:[a-f0-9]{64}$")
58
+ offers_digest: str = Field(pattern=r"^sha256:[a-f0-9]{64}$")
59
+ policy_digest: str = Field(pattern=r"^[a-f0-9]{64}$")
60
+ grant_generation: int = Field(ge=1)
61
+ allowed_media_types: list[Literal["audio/wav"]] = Field(min_length=1, max_length=1)
62
+ expires_at: str = Field(min_length=24, max_length=32)
63
+ limits: OutputArtifactLimits
64
+
65
+ @model_validator(mode="after")
66
+ def validate_limits(self) -> Self:
67
+ if self.limits.max_artifact_bytes > self.limits.max_total_bytes:
68
+ raise ValueError("normalize Sink 单件上限超过总量上限")
69
+ return self
70
+
71
+
41
72
  class ProviderEgress(CanonicalModel):
42
73
  mode: Literal["forbidden", "configured-endpoint"]
43
74
  deployment_key: str | None = None
@@ -80,6 +111,13 @@ class ProviderDataPolicy(CanonicalModel):
80
111
 
81
112
  class StartSpeechRunRequest(CanonicalModel):
82
113
  kind: Literal["speech"]
114
+ run_id: str | None = Field(
115
+ default=None,
116
+ pattern=r"^speech_[a-f0-9]{64}$",
117
+ )
118
+ # Missing version remains the V1 transcribe wire contract.
119
+ contract_version: Literal[2] | None = None
120
+ variant: Literal["normalize", "transcribe", "translate"] | None = None
83
121
  operation_key: str = Field(min_length=1, max_length=160)
84
122
  options: JsonValue
85
123
  source: ArtifactInput
@@ -88,6 +126,26 @@ class StartSpeechRunRequest(CanonicalModel):
88
126
  limits: RuntimeLimits
89
127
  provider_egress: ProviderEgress
90
128
  provider_data_policy: ProviderDataPolicy | None = None
129
+ output_artifact_sink: OutputArtifactSink | None = None
130
+
131
+ @model_validator(mode="after")
132
+ def validate_contract(self) -> Self:
133
+ if self.contract_version is None and self.variant is None:
134
+ if self.run_id is not None or self.output_artifact_sink is not None:
135
+ raise ValueError("Speech V1 不接受 Output Artifact")
136
+ return self
137
+ if self.contract_version != 2 or self.variant is None:
138
+ raise ValueError("Speech V2 contractVersion 与 variant 必须同时指定")
139
+ if self.variant == "normalize":
140
+ if self.run_id is None or self.output_artifact_sink is None:
141
+ raise ValueError("normalize 缺少 Host Run 或 Output Artifact Sink")
142
+ elif self.run_id is not None or self.output_artifact_sink is not None:
143
+ raise ValueError("非 normalize 不能指定 Output Artifact")
144
+ return self
145
+
146
+ @property
147
+ def effective_variant(self) -> Literal["normalize", "transcribe", "translate"]:
148
+ return self.variant or "transcribe"
91
149
 
92
150
 
93
151
  class StartRunResponse(CanonicalModel):