cartethyia 2.7.0 → 2.7.2

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 (330) hide show
  1. package/.env.example +107 -106
  2. package/README.md +25 -2
  3. package/dist/dashboard/assets/{BannedPage-P76QLGJA.js → BannedPage-XaUmiBrC.js} +1 -1
  4. package/dist/dashboard/assets/BannedPage-XaUmiBrC.js.gz +0 -0
  5. package/dist/dashboard/assets/{CliToolDetailPage-DdBggJS9.js → CliToolDetailPage-CqD7QiLM.js} +1 -1
  6. package/dist/dashboard/assets/CliToolDetailPage-CqD7QiLM.js.gz +0 -0
  7. package/dist/dashboard/assets/{CliToolsPage-aN1iXhhP.js → CliToolsPage-0XUo4UMn.js} +1 -1
  8. package/dist/dashboard/assets/CliToolsPage-0XUo4UMn.js.gz +0 -0
  9. package/dist/dashboard/assets/{CombosPage-DE-F1nxZ.js → CombosPage-CXGS_Ct-.js} +1 -1
  10. package/dist/dashboard/assets/CombosPage-CXGS_Ct-.js.gz +0 -0
  11. package/dist/dashboard/assets/ConsoleLogPage-DGnGNYnM.js +1 -0
  12. package/dist/dashboard/assets/ConsoleLogPage-DGnGNYnM.js.gz +0 -0
  13. package/dist/dashboard/assets/{CustomizationPage-3CUxMy2X.js → CustomizationPage-DAG5J9EY.js} +1 -1
  14. package/dist/dashboard/assets/CustomizationPage-DAG5J9EY.js.gz +0 -0
  15. package/dist/dashboard/assets/HealthEventsModal-BKtAPYVF.js +1 -0
  16. package/dist/dashboard/assets/HealthEventsModal-BKtAPYVF.js.gz +0 -0
  17. package/dist/dashboard/assets/LoginPage-BdmRLnq2.js +1 -0
  18. package/dist/dashboard/assets/LoginPage-BdmRLnq2.js.gz +0 -0
  19. package/dist/dashboard/assets/{ModelPicker-DKVslqG3.js → ModelPicker-BH8Tehdi.js} +1 -1
  20. package/dist/dashboard/assets/ModelPicker-BH8Tehdi.js.gz +0 -0
  21. package/dist/dashboard/assets/OverviewPage-CqMxibJB.js +1 -0
  22. package/dist/dashboard/assets/OverviewPage-CqMxibJB.js.gz +0 -0
  23. package/dist/dashboard/assets/ProviderDetailPage-CqMh2NRX.js +4 -0
  24. package/dist/dashboard/assets/ProviderDetailPage-CqMh2NRX.js.gz +0 -0
  25. package/dist/dashboard/assets/ProviderIcon-FB6yS4v1.js +1 -0
  26. package/dist/dashboard/assets/ProviderIcon-FB6yS4v1.js.gz +0 -0
  27. package/dist/dashboard/assets/ProvidersPage-B9uWu4Nz.js +1 -0
  28. package/dist/dashboard/assets/ProvidersPage-B9uWu4Nz.js.gz +0 -0
  29. package/dist/dashboard/assets/ProxyPage-BoWOhpDO.js +9 -0
  30. package/dist/dashboard/assets/ProxyPage-BoWOhpDO.js.gz +0 -0
  31. package/dist/dashboard/assets/{QuotaPage-BjldQwIU.js → QuotaPage-7P2n--Fv.js} +1 -1
  32. package/dist/dashboard/assets/QuotaPage-7P2n--Fv.js.gz +0 -0
  33. package/dist/dashboard/assets/{SettingsPage-DUIoLRxk.js → SettingsPage-CxZ1fTkN.js} +1 -1
  34. package/dist/dashboard/assets/SettingsPage-CxZ1fTkN.js.gz +0 -0
  35. package/dist/dashboard/assets/SetupPage-Co12Zzaw.js +1 -0
  36. package/dist/dashboard/assets/SetupPage-Co12Zzaw.js.gz +0 -0
  37. package/dist/dashboard/assets/{SortableList-fI7JkdIB.js → SortableList-Dqf4zpS-.js} +1 -1
  38. package/dist/dashboard/assets/SortableList-Dqf4zpS-.js.gz +0 -0
  39. package/dist/dashboard/assets/{StudioPage-CHVZqskZ.js → StudioPage-Byj2lAEm.js} +3 -3
  40. package/dist/dashboard/assets/StudioPage-Byj2lAEm.js.gz +0 -0
  41. package/dist/dashboard/assets/UsagePage-CTkLAuRc.js +56 -0
  42. package/dist/dashboard/assets/UsagePage-CTkLAuRc.js.gz +0 -0
  43. package/dist/dashboard/assets/{api-keys-DFY_fZU4.js → api-keys-CUi6UXsR.js} +1 -1
  44. package/dist/dashboard/assets/api-keys-CUi6UXsR.js.gz +0 -0
  45. package/dist/dashboard/assets/app-KvsUJUMF.css +2 -0
  46. package/dist/dashboard/assets/app-KvsUJUMF.css.gz +0 -0
  47. package/dist/dashboard/assets/{assert-CwdnZARt.js → assert-67xXH5rf.js} +1 -1
  48. package/dist/dashboard/assets/assert-67xXH5rf.js.gz +0 -0
  49. package/dist/dashboard/assets/{badge-Dr-uf0Au.js → badge-DC2L43Mj.js} +1 -1
  50. package/dist/dashboard/assets/badge-DC2L43Mj.js.gz +0 -0
  51. package/dist/dashboard/assets/{banner-BddmMdoS.js → banner-BBVcb0J3.js} +1 -1
  52. package/dist/dashboard/assets/banner-BBVcb0J3.js.gz +0 -0
  53. package/dist/dashboard/assets/{card-CK0PXWlX.js → card-Cca1XC6G.js} +1 -1
  54. package/dist/dashboard/assets/card-Cca1XC6G.js.gz +0 -0
  55. package/dist/dashboard/assets/{cli-tools-CcB6mGh2.js → cli-tools-KvsWa1Jl.js} +1 -1
  56. package/dist/dashboard/assets/cli-tools-KvsWa1Jl.js.gz +0 -0
  57. package/dist/dashboard/assets/{clipboard-button-DGb2P9Yx.js → clipboard-button-DUzEbynq.js} +1 -1
  58. package/dist/dashboard/assets/clipboard-button-DUzEbynq.js.gz +0 -0
  59. package/dist/dashboard/assets/{dialog-LkfuL50u.js → dialog-DFvWhwTa.js} +1 -1
  60. package/dist/dashboard/assets/dialog-DFvWhwTa.js.gz +0 -0
  61. package/dist/dashboard/assets/{drawer-ZbEZ_84c.js → drawer-CFM2sMX1.js} +1 -1
  62. package/dist/dashboard/assets/drawer-CFM2sMX1.js.gz +0 -0
  63. package/dist/dashboard/assets/{entry-BJfsVMvA.js → entry-Bqy8RFvM.js} +1 -1
  64. package/dist/dashboard/assets/entry-Bqy8RFvM.js.gz +0 -0
  65. package/dist/dashboard/assets/{entry-CWBY5A0f.js → entry-ClCMCWWQ.js} +3 -3
  66. package/dist/dashboard/assets/entry-ClCMCWWQ.js.gz +0 -0
  67. package/dist/dashboard/assets/{entry-CvC1-Zqx.js → entry-_xHo5x6r.js} +1 -1
  68. package/dist/dashboard/assets/entry-_xHo5x6r.js.gz +0 -0
  69. package/dist/dashboard/assets/formatters-CWWZ-uCZ.js +1 -0
  70. package/dist/dashboard/assets/formatters-CWWZ-uCZ.js.gz +0 -0
  71. package/dist/dashboard/assets/{github-badge-BxpznFLt.js → github-badge-C9e8Ts7e.js} +1 -1
  72. package/dist/dashboard/assets/github-badge-C9e8Ts7e.js.gz +0 -0
  73. package/dist/dashboard/assets/{index-CDzP3ovJ.js → index-DFVFrnWW.js} +2 -2
  74. package/dist/dashboard/assets/index-DFVFrnWW.js.gz +0 -0
  75. package/dist/dashboard/assets/{inline-Ae13MQ5w.js → inline-BFUcvllj.js} +1 -1
  76. package/dist/dashboard/assets/inline-BFUcvllj.js.gz +0 -0
  77. package/dist/dashboard/assets/{input-D23fo7cp.js → input-C_ILAvBb.js} +1 -1
  78. package/dist/dashboard/assets/input-C_ILAvBb.js.gz +0 -0
  79. package/dist/dashboard/assets/{layout-CSsxjj5P.js → layout-CBIyT6Rt.js} +1 -1
  80. package/dist/dashboard/assets/layout-CBIyT6Rt.js.gz +0 -0
  81. package/dist/dashboard/assets/{list-row-dmbwZMDY.js → list-row-ebJ_-Nsb.js} +1 -1
  82. package/dist/dashboard/assets/list-row-ebJ_-Nsb.js.gz +0 -0
  83. package/dist/dashboard/assets/{live-BG1oUsZZ.js → live-D0mm--ME.js} +1 -1
  84. package/dist/dashboard/assets/live-D0mm--ME.js.gz +0 -0
  85. package/dist/dashboard/assets/{mutation-DSF-e4ho.js → mutation-sMD965oO.js} +1 -1
  86. package/dist/dashboard/assets/mutation-sMD965oO.js.gz +0 -0
  87. package/dist/dashboard/assets/{numbers-Bm_aECY7.js → numbers-BO9dKaFr.js} +1 -1
  88. package/dist/dashboard/assets/numbers-BO9dKaFr.js.gz +0 -0
  89. package/dist/dashboard/assets/providers/inception.svg +5 -0
  90. package/dist/dashboard/assets/providers/inception.svg.gz +0 -0
  91. package/dist/dashboard/assets/{query-keys-CF56nYIV.js → query-keys-CfL_Zdoh.js} +1 -1
  92. package/dist/dashboard/assets/query-keys-CfL_Zdoh.js.gz +0 -0
  93. package/dist/dashboard/assets/{routing-CeA71n7G.js → routing-BnIpi1IJ.js} +1 -1
  94. package/dist/dashboard/assets/routing-BnIpi1IJ.js.gz +0 -0
  95. package/dist/dashboard/assets/{select-KIT0rC6G.js → select-DBNVLdUu.js} +1 -1
  96. package/dist/dashboard/assets/select-DBNVLdUu.js.gz +0 -0
  97. package/dist/dashboard/assets/{settings-B_2gkePf.js → settings-BAFhDgX8.js} +1 -1
  98. package/dist/dashboard/assets/settings-BAFhDgX8.js.gz +0 -0
  99. package/dist/dashboard/assets/{stack-D7WOSzcX.js → stack-EMrOMt4I.js} +1 -1
  100. package/dist/dashboard/assets/stack-EMrOMt4I.js.gz +0 -0
  101. package/dist/dashboard/assets/{state-BBxtKZOQ.js → state-CWoo7ty5.js} +1 -1
  102. package/dist/dashboard/assets/state-CWoo7ty5.js.gz +0 -0
  103. package/dist/dashboard/assets/{switch-DNS_D5Ea.js → switch-CCjT6IkF.js} +1 -1
  104. package/dist/dashboard/assets/switch-CCjT6IkF.js.gz +0 -0
  105. package/dist/dashboard/assets/{useMutation-BgoxdoMk.js → useMutation-DjkLHUcE.js} +1 -1
  106. package/dist/dashboard/assets/useMutation-DjkLHUcE.js.gz +0 -0
  107. package/dist/dashboard/index.html +1 -1
  108. package/dist/dashboard/index.html.gz +0 -0
  109. package/dist/dashboard/precompressed-assets.json +54 -53
  110. package/migrations/full-postgres/0024_add_pool_provider_cooldowns.sql +12 -0
  111. package/migrations/full-postgres/0025_add_pool_random_strategy.sql +3 -0
  112. package/migrations/lite-sqlite3/0024_add_pool_provider_cooldowns.sql +12 -0
  113. package/migrations/lite-sqlite3/0025_add_pool_random_strategy.sql +20 -0
  114. package/package.json +96 -96
  115. package/scripts/cli/console-menu.ts +5 -2
  116. package/scripts/cli/tray.ts +21 -6
  117. package/src/boot/app.ts +684 -714
  118. package/src/boot/dependencies.ts +65 -62
  119. package/src/config/cooldowns.ts +16 -0
  120. package/src/config/providers.ts +13 -1
  121. package/src/config/resources.ts +70 -70
  122. package/src/config/security.ts +37 -37
  123. package/src/config/spec.ts +371 -358
  124. package/src/console/cli-tools/contracts.ts +10 -4
  125. package/src/console/console-router.ts +233 -225
  126. package/src/console/domain-registration.ts +398 -381
  127. package/src/console/providers/catalog/catalog-projections.ts +2 -6
  128. package/src/console/providers/catalog/contracts.ts +19 -0
  129. package/src/console/providers/catalog/provider-operations.ts +38 -0
  130. package/src/console/providers/catalog/routes.ts +14 -0
  131. package/src/console/providers/catalog/store-helpers.ts +326 -0
  132. package/src/console/providers/catalog/store.ts +1292 -1563
  133. package/src/console/quota/account-quota-global.ts +2 -2
  134. package/src/console/quota/account-quota-shared.ts +1 -1
  135. package/src/console/quota/account-quota-tenant.ts +35 -12
  136. package/src/console/quota/quota-view.ts +2 -2
  137. package/src/console/routing/pools/contracts.ts +41 -1
  138. package/src/console/routing/pools/operations.ts +183 -4
  139. package/src/console/routing/pools/relay-deploy.ts +17 -4
  140. package/src/console/routing/pools/routes.ts +30 -0
  141. package/src/console/routing/pools/store.ts +98 -8
  142. package/src/main.ts +183 -167
  143. package/src/network/outbound-fetch.ts +124 -208
  144. package/src/network/pool/agent.ts +15 -0
  145. package/src/network/pool/pool-state.ts +7 -0
  146. package/src/network/pool/selector.ts +110 -2
  147. package/src/network/pool-cooldown-store.ts +252 -0
  148. package/src/network/pool-health-machine.ts +5 -3
  149. package/src/network/pool-health.ts +36 -9
  150. package/src/observability/event-query.ts +57 -0
  151. package/src/observability/metrics.ts +467 -467
  152. package/src/observability/runtime-metrics.ts +269 -269
  153. package/src/persistence/health-event-flush.ts +2 -0
  154. package/src/persistence/schema.ts +19 -0
  155. package/src/persistence/usage-flush.ts +8 -3
  156. package/src/protocol/canonical-model.ts +1 -1
  157. package/src/protocol/dsml.ts +377 -0
  158. package/src/protocol/gateway-error.ts +232 -232
  159. package/src/{providers/provider-reasoning.ts → protocol/reasoning.ts} +10 -9
  160. package/src/protocol/request/chat.ts +7 -7
  161. package/src/protocol/request/codex.ts +2 -2
  162. package/src/protocol/request/gemini.ts +1 -1
  163. package/src/protocol/request/messages.ts +6 -6
  164. package/src/protocol/request/responses.ts +7 -7
  165. package/src/protocol/response/dsml.ts +12 -133
  166. package/src/{transport → protocol}/translation/attachment-integrity.ts +2 -2
  167. package/src/{transport → protocol}/translation/cache-controls.ts +1 -1
  168. package/src/{transport → protocol}/translation/capabilities.ts +2 -2
  169. package/src/{transport → protocol}/translation/normalize.ts +1 -1
  170. package/src/{transport → protocol}/translation/quirks.ts +1 -1
  171. package/src/{transport → protocol}/translation/search.ts +1 -1
  172. package/src/{transport → protocol}/translation/thinking.ts +23 -1
  173. package/src/{transport → protocol}/translation/tool-id.ts +1 -1
  174. package/src/{transport → protocol}/translation/tool-repair.ts +2 -2
  175. package/src/protocol/upstream-parsing.ts +176 -0
  176. package/src/providers/adapters/compatible-adapter.ts +11 -2
  177. package/src/providers/authentication/oauth-refresh-service.ts +9 -5
  178. package/src/providers/default-registry.ts +22 -5
  179. package/src/providers/discovery/probe-phases.ts +16 -3
  180. package/src/providers/discovery/probing-service.ts +4 -1
  181. package/src/providers/integrations/anthropic.ts +1 -1
  182. package/src/providers/integrations/buddy/buddy-catalog-shared.ts +1 -4
  183. package/src/providers/integrations/buddy/buddy-chat-shared.ts +141 -8
  184. package/src/providers/integrations/buddy/buddy-checkin.ts +5 -1
  185. package/src/providers/integrations/buddy/buddy-growth-shared.ts +0 -82
  186. package/src/providers/integrations/buddy/buddy-identity.ts +25 -0
  187. package/src/providers/integrations/buddy/buddy-server-tools.ts +240 -0
  188. package/src/providers/integrations/buddy/codebuddy/cn-catalog.ts +36 -0
  189. package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +6 -31
  190. package/src/providers/integrations/buddy/codebuddy/provider.ts +2 -0
  191. package/src/providers/integrations/buddy/codebuddy/shared.ts +19 -4
  192. package/src/providers/integrations/buddy/turing-device-token.ts +262 -0
  193. package/src/providers/integrations/buddy/workbuddy/provider.ts +2 -0
  194. package/src/providers/integrations/buddy/workbuddy/shared.ts +56 -3
  195. package/src/providers/integrations/claude/claude.ts +1 -1
  196. package/src/providers/integrations/cloudflareai/cloudflareai.ts +1 -1
  197. package/src/providers/integrations/codex/codex-errors.ts +1 -1
  198. package/src/providers/integrations/codex/codex.ts +1 -1
  199. package/src/providers/integrations/factory/factory.ts +1 -1
  200. package/src/providers/integrations/github/github.ts +1 -1
  201. package/src/providers/integrations/grok/grok.ts +1 -1
  202. package/src/providers/integrations/inception/provider.ts +91 -0
  203. package/src/providers/integrations/inferhub.ts +1 -1
  204. package/src/providers/integrations/kiro/kiro-request.ts +1 -1
  205. package/src/providers/integrations/mistral.ts +193 -19
  206. package/src/providers/integrations/ollamacloud-models.ts +50 -0
  207. package/src/providers/integrations/opencode/fingerprint.ts +19 -2
  208. package/src/providers/integrations/opencode/prompt.ts +111 -0
  209. package/src/providers/integrations/opencode/provider.ts +101 -23
  210. package/src/providers/integrations/opencode/tool-definitions.ts +4 -4
  211. package/src/providers/integrations/qoder/qoder-checkin.ts +303 -0
  212. package/src/providers/integrations/qoder/qoder-oauth.ts +12 -1
  213. package/src/providers/integrations/qoder/qoder-realm.ts +20 -0
  214. package/src/providers/integrations/qoder/qoder-text-tools.ts +277 -0
  215. package/src/providers/integrations/qoder/qoder.ts +315 -400
  216. package/src/providers/integrations/shared/install-id.ts +2 -1
  217. package/src/providers/integrations/trae/trae-cache.ts +110 -0
  218. package/src/providers/integrations/trae/trae-oauth.ts +29 -4
  219. package/src/providers/integrations/trae/trae-protocol.ts +2 -1
  220. package/src/providers/integrations/trae/trae.ts +106 -23
  221. package/src/providers/operations/account-error-classification.ts +1684 -0
  222. package/src/providers/operations/account-health-service.ts +27 -1503
  223. package/src/providers/operations/account-reset-service.ts +2 -0
  224. package/src/providers/operations/provider-catalog-service.ts +11 -1
  225. package/src/providers/provider-contracts.ts +7 -4
  226. package/src/providers/provider-metadata.ts +25 -4
  227. package/src/providers/provider-probe.ts +2 -2
  228. package/src/{console → providers}/quota/cache.ts +1 -1
  229. package/src/{console → providers}/quota/refresh.ts +5 -5
  230. package/src/security/cli-client-fingerprint.ts +9 -2
  231. package/src/transport/dispatch/attempt-finalize.ts +114 -12
  232. package/src/transport/dispatch/attempt-loop.ts +3 -0
  233. package/src/transport/dispatch/empty-stream.ts +9 -2
  234. package/src/transport/dispatch/generation.ts +37 -55
  235. package/src/transport/dispatch/handler-deps.ts +32 -0
  236. package/src/transport/dispatch/leases.ts +9 -4
  237. package/src/transport/dispatch/proxy-request.ts +6 -30
  238. package/src/transport/dispatch/responses-compact.ts +22 -43
  239. package/src/transport/dispatch/streaming/finalize.ts +3 -0
  240. package/src/transport/dispatch/streaming/stream.ts +0 -8
  241. package/src/transport/dispatch/systemone.ts +22 -42
  242. package/src/transport/dispatch/token-count.ts +23 -39
  243. package/src/transport/dispatch/upstream.ts +1 -3
  244. package/src/transport/dispatch/websearch.ts +22 -41
  245. package/src/transport/failure-policy.ts +92 -178
  246. package/src/transport/guards/abuse-guard.ts +10 -0
  247. package/src/transport/guards/auth-guard.ts +109 -109
  248. package/src/transport/guards/error-lifecycle.ts +1 -1
  249. package/src/transport/guards/pipeline.ts +220 -220
  250. package/src/transport/guards/request-context.ts +245 -238
  251. package/src/transport/request/degradation.ts +20 -1
  252. package/src/transport/request/inflight.ts +4 -114
  253. package/src/transport/request/preparer.ts +6 -6
  254. package/src/transport/request/state.ts +66 -38
  255. package/src/transport/request/tenant-preferences.ts +1 -1
  256. package/src/transport/routing/route-catalog.ts +75 -43
  257. package/src/transport/routing/router.ts +30 -2
  258. package/src/transport/surface/messages/parse.ts +1 -1
  259. package/src/transport/surface/responses/encode.ts +2 -3
  260. package/src/transport/surface/responses/parse.ts +49 -7
  261. package/src/types/undici.d.ts +33 -0
  262. package/src/workers/buddy-growth-tasks.ts +346 -425
  263. package/src/workers/daily-checkin.ts +17 -6
  264. package/src/workers/quota-refresh-sweep.ts +2 -2
  265. package/src/workers/scheduler.ts +17 -1
  266. package/dist/dashboard/assets/BannedPage-P76QLGJA.js.gz +0 -0
  267. package/dist/dashboard/assets/CliToolDetailPage-DdBggJS9.js.gz +0 -0
  268. package/dist/dashboard/assets/CliToolsPage-aN1iXhhP.js.gz +0 -0
  269. package/dist/dashboard/assets/CombosPage-DE-F1nxZ.js.gz +0 -0
  270. package/dist/dashboard/assets/ConsoleLogPage-5SpmfLyw.js +0 -1
  271. package/dist/dashboard/assets/ConsoleLogPage-5SpmfLyw.js.gz +0 -0
  272. package/dist/dashboard/assets/CustomizationPage-3CUxMy2X.js.gz +0 -0
  273. package/dist/dashboard/assets/HealthEventsModal-BkmJQKC0.js +0 -1
  274. package/dist/dashboard/assets/HealthEventsModal-BkmJQKC0.js.gz +0 -0
  275. package/dist/dashboard/assets/LoginPage-B_0FUKWc.js +0 -1
  276. package/dist/dashboard/assets/LoginPage-B_0FUKWc.js.gz +0 -0
  277. package/dist/dashboard/assets/ModelPicker-DKVslqG3.js.gz +0 -0
  278. package/dist/dashboard/assets/OverviewPage-D-H4MEiT.js +0 -1
  279. package/dist/dashboard/assets/OverviewPage-D-H4MEiT.js.gz +0 -0
  280. package/dist/dashboard/assets/ProviderDetailPage-CEJw6Guo.js +0 -3
  281. package/dist/dashboard/assets/ProviderDetailPage-CEJw6Guo.js.gz +0 -0
  282. package/dist/dashboard/assets/ProviderIcon-CmfzxpVM.js +0 -1
  283. package/dist/dashboard/assets/ProviderIcon-CmfzxpVM.js.gz +0 -0
  284. package/dist/dashboard/assets/ProvidersPage-CEplAR-i.js +0 -1
  285. package/dist/dashboard/assets/ProvidersPage-CEplAR-i.js.gz +0 -0
  286. package/dist/dashboard/assets/ProxyPage-CAN7hy0f.js +0 -7
  287. package/dist/dashboard/assets/ProxyPage-CAN7hy0f.js.gz +0 -0
  288. package/dist/dashboard/assets/QuotaPage-BjldQwIU.js.gz +0 -0
  289. package/dist/dashboard/assets/SettingsPage-DUIoLRxk.js.gz +0 -0
  290. package/dist/dashboard/assets/SetupPage-_bCaTM0v.js +0 -1
  291. package/dist/dashboard/assets/SetupPage-_bCaTM0v.js.gz +0 -0
  292. package/dist/dashboard/assets/SortableList-fI7JkdIB.js.gz +0 -0
  293. package/dist/dashboard/assets/StudioPage-CHVZqskZ.js.gz +0 -0
  294. package/dist/dashboard/assets/UsagePage-DiFBymHj.js +0 -56
  295. package/dist/dashboard/assets/UsagePage-DiFBymHj.js.gz +0 -0
  296. package/dist/dashboard/assets/api-keys-DFY_fZU4.js.gz +0 -0
  297. package/dist/dashboard/assets/app-BOJ1_xUB.css +0 -2
  298. package/dist/dashboard/assets/app-BOJ1_xUB.css.gz +0 -0
  299. package/dist/dashboard/assets/assert-CwdnZARt.js.gz +0 -0
  300. package/dist/dashboard/assets/badge-Dr-uf0Au.js.gz +0 -0
  301. package/dist/dashboard/assets/banner-BddmMdoS.js.gz +0 -0
  302. package/dist/dashboard/assets/card-CK0PXWlX.js.gz +0 -0
  303. package/dist/dashboard/assets/cli-tools-CcB6mGh2.js.gz +0 -0
  304. package/dist/dashboard/assets/clipboard-button-DGb2P9Yx.js.gz +0 -0
  305. package/dist/dashboard/assets/cog-CdWhsajs.js +0 -1
  306. package/dist/dashboard/assets/cog-CdWhsajs.js.gz +0 -0
  307. package/dist/dashboard/assets/dialog-LkfuL50u.js.gz +0 -0
  308. package/dist/dashboard/assets/drawer-ZbEZ_84c.js.gz +0 -0
  309. package/dist/dashboard/assets/entry-BJfsVMvA.js.gz +0 -0
  310. package/dist/dashboard/assets/entry-CWBY5A0f.js.gz +0 -0
  311. package/dist/dashboard/assets/entry-CvC1-Zqx.js.gz +0 -0
  312. package/dist/dashboard/assets/github-badge-BxpznFLt.js.gz +0 -0
  313. package/dist/dashboard/assets/index-CDzP3ovJ.js.gz +0 -0
  314. package/dist/dashboard/assets/inline-Ae13MQ5w.js.gz +0 -0
  315. package/dist/dashboard/assets/input-D23fo7cp.js.gz +0 -0
  316. package/dist/dashboard/assets/layout-CSsxjj5P.js.gz +0 -0
  317. package/dist/dashboard/assets/list-row-dmbwZMDY.js.gz +0 -0
  318. package/dist/dashboard/assets/live-BG1oUsZZ.js.gz +0 -0
  319. package/dist/dashboard/assets/mutation-DSF-e4ho.js.gz +0 -0
  320. package/dist/dashboard/assets/numbers-Bm_aECY7.js.gz +0 -0
  321. package/dist/dashboard/assets/query-keys-CF56nYIV.js.gz +0 -0
  322. package/dist/dashboard/assets/routing-CeA71n7G.js.gz +0 -0
  323. package/dist/dashboard/assets/select-KIT0rC6G.js.gz +0 -0
  324. package/dist/dashboard/assets/settings-B_2gkePf.js.gz +0 -0
  325. package/dist/dashboard/assets/stack-D7WOSzcX.js.gz +0 -0
  326. package/dist/dashboard/assets/state-BBxtKZOQ.js.gz +0 -0
  327. package/dist/dashboard/assets/switch-DNS_D5Ea.js.gz +0 -0
  328. package/dist/dashboard/assets/useMutation-BgoxdoMk.js.gz +0 -0
  329. /package/dist/dashboard/assets/{app-CaXZ9SCn.js → app-BaWd0puM.js} +0 -0
  330. /package/dist/dashboard/assets/{app-CaXZ9SCn.js.gz → app-BaWd0puM.js.gz} +0 -0
@@ -48,6 +48,7 @@ import {
48
48
  ValidatedNetworkBindingFactory,
49
49
  } from "../network/pool/resolver";
50
50
  import { NetworkPoolSelector } from "../network/pool/selector";
51
+ import { restorePoolProbeVerdicts, restoreProviderCooldownMarkers } from "../network/pool-cooldown-store";
51
52
  import { ScheduledTaskRegistry } from "../workers/scheduler";
52
53
  import { quotaRefreshSweep } from "../workers/quota-refresh-sweep";
53
54
  import { markProviderApiKeyCredential } from "../console/quota/account-quota-shared";
@@ -55,15 +56,11 @@ import { dailyCheckinEgressForPass } from "../workers/daily-checkin";
55
56
  import {
56
57
  buddyAdoptTask,
57
58
  buddyActivityReportTask,
58
- buddyLotteryTask,
59
- buddyRedeemTask,
60
59
  buddyStreakTask,
61
- buddyTravelTask,
62
- buddyTrialTask,
63
60
  syncBuddyUsage,
64
61
  } from "../workers/buddy-growth-tasks";
65
62
  import { createAccountSecretResolver } from "../providers/operations/provider-credential-service";
66
- import { quotaCacheSize } from "../console/quota/cache";
63
+ import { quotaCacheSize } from "../providers/quota/cache";
67
64
  import { modelCatalogCacheSize } from "../providers/operations/model-catalog-cache";
68
65
  import { preferencesReaderFor } from "../persistence/tenant-preferences";
69
66
  import { sweepExpiredCooldowns } from "../providers/operations/account-health-service";
@@ -260,39 +257,6 @@ export async function buildProductionDeps(): Promise<ProductionDeps> {
260
257
  redis ? new RedisAdmissionController(redis) : undefined,
261
258
  redis,
262
259
  );
263
- const readRoutingAccountInflight = async (
264
- providerId: string,
265
- tenantId: string | null,
266
- ): Promise<readonly { accountId: string; inflight: number }[]> => {
267
- if (providerId === "" || tenantId === null) return [];
268
- const accounts = await new DrizzleProviderCatalogStore(db, {
269
- telemetryBuffer,
270
- bundledModelCatalog: bundledCatalog,
271
- providerRegistry: registry,
272
- outboundFetchFor: async () => ({ fetch: networkBindingFactory.fetch(undefined, tenantId) }),
273
- snapshotInvalidator: snapshotService,
274
- }).listAccounts(tenantId, providerId);
275
- const owned = new Set(accounts.map((account) => account.id));
276
- // Scoped read: only the provider's own candidates for this tenant are
277
- // queried, so the console read costs one exact MGET over the accounts it
278
- // is about to report on — not a fleet-wide `KEYS` scan whose cost is the
279
- // size of every bucket every process ever wrote.
280
- const routeSnapshot = await snapshotService.getSnapshot();
281
- const ownedCandidates = routeSnapshot.candidates.filter(
282
- (candidate) =>
283
- candidate.provider_id === providerId &&
284
- candidate.provider_account_id !== undefined &&
285
- owned.has(candidate.provider_account_id),
286
- );
287
- const snapshot = await routingEngine.accountInflightSnapshot(ownedCandidates);
288
- const totals = new Map<string, number>();
289
- for (const [bucket, count] of snapshot) {
290
- const accountId = bucket.slice(bucket.lastIndexOf(":") + 1);
291
- if (!owned.has(accountId)) continue;
292
- totals.set(accountId, (totals.get(accountId) ?? 0) + count);
293
- }
294
- return [...totals].map(([accountId, inflight]) => ({ accountId, inflight }));
295
- };
296
260
  // Peer processes' settings mutations arrive here. Subscribed once for the
297
261
  // process: the revision counter is process-global, and the handler only
298
262
  // moves it forward.
@@ -379,6 +343,10 @@ export async function buildProductionDeps(): Promise<ProductionDeps> {
379
343
  scheduledTasks.register({
380
344
  name: "account-health-sweep",
381
345
  intervalMs: 30_000,
346
+ // Offsets the pass from lease-sweep and runtime-metrics, which share the
347
+ // same cadence: under the registry's single-flight gate, aligned ticks
348
+ // make the 30s tasks skip each other when one runs long.
349
+ initialDelayMs: 5_000,
382
350
  // Recovers cooldowns deployment-wide and invalidates the shared snapshot.
383
351
  shared: true,
384
352
  run: async () => {
@@ -420,6 +388,22 @@ export async function buildProductionDeps(): Promise<ProductionDeps> {
420
388
  });
421
389
  const telemetryBuffer = new TelemetryBatchBuffer(db);
422
390
  const poolSelector = new NetworkPoolSelector(redis);
391
+ // Replay the provider cooldown markers the previous process recorded
392
+ // (`network_pools.provider_cooldowns`). Without this a restart made dispatch
393
+ // forget every egress the upstream was still refusing: the flags are
394
+ // process-local otherwise, so the pool re-entered rotation and took 429s the
395
+ // first request after boot re-discovered. Expired entries are dropped by the
396
+ // restore itself. Best-effort: a failed read leaves selection matching what
397
+ // a fresh deployment would do.
398
+ try {
399
+ const restored = await restoreProviderCooldownMarkers(poolSelector, db);
400
+ const replayed = await restorePoolProbeVerdicts(poolSelector, db);
401
+ if (restored > 0 || replayed > 0) {
402
+ log.info("[pool-selector] restored provider cooldown state", { mirrored: restored, probeVerdictsApplied: replayed });
403
+ }
404
+ } catch (error) {
405
+ log.warn("[pool-selector] provider cooldown restore failed", { error: String(error) });
406
+ }
423
407
  const poolAgentResolver = new PoolAgentResolver(
424
408
  new DrizzleNetworkPoolLoader(db),
425
409
  { ssrfPolicy, db },
@@ -429,6 +413,49 @@ export async function buildProductionDeps(): Promise<ProductionDeps> {
429
413
  undefined,
430
414
  poolAgentResolver,
431
415
  );
416
+ // The account-inflight reader the console queries. It lives after the deps
417
+ // it closes over (telemetry buffer, binding factory), so the closure never
418
+ // touches a TDZ binding; it also reuses one catalog store per tenant instead
419
+ // of constructing a fresh store object on every console read.
420
+ let lastInflightTenant = "";
421
+ let lastInflightStore: DrizzleProviderCatalogStore | undefined;
422
+ const readRoutingAccountInflight = async (
423
+ providerId: string,
424
+ tenantId: string | null,
425
+ ): Promise<readonly { accountId: string; inflight: number }[]> => {
426
+ if (providerId === "" || tenantId === null) return [];
427
+ if (tenantId !== lastInflightTenant || lastInflightStore === undefined) {
428
+ lastInflightStore = new DrizzleProviderCatalogStore(db, {
429
+ telemetryBuffer,
430
+ bundledModelCatalog: bundledCatalog,
431
+ providerRegistry: registry,
432
+ outboundFetchFor: async () => ({ fetch: networkBindingFactory.fetch(undefined, tenantId) }),
433
+ snapshotInvalidator: snapshotService,
434
+ });
435
+ lastInflightTenant = tenantId;
436
+ }
437
+ const accounts = await lastInflightStore.listAccounts(tenantId, providerId);
438
+ const owned = new Set(accounts.map((account) => account.id));
439
+ // Scoped read: only the provider's own candidates for this tenant are
440
+ // queried, so the console read costs one exact MGET over the accounts it
441
+ // is about to report on — not a fleet-wide `KEYS` scan whose cost is the
442
+ // size of every bucket every process ever wrote.
443
+ const routeSnapshot = await snapshotService.getSnapshot();
444
+ const ownedCandidates = routeSnapshot.candidates.filter(
445
+ (candidate) =>
446
+ candidate.provider_id === providerId &&
447
+ candidate.provider_account_id !== undefined &&
448
+ owned.has(candidate.provider_account_id),
449
+ );
450
+ const snapshot = await routingEngine.accountInflightSnapshot(ownedCandidates);
451
+ const totals = new Map<string, number>();
452
+ for (const [bucket, count] of snapshot) {
453
+ const accountId = bucket.slice(bucket.lastIndexOf(":") + 1);
454
+ if (!owned.has(accountId)) continue;
455
+ totals.set(accountId, (totals.get(accountId) ?? 0) + count);
456
+ }
457
+ return [...totals].map(([accountId, inflight]) => ({ accountId, inflight }));
458
+ };
432
459
  // Runtime gauges (memory + bounded-collection sizes) sampled on the shared
433
460
  // maintenance cadence. The sampler reads live sizes through closures so the
434
461
  // observability layer never holds direct references to routing/pool state.
@@ -606,36 +633,12 @@ export async function buildProductionDeps(): Promise<ProductionDeps> {
606
633
  initialDelayMs: 5 * 60_000,
607
634
  run: () => buddyStreakTask(buddyTaskDeps),
608
635
  });
609
- scheduledTasks.register({
610
- name: "buddy-redeem",
611
- intervalMs: 24 * 60 * 60_000,
612
- initialDelayMs: 8 * 60_000,
613
- run: () => buddyRedeemTask(buddyTaskDeps),
614
- });
615
- scheduledTasks.register({
616
- name: "buddy-lottery",
617
- intervalMs: 24 * 60 * 60_000,
618
- initialDelayMs: 11 * 60_000,
619
- run: () => buddyLotteryTask(buddyTaskDeps),
620
- });
621
- scheduledTasks.register({
622
- name: "buddy-travel",
623
- intervalMs: 24 * 60 * 60_000,
624
- initialDelayMs: 14 * 60_000,
625
- run: () => buddyTravelTask(buddyTaskDeps),
626
- });
627
636
  scheduledTasks.register({
628
637
  name: "buddy-adopt",
629
638
  intervalMs: 24 * 60 * 60_000,
630
639
  initialDelayMs: 17 * 60_000,
631
640
  run: () => buddyAdoptTask(buddyTaskDeps),
632
641
  });
633
- scheduledTasks.register({
634
- name: "buddy-trial",
635
- intervalMs: 24 * 60 * 60_000,
636
- initialDelayMs: 20 * 60_000,
637
- run: () => buddyTrialTask(buddyTaskDeps),
638
- });
639
642
  // Growth-activity report: the `/v2/report` batch that lights the login streak
640
643
  // and scores the daily growth event. Once a day with its own ledger — it used
641
644
  // to ride the quota sweep with no marker of its own, so its eight POSTs ran
@@ -168,6 +168,21 @@ export function resolveAccountDisabledRecoveryMs(): number {
168
168
  return _disabledRecovery;
169
169
  }
170
170
 
171
+ let _poolProviderRateLimitCooldown: number | undefined;
172
+ /**
173
+ * Fallback cooldown for a provider-scoped egress 429 when the upstream named no
174
+ * window (default 1h). See `CARTETHYIA_POOL_PROVIDER_RATE_LIMIT_COOLDOWN_MS`.
175
+ * An upstream `retryAfterMs` always wins over this.
176
+ */
177
+ export function resolvePoolProviderRateLimitCooldownMs(): number {
178
+ if (_poolProviderRateLimitCooldown === undefined)
179
+ _poolProviderRateLimitCooldown = readInt(
180
+ "CARTETHYIA_POOL_PROVIDER_RATE_LIMIT_COOLDOWN_MS",
181
+ CONFIG_SPEC.CARTETHYIA_POOL_PROVIDER_RATE_LIMIT_COOLDOWN_MS,
182
+ );
183
+ return _poolProviderRateLimitCooldown;
184
+ }
185
+
171
186
  /**
172
187
  * Resets all cached cooldown values. Call this in tests that override
173
188
  * cooldown env-vars between test cases.
@@ -180,6 +195,7 @@ export function resetCooldownCacheForTests(): void {
180
195
  _unclassifiedCooldown = undefined;
181
196
  _escalationMaxCooldown = undefined;
182
197
  _poolCooldown = undefined;
198
+ _poolProviderRateLimitCooldown = undefined;
183
199
  _authRejectedCooldown = undefined;
184
200
  _policyBlockCooldown = undefined;
185
201
  _disabledRecovery = undefined;
@@ -4,7 +4,7 @@
4
4
  * public origin.
5
5
  */
6
6
 
7
- import { CONFIG_SPEC, readInt, readRequired, readText } from "./spec";
7
+ import { CONFIG_SPEC, readInt, readOptionalText, readRequired, readText } from "./spec";
8
8
 
9
9
  /** Reads and normalizes the public OAuth origin. */
10
10
  export function requirePublicOrigin(): string {
@@ -48,6 +48,18 @@ export function resolveKimiQuotaBaseUrl(): string {
48
48
  );
49
49
  }
50
50
 
51
+ /**
52
+ * Operator override for the directory installation identities are persisted in.
53
+ * Read here so the only literal `process.env` for this value stays in config;
54
+ * the providers that stamp an install id consume the resolver instead.
55
+ */
56
+ export function resolveInstallIdDir(): string | undefined {
57
+ return readOptionalText(
58
+ "CARTETHYIA_INSTALL_ID_DIR",
59
+ CONFIG_SPEC.CARTETHYIA_INSTALL_ID_DIR,
60
+ );
61
+ }
62
+
51
63
  /**
52
64
  * Operator override for the OpenCode free-tier ids upstream refuses, or
53
65
  * `undefined` when unset — meaning the bundled, measured list stands.
@@ -1,70 +1,70 @@
1
- /**
2
- * Canonical resource policy: how much memory this process may use, and how
3
- * much room it must keep free.
4
- *
5
- * The memory figure used to be decided in unrelated places — a host-wide
6
- * `os.totalmem()` in the headroom guard, a fallback constant in
7
- * `runtime-metrics.ts` — which meant a container limited to 512 MiB shed on
8
- * the same threshold as a 4 GiB one and only reacted once the *host* was
9
- * nearly full. On a 1-core / 2 GiB VPS that is the difference between
10
- * shedding early and being OOM-killed, and the OOM kills the dashboard too —
11
- * the one surface that would have explained what happened.
12
- *
13
- * How many requests may run at once is NOT decided here. That is admission's
14
- * job: the tenant ceiling and each key's own limit, both set in Settings. A
15
- * second, process-wide ceiling derived from memory sat below the number an
16
- * operator could configure and refused requests that every configured limit
17
- * still allowed, which is why it is gone.
18
- */
19
- import { CONFIG_SPEC, readOptionalInt } from "./spec";
20
-
21
- /** Used when no limit is discoverable at all (native dev, macOS, Windows). */
22
- export const FALLBACK_MEMORY_LIMIT_BYTES = 512 * 1024 * 1024;
23
-
24
-
25
- /**
26
- * How many copies of a request body are resident at once.
27
- *
28
- * Measured, not assumed: the socket read buffer, the parsed object graph, and
29
- * the upstream re-serialization each hold a body-sized allocation, and garbage
30
- * collection lags rather than keeping them to one. Budgeting one copy halves
31
- * the real peak and lets the process admit twice the concurrency it can hold.
32
- */
33
- export const REQUEST_BODY_TRANSIENT_COPIES = 2;
34
-
35
- /** Share of the memory limit all in-flight request bodies together may hold. */
36
- export const REQUEST_BODY_BUDGET_FRACTION = 0.25;
37
-
38
- /** Share of the memory limit kept free for allocation spikes and GC slack. */
39
- export const MEMORY_HEADROOM_FRACTION = 0.2;
40
-
41
-
42
- /**
43
- * Bytes kept free below the memory limit.
44
- *
45
- * A guard that only reacts once RSS equals the limit has already lost: the
46
- * allocation that breaches it happens between two samples, and the spike that
47
- * matters — an upstream response landing all at once — arrives in far less
48
- * than one sample interval.
49
- */
50
- export function deriveMemoryHeadroomBytes(memoryLimitBytes: number): number {
51
- return Math.ceil(memoryLimitBytes * MEMORY_HEADROOM_FRACTION);
52
- }
53
-
54
-
55
- /**
56
- * Slack added on top of every key's concurrency limit.
57
- *
58
- * Operator configuration, not something a key carries about itself: it is
59
- * deliberate hidden allowance so a run that lands a few requests over the
60
- * number an operator chose is not refused at that number. The effective
61
- * ceiling is `limit + tolerance`, and only requests past it are refused.
62
- */
63
- export function resolveConcurrencyTolerance(): number {
64
- const value = readOptionalInt(
65
- "CARTETHYIA_CONCURRENCY_TOLERANCE",
66
- CONFIG_SPEC.CARTETHYIA_CONCURRENCY_TOLERANCE,
67
- );
68
- return value === undefined ? 0 : Math.max(0, value);
69
- }
70
-
1
+ /**
2
+ * Canonical resource policy: how much memory this process may use, and how
3
+ * much room it must keep free.
4
+ *
5
+ * The memory figure used to be decided in unrelated places — a host-wide
6
+ * `os.totalmem()` in the headroom guard, a fallback constant in
7
+ * `runtime-metrics.ts` — which meant a container limited to 512 MiB shed on
8
+ * the same threshold as a 4 GiB one and only reacted once the *host* was
9
+ * nearly full. On a 1-core / 2 GiB VPS that is the difference between
10
+ * shedding early and being OOM-killed, and the OOM kills the dashboard too —
11
+ * the one surface that would have explained what happened.
12
+ *
13
+ * How many requests may run at once is NOT decided here. That is admission's
14
+ * job: the tenant ceiling and each key's own limit, both set in Settings. A
15
+ * second, process-wide ceiling derived from memory sat below the number an
16
+ * operator could configure and refused requests that every configured limit
17
+ * still allowed, which is why it is gone.
18
+ */
19
+ import { CONFIG_SPEC, readOptionalInt } from "./spec";
20
+
21
+ /** Used when no limit is discoverable at all (native dev, macOS, Windows). */
22
+ export const FALLBACK_MEMORY_LIMIT_BYTES = 512 * 1024 * 1024;
23
+
24
+
25
+ /**
26
+ * How many copies of a request body are resident at once.
27
+ *
28
+ * Measured, not assumed: the socket read buffer, the parsed object graph, and
29
+ * the upstream re-serialization each hold a body-sized allocation, and garbage
30
+ * collection lags rather than keeping them to one. Budgeting one copy halves
31
+ * the real peak and lets the process admit twice the concurrency it can hold.
32
+ */
33
+ export const REQUEST_BODY_TRANSIENT_COPIES = 2;
34
+
35
+ /** Share of the memory limit all in-flight request bodies together may hold. */
36
+ export const REQUEST_BODY_BUDGET_FRACTION = 0.25;
37
+
38
+ /** Share of the memory limit kept free for allocation spikes and GC slack. */
39
+ export const MEMORY_HEADROOM_FRACTION = 0.2;
40
+
41
+
42
+ /**
43
+ * Bytes kept free below the memory limit.
44
+ *
45
+ * A guard that only reacts once RSS equals the limit has already lost: the
46
+ * allocation that breaches it happens between two samples, and the spike that
47
+ * matters — an upstream response landing all at once — arrives in far less
48
+ * than one sample interval.
49
+ */
50
+ export function deriveMemoryHeadroomBytes(memoryLimitBytes: number): number {
51
+ return Math.ceil(memoryLimitBytes * MEMORY_HEADROOM_FRACTION);
52
+ }
53
+
54
+
55
+ /**
56
+ * Slack added on top of every key's concurrency limit.
57
+ *
58
+ * Operator configuration, not something a key carries about itself: it is
59
+ * deliberate hidden allowance so a run that lands a few requests over the
60
+ * number an operator chose is not refused at that number. The effective
61
+ * ceiling is `limit + tolerance`, and only requests past it are refused.
62
+ */
63
+ export function resolveConcurrencyTolerance(): number {
64
+ const value = readOptionalInt(
65
+ "CARTETHYIA_CONCURRENCY_TOLERANCE",
66
+ CONFIG_SPEC.CARTETHYIA_CONCURRENCY_TOLERANCE,
67
+ );
68
+ return value === undefined ? 0 : Math.max(0, value);
69
+ }
70
+
@@ -1,37 +1,37 @@
1
- /**
2
- * Security resolvers: the application encryption key and the abuse-protection
3
- * ceiling.
4
- */
5
-
6
- import { CONFIG_SPEC, readInt, readRequired } from "./spec";
7
-
8
- const ENCRYPTION_KEY_LENGTH = 32;
9
-
10
- /**
11
- * Per-IP request ceiling for the unauthenticated abuse-protection layer.
12
- * Defaults to 240 requests per window so an unset env does not mean unlimited;
13
- * operators tune it via `IP_RATE_MAX_PER_WINDOW`.
14
- */
15
- export function resolveIpRateLimit(): number {
16
- return readInt("IP_RATE_MAX_PER_WINDOW", CONFIG_SPEC.IP_RATE_MAX_PER_WINDOW);
17
- }
18
-
19
- /** Reads the application encryption key without caching it. */
20
- export function requireEncryptionKeyEnv(): string {
21
- return readRequired("CARTETHYIA_ENCRYPTION_KEY", CONFIG_SPEC.CARTETHYIA_ENCRYPTION_KEY);
22
- }
23
-
24
- /** Decodes and validates a base64 or hexadecimal 256-bit encryption key. */
25
- export function decodeEncryptionKey(raw: string): Buffer {
26
- const candidate = raw.trim();
27
- const buf = /^[0-9a-fA-F]{64}$/.test(candidate)
28
- ? Buffer.from(candidate, "hex")
29
- : Buffer.from(candidate, "base64");
30
- if (buf.length !== ENCRYPTION_KEY_LENGTH) {
31
- throw new Error(
32
- `CARTETHYIA_ENCRYPTION_KEY must decode to exactly ${ENCRYPTION_KEY_LENGTH} bytes (got ${buf.length}); ` +
33
- "provide a base64 or hex encoded 256-bit key",
34
- );
35
- }
36
- return buf;
37
- }
1
+ /**
2
+ * Security resolvers: the application encryption key and the abuse-protection
3
+ * ceiling.
4
+ */
5
+
6
+ import { CONFIG_SPEC, readInt, readRequired } from "./spec";
7
+
8
+ const ENCRYPTION_KEY_LENGTH = 32;
9
+
10
+ /**
11
+ * Per-IP request ceiling for the unauthenticated abuse-protection layer.
12
+ * Defaults to 240 requests per window so an unset env does not mean unlimited;
13
+ * operators tune it via `IP_RATE_MAX_PER_WINDOW`.
14
+ */
15
+ export function resolveIpRateLimit(): number {
16
+ return readInt("IP_RATE_MAX_PER_WINDOW", CONFIG_SPEC.IP_RATE_MAX_PER_WINDOW);
17
+ }
18
+
19
+ /** Reads the application encryption key without caching it. */
20
+ export function requireEncryptionKeyEnv(): string {
21
+ return readRequired("CARTETHYIA_ENCRYPTION_KEY", CONFIG_SPEC.CARTETHYIA_ENCRYPTION_KEY);
22
+ }
23
+
24
+ /** Decodes and validates a base64 or hexadecimal 256-bit encryption key. */
25
+ export function decodeEncryptionKey(raw: string): Buffer {
26
+ const candidate = raw.trim();
27
+ const buf = /^[0-9a-fA-F]{64}$/.test(candidate)
28
+ ? Buffer.from(candidate, "hex")
29
+ : Buffer.from(candidate, "base64");
30
+ if (buf.length !== ENCRYPTION_KEY_LENGTH) {
31
+ throw new Error(
32
+ `CARTETHYIA_ENCRYPTION_KEY must decode to exactly ${ENCRYPTION_KEY_LENGTH} bytes (got ${buf.length}); ` +
33
+ "provide a base64 or hex encoded 256-bit key",
34
+ );
35
+ }
36
+ return buf;
37
+ }