cartethyia 2.7.4 → 2.7.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (553) hide show
  1. package/.env.example +2 -0
  2. package/README.md +17 -5
  3. package/dist/dashboard/assets/{BannedPage-ChG5svFb.js → BannedPage-Cj15GsMp.js} +1 -1
  4. package/dist/dashboard/assets/BannedPage-Cj15GsMp.js.gz +0 -0
  5. package/dist/dashboard/assets/ClaimGiftsButton-D83pBJ6J.js +1 -0
  6. package/dist/dashboard/assets/ClaimGiftsButton-D83pBJ6J.js.gz +0 -0
  7. package/dist/dashboard/assets/{CliToolDetailPage-BIcWQun7.js → CliToolDetailPage-DZKmVuJN.js} +1 -1
  8. package/dist/dashboard/assets/CliToolDetailPage-DZKmVuJN.js.gz +0 -0
  9. package/dist/dashboard/assets/{CliToolsPage-Bf2VelDe.js → CliToolsPage-BIccZmeL.js} +1 -1
  10. package/dist/dashboard/assets/CliToolsPage-BIccZmeL.js.gz +0 -0
  11. package/dist/dashboard/assets/CombosPage-DEEKkYD-.js +1 -0
  12. package/dist/dashboard/assets/CombosPage-DEEKkYD-.js.gz +0 -0
  13. package/dist/dashboard/assets/ConsoleLogPage-D5rol2j2.js +1 -0
  14. package/dist/dashboard/assets/ConsoleLogPage-D5rol2j2.js.gz +0 -0
  15. package/dist/dashboard/assets/{CustomizationPage-ggeMSjte.js → CustomizationPage-BKj_r0rp.js} +1 -1
  16. package/dist/dashboard/assets/CustomizationPage-BKj_r0rp.js.gz +0 -0
  17. package/dist/dashboard/assets/InjectSystemPromptPanel-B5nZvmf0.js +656 -0
  18. package/dist/dashboard/assets/InjectSystemPromptPanel-B5nZvmf0.js.gz +0 -0
  19. package/dist/dashboard/assets/{LoginPage-U6AKaJcF.js → LoginPage-DkXb6dx-.js} +1 -1
  20. package/dist/dashboard/assets/LoginPage-DkXb6dx-.js.gz +0 -0
  21. package/dist/dashboard/assets/{ModelPicker-BMS2t8BA.js → ModelPicker-DnQPSIA8.js} +1 -1
  22. package/dist/dashboard/assets/ModelPicker-DnQPSIA8.js.gz +0 -0
  23. package/dist/dashboard/assets/OverviewPage-Ci2wQKUz.js +1 -0
  24. package/dist/dashboard/assets/OverviewPage-Ci2wQKUz.js.gz +0 -0
  25. package/dist/dashboard/assets/ProviderDetailPage-odlgYrOg.js +5 -0
  26. package/dist/dashboard/assets/ProviderDetailPage-odlgYrOg.js.gz +0 -0
  27. package/dist/dashboard/assets/ProviderIcon-EfeloBn4.js +1 -0
  28. package/dist/dashboard/assets/ProviderIcon-EfeloBn4.js.gz +0 -0
  29. package/dist/dashboard/assets/ProvidersPage-CqFBQEB8.js +1 -0
  30. package/dist/dashboard/assets/ProvidersPage-CqFBQEB8.js.gz +0 -0
  31. package/dist/dashboard/assets/{ProxyPage-lsrx7NYg.js → ProxyPage-5SnUSy3j.js} +1 -1
  32. package/dist/dashboard/assets/ProxyPage-5SnUSy3j.js.gz +0 -0
  33. package/dist/dashboard/assets/QuotaPage-CmnMDzhP.js +1 -0
  34. package/dist/dashboard/assets/QuotaPage-CmnMDzhP.js.gz +0 -0
  35. package/dist/dashboard/assets/SettingsPage-BJtRZ4qD.js +1 -0
  36. package/dist/dashboard/assets/SettingsPage-BJtRZ4qD.js.gz +0 -0
  37. package/dist/dashboard/assets/{SetupPage-f0DE1h0-.js → SetupPage-WTa0sktC.js} +1 -1
  38. package/dist/dashboard/assets/SetupPage-WTa0sktC.js.gz +0 -0
  39. package/dist/dashboard/assets/{StudioPage-Cq5tW7hW.js → StudioPage-BfAb5-o_.js} +3 -3
  40. package/dist/dashboard/assets/StudioPage-BfAb5-o_.js.gz +0 -0
  41. package/dist/dashboard/assets/UsagePage-BNzeZ2Vk.js +56 -0
  42. package/dist/dashboard/assets/UsagePage-BNzeZ2Vk.js.gz +0 -0
  43. package/dist/dashboard/assets/api-keys-l29JROPv.js +1 -0
  44. package/dist/dashboard/assets/api-keys-l29JROPv.js.gz +0 -0
  45. package/dist/dashboard/assets/app--PJWq8_C.css +2 -0
  46. package/dist/dashboard/assets/app--PJWq8_C.css.gz +0 -0
  47. package/dist/dashboard/assets/assert-C0K-c4eo.js +1 -0
  48. package/dist/dashboard/assets/assert-C0K-c4eo.js.gz +0 -0
  49. package/dist/dashboard/assets/{badge-D9CS6dZk.js → badge-DfbhQU4k.js} +1 -1
  50. package/dist/dashboard/assets/badge-DfbhQU4k.js.gz +0 -0
  51. package/dist/dashboard/assets/{banner-DympBNq4.js → banner-BllT4LcF.js} +1 -1
  52. package/dist/dashboard/assets/banner-BllT4LcF.js.gz +0 -0
  53. package/dist/dashboard/assets/brand/login_register.jpg +0 -0
  54. package/dist/dashboard/assets/brand/login_register_initial.jpg +0 -0
  55. package/dist/dashboard/assets/brand/og_bansos.jpg +0 -0
  56. package/dist/dashboard/assets/brand/og_image.jpg +0 -0
  57. package/dist/dashboard/assets/{card-ibRWU_uT.js → card-CtkX66Wd.js} +1 -1
  58. package/dist/dashboard/assets/card-CtkX66Wd.js.gz +0 -0
  59. package/dist/dashboard/assets/chevron-left-Bf6CaTSJ.js +1 -0
  60. package/dist/dashboard/assets/chevron-left-Bf6CaTSJ.js.gz +0 -0
  61. package/dist/dashboard/assets/cli-tools-CQXVMn4P.js +1 -0
  62. package/dist/dashboard/assets/cli-tools-CQXVMn4P.js.gz +0 -0
  63. package/dist/dashboard/assets/{clipboard-button-DJm-9pz7.js → clipboard-button-BQqBQd2n.js} +1 -1
  64. package/dist/dashboard/assets/clipboard-button-BQqBQd2n.js.gz +0 -0
  65. package/dist/dashboard/assets/{dialog-8fiE6tec.js → dialog-BEK7KP0H.js} +1 -1
  66. package/dist/dashboard/assets/dialog-BEK7KP0H.js.gz +0 -0
  67. package/dist/dashboard/assets/{drawer-CqGYeOrd.js → drawer-BrTEZ8zu.js} +1 -1
  68. package/dist/dashboard/assets/drawer-BrTEZ8zu.js.gz +0 -0
  69. package/dist/dashboard/assets/entry-CHWxbS56.js +1 -0
  70. package/dist/dashboard/assets/entry-CHWxbS56.js.gz +0 -0
  71. package/dist/dashboard/assets/{entry-BSAsYsmV.js → entry-EBHJ3DrE.js} +1 -1
  72. package/dist/dashboard/assets/entry-EBHJ3DrE.js.gz +0 -0
  73. package/dist/dashboard/assets/entry-LCS85hiq.js +4 -0
  74. package/dist/dashboard/assets/entry-LCS85hiq.js.gz +0 -0
  75. package/dist/dashboard/assets/{formatters-D-MTJxMr.js → formatters-BAACA727.js} +1 -1
  76. package/dist/dashboard/assets/formatters-BAACA727.js.gz +0 -0
  77. package/dist/dashboard/assets/{github-badge-CPK47xQx.js → github-badge-YSCE4UC2.js} +1 -1
  78. package/dist/dashboard/assets/github-badge-YSCE4UC2.js.gz +0 -0
  79. package/dist/dashboard/assets/{index-DBNPOfhV.js → index-FQlj0HUT.js} +2 -2
  80. package/dist/dashboard/assets/index-FQlj0HUT.js.gz +0 -0
  81. package/dist/dashboard/assets/{inline-BT0zntEr.js → inline-C4gU5iOL.js} +1 -1
  82. package/dist/dashboard/assets/inline-C4gU5iOL.js.gz +0 -0
  83. package/dist/dashboard/assets/{input-Da2ZViWg.js → input-BHHX_Tmm.js} +1 -1
  84. package/dist/dashboard/assets/input-BHHX_Tmm.js.gz +0 -0
  85. package/dist/dashboard/assets/{layout-BXNeLw6M.js → layout-CajrYUnD.js} +1 -1
  86. package/dist/dashboard/assets/layout-CajrYUnD.js.gz +0 -0
  87. package/dist/dashboard/assets/{list-row-BycOrrIV.js → list-row-Bx2I7XUe.js} +1 -1
  88. package/dist/dashboard/assets/list-row-Bx2I7XUe.js.gz +0 -0
  89. package/dist/dashboard/assets/live-DNNqRbj2.js +1 -0
  90. package/dist/dashboard/assets/live-DNNqRbj2.js.gz +0 -0
  91. package/dist/dashboard/assets/network-C5UBACrJ.js +1 -0
  92. package/dist/dashboard/assets/network-C5UBACrJ.js.gz +0 -0
  93. package/dist/dashboard/assets/numbers-CUWAZK4J.js +2 -0
  94. package/dist/dashboard/assets/numbers-CUWAZK4J.js.gz +0 -0
  95. package/dist/dashboard/assets/pagination-Xsi9Qx04.js +1 -0
  96. package/dist/dashboard/assets/pagination-Xsi9Qx04.js.gz +0 -0
  97. package/dist/dashboard/assets/quota-DvQbD_sr.js +1 -0
  98. package/dist/dashboard/assets/quota-DvQbD_sr.js.gz +0 -0
  99. package/dist/dashboard/assets/routing-in0f_IfK.js +1 -0
  100. package/dist/dashboard/assets/routing-in0f_IfK.js.gz +0 -0
  101. package/dist/dashboard/assets/{select-BIYIBkMU.js → select-BLULNqTp.js} +1 -1
  102. package/dist/dashboard/assets/select-BLULNqTp.js.gz +0 -0
  103. package/dist/dashboard/assets/settings-CcGczj6I.js +1 -0
  104. package/dist/dashboard/assets/settings-CcGczj6I.js.gz +0 -0
  105. package/dist/dashboard/assets/shield-BMQNxORh.js +1 -0
  106. package/dist/dashboard/assets/shield-BMQNxORh.js.gz +0 -0
  107. package/dist/dashboard/assets/{stack-DfgnFDWY.js → stack-D2lIR0tg.js} +1 -1
  108. package/dist/dashboard/assets/stack-D2lIR0tg.js.gz +0 -0
  109. package/dist/dashboard/assets/{state-CkfdYWel.js → state-DTARjfiW.js} +1 -1
  110. package/dist/dashboard/assets/state-DTARjfiW.js.gz +0 -0
  111. package/dist/dashboard/assets/{switch-SYm237HG.js → switch-c0oPSnqW.js} +1 -1
  112. package/dist/dashboard/assets/switch-c0oPSnqW.js.gz +0 -0
  113. package/dist/dashboard/assets/{useMutation-BQqCRNIi.js → useMutation-m8WGNPrz.js} +1 -1
  114. package/dist/dashboard/assets/useMutation-m8WGNPrz.js.gz +0 -0
  115. package/dist/dashboard/console-backgrounds.jpg +0 -0
  116. package/dist/dashboard/console-backgrounds_initial.jpg +0 -0
  117. package/dist/dashboard/index.html +2 -2
  118. package/dist/dashboard/index.html.gz +0 -0
  119. package/dist/dashboard/precompressed-assets.json +56 -55
  120. package/migrations/full-postgres/0036_system_prompt_presets_refs.sql +10 -0
  121. package/migrations/full-postgres/0037_share_concurrency_scope.sql +3 -0
  122. package/migrations/full-postgres/0038_add_accounts_quota_snapshot.sql +7 -0
  123. package/migrations/full-postgres/0039_add_telemetry_provider_model_indexes.sql +13 -0
  124. package/migrations/full-postgres/0040_add_models_dialect.sql +3 -0
  125. package/migrations/full-postgres/0041_add_daily_checkin_ledger_account_fk.sql +7 -0
  126. package/migrations/lite-sqlite3/0036_system_prompt_presets_refs.sql +14 -0
  127. package/migrations/lite-sqlite3/0037_share_concurrency_scope.sql +3 -0
  128. package/migrations/lite-sqlite3/0038_add_accounts_quota_snapshot.sql +4 -0
  129. package/migrations/lite-sqlite3/0039_add_telemetry_provider_model_indexes.sql +6 -0
  130. package/migrations/lite-sqlite3/0040_add_models_dialect.sql +3 -0
  131. package/migrations/lite-sqlite3/0041_add_daily_checkin_ledger_account_fk.sql +26 -0
  132. package/package.json +4 -6
  133. package/scripts/cli/update.ts +15 -41
  134. package/src/boot/app.ts +58 -2
  135. package/src/boot/dependencies.ts +786 -732
  136. package/src/boot/lifecycle.ts +11 -12
  137. package/src/config/cooldowns.ts +34 -55
  138. package/src/config/network.ts +1 -1
  139. package/src/config/package-paths.ts +1 -1
  140. package/src/config/providers.ts +13 -14
  141. package/src/config/resources.ts +0 -5
  142. package/src/config/runtime.ts +2 -5
  143. package/src/config/spec.ts +26 -6
  144. package/src/console/api-keys/ban-constants.ts +38 -0
  145. package/src/console/api-keys/contracts.ts +71 -62
  146. package/src/console/api-keys/routes.ts +48 -12
  147. package/src/console/auth/access.ts +4 -4
  148. package/src/console/auth/account-reset.ts +1 -1
  149. package/src/console/auth/audit-recorder.ts +1 -1
  150. package/src/console/auth/lockout.ts +7 -3
  151. package/src/console/auth/row-coercion.ts +57 -12
  152. package/src/console/auth/session-resolver.ts +1 -1
  153. package/src/console/auth/session-service.ts +8 -2
  154. package/src/console/auth/session.ts +37 -31
  155. package/src/console/auth/setup-service.ts +4 -3
  156. package/src/console/auth/user-store.ts +1 -1
  157. package/src/console/backup/9router-map.ts +0 -3
  158. package/src/console/backup/contracts.ts +9 -9
  159. package/src/console/backup/export-endpoint.ts +5 -6
  160. package/src/console/backup/nine-router.ts +1 -7
  161. package/src/console/backup/routes.ts +29 -33
  162. package/src/console/backup/service.ts +468 -468
  163. package/src/console/backup/store.ts +47 -29
  164. package/src/console/backup/validate.ts +11 -1
  165. package/src/console/cli-tools/contracts.ts +7 -7
  166. package/src/console/cli-tools/injectors/cline.ts +110 -110
  167. package/src/console/cli-tools/injectors/codex.ts +181 -181
  168. package/src/console/cli-tools/injectors/copilot.ts +108 -108
  169. package/src/console/cli-tools/injectors/driver.ts +247 -247
  170. package/src/console/cli-tools/injectors/jcode.ts +115 -115
  171. package/src/console/cli-tools/injectors/openclaw.ts +161 -161
  172. package/src/console/cli-tools/injectors/opencode.ts +153 -153
  173. package/src/console/cli-tools/routes.ts +1 -1
  174. package/src/console/cli-tools/service.ts +1 -1
  175. package/src/console/cli-tools/store.ts +2 -2
  176. package/src/console/console-router.ts +32 -0
  177. package/src/console/dashboard-assets.ts +9 -9
  178. package/src/console/domain-registration.ts +16 -2
  179. package/src/console/inferhub/client.ts +0 -7
  180. package/src/console/inferhub/key-store.ts +1 -1
  181. package/src/console/inferhub/normalize.ts +2 -2
  182. package/src/console/inferhub/routes.ts +1 -1
  183. package/src/console/inferhub/types.ts +1 -1
  184. package/src/console/observability/contracts.ts +1 -1
  185. package/src/console/observability/live.ts +31 -18
  186. package/src/console/observability/logs.ts +1 -1
  187. package/src/console/observability/sse.ts +62 -8
  188. package/src/console/observability/store.ts +20 -4
  189. package/src/console/providers/catalog/catalog-projections.ts +1 -1
  190. package/src/console/providers/catalog/contracts.ts +39 -0
  191. package/src/console/providers/catalog/model-operations.ts +1 -1
  192. package/src/console/providers/catalog/provider-operations.ts +95 -17
  193. package/src/console/providers/catalog/public-model-store.ts +0 -0
  194. package/src/console/providers/catalog/routes.ts +27 -3
  195. package/src/console/providers/catalog/store.ts +147 -8
  196. package/src/console/providers/detail/routes.ts +1 -1
  197. package/src/console/providers/oauth/callback-listener.ts +100 -18
  198. package/src/console/providers/oauth/routes.ts +92 -13
  199. package/src/console/quota/account-quota-global.ts +41 -3
  200. package/src/console/quota/account-quota-shared.ts +10 -3
  201. package/src/console/quota/account-quota-tenant.ts +259 -66
  202. package/src/console/quota/daily-growth-job.ts +308 -0
  203. package/src/console/quota/native-identity.ts +2 -2
  204. package/src/console/quota/quota-view.ts +1 -1
  205. package/src/console/remote-support/routes.ts +1 -1
  206. package/src/console/remote-support/service.ts +2 -2
  207. package/src/console/routing/model/contracts.ts +1 -1
  208. package/src/console/routing/pools/contracts.ts +2 -2
  209. package/src/console/routing/pools/import-parse.ts +3 -3
  210. package/src/console/routing/pools/relay-deploy.ts +3 -3
  211. package/src/console/settings/contracts.ts +29 -2
  212. package/src/console/settings/store.ts +20 -0
  213. package/src/console/share/share-router.ts +100 -32
  214. package/src/console/share/share-stats.ts +4 -4
  215. package/src/console/share/share-usage.ts +1 -1
  216. package/src/console/shared/errors.ts +2 -2
  217. package/src/console/shared/object.ts +18 -0
  218. package/src/console/shared/read-through-cache.ts +4 -4
  219. package/src/console/shared/transaction.ts +14 -0
  220. package/src/console/system-prompt/contracts.ts +89 -12
  221. package/src/console/system-prompt/routes.ts +98 -45
  222. package/src/console/updates/apply-update.ts +244 -0
  223. package/src/console/updates/routes.ts +252 -97
  224. package/src/main.ts +11 -5
  225. package/src/network/http2-bidi.ts +1 -1
  226. package/src/network/ip-utils.ts +298 -0
  227. package/src/network/outbound-fetch.ts +1 -1
  228. package/src/network/pool/agent.ts +7 -6
  229. package/src/network/pool/byte-accounting.ts +1 -1
  230. package/src/network/pool/resolver.ts +16 -12
  231. package/src/network/pool/selector.ts +150 -136
  232. package/src/network/pool-health-machine.ts +1 -1
  233. package/src/network/rate-limit-verdict.ts +1 -1
  234. package/src/network/retry.ts +1 -1
  235. package/src/network/ssrf.ts +20 -71
  236. package/src/observability/event-log.ts +2 -2
  237. package/src/observability/event-query.ts +59 -291
  238. package/src/observability/log-sanitize.ts +1 -1
  239. package/src/observability/metrics.ts +4 -4
  240. package/src/observability/payload-capture.ts +6 -5
  241. package/src/observability/payload-store.ts +1 -1
  242. package/src/observability/runtime-metrics.ts +1 -1
  243. package/src/observability/telemetry-buffer.ts +12 -3
  244. package/src/observability/telemetry-reconciliation.ts +3 -3
  245. package/src/observability/token-speed.ts +1 -1
  246. package/src/persistence/connection-url.ts +1 -1
  247. package/src/persistence/db-handle.ts +1 -1
  248. package/src/persistence/db-pg.ts +2 -2
  249. package/src/persistence/db-sqlite.ts +3 -3
  250. package/src/persistence/dialect.ts +5 -1
  251. package/src/persistence/readiness.ts +1 -1
  252. package/src/persistence/redis.ts +1 -1
  253. package/src/persistence/schema.ts +113 -40
  254. package/src/persistence/shared-cache.ts +2 -2
  255. package/src/persistence/sort-order.ts +1 -1
  256. package/src/persistence/stores/api-key-store.ts +10 -1
  257. package/src/persistence/stores/share-store.ts +20 -8
  258. package/src/persistence/system-prompt-store.ts +63 -18
  259. package/src/persistence/tenant-preferences.ts +1 -1
  260. package/src/persistence/ttl-cache.ts +3 -3
  261. package/src/persistence/usage-flush.ts +7 -2
  262. package/src/protocol/canonical-model.ts +1 -1
  263. package/src/protocol/codecs.ts +6 -10
  264. package/src/protocol/dsml.ts +3 -49
  265. package/src/protocol/image-clamp.ts +2 -2
  266. package/src/protocol/reasoning-text.ts +1 -1
  267. package/src/protocol/reasoning.ts +7 -52
  268. package/src/protocol/registry.ts +50 -15
  269. package/src/protocol/request/chat.ts +46 -19
  270. package/src/protocol/request/codex.ts +1 -1
  271. package/src/protocol/request/messages.ts +46 -14
  272. package/src/protocol/request/responses.ts +19 -10
  273. package/src/protocol/response/chat.ts +25 -10
  274. package/src/protocol/response/codex.ts +1 -1
  275. package/src/protocol/response/messages.ts +1 -1
  276. package/src/protocol/response/omp-tool-call-markup.ts +1 -1
  277. package/src/protocol/response/responses.ts +1 -1
  278. package/src/protocol/sse-decoder.ts +50 -1
  279. package/src/protocol/stop-reason.ts +2 -2
  280. package/src/protocol/think-split.ts +347 -0
  281. package/src/protocol/translation/attachment-integrity.ts +3 -3
  282. package/src/protocol/translation/cache-controls.ts +8 -20
  283. package/src/protocol/translation/capabilities.ts +6 -6
  284. package/src/protocol/translation/dialect/deepseek.ts +10 -0
  285. package/src/protocol/translation/dialect/inband.ts +39 -0
  286. package/src/protocol/translation/dialect/model-thinking.ts +89 -0
  287. package/src/protocol/translation/dialect/qwen.ts +114 -0
  288. package/src/protocol/translation/dialect/resolver.ts +83 -0
  289. package/src/protocol/translation/dialect/rules.ts +93 -0
  290. package/src/protocol/translation/dialect/thinking-ladder.ts +245 -0
  291. package/src/protocol/translation/dialect/types.ts +58 -0
  292. package/src/protocol/translation/quirks.ts +18 -4
  293. package/src/protocol/translation/thinking.ts +12 -252
  294. package/src/protocol/translation/tool-id.ts +3 -3
  295. package/src/protocol/translation/tool-repair.ts +1 -1
  296. package/src/protocol/upstream/post-json.ts +6 -1
  297. package/src/providers/adapters/compatible-adapter.ts +42 -3
  298. package/src/providers/authentication/jwt-validator.ts +0 -5
  299. package/src/providers/authentication/oauth-client.ts +254 -253
  300. package/src/providers/authentication/oauth-flow-store.ts +42 -7
  301. package/src/providers/bundled-discovery-providers.ts +6 -30
  302. package/src/providers/configured-openai-providers.ts +82 -97
  303. package/src/providers/default-registry.ts +23 -35
  304. package/src/providers/discovery/catalog/catalog-format.ts +13 -2
  305. package/src/providers/discovery/discovery-types.ts +0 -1
  306. package/src/providers/discovery/openai-model-discovery.ts +2 -2
  307. package/src/providers/discovery/probe-phases.ts +48 -13
  308. package/src/providers/discovery/probing-service.ts +6 -8
  309. package/src/providers/integrations/agentrouter/agentrouter.ts +12 -2
  310. package/src/providers/integrations/antigravity/antigravity-oauth.ts +0 -1
  311. package/src/providers/integrations/antigravity/antigravity-protocol.ts +0 -14
  312. package/src/providers/integrations/antigravity/antigravity.ts +2 -2
  313. package/src/providers/integrations/buddy/buddy-catalog-shared.ts +1 -1
  314. package/src/providers/integrations/buddy/buddy-chat-shared.ts +20 -4
  315. package/src/providers/integrations/buddy/buddy-cli-headers.ts +19 -52
  316. package/src/providers/integrations/buddy/buddy-identity.ts +86 -0
  317. package/src/providers/integrations/buddy/buddy-oauth-shared.ts +1 -1
  318. package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +1 -2
  319. package/src/providers/integrations/buddy/codebuddy/provider.ts +4 -15
  320. package/src/providers/integrations/buddy/codebuddy/shared.ts +30 -9
  321. package/src/providers/integrations/buddy/workbuddy/provider.ts +5 -9
  322. package/src/providers/integrations/buddy/workbuddy/shared.ts +21 -3
  323. package/src/providers/integrations/claude/claude-cch.ts +148 -148
  324. package/src/providers/integrations/claude/fallback-credit.ts +3 -7
  325. package/src/providers/integrations/cline/cline.ts +1 -1
  326. package/src/providers/integrations/codex/codex-device-code.ts +101 -101
  327. package/src/providers/integrations/codex/codex-oauth.ts +0 -1
  328. package/src/providers/integrations/commandcode/commandcode.ts +75 -115
  329. package/src/providers/integrations/commandcode/quota.ts +131 -51
  330. package/src/providers/integrations/cursor/cursor-http2.ts +0 -3
  331. package/src/providers/integrations/cursor/cursor-quota.ts +2 -6
  332. package/src/providers/integrations/cursor/cursor-request.ts +0 -87
  333. package/src/providers/integrations/cursor/cursor-shared.ts +0 -4
  334. package/src/providers/integrations/cursor/cursor.ts +38 -5
  335. package/src/providers/integrations/cursor/protocol/framing.ts +0 -56
  336. package/src/providers/integrations/cursor/protocol/messages.ts +0 -96
  337. package/src/providers/integrations/factory/factory.ts +9 -9
  338. package/src/providers/integrations/github/github-discovery.ts +55 -10
  339. package/src/providers/integrations/github/github-oauth.ts +6 -5
  340. package/src/providers/integrations/github/github.ts +41 -14
  341. package/src/providers/integrations/grok/grok-oauth.ts +261 -261
  342. package/src/providers/integrations/grok/grok-quota-frame.ts +1 -1
  343. package/src/providers/integrations/grok/grok-quota.ts +16 -7
  344. package/src/providers/integrations/grok/grok-ratelimit-store.ts +0 -5
  345. package/src/providers/integrations/grok/grok-ratelimit.ts +3 -0
  346. package/src/providers/integrations/grok/grok.ts +86 -36
  347. package/src/providers/integrations/kilo/kilo-oauth.ts +1 -1
  348. package/src/providers/integrations/kiro/kiro-cache.ts +31 -0
  349. package/src/providers/integrations/kiro/kiro-catalog.ts +31 -5
  350. package/src/providers/integrations/kiro/kiro-discovery.ts +47 -43
  351. package/src/providers/integrations/kiro/kiro-machine-id.ts +6 -3
  352. package/src/providers/integrations/kiro/kiro-oauth.ts +523 -255
  353. package/src/providers/integrations/kiro/kiro-quota.ts +35 -47
  354. package/src/providers/integrations/kiro/kiro-request.ts +8 -11
  355. package/src/providers/integrations/kiro/kiro-stream.ts +13 -0
  356. package/src/providers/integrations/kiro/kiro.ts +81 -68
  357. package/src/providers/integrations/muse/muse-oauth.ts +0 -1
  358. package/src/providers/integrations/muse/muse-quota.ts +0 -4
  359. package/src/providers/integrations/opencode/fingerprint.ts +7 -5
  360. package/src/providers/integrations/opencode/provider.ts +17 -24
  361. package/src/providers/integrations/qoder/qoder-catalog.ts +85 -8
  362. package/src/providers/integrations/qoder/qoder-oauth.ts +165 -25
  363. package/src/providers/integrations/qoder/qoder-quota.ts +48 -6
  364. package/src/providers/integrations/qoder/qoder-realm.ts +10 -31
  365. package/src/providers/integrations/qoder/qoder-sse.ts +159 -0
  366. package/src/providers/integrations/qoder/qoder-umid.ts +21 -25
  367. package/src/providers/integrations/qoder/qoder-virtual.ts +12 -0
  368. package/src/providers/integrations/qoder/qoder.ts +354 -62
  369. package/src/providers/integrations/shared/connect.ts +0 -54
  370. package/src/providers/integrations/shared/hash.ts +15 -0
  371. package/src/providers/integrations/shared/session-cache.ts +136 -0
  372. package/src/providers/integrations/trae/trae-cache.ts +7 -87
  373. package/src/providers/integrations/trae/trae-catalog.ts +1 -6
  374. package/src/providers/integrations/trae/trae-protocol.ts +17 -8
  375. package/src/providers/integrations/trae/trae-realm.ts +0 -7
  376. package/src/providers/integrations/trae/trae.ts +53 -10
  377. package/src/providers/integrations/xai/xai-oauth.ts +5 -5
  378. package/src/providers/integrations/zcode-start-plan/gifts.ts +265 -0
  379. package/src/providers/integrations/zcode-start-plan/oauth.ts +154 -0
  380. package/src/providers/integrations/zcode-start-plan/provider.ts +131 -0
  381. package/src/providers/integrations/zcode-start-plan/quota.ts +152 -0
  382. package/src/providers/integrations/zcode-start-plan/zcode_prompt.json +10 -0
  383. package/src/providers/operations/account-error-classification.ts +131 -116
  384. package/src/providers/operations/account-health-service.ts +31 -39
  385. package/src/providers/operations/account-reset-service.ts +58 -1
  386. package/src/providers/operations/cli-platform.ts +31 -5
  387. package/src/providers/operations/client-version-resolver.ts +4 -6
  388. package/src/providers/operations/client-versions.ts +10 -7
  389. package/src/providers/operations/provider-catalog-seeder.ts +2 -0
  390. package/src/providers/operations/provider-catalog-service.ts +18 -2
  391. package/src/providers/operations/provider-credential-service.ts +19 -2
  392. package/src/providers/provider-contracts.ts +18 -7
  393. package/src/providers/provider-metadata.ts +17 -2
  394. package/src/providers/provider-model-definition.ts +22 -1
  395. package/src/providers/provider-probe.ts +4 -2
  396. package/src/providers/provider-usage.ts +81 -16
  397. package/src/providers/quota/cache.ts +29 -0
  398. package/src/providers/quota/quota-contracts.ts +4 -0
  399. package/src/providers/quota/refresh.ts +27 -4
  400. package/src/providers/quota/snapshot.ts +94 -0
  401. package/src/providers/search/search-provider.ts +1 -3
  402. package/src/providers/search/search-providers.ts +2 -1
  403. package/src/security/abuse.ts +4 -4
  404. package/src/security/admission/service.ts +2 -2
  405. package/src/security/admission/ttl.ts +61 -61
  406. package/src/security/api-key-auth.ts +16 -4
  407. package/src/security/client-router-fingerprint.ts +4 -4
  408. package/src/security/credential-codec.ts +3 -3
  409. package/src/security/csrf.ts +2 -2
  410. package/src/security/ip-boundary.ts +16 -189
  411. package/src/security/model-access-rule.ts +1 -1
  412. package/src/security/outbound-headers.ts +18 -0
  413. package/src/shared/invoke-guard.ts +47 -0
  414. package/src/transport/contracts.ts +13 -4
  415. package/src/transport/dispatch/attempt-finalize.ts +1 -1
  416. package/src/transport/dispatch/capture-policy.ts +34 -37
  417. package/src/transport/dispatch/empty-stream.ts +23 -20
  418. package/src/transport/dispatch/generation.ts +25 -45
  419. package/src/transport/dispatch/native-shared.ts +127 -0
  420. package/src/transport/dispatch/proxy-request.ts +3 -1
  421. package/src/transport/dispatch/responses-compact.ts +25 -46
  422. package/src/transport/dispatch/streaming/encoder.ts +18 -72
  423. package/src/transport/dispatch/streaming/finalize.ts +2 -2
  424. package/src/transport/dispatch/streaming/stream.ts +7 -3
  425. package/src/transport/dispatch/streaming/thinking-loop.ts +1 -1
  426. package/src/transport/dispatch/systemone.ts +20 -48
  427. package/src/transport/dispatch/token-count.ts +41 -38
  428. package/src/transport/dispatch/websearch.ts +19 -45
  429. package/src/transport/drain-endpoint.ts +1 -1
  430. package/src/transport/failure-policy.ts +6 -0
  431. package/src/transport/guards/auth-guard.ts +0 -6
  432. package/src/transport/guards/body-policy.ts +3 -3
  433. package/src/transport/guards/headroom-guard.ts +1 -1
  434. package/src/transport/guards/pipeline.ts +1 -1
  435. package/src/transport/guards/request-context.ts +12 -17
  436. package/src/transport/request/preparer.ts +286 -7
  437. package/src/transport/request/rtk/compress-request.ts +3 -3
  438. package/src/transport/request/rtk/rtk-constants.ts +2 -2
  439. package/src/transport/request/rtk/rtk-filters.ts +8 -8
  440. package/src/transport/request/state.ts +1 -13
  441. package/src/transport/request/system-prompt-injection.ts +74 -11
  442. package/src/transport/request/tenant-preferences.ts +22 -0
  443. package/src/transport/routing/candidate-index.ts +2 -2
  444. package/src/transport/routing/round-robin.ts +1 -1
  445. package/src/transport/routing/route-catalog.ts +17 -18
  446. package/src/transport/routing/route-model.ts +126 -33
  447. package/src/transport/routing/router.ts +152 -37
  448. package/src/transport/shutdown-notice.ts +2 -2
  449. package/src/transport/surface/adapters.ts +3 -3
  450. package/src/transport/surface/chat/encode.ts +7 -7
  451. package/src/transport/surface/chat/parse.ts +7 -7
  452. package/src/transport/surface/completion.ts +2 -2
  453. package/src/transport/surface/content-parts.ts +1 -1
  454. package/src/transport/surface/dialects.ts +2 -2
  455. package/src/transport/surface/messages/parse.ts +3 -3
  456. package/src/transport/surface/responses/adapter.ts +1 -1
  457. package/src/transport/surface/responses/errors.ts +0 -9
  458. package/src/transport/vision-handoff-cache.ts +58 -0
  459. package/src/transport/vision-handoff.ts +376 -0
  460. package/src/workers/account-identity-sweep.ts +2 -2
  461. package/src/workers/buddy-growth-tasks.ts +392 -346
  462. package/src/workers/daily-checkin.ts +102 -49
  463. package/src/workers/maintenance-lease.ts +2 -35
  464. package/src/workers/oauth-refresh-sweep.ts +8 -1
  465. package/src/workers/quota-refresh-sweep.ts +120 -82
  466. package/src/workers/scheduler.ts +67 -4
  467. package/src/workers/sweep-runner.ts +24 -5
  468. package/src/workers/zcode-gift-claim.ts +108 -0
  469. package/dist/dashboard/assets/AccountCooldown-Bjo92Qat.js +0 -1
  470. package/dist/dashboard/assets/AccountCooldown-Bjo92Qat.js.gz +0 -0
  471. package/dist/dashboard/assets/BannedPage-ChG5svFb.js.gz +0 -0
  472. package/dist/dashboard/assets/CliToolDetailPage-BIcWQun7.js.gz +0 -0
  473. package/dist/dashboard/assets/CliToolsPage-Bf2VelDe.js.gz +0 -0
  474. package/dist/dashboard/assets/CombosPage-DCgm5g6g.js +0 -1
  475. package/dist/dashboard/assets/CombosPage-DCgm5g6g.js.gz +0 -0
  476. package/dist/dashboard/assets/ConsoleLogPage-a8mD0QF1.js +0 -1
  477. package/dist/dashboard/assets/ConsoleLogPage-a8mD0QF1.js.gz +0 -0
  478. package/dist/dashboard/assets/CustomizationPage-ggeMSjte.js.gz +0 -0
  479. package/dist/dashboard/assets/HealthEventsModal-DzSWzE_j.js +0 -1
  480. package/dist/dashboard/assets/HealthEventsModal-DzSWzE_j.js.gz +0 -0
  481. package/dist/dashboard/assets/LoginPage-U6AKaJcF.js.gz +0 -0
  482. package/dist/dashboard/assets/ModelPicker-BMS2t8BA.js.gz +0 -0
  483. package/dist/dashboard/assets/OverviewPage-D2D4IiHl.js +0 -1
  484. package/dist/dashboard/assets/OverviewPage-D2D4IiHl.js.gz +0 -0
  485. package/dist/dashboard/assets/ProviderDetailPage-CrNf7j_X.js +0 -5
  486. package/dist/dashboard/assets/ProviderDetailPage-CrNf7j_X.js.gz +0 -0
  487. package/dist/dashboard/assets/ProviderIcon-FB6yS4v1.js +0 -1
  488. package/dist/dashboard/assets/ProviderIcon-FB6yS4v1.js.gz +0 -0
  489. package/dist/dashboard/assets/ProvidersPage-BSDbquc1.js +0 -1
  490. package/dist/dashboard/assets/ProvidersPage-BSDbquc1.js.gz +0 -0
  491. package/dist/dashboard/assets/ProxyPage-lsrx7NYg.js.gz +0 -0
  492. package/dist/dashboard/assets/QuotaPage-L0skzML8.js +0 -1
  493. package/dist/dashboard/assets/QuotaPage-L0skzML8.js.gz +0 -0
  494. package/dist/dashboard/assets/SettingsPage-CmAbjRHW.js +0 -1
  495. package/dist/dashboard/assets/SettingsPage-CmAbjRHW.js.gz +0 -0
  496. package/dist/dashboard/assets/SetupPage-f0DE1h0-.js.gz +0 -0
  497. package/dist/dashboard/assets/SortableList-ClzpwyET.js +0 -1
  498. package/dist/dashboard/assets/SortableList-ClzpwyET.js.gz +0 -0
  499. package/dist/dashboard/assets/StudioPage-Cq5tW7hW.js.gz +0 -0
  500. package/dist/dashboard/assets/UsagePage-Dfc9rWQn.js +0 -56
  501. package/dist/dashboard/assets/UsagePage-Dfc9rWQn.js.gz +0 -0
  502. package/dist/dashboard/assets/api-keys-C38iXoO4.js +0 -1
  503. package/dist/dashboard/assets/api-keys-C38iXoO4.js.gz +0 -0
  504. package/dist/dashboard/assets/app-a159k0ub.css +0 -2
  505. package/dist/dashboard/assets/app-a159k0ub.css.gz +0 -0
  506. package/dist/dashboard/assets/assert-DbRA8EG2.js +0 -15
  507. package/dist/dashboard/assets/assert-DbRA8EG2.js.gz +0 -0
  508. package/dist/dashboard/assets/badge-D9CS6dZk.js.gz +0 -0
  509. package/dist/dashboard/assets/banner-DympBNq4.js.gz +0 -0
  510. package/dist/dashboard/assets/card-ibRWU_uT.js.gz +0 -0
  511. package/dist/dashboard/assets/cli-tools-63qBGaRw.js +0 -1
  512. package/dist/dashboard/assets/cli-tools-63qBGaRw.js.gz +0 -0
  513. package/dist/dashboard/assets/clipboard-button-DJm-9pz7.js.gz +0 -0
  514. package/dist/dashboard/assets/dialog-8fiE6tec.js.gz +0 -0
  515. package/dist/dashboard/assets/drawer-CqGYeOrd.js.gz +0 -0
  516. package/dist/dashboard/assets/entry-BSAsYsmV.js.gz +0 -0
  517. package/dist/dashboard/assets/entry-CiIQULZD.js +0 -4
  518. package/dist/dashboard/assets/entry-CiIQULZD.js.gz +0 -0
  519. package/dist/dashboard/assets/entry-Daz0lJ-W.js +0 -1
  520. package/dist/dashboard/assets/entry-Daz0lJ-W.js.gz +0 -0
  521. package/dist/dashboard/assets/formatters-D-MTJxMr.js.gz +0 -0
  522. package/dist/dashboard/assets/github-badge-CPK47xQx.js.gz +0 -0
  523. package/dist/dashboard/assets/index-DBNPOfhV.js.gz +0 -0
  524. package/dist/dashboard/assets/inline-BT0zntEr.js.gz +0 -0
  525. package/dist/dashboard/assets/input-Da2ZViWg.js.gz +0 -0
  526. package/dist/dashboard/assets/layout-BXNeLw6M.js.gz +0 -0
  527. package/dist/dashboard/assets/list-row-BycOrrIV.js.gz +0 -0
  528. package/dist/dashboard/assets/live-DCQNZpF7.js +0 -1
  529. package/dist/dashboard/assets/live-DCQNZpF7.js.gz +0 -0
  530. package/dist/dashboard/assets/network-9Yj2ZyLo.js +0 -1
  531. package/dist/dashboard/assets/network-9Yj2ZyLo.js.gz +0 -0
  532. package/dist/dashboard/assets/numbers-CEagj6ZZ.js +0 -2
  533. package/dist/dashboard/assets/numbers-CEagj6ZZ.js.gz +0 -0
  534. package/dist/dashboard/assets/quota-Caw7wqci.js +0 -1
  535. package/dist/dashboard/assets/quota-Caw7wqci.js.gz +0 -0
  536. package/dist/dashboard/assets/routing-CO9zCUus.js +0 -1
  537. package/dist/dashboard/assets/routing-CO9zCUus.js.gz +0 -0
  538. package/dist/dashboard/assets/select-BIYIBkMU.js.gz +0 -0
  539. package/dist/dashboard/assets/settings-jS3-PNSA.js +0 -1
  540. package/dist/dashboard/assets/settings-jS3-PNSA.js.gz +0 -0
  541. package/dist/dashboard/assets/stack-DfgnFDWY.js.gz +0 -0
  542. package/dist/dashboard/assets/state-CkfdYWel.js.gz +0 -0
  543. package/dist/dashboard/assets/stream-retry-5QnVuSSo.js +0 -1
  544. package/dist/dashboard/assets/stream-retry-5QnVuSSo.js.gz +0 -0
  545. package/dist/dashboard/assets/switch-SYm237HG.js.gz +0 -0
  546. package/dist/dashboard/assets/useMutation-BQqCRNIi.js.gz +0 -0
  547. package/src/network/response-headers.ts +0 -39
  548. package/src/protocol/response/dsml.ts +0 -38
  549. package/src/providers/integrations/xiaomi-mimo/think-stream.ts +0 -229
  550. /package/dist/dashboard/assets/{app-BayTTy01.js → app-Bgd3zANQ.js} +0 -0
  551. /package/dist/dashboard/assets/{app-BayTTy01.js.gz → app-Bgd3zANQ.js.gz} +0 -0
  552. /package/dist/dashboard/assets/brand/{og_bansos.webp → og_bansos_initial.webp} +0 -0
  553. /package/dist/dashboard/assets/brand/{og_image.webp → og_image_initial.webp} +0 -0
@@ -11,7 +11,7 @@ import { generateShareSlug, hashShareToken } from "../../persistence/stores/shar
11
11
  import type { AccessDecision } from "../../security/access-control";
12
12
  import { ConsoleDomainError, errorResponse, requireTenantScope } from "../shared/errors";
13
13
  import { literalUnion } from "../shared/elysia-schema";
14
- import { API_KEY_MODES, API_KEY_MODEL_ACCESS_MODES, type ShareLinkKind } from "../../persistence/schema";
14
+ import { API_KEY_MODES, API_KEY_MODEL_ACCESS_MODES, API_KEY_CONCURRENCY_SCOPES, type ShareLinkKind } from "../../persistence/schema";
15
15
  import type { ApiKeyConfig, ApiKeyResponse, BanApiKeyRequest, UpdateApiKeyResponse } from "./contracts";
16
16
  import type {
17
17
  ApiKeyPatch,
@@ -20,6 +20,7 @@ import type {
20
20
  SharedChildrenPageOptions,
21
21
  } from "../../persistence/stores/api-key-store";
22
22
  import {
23
+ API_KEY_BAN_REASONS,
23
24
  BAN_DURATION_CHOICES,
24
25
  DEFAULT_BAN_MINUTES,
25
26
  generateApiKeySecret,
@@ -40,6 +41,7 @@ import {
40
41
  } from "./contracts";
41
42
  import type { SharedKeyActivityDetail, SharedKeySummary } from "../share/share-usage";
42
43
  import { popupImageBytes } from "./share-popup-image";
44
+ import { log } from "../../observability/logger";
43
45
 
44
46
  const SHARED_CHILD_SORT_COLUMNS = new Set<SharedChildSortColumn>([
45
47
  "createdAt",
@@ -50,6 +52,9 @@ const SHARED_CHILD_SORT_COLUMNS = new Set<SharedChildSortColumn>([
50
52
  "enabled",
51
53
  ]);
52
54
 
55
+ /** The offered ban durations as a set of minute values, hoisted once. */
56
+ const BAN_DURATION_MINUTES = new Set(BAN_DURATION_CHOICES.map((choice) => choice.minutes));
57
+
53
58
  /** Parses and bounds the paginated share-child list query. */
54
59
  function parseSharedChildrenQuery(query: URLSearchParams): SharedChildrenPageOptions {
55
60
  const limitRaw = query.get("limit");
@@ -95,7 +100,16 @@ export function createApiKeyOperations(config: ApiKeyConfig) {
95
100
  if (record.keyMode !== "share") return response;
96
101
  const family = await config.store
97
102
  .sumFamilyTelemetryConsumed(authorized.tenantId, record.id)
98
- .catch(() => 0);
103
+ .catch((error) => {
104
+ // A failed sum must not fail the whole list, but it must not
105
+ // be silent either: reporting the share template's spend as 0
106
+ // under-reports consumed tokens in the console list.
107
+ log.warn(
108
+ `[api-keys] sumFamilyTelemetryConsumed failed for key ${record.id}; reporting 0`,
109
+ error as Error,
110
+ );
111
+ return 0;
112
+ });
99
113
  return { ...response, tokensConsumed: response.tokensConsumed + family };
100
114
  }),
101
115
  );
@@ -187,6 +201,7 @@ export function createApiKeyOperations(config: ApiKeyConfig) {
187
201
  ...(request.monthlyTokenLimit == null ? {} : { monthlyTokenLimit: request.monthlyTokenLimit }),
188
202
  ...(request.lifetimeTokenBudget == null ? {} : { lifetimeTokenBudget: request.lifetimeTokenBudget }),
189
203
  ...(request.maxConcurrentRequests == null ? {} : { maxConcurrentRequests: request.maxConcurrentRequests }),
204
+ ...(request.concurrencyScope !== undefined ? { concurrencyScope: request.concurrencyScope } : {}),
190
205
  ...(request.maxChildren == null ? {} : { maxChildren: request.maxChildren }),
191
206
  ...(request.modelPrefix === undefined ? {} : { modelPrefix: request.modelPrefix }),
192
207
  modelAccessMode: request.modelAccessMode ?? "whitelist",
@@ -322,6 +337,7 @@ export function createApiKeyOperations(config: ApiKeyConfig) {
322
337
  ...(patchRequest.monthlyTokenLimit === undefined ? {} : { monthlyTokenLimit: patchRequest.monthlyTokenLimit }),
323
338
  ...(patchRequest.lifetimeTokenBudget === undefined ? {} : { lifetimeTokenBudget: patchRequest.lifetimeTokenBudget }),
324
339
  ...(patchRequest.maxConcurrentRequests === undefined ? {} : { maxConcurrentRequests: patchRequest.maxConcurrentRequests }),
340
+ ...(patchRequest.concurrencyScope !== undefined ? { concurrencyScope: patchRequest.concurrencyScope } : {}),
325
341
  ...(patchRequest.maxChildren === undefined ? {} : { maxChildren: patchRequest.maxChildren }),
326
342
  ...(patchRequest.modelPrefix === undefined ? {} : { modelPrefix: patchRequest.modelPrefix }),
327
343
  ...(patchRequest.modelAccessMode === undefined ? {} : { modelAccessMode: patchRequest.modelAccessMode }),
@@ -791,8 +807,7 @@ export function createApiKeyOperations(config: ApiKeyConfig) {
791
807
  // clamping would answer a request for 90 days with a week and report
792
808
  // success.
793
809
  const chosenMinutes = request.minutes === undefined ? DEFAULT_BAN_MINUTES : request.minutes;
794
- const allowed = new Set(BAN_DURATION_CHOICES.map((choice) => choice.minutes));
795
- if (!allowed.has(chosenMinutes)) {
810
+ if (!BAN_DURATION_MINUTES.has(chosenMinutes)) {
796
811
  throw new ConsoleDomainError(
797
812
  "invalid_request",
798
813
  400,
@@ -814,11 +829,27 @@ export function createApiKeyOperations(config: ApiKeyConfig) {
814
829
  access: authorized,
815
830
  action: "api_key.banned",
816
831
  target: keyId,
817
- detail: { reason, ...(bannedUntil === null ? { permanent: true } : { bannedUntil: bannedUntil.toISOString() }) },
832
+ detail: {
833
+ reason,
834
+ // The human-readable label is derived server-side from the machine
835
+ // `reason`, so the audit trail cannot be skewed by a client-authored
836
+ // label. Unknown reasons (custom text) simply carry no label; the
837
+ // free-text `reason` above is still recorded verbatim.
838
+ ...(API_KEY_BAN_REASONS.find((r) => r.value === reason)?.label === undefined
839
+ ? {}
840
+ : { reasonLabel: API_KEY_BAN_REASONS.find((r) => r.value === reason)!.label }),
841
+ ...(bannedUntil === null ? { permanent: true } : { bannedUntil: bannedUntil.toISOString() }),
842
+ },
843
+ });
844
+ // The ban mutates the row already fetched; derive the response from that
845
+ // record instead of re-fetching after the write. Match the store's own
846
+ // mapping: a `null` (permanent) `bannedUntil` is omitted, not carried.
847
+ return sanitizeApiKeyResponse({
848
+ ...current,
849
+ bannedAt: new Date(),
850
+ banReason: reason,
851
+ ...(bannedUntil ? { bannedUntil } : {}),
818
852
  });
819
- const record = await config.store.get(authorized.tenantId, keyId);
820
- if (!record) throw new ConsoleDomainError("key_not_found", 404, "Key not found");
821
- return sanitizeApiKeyResponse(record);
822
853
  },
823
854
  async unbanKey(
824
855
  access: AccessDecision | undefined,
@@ -836,9 +867,11 @@ export function createApiKeyOperations(config: ApiKeyConfig) {
836
867
  action: "api_key.unbanned",
837
868
  target: keyId,
838
869
  });
839
- const record = await config.store.get(authorized.tenantId, keyId);
840
- if (!record) throw new ConsoleDomainError("key_not_found", 404, "Key not found");
841
- return sanitizeApiKeyResponse(record);
870
+ // Unban clears all three ban columns and re-enables the key; derive the
871
+ // response from the pre-fetch instead of re-fetching after the write.
872
+ const { bannedAt: _bannedAt, banReason: _banReason, bannedUntil: _bannedUntil, ...currentRest } = current;
873
+ const unbannedRecord: ApiKeyRecord = { ...currentRest, enabled: true };
874
+ return sanitizeApiKeyResponse(unbannedRecord);
842
875
  },
843
876
  };
844
877
  return operations;
@@ -858,6 +891,7 @@ const apiKeyBody = t.Object({
858
891
  monthlyTokenLimit: t.Optional(t.Union([t.Number(), t.Null()])),
859
892
  lifetimeTokenBudget: t.Optional(t.Union([t.Number(), t.Null()])),
860
893
  maxConcurrentRequests: t.Optional(t.Union([t.Number(), t.Null()])),
894
+ concurrencyScope: t.Optional(literalUnion(API_KEY_CONCURRENCY_SCOPES)),
861
895
  modelPrefix: t.Optional(t.String()),
862
896
  modelAccessMode: t.Optional(literalUnion(API_KEY_MODEL_ACCESS_MODES)),
863
897
  modelList: t.Optional(t.Array(t.String())),
@@ -1020,7 +1054,9 @@ export function createApiKeyRoutes(config: ApiKeyConfig): AnyElysia {
1020
1054
  {
1021
1055
  body: t.Object({
1022
1056
  reason: t.String(),
1023
- reasonLabel: t.Optional(t.String()),
1057
+ // `reasonLabel` is deliberately not accepted: the audit label is
1058
+ // derived server-side from `reason` (see `banKey`), so a client
1059
+ // cannot author the human-readable audit entry.
1024
1060
  minutes: t.Optional(t.Union([t.Number({ minimum: 1 }), t.Null()])),
1025
1061
  }),
1026
1062
  },
@@ -1,4 +1,4 @@
1
- import type { AccessDecision } from "../../security/access-control";
2
-
3
- /** Resolves the authenticated console request to a tenant-scoped decision. */
4
- export type ConsoleAccessResolver = (request: Request) => AccessDecision | undefined;
1
+ import type { AccessDecision } from "../../security/access-control";
2
+
3
+ /** Resolves the authenticated console request to a tenant-scoped decision. */
4
+ export type ConsoleAccessResolver = (request: Request) => AccessDecision | undefined;
@@ -25,7 +25,7 @@
25
25
  import { GatewayError } from "../../protocol/gateway-error";
26
26
  import { isLoopbackPeer, tokenMatches } from "../../transport/drain-endpoint";
27
27
 
28
- export interface ConsoleAccountResetDeps {
28
+ interface ConsoleAccountResetDeps {
29
29
  /** Shared secret; the route is registered only when this is set. */
30
30
  readonly token: string;
31
31
  /** Removes every console user and session. Resolves to the number removed. */
@@ -8,7 +8,7 @@ import {
8
8
  import type { CartethyiaDatabase } from "../../persistence/db-handle";
9
9
  import { type AccessDecision } from "../../security/access-control";
10
10
 
11
- export interface AuditRecordInput {
11
+ interface AuditRecordInput {
12
12
  readonly access: AccessDecision;
13
13
  readonly action: string;
14
14
  readonly target: string;
@@ -7,8 +7,9 @@ import {
7
7
  } from "../../persistence/schema";
8
8
 
9
9
  import type { CartethyiaDatabase } from "../../persistence/db-handle";
10
+ import { log } from "../../observability/logger";
10
11
 
11
- export class ConsoleLockoutStore {
12
+ class ConsoleLockoutStore {
12
13
  constructor(private readonly db: CartethyiaDatabase) {}
13
14
 
14
15
  async getLockedUntil(ip: string): Promise<Date | null | undefined> {
@@ -143,8 +144,11 @@ export class ConsoleLockoutService {
143
144
  enforcement: "full_network_lockout",
144
145
  },
145
146
  });
146
- } catch {
147
- // Best-effort audit logging; the ban itself remains enforced via the DB row.
147
+ } catch (error) {
148
+ // Best-effort audit logging; the ban itself remains enforced via the DB
149
+ // row. A failed write is logged — for a security-relevant event
150
+ // (`security.ip_banned`) total silence would hide a broken audit trail.
151
+ log.error(`[auth] failed to record ip_banned audit for ${ip}`, error as Error, reason);
148
152
  }
149
153
  }
150
154
 
@@ -3,7 +3,7 @@ import type { ConsoleSession, ConsoleUser } from "../../persistence/schema";
3
3
 
4
4
  import { isRecord } from "../../protocol/codecs";
5
5
 
6
- export function asDate(value: unknown): Date | undefined {
6
+ function asDate(value: unknown): Date | undefined {
7
7
  if (value instanceof Date && !Number.isNaN(value.getTime())) return value;
8
8
  if (typeof value === "string" || typeof value === "number") {
9
9
  const date = new Date(value);
@@ -14,33 +14,78 @@ export function asDate(value: unknown): Date | undefined {
14
14
 
15
15
  export function sessionRecord(value: unknown): ConsoleSession | undefined {
16
16
  if (!isRecord(value)) return undefined;
17
+ const id = value.id;
18
+ const userId = value.userId;
19
+ const sessionToken = value.sessionToken;
20
+ const ipAddress = value.ipAddress;
21
+ const userAgent = value.userAgent;
17
22
  if (
18
- typeof value.id !== "string" ||
19
- typeof value.userId !== "string" ||
20
- typeof value.sessionToken !== "string"
23
+ typeof id !== "string" ||
24
+ typeof userId !== "string" ||
25
+ typeof sessionToken !== "string"
21
26
  ) {
22
27
  return undefined;
23
28
  }
24
29
  const expiresAt = asDate(value.expiresAt);
25
30
  const createdAt = asDate(value.createdAt);
26
31
  if (!expiresAt || !createdAt) return undefined;
27
- return { ...value, expiresAt, createdAt } as unknown as ConsoleSession;
32
+ // Construct the result explicitly field-by-field so the output shape is
33
+ // exactly what was validated — arbitrary extra keys on a driver row must
34
+ // not survive into the typed session.
35
+ return {
36
+ id,
37
+ userId,
38
+ sessionToken,
39
+ ...(ipAddress === null || typeof ipAddress === "string" ? { ipAddress } : { ipAddress: null }),
40
+ ...(userAgent === null || typeof userAgent === "string" ? { userAgent } : { userAgent: null }),
41
+ expiresAt,
42
+ createdAt,
43
+ };
28
44
  }
29
45
 
30
46
  export function userRecord(value: unknown): ConsoleUser | undefined {
31
47
  if (!isRecord(value)) return undefined;
48
+ const id = value.id;
49
+ const username = value.username;
50
+ const passwordHash = value.passwordHash;
51
+ const isActive = value.isActive;
32
52
  if (
33
- typeof value.id !== "string" ||
34
- typeof value.username !== "string" ||
35
- typeof value.passwordHash !== "string" ||
36
- typeof value.isActive !== "boolean"
53
+ typeof id !== "string" ||
54
+ typeof username !== "string" ||
55
+ typeof passwordHash !== "string" ||
56
+ typeof isActive !== "boolean"
37
57
  ) {
38
58
  return undefined;
39
59
  }
40
- return value as unknown as ConsoleUser;
60
+ // Timestamps are coerced through `asDate` because SQLite hands them back as
61
+ // text; a login path that rejects non-Date values must still work there.
62
+ const createdAt = asDate(value.createdAt);
63
+ const updatedAt = asDate(value.updatedAt);
64
+ if (!createdAt || !updatedAt) return undefined;
65
+ const tenantId = value.tenantId;
66
+ const email = value.email;
67
+ const displayName = value.displayName;
68
+ const isFirstBoot = value.isFirstBoot;
69
+ const isPlatformAdmin = value.isPlatformAdmin;
70
+ // Construct the result explicitly field-by-field so the output shape is
71
+ // exactly what was validated — arbitrary extra keys on a driver row must
72
+ // not survive into the typed user.
73
+ return {
74
+ id,
75
+ tenantId: typeof tenantId === "string" ? tenantId : "",
76
+ username,
77
+ email: typeof email === "string" ? email : null,
78
+ passwordHash,
79
+ displayName: typeof displayName === "string" ? displayName : null,
80
+ isActive,
81
+ isPlatformAdmin: isPlatformAdmin === true,
82
+ isFirstBoot: isFirstBoot === true,
83
+ createdAt,
84
+ updatedAt,
85
+ };
41
86
  }
42
87
 
43
- export function mutationAffectedRows(value: unknown): number | undefined {
88
+ function mutationAffectedRows(value: unknown): number | undefined {
44
89
  if (!isRecord(value)) return undefined;
45
90
  for (const key of ["rowCount", "rowsAffected", "changes"] as const) {
46
91
  const count = value[key];
@@ -50,7 +95,7 @@ export function mutationAffectedRows(value: unknown): number | undefined {
50
95
  }
51
96
 
52
97
  /** Awaitable Drizzle mutation builder that may expose `.returning()`. */
53
- export interface ReturningBuilder extends PromiseLike<unknown> {
98
+ interface ReturningBuilder extends PromiseLike<unknown> {
54
99
  returning?: (...columns: unknown[]) => PromiseLike<unknown[]>;
55
100
  }
56
101
 
@@ -45,7 +45,7 @@ export async function resolveConsoleAccess(
45
45
  }
46
46
 
47
47
  /** The signed-in console user behind a request, or `null` when unauthenticated. */
48
- export interface ResolvedConsoleUser {
48
+ interface ResolvedConsoleUser {
49
49
  readonly id: string;
50
50
  readonly sessionId: string;
51
51
  readonly tenantId: string;
@@ -58,7 +58,7 @@ function newSessionToken(): string {
58
58
  return randomBytes(32).toString("base64url");
59
59
  }
60
60
 
61
- export class ConsoleSessionStore {
61
+ class ConsoleSessionStore {
62
62
  constructor(private readonly db: CartethyiaDatabase) {}
63
63
 
64
64
  async create(newSession: {
@@ -73,7 +73,13 @@ export class ConsoleSessionStore {
73
73
  const inserted = await returningRows(this.db.insert(consoleSessions).values(newSession));
74
74
  assertMutationApplied(inserted, "Console session");
75
75
  const persisted = sessionRecord(inserted.rows[0]);
76
- return persisted ?? ({ ...newSession } as unknown as ConsoleSession);
76
+ if (!persisted) {
77
+ // `assertMutationApplied` already refused a failed insert, so an
78
+ // unshaped row here is a driver/schema mismatch — pretending success by
79
+ // returning the input cast to ConsoleSession would mask it.
80
+ throw new Error("Console session row could not be coerced after insert");
81
+ }
82
+ return persisted;
77
83
  }
78
84
 
79
85
  async findValidByTokenHash(tokenHash: string, now: Date): Promise<ConsoleSession | null> {
@@ -13,12 +13,13 @@ import type { FirstBootSetupService } from "./setup-service";
13
13
  import { isTrustedProxyPeer, resolveClientIdentity } from "../../security/ip-boundary";
14
14
  import type { TrustedProxyBoundary } from "../../config/network";
15
15
  import { readSessionCookie } from "./session-resolver";
16
- export interface LoginRequest {
16
+ import { asConsoleDb } from "../shared/transaction";
17
+ interface LoginRequest {
17
18
  readonly username: string;
18
19
  readonly password: string;
19
20
  }
20
21
 
21
- export interface LoginResponse {
22
+ interface LoginResponse {
22
23
  readonly status: "success" | "failed";
23
24
  /** Session secrets are cookie-only and are never returned in JSON. */
24
25
  readonly session_id?: never;
@@ -47,13 +48,13 @@ export type SessionStatusResponse =
47
48
  }
48
49
  | { readonly status: "unauthenticated" };
49
50
 
50
- export interface SetupRequest {
51
+ interface SetupRequest {
51
52
  readonly password: string;
52
53
  readonly username?: string;
53
54
  readonly display_name?: string;
54
55
  }
55
56
 
56
- export interface SetupResponse {
57
+ interface SetupResponse {
57
58
  readonly status: "success" | "failed";
58
59
  readonly message?: string;
59
60
  }
@@ -65,7 +66,7 @@ export interface SetupResponse {
65
66
  * report that instead of only "it worked".
66
67
  */
67
68
  /** A backup's cover, so the wizard can show what a file holds before restoring. */
68
- export interface SetupInspectResponse extends SetupResponse {
69
+ interface SetupInspectResponse extends SetupResponse {
69
70
  readonly format?: "native" | "nine_router";
70
71
  readonly version?: number;
71
72
  readonly exportedAt?: string;
@@ -79,18 +80,18 @@ export interface SetupInspectResponse extends SetupResponse {
79
80
  readonly warnings?: readonly { readonly table?: string; readonly column?: string; readonly reason: string }[];
80
81
  }
81
82
 
82
- export interface SetupRestoreResponse extends SetupResponse {
83
+ interface SetupRestoreResponse extends SetupResponse {
83
84
  readonly restored?: Record<string, number>;
84
85
  readonly warnings?: readonly { readonly table?: string; readonly column?: string; readonly reason: string }[];
85
86
  /** The administrator this install was given, since the file carries none. */
86
87
  readonly createdAdmin?: boolean;
87
88
  }
88
89
 
89
- export interface LogoutResponse {
90
+ interface LogoutResponse {
90
91
  readonly status: "success";
91
92
  }
92
93
 
93
- export interface ChangePasswordResponse {
94
+ interface ChangePasswordResponse {
94
95
  readonly status: "success" | "failed";
95
96
  readonly message?: string;
96
97
  }
@@ -183,6 +184,31 @@ async function lockedOutMessage(
183
184
  };
184
185
  }
185
186
 
187
+ /**
188
+ * Records one failed login and answers with the 429/401 result, including the
189
+ * `retry-after` header when the failure took the lock. Shared by the unknown /
190
+ * inactive-user and wrong-password branches, which differ only in the audit
191
+ * reason string.
192
+ */
193
+ async function rejectLogin(
194
+ lockoutService: ConsoleLockoutService,
195
+ lockoutKey: string,
196
+ reason: string,
197
+ set: { status?: number | string; headers: Record<string, string | number | string[]> },
198
+ ): Promise<LoginResponse> {
199
+ const isBanned = await lockoutService.recordFailure(lockoutKey, reason);
200
+ if (isBanned) {
201
+ set.status = 429;
202
+ set.headers["retry-after"] = String(await lockoutService.remainingLockSeconds(lockoutKey));
203
+ return lockedOutMessage(lockoutService, lockoutKey);
204
+ }
205
+ set.status = 401;
206
+ return {
207
+ status: "failed",
208
+ message: await failedLoginMessage(lockoutService, lockoutKey),
209
+ } satisfies LoginResponse;
210
+ }
211
+
186
212
  function parseLoginRequest(body: unknown): LoginRequest | undefined {
187
213
  if (!isRecord(body) || typeof body.password !== "string") {
188
214
  return undefined;
@@ -266,32 +292,12 @@ export function createConsoleAuthRoutes(
266
292
  const rows = await readUsers(database, input.username);
267
293
  const user = userRecord(rows[0]);
268
294
  if (!user || !user.isActive) {
269
- const isBanned = await lockoutService.recordFailure(lockoutKey, "unknown_user");
270
- if (isBanned) {
271
- set.status = 429;
272
- set.headers["retry-after"] = String(await lockoutService.remainingLockSeconds(lockoutKey));
273
- return lockedOutMessage(lockoutService, lockoutKey);
274
- }
275
- set.status = 401;
276
- return {
277
- status: "failed",
278
- message: await failedLoginMessage(lockoutService, lockoutKey),
279
- } satisfies LoginResponse;
295
+ return rejectLogin(lockoutService, lockoutKey, "unknown_user", set);
280
296
  }
281
297
 
282
298
  const valid = await credentialService.verifyPassword(input.password, user.passwordHash);
283
299
  if (!valid) {
284
- const isBanned = await lockoutService.recordFailure(lockoutKey, "invalid_password");
285
- if (isBanned) {
286
- set.status = 429;
287
- set.headers["retry-after"] = String(await lockoutService.remainingLockSeconds(lockoutKey));
288
- return lockedOutMessage(lockoutService, lockoutKey);
289
- }
290
- set.status = 401;
291
- return {
292
- status: "failed",
293
- message: await failedLoginMessage(lockoutService, lockoutKey),
294
- } satisfies LoginResponse;
300
+ return rejectLogin(lockoutService, lockoutKey, "invalid_password", set);
295
301
  }
296
302
 
297
303
  await lockoutService.clearFailures(lockoutKey);
@@ -526,7 +532,7 @@ export function createConsoleAuthRoutes(
526
532
  });
527
533
  return invalidated;
528
534
  };
529
- await database.transaction((tx) => rotateInScope(tx as unknown as CartethyiaDatabase));
535
+ await database.transaction((tx) => rotateInScope(asConsoleDb(tx)));
530
536
  set.status = 200;
531
537
  return { status: "success" } satisfies ChangePasswordResponse;
532
538
  },
@@ -20,6 +20,7 @@ import {
20
20
  import { resolveDefaultGatewayApiKey } from "../../config/runtime";
21
21
  import { TENANT_KEY_SCOPES } from "../../security/access-control";
22
22
  import { isRecord } from "../../protocol/codecs";
23
+ import { asConsoleDb } from "../shared/transaction";
23
24
  import { BackupService } from "../backup/service";
24
25
  import type { BackupInspection } from "../backup/service";
25
26
  import type { RestoreWarning } from "../backup/contracts";
@@ -28,14 +29,14 @@ import type { RestoreWarning } from "../backup/contracts";
28
29
  // and the mode is read from the environment the process was started with.
29
30
 
30
31
  /** The administrator to create when a restored backup left none to sign in with. */
31
- export interface AdminBootstrap {
32
+ interface AdminBootstrap {
32
33
  readonly password: string;
33
34
  readonly username?: string;
34
35
  readonly displayName?: string;
35
36
  }
36
37
 
37
38
  /** What a first-boot restore did, so the console can report it honestly. */
38
- export interface RestoreBackupOutcome {
39
+ interface RestoreBackupOutcome {
39
40
  readonly restored: Record<string, number>;
40
41
  readonly skipped: Record<string, number>;
41
42
  /** Parts of the file that could not be restored. */
@@ -171,7 +172,7 @@ export class FirstBootSetupService {
171
172
  // unserialized lets two boots both pass the check.
172
173
  await this.db.transaction(async (transaction) => {
173
174
  await transaction.execute(firstBootSetupLock());
174
- await create(transaction as unknown as CartethyiaDatabase);
175
+ await create(asConsoleDb(transaction));
175
176
  });
176
177
  }
177
178
 
@@ -6,7 +6,7 @@ import {
6
6
 
7
7
  import type { CartethyiaDatabase } from "../../persistence/db-handle";
8
8
 
9
- export class ConsoleUserStore {
9
+ class ConsoleUserStore {
10
10
  constructor(private readonly db: CartethyiaDatabase) {}
11
11
 
12
12
  async findByUsername(username: string): Promise<unknown | undefined> {
@@ -106,9 +106,6 @@ export const PROXY_KINDS: Readonly<Record<string, "http" | "https" | "socks5">>
106
106
  workers: "https",
107
107
  };
108
108
 
109
- /** Proxy types we recognize but cannot represent, with the reason. */
110
- export const REFUSED_PROXY_KINDS: Readonly<Record<string, string>> = {};
111
-
112
109
  /**
113
110
  * Router combo strategy → ours.
114
111
  *
@@ -170,9 +170,15 @@ const SEALED_COLUMNS: Readonly<Record<string, Readonly<Record<string, string>>>>
170
170
  share_links: { token_encrypted: "share_token" },
171
171
  };
172
172
 
173
+ const SEALED_COLUMNS_CACHE = new WeakMap<Table, ReadonlySet<string>>();
174
+
173
175
  /** The sealed columns of `table`, by column name. */
174
176
  export function sealedColumns(table: Table): ReadonlySet<string> {
175
- return new Set(Object.keys(SEALED_COLUMNS[getTableName(table)] ?? {}));
177
+ const cached = SEALED_COLUMNS_CACHE.get(table);
178
+ if (cached) return cached;
179
+ const names = new Set(Object.keys(SEALED_COLUMNS[getTableName(table)] ?? {}));
180
+ SEALED_COLUMNS_CACHE.set(table, names);
181
+ return names;
176
182
  }
177
183
 
178
184
  /**
@@ -205,7 +211,6 @@ export function sealedAliases(table: Table): ReadonlyMap<string, string> {
205
211
  return out;
206
212
  }
207
213
 
208
-
209
214
  /**
210
215
  * Which rows of a table belong to one tenant, and how a restore replaces them.
211
216
  *
@@ -236,7 +241,7 @@ export function sealedAliases(table: Table): ReadonlyMap<string, string> {
236
241
  * - `self` — the tenant row itself, matched on its own id, upserted and never
237
242
  * deleted.
238
243
  */
239
- export type TableOwnership =
244
+ type TableOwnership =
240
245
  | { readonly kind: "direct"; readonly column: Column; readonly replace: "delete" | "upsert" }
241
246
  | {
242
247
  readonly kind: "viaParent";
@@ -493,7 +498,7 @@ export function findTable(section: BackupSection, name: string): Table | undefin
493
498
  return tablesForSection(section).find((table) => tableName(table) === name);
494
499
  }
495
500
 
496
- export interface BackupExportResult {
501
+ export interface BackupPayloadExport {
497
502
  readonly payload: BackupPayload;
498
503
  readonly counts: Record<string, number>;
499
504
  }
@@ -511,11 +516,6 @@ export interface ValidatedTable {
511
516
  readonly rows: readonly BackupRow[];
512
517
  }
513
518
 
514
- export interface RestoreResult {
515
- readonly restored: Record<string, number>;
516
- readonly skipped: Record<string, number>;
517
- }
518
-
519
519
  /**
520
520
  * One part of a backup that could not be restored, and why.
521
521
  *
@@ -23,11 +23,11 @@ import { homedir } from "node:os";
23
23
  import { join } from "node:path";
24
24
  import { GatewayError } from "../../protocol/gateway-error";
25
25
  import { isLoopbackPeer, tokenMatches } from "../../transport/drain-endpoint";
26
- import { BACKUP_SECTIONS, tablesForSection } from "./contracts";
26
+ import { tablesForSection } from "./contracts";
27
27
  import { compressBackupStream, exportBackupStream } from "./store";
28
28
  import type { CartethyiaDatabase } from "../../persistence/db-handle";
29
29
 
30
- export interface BackupExportDeps {
30
+ interface BackupExportDeps {
31
31
  readonly db: CartethyiaDatabase;
32
32
  /** Shared secret; the route is registered only when this is set. */
33
33
  readonly token: string;
@@ -37,7 +37,8 @@ export interface BackupExportDeps {
37
37
  readonly resolvePeerAddress: (request: Request) => string | null;
38
38
  }
39
39
 
40
- export interface BackupExportResult {
40
+ /** The file-write result of an operator export, distinct from the payload shape `BackupPayloadExport` in `contracts.ts`. */
41
+ export interface BackupExportFileResult {
41
42
  readonly path: string;
42
43
  readonly bytes: number;
43
44
  readonly counts: Record<string, number>;
@@ -89,7 +90,7 @@ export function createBackupExportHandler(
89
90
  );
90
91
  }
91
92
 
92
- const result: BackupExportResult = { path, bytes: gzipped.byteLength, counts };
93
+ const result: BackupExportFileResult = { path, bytes: gzipped.byteLength, counts };
93
94
  return new Response(JSON.stringify(result), {
94
95
  status: 200,
95
96
  headers: { "content-type": "application/json", "cache-control": "no-store" },
@@ -108,5 +109,3 @@ function downloadDirectory(): string {
108
109
  : join(process.env.XDG_DOWNLOAD_DIR ?? join(home, "Downloads"));
109
110
  return candidate;
110
111
  }
111
-
112
- export { BACKUP_SECTIONS };
@@ -29,7 +29,6 @@ import {
29
29
  isNodeProviderId,
30
30
  NODE_WIRE_FAMILIES,
31
31
  PROXY_KINDS,
32
- REFUSED_PROXY_KINDS,
33
32
  UNSUPPORTED_PROVIDERS,
34
33
  UPSTREAM_PROVIDERS,
35
34
  } from "./9router-map";
@@ -50,7 +49,7 @@ export interface ImportReport {
50
49
  readonly remapped: readonly string[];
51
50
  }
52
51
 
53
- export interface ConversionResult {
52
+ interface ConversionResult {
54
53
  readonly payload: BackupPayload;
55
54
  readonly report: ImportReport;
56
55
  }
@@ -479,11 +478,6 @@ export function convert9RouterBackup(input: unknown, tenantId: string): Conversi
479
478
  return;
480
479
  }
481
480
  const rawKind = text(entry.type) ?? "http";
482
- const refused = REFUSED_PROXY_KINDS[rawKind];
483
- if (refused !== undefined) {
484
- skipped.push(`proxy pool "${label}": type "${rawKind}" (${refused})`);
485
- return;
486
- }
487
481
  const kind = PROXY_KINDS[rawKind];
488
482
  if (kind === undefined) {
489
483
  skipped.push(`proxy pool "${label}": unknown type "${rawKind}"`);