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.
- package/.env.example +2 -0
- package/README.md +17 -5
- package/dist/dashboard/assets/{BannedPage-ChG5svFb.js → BannedPage-Cj15GsMp.js} +1 -1
- package/dist/dashboard/assets/BannedPage-Cj15GsMp.js.gz +0 -0
- package/dist/dashboard/assets/ClaimGiftsButton-D83pBJ6J.js +1 -0
- package/dist/dashboard/assets/ClaimGiftsButton-D83pBJ6J.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolDetailPage-BIcWQun7.js → CliToolDetailPage-DZKmVuJN.js} +1 -1
- package/dist/dashboard/assets/CliToolDetailPage-DZKmVuJN.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolsPage-Bf2VelDe.js → CliToolsPage-BIccZmeL.js} +1 -1
- package/dist/dashboard/assets/CliToolsPage-BIccZmeL.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-DEEKkYD-.js +1 -0
- package/dist/dashboard/assets/CombosPage-DEEKkYD-.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-D5rol2j2.js +1 -0
- package/dist/dashboard/assets/ConsoleLogPage-D5rol2j2.js.gz +0 -0
- package/dist/dashboard/assets/{CustomizationPage-ggeMSjte.js → CustomizationPage-BKj_r0rp.js} +1 -1
- package/dist/dashboard/assets/CustomizationPage-BKj_r0rp.js.gz +0 -0
- package/dist/dashboard/assets/InjectSystemPromptPanel-B5nZvmf0.js +656 -0
- package/dist/dashboard/assets/InjectSystemPromptPanel-B5nZvmf0.js.gz +0 -0
- package/dist/dashboard/assets/{LoginPage-U6AKaJcF.js → LoginPage-DkXb6dx-.js} +1 -1
- package/dist/dashboard/assets/LoginPage-DkXb6dx-.js.gz +0 -0
- package/dist/dashboard/assets/{ModelPicker-BMS2t8BA.js → ModelPicker-DnQPSIA8.js} +1 -1
- package/dist/dashboard/assets/ModelPicker-DnQPSIA8.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-Ci2wQKUz.js +1 -0
- package/dist/dashboard/assets/OverviewPage-Ci2wQKUz.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-odlgYrOg.js +5 -0
- package/dist/dashboard/assets/ProviderDetailPage-odlgYrOg.js.gz +0 -0
- package/dist/dashboard/assets/ProviderIcon-EfeloBn4.js +1 -0
- package/dist/dashboard/assets/ProviderIcon-EfeloBn4.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-CqFBQEB8.js +1 -0
- package/dist/dashboard/assets/ProvidersPage-CqFBQEB8.js.gz +0 -0
- package/dist/dashboard/assets/{ProxyPage-lsrx7NYg.js → ProxyPage-5SnUSy3j.js} +1 -1
- package/dist/dashboard/assets/ProxyPage-5SnUSy3j.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-CmnMDzhP.js +1 -0
- package/dist/dashboard/assets/QuotaPage-CmnMDzhP.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-BJtRZ4qD.js +1 -0
- package/dist/dashboard/assets/SettingsPage-BJtRZ4qD.js.gz +0 -0
- package/dist/dashboard/assets/{SetupPage-f0DE1h0-.js → SetupPage-WTa0sktC.js} +1 -1
- package/dist/dashboard/assets/SetupPage-WTa0sktC.js.gz +0 -0
- package/dist/dashboard/assets/{StudioPage-Cq5tW7hW.js → StudioPage-BfAb5-o_.js} +3 -3
- package/dist/dashboard/assets/StudioPage-BfAb5-o_.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-BNzeZ2Vk.js +56 -0
- package/dist/dashboard/assets/UsagePage-BNzeZ2Vk.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-l29JROPv.js +1 -0
- package/dist/dashboard/assets/api-keys-l29JROPv.js.gz +0 -0
- package/dist/dashboard/assets/app--PJWq8_C.css +2 -0
- package/dist/dashboard/assets/app--PJWq8_C.css.gz +0 -0
- package/dist/dashboard/assets/assert-C0K-c4eo.js +1 -0
- package/dist/dashboard/assets/assert-C0K-c4eo.js.gz +0 -0
- package/dist/dashboard/assets/{badge-D9CS6dZk.js → badge-DfbhQU4k.js} +1 -1
- package/dist/dashboard/assets/badge-DfbhQU4k.js.gz +0 -0
- package/dist/dashboard/assets/{banner-DympBNq4.js → banner-BllT4LcF.js} +1 -1
- package/dist/dashboard/assets/banner-BllT4LcF.js.gz +0 -0
- package/dist/dashboard/assets/brand/login_register.jpg +0 -0
- package/dist/dashboard/assets/brand/login_register_initial.jpg +0 -0
- package/dist/dashboard/assets/brand/og_bansos.jpg +0 -0
- package/dist/dashboard/assets/brand/og_image.jpg +0 -0
- package/dist/dashboard/assets/{card-ibRWU_uT.js → card-CtkX66Wd.js} +1 -1
- package/dist/dashboard/assets/card-CtkX66Wd.js.gz +0 -0
- package/dist/dashboard/assets/chevron-left-Bf6CaTSJ.js +1 -0
- package/dist/dashboard/assets/chevron-left-Bf6CaTSJ.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-CQXVMn4P.js +1 -0
- package/dist/dashboard/assets/cli-tools-CQXVMn4P.js.gz +0 -0
- package/dist/dashboard/assets/{clipboard-button-DJm-9pz7.js → clipboard-button-BQqBQd2n.js} +1 -1
- package/dist/dashboard/assets/clipboard-button-BQqBQd2n.js.gz +0 -0
- package/dist/dashboard/assets/{dialog-8fiE6tec.js → dialog-BEK7KP0H.js} +1 -1
- package/dist/dashboard/assets/dialog-BEK7KP0H.js.gz +0 -0
- package/dist/dashboard/assets/{drawer-CqGYeOrd.js → drawer-BrTEZ8zu.js} +1 -1
- package/dist/dashboard/assets/drawer-BrTEZ8zu.js.gz +0 -0
- package/dist/dashboard/assets/entry-CHWxbS56.js +1 -0
- package/dist/dashboard/assets/entry-CHWxbS56.js.gz +0 -0
- package/dist/dashboard/assets/{entry-BSAsYsmV.js → entry-EBHJ3DrE.js} +1 -1
- package/dist/dashboard/assets/entry-EBHJ3DrE.js.gz +0 -0
- package/dist/dashboard/assets/entry-LCS85hiq.js +4 -0
- package/dist/dashboard/assets/entry-LCS85hiq.js.gz +0 -0
- package/dist/dashboard/assets/{formatters-D-MTJxMr.js → formatters-BAACA727.js} +1 -1
- package/dist/dashboard/assets/formatters-BAACA727.js.gz +0 -0
- package/dist/dashboard/assets/{github-badge-CPK47xQx.js → github-badge-YSCE4UC2.js} +1 -1
- package/dist/dashboard/assets/github-badge-YSCE4UC2.js.gz +0 -0
- package/dist/dashboard/assets/{index-DBNPOfhV.js → index-FQlj0HUT.js} +2 -2
- package/dist/dashboard/assets/index-FQlj0HUT.js.gz +0 -0
- package/dist/dashboard/assets/{inline-BT0zntEr.js → inline-C4gU5iOL.js} +1 -1
- package/dist/dashboard/assets/inline-C4gU5iOL.js.gz +0 -0
- package/dist/dashboard/assets/{input-Da2ZViWg.js → input-BHHX_Tmm.js} +1 -1
- package/dist/dashboard/assets/input-BHHX_Tmm.js.gz +0 -0
- package/dist/dashboard/assets/{layout-BXNeLw6M.js → layout-CajrYUnD.js} +1 -1
- package/dist/dashboard/assets/layout-CajrYUnD.js.gz +0 -0
- package/dist/dashboard/assets/{list-row-BycOrrIV.js → list-row-Bx2I7XUe.js} +1 -1
- package/dist/dashboard/assets/list-row-Bx2I7XUe.js.gz +0 -0
- package/dist/dashboard/assets/live-DNNqRbj2.js +1 -0
- package/dist/dashboard/assets/live-DNNqRbj2.js.gz +0 -0
- package/dist/dashboard/assets/network-C5UBACrJ.js +1 -0
- package/dist/dashboard/assets/network-C5UBACrJ.js.gz +0 -0
- package/dist/dashboard/assets/numbers-CUWAZK4J.js +2 -0
- package/dist/dashboard/assets/numbers-CUWAZK4J.js.gz +0 -0
- package/dist/dashboard/assets/pagination-Xsi9Qx04.js +1 -0
- package/dist/dashboard/assets/pagination-Xsi9Qx04.js.gz +0 -0
- package/dist/dashboard/assets/quota-DvQbD_sr.js +1 -0
- package/dist/dashboard/assets/quota-DvQbD_sr.js.gz +0 -0
- package/dist/dashboard/assets/routing-in0f_IfK.js +1 -0
- package/dist/dashboard/assets/routing-in0f_IfK.js.gz +0 -0
- package/dist/dashboard/assets/{select-BIYIBkMU.js → select-BLULNqTp.js} +1 -1
- package/dist/dashboard/assets/select-BLULNqTp.js.gz +0 -0
- package/dist/dashboard/assets/settings-CcGczj6I.js +1 -0
- package/dist/dashboard/assets/settings-CcGczj6I.js.gz +0 -0
- package/dist/dashboard/assets/shield-BMQNxORh.js +1 -0
- package/dist/dashboard/assets/shield-BMQNxORh.js.gz +0 -0
- package/dist/dashboard/assets/{stack-DfgnFDWY.js → stack-D2lIR0tg.js} +1 -1
- package/dist/dashboard/assets/stack-D2lIR0tg.js.gz +0 -0
- package/dist/dashboard/assets/{state-CkfdYWel.js → state-DTARjfiW.js} +1 -1
- package/dist/dashboard/assets/state-DTARjfiW.js.gz +0 -0
- package/dist/dashboard/assets/{switch-SYm237HG.js → switch-c0oPSnqW.js} +1 -1
- package/dist/dashboard/assets/switch-c0oPSnqW.js.gz +0 -0
- package/dist/dashboard/assets/{useMutation-BQqCRNIi.js → useMutation-m8WGNPrz.js} +1 -1
- package/dist/dashboard/assets/useMutation-m8WGNPrz.js.gz +0 -0
- package/dist/dashboard/console-backgrounds.jpg +0 -0
- package/dist/dashboard/console-backgrounds_initial.jpg +0 -0
- package/dist/dashboard/index.html +2 -2
- package/dist/dashboard/index.html.gz +0 -0
- package/dist/dashboard/precompressed-assets.json +56 -55
- package/migrations/full-postgres/0036_system_prompt_presets_refs.sql +10 -0
- package/migrations/full-postgres/0037_share_concurrency_scope.sql +3 -0
- package/migrations/full-postgres/0038_add_accounts_quota_snapshot.sql +7 -0
- package/migrations/full-postgres/0039_add_telemetry_provider_model_indexes.sql +13 -0
- package/migrations/full-postgres/0040_add_models_dialect.sql +3 -0
- package/migrations/full-postgres/0041_add_daily_checkin_ledger_account_fk.sql +7 -0
- package/migrations/lite-sqlite3/0036_system_prompt_presets_refs.sql +14 -0
- package/migrations/lite-sqlite3/0037_share_concurrency_scope.sql +3 -0
- package/migrations/lite-sqlite3/0038_add_accounts_quota_snapshot.sql +4 -0
- package/migrations/lite-sqlite3/0039_add_telemetry_provider_model_indexes.sql +6 -0
- package/migrations/lite-sqlite3/0040_add_models_dialect.sql +3 -0
- package/migrations/lite-sqlite3/0041_add_daily_checkin_ledger_account_fk.sql +26 -0
- package/package.json +4 -6
- package/scripts/cli/update.ts +15 -41
- package/src/boot/app.ts +58 -2
- package/src/boot/dependencies.ts +786 -732
- package/src/boot/lifecycle.ts +11 -12
- package/src/config/cooldowns.ts +34 -55
- package/src/config/network.ts +1 -1
- package/src/config/package-paths.ts +1 -1
- package/src/config/providers.ts +13 -14
- package/src/config/resources.ts +0 -5
- package/src/config/runtime.ts +2 -5
- package/src/config/spec.ts +26 -6
- package/src/console/api-keys/ban-constants.ts +38 -0
- package/src/console/api-keys/contracts.ts +71 -62
- package/src/console/api-keys/routes.ts +48 -12
- package/src/console/auth/access.ts +4 -4
- package/src/console/auth/account-reset.ts +1 -1
- package/src/console/auth/audit-recorder.ts +1 -1
- package/src/console/auth/lockout.ts +7 -3
- package/src/console/auth/row-coercion.ts +57 -12
- package/src/console/auth/session-resolver.ts +1 -1
- package/src/console/auth/session-service.ts +8 -2
- package/src/console/auth/session.ts +37 -31
- package/src/console/auth/setup-service.ts +4 -3
- package/src/console/auth/user-store.ts +1 -1
- package/src/console/backup/9router-map.ts +0 -3
- package/src/console/backup/contracts.ts +9 -9
- package/src/console/backup/export-endpoint.ts +5 -6
- package/src/console/backup/nine-router.ts +1 -7
- package/src/console/backup/routes.ts +29 -33
- package/src/console/backup/service.ts +468 -468
- package/src/console/backup/store.ts +47 -29
- package/src/console/backup/validate.ts +11 -1
- package/src/console/cli-tools/contracts.ts +7 -7
- package/src/console/cli-tools/injectors/cline.ts +110 -110
- package/src/console/cli-tools/injectors/codex.ts +181 -181
- package/src/console/cli-tools/injectors/copilot.ts +108 -108
- package/src/console/cli-tools/injectors/driver.ts +247 -247
- package/src/console/cli-tools/injectors/jcode.ts +115 -115
- package/src/console/cli-tools/injectors/openclaw.ts +161 -161
- package/src/console/cli-tools/injectors/opencode.ts +153 -153
- package/src/console/cli-tools/routes.ts +1 -1
- package/src/console/cli-tools/service.ts +1 -1
- package/src/console/cli-tools/store.ts +2 -2
- package/src/console/console-router.ts +32 -0
- package/src/console/dashboard-assets.ts +9 -9
- package/src/console/domain-registration.ts +16 -2
- package/src/console/inferhub/client.ts +0 -7
- package/src/console/inferhub/key-store.ts +1 -1
- package/src/console/inferhub/normalize.ts +2 -2
- package/src/console/inferhub/routes.ts +1 -1
- package/src/console/inferhub/types.ts +1 -1
- package/src/console/observability/contracts.ts +1 -1
- package/src/console/observability/live.ts +31 -18
- package/src/console/observability/logs.ts +1 -1
- package/src/console/observability/sse.ts +62 -8
- package/src/console/observability/store.ts +20 -4
- package/src/console/providers/catalog/catalog-projections.ts +1 -1
- package/src/console/providers/catalog/contracts.ts +39 -0
- package/src/console/providers/catalog/model-operations.ts +1 -1
- package/src/console/providers/catalog/provider-operations.ts +95 -17
- package/src/console/providers/catalog/public-model-store.ts +0 -0
- package/src/console/providers/catalog/routes.ts +27 -3
- package/src/console/providers/catalog/store.ts +147 -8
- package/src/console/providers/detail/routes.ts +1 -1
- package/src/console/providers/oauth/callback-listener.ts +100 -18
- package/src/console/providers/oauth/routes.ts +92 -13
- package/src/console/quota/account-quota-global.ts +41 -3
- package/src/console/quota/account-quota-shared.ts +10 -3
- package/src/console/quota/account-quota-tenant.ts +259 -66
- package/src/console/quota/daily-growth-job.ts +308 -0
- package/src/console/quota/native-identity.ts +2 -2
- package/src/console/quota/quota-view.ts +1 -1
- package/src/console/remote-support/routes.ts +1 -1
- package/src/console/remote-support/service.ts +2 -2
- package/src/console/routing/model/contracts.ts +1 -1
- package/src/console/routing/pools/contracts.ts +2 -2
- package/src/console/routing/pools/import-parse.ts +3 -3
- package/src/console/routing/pools/relay-deploy.ts +3 -3
- package/src/console/settings/contracts.ts +29 -2
- package/src/console/settings/store.ts +20 -0
- package/src/console/share/share-router.ts +100 -32
- package/src/console/share/share-stats.ts +4 -4
- package/src/console/share/share-usage.ts +1 -1
- package/src/console/shared/errors.ts +2 -2
- package/src/console/shared/object.ts +18 -0
- package/src/console/shared/read-through-cache.ts +4 -4
- package/src/console/shared/transaction.ts +14 -0
- package/src/console/system-prompt/contracts.ts +89 -12
- package/src/console/system-prompt/routes.ts +98 -45
- package/src/console/updates/apply-update.ts +244 -0
- package/src/console/updates/routes.ts +252 -97
- package/src/main.ts +11 -5
- package/src/network/http2-bidi.ts +1 -1
- package/src/network/ip-utils.ts +298 -0
- package/src/network/outbound-fetch.ts +1 -1
- package/src/network/pool/agent.ts +7 -6
- package/src/network/pool/byte-accounting.ts +1 -1
- package/src/network/pool/resolver.ts +16 -12
- package/src/network/pool/selector.ts +150 -136
- package/src/network/pool-health-machine.ts +1 -1
- package/src/network/rate-limit-verdict.ts +1 -1
- package/src/network/retry.ts +1 -1
- package/src/network/ssrf.ts +20 -71
- package/src/observability/event-log.ts +2 -2
- package/src/observability/event-query.ts +59 -291
- package/src/observability/log-sanitize.ts +1 -1
- package/src/observability/metrics.ts +4 -4
- package/src/observability/payload-capture.ts +6 -5
- package/src/observability/payload-store.ts +1 -1
- package/src/observability/runtime-metrics.ts +1 -1
- package/src/observability/telemetry-buffer.ts +12 -3
- package/src/observability/telemetry-reconciliation.ts +3 -3
- package/src/observability/token-speed.ts +1 -1
- package/src/persistence/connection-url.ts +1 -1
- package/src/persistence/db-handle.ts +1 -1
- package/src/persistence/db-pg.ts +2 -2
- package/src/persistence/db-sqlite.ts +3 -3
- package/src/persistence/dialect.ts +5 -1
- package/src/persistence/readiness.ts +1 -1
- package/src/persistence/redis.ts +1 -1
- package/src/persistence/schema.ts +113 -40
- package/src/persistence/shared-cache.ts +2 -2
- package/src/persistence/sort-order.ts +1 -1
- package/src/persistence/stores/api-key-store.ts +10 -1
- package/src/persistence/stores/share-store.ts +20 -8
- package/src/persistence/system-prompt-store.ts +63 -18
- package/src/persistence/tenant-preferences.ts +1 -1
- package/src/persistence/ttl-cache.ts +3 -3
- package/src/persistence/usage-flush.ts +7 -2
- package/src/protocol/canonical-model.ts +1 -1
- package/src/protocol/codecs.ts +6 -10
- package/src/protocol/dsml.ts +3 -49
- package/src/protocol/image-clamp.ts +2 -2
- package/src/protocol/reasoning-text.ts +1 -1
- package/src/protocol/reasoning.ts +7 -52
- package/src/protocol/registry.ts +50 -15
- package/src/protocol/request/chat.ts +46 -19
- package/src/protocol/request/codex.ts +1 -1
- package/src/protocol/request/messages.ts +46 -14
- package/src/protocol/request/responses.ts +19 -10
- package/src/protocol/response/chat.ts +25 -10
- package/src/protocol/response/codex.ts +1 -1
- package/src/protocol/response/messages.ts +1 -1
- package/src/protocol/response/omp-tool-call-markup.ts +1 -1
- package/src/protocol/response/responses.ts +1 -1
- package/src/protocol/sse-decoder.ts +50 -1
- package/src/protocol/stop-reason.ts +2 -2
- package/src/protocol/think-split.ts +347 -0
- package/src/protocol/translation/attachment-integrity.ts +3 -3
- package/src/protocol/translation/cache-controls.ts +8 -20
- package/src/protocol/translation/capabilities.ts +6 -6
- package/src/protocol/translation/dialect/deepseek.ts +10 -0
- package/src/protocol/translation/dialect/inband.ts +39 -0
- package/src/protocol/translation/dialect/model-thinking.ts +89 -0
- package/src/protocol/translation/dialect/qwen.ts +114 -0
- package/src/protocol/translation/dialect/resolver.ts +83 -0
- package/src/protocol/translation/dialect/rules.ts +93 -0
- package/src/protocol/translation/dialect/thinking-ladder.ts +245 -0
- package/src/protocol/translation/dialect/types.ts +58 -0
- package/src/protocol/translation/quirks.ts +18 -4
- package/src/protocol/translation/thinking.ts +12 -252
- package/src/protocol/translation/tool-id.ts +3 -3
- package/src/protocol/translation/tool-repair.ts +1 -1
- package/src/protocol/upstream/post-json.ts +6 -1
- package/src/providers/adapters/compatible-adapter.ts +42 -3
- package/src/providers/authentication/jwt-validator.ts +0 -5
- package/src/providers/authentication/oauth-client.ts +254 -253
- package/src/providers/authentication/oauth-flow-store.ts +42 -7
- package/src/providers/bundled-discovery-providers.ts +6 -30
- package/src/providers/configured-openai-providers.ts +82 -97
- package/src/providers/default-registry.ts +23 -35
- package/src/providers/discovery/catalog/catalog-format.ts +13 -2
- package/src/providers/discovery/discovery-types.ts +0 -1
- package/src/providers/discovery/openai-model-discovery.ts +2 -2
- package/src/providers/discovery/probe-phases.ts +48 -13
- package/src/providers/discovery/probing-service.ts +6 -8
- package/src/providers/integrations/agentrouter/agentrouter.ts +12 -2
- package/src/providers/integrations/antigravity/antigravity-oauth.ts +0 -1
- package/src/providers/integrations/antigravity/antigravity-protocol.ts +0 -14
- package/src/providers/integrations/antigravity/antigravity.ts +2 -2
- package/src/providers/integrations/buddy/buddy-catalog-shared.ts +1 -1
- package/src/providers/integrations/buddy/buddy-chat-shared.ts +20 -4
- package/src/providers/integrations/buddy/buddy-cli-headers.ts +19 -52
- package/src/providers/integrations/buddy/buddy-identity.ts +86 -0
- package/src/providers/integrations/buddy/buddy-oauth-shared.ts +1 -1
- package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +1 -2
- package/src/providers/integrations/buddy/codebuddy/provider.ts +4 -15
- package/src/providers/integrations/buddy/codebuddy/shared.ts +30 -9
- package/src/providers/integrations/buddy/workbuddy/provider.ts +5 -9
- package/src/providers/integrations/buddy/workbuddy/shared.ts +21 -3
- package/src/providers/integrations/claude/claude-cch.ts +148 -148
- package/src/providers/integrations/claude/fallback-credit.ts +3 -7
- package/src/providers/integrations/cline/cline.ts +1 -1
- package/src/providers/integrations/codex/codex-device-code.ts +101 -101
- package/src/providers/integrations/codex/codex-oauth.ts +0 -1
- package/src/providers/integrations/commandcode/commandcode.ts +75 -115
- package/src/providers/integrations/commandcode/quota.ts +131 -51
- package/src/providers/integrations/cursor/cursor-http2.ts +0 -3
- package/src/providers/integrations/cursor/cursor-quota.ts +2 -6
- package/src/providers/integrations/cursor/cursor-request.ts +0 -87
- package/src/providers/integrations/cursor/cursor-shared.ts +0 -4
- package/src/providers/integrations/cursor/cursor.ts +38 -5
- package/src/providers/integrations/cursor/protocol/framing.ts +0 -56
- package/src/providers/integrations/cursor/protocol/messages.ts +0 -96
- package/src/providers/integrations/factory/factory.ts +9 -9
- package/src/providers/integrations/github/github-discovery.ts +55 -10
- package/src/providers/integrations/github/github-oauth.ts +6 -5
- package/src/providers/integrations/github/github.ts +41 -14
- package/src/providers/integrations/grok/grok-oauth.ts +261 -261
- package/src/providers/integrations/grok/grok-quota-frame.ts +1 -1
- package/src/providers/integrations/grok/grok-quota.ts +16 -7
- package/src/providers/integrations/grok/grok-ratelimit-store.ts +0 -5
- package/src/providers/integrations/grok/grok-ratelimit.ts +3 -0
- package/src/providers/integrations/grok/grok.ts +86 -36
- package/src/providers/integrations/kilo/kilo-oauth.ts +1 -1
- package/src/providers/integrations/kiro/kiro-cache.ts +31 -0
- package/src/providers/integrations/kiro/kiro-catalog.ts +31 -5
- package/src/providers/integrations/kiro/kiro-discovery.ts +47 -43
- package/src/providers/integrations/kiro/kiro-machine-id.ts +6 -3
- package/src/providers/integrations/kiro/kiro-oauth.ts +523 -255
- package/src/providers/integrations/kiro/kiro-quota.ts +35 -47
- package/src/providers/integrations/kiro/kiro-request.ts +8 -11
- package/src/providers/integrations/kiro/kiro-stream.ts +13 -0
- package/src/providers/integrations/kiro/kiro.ts +81 -68
- package/src/providers/integrations/muse/muse-oauth.ts +0 -1
- package/src/providers/integrations/muse/muse-quota.ts +0 -4
- package/src/providers/integrations/opencode/fingerprint.ts +7 -5
- package/src/providers/integrations/opencode/provider.ts +17 -24
- package/src/providers/integrations/qoder/qoder-catalog.ts +85 -8
- package/src/providers/integrations/qoder/qoder-oauth.ts +165 -25
- package/src/providers/integrations/qoder/qoder-quota.ts +48 -6
- package/src/providers/integrations/qoder/qoder-realm.ts +10 -31
- package/src/providers/integrations/qoder/qoder-sse.ts +159 -0
- package/src/providers/integrations/qoder/qoder-umid.ts +21 -25
- package/src/providers/integrations/qoder/qoder-virtual.ts +12 -0
- package/src/providers/integrations/qoder/qoder.ts +354 -62
- package/src/providers/integrations/shared/connect.ts +0 -54
- package/src/providers/integrations/shared/hash.ts +15 -0
- package/src/providers/integrations/shared/session-cache.ts +136 -0
- package/src/providers/integrations/trae/trae-cache.ts +7 -87
- package/src/providers/integrations/trae/trae-catalog.ts +1 -6
- package/src/providers/integrations/trae/trae-protocol.ts +17 -8
- package/src/providers/integrations/trae/trae-realm.ts +0 -7
- package/src/providers/integrations/trae/trae.ts +53 -10
- package/src/providers/integrations/xai/xai-oauth.ts +5 -5
- package/src/providers/integrations/zcode-start-plan/gifts.ts +265 -0
- package/src/providers/integrations/zcode-start-plan/oauth.ts +154 -0
- package/src/providers/integrations/zcode-start-plan/provider.ts +131 -0
- package/src/providers/integrations/zcode-start-plan/quota.ts +152 -0
- package/src/providers/integrations/zcode-start-plan/zcode_prompt.json +10 -0
- package/src/providers/operations/account-error-classification.ts +131 -116
- package/src/providers/operations/account-health-service.ts +31 -39
- package/src/providers/operations/account-reset-service.ts +58 -1
- package/src/providers/operations/cli-platform.ts +31 -5
- package/src/providers/operations/client-version-resolver.ts +4 -6
- package/src/providers/operations/client-versions.ts +10 -7
- package/src/providers/operations/provider-catalog-seeder.ts +2 -0
- package/src/providers/operations/provider-catalog-service.ts +18 -2
- package/src/providers/operations/provider-credential-service.ts +19 -2
- package/src/providers/provider-contracts.ts +18 -7
- package/src/providers/provider-metadata.ts +17 -2
- package/src/providers/provider-model-definition.ts +22 -1
- package/src/providers/provider-probe.ts +4 -2
- package/src/providers/provider-usage.ts +81 -16
- package/src/providers/quota/cache.ts +29 -0
- package/src/providers/quota/quota-contracts.ts +4 -0
- package/src/providers/quota/refresh.ts +27 -4
- package/src/providers/quota/snapshot.ts +94 -0
- package/src/providers/search/search-provider.ts +1 -3
- package/src/providers/search/search-providers.ts +2 -1
- package/src/security/abuse.ts +4 -4
- package/src/security/admission/service.ts +2 -2
- package/src/security/admission/ttl.ts +61 -61
- package/src/security/api-key-auth.ts +16 -4
- package/src/security/client-router-fingerprint.ts +4 -4
- package/src/security/credential-codec.ts +3 -3
- package/src/security/csrf.ts +2 -2
- package/src/security/ip-boundary.ts +16 -189
- package/src/security/model-access-rule.ts +1 -1
- package/src/security/outbound-headers.ts +18 -0
- package/src/shared/invoke-guard.ts +47 -0
- package/src/transport/contracts.ts +13 -4
- package/src/transport/dispatch/attempt-finalize.ts +1 -1
- package/src/transport/dispatch/capture-policy.ts +34 -37
- package/src/transport/dispatch/empty-stream.ts +23 -20
- package/src/transport/dispatch/generation.ts +25 -45
- package/src/transport/dispatch/native-shared.ts +127 -0
- package/src/transport/dispatch/proxy-request.ts +3 -1
- package/src/transport/dispatch/responses-compact.ts +25 -46
- package/src/transport/dispatch/streaming/encoder.ts +18 -72
- package/src/transport/dispatch/streaming/finalize.ts +2 -2
- package/src/transport/dispatch/streaming/stream.ts +7 -3
- package/src/transport/dispatch/streaming/thinking-loop.ts +1 -1
- package/src/transport/dispatch/systemone.ts +20 -48
- package/src/transport/dispatch/token-count.ts +41 -38
- package/src/transport/dispatch/websearch.ts +19 -45
- package/src/transport/drain-endpoint.ts +1 -1
- package/src/transport/failure-policy.ts +6 -0
- package/src/transport/guards/auth-guard.ts +0 -6
- package/src/transport/guards/body-policy.ts +3 -3
- package/src/transport/guards/headroom-guard.ts +1 -1
- package/src/transport/guards/pipeline.ts +1 -1
- package/src/transport/guards/request-context.ts +12 -17
- package/src/transport/request/preparer.ts +286 -7
- package/src/transport/request/rtk/compress-request.ts +3 -3
- package/src/transport/request/rtk/rtk-constants.ts +2 -2
- package/src/transport/request/rtk/rtk-filters.ts +8 -8
- package/src/transport/request/state.ts +1 -13
- package/src/transport/request/system-prompt-injection.ts +74 -11
- package/src/transport/request/tenant-preferences.ts +22 -0
- package/src/transport/routing/candidate-index.ts +2 -2
- package/src/transport/routing/round-robin.ts +1 -1
- package/src/transport/routing/route-catalog.ts +17 -18
- package/src/transport/routing/route-model.ts +126 -33
- package/src/transport/routing/router.ts +152 -37
- package/src/transport/shutdown-notice.ts +2 -2
- package/src/transport/surface/adapters.ts +3 -3
- package/src/transport/surface/chat/encode.ts +7 -7
- package/src/transport/surface/chat/parse.ts +7 -7
- package/src/transport/surface/completion.ts +2 -2
- package/src/transport/surface/content-parts.ts +1 -1
- package/src/transport/surface/dialects.ts +2 -2
- package/src/transport/surface/messages/parse.ts +3 -3
- package/src/transport/surface/responses/adapter.ts +1 -1
- package/src/transport/surface/responses/errors.ts +0 -9
- package/src/transport/vision-handoff-cache.ts +58 -0
- package/src/transport/vision-handoff.ts +376 -0
- package/src/workers/account-identity-sweep.ts +2 -2
- package/src/workers/buddy-growth-tasks.ts +392 -346
- package/src/workers/daily-checkin.ts +102 -49
- package/src/workers/maintenance-lease.ts +2 -35
- package/src/workers/oauth-refresh-sweep.ts +8 -1
- package/src/workers/quota-refresh-sweep.ts +120 -82
- package/src/workers/scheduler.ts +67 -4
- package/src/workers/sweep-runner.ts +24 -5
- package/src/workers/zcode-gift-claim.ts +108 -0
- package/dist/dashboard/assets/AccountCooldown-Bjo92Qat.js +0 -1
- package/dist/dashboard/assets/AccountCooldown-Bjo92Qat.js.gz +0 -0
- package/dist/dashboard/assets/BannedPage-ChG5svFb.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-BIcWQun7.js.gz +0 -0
- package/dist/dashboard/assets/CliToolsPage-Bf2VelDe.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-DCgm5g6g.js +0 -1
- package/dist/dashboard/assets/CombosPage-DCgm5g6g.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-a8mD0QF1.js +0 -1
- package/dist/dashboard/assets/ConsoleLogPage-a8mD0QF1.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-ggeMSjte.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-DzSWzE_j.js +0 -1
- package/dist/dashboard/assets/HealthEventsModal-DzSWzE_j.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-U6AKaJcF.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-BMS2t8BA.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-D2D4IiHl.js +0 -1
- package/dist/dashboard/assets/OverviewPage-D2D4IiHl.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-CrNf7j_X.js +0 -5
- package/dist/dashboard/assets/ProviderDetailPage-CrNf7j_X.js.gz +0 -0
- package/dist/dashboard/assets/ProviderIcon-FB6yS4v1.js +0 -1
- package/dist/dashboard/assets/ProviderIcon-FB6yS4v1.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-BSDbquc1.js +0 -1
- package/dist/dashboard/assets/ProvidersPage-BSDbquc1.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-lsrx7NYg.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-L0skzML8.js +0 -1
- package/dist/dashboard/assets/QuotaPage-L0skzML8.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-CmAbjRHW.js +0 -1
- package/dist/dashboard/assets/SettingsPage-CmAbjRHW.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-f0DE1h0-.js.gz +0 -0
- package/dist/dashboard/assets/SortableList-ClzpwyET.js +0 -1
- package/dist/dashboard/assets/SortableList-ClzpwyET.js.gz +0 -0
- package/dist/dashboard/assets/StudioPage-Cq5tW7hW.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-Dfc9rWQn.js +0 -56
- package/dist/dashboard/assets/UsagePage-Dfc9rWQn.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-C38iXoO4.js +0 -1
- package/dist/dashboard/assets/api-keys-C38iXoO4.js.gz +0 -0
- package/dist/dashboard/assets/app-a159k0ub.css +0 -2
- package/dist/dashboard/assets/app-a159k0ub.css.gz +0 -0
- package/dist/dashboard/assets/assert-DbRA8EG2.js +0 -15
- package/dist/dashboard/assets/assert-DbRA8EG2.js.gz +0 -0
- package/dist/dashboard/assets/badge-D9CS6dZk.js.gz +0 -0
- package/dist/dashboard/assets/banner-DympBNq4.js.gz +0 -0
- package/dist/dashboard/assets/card-ibRWU_uT.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-63qBGaRw.js +0 -1
- package/dist/dashboard/assets/cli-tools-63qBGaRw.js.gz +0 -0
- package/dist/dashboard/assets/clipboard-button-DJm-9pz7.js.gz +0 -0
- package/dist/dashboard/assets/dialog-8fiE6tec.js.gz +0 -0
- package/dist/dashboard/assets/drawer-CqGYeOrd.js.gz +0 -0
- package/dist/dashboard/assets/entry-BSAsYsmV.js.gz +0 -0
- package/dist/dashboard/assets/entry-CiIQULZD.js +0 -4
- package/dist/dashboard/assets/entry-CiIQULZD.js.gz +0 -0
- package/dist/dashboard/assets/entry-Daz0lJ-W.js +0 -1
- package/dist/dashboard/assets/entry-Daz0lJ-W.js.gz +0 -0
- package/dist/dashboard/assets/formatters-D-MTJxMr.js.gz +0 -0
- package/dist/dashboard/assets/github-badge-CPK47xQx.js.gz +0 -0
- package/dist/dashboard/assets/index-DBNPOfhV.js.gz +0 -0
- package/dist/dashboard/assets/inline-BT0zntEr.js.gz +0 -0
- package/dist/dashboard/assets/input-Da2ZViWg.js.gz +0 -0
- package/dist/dashboard/assets/layout-BXNeLw6M.js.gz +0 -0
- package/dist/dashboard/assets/list-row-BycOrrIV.js.gz +0 -0
- package/dist/dashboard/assets/live-DCQNZpF7.js +0 -1
- package/dist/dashboard/assets/live-DCQNZpF7.js.gz +0 -0
- package/dist/dashboard/assets/network-9Yj2ZyLo.js +0 -1
- package/dist/dashboard/assets/network-9Yj2ZyLo.js.gz +0 -0
- package/dist/dashboard/assets/numbers-CEagj6ZZ.js +0 -2
- package/dist/dashboard/assets/numbers-CEagj6ZZ.js.gz +0 -0
- package/dist/dashboard/assets/quota-Caw7wqci.js +0 -1
- package/dist/dashboard/assets/quota-Caw7wqci.js.gz +0 -0
- package/dist/dashboard/assets/routing-CO9zCUus.js +0 -1
- package/dist/dashboard/assets/routing-CO9zCUus.js.gz +0 -0
- package/dist/dashboard/assets/select-BIYIBkMU.js.gz +0 -0
- package/dist/dashboard/assets/settings-jS3-PNSA.js +0 -1
- package/dist/dashboard/assets/settings-jS3-PNSA.js.gz +0 -0
- package/dist/dashboard/assets/stack-DfgnFDWY.js.gz +0 -0
- package/dist/dashboard/assets/state-CkfdYWel.js.gz +0 -0
- package/dist/dashboard/assets/stream-retry-5QnVuSSo.js +0 -1
- package/dist/dashboard/assets/stream-retry-5QnVuSSo.js.gz +0 -0
- package/dist/dashboard/assets/switch-SYm237HG.js.gz +0 -0
- package/dist/dashboard/assets/useMutation-BQqCRNIi.js.gz +0 -0
- package/src/network/response-headers.ts +0 -39
- package/src/protocol/response/dsml.ts +0 -38
- package/src/providers/integrations/xiaomi-mimo/think-stream.ts +0 -229
- /package/dist/dashboard/assets/{app-BayTTy01.js → app-Bgd3zANQ.js} +0 -0
- /package/dist/dashboard/assets/{app-BayTTy01.js.gz → app-Bgd3zANQ.js.gz} +0 -0
- /package/dist/dashboard/assets/brand/{og_bansos.webp → og_bansos_initial.webp} +0 -0
- /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(() =>
|
|
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
|
-
|
|
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: {
|
|
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
|
-
|
|
840
|
-
|
|
841
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
19
|
-
typeof
|
|
20
|
-
typeof
|
|
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
|
-
|
|
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
|
|
34
|
-
typeof
|
|
35
|
-
typeof
|
|
36
|
-
typeof
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
16
|
+
import { asConsoleDb } from "../shared/transaction";
|
|
17
|
+
interface LoginRequest {
|
|
17
18
|
readonly username: string;
|
|
18
19
|
readonly password: string;
|
|
19
20
|
}
|
|
20
21
|
|
|
21
|
-
|
|
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
|
-
|
|
51
|
+
interface SetupRequest {
|
|
51
52
|
readonly password: string;
|
|
52
53
|
readonly username?: string;
|
|
53
54
|
readonly display_name?: string;
|
|
54
55
|
}
|
|
55
56
|
|
|
56
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
90
|
+
interface LogoutResponse {
|
|
90
91
|
readonly status: "success";
|
|
91
92
|
}
|
|
92
93
|
|
|
93
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 {
|
|
26
|
+
import { tablesForSection } from "./contracts";
|
|
27
27
|
import { compressBackupStream, exportBackupStream } from "./store";
|
|
28
28
|
import type { CartethyiaDatabase } from "../../persistence/db-handle";
|
|
29
29
|
|
|
30
|
-
|
|
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
|
|
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:
|
|
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
|
-
|
|
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}"`);
|