From 48ace614a3a0b983ceb5ddb3145e5db69dad3589 Mon Sep 17 00:00:00 2001 From: WebPerson Date: Sat, 29 Aug 2026 13:16:47 -0500 Subject: [PATCH 01/22] fix(barrel): migrate src/app/ off the localDb barrel to direct db imports (#59) Replace all 122 src/app imports from @/lib/localDb with direct imports from the owning @/lib/db/* modules, per AGENTS.md G14. The barrel is a pure re-export layer; every consumer now imports the domain module that actually owns each symbol (settings, providers, combos, apiKeys, ...), which shrinks the module graph and removes the god-coupling point. Mechanical codemod only - no behavior change. Alias re-exports preserved (e.g. updateWebhookRecord -> updateWebhook as updateWebhookRecord), type-only imports stay type-only, dynamic imports keep lazy semantics, and test vi.mock targets re-point at the real module. Prune the 121 now-satisfied no-restricted-imports suppressions for the migrated files. Verified: typecheck:core green, eslint src/app green, check:cycles green, focused db unit tests pass (124/128; the 4 failures are the pre-existing omp.ts suite, identical on clean HEAD). --- config/quality/eslint-suppressions.json | 582 +----------------- src/app/(dashboard)/home/page.tsx | 2 +- src/app/api/auth/oidc/callback/route.ts | 3 +- src/app/api/auth/oidc/login/route.ts | 2 +- src/app/api/batches/[id]/route.ts | 2 +- src/app/api/batches/route.ts | 2 +- src/app/api/cache/route.ts | 2 +- .../api/cli-tools/claude-settings/route.ts | 2 +- src/app/api/cli-tools/codex-settings/route.ts | 2 +- src/app/api/cli-tools/keys/route.ts | 2 +- src/app/api/cli/connect/route.ts | 2 +- src/app/api/combos/reorder/route.ts | 3 +- src/app/api/combos/route.ts | 9 +- src/app/api/combos/test/route.ts | 3 +- src/app/api/db-backups/route.ts | 2 +- src/app/api/evals/route.ts | 3 +- src/app/api/evals/suites/[suiteId]/route.ts | 2 +- src/app/api/evals/suites/route.ts | 2 +- src/app/api/files/[id]/content/route.ts | 2 +- src/app/api/files/route.ts | 2 +- src/app/api/keys/[id]/regenerate/route.ts | 2 +- src/app/api/keys/[id]/reveal/route.ts | 2 +- src/app/api/keys/[id]/route.ts | 15 +- src/app/api/keys/groups/[id]/keys/route.ts | 7 +- .../api/keys/groups/[id]/permissions/route.ts | 2 +- src/app/api/keys/groups/[id]/route.ts | 2 +- src/app/api/keys/groups/route.ts | 2 +- src/app/api/keys/route.ts | 4 +- src/app/api/memory/reindex/route.ts | 4 +- src/app/api/middleware/hooks/[name]/route.ts | 7 +- src/app/api/middleware/hooks/route.ts | 7 +- .../api/model-combo-mappings/[id]/route.ts | 2 +- src/app/api/model-combo-mappings/route.ts | 2 +- src/app/api/models/test-all/route.ts | 2 +- src/app/api/monitoring/health/route.ts | 3 +- src/app/api/pricing/models/route.ts | 3 +- src/app/api/pricing/route.ts | 2 +- src/app/api/provider-models/route.ts | 2 +- src/app/api/providers/[id]/login/route.ts | 3 +- src/app/api/providers/[id]/models/route.ts | 9 +- src/app/api/providers/[id]/refresh/route.ts | 2 +- src/app/api/providers/[id]/route.ts | 9 +- .../api/providers/[id]/sync-models/route.ts | 2 +- src/app/api/providers/[id]/test/route.ts | 9 +- src/app/api/providers/bulk/route.ts | 3 +- src/app/api/providers/client/route.ts | 2 +- .../command-code/auth/apply/route.ts | 7 +- src/app/api/providers/import/route.ts | 3 +- src/app/api/providers/quota-windows/route.ts | 2 +- src/app/api/providers/validate/route.ts | 3 +- src/app/api/quota/groups/[id]/route.ts | 8 +- src/app/api/quota/groups/route.ts | 2 +- src/app/api/quota/keys/[id]/models/route.ts | 7 +- .../api/quota/plans/[connectionId]/route.ts | 8 +- src/app/api/quota/plans/route.ts | 2 +- src/app/api/quota/pools/[id]/route.ts | 2 +- src/app/api/quota/pools/[id]/usage/route.ts | 2 +- src/app/api/quota/pools/route.ts | 7 +- src/app/api/quota/preview/route.ts | 2 +- src/app/api/rate-limits/route.ts | 2 +- src/app/api/resilience/route.ts | 3 +- src/app/api/settings/__tests__/memory.test.ts | 4 +- .../api/settings/__tests__/settings.test.ts | 8 +- src/app/api/settings/authz-inventory/route.ts | 2 +- .../settings/auto-disable-accounts/route.ts | 2 +- src/app/api/settings/combo-defaults/route.ts | 2 +- src/app/api/settings/export-json/route.ts | 19 +- .../free-proxies/[id]/add-to-pool/route.ts | 2 +- .../free-proxies/bulk-add-to-pool/route.ts | 2 +- src/app/api/settings/free-proxies/route.ts | 2 +- .../api/settings/free-proxies/stats/route.ts | 2 +- src/app/api/settings/memory/route.ts | 4 +- src/app/api/settings/payload-rules/route.ts | 2 +- .../proxies/[id]/repair-relay/route.ts | 2 +- .../api/settings/proxies/assignments/route.ts | 3 +- .../api/settings/proxies/auto-test/route.ts | 2 +- .../settings/proxies/batch-activate/route.ts | 26 +- .../settings/proxies/batch-delete/route.ts | 33 +- .../api/settings/proxies/bulk-assign/route.ts | 2 +- .../api/settings/proxies/bulk-import/route.ts | 2 +- src/app/api/settings/proxies/health/route.ts | 2 +- src/app/api/settings/proxies/migrate/route.ts | 2 +- src/app/api/settings/proxies/pool/route.ts | 7 +- src/app/api/settings/proxies/route.ts | 2 +- .../settings/proxy/cloudflare-deploy/route.ts | 2 +- .../api/settings/proxy/deno-deploy/route.ts | 2 +- src/app/api/settings/proxy/route.ts | 9 +- src/app/api/settings/proxy/test/route.ts | 4 +- .../api/settings/proxy/vercel-deploy/route.ts | 2 +- src/app/api/settings/qdrant/route.ts | 4 +- src/app/api/settings/quota-store/route.ts | 2 +- src/app/api/settings/system-prompt/route.ts | 2 +- src/app/api/settings/thinking-budget/route.ts | 2 +- src/app/api/skills/collect/chaos/route.ts | 7 +- src/app/api/sync/cloud/route.ts | 25 +- src/app/api/token-health/route.ts | 2 +- .../api/tools/agent-bridge/server/route.ts | 10 +- src/app/api/translator/send/route.ts | 2 +- src/app/api/translator/translate/route.ts | 2 +- src/app/api/usage/quota/route.ts | 2 +- src/app/api/usage/token-limits/route.ts | 5 +- src/app/api/v1/_helpers/apiKeyScope.ts | 2 +- src/app/api/v1/audio/transcriptions/route.ts | 3 +- src/app/api/v1/batches/[id]/cancel/route.ts | 2 +- src/app/api/v1/batches/[id]/route.ts | 2 +- .../api/v1/batches/delete-completed/route.ts | 2 +- src/app/api/v1/batches/route.ts | 3 +- src/app/api/v1/combos/route.ts | 2 +- src/app/api/v1/files/[id]/content/route.ts | 2 +- src/app/api/v1/files/[id]/route.ts | 2 +- src/app/api/v1/files/route.ts | 2 +- src/app/api/v1/images/edits/route.ts | 2 +- .../management/proxies/assignments/route.ts | 3 +- .../management/proxies/bulk-assign/route.ts | 2 +- .../api/v1/management/proxies/health/route.ts | 2 +- src/app/api/v1/management/proxies/route.ts | 2 +- src/app/api/v1/me/status/route.ts | 2 +- src/app/api/v1/models/catalog.ts | 34 +- src/app/api/v1beta/models/route.ts | 2 +- src/app/api/webhooks/[id]/deliveries/route.ts | 3 +- src/app/api/webhooks/[id]/route.ts | 2 +- src/app/api/webhooks/[id]/test/route.ts | 4 +- src/app/api/webhooks/route.ts | 2 +- 123 files changed, 295 insertions(+), 805 deletions(-) diff --git a/config/quality/eslint-suppressions.json b/config/quality/eslint-suppressions.json index ad571e9e24ab..e234b3973d2a 100644 --- a/config/quality/eslint-suppressions.json +++ b/config/quality/eslint-suppressions.json @@ -1223,126 +1223,31 @@ "count": 1 } }, - "src/app/(dashboard)/home/page.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "src/app/api/assess/route.ts": { "@typescript-eslint/no-unused-vars": { "count": 1 } }, - "src/app/api/auth/oidc/callback/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/auth/oidc/login/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/batches/[id]/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/batches/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/cache/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/cli-tools/claude-settings/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/app/api/cli-tools/cline-settings/route.ts": { "@typescript-eslint/no-unused-vars": { "count": 1 } }, - "src/app/api/cli-tools/codex-settings/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/cli-tools/keys/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/app/api/cli-tools/kilo-settings/route.ts": { "@typescript-eslint/no-unused-vars": { "count": 1 } }, - "src/app/api/cli/connect/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/app/api/combos/duplicate/route.ts": { "@typescript-eslint/no-unused-vars": { "count": 2 } }, - "src/app/api/combos/reorder/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/combos/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/combos/test/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/app/api/compression/compare/verify/route.ts": { "no-restricted-imports": { "count": 1 } }, - "src/app/api/db-backups/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/evals/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/evals/suites/[suiteId]/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/evals/suites/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/files/[id]/content/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/files/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/app/api/github-skills/route.ts": { "@typescript-eslint/no-unused-vars": { "count": 1 @@ -1353,61 +1258,24 @@ "count": 1 } }, - "src/app/api/keys/[id]/regenerate/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/keys/[id]/reveal/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/keys/[id]/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/app/api/keys/groups/[id]/keys/route.ts": { "@typescript-eslint/no-unused-vars": { "count": 4 - }, - "no-restricted-imports": { - "count": 1 } }, "src/app/api/keys/groups/[id]/permissions/route.ts": { "@typescript-eslint/no-unused-vars": { "count": 5 - }, - "no-restricted-imports": { - "count": 1 } }, "src/app/api/keys/groups/[id]/route.ts": { "@typescript-eslint/no-unused-vars": { "count": 9 - }, - "no-restricted-imports": { - "count": 1 } }, "src/app/api/keys/groups/route.ts": { "@typescript-eslint/no-unused-vars": { "count": 3 - }, - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/keys/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/memory/reindex/route.ts": { - "no-restricted-imports": { - "count": 1 } }, "src/app/api/memory/route.ts": { @@ -1418,34 +1286,6 @@ "src/app/api/middleware/hooks/[name]/route.ts": { "@typescript-eslint/no-unused-vars": { "count": 1 - }, - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/middleware/hooks/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/model-combo-mappings/[id]/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/model-combo-mappings/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/models/test-all/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/monitoring/health/route.ts": { - "no-restricted-imports": { - "count": 1 } }, "src/app/api/oauth/[provider]/[action]/route.ts": { @@ -1458,26 +1298,6 @@ "count": 1 } }, - "src/app/api/pricing/models/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/pricing/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/provider-models/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/providers/[id]/login/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/app/api/providers/[id]/models/discovery/providerModelsConfig.ts": { "@typescript-eslint/no-unused-vars": { "count": 1 @@ -1486,29 +1306,6 @@ "src/app/api/providers/[id]/models/route.ts": { "@typescript-eslint/no-unused-vars": { "count": 9 - }, - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/providers/[id]/refresh/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/providers/[id]/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/providers/[id]/sync-models/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/providers/[id]/test/route.ts": { - "no-restricted-imports": { - "count": 1 } }, "src/app/api/providers/bulk-web-session/route.ts": { @@ -1516,96 +1313,11 @@ "count": 1 } }, - "src/app/api/providers/bulk/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/providers/client/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/providers/command-code/auth/apply/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/providers/import/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/providers/quota-windows/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/providers/validate/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/quota/groups/[id]/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/quota/groups/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/quota/keys/[id]/models/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/quota/plans/[connectionId]/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/quota/plans/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/quota/pools/[id]/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/quota/pools/[id]/usage/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/quota/pools/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/quota/preview/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/rate-limits/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/app/api/relay/tokens/[id]/route.ts": { "@typescript-eslint/no-unused-vars": { "count": 2 } }, - "src/app/api/resilience/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/app/api/search/stats/route.ts": { "@typescript-eslint/no-unused-vars": { "count": 1 @@ -1616,162 +1328,14 @@ "count": 1 } }, - "src/app/api/settings/__tests__/memory.test.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/settings/__tests__/settings.test.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/settings/authz-inventory/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/settings/auto-disable-accounts/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/settings/combo-defaults/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/settings/export-json/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/settings/free-proxies/[id]/add-to-pool/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/settings/free-proxies/bulk-add-to-pool/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/settings/free-proxies/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/settings/free-proxies/stats/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/settings/memory/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/app/api/settings/obsidian/route.ts": { "@typescript-eslint/no-unused-vars": { "count": 2 } }, - "src/app/api/settings/payload-rules/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/settings/proxies/[id]/repair-relay/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/settings/proxies/assignments/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/settings/proxies/auto-test/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/settings/proxies/batch-activate/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/settings/proxies/batch-delete/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/settings/proxies/bulk-assign/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/settings/proxies/bulk-import/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/settings/proxies/health/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/settings/proxies/migrate/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/settings/proxies/pool/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/settings/proxies/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/settings/proxy/cloudflare-deploy/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/settings/proxy/deno-deploy/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/settings/proxy/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/settings/proxy/test/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/settings/proxy/vercel-deploy/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/app/api/settings/qdrant/route.ts": { "@typescript-eslint/no-unused-vars": { "count": 1 - }, - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/settings/quota-store/route.ts": { - "no-restricted-imports": { - "count": 1 } }, "src/app/api/settings/route.ts": { @@ -1779,27 +1343,9 @@ "count": 1 } }, - "src/app/api/settings/system-prompt/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/app/api/settings/thinking-budget/route.ts": { "@typescript-eslint/no-unused-vars": { "count": 2 - }, - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/skills/collect/chaos/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/sync/cloud/route.ts": { - "no-restricted-imports": { - "count": 1 } }, "src/app/api/sync/initialize/route.ts": { @@ -1812,114 +1358,34 @@ "count": 1 } }, - "src/app/api/token-health/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/tools/agent-bridge/server/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/app/api/tools/traffic-inspector/ws/route.ts": { "@typescript-eslint/no-unused-vars": { "count": 1 } }, - "src/app/api/translator/send/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/translator/translate/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/app/api/usage/analytics/route.ts": { "@typescript-eslint/no-unused-vars": { "count": 1 } }, - "src/app/api/usage/quota/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/usage/token-limits/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/v1/_helpers/apiKeyScope.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/app/api/v1/audio/speech/route.ts": { "@typescript-eslint/no-unused-vars": { "count": 1 } }, - "src/app/api/v1/audio/transcriptions/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/v1/batches/[id]/cancel/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/v1/batches/[id]/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/v1/batches/delete-completed/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/v1/batches/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/app/api/v1/chat/completions/route.ts": { "@typescript-eslint/no-unused-vars": { "count": 1 } }, - "src/app/api/v1/combos/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/app/api/v1/embeddings/route.ts": { "@typescript-eslint/no-unused-vars": { "count": 1 } }, - "src/app/api/v1/files/[id]/content/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/v1/files/[id]/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/v1/files/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/app/api/v1/images/edits/route.ts": { "no-restricted-imports": { - "count": 2 + "count": 1 } }, "src/app/api/v1/images/generations/route.ts": { @@ -1927,26 +1393,6 @@ "count": 1 } }, - "src/app/api/v1/management/proxies/assignments/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/v1/management/proxies/bulk-assign/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/v1/management/proxies/health/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/v1/management/proxies/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/app/api/v1/messages/count_tokens/route.ts": { "@typescript-eslint/no-unused-vars": { "count": 2 @@ -1963,9 +1409,6 @@ "src/app/api/v1/models/catalog.ts": { "@typescript-eslint/no-unused-vars": { "count": 10 - }, - "no-restricted-imports": { - "count": 1 } }, "src/app/api/v1/models/catalogCache.ts": { @@ -2033,32 +1476,9 @@ "count": 1 } }, - "src/app/api/v1beta/models/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/webhooks/[id]/deliveries/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/webhooks/[id]/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/app/api/webhooks/[id]/test/route.ts": { "@typescript-eslint/no-unused-vars": { "count": 1 - }, - "no-restricted-imports": { - "count": 2 - } - }, - "src/app/api/webhooks/route.ts": { - "no-restricted-imports": { - "count": 1 } }, "src/app/docs/components/FeedbackWidget.tsx": { diff --git a/src/app/(dashboard)/home/page.tsx b/src/app/(dashboard)/home/page.tsx index dc9433973e25..7580d67c1699 100644 --- a/src/app/(dashboard)/home/page.tsx +++ b/src/app/(dashboard)/home/page.tsx @@ -1,5 +1,5 @@ import { getMachineId } from "@/shared/utils/machine"; -import { getSettings } from "@/lib/localDb"; +import { getSettings } from "@/lib/db/settings"; import HomePageClient from "../dashboard/HomePageClient"; import BootstrapBanner from "../dashboard/BootstrapBanner"; import KimiSponsorBanner from "../dashboard/KimiSponsorBanner"; diff --git a/src/app/api/auth/oidc/callback/route.ts b/src/app/api/auth/oidc/callback/route.ts index fbd5a8df710f..45e2836f9484 100644 --- a/src/app/api/auth/oidc/callback/route.ts +++ b/src/app/api/auth/oidc/callback/route.ts @@ -1,5 +1,6 @@ import { NextResponse } from "next/server"; -import { getCachedSettings, updateSettings } from "@/lib/localDb"; +import { getCachedSettings } from "@/lib/db/readCache"; +import { updateSettings } from "@/lib/db/settings"; import { SignJWT, jwtVerify, createRemoteJWKSet } from "jose"; import { cookies } from "next/headers"; import { timingSafeCompare } from "@/shared/utils/timingSafeCompare"; diff --git a/src/app/api/auth/oidc/login/route.ts b/src/app/api/auth/oidc/login/route.ts index a49dbbe431d8..fdb7387dcdb6 100644 --- a/src/app/api/auth/oidc/login/route.ts +++ b/src/app/api/auth/oidc/login/route.ts @@ -1,5 +1,5 @@ import { NextResponse } from "next/server"; -import { getCachedSettings } from "@/lib/localDb"; +import { getCachedSettings } from "@/lib/db/readCache"; /** * GET /api/auth/oidc/login diff --git a/src/app/api/batches/[id]/route.ts b/src/app/api/batches/[id]/route.ts index 5d4a73f3d2db..8ae3240df381 100644 --- a/src/app/api/batches/[id]/route.ts +++ b/src/app/api/batches/[id]/route.ts @@ -1,6 +1,6 @@ import { NextResponse } from "next/server"; export const dynamic = "force-dynamic"; -import { getBatch } from "@/lib/localDb"; +import { getBatch } from "@/lib/db/batches"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; export async function GET(request: Request, { params }: { params: { id: string } }) { diff --git a/src/app/api/batches/route.ts b/src/app/api/batches/route.ts index 4c8c54d46e05..afd0a28ebbed 100644 --- a/src/app/api/batches/route.ts +++ b/src/app/api/batches/route.ts @@ -1,6 +1,6 @@ import { NextResponse } from "next/server"; export const dynamic = "force-dynamic"; -import { listBatches } from "@/lib/localDb"; +import { listBatches } from "@/lib/db/batches"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; export async function GET(request: Request) { diff --git a/src/app/api/cache/route.ts b/src/app/api/cache/route.ts index 6413626fc2a6..6644651b6806 100644 --- a/src/app/api/cache/route.ts +++ b/src/app/api/cache/route.ts @@ -9,7 +9,7 @@ import { } from "@/lib/semanticCache"; import { getIdempotencyStats } from "@/lib/idempotencyLayer"; import { getCacheMetrics, getCacheTrend } from "@/lib/db/settings"; -import { getCachedSettings } from "@/lib/localDb"; +import { getCachedSettings } from "@/lib/db/readCache"; import { isAuthenticated } from "@/shared/utils/apiAuth"; import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error"; diff --git a/src/app/api/cli-tools/claude-settings/route.ts b/src/app/api/cli-tools/claude-settings/route.ts index 1a3d735f3f25..b6030494126d 100644 --- a/src/app/api/cli-tools/claude-settings/route.ts +++ b/src/app/api/cli-tools/claude-settings/route.ts @@ -14,7 +14,7 @@ import { normalizeClaudeBaseUrl } from "@/shared/services/claudeCliConfig"; import { saveCliToolLastConfigured, deleteCliToolLastConfigured } from "@/lib/db/cliToolState"; import { cliSettingsEnvSchema } from "@/shared/validation/schemas"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; -import { getApiKeyById } from "@/lib/localDb"; +import { getApiKeyById } from "@/lib/db/apiKeys"; import { readJsoncConfig } from "../_lib/jsoncConfig"; // Get claude settings path based on OS diff --git a/src/app/api/cli-tools/codex-settings/route.ts b/src/app/api/cli-tools/codex-settings/route.ts index 0212880f7dc8..40a094c840ae 100644 --- a/src/app/api/cli-tools/codex-settings/route.ts +++ b/src/app/api/cli-tools/codex-settings/route.ts @@ -13,7 +13,7 @@ import { createMultiBackup } from "@/shared/services/backupService"; import { saveCliToolLastConfigured, deleteCliToolLastConfigured } from "@/lib/db/cliToolState"; import { cliModelConfigSchema } from "@/shared/validation/schemas"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; -import { getApiKeyById } from "@/lib/localDb"; +import { getApiKeyById } from "@/lib/db/apiKeys"; import { normalizeCodexBaseUrl } from "@/shared/utils/codexBaseUrl"; import { migrateCodexFeatureFlags } from "@/shared/utils/codexConfig"; diff --git a/src/app/api/cli-tools/keys/route.ts b/src/app/api/cli-tools/keys/route.ts index 36ccd7c40fa0..c26556f609b5 100644 --- a/src/app/api/cli-tools/keys/route.ts +++ b/src/app/api/cli-tools/keys/route.ts @@ -1,6 +1,6 @@ import { NextResponse } from "next/server"; import { requireCliToolsAuth } from "@/lib/api/requireCliToolsAuth"; -import { getApiKeys } from "@/lib/localDb"; +import { getApiKeys } from "@/lib/db/apiKeys"; import { maskStoredApiKey } from "@/lib/apiKeyExposure"; // GET /api/cli-tools/keys - List API keys with raw values for authenticated CLI tools UI only diff --git a/src/app/api/cli/connect/route.ts b/src/app/api/cli/connect/route.ts index 7a16e686b47a..1cae2c918abb 100644 --- a/src/app/api/cli/connect/route.ts +++ b/src/app/api/cli/connect/route.ts @@ -1,7 +1,7 @@ import { NextResponse } from "next/server"; import { z } from "zod"; import { getAuditRequestContext, logAuditEvent } from "@/lib/compliance/index"; -import { getCachedSettings } from "@/lib/localDb"; +import { getCachedSettings } from "@/lib/db/readCache"; import { ensurePersistentManagementPasswordHash, getStoredManagementPassword, diff --git a/src/app/api/combos/reorder/route.ts b/src/app/api/combos/reorder/route.ts index ee66db0edd0e..3e1c850e5a0c 100644 --- a/src/app/api/combos/reorder/route.ts +++ b/src/app/api/combos/reorder/route.ts @@ -1,5 +1,6 @@ import { NextResponse } from "next/server"; -import { reorderCombos, isCloudEnabled } from "@/lib/localDb"; +import { reorderCombos } from "@/lib/db/combos"; +import { isCloudEnabled } from "@/lib/db/settings"; import { getConsistentMachineId } from "@/shared/utils/machineId"; import { syncToCloud } from "@/lib/cloudSync"; import { reorderCombosSchema } from "@/shared/validation/schemas"; diff --git a/src/app/api/combos/route.ts b/src/app/api/combos/route.ts index c13466a800e5..592cb8f65e74 100644 --- a/src/app/api/combos/route.ts +++ b/src/app/api/combos/route.ts @@ -1,11 +1,6 @@ import { NextResponse } from "next/server"; -import { - getCombos, - getCombosCount, - createCombo, - getComboByName, - isCloudEnabled, -} from "@/lib/localDb"; +import { getCombos, getCombosCount, createCombo, getComboByName } from "@/lib/db/combos"; +import { isCloudEnabled } from "@/lib/db/settings"; import { getConsistentMachineId } from "@/shared/utils/machineId"; import { syncToCloud } from "@/lib/cloudSync"; import { validateCompositeTiersConfig } from "@/lib/combos/compositeTiers"; diff --git a/src/app/api/combos/test/route.ts b/src/app/api/combos/test/route.ts index bd73410d776b..d44a3a3bd401 100644 --- a/src/app/api/combos/test/route.ts +++ b/src/app/api/combos/test/route.ts @@ -1,7 +1,8 @@ import { randomUUID } from "node:crypto"; import { NextResponse } from "next/server"; import { buildComboTestRequestBody, extractComboTestResponseText } from "@/lib/combos/testHealth"; -import { getComboByName, getCombos, pickApiKeyForInternalUse } from "@/lib/localDb"; +import { getComboByName, getCombos } from "@/lib/db/combos"; +import { pickApiKeyForInternalUse } from "@/lib/db/apiKeys"; import { getRuntimePorts } from "@/lib/runtime/ports"; import { resolveNestedComboTargets } from "@omniroute/open-sse/services/combo.ts"; import { testComboSchema } from "@/shared/validation/schemas"; diff --git a/src/app/api/db-backups/route.ts b/src/app/api/db-backups/route.ts index 04661e7d3779..1d94aef45515 100644 --- a/src/app/api/db-backups/route.ts +++ b/src/app/api/db-backups/route.ts @@ -8,7 +8,7 @@ import { setDbBackupMaxFiles, getDbBackupRetentionDays, setDbBackupRetentionDays, -} from "@/lib/localDb"; +} from "@/lib/db/backup"; import { dbBackupCleanupSchema, dbBackupRestoreSchema } from "@/shared/validation/schemas"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; import { isAuthenticated } from "@/shared/utils/apiAuth"; diff --git a/src/app/api/evals/route.ts b/src/app/api/evals/route.ts index 58ca8ab23d28..3415c71fcd0e 100644 --- a/src/app/api/evals/route.ts +++ b/src/app/api/evals/route.ts @@ -1,6 +1,7 @@ import { randomUUID } from "node:crypto"; import { NextResponse } from "next/server"; -import { getEvalScorecard, listEvalRuns, getApiKeys } from "@/lib/localDb"; +import { getEvalScorecard, listEvalRuns } from "@/lib/db/evals"; +import { getApiKeys } from "@/lib/db/apiKeys"; import { listSuites, runSuite, createScorecard } from "@/lib/evals/evalRunner"; import { buildEvalTargetOptions, runEvalSuiteAgainstTarget } from "@/lib/evals/runtime"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; diff --git a/src/app/api/evals/suites/[suiteId]/route.ts b/src/app/api/evals/suites/[suiteId]/route.ts index b62d8c98a78e..b5b29573341b 100644 --- a/src/app/api/evals/suites/[suiteId]/route.ts +++ b/src/app/api/evals/suites/[suiteId]/route.ts @@ -1,5 +1,5 @@ import { NextResponse } from "next/server"; -import { deleteCustomEvalSuite, getCustomEvalSuite, saveCustomEvalSuite } from "@/lib/localDb"; +import { deleteCustomEvalSuite, getCustomEvalSuite, saveCustomEvalSuite } from "@/lib/db/evals"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; import { evalSuiteSaveSchema } from "@/shared/validation/schemas"; diff --git a/src/app/api/evals/suites/route.ts b/src/app/api/evals/suites/route.ts index 15046941e88b..ad93940a739e 100644 --- a/src/app/api/evals/suites/route.ts +++ b/src/app/api/evals/suites/route.ts @@ -1,5 +1,5 @@ import { NextResponse } from "next/server"; -import { saveCustomEvalSuite } from "@/lib/localDb"; +import { saveCustomEvalSuite } from "@/lib/db/evals"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; import { evalSuiteSaveSchema } from "@/shared/validation/schemas"; diff --git a/src/app/api/files/[id]/content/route.ts b/src/app/api/files/[id]/content/route.ts index 2a671eb46de3..e742db653d55 100644 --- a/src/app/api/files/[id]/content/route.ts +++ b/src/app/api/files/[id]/content/route.ts @@ -1,5 +1,5 @@ import { NextResponse } from "next/server"; -import { getFile, getFileContent } from "@/lib/localDb"; +import { getFile, getFileContent } from "@/lib/db/files"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; export async function GET(request: Request, { params }: { params: Promise<{ id: string }> }) { diff --git a/src/app/api/files/route.ts b/src/app/api/files/route.ts index fb34312f199a..27949f174839 100644 --- a/src/app/api/files/route.ts +++ b/src/app/api/files/route.ts @@ -1,5 +1,5 @@ import { NextResponse } from "next/server"; -import { listFiles } from "@/lib/localDb"; +import { listFiles } from "@/lib/db/files"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; export async function GET(request: Request) { diff --git a/src/app/api/keys/[id]/regenerate/route.ts b/src/app/api/keys/[id]/regenerate/route.ts index 6a0fa81e75b2..b5f3505d3c67 100644 --- a/src/app/api/keys/[id]/regenerate/route.ts +++ b/src/app/api/keys/[id]/regenerate/route.ts @@ -1,5 +1,5 @@ import { NextResponse } from "next/server"; -import { regenerateApiKey } from "@/lib/localDb"; +import { regenerateApiKey } from "@/lib/db/apiKeys"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; import * as log from "@/sse/utils/logger"; diff --git a/src/app/api/keys/[id]/reveal/route.ts b/src/app/api/keys/[id]/reveal/route.ts index 489fa5e9cf97..dd3c1f09a96e 100644 --- a/src/app/api/keys/[id]/reveal/route.ts +++ b/src/app/api/keys/[id]/reveal/route.ts @@ -1,5 +1,5 @@ import { NextResponse } from "next/server"; -import { getApiKeyById } from "@/lib/localDb"; +import { getApiKeyById } from "@/lib/db/apiKeys"; import { isApiKeyRevealEnabled } from "@/lib/apiKeyExposure"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; import * as log from "@/sse/utils/logger"; diff --git a/src/app/api/keys/[id]/route.ts b/src/app/api/keys/[id]/route.ts index f3df18a23093..f1e354d29051 100644 --- a/src/app/api/keys/[id]/route.ts +++ b/src/app/api/keys/[id]/route.ts @@ -3,9 +3,9 @@ import { deleteApiKey, getApiKeyById, updateApiKeyPermissions, - isCloudEnabled, ApiKeyPolicyInvariantError, -} from "@/lib/localDb"; +} from "@/lib/db/apiKeys"; +import { isCloudEnabled } from "@/lib/db/settings"; import { getConsistentMachineId } from "@/shared/utils/machineId"; import { syncToCloud } from "@/lib/cloudSync"; import { updateKeyPermissionsSchema } from "@/shared/validation/schemas"; @@ -162,10 +162,13 @@ export async function PATCH(request, { params }) { }); } catch (error) { if (error instanceof ApiKeyPolicyInvariantError) { - return NextResponse.json(buildErrorBody(400, error.message, null, { - type: "lease_error", - code: error.code, - }), { status: 400 }); + return NextResponse.json( + buildErrorBody(400, error.message, null, { + type: "lease_error", + code: error.code, + }), + { status: 400 } + ); } log.error("keys", "Error updating key permissions", error); return NextResponse.json({ error: "Failed to update permissions" }, { status: 500 }); diff --git a/src/app/api/keys/groups/[id]/keys/route.ts b/src/app/api/keys/groups/[id]/keys/route.ts index 04ff6775c206..db2456142edc 100644 --- a/src/app/api/keys/groups/[id]/keys/route.ts +++ b/src/app/api/keys/groups/[id]/keys/route.ts @@ -1,6 +1,11 @@ import { NextResponse } from "next/server"; import { z } from "zod"; -import { addKeyToGroup, removeKeyFromGroup, getGroupMembers, getKeyGroup } from "@/lib/localDb"; +import { + addKeyToGroup, + removeKeyFromGroup, + getGroupMembers, + getKeyGroup, +} from "@/lib/db/apiKeyGroups"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; type RouteParams = { params: Promise<{ id: string }> }; diff --git a/src/app/api/keys/groups/[id]/permissions/route.ts b/src/app/api/keys/groups/[id]/permissions/route.ts index d9f7391e8ab1..dce04e0b1e0b 100644 --- a/src/app/api/keys/groups/[id]/permissions/route.ts +++ b/src/app/api/keys/groups/[id]/permissions/route.ts @@ -5,7 +5,7 @@ import { removeGroupPermission, getGroupPermissions, getKeyGroup, -} from "@/lib/localDb"; +} from "@/lib/db/apiKeyGroups"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; type RouteParams = { params: Promise<{ id: string }> }; diff --git a/src/app/api/keys/groups/[id]/route.ts b/src/app/api/keys/groups/[id]/route.ts index e8289a0121d8..6cea718ba34c 100644 --- a/src/app/api/keys/groups/[id]/route.ts +++ b/src/app/api/keys/groups/[id]/route.ts @@ -9,7 +9,7 @@ import { removeGroupPermission, addKeyToGroup, removeKeyFromGroup, -} from "@/lib/localDb"; +} from "@/lib/db/apiKeyGroups"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; type RouteParams = { params: Promise<{ id: string }> }; diff --git a/src/app/api/keys/groups/route.ts b/src/app/api/keys/groups/route.ts index 87c0b4f289f1..f0766dc59e83 100644 --- a/src/app/api/keys/groups/route.ts +++ b/src/app/api/keys/groups/route.ts @@ -1,6 +1,6 @@ import { NextResponse } from "next/server"; import { z } from "zod"; -import { getAllKeyGroups, createKeyGroup, getKeyGroup } from "@/lib/localDb"; +import { getAllKeyGroups, createKeyGroup, getKeyGroup } from "@/lib/db/apiKeyGroups"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; const createKeyGroupSchema = z.object({ diff --git a/src/app/api/keys/route.ts b/src/app/api/keys/route.ts index d50b4c9d1753..11f4b94b97fe 100644 --- a/src/app/api/keys/route.ts +++ b/src/app/api/keys/route.ts @@ -3,9 +3,9 @@ import { getApiKeys, getApiKeysCount, createApiKey, - isCloudEnabled, updateApiKeyPermissions, -} from "@/lib/localDb"; +} from "@/lib/db/apiKeys"; +import { isCloudEnabled } from "@/lib/db/settings"; import { getConsistentMachineId } from "@/shared/utils/machineId"; import { syncToCloud } from "@/lib/cloudSync"; import { createKeySchema } from "@/shared/validation/schemas"; diff --git a/src/app/api/memory/reindex/route.ts b/src/app/api/memory/reindex/route.ts index bdd51678cea3..cbf2b8bc6be7 100644 --- a/src/app/api/memory/reindex/route.ts +++ b/src/app/api/memory/reindex/route.ts @@ -3,7 +3,7 @@ import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; import { validateBody, isValidationFailure } from "@/shared/validation/helpers"; import { MemoryReindexSchema } from "@/shared/schemas/memory"; import { runReindexBatch, getReindexPending } from "@/lib/memory/reindex"; -import { markAllMemoriesNeedReindex } from "@/lib/localDb"; +import { markAllMemoriesNeedReindex } from "@/lib/db/memoryVec"; import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error.ts"; import { logger } from "@omniroute/open-sse/utils/logger.ts"; @@ -19,7 +19,7 @@ export async function POST(request: Request) { } catch { return NextResponse.json( { error: { message: "Invalid JSON body", details: [] } }, - { status: 400 }, + { status: 400 } ); } diff --git a/src/app/api/middleware/hooks/[name]/route.ts b/src/app/api/middleware/hooks/[name]/route.ts index 647cb523cca4..ad7b50e43eb1 100644 --- a/src/app/api/middleware/hooks/[name]/route.ts +++ b/src/app/api/middleware/hooks/[name]/route.ts @@ -5,7 +5,7 @@ import { updateMiddlewareHook, deleteMiddlewareHook, getHookLogs, -} from "@/lib/localDb"; +} from "@/lib/db/middleware"; import { registerHook, unregisterHook, updateHook } from "@/lib/middleware/registry"; import type { HookConfig } from "@/lib/middleware/types"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; @@ -106,7 +106,10 @@ export async function PUT(request: Request, { params }: RouteParams) { return NextResponse.json({ hook: saved }); } catch (error: any) { console.error("[API] PUT /api/middleware/hooks/[name] error:", error); - return NextResponse.json({ error: sanitizeErrorMessage(error) || "Failed to update hook" }, { status: 500 }); + return NextResponse.json( + { error: sanitizeErrorMessage(error) || "Failed to update hook" }, + { status: 500 } + ); } } diff --git a/src/app/api/middleware/hooks/route.ts b/src/app/api/middleware/hooks/route.ts index ea8b40ef69c3..e630dc8c8b98 100644 --- a/src/app/api/middleware/hooks/route.ts +++ b/src/app/api/middleware/hooks/route.ts @@ -5,7 +5,7 @@ import { createMiddlewareHook, getMiddlewareHook, getHookLogs, -} from "@/lib/localDb"; +} from "@/lib/db/middleware"; import { registerHook, getAllHooks } from "@/lib/middleware/registry"; import type { HookConfig, CreateHookRequest } from "@/lib/middleware/types"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; @@ -115,6 +115,9 @@ export async function POST(request: Request) { return NextResponse.json({ hook: saved }, { status: 201 }); } catch (error: any) { console.error("[API] POST /api/middleware/hooks error:", error); - return NextResponse.json({ error: sanitizeErrorMessage(error) || "Failed to create hook" }, { status: 500 }); + return NextResponse.json( + { error: sanitizeErrorMessage(error) || "Failed to create hook" }, + { status: 500 } + ); } } diff --git a/src/app/api/model-combo-mappings/[id]/route.ts b/src/app/api/model-combo-mappings/[id]/route.ts index 33393201f8dd..750c10ccccb6 100644 --- a/src/app/api/model-combo-mappings/[id]/route.ts +++ b/src/app/api/model-combo-mappings/[id]/route.ts @@ -11,7 +11,7 @@ import { updateModelComboMapping, deleteModelComboMapping, getModelComboMappingById, -} from "@/lib/localDb"; +} from "@/lib/db/modelComboMappings"; import { validateBody, isValidationFailure } from "@/shared/validation/helpers"; const updateMappingSchema = z.object({ diff --git a/src/app/api/model-combo-mappings/route.ts b/src/app/api/model-combo-mappings/route.ts index ccc65170be53..0d7605b5296a 100644 --- a/src/app/api/model-combo-mappings/route.ts +++ b/src/app/api/model-combo-mappings/route.ts @@ -7,7 +7,7 @@ import { z } from "zod"; import { NextResponse } from "next/server"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; -import { createModelComboMapping, getModelComboMappings } from "@/lib/localDb"; +import { createModelComboMapping, getModelComboMappings } from "@/lib/db/modelComboMappings"; import { paginationSchema } from "@/shared/validation/schemas"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; import { validatedJsonBody } from "@/shared/validation/helpers"; diff --git a/src/app/api/models/test-all/route.ts b/src/app/api/models/test-all/route.ts index 5492b40a2722..9601745016c7 100644 --- a/src/app/api/models/test-all/route.ts +++ b/src/app/api/models/test-all/route.ts @@ -14,7 +14,7 @@ import { NextResponse } from "next/server"; import { z } from "zod"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; import { DEFAULT_MODEL_TEST_TIMEOUT_MS, runSingleModelTest } from "@/lib/api/modelTestRunner"; -import { setModelIsHidden } from "@/lib/localDb"; +import { setModelIsHidden } from "@/lib/db/models"; import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error"; import { getSettings } from "@/lib/db/settings"; import { isFreeModel, providerHasFreeModels } from "@/shared/utils/freeModels"; diff --git a/src/app/api/monitoring/health/route.ts b/src/app/api/monitoring/health/route.ts index b3e031b34825..f913dbb2c551 100644 --- a/src/app/api/monitoring/health/route.ts +++ b/src/app/api/monitoring/health/route.ts @@ -1,5 +1,6 @@ import { NextResponse } from "next/server"; -import { getProviderConnections, getCachedSettings } from "@/lib/localDb"; +import { getProviderConnections } from "@/lib/db/providers"; +import { getCachedSettings } from "@/lib/db/readCache"; import { buildHealthPayload } from "@/lib/monitoring/observability"; import { readRunningBuildSha } from "@/lib/monitoring/buildSha"; import { APP_CONFIG } from "@/shared/constants/config"; diff --git a/src/app/api/pricing/models/route.ts b/src/app/api/pricing/models/route.ts index bf25fe1e0e58..0544a979d64d 100644 --- a/src/app/api/pricing/models/route.ts +++ b/src/app/api/pricing/models/route.ts @@ -1,6 +1,7 @@ import { NextResponse } from "next/server"; import { REGISTRY } from "@omniroute/open-sse/config/providerRegistry.ts"; -import { getAllCustomModels, getAllSyncedAvailableModels, getPricing } from "@/lib/localDb"; +import { getAllCustomModels, getAllSyncedAvailableModels } from "@/lib/db/models"; +import { getPricing } from "@/lib/db/settings"; import { getProviderPrefixIndex } from "@/lib/providerNodePrefixes"; function asRecord(value: unknown): Record { diff --git a/src/app/api/pricing/route.ts b/src/app/api/pricing/route.ts index 7db9659574a2..e1849e672b11 100644 --- a/src/app/api/pricing/route.ts +++ b/src/app/api/pricing/route.ts @@ -6,7 +6,7 @@ import { updatePricing, resetPricing, resetAllPricing, -} from "@/lib/localDb"; +} from "@/lib/db/settings"; import { updatePricingSchema } from "@/shared/validation/schemas"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; diff --git a/src/app/api/provider-models/route.ts b/src/app/api/provider-models/route.ts index 007ab7673a28..beba1dbc1f3c 100644 --- a/src/app/api/provider-models/route.ts +++ b/src/app/api/provider-models/route.ts @@ -11,7 +11,7 @@ import { mergeModelCompatOverride, getHiddenModelsByProvider, type ModelCompatPatch, -} from "@/lib/localDb"; +} from "@/lib/db/models"; import { getModelContextOverrideRecord, setModelContextOverride, diff --git a/src/app/api/providers/[id]/login/route.ts b/src/app/api/providers/[id]/login/route.ts index be62a774d748..ec9cb5376cdb 100644 --- a/src/app/api/providers/[id]/login/route.ts +++ b/src/app/api/providers/[id]/login/route.ts @@ -7,7 +7,8 @@ */ import { NextRequest, NextResponse } from "next/server"; -import { getCachedProviderConnectionById, updateProviderConnection } from "@/lib/localDb"; +import { getCachedProviderConnectionById } from "@/lib/db/readCache"; +import { updateProviderConnection } from "@/lib/db/providers"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; import { clampLoginTimeoutMs } from "@/lib/api/loginTimeout"; import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error.ts"; diff --git a/src/app/api/providers/[id]/models/route.ts b/src/app/api/providers/[id]/models/route.ts index ee2baeb39302..5bcc72110991 100755 --- a/src/app/api/providers/[id]/models/route.ts +++ b/src/app/api/providers/[id]/models/route.ts @@ -11,11 +11,8 @@ import { resolveAlibabaProviderModelsUrl } from "@/shared/constants/alibabaProvi import { getStaticModelsForProvider } from "@/lib/providers/staticModels"; import { providerUsesCuratedModelsOnly } from "@/lib/providers/modelListingCapability"; import { mergeModelsWithCustomPrecedence } from "@/lib/providers/modelMetadataPrecedence"; -import { - getCachedProviderConnectionById, - getModelIsHidden, - resolveProxyForProvider, -} from "@/lib/localDb"; +import { getCachedProviderConnectionById } from "@/lib/db/readCache"; +import { resolveProxyForProvider } from "@/lib/db/proxies"; import { SAFE_OUTBOUND_FETCH_PRESETS, SafeOutboundFetchError, @@ -86,7 +83,7 @@ import { import { buildProviderModelsUrl, getDiscoveryClientVersionOptions } from "./discoveryClientVersion"; import { getAdobeModels } from "./adobeFireflyDiscovery"; import { parseGeminiModelsList } from "@/lib/providerModels/geminiModelsParser"; -import { getSyncedAvailableModels, getCustomModels } from "@/lib/db/models"; +import { getSyncedAvailableModels, getCustomModels, getModelIsHidden } from "@/lib/db/models"; import { isConnectionUnavailableToAuxiliaryActivity } from "@/lib/exclusiveLeaseIsolation"; import { fetchCursorAgentModels } from "@/lib/providerModels/cursorAgent"; import { fetchCursorAvailableModels } from "@/lib/providerModels/cursorAvailableModels"; diff --git a/src/app/api/providers/[id]/refresh/route.ts b/src/app/api/providers/[id]/refresh/route.ts index 830e90aa819d..85f68930f557 100644 --- a/src/app/api/providers/[id]/refresh/route.ts +++ b/src/app/api/providers/[id]/refresh/route.ts @@ -1,5 +1,5 @@ import { NextResponse } from "next/server"; -import { getCachedProviderConnectionById } from "@/lib/localDb"; +import { getCachedProviderConnectionById } from "@/lib/db/readCache"; import { updateProviderConnection } from "@/lib/db/providers"; import { getAccessToken, diff --git a/src/app/api/providers/[id]/route.ts b/src/app/api/providers/[id]/route.ts index 2534bd58528b..5085f8fb135e 100644 --- a/src/app/api/providers/[id]/route.ts +++ b/src/app/api/providers/[id]/route.ts @@ -4,12 +4,9 @@ import { getProviderAuditTarget, summarizeProviderConnectionForAudit, } from "@/lib/compliance/providerAudit"; -import { - getCachedProviderConnectionById, - updateProviderConnection, - deleteProviderConnection, - isCloudEnabled, -} from "@/lib/localDb"; +import { getCachedProviderConnectionById } from "@/lib/db/readCache"; +import { updateProviderConnection, deleteProviderConnection } from "@/lib/db/providers"; +import { isCloudEnabled } from "@/lib/db/settings"; import { getConsistentMachineId } from "@/shared/utils/machineId"; import { syncToCloud } from "@/lib/cloudSync"; import { updateProviderConnectionSchema } from "@/shared/validation/schemas"; diff --git a/src/app/api/providers/[id]/sync-models/route.ts b/src/app/api/providers/[id]/sync-models/route.ts index 7d759063607d..59f2de8998e2 100644 --- a/src/app/api/providers/[id]/sync-models/route.ts +++ b/src/app/api/providers/[id]/sync-models/route.ts @@ -1,5 +1,5 @@ import { NextResponse } from "next/server"; -import { getCachedProviderConnectionById } from "@/lib/localDb"; +import { getCachedProviderConnectionById } from "@/lib/db/readCache"; import { deleteImportedCustomModels, deleteSyncedAvailableModelsForProvider, diff --git a/src/app/api/providers/[id]/test/route.ts b/src/app/api/providers/[id]/test/route.ts index 7688eb3d061c..cc663a95c815 100644 --- a/src/app/api/providers/[id]/test/route.ts +++ b/src/app/api/providers/[id]/test/route.ts @@ -1,12 +1,9 @@ import { NextResponse } from "next/server"; import { z } from "zod"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; -import { - getCachedProviderConnectionById, - updateProviderConnection, - isCloudEnabled, - resolveProxyForConnection, -} from "@/lib/localDb"; +import { getCachedProviderConnectionById } from "@/lib/db/readCache"; +import { updateProviderConnection } from "@/lib/db/providers"; +import { isCloudEnabled, resolveProxyForConnection } from "@/lib/db/settings"; import { getConsistentMachineId } from "@/shared/utils/machineId"; import { syncToCloud } from "@/lib/cloudSync"; import { validateProviderApiKey } from "@/lib/providers/validation"; diff --git a/src/app/api/providers/bulk/route.ts b/src/app/api/providers/bulk/route.ts index 6799ff4f9efd..13920e587d89 100644 --- a/src/app/api/providers/bulk/route.ts +++ b/src/app/api/providers/bulk/route.ts @@ -28,7 +28,8 @@ import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; import { isManagedProviderConnectionId } from "@/lib/providers/catalog"; import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error"; import { validateProviderApiKey } from "@/lib/providers/validation"; -import { getProxyForLevel, resolveProxyForProvider } from "@/lib/localDb"; +import { getProxyForLevel } from "@/lib/db/settings"; +import { resolveProxyForProvider } from "@/lib/db/proxies"; import { runWithProxyContext } from "@omniroute/open-sse/utils/proxyFetch.ts"; import { rejectRetiredCommonChatGptWebProvider } from "@/lib/providers/chatgptWebRetirementResponse"; diff --git a/src/app/api/providers/client/route.ts b/src/app/api/providers/client/route.ts index 84875c28081c..7a9f65d2069b 100644 --- a/src/app/api/providers/client/route.ts +++ b/src/app/api/providers/client/route.ts @@ -1,5 +1,5 @@ import { NextResponse } from "next/server"; -import { getProviderConnections } from "@/lib/localDb"; +import { getProviderConnections } from "@/lib/db/providers"; // GET /api/providers/client - List all connections for client (includes sensitive fields for sync) export async function GET() { diff --git a/src/app/api/providers/command-code/auth/apply/route.ts b/src/app/api/providers/command-code/auth/apply/route.ts index 3aee6b1a6ecc..82d13f24cd38 100644 --- a/src/app/api/providers/command-code/auth/apply/route.ts +++ b/src/app/api/providers/command-code/auth/apply/route.ts @@ -1,10 +1,7 @@ import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; import { consumeCommandCodeAuthSecret } from "@/lib/db/commandCodeAuth"; -import { - createProviderConnection, - updateProviderConnection, -} from "@/lib/db/providers"; -import { getCachedProviderConnectionById } from "@/lib/localDb"; +import { createProviderConnection, updateProviderConnection } from "@/lib/db/providers"; +import { getCachedProviderConnectionById } from "@/lib/db/readCache"; import { sanitizeProviderSpecificDataForResponse } from "@/lib/providers/requestDefaults"; import { commandCodeApplySchema, noStoreJson, stateHashFromState } from "../shared"; diff --git a/src/app/api/providers/import/route.ts b/src/app/api/providers/import/route.ts index c74d85cfbcf9..dcedfc9c9b08 100644 --- a/src/app/api/providers/import/route.ts +++ b/src/app/api/providers/import/route.ts @@ -27,7 +27,8 @@ import { import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error"; import { validateProviderApiKey } from "@/lib/providers/validation"; -import { getProxyForLevel, resolveProxyForProvider } from "@/lib/localDb"; +import { getProxyForLevel } from "@/lib/db/settings"; +import { resolveProxyForProvider } from "@/lib/db/proxies"; import { runWithProxyContext } from "@omniroute/open-sse/utils/proxyFetch.ts"; import { rejectRetiredCommonChatGptWebProvider } from "@/lib/providers/chatgptWebRetirementResponse"; diff --git a/src/app/api/providers/quota-windows/route.ts b/src/app/api/providers/quota-windows/route.ts index d4a0d842f555..90c1be810bf0 100644 --- a/src/app/api/providers/quota-windows/route.ts +++ b/src/app/api/providers/quota-windows/route.ts @@ -1,6 +1,6 @@ import { NextResponse } from "next/server"; import { getAllProviderQuotaWindows } from "@omniroute/open-sse/services/quotaPreflight.ts"; -import { getCachedSettings } from "@/lib/localDb"; +import { getCachedSettings } from "@/lib/db/readCache"; import { resolveResilienceSettings } from "@/lib/resilience/settings"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; diff --git a/src/app/api/providers/validate/route.ts b/src/app/api/providers/validate/route.ts index ef5565bf139f..7d92d4ac92d3 100644 --- a/src/app/api/providers/validate/route.ts +++ b/src/app/api/providers/validate/route.ts @@ -8,7 +8,8 @@ import { isAnthropicCompatibleProvider, } from "@/shared/constants/providers"; import { validateProviderApiKey } from "@/lib/providers/validation"; -import { getProxyForLevel, resolveProxyForProvider } from "@/lib/localDb"; +import { getProxyForLevel } from "@/lib/db/settings"; +import { resolveProxyForProvider } from "@/lib/db/proxies"; import { validateProviderApiKeySchema } from "@/shared/validation/schemas"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; import { runWithProxyContextOrDirect } from "@omniroute/open-sse/utils/proxyFetch.ts"; diff --git a/src/app/api/quota/groups/[id]/route.ts b/src/app/api/quota/groups/[id]/route.ts index 7496dbdd6d96..174617cb556f 100644 --- a/src/app/api/quota/groups/[id]/route.ts +++ b/src/app/api/quota/groups/[id]/route.ts @@ -22,7 +22,8 @@ import { NextResponse } from "next/server"; import { buildErrorBody } from "@omniroute/open-sse/utils/error"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; import { GroupRenameSchema } from "@/shared/schemas/quota"; -import { renameGroup, deleteGroup, getGroup, getPoolsByGroup } from "@/lib/localDb"; +import { renameGroup, deleteGroup, getGroup } from "@/lib/db/quotaGroups"; +import { getPoolsByGroup } from "@/lib/db/quotaPools"; export const dynamic = "force-dynamic"; @@ -55,10 +56,7 @@ export async function PATCH(request: Request, { params }: RouteParams): Promise< await syncQuotaCombos(pool.id); } catch (err) { // Guard: combo-sync failure must never break group rename callers. - console.warn( - "[quota-groups] syncQuotaCombos failed (non-fatal):", - (err as Error)?.message, - ); + console.warn("[quota-groups] syncQuotaCombos failed (non-fatal):", (err as Error)?.message); } } diff --git a/src/app/api/quota/groups/route.ts b/src/app/api/quota/groups/route.ts index 04962ebe9fa5..e5b61ecc720b 100644 --- a/src/app/api/quota/groups/route.ts +++ b/src/app/api/quota/groups/route.ts @@ -15,7 +15,7 @@ import { NextResponse } from "next/server"; import { buildErrorBody } from "@omniroute/open-sse/utils/error"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; import { GroupCreateSchema } from "@/shared/schemas/quota"; -import { listGroups, createGroup } from "@/lib/localDb"; +import { listGroups, createGroup } from "@/lib/db/quotaGroups"; export const dynamic = "force-dynamic"; diff --git a/src/app/api/quota/keys/[id]/models/route.ts b/src/app/api/quota/keys/[id]/models/route.ts index d4297cfa51a0..4c2435bde17b 100644 --- a/src/app/api/quota/keys/[id]/models/route.ts +++ b/src/app/api/quota/keys/[id]/models/route.ts @@ -19,7 +19,8 @@ import { NextResponse } from "next/server"; import { buildErrorBody } from "@omniroute/open-sse/utils/error"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; -import { getApiKeyById, getCombos } from "@/lib/localDb"; +import { getApiKeyById } from "@/lib/db/apiKeys"; +import { getCombos } from "@/lib/db/combos"; import { resolveQuotaKeyScope } from "@/lib/quota/quotaKey"; import { filterModelsToQuotaPools } from "@/lib/quota/quotaCombos"; @@ -39,9 +40,7 @@ export async function GET(request: Request, { params }: RouteParams): Promise).allowedQuotas, - ) + const allowedQuotas: string[] = Array.isArray((key as Record).allowedQuotas) ? ((key as Record).allowedQuotas as string[]) : []; diff --git a/src/app/api/quota/plans/[connectionId]/route.ts b/src/app/api/quota/plans/[connectionId]/route.ts index b6fa352f0428..94d48017aef2 100644 --- a/src/app/api/quota/plans/[connectionId]/route.ts +++ b/src/app/api/quota/plans/[connectionId]/route.ts @@ -20,10 +20,10 @@ import { buildErrorBody } from "@omniroute/open-sse/utils/error"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; import { PlanUpsertSchema } from "@/shared/schemas/quota"; import { - getProviderPlan, - upsertProviderPlan, - deleteProviderPlan, -} from "@/lib/localDb"; + getPlan as getProviderPlan, + upsertPlan as upsertProviderPlan, + deletePlan as deleteProviderPlan, +} from "@/lib/db/providerPlans"; import { resolvePlan } from "@/lib/quota/planResolver"; import { resolveConnectionProvider } from "@/lib/quota/connectionProvider"; import { logAuditEvent, getAuditRequestContext } from "@/lib/compliance/index"; diff --git a/src/app/api/quota/plans/route.ts b/src/app/api/quota/plans/route.ts index 92fba4fadc40..7dc0903e2d97 100644 --- a/src/app/api/quota/plans/route.ts +++ b/src/app/api/quota/plans/route.ts @@ -17,7 +17,7 @@ import { NextResponse } from "next/server"; import { buildErrorBody } from "@omniroute/open-sse/utils/error"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; -import { listProviderPlans } from "@/lib/localDb"; +import { listPlans as listProviderPlans } from "@/lib/db/providerPlans"; import { knownProviders, getKnownPlan } from "@/lib/quota/planRegistry"; export const dynamic = "force-dynamic"; diff --git a/src/app/api/quota/pools/[id]/route.ts b/src/app/api/quota/pools/[id]/route.ts index 40ef94336b73..bcc346891f2c 100644 --- a/src/app/api/quota/pools/[id]/route.ts +++ b/src/app/api/quota/pools/[id]/route.ts @@ -15,7 +15,7 @@ import { NextResponse } from "next/server"; import { buildErrorBody } from "@omniroute/open-sse/utils/error"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; import { PoolUpdateSchema } from "@/shared/schemas/quota"; -import { getPool, updatePool, deletePool } from "@/lib/localDb"; +import { getPool, updatePool, deletePool } from "@/lib/db/quotaPools"; import { logAuditEvent, getAuditRequestContext } from "@/lib/compliance/index"; import { reconcilePoolExclusivity } from "@/lib/quota/quotaKey"; diff --git a/src/app/api/quota/pools/[id]/usage/route.ts b/src/app/api/quota/pools/[id]/usage/route.ts index cb03f4a04ccf..858285d17092 100644 --- a/src/app/api/quota/pools/[id]/usage/route.ts +++ b/src/app/api/quota/pools/[id]/usage/route.ts @@ -15,7 +15,7 @@ import { NextResponse } from "next/server"; import { buildErrorBody } from "@omniroute/open-sse/utils/error"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; -import { getPool } from "@/lib/localDb"; +import { getPool } from "@/lib/db/quotaPools"; import { getQuotaStore } from "@/lib/quota/QuotaStore"; import { resolvePlan } from "@/lib/quota/planResolver"; import { resolveConnectionProvider } from "@/lib/quota/connectionProvider"; diff --git a/src/app/api/quota/pools/route.ts b/src/app/api/quota/pools/route.ts index 0a7cd7202012..7f759f2b545b 100644 --- a/src/app/api/quota/pools/route.ts +++ b/src/app/api/quota/pools/route.ts @@ -16,7 +16,7 @@ import { NextResponse } from "next/server"; import { buildErrorBody } from "@omniroute/open-sse/utils/error"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; import { PoolCreateSchema } from "@/shared/schemas/quota"; -import { listPools, createPool, ensurePool } from "@/lib/localDb"; +import { listPools, createPool, ensurePool } from "@/lib/db/quotaPools"; import { logAuditEvent, getAuditRequestContext } from "@/lib/compliance/index"; export const dynamic = "force-dynamic"; @@ -66,7 +66,10 @@ export async function POST(request: Request): Promise { requestId: ctx.requestId, }); - return NextResponse.json({ pool, ...(ensured ? { created: ensured.created, updated: ensured.updated } : {}) }, { status: ensured?.created === false ? 200 : 201 }); + return NextResponse.json( + { pool, ...(ensured ? { created: ensured.created, updated: ensured.updated } : {}) }, + { status: ensured?.created === false ? 200 : 201 } + ); } catch (err) { const message = err instanceof Error ? err.message : "Failed to create pool"; return NextResponse.json(buildErrorBody(500, message), { status: 500 }); diff --git a/src/app/api/quota/preview/route.ts b/src/app/api/quota/preview/route.ts index 242289fdbfdc..e2c78e92dc07 100644 --- a/src/app/api/quota/preview/route.ts +++ b/src/app/api/quota/preview/route.ts @@ -25,7 +25,7 @@ import { NextResponse } from "next/server"; import { buildErrorBody } from "@omniroute/open-sse/utils/error"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; import { QuotaPreviewQuerySchema } from "@/shared/schemas/quota"; -import { getPool } from "@/lib/localDb"; +import { getPool } from "@/lib/db/quotaPools"; import { enforceQuotaShare } from "@/lib/quota/enforce"; export const dynamic = "force-dynamic"; diff --git a/src/app/api/rate-limits/route.ts b/src/app/api/rate-limits/route.ts index dbfe87dabf1b..a7c4d3ee002f 100644 --- a/src/app/api/rate-limits/route.ts +++ b/src/app/api/rate-limits/route.ts @@ -1,7 +1,7 @@ import { NextResponse } from "next/server"; import { getAllModelLockouts } from "@omniroute/open-sse/services/accountFallback.ts"; import { getCacheStats } from "@omniroute/open-sse/services/signatureCache.ts"; -import { getProviderConnections, updateProviderConnection } from "@/lib/localDb"; +import { getProviderConnections, updateProviderConnection } from "@/lib/db/providers"; import { enableRateLimitProtection, disableRateLimitProtection, diff --git a/src/app/api/resilience/route.ts b/src/app/api/resilience/route.ts index 288104174836..7b89aade8f68 100644 --- a/src/app/api/resilience/route.ts +++ b/src/app/api/resilience/route.ts @@ -1,5 +1,6 @@ import { NextResponse } from "next/server"; -import { getCachedSettings, getSettings, updateSettings } from "@/lib/localDb"; +import { getCachedSettings } from "@/lib/db/readCache"; +import { getSettings, updateSettings } from "@/lib/db/settings"; import { buildLegacyResilienceCompat, mergeResilienceSettings, diff --git a/src/app/api/settings/__tests__/memory.test.ts b/src/app/api/settings/__tests__/memory.test.ts index a865425de749..cb8f63f0c0de 100644 --- a/src/app/api/settings/__tests__/memory.test.ts +++ b/src/app/api/settings/__tests__/memory.test.ts @@ -4,7 +4,7 @@ vi.mock("../../../../shared/utils/apiAuth", () => ({ isAuthenticated: vi.fn(), })); -vi.mock("../../../../lib/localDb", () => ({ +vi.mock("@/lib/db/settings", () => ({ getSettings: vi.fn(), updateSettings: vi.fn(), })); @@ -19,7 +19,7 @@ vi.mock("../../../../lib/memory/settings", async () => { import { GET, PUT } from "../memory/route"; import { isAuthenticated } from "../../../../shared/utils/apiAuth"; -import { getSettings, updateSettings } from "../../../../lib/localDb"; +import { getSettings, updateSettings } from "@/lib/db/settings"; import { invalidateMemorySettingsCache } from "../../../../lib/memory/settings"; function createRequest(method: "GET" | "PUT", body?: unknown) { diff --git a/src/app/api/settings/__tests__/settings.test.ts b/src/app/api/settings/__tests__/settings.test.ts index ce7d1c579a34..85e6f4f3cc5d 100644 --- a/src/app/api/settings/__tests__/settings.test.ts +++ b/src/app/api/settings/__tests__/settings.test.ts @@ -1,9 +1,9 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; import { PATCH } from "../route"; -// Mock the localDb functions used in the route -vi.mock("../../../../lib/localDb", () => { - const original = vi.importActual("../../../../lib/localDb"); +// Mock the settings db functions used in the route +vi.mock("@/lib/db/settings", () => { + const original = vi.importActual("@/lib/db/settings"); return { ...original, getSettings: vi.fn(), @@ -11,7 +11,7 @@ vi.mock("../../../../lib/localDb", () => { }; }); -import { getSettings, updateSettings } from "../../../../lib/localDb"; +import { getSettings, updateSettings } from "@/lib/db/settings"; // Helper to create a Request with JSON body function createPatchRequest(body: unknown) { diff --git a/src/app/api/settings/authz-inventory/route.ts b/src/app/api/settings/authz-inventory/route.ts index 4b309a992eec..6f012a96d753 100644 --- a/src/app/api/settings/authz-inventory/route.ts +++ b/src/app/api/settings/authz-inventory/route.ts @@ -1,5 +1,5 @@ import { NextResponse } from "next/server"; -import { getSettings } from "@/lib/localDb"; +import { getSettings } from "@/lib/db/settings"; import { isAuthRequired, isDashboardSessionAuthenticated } from "@/shared/utils/apiAuth"; import { createErrorResponse } from "@/lib/api/errorResponse"; import { extractApiKey, isValidApiKey } from "@/sse/services/auth"; diff --git a/src/app/api/settings/auto-disable-accounts/route.ts b/src/app/api/settings/auto-disable-accounts/route.ts index b7e6daa28a7c..ccb3a4294b94 100644 --- a/src/app/api/settings/auto-disable-accounts/route.ts +++ b/src/app/api/settings/auto-disable-accounts/route.ts @@ -1,5 +1,5 @@ import { NextResponse } from "next/server"; -import { getSettings, updateSettings } from "@/lib/localDb"; +import { getSettings, updateSettings } from "@/lib/db/settings"; import { updateAutoDisableAccountsSchema } from "@/shared/validation/schemas"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; diff --git a/src/app/api/settings/combo-defaults/route.ts b/src/app/api/settings/combo-defaults/route.ts index 643be54c0e65..176e46bbfb76 100644 --- a/src/app/api/settings/combo-defaults/route.ts +++ b/src/app/api/settings/combo-defaults/route.ts @@ -1,5 +1,5 @@ import { NextResponse } from "next/server"; -import { getSettings, updateSettings } from "@/lib/localDb"; +import { getSettings, updateSettings } from "@/lib/db/settings"; import { updateComboDefaultsSchema } from "@/shared/validation/schemas"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; diff --git a/src/app/api/settings/export-json/route.ts b/src/app/api/settings/export-json/route.ts index 83af8abc268c..8b894875ae63 100644 --- a/src/app/api/settings/export-json/route.ts +++ b/src/app/api/settings/export-json/route.ts @@ -1,11 +1,9 @@ import { NextResponse } from "next/server"; -import { - getSettings, - getProviderConnections, - getCachedProviderNodes, - getCombos, - getApiKeys, -} from "@/lib/localDb"; +import { getSettings } from "@/lib/db/settings"; +import { getProviderConnections } from "@/lib/db/providers"; +import { getCachedProviderNodes } from "@/lib/db/readCache"; +import { getCombos } from "@/lib/db/combos"; +import { getApiKeys } from "@/lib/db/apiKeys"; import { isAuthRequired, isAuthenticated } from "@/shared/utils/apiAuth"; import { getAllUsageHistory, @@ -75,9 +73,10 @@ export async function GET(request: Request) { // #6328: honor hidePaidModels at the export boundary so backup files // cannot silently smuggle paid model ids back in on import. - const combos = rawSettings.hidePaidModels === true - ? filterPaidComboSteps(combosRaw as Array<{ models?: unknown }>) - : combosRaw; + const combos = + rawSettings.hidePaidModels === true + ? filterPaidComboSteps(combosRaw as Array<{ models?: unknown }>) + : combosRaw; const exportData: Record = { settings: safeSettings, diff --git a/src/app/api/settings/free-proxies/[id]/add-to-pool/route.ts b/src/app/api/settings/free-proxies/[id]/add-to-pool/route.ts index 2e138457461a..6e3187be875e 100644 --- a/src/app/api/settings/free-proxies/[id]/add-to-pool/route.ts +++ b/src/app/api/settings/free-proxies/[id]/add-to-pool/route.ts @@ -1,7 +1,7 @@ import { request as undiciRequest } from "undici"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; import { createErrorResponse, createErrorResponseFromUnknown } from "@/lib/api/errorResponse"; -import { getFreeProxyById, promoteFreeProxyToPool } from "@/lib/localDb"; +import { getFreeProxyById, promoteFreeProxyToPool } from "@/lib/db/freeProxies"; import { createProxyDispatcher, proxyConfigToUrl, diff --git a/src/app/api/settings/free-proxies/bulk-add-to-pool/route.ts b/src/app/api/settings/free-proxies/bulk-add-to-pool/route.ts index c7d2108d5e47..984fc6f4befa 100644 --- a/src/app/api/settings/free-proxies/bulk-add-to-pool/route.ts +++ b/src/app/api/settings/free-proxies/bulk-add-to-pool/route.ts @@ -3,7 +3,7 @@ import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; import { createErrorResponse, createErrorResponseFromUnknown } from "@/lib/api/errorResponse"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; import { freeProxyBulkAddSchema } from "@/shared/validation/freeProxySchemas"; -import { getFreeProxyById, promoteFreeProxyToPool } from "@/lib/localDb"; +import { getFreeProxyById, promoteFreeProxyToPool } from "@/lib/db/freeProxies"; import { createProxyDispatcher, proxyConfigToUrl, diff --git a/src/app/api/settings/free-proxies/route.ts b/src/app/api/settings/free-proxies/route.ts index a36132535f34..9bc4af1c896b 100644 --- a/src/app/api/settings/free-proxies/route.ts +++ b/src/app/api/settings/free-proxies/route.ts @@ -9,7 +9,7 @@ import { clearFreeProxiesBySource, getFreeProxyStats, getFreeProxySyncErrors, -} from "@/lib/localDb"; +} from "@/lib/db/freeProxies"; import type { FreeProxySourceId } from "@/lib/freeProxyProviders/types"; export async function GET(request: Request) { diff --git a/src/app/api/settings/free-proxies/stats/route.ts b/src/app/api/settings/free-proxies/stats/route.ts index 3a7ecdb4e550..700a0f4cbaca 100644 --- a/src/app/api/settings/free-proxies/stats/route.ts +++ b/src/app/api/settings/free-proxies/stats/route.ts @@ -1,6 +1,6 @@ import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; import { createErrorResponseFromUnknown } from "@/lib/api/errorResponse"; -import { getFreeProxyStats } from "@/lib/localDb"; +import { getFreeProxyStats } from "@/lib/db/freeProxies"; import { getAllProviders } from "@/lib/freeProxyProviders"; import { isFreeProxyAutoSyncEnabled, diff --git a/src/app/api/settings/memory/route.ts b/src/app/api/settings/memory/route.ts index 084771d258a8..6bf0df0a907f 100644 --- a/src/app/api/settings/memory/route.ts +++ b/src/app/api/settings/memory/route.ts @@ -1,5 +1,5 @@ import { NextRequest, NextResponse } from "next/server"; -import { getSettings, updateSettings } from "@/lib/localDb"; +import { getSettings, updateSettings } from "@/lib/db/settings"; import { isAuthenticated } from "@/shared/utils/apiAuth"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; import { MemorySettingsExtendedSchema } from "@/shared/schemas/memory"; @@ -35,7 +35,7 @@ export async function PUT(request: NextRequest) { } catch { return NextResponse.json( { error: { message: "Invalid JSON body", details: [] } }, - { status: 400 }, + { status: 400 } ); } diff --git a/src/app/api/settings/payload-rules/route.ts b/src/app/api/settings/payload-rules/route.ts index 8a1a5a7bcdf0..7a632e7e6a61 100644 --- a/src/app/api/settings/payload-rules/route.ts +++ b/src/app/api/settings/payload-rules/route.ts @@ -1,5 +1,5 @@ import { NextResponse } from "next/server"; -import { updateSettings } from "@/lib/localDb"; +import { updateSettings } from "@/lib/db/settings"; import { getPayloadRulesConfig, normalizePayloadRulesConfig, diff --git a/src/app/api/settings/proxies/[id]/repair-relay/route.ts b/src/app/api/settings/proxies/[id]/repair-relay/route.ts index 3c2b07586693..ee6a22870744 100644 --- a/src/app/api/settings/proxies/[id]/repair-relay/route.ts +++ b/src/app/api/settings/proxies/[id]/repair-relay/route.ts @@ -1,7 +1,7 @@ import { z } from "zod"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; import { createErrorResponse, createErrorResponseFromUnknown } from "@/lib/api/errorResponse"; -import { getProxyById, updateProxy } from "@/lib/localDb"; +import { getProxyById, updateProxy } from "@/lib/db/proxies"; import { decrypt } from "@/lib/db/encryption"; import { isRelayProxyType, relayRepairMode } from "@/lib/db/proxies/mappers"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; diff --git a/src/app/api/settings/proxies/assignments/route.ts b/src/app/api/settings/proxies/assignments/route.ts index 39e3d188ba12..990f082ed26a 100644 --- a/src/app/api/settings/proxies/assignments/route.ts +++ b/src/app/api/settings/proxies/assignments/route.ts @@ -1,4 +1,5 @@ -import { assignProxyToScope, getProxyAssignments, resolveProxyForConnection } from "@/lib/localDb"; +import { assignProxyToScope, getProxyAssignments } from "@/lib/db/proxies"; +import { resolveProxyForConnection } from "@/lib/db/settings"; import { proxyAssignmentSchema } from "@/shared/validation/schemas"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; import { createErrorResponse, createErrorResponseFromUnknown } from "@/lib/api/errorResponse"; diff --git a/src/app/api/settings/proxies/auto-test/route.ts b/src/app/api/settings/proxies/auto-test/route.ts index 4d7b4f3a4ebd..17748b5e44bc 100644 --- a/src/app/api/settings/proxies/auto-test/route.ts +++ b/src/app/api/settings/proxies/auto-test/route.ts @@ -1,5 +1,5 @@ import { z } from "zod"; -import { deleteProxyById, listProxies, updateProxy } from "@/lib/localDb"; +import { deleteProxyById, listProxies, updateProxy } from "@/lib/db/proxies"; import { createErrorResponseFromUnknown } from "@/lib/api/errorResponse"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; import { createProxyDispatcher, proxyConfigToUrl } from "@omniroute/open-sse/utils/proxyDispatcher"; diff --git a/src/app/api/settings/proxies/batch-activate/route.ts b/src/app/api/settings/proxies/batch-activate/route.ts index 9bc11f1999a0..f086d3f335d9 100644 --- a/src/app/api/settings/proxies/batch-activate/route.ts +++ b/src/app/api/settings/proxies/batch-activate/route.ts @@ -1,5 +1,5 @@ import { z } from "zod"; -import { updateProxy } from "@/lib/localDb"; +import { updateProxy } from "@/lib/db/proxies"; import { createErrorResponse, createErrorResponseFromUnknown } from "@/lib/api/errorResponse"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; import { clearDispatcherCache } from "@omniroute/open-sse/utils/proxyDispatcher"; @@ -28,12 +28,20 @@ export async function POST(request: Request) { try { rawBody = await request.json(); } catch { - return createErrorResponse({ status: 400, message: "Invalid JSON body", type: "invalid_request" }); + return createErrorResponse({ + status: 400, + message: "Invalid JSON body", + type: "invalid_request", + }); } const validation = validateBody(batchActivateSchema, rawBody); if (isValidationFailure(validation)) { - return createErrorResponse({ status: 400, message: validation.error.message, type: "invalid_request" }); + return createErrorResponse({ + status: 400, + message: validation.error.message, + type: "invalid_request", + }); } const { ids, status } = validation.data; @@ -51,12 +59,20 @@ export async function POST(request: Request) { results.push({ id, success: false, error: "Proxy not found" }); } } catch (err) { - results.push({ id, success: false, error: err instanceof Error ? err.message : "Unknown error" }); + results.push({ + id, + success: false, + error: err instanceof Error ? err.message : "Unknown error", + }); } } if (updatedCount > 0) { - try { clearDispatcherCache(); } catch { /* non-critical */ } + try { + clearDispatcherCache(); + } catch { + /* non-critical */ + } } return Response.json({ diff --git a/src/app/api/settings/proxies/batch-delete/route.ts b/src/app/api/settings/proxies/batch-delete/route.ts index ac343fb9535c..81fe2b40f14d 100644 --- a/src/app/api/settings/proxies/batch-delete/route.ts +++ b/src/app/api/settings/proxies/batch-delete/route.ts @@ -1,5 +1,5 @@ import { z } from "zod"; -import { deleteProxyById } from "@/lib/localDb"; +import { deleteProxyById } from "@/lib/db/proxies"; import { createErrorResponse, createErrorResponseFromUnknown } from "@/lib/api/errorResponse"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; import { clearDispatcherCache } from "@omniroute/open-sse/utils/proxyDispatcher"; @@ -22,12 +22,20 @@ export async function POST(request: Request) { try { rawBody = await request.json(); } catch { - return createErrorResponse({ status: 400, message: "Invalid JSON body", type: "invalid_request" }); + return createErrorResponse({ + status: 400, + message: "Invalid JSON body", + type: "invalid_request", + }); } const validation = validateBody(batchDeleteSchema, rawBody); if (isValidationFailure(validation)) { - return createErrorResponse({ status: 400, message: validation.error.message, type: "invalid_request" }); + return createErrorResponse({ + status: 400, + message: validation.error.message, + type: "invalid_request", + }); } const { ids, force } = validation.data; @@ -45,15 +53,28 @@ export async function POST(request: Request) { results.push({ id, success: false, error: "Proxy not found" }); } } catch (err) { - results.push({ id, success: false, error: err instanceof Error ? err.message : "Unknown error" }); + results.push({ + id, + success: false, + error: err instanceof Error ? err.message : "Unknown error", + }); } } if (deletedCount > 0) { - try { clearDispatcherCache(); } catch { /* non-critical */ } + try { + clearDispatcherCache(); + } catch { + /* non-critical */ + } } - return Response.json({ success: deletedCount > 0, deleted: deletedCount, failed: ids.length - deletedCount, results }); + return Response.json({ + success: deletedCount > 0, + deleted: deletedCount, + failed: ids.length - deletedCount, + results, + }); } catch (error) { return createErrorResponseFromUnknown(error, "Failed to batch delete proxies"); } diff --git a/src/app/api/settings/proxies/bulk-assign/route.ts b/src/app/api/settings/proxies/bulk-assign/route.ts index bcd104bacfd8..b322ab335cf8 100644 --- a/src/app/api/settings/proxies/bulk-assign/route.ts +++ b/src/app/api/settings/proxies/bulk-assign/route.ts @@ -1,4 +1,4 @@ -import { bulkAssignProxyToScope } from "@/lib/localDb"; +import { bulkAssignProxyToScope } from "@/lib/db/proxies"; import { bulkProxyAssignmentSchema } from "@/shared/validation/schemas"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; import { createErrorResponse, createErrorResponseFromUnknown } from "@/lib/api/errorResponse"; diff --git a/src/app/api/settings/proxies/bulk-import/route.ts b/src/app/api/settings/proxies/bulk-import/route.ts index 9f3c3307ded2..0dbf359477f2 100644 --- a/src/app/api/settings/proxies/bulk-import/route.ts +++ b/src/app/api/settings/proxies/bulk-import/route.ts @@ -1,4 +1,4 @@ -import { upsertProxy } from "@/lib/localDb"; +import { upsertProxy } from "@/lib/db/proxies"; import { bulkImportProxiesSchema } from "@/shared/validation/schemas"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; import { createErrorResponse, createErrorResponseFromUnknown } from "@/lib/api/errorResponse"; diff --git a/src/app/api/settings/proxies/health/route.ts b/src/app/api/settings/proxies/health/route.ts index e12c10a8b38a..a23152f56fe1 100644 --- a/src/app/api/settings/proxies/health/route.ts +++ b/src/app/api/settings/proxies/health/route.ts @@ -1,4 +1,4 @@ -import { getProxyHealthStats } from "@/lib/localDb"; +import { getProxyHealthStats } from "@/lib/db/proxies"; import { createErrorResponseFromUnknown } from "@/lib/api/errorResponse"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; diff --git a/src/app/api/settings/proxies/migrate/route.ts b/src/app/api/settings/proxies/migrate/route.ts index c90a194c457b..5e09e2e868ac 100644 --- a/src/app/api/settings/proxies/migrate/route.ts +++ b/src/app/api/settings/proxies/migrate/route.ts @@ -1,4 +1,4 @@ -import { migrateLegacyProxyConfigToRegistry } from "@/lib/localDb"; +import { migrateLegacyProxyConfigToRegistry } from "@/lib/db/proxies"; import { createErrorResponse, createErrorResponseFromUnknown } from "@/lib/api/errorResponse"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; import { z } from "zod"; diff --git a/src/app/api/settings/proxies/pool/route.ts b/src/app/api/settings/proxies/pool/route.ts index 57894dd4a554..bb2f96a1a36e 100644 --- a/src/app/api/settings/proxies/pool/route.ts +++ b/src/app/api/settings/proxies/pool/route.ts @@ -4,11 +4,8 @@ import { getScopeProxyPool, getScopeRotationStrategy, setScopeRotationStrategy, -} from "@/lib/localDb"; -import { - proxyPoolMemberSchema, - proxyRotationStrategySchema, -} from "@/shared/validation/schemas"; +} from "@/lib/db/proxies"; +import { proxyPoolMemberSchema, proxyRotationStrategySchema } from "@/shared/validation/schemas"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; import { createErrorResponse, createErrorResponseFromUnknown } from "@/lib/api/errorResponse"; import { clearDispatcherCache } from "@omniroute/open-sse/utils/proxyDispatcher"; diff --git a/src/app/api/settings/proxies/route.ts b/src/app/api/settings/proxies/route.ts index 30854fa1805a..fe3153629d27 100644 --- a/src/app/api/settings/proxies/route.ts +++ b/src/app/api/settings/proxies/route.ts @@ -1,4 +1,4 @@ -import { listProxies } from "@/lib/localDb"; +import { listProxies } from "@/lib/db/proxies"; import { handleProxyCreate, handleProxyDelete, diff --git a/src/app/api/settings/proxy/cloudflare-deploy/route.ts b/src/app/api/settings/proxy/cloudflare-deploy/route.ts index 7f8f7a1ef604..e4eef2081adb 100644 --- a/src/app/api/settings/proxy/cloudflare-deploy/route.ts +++ b/src/app/api/settings/proxy/cloudflare-deploy/route.ts @@ -3,7 +3,7 @@ import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; import { createErrorResponse, createErrorResponseFromUnknown } from "@/lib/api/errorResponse"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; import { cloudflareDeploySchema } from "@/shared/validation/freeProxySchemas"; -import { createProxy } from "@/lib/localDb"; +import { createProxy } from "@/lib/db/proxies"; import { encrypt } from "@/lib/db/encryption"; import { buildCloudflareWorkerScript, diff --git a/src/app/api/settings/proxy/deno-deploy/route.ts b/src/app/api/settings/proxy/deno-deploy/route.ts index 0c277e5d62ba..3c10c59ca8f9 100644 --- a/src/app/api/settings/proxy/deno-deploy/route.ts +++ b/src/app/api/settings/proxy/deno-deploy/route.ts @@ -3,7 +3,7 @@ import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; import { createErrorResponse, createErrorResponseFromUnknown } from "@/lib/api/errorResponse"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; import { denoDeploySchema } from "@/shared/validation/freeProxySchemas"; -import { createProxy } from "@/lib/localDb"; +import { createProxy } from "@/lib/db/proxies"; import { encrypt } from "@/lib/db/encryption"; import { isPrivateRelayHostname } from "@/lib/proxyRelay/privateHostname"; diff --git a/src/app/api/settings/proxy/route.ts b/src/app/api/settings/proxy/route.ts index 0a8c1064b86b..d05d0eb4c2ac 100755 --- a/src/app/api/settings/proxy/route.ts +++ b/src/app/api/settings/proxy/route.ts @@ -4,9 +4,8 @@ import { getProxyForLevel, deleteProxyForLevel, resolveProxyForConnection, - getProxyAssignments, - getProxyById, -} from "../../../../lib/localDb"; +} from "@/lib/db/settings"; +import { getProxyAssignments, getProxyById } from "@/lib/db/proxies"; import { clearDispatcherCache } from "@omniroute/open-sse/utils/proxyDispatcher"; import { updateProxyConfigSchema } from "@/shared/validation/schemas"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; @@ -68,9 +67,7 @@ function getRegistryScopeForLevel( return undefined; } - return PROXY_LEVEL_TO_REGISTRY_SCOPE[ - level as keyof typeof PROXY_LEVEL_TO_REGISTRY_SCOPE - ]; + return PROXY_LEVEL_TO_REGISTRY_SCOPE[level as keyof typeof PROXY_LEVEL_TO_REGISTRY_SCOPE]; } async function getRegistryProxyForLevel(level: string, id: string | null) { diff --git a/src/app/api/settings/proxy/test/route.ts b/src/app/api/settings/proxy/test/route.ts index c2ceef616b63..8c4a5c04213c 100644 --- a/src/app/api/settings/proxy/test/route.ts +++ b/src/app/api/settings/proxy/test/route.ts @@ -10,8 +10,8 @@ import { probeEchoTargets } from "@/lib/proxyEchoTarget"; import { testProxySchema } from "@/shared/validation/schemas"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; import { createErrorResponse, createErrorResponseFromUnknown } from "@/lib/api/errorResponse"; -import { getProxyById } from "@/lib/localDb"; -import { extractRelayAuth } from "@/lib/db/proxies"; + +import { extractRelayAuth, getProxyById } from "@/lib/db/proxies"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error"; import { buildRelayTestResult } from "./relayTestResult"; diff --git a/src/app/api/settings/proxy/vercel-deploy/route.ts b/src/app/api/settings/proxy/vercel-deploy/route.ts index 6debb6e7e656..a510d8f96951 100644 --- a/src/app/api/settings/proxy/vercel-deploy/route.ts +++ b/src/app/api/settings/proxy/vercel-deploy/route.ts @@ -3,7 +3,7 @@ import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; import { createErrorResponse, createErrorResponseFromUnknown } from "@/lib/api/errorResponse"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; import { vercelDeploySchema } from "@/shared/validation/freeProxySchemas"; -import { createProxy } from "@/lib/localDb"; +import { createProxy } from "@/lib/db/proxies"; import { encrypt } from "@/lib/db/encryption"; // Shared SSRF-safe relay-path resolver — the same pure guard embedded in the // Deno Deploy worker. Both edge relays must enforce identical path validation, diff --git a/src/app/api/settings/qdrant/route.ts b/src/app/api/settings/qdrant/route.ts index eff1e2ab01b4..33f4f78e006e 100644 --- a/src/app/api/settings/qdrant/route.ts +++ b/src/app/api/settings/qdrant/route.ts @@ -3,7 +3,7 @@ import { isAuthenticated } from "@/shared/utils/apiAuth"; import { validateBody, isValidationFailure } from "@/shared/validation/helpers"; import { QdrantSettingsUpdateSchema } from "@/shared/schemas/qdrant"; import { getQdrantConfig, normalizeQdrantConfig } from "@/lib/memory/qdrant"; -import { updateSettings, getSettings } from "@/lib/localDb"; +import { updateSettings, getSettings } from "@/lib/db/settings"; import { invalidateMemorySettingsCache } from "@/lib/memory/settings"; import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error.ts"; @@ -56,7 +56,7 @@ export async function PUT(request: NextRequest) { } catch { return NextResponse.json( { error: { message: "Invalid JSON body", details: [] } }, - { status: 400 }, + { status: 400 } ); } diff --git a/src/app/api/settings/quota-store/route.ts b/src/app/api/settings/quota-store/route.ts index 631d4eb220f1..f05ee8c4fd3f 100644 --- a/src/app/api/settings/quota-store/route.ts +++ b/src/app/api/settings/quota-store/route.ts @@ -23,7 +23,7 @@ import { NextResponse } from "next/server"; import { buildErrorBody } from "@omniroute/open-sse/utils/error"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; import { QuotaStoreSettingsSchema } from "@/shared/schemas/quota"; -import { getSettings, updateSettings } from "@/lib/localDb"; +import { getSettings, updateSettings } from "@/lib/db/settings"; import { logAuditEvent, getAuditRequestContext } from "@/lib/compliance/index"; import { resetQuotaStoreSingleton } from "@/lib/quota/QuotaStore"; diff --git a/src/app/api/settings/system-prompt/route.ts b/src/app/api/settings/system-prompt/route.ts index 63f1209ac247..01c958effbae 100644 --- a/src/app/api/settings/system-prompt/route.ts +++ b/src/app/api/settings/system-prompt/route.ts @@ -3,7 +3,7 @@ import { setSystemPromptConfig, getSystemPromptConfig, } from "@omniroute/open-sse/services/systemPrompt.ts"; -import { updateSettings } from "@/lib/localDb"; +import { updateSettings } from "@/lib/db/settings"; import { updateSystemPromptSchema } from "@/shared/validation/schemas"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; diff --git a/src/app/api/settings/thinking-budget/route.ts b/src/app/api/settings/thinking-budget/route.ts index 5f2d5932bc3d..485ea732473f 100644 --- a/src/app/api/settings/thinking-budget/route.ts +++ b/src/app/api/settings/thinking-budget/route.ts @@ -1,5 +1,5 @@ import { NextResponse } from "next/server"; -import { getSettings, updateSettings } from "@/lib/localDb"; +import { getSettings, updateSettings } from "@/lib/db/settings"; import { setThinkingBudgetConfig, getThinkingBudgetConfig, diff --git a/src/app/api/skills/collect/chaos/route.ts b/src/app/api/skills/collect/chaos/route.ts index 04268993ec89..35075ced5ebe 100644 --- a/src/app/api/skills/collect/chaos/route.ts +++ b/src/app/api/skills/collect/chaos/route.ts @@ -27,7 +27,7 @@ import { NextResponse } from "next/server"; import { z } from "zod"; import { validateBody, isValidationFailure } from "@/shared/validation/helpers"; import { buildErrorBody, sanitizeErrorMessage } from "@omniroute/open-sse/utils/error"; -import { validateApiKey, getApiKeyMetadata } from "@/lib/localDb"; +import { validateApiKey, getApiKeyMetadata } from "@/lib/db/apiKeys"; import { getChaosConfig } from "@/lib/chaos/chaosConfig"; import { executeChaosRun, type ChaosRunResult } from "@/lib/chaos/chaosExecutor"; import * as log from "@/sse/utils/logger"; @@ -122,7 +122,10 @@ export async function POST(request: Request) { const globalConfig = await getChaosConfig(); if (!globalConfig.enabled) { return NextResponse.json( - buildErrorBody(400, "Chaos Mode is not enabled globally. Enable it in Dashboard → Chaos Mode."), + buildErrorBody( + 400, + "Chaos Mode is not enabled globally. Enable it in Dashboard → Chaos Mode." + ), { status: 400 } ); } diff --git a/src/app/api/sync/cloud/route.ts b/src/app/api/sync/cloud/route.ts index 2f37d15a9d60..44890fc51010 100644 --- a/src/app/api/sync/cloud/route.ts +++ b/src/app/api/sync/cloud/route.ts @@ -1,5 +1,6 @@ import { NextResponse } from "next/server"; -import { getApiKeys, createApiKey, pickApiKeyForInternalUse, updateSettings } from "@/lib/localDb"; +import { getApiKeys, createApiKey, pickApiKeyForInternalUse } from "@/lib/db/apiKeys"; +import { updateSettings } from "@/lib/db/settings"; import { getConsistentMachineId } from "@/shared/utils/machineId"; import { syncToCloud, fetchWithTimeout, CLOUD_URL } from "@/lib/cloudSync"; import fs from "fs/promises"; @@ -53,7 +54,10 @@ export async function GET() { return NextResponse.json({ enabled: true, connected: false }); } } catch (error: unknown) { - return NextResponse.json({ enabled: false, error: sanitizeErrorMessage(error) }, { status: 500 }); + return NextResponse.json( + { enabled: false, error: sanitizeErrorMessage(error) }, + { status: 500 } + ); } } @@ -67,7 +71,12 @@ export async function POST(request: any) { rawBody = await request.json(); } catch { return NextResponse.json( - { error: { message: "Invalid request", details: [{ field: "body", message: "Invalid JSON body" }] } }, + { + error: { + message: "Invalid request", + details: [{ field: "body", message: "Invalid JSON body" }], + }, + }, { status: 400 } ); } @@ -92,7 +101,10 @@ export async function POST(request: any) { } // Sync first — only enable if sync succeeds const enableResult = await syncAndVerify(machineId, createdKey?.key, keys); - const enableBody = await enableResult.clone().json().catch(() => ({})); + const enableBody = await enableResult + .clone() + .json() + .catch(() => ({})); // Only persist cloudEnabled if sync succeeded (body.success exists) if (enableBody.success) { await updateSettings({ cloudEnabled: true }); @@ -125,10 +137,7 @@ async function syncAndVerify(machineId: string, createdKey: any, existingKeys: a // Step 1: Sync data to cloud const syncResult: any = await syncToCloud(machineId, createdKey); if (syncResult.error) { - return NextResponse.json( - { error: `Cloud sync failed: ${syncResult.error}` }, - { status: 502 } - ); + return NextResponse.json({ error: `Cloud sync failed: ${syncResult.error}` }, { status: 502 }); } // Build the cloud URL for the frontend to use diff --git a/src/app/api/token-health/route.ts b/src/app/api/token-health/route.ts index b1176aefd3b6..b5e62e28f37b 100644 --- a/src/app/api/token-health/route.ts +++ b/src/app/api/token-health/route.ts @@ -5,7 +5,7 @@ * Used by TokenHealthBadge in the Header. */ -import { getProviderConnections } from "@/lib/localDb"; +import { getProviderConnections } from "@/lib/db/providers"; import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error.ts"; export async function GET() { diff --git a/src/app/api/tools/agent-bridge/server/route.ts b/src/app/api/tools/agent-bridge/server/route.ts index bffa28138098..b53aee070303 100644 --- a/src/app/api/tools/agent-bridge/server/route.ts +++ b/src/app/api/tools/agent-bridge/server/route.ts @@ -18,7 +18,7 @@ import { import path from "path"; import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error"; import { createErrorResponse } from "@/lib/api/errorResponse"; -import { pickApiKeyForInternalUse } from "@/lib/localDb"; +import { pickApiKeyForInternalUse } from "@/lib/db/apiKeys"; /** * Resolve the OmniRoute API key the spawned MITM child (`server.cjs`) uses to @@ -69,7 +69,9 @@ export async function POST(request: Request): Promise { try { if (action === "start") { const suppliedPassword = - typeof raw.sudoPassword === "string" ? normalizeMitmSudoPasswordInput(raw.sudoPassword) : ""; + typeof raw.sudoPassword === "string" + ? normalizeMitmSudoPasswordInput(raw.sudoPassword) + : ""; if (suppliedPassword) setCachedPassword(suppliedPassword); const apiKey = await resolveRouterApiKey(rawApiKey); const { startMitm } = await import("@/mitm/manager.runtime"); @@ -106,7 +108,9 @@ export async function POST(request: Request): Promise { const result = await installCertResult(sudoPassword, certPath); if (result.installed) { const suppliedPassword = - typeof raw.sudoPassword === "string" ? normalizeMitmSudoPasswordInput(raw.sudoPassword) : ""; + typeof raw.sudoPassword === "string" + ? normalizeMitmSudoPasswordInput(raw.sudoPassword) + : ""; if (process.platform !== "win32" && suppliedPassword) { setCachedPassword(suppliedPassword); } diff --git a/src/app/api/translator/send/route.ts b/src/app/api/translator/send/route.ts index 3678509fd518..3dd51fb88007 100644 --- a/src/app/api/translator/send/route.ts +++ b/src/app/api/translator/send/route.ts @@ -5,7 +5,7 @@ import { detectFormat, getTargetFormat, } from "@omniroute/open-sse/services/provider.ts"; -import { getProviderConnections } from "@/lib/localDb"; +import { getProviderConnections } from "@/lib/db/providers"; import { isConnectionUnavailableToAuxiliaryActivity } from "@/lib/exclusiveLeaseIsolation"; import { toJsonErrorPayload } from "@/shared/utils/upstreamError"; import { logTranslationEvent } from "@/lib/translatorEvents"; diff --git a/src/app/api/translator/translate/route.ts b/src/app/api/translator/translate/route.ts index 8c14ea33a426..37b20df45056 100644 --- a/src/app/api/translator/translate/route.ts +++ b/src/app/api/translator/translate/route.ts @@ -7,7 +7,7 @@ import { } from "@omniroute/open-sse/services/provider.ts"; import { translateRequest } from "@omniroute/open-sse/translator/index.ts"; import { FORMATS } from "@omniroute/open-sse/translator/formats.ts"; -import { getProviderConnections } from "@/lib/localDb"; +import { getProviderConnections } from "@/lib/db/providers"; import { isConnectionUnavailableToAuxiliaryActivity } from "@/lib/exclusiveLeaseIsolation"; import { translatorTranslateSchema } from "@/shared/validation/schemas"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; diff --git a/src/app/api/usage/quota/route.ts b/src/app/api/usage/quota/route.ts index 346c4394786b..cae0e7becb02 100644 --- a/src/app/api/usage/quota/route.ts +++ b/src/app/api/usage/quota/route.ts @@ -1,5 +1,5 @@ import { NextResponse } from "next/server"; -import { getProviderConnections } from "@/lib/localDb"; +import { getProviderConnections } from "@/lib/db/providers"; import { getLearnedLimits, getRateLimitStatus, diff --git a/src/app/api/usage/token-limits/route.ts b/src/app/api/usage/token-limits/route.ts index 18d5ab664e33..5eb490e7fb34 100644 --- a/src/app/api/usage/token-limits/route.ts +++ b/src/app/api/usage/token-limits/route.ts @@ -13,14 +13,15 @@ import { NextResponse } from "next/server"; import { setTokenLimitSchema } from "@/shared/validation/schemas"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; import { buildErrorBody, sanitizeErrorMessage } from "@omniroute/open-sse/utils/error"; + import { + TokenLimit, listTokenLimits, upsertTokenLimit, deleteTokenLimit, getWindowUsage, resetWindowIfElapsed, -} from "@/lib/localDb"; -import type { TokenLimit } from "@/lib/db/tokenLimits"; +} from "@/lib/db/tokenLimits"; export async function GET(request: Request) { try { diff --git a/src/app/api/v1/_helpers/apiKeyScope.ts b/src/app/api/v1/_helpers/apiKeyScope.ts index 18eeb04c6b83..4d238a7818b9 100644 --- a/src/app/api/v1/_helpers/apiKeyScope.ts +++ b/src/app/api/v1/_helpers/apiKeyScope.ts @@ -1,4 +1,4 @@ -import { getApiKeyMetadata } from "@/lib/localDb"; +import { getApiKeyMetadata } from "@/lib/db/apiKeys"; import { extractApiKey } from "@/sse/services/auth"; import { isDashboardSessionAuthenticated } from "@/shared/utils/apiAuth"; diff --git a/src/app/api/v1/audio/transcriptions/route.ts b/src/app/api/v1/audio/transcriptions/route.ts index c059098bc092..0a8ce034aef9 100644 --- a/src/app/api/v1/audio/transcriptions/route.ts +++ b/src/app/api/v1/audio/transcriptions/route.ts @@ -24,7 +24,8 @@ import { } from "@/app/api/v1/_shared/rateLimit"; import { attachOmniRouteMetaToResponse } from "@/domain/omnirouteResponseMeta"; import { generateRequestId } from "@/shared/utils/requestId"; -import { getComboByName, getCombos, getDatabaseSettings } from "@/lib/localDb"; +import { getComboByName, getCombos } from "@/lib/db/combos"; +import { getDatabaseSettings } from "@/lib/db/databaseSettings"; import { handleComboChat } from "@omniroute/open-sse/services/combo.ts"; import { log } from "@omniroute/open-sse/utils/logger.ts"; diff --git a/src/app/api/v1/batches/[id]/cancel/route.ts b/src/app/api/v1/batches/[id]/cancel/route.ts index fec9aa39c9e1..3222f0f0d811 100644 --- a/src/app/api/v1/batches/[id]/cancel/route.ts +++ b/src/app/api/v1/batches/[id]/cancel/route.ts @@ -1,5 +1,5 @@ import { CORS_HEADERS, handleCorsOptions } from "@/shared/utils/cors"; -import { getBatch, updateBatch } from "@/lib/localDb"; +import { getBatch, updateBatch } from "@/lib/db/batches"; import { NextResponse } from "next/server"; import { getApiKeyRequestScope } from "@/app/api/v1/_helpers/apiKeyScope"; import { formatBatchResponse } from "../../formatBatchResponse"; diff --git a/src/app/api/v1/batches/[id]/route.ts b/src/app/api/v1/batches/[id]/route.ts index a536902a7845..7ce3867906d3 100644 --- a/src/app/api/v1/batches/[id]/route.ts +++ b/src/app/api/v1/batches/[id]/route.ts @@ -1,5 +1,5 @@ import { CORS_HEADERS, handleCorsOptions } from "@/shared/utils/cors"; -import { getBatch, deleteBatch } from "@/lib/localDb"; +import { getBatch, deleteBatch } from "@/lib/db/batches"; import { NextResponse } from "next/server"; import { getApiKeyRequestScope } from "@/app/api/v1/_helpers/apiKeyScope"; import { formatBatchResponse } from "../formatBatchResponse"; diff --git a/src/app/api/v1/batches/delete-completed/route.ts b/src/app/api/v1/batches/delete-completed/route.ts index 794f96cad43e..0659253e0963 100644 --- a/src/app/api/v1/batches/delete-completed/route.ts +++ b/src/app/api/v1/batches/delete-completed/route.ts @@ -1,5 +1,5 @@ import { CORS_HEADERS, handleCorsOptions } from "@/shared/utils/cors"; -import { deleteCompletedBatches } from "@/lib/localDb"; +import { deleteCompletedBatches } from "@/lib/db/batches"; import { NextResponse } from "next/server"; import { getApiKeyRequestScope } from "@/app/api/v1/_helpers/apiKeyScope"; diff --git a/src/app/api/v1/batches/route.ts b/src/app/api/v1/batches/route.ts index 9b2be67f6cac..f64a46d97240 100644 --- a/src/app/api/v1/batches/route.ts +++ b/src/app/api/v1/batches/route.ts @@ -1,5 +1,6 @@ import { CORS_HEADERS, handleCorsOptions } from "@/shared/utils/cors"; -import { createBatch, getFile, listBatches, countBatches } from "@/lib/localDb"; +import { createBatch, listBatches, countBatches } from "@/lib/db/batches"; +import { getFile } from "@/lib/db/files"; import { v1BatchCreateSchema } from "@/shared/validation/schemas"; import { NextResponse } from "next/server"; import { getApiKeyRequestScope } from "@/app/api/v1/_helpers/apiKeyScope"; diff --git a/src/app/api/v1/combos/route.ts b/src/app/api/v1/combos/route.ts index 91a4a4fb34de..5c84d13e1650 100644 --- a/src/app/api/v1/combos/route.ts +++ b/src/app/api/v1/combos/route.ts @@ -8,7 +8,7 @@ * routing details (account/connection ids, weights, internal labels). */ import { NextResponse } from "next/server"; -import { getCombos } from "@/lib/localDb"; +import { getCombos } from "@/lib/db/combos"; import { errorResponse } from "@omniroute/open-sse/utils/error.ts"; import { HTTP_STATUS } from "@omniroute/open-sse/config/constants.ts"; import { extractApiKey, isValidApiKey } from "@/sse/services/auth"; diff --git a/src/app/api/v1/files/[id]/content/route.ts b/src/app/api/v1/files/[id]/content/route.ts index 7e7fc79c1ac6..33bf4fdab452 100644 --- a/src/app/api/v1/files/[id]/content/route.ts +++ b/src/app/api/v1/files/[id]/content/route.ts @@ -1,5 +1,5 @@ import { CORS_HEADERS, handleCorsOptions } from "@/shared/utils/cors"; -import { getFile, getFileContent } from "@/lib/localDb"; +import { getFile, getFileContent } from "@/lib/db/files"; import { NextResponse } from "next/server"; import { getApiKeyRequestScope } from "@/app/api/v1/_helpers/apiKeyScope"; diff --git a/src/app/api/v1/files/[id]/route.ts b/src/app/api/v1/files/[id]/route.ts index 66565e52ecfe..953903cca41e 100644 --- a/src/app/api/v1/files/[id]/route.ts +++ b/src/app/api/v1/files/[id]/route.ts @@ -1,5 +1,5 @@ import { CORS_HEADERS, handleCorsOptions } from "@/shared/utils/cors"; -import { getFile, deleteFile, formatFileResponse } from "@/lib/localDb"; +import { getFile, deleteFile, formatFileResponse } from "@/lib/db/files"; import { NextResponse } from "next/server"; import { getApiKeyRequestScope } from "@/app/api/v1/_helpers/apiKeyScope"; diff --git a/src/app/api/v1/files/route.ts b/src/app/api/v1/files/route.ts index 2b9d02e49ae3..4550755a151d 100644 --- a/src/app/api/v1/files/route.ts +++ b/src/app/api/v1/files/route.ts @@ -1,5 +1,5 @@ import { CORS_HEADERS, handleCorsOptions } from "@/shared/utils/cors"; -import { createFile, listFiles, formatFileResponse, countFiles } from "@/lib/localDb"; +import { createFile, listFiles, formatFileResponse, countFiles } from "@/lib/db/files"; import { NextResponse } from "next/server"; import { getApiKeyRequestScope } from "@/app/api/v1/_helpers/apiKeyScope"; diff --git a/src/app/api/v1/images/edits/route.ts b/src/app/api/v1/images/edits/route.ts index b70c7472760f..63da01ed1b68 100644 --- a/src/app/api/v1/images/edits/route.ts +++ b/src/app/api/v1/images/edits/route.ts @@ -30,7 +30,7 @@ import { validateCodexImageEditReferences, } from "@/lib/images/imageRouteModel"; import { isMicrosoftDesignerWebProviderRetiredError } from "@/shared/constants/designerWebRetirement"; -import { resolveProxyForConnection } from "@/lib/localDb"; +import { resolveProxyForConnection } from "@/lib/db/settings"; import { runWithProxyContext } from "@omniroute/open-sse/utils/proxyFetch.ts"; import { isCodexFreePlan } from "@omniroute/open-sse/executors/codex/tools.ts"; import { diff --git a/src/app/api/v1/management/proxies/assignments/route.ts b/src/app/api/v1/management/proxies/assignments/route.ts index 16539528278f..c082347b25b6 100644 --- a/src/app/api/v1/management/proxies/assignments/route.ts +++ b/src/app/api/v1/management/proxies/assignments/route.ts @@ -1,4 +1,5 @@ -import { assignProxyToScope, getProxyAssignments, resolveProxyForConnection } from "@/lib/localDb"; +import { assignProxyToScope, getProxyAssignments } from "@/lib/db/proxies"; +import { resolveProxyForConnection } from "@/lib/db/settings"; import { proxyAssignmentSchema } from "@/shared/validation/schemas"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; import { createErrorResponse, createErrorResponseFromUnknown } from "@/lib/api/errorResponse"; diff --git a/src/app/api/v1/management/proxies/bulk-assign/route.ts b/src/app/api/v1/management/proxies/bulk-assign/route.ts index 32a3434d233a..5c8278b4a1f3 100644 --- a/src/app/api/v1/management/proxies/bulk-assign/route.ts +++ b/src/app/api/v1/management/proxies/bulk-assign/route.ts @@ -1,4 +1,4 @@ -import { bulkAssignProxyToScope } from "@/lib/localDb"; +import { bulkAssignProxyToScope } from "@/lib/db/proxies"; import { bulkProxyAssignmentSchema } from "@/shared/validation/schemas"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; import { createErrorResponse, createErrorResponseFromUnknown } from "@/lib/api/errorResponse"; diff --git a/src/app/api/v1/management/proxies/health/route.ts b/src/app/api/v1/management/proxies/health/route.ts index e12c10a8b38a..a23152f56fe1 100644 --- a/src/app/api/v1/management/proxies/health/route.ts +++ b/src/app/api/v1/management/proxies/health/route.ts @@ -1,4 +1,4 @@ -import { getProxyHealthStats } from "@/lib/localDb"; +import { getProxyHealthStats } from "@/lib/db/proxies"; import { createErrorResponseFromUnknown } from "@/lib/api/errorResponse"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; diff --git a/src/app/api/v1/management/proxies/route.ts b/src/app/api/v1/management/proxies/route.ts index 2eaccbd4967c..336fadeecca1 100644 --- a/src/app/api/v1/management/proxies/route.ts +++ b/src/app/api/v1/management/proxies/route.ts @@ -1,4 +1,4 @@ -import { listProxies } from "@/lib/localDb"; +import { listProxies } from "@/lib/db/proxies"; import { handleProxyCreate, handleProxyDelete, diff --git a/src/app/api/v1/me/status/route.ts b/src/app/api/v1/me/status/route.ts index 2cae418d8332..c41fd3df732e 100644 --- a/src/app/api/v1/me/status/route.ts +++ b/src/app/api/v1/me/status/route.ts @@ -18,7 +18,7 @@ export async function GET(request: Request) { const apiKey = extractBearerToken(request); if (!apiKey) return authError(401); - const { validateApiKey, getApiKeyMetadata } = await import("@/lib/localDb"); + const { validateApiKey, getApiKeyMetadata } = await import("@/lib/db/apiKeys"); const valid = await validateApiKey(apiKey); if (!valid) return authError(401); diff --git a/src/app/api/v1/models/catalog.ts b/src/app/api/v1/models/catalog.ts index 62644fc6b58a..2abcdd7070d4 100644 --- a/src/app/api/v1/models/catalog.ts +++ b/src/app/api/v1/models/catalog.ts @@ -1,14 +1,7 @@ import { PROVIDER_MODELS, PROVIDER_ID_TO_ALIAS } from "@/shared/constants/models"; import { NOAUTH_PROVIDERS } from "@/shared/constants/providers"; -import { - getCachedRawProviderConnections, - getCombos, - getAllCustomModels, - getSettings, - getCachedProviderNodes, - getModelAliases, - getHiddenModelsByProvider, -} from "@/lib/localDb"; +import { getCombos } from "@/lib/db/combos"; +import { getSettings } from "@/lib/db/settings"; import { getUserDatabaseSettings } from "@/lib/db/databaseSettings"; import { createLazyConnectionView } from "@/lib/db/providers/lazyConnectionView"; import { extractAliasBackedModels } from "./aliasBackedModels"; @@ -49,9 +42,16 @@ import { getSyncedAvailableModelsByConnection, SYNCED_AVAILABLE_MODELS_MALFORMED, type SyncedAvailableModel, + getAllCustomModels, + getModelAliases, + getHiddenModelsByProvider, } from "@/lib/db/models"; import { getAllActiveSyncedModels } from "@/lib/db/models/activeSyncedCatalog"; -import { getModelCatalogCacheVersion } from "@/lib/db/readCache"; +import { + getModelCatalogCacheVersion, + getCachedRawProviderConnections, + getCachedProviderNodes, +} from "@/lib/db/readCache"; import { getCompatibleFallbackModels } from "@/lib/providers/managedAvailableModels"; import { providerUsesCuratedModelsOnly, @@ -342,7 +342,12 @@ async function buildUnifiedModelsResponseCore( // explicitly — a disabled router rejects every auto/* id with a 400, so // listing them offers the client a choice that cannot succeed. const hideAuto = settings.hideAutoCombos === true || settings.autoRoutingEnabled === false; - const shouldHidePaid = (providerKey: string, modelId: string, pricing?: unknown, isFree?: boolean): boolean => { + const shouldHidePaid = ( + providerKey: string, + modelId: string, + pricing?: unknown, + isFree?: boolean + ): boolean => { if (!hidePaid) return false; const provider = aliasToProviderId[providerKey] || providerKey; // isFree:true is the first door — custom row kept even when its provider is outside FREE_MODEL_BUDGETS. @@ -1590,7 +1595,12 @@ async function buildUnifiedModelsResponseCore( // Custom entries do not carry pricing, so shouldHidePaid() decides // via FREE_MODEL_IDS_BY_PROVIDER — matches synced/PROVIDER_MODELS. if ( - shouldHidePaid(canonicalProviderId, modelId, (model as { pricing?: unknown }).pricing, (model as any).isFree) + shouldHidePaid( + canonicalProviderId, + modelId, + (model as { pricing?: unknown }).pricing, + (model as any).isFree + ) ) continue; if (shouldHideByExposure(canonicalProviderId, modelId)) continue; diff --git a/src/app/api/v1beta/models/route.ts b/src/app/api/v1beta/models/route.ts index a82719a12bb1..bf9ddec175de 100644 --- a/src/app/api/v1beta/models/route.ts +++ b/src/app/api/v1beta/models/route.ts @@ -5,7 +5,7 @@ import { getAllSyncedAvailableModels, getSyncedAvailableModels, } from "@/lib/db/models"; -import { getProviderConnections } from "@/lib/localDb"; +import { getProviderConnections } from "@/lib/db/providers"; import { getResolvedModelCapabilities } from "@/lib/modelCapabilities"; import { getSyncedCapabilities } from "@/lib/modelsDevSync"; import { mergeCustomModelMetadata } from "@/lib/providers/modelMetadataPrecedence"; diff --git a/src/app/api/webhooks/[id]/deliveries/route.ts b/src/app/api/webhooks/[id]/deliveries/route.ts index 0f7aee3516a9..1723c2eaf25c 100644 --- a/src/app/api/webhooks/[id]/deliveries/route.ts +++ b/src/app/api/webhooks/[id]/deliveries/route.ts @@ -5,7 +5,8 @@ import { NextResponse } from "next/server"; import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error"; -import { getWebhook, getDeliveries } from "@/lib/localDb"; +import { getWebhook } from "@/lib/db/webhooks"; +import { getDeliveries } from "@/lib/db/webhookDeliveries"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; export async function GET(request: Request, { params }: { params: Promise<{ id: string }> }) { diff --git a/src/app/api/webhooks/[id]/route.ts b/src/app/api/webhooks/[id]/route.ts index 98fc6e79c753..fab9daac12ef 100644 --- a/src/app/api/webhooks/[id]/route.ts +++ b/src/app/api/webhooks/[id]/route.ts @@ -8,7 +8,7 @@ import { z } from "zod"; import { NextResponse } from "next/server"; import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error"; -import { getWebhook, updateWebhookRecord, deleteWebhook } from "@/lib/localDb"; +import { getWebhook, updateWebhook as updateWebhookRecord, deleteWebhook } from "@/lib/db/webhooks"; import { validateBody, isValidationFailure } from "@/shared/validation/helpers"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; import { encryptMetadata } from "@/lib/webhookDispatcher"; diff --git a/src/app/api/webhooks/[id]/test/route.ts b/src/app/api/webhooks/[id]/test/route.ts index 7e81f9e9c628..618bf49836de 100644 --- a/src/app/api/webhooks/[id]/test/route.ts +++ b/src/app/api/webhooks/[id]/test/route.ts @@ -5,14 +5,14 @@ import { NextResponse } from "next/server"; import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error"; -import { getWebhook } from "@/lib/localDb"; +import { getWebhook, recordWebhookDelivery } from "@/lib/db/webhooks"; import { decryptMetadata } from "@/lib/webhookDispatcher"; import { buildSlackPayload } from "@/lib/webhooks/integrations/slack"; import { buildTelegramUrl, buildTelegramPayload } from "@/lib/webhooks/integrations/telegram"; import { buildDiscordPayload } from "@/lib/webhooks/integrations/discord"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; import { insertDelivery } from "@/lib/db/webhookDeliveries"; -import { recordWebhookDelivery } from "@/lib/localDb"; +import { getWebhook, recordWebhookDelivery } from "@/lib/db/webhooks"; import { isPrivateHost, OutboundUrlGuardError } from "@/shared/network/outboundUrlGuard"; import { parseAndValidateWebhookUrl } from "@/shared/network/outboundUrlGuardPolicy"; import crypto from "crypto"; diff --git a/src/app/api/webhooks/route.ts b/src/app/api/webhooks/route.ts index 2cb018e13fb6..c31b5c71fac2 100644 --- a/src/app/api/webhooks/route.ts +++ b/src/app/api/webhooks/route.ts @@ -7,7 +7,7 @@ import { z } from "zod"; import { NextResponse } from "next/server"; import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error"; -import { getWebhooks, createWebhook } from "@/lib/localDb"; +import { getWebhooks, createWebhook } from "@/lib/db/webhooks"; import { validateBody, isValidationFailure } from "@/shared/validation/helpers"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; import { encryptMetadata } from "@/lib/webhookDispatcher"; From 06eb091eb7cb7832f5da90659eca7169b4005bf9 Mon Sep 17 00:00:00 2001 From: WebPerson Date: Sat, 29 Aug 2026 13:24:15 -0500 Subject: [PATCH 02/22] fix(barrel): migrate src/lib/ off the localDb barrel to direct db imports (#59) Replace all 46 src/lib imports from @/lib/localDb with direct imports from the owning @/lib/db/* modules, per AGENTS.md G14. Covers static named imports, alias re-exports, dynamic imports (lazy semantics kept), namespace destructures, and the Promise.all array-destructure pattern (e.g. providerDiscovery, internalUsageCommand, saturationSignals). Mechanical codemod only - no behavior change. Type-only imports stay type-only, test vi.mock targets re-point at the real module, and the star-export graph (reasoningRoutingRules, etc.) is resolved via the TypeScript compiler API. Prune the 34 now-satisfied no-restricted-imports suppressions for the migrated files. Verified: typecheck:core green, eslint src/lib green, check:cycles green, focused db unit tests pass (183/191; the 8 failures are the pre-existing stdio-guard suite #8181, identical on clean HEAD). --- config/quality/eslint-suppressions.json | 160 ------------------ src/lib/a2a/skills/providerDiscovery.ts | 2 +- src/lib/api/proxyRegistryRouteHandlers.ts | 2 +- src/lib/cloudSync.ts | 2 +- src/lib/combos/builderOptions.ts | 13 +- src/lib/container.ts | 2 +- src/lib/credentialHealth/scheduler.ts | 2 +- src/lib/evals/runtime.ts | 3 +- src/lib/freeProxyProviders/scheduler.ts | 2 +- src/lib/freeProxyProviders/syncCycle.ts | 2 +- src/lib/guardrails/visionBridge.ts | 2 +- src/lib/idempotencyLayer.ts | 2 +- src/lib/images/imageRouteModel.ts | 2 +- src/lib/localHealthCheck.ts | 3 +- src/lib/memory/embedding/index.ts | 2 +- src/lib/memory/reindex.ts | 6 +- src/lib/memory/store.ts | 2 +- src/lib/memory/vectorStore.ts | 61 +++---- src/lib/modelsDevSync.ts | 2 +- src/lib/monitoring/providerHealthAutopilot.ts | 2 +- src/lib/oauth/utils/agyAuthImport.ts | 2 +- src/lib/oauth/utils/claudeAuthFile.ts | 6 +- src/lib/oauth/utils/claudeAuthImport.ts | 2 +- src/lib/oauth/utils/codexAuthFile.ts | 12 +- src/lib/oauth/utils/codexAuthImport.ts | 2 +- .../providerModels/managedAvailableModels.ts | 4 +- src/lib/proxyEgress.ts | 2 +- src/lib/proxyHealth/scheduler.ts | 2 +- src/lib/quota/connectionProvider.ts | 2 +- src/lib/quota/planResolver.ts | 2 +- src/lib/quota/quotaCombos.ts | 2 +- src/lib/quota/quotaKey.ts | 6 +- src/lib/quota/redisQuotaStore.ts | 5 +- src/lib/quota/saturationSignals.ts | 42 ++--- src/lib/quota/sqliteQuotaStore.ts | 3 +- src/lib/services/quotaAutoPing.ts | 42 ++--- src/lib/sync/bundle.ts | 16 +- src/lib/tokenHealthCheck.ts | 10 +- src/lib/tokenHealthCheckCopilot.ts | 25 ++- src/lib/usage/apiKeySelfService.ts | 13 +- src/lib/usage/callLogs.ts | 4 +- src/lib/usage/codexResetCredits.ts | 3 +- src/lib/usage/costCalculator.ts | 4 +- src/lib/usage/internalUsageCommand.ts | 17 +- src/lib/usage/usageStats.ts | 2 +- src/lib/usageAnalytics.ts | 2 +- src/lib/ws/handshake.ts | 2 +- 47 files changed, 173 insertions(+), 335 deletions(-) diff --git a/config/quality/eslint-suppressions.json b/config/quality/eslint-suppressions.json index e234b3973d2a..74a52b6a5536 100644 --- a/config/quality/eslint-suppressions.json +++ b/config/quality/eslint-suppressions.json @@ -1539,11 +1539,6 @@ "count": 1 } }, - "src/lib/api/proxyRegistryRouteHandlers.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/lib/cli-helper/config-generator/claude.ts": { "@typescript-eslint/no-unused-vars": { "count": 1 @@ -1584,16 +1579,6 @@ "count": 2 } }, - "src/lib/cloudSync.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/lib/combos/builderOptions.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/lib/combos/controlCenter.ts": { "no-restricted-syntax": { "count": 1 @@ -1604,11 +1589,6 @@ "count": 1 } }, - "src/lib/container.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/lib/copilot/engine.ts": { "@typescript-eslint/no-unused-vars": { "count": 2 @@ -1622,9 +1602,6 @@ "src/lib/credentialHealth/scheduler.ts": { "@typescript-eslint/no-unused-vars": { "count": 1 - }, - "no-restricted-imports": { - "count": 1 } }, "src/lib/db/apiKeys.ts": { @@ -1747,21 +1724,6 @@ "count": 1 } }, - "src/lib/evals/runtime.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/lib/freeProxyProviders/scheduler.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/lib/freeProxyProviders/syncCycle.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/lib/guardrails/promptInjection.ts": { "@typescript-eslint/no-unused-vars": { "count": 2 @@ -1772,26 +1734,11 @@ "count": 1 } }, - "src/lib/idempotencyLayer.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/lib/images/imageRouteModel.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/lib/jobRegistry/index.ts": { "@typescript-eslint/no-unused-vars": { "count": 1 } }, - "src/lib/localHealthCheck.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/lib/memory/__tests__/generic-backend.test.ts": { "@typescript-eslint/no-unused-vars": { "count": 4 @@ -1802,21 +1749,11 @@ "count": 1 } }, - "src/lib/memory/embedding/index.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/lib/memory/genericBackend.ts": { "@typescript-eslint/no-unused-vars": { "count": 1 } }, - "src/lib/memory/reindex.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/lib/memory/retrieval.ts": { "@typescript-eslint/no-unused-vars": { "count": 1 @@ -1827,16 +1764,6 @@ "count": 2 } }, - "src/lib/memory/store.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/lib/memory/vectorStore.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/lib/middleware/registry.ts": { "@typescript-eslint/no-unused-vars": { "count": 2 @@ -1851,9 +1778,6 @@ "@typescript-eslint/no-unused-vars": { "count": 1 }, - "no-restricted-imports": { - "count": 1 - }, "no-restricted-syntax": { "count": 1 } @@ -1913,32 +1837,9 @@ "count": 3 } }, - "src/lib/oauth/utils/agyAuthImport.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/lib/oauth/utils/claudeAuthFile.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/lib/oauth/utils/claudeAuthImport.ts": { "@typescript-eslint/no-unused-vars": { "count": 1 - }, - "no-restricted-imports": { - "count": 1 - } - }, - "src/lib/oauth/utils/codexAuthFile.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/lib/oauth/utils/codexAuthImport.ts": { - "no-restricted-imports": { - "count": 1 } }, "src/lib/obsidian/api.ts": { @@ -1966,11 +1867,6 @@ "count": 2 } }, - "src/lib/providerModels/managedAvailableModels.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/lib/providers/validation.ts": { "@typescript-eslint/no-unused-vars": { "count": 1 @@ -1986,45 +1882,19 @@ "count": 1 } }, - "src/lib/proxyHealth/scheduler.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/lib/quota/planResolver.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/lib/quota/quotaAdapters.ts": { "@typescript-eslint/no-unused-vars": { "count": 1 } }, - "src/lib/quota/quotaCombos.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/lib/quota/quotaKey.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/lib/quota/redisQuotaStore.ts": { "@typescript-eslint/no-unused-vars": { "count": 1 - }, - "no-restricted-imports": { - "count": 1 } }, "src/lib/quota/sqliteQuotaStore.ts": { "@typescript-eslint/no-unused-vars": { "count": 1 - }, - "no-restricted-imports": { - "count": 1 } }, "src/lib/semanticCache.ts": { @@ -2037,11 +1907,6 @@ "count": 1 } }, - "src/lib/services/quotaAutoPing.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/lib/skills/a2a.ts": { "@typescript-eslint/no-unused-vars": { "count": 2 @@ -2082,11 +1947,6 @@ "count": 1 } }, - "src/lib/sync/bundle.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/lib/tailscaleTunnel.ts": { "@typescript-eslint/no-unused-vars": { "count": 1 @@ -2097,16 +1957,6 @@ "count": 1 } }, - "src/lib/tokenHealthCheck.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/lib/tokenHealthCheckCopilot.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/lib/usage/apiKeySelfService.ts": { "no-restricted-syntax": { "count": 1 @@ -2122,11 +1972,6 @@ "count": 1 } }, - "src/lib/usage/codexResetCredits.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/lib/usage/costCalculator.ts": { "no-restricted-syntax": { "count": 1 @@ -2170,11 +2015,6 @@ "count": 1 } }, - "src/lib/ws/handshake.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/lib/zed-oauth/keychain-reader.ts": { "@typescript-eslint/no-unused-vars": { "count": 1 diff --git a/src/lib/a2a/skills/providerDiscovery.ts b/src/lib/a2a/skills/providerDiscovery.ts index bec8a116ac24..75c22dc7c2eb 100644 --- a/src/lib/a2a/skills/providerDiscovery.ts +++ b/src/lib/a2a/skills/providerDiscovery.ts @@ -120,7 +120,7 @@ export interface ProviderDiscoveryResult { export async function executeProviderDiscovery(task: A2ATask): Promise { const [{ getProviderConnections }, { getAllCircuitBreakerStatuses }] = await Promise.all([ - import("@/lib/localDb"), + import("@/lib/db/providers"), import("@/shared/utils/circuitBreaker"), ]); diff --git a/src/lib/api/proxyRegistryRouteHandlers.ts b/src/lib/api/proxyRegistryRouteHandlers.ts index b5b2e9bf4e63..c6d3c7ea23d0 100644 --- a/src/lib/api/proxyRegistryRouteHandlers.ts +++ b/src/lib/api/proxyRegistryRouteHandlers.ts @@ -6,7 +6,7 @@ import { getProxyWhereUsed, updateProxy, updateProxyAndAssign, -} from "@/lib/localDb"; +} from "@/lib/db/proxies"; import { createErrorResponse, createErrorResponseFromUnknown } from "@/lib/api/errorResponse"; import { createProxyRegistrySchema, updateProxyRegistrySchema } from "@/shared/validation/schemas"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; diff --git a/src/lib/cloudSync.ts b/src/lib/cloudSync.ts index c26459f71de5..48084d002529 100644 --- a/src/lib/cloudSync.ts +++ b/src/lib/cloudSync.ts @@ -1,5 +1,5 @@ import crypto from "crypto"; -import { getProviderConnections, updateProviderConnection } from "@/lib/localDb"; +import { getProviderConnections, updateProviderConnection } from "@/lib/db/providers"; import { buildConfigSyncEnvelope, toLegacyCloudSyncPayload } from "@/lib/sync/bundle"; const CLOUD_URL = process.env.CLOUD_URL || process.env.NEXT_PUBLIC_CLOUD_URL; diff --git a/src/lib/combos/builderOptions.ts b/src/lib/combos/builderOptions.ts index 625356ab70b7..0f1cb529ccad 100644 --- a/src/lib/combos/builderOptions.ts +++ b/src/lib/combos/builderOptions.ts @@ -1,12 +1,7 @@ -import { - getAllCustomModels, - getAllSyncedAvailableModels, - getCombos, - getModelIsHidden, - getProviderConnections, - getProviderNodes, - getSettings, -} from "@/lib/localDb"; +import { getAllCustomModels, getAllSyncedAvailableModels, getModelIsHidden } from "@/lib/db/models"; +import { getCombos } from "@/lib/db/combos"; +import { getProviderConnections, getProviderNodes } from "@/lib/db/providers"; +import { getSettings } from "@/lib/db/settings"; import { getAccountDisplayName, getProviderDisplayName } from "@/lib/display/names"; import { getCompatibleFallbackModels } from "@/lib/providers/managedAvailableModels"; import { getResolvedModelCapabilities } from "@/lib/modelCapabilities"; diff --git a/src/lib/container.ts b/src/lib/container.ts index f3aa1af3f8a4..2f59af30e55c 100644 --- a/src/lib/container.ts +++ b/src/lib/container.ts @@ -23,7 +23,7 @@ import { encrypt, encryptConnectionFields, } from "./db/encryption.ts"; -import { getSettings } from "./localDb.ts"; +import { getSettings } from "@/lib/db/settings"; import { getCircuitBreaker } from "../shared/utils/circuitBreaker.ts"; import { recordTelemetry, RequestTelemetry } from "../shared/utils/requestTelemetry.ts"; diff --git a/src/lib/credentialHealth/scheduler.ts b/src/lib/credentialHealth/scheduler.ts index 62b2d41f4a80..15fb56c87668 100644 --- a/src/lib/credentialHealth/scheduler.ts +++ b/src/lib/credentialHealth/scheduler.ts @@ -18,7 +18,7 @@ */ import { testSingleConnection } from "@/app/api/providers/[id]/test/route"; -import { getProviderConnections } from "@/lib/localDb"; +import { getProviderConnections } from "@/lib/db/providers"; import { setCredentialHealth, removeCredentialHealth, diff --git a/src/lib/evals/runtime.ts b/src/lib/evals/runtime.ts index 9dc728f16fbd..d3ba6b0a5be2 100644 --- a/src/lib/evals/runtime.ts +++ b/src/lib/evals/runtime.ts @@ -1,7 +1,8 @@ import { POST as postChatCompletion } from "@/app/api/v1/chat/completions/route"; import type { PersistedEvalRun, EvalTargetType } from "@/lib/db/evals"; import { saveEvalRun } from "@/lib/db/evals"; -import { getApiKeyById, getCombos } from "@/lib/localDb"; +import { getApiKeyById } from "@/lib/db/apiKeys"; +import { getCombos } from "@/lib/db/combos"; import { getSuite, listSuites, runSuite } from "./evalRunner"; export interface EvalTargetInput { diff --git a/src/lib/freeProxyProviders/scheduler.ts b/src/lib/freeProxyProviders/scheduler.ts index 36e38ba64781..634cfb8d92da 100644 --- a/src/lib/freeProxyProviders/scheduler.ts +++ b/src/lib/freeProxyProviders/scheduler.ts @@ -22,7 +22,7 @@ */ import { getEnabledProviders } from "@/lib/freeProxyProviders"; -import { getFreeProxyStats } from "@/lib/localDb"; +import { getFreeProxyStats } from "@/lib/db/freeProxies"; import { runFreeProxySyncCycle, type FreeProxySyncCycleResult } from "./syncCycle"; const STARTUP_DELAY_MS = 5_000; diff --git a/src/lib/freeProxyProviders/syncCycle.ts b/src/lib/freeProxyProviders/syncCycle.ts index 666b6234e52c..d194091b0c52 100644 --- a/src/lib/freeProxyProviders/syncCycle.ts +++ b/src/lib/freeProxyProviders/syncCycle.ts @@ -3,7 +3,7 @@ import { recordFreeProxySync, clearFreeProxySyncErrors, recordFreeProxySyncErrors, -} from "@/lib/localDb"; +} from "@/lib/db/freeProxies"; import type { FreeProxyProvider } from "@/lib/freeProxyProviders/types"; export interface FreeProxySyncCycleResult { diff --git a/src/lib/guardrails/visionBridge.ts b/src/lib/guardrails/visionBridge.ts index db5603538692..c24729a3c99e 100644 --- a/src/lib/guardrails/visionBridge.ts +++ b/src/lib/guardrails/visionBridge.ts @@ -53,7 +53,7 @@ export async function getComboVisionBridgeDecision( model: string ): Promise { try { - const { getComboByName } = await import("@/lib/localDb"); + const { getComboByName } = await import("@/lib/db/combos"); const { resolveComboForModel } = await import("@/lib/db/modelComboMappings"); // 1. Try to find combo by exact name match diff --git a/src/lib/idempotencyLayer.ts b/src/lib/idempotencyLayer.ts index 628541cb80e6..da997c00161f 100644 --- a/src/lib/idempotencyLayer.ts +++ b/src/lib/idempotencyLayer.ts @@ -10,7 +10,7 @@ * @module lib/idempotencyLayer */ -import { getSettings } from "@/lib/localDb"; +import { getSettings } from "@/lib/db/settings"; const DEFAULT_WINDOW_MS = 5000; diff --git a/src/lib/images/imageRouteModel.ts b/src/lib/images/imageRouteModel.ts index 2c7f0ab3e5a3..41f414e0ef4a 100644 --- a/src/lib/images/imageRouteModel.ts +++ b/src/lib/images/imageRouteModel.ts @@ -18,7 +18,7 @@ import { parseImageModel } from "@omniroute/open-sse/config/imageRegistry.ts"; import { resolveComboTargets } from "@omniroute/open-sse/services/combo.ts"; import { getComboByName, getCombos } from "@/lib/db/combos"; -import { getCachedProviderNodes } from "@/lib/localDb"; +import { getCachedProviderNodes } from "@/lib/db/readCache"; import { assertMicrosoftDesignerWebProviderAvailable } from "@/shared/constants/designerWebRetirement"; import { assertCommonChatGptWebModelAvailable } from "@/shared/constants/chatgptWebRetirement"; diff --git a/src/lib/localHealthCheck.ts b/src/lib/localHealthCheck.ts index a496d4039971..1853fc910c27 100644 --- a/src/lib/localHealthCheck.ts +++ b/src/lib/localHealthCheck.ts @@ -11,7 +11,7 @@ * Uses Promise.allSettled so one slow/down node doesn't block others. */ -import { getCachedProviderNodes } from "@/lib/localDb"; +import { getCachedProviderNodes } from "@/lib/db/readCache"; import { isAutomatedTestProcess } from "@/shared/utils/testProcess"; // ── Types ──────────────────────────────────────────────────────────────── @@ -38,7 +38,6 @@ function isBuildProcess(): boolean { return typeof process !== "undefined" && process.env.NEXT_PHASE === "phase-production-build"; } - // ── State (globalThis survives HMR re-evaluation) ─────────────────────── declare global { diff --git a/src/lib/memory/embedding/index.ts b/src/lib/memory/embedding/index.ts index 9adf55f69ea7..8205b755241a 100644 --- a/src/lib/memory/embedding/index.ts +++ b/src/lib/memory/embedding/index.ts @@ -5,7 +5,7 @@ import { type EmbeddingProviderNodeRow, } from "@omniroute/open-sse/config/embeddingRegistry.ts"; import { getProviderCredentials } from "@/sse/services/auth"; -import { getCachedProviderNodes } from "@/lib/localDb"; +import { getCachedProviderNodes } from "@/lib/db/readCache"; import type { MemorySettingsExtended } from "@/shared/schemas/memory"; import type { EmbeddingResolution, diff --git a/src/lib/memory/reindex.ts b/src/lib/memory/reindex.ts index da7317b10a2d..8d459edaa769 100644 --- a/src/lib/memory/reindex.ts +++ b/src/lib/memory/reindex.ts @@ -7,7 +7,7 @@ import { getMemoryReindexQueue, countMemoryReindexPending, markMemoryNeedsReindex, -} from "@/lib/localDb"; +} from "@/lib/db/memoryVec"; import { resolveEmbeddingSource, embed } from "./embedding"; import { getVectorStore } from "./vectorStore"; import { getMemorySettings } from "./settings"; @@ -23,9 +23,7 @@ const log = logger("MEMORY_REINDEX"); * * @returns { processed: number; errors: number } */ -export async function runReindexBatch( - limit = 100 -): Promise<{ processed: number; errors: number }> { +export async function runReindexBatch(limit = 100): Promise<{ processed: number; errors: number }> { const queue = getMemoryReindexQueue(limit); if (queue.length === 0) { diff --git a/src/lib/memory/store.ts b/src/lib/memory/store.ts index 8f95b847763f..e0441eb3b22e 100644 --- a/src/lib/memory/store.ts +++ b/src/lib/memory/store.ts @@ -10,7 +10,7 @@ import { sanitizeErrorMessage } from "../../../open-sse/utils/error.ts"; import { resolveEmbeddingSource, embed } from "./embedding"; import { getVectorStore } from "./vectorStore"; import { getMemorySettings } from "./settings"; -import { markMemoryNeedsReindex } from "@/lib/localDb"; +import { markMemoryNeedsReindex } from "@/lib/db/memoryVec"; const log = logger("MEMORY_STORE"); diff --git a/src/lib/memory/vectorStore.ts b/src/lib/memory/vectorStore.ts index 92f706a3d46e..6996b8402444 100644 --- a/src/lib/memory/vectorStore.ts +++ b/src/lib/memory/vectorStore.ts @@ -14,7 +14,7 @@ import { setMemoryVecMeta, markAllMemoriesNeedReindex, countMemoryReindexPending, -} from "../localDb"; +} from "@/lib/db/memoryVec"; import { getDbInstance } from "../db/core"; import { logger } from "../../../open-sse/utils/logger.ts"; import { sanitizeErrorMessage } from "../../../open-sse/utils/error.ts"; @@ -54,7 +54,7 @@ export interface VectorStore { vector: Float32Array, queryText: string, topK: number, - apiKeyId?: string, + apiKeyId?: string ): Promise; /** Stats for UI Engine status. */ stats(): Promise<{ @@ -151,7 +151,7 @@ function recreateFromMeta(): boolean { const db = getDbInstance(); const q = storedVecQuantization(meta.embeddingSignature); db.exec( - `CREATE VIRTUAL TABLE IF NOT EXISTS vec_memories USING vec0(embedding ${vecColumnType(meta.activeDim, q)})`, + `CREATE VIRTUAL TABLE IF NOT EXISTS vec_memories USING vec0(embedding ${vecColumnType(meta.activeDim, q)})` ); return true; } @@ -189,7 +189,7 @@ class VectorStoreImpl implements VectorStore { const q = storedVecQuantization(meta.embeddingSignature); try { db.exec( - `CREATE VIRTUAL TABLE IF NOT EXISTS vec_memories USING vec0(embedding ${vecColumnType(dim, q)})`, + `CREATE VIRTUAL TABLE IF NOT EXISTS vec_memories USING vec0(embedding ${vecColumnType(dim, q)})` ); setMemoryVecMeta({ vecLoaded: true, activeDim: dim }); return { ready: true, reason: `vec_memories created with dim=${dim} (${q})` }; @@ -207,8 +207,7 @@ class VectorStoreImpl implements VectorStore { // Map UUID memoryId → INTEGER rowid (the rowid is used as the FK into vec_memories). const row = db.prepare("SELECT rowid FROM memories WHERE id = ?").get(memoryId) as - | { rowid: number } - | undefined; + { rowid: number } | undefined; if (!row) { throw new Error(`memory not found: ${memoryId}`); @@ -222,7 +221,7 @@ class VectorStoreImpl implements VectorStore { db.prepare("DELETE FROM vec_memories WHERE rowid = ?").run(BigInt(row.rowid)); db.prepare(`INSERT INTO vec_memories(rowid, embedding) VALUES (?, ${vecValueExpr(q)})`).run( BigInt(row.rowid), - encodeVector(vector), + encodeVector(vector) ); } catch (err: unknown) { // Self-heal: a concurrent ensureReady()'s reset raced ahead of us and @@ -232,7 +231,7 @@ class VectorStoreImpl implements VectorStore { db.prepare("DELETE FROM vec_memories WHERE rowid = ?").run(BigInt(row.rowid)); db.prepare(`INSERT INTO vec_memories(rowid, embedding) VALUES (?, ${vecValueExpr(q)})`).run( BigInt(row.rowid), - encodeVector(vector), + encodeVector(vector) ); } } @@ -241,7 +240,7 @@ class VectorStoreImpl implements VectorStore { const db = getDbInstance(); try { db.prepare( - "DELETE FROM vec_memories WHERE rowid = (SELECT rowid FROM memories WHERE id = ?)", + "DELETE FROM vec_memories WHERE rowid = (SELECT rowid FROM memories WHERE id = ?)" ).run(memoryId); } catch (err: unknown) { if (!isMissingVecTableError(err)) throw err; @@ -254,7 +253,7 @@ class VectorStoreImpl implements VectorStore { async searchVector( vector: Float32Array, topK: number, - apiKeyId?: string, + apiKeyId?: string ): Promise { const db = getDbInstance(); const k = topK > 0 ? topK : TOP_K_DEFAULT; @@ -268,12 +267,12 @@ class VectorStoreImpl implements VectorStore { WHERE v.embedding MATCH ${vecValueExpr(q)} AND ($apiKeyId IS NULL OR m.api_key_id = $apiKeyId) AND k = ? - ORDER BY v.distance ASC`, + ORDER BY v.distance ASC` ) .all(encodeVector(vector), { apiKeyId: apiKeyId ?? null }, k) as Array<{ - memory_id: string; - distance: number; - }>; + memory_id: string; + distance: number; + }>; return rows.map((r) => ({ memoryId: r.memory_id, @@ -286,7 +285,7 @@ class VectorStoreImpl implements VectorStore { vector: Float32Array, queryText: string, topK: number, - apiKeyId?: string, + apiKeyId?: string ): Promise { const db = getDbInstance(); const k = topK > 0 ? topK : TOP_K_DEFAULT; @@ -338,23 +337,16 @@ class VectorStoreImpl implements VectorStore { SELECT memory_id, vec_rank, fts_rank, vec_distance, fts_score, rrf_score FROM fused ORDER BY rrf_score DESC - LIMIT ?`, + LIMIT ?` ) - .all( - encodeVector(vector), - { apiKeyId: apiKeyId ?? null }, - k, - queryText, - k, - k, - ) as Array<{ - memory_id: string; - vec_rank: number | null; - fts_rank: number | null; - vec_distance: number | null; - fts_score: number | null; - rrf_score: number; - }>; + .all(encodeVector(vector), { apiKeyId: apiKeyId ?? null }, k, queryText, k, k) as Array<{ + memory_id: string; + vec_rank: number | null; + fts_rank: number | null; + vec_distance: number | null; + fts_score: number | null; + rrf_score: number; + }>; return rows.map((r) => ({ memoryId: r.memory_id, @@ -376,8 +368,7 @@ class VectorStoreImpl implements VectorStore { try { const db = getDbInstance(); const row = db.prepare("SELECT COUNT(*) AS cnt FROM vec_memories").get() as - | { cnt: number } - | undefined; + { cnt: number } | undefined; rowCount = row?.cnt ?? 0; } catch { // vec_memories may not exist yet — not an error, just 0 rows. @@ -431,7 +422,7 @@ export function getVectorStore(): VectorStore | null { // Test seam: VECTOR_STORE_DISABLE_VEC=true forces null (simulates cloud/WASM environment). if (process.env["VECTOR_STORE_DISABLE_VEC"] === "true") { log.warn( - "VECTOR_STORE_DISABLE_VEC is set — sqlite-vec disabled. Degrading to FTS5 keyword search.", + "VECTOR_STORE_DISABLE_VEC is set — sqlite-vec disabled. Degrading to FTS5 keyword search." ); _instance = null; return null; @@ -445,7 +436,7 @@ export function getVectorStore(): VectorStore | null { if (!raw || typeof raw.loadExtension !== "function") { log.warn( "sqlite-vec not loaded: db driver does not support loadExtension (cloud/WASM backend). " + - "Degrading to FTS5 keyword search.", + "Degrading to FTS5 keyword search." ); _instance = null; return null; diff --git a/src/lib/modelsDevSync.ts b/src/lib/modelsDevSync.ts index c4c0f2f27385..1400ad7ed408 100644 --- a/src/lib/modelsDevSync.ts +++ b/src/lib/modelsDevSync.ts @@ -770,7 +770,7 @@ export async function initModelsDevSync(): Promise { return; } - const { getSettings } = await import("./localDb"); + const { getSettings } = await import("@/lib/db/settings"); const settings = await getSettings(); if (!isModelsDevSyncEnvForcedOn() && settings.modelsDevSyncEnabled !== true) { diff --git a/src/lib/monitoring/providerHealthAutopilot.ts b/src/lib/monitoring/providerHealthAutopilot.ts index e29844da3687..b5fed7d41304 100644 --- a/src/lib/monitoring/providerHealthAutopilot.ts +++ b/src/lib/monitoring/providerHealthAutopilot.ts @@ -1,7 +1,7 @@ import { createHash } from "crypto"; import { getProviderConnections, updateProviderConnection } from "@/lib/db/providers"; -import { getCachedProviderConnectionById } from "@/lib/localDb"; +import { getCachedProviderConnectionById } from "@/lib/db/readCache"; import { clearProviderFailure, clearModelLock } from "@omniroute/open-sse/services/accountFallback"; import { resolveProviderAlias } from "@omniroute/open-sse/services/model"; diff --git a/src/lib/oauth/utils/agyAuthImport.ts b/src/lib/oauth/utils/agyAuthImport.ts index d0a526eb23cb..c7c4e42a3c2b 100644 --- a/src/lib/oauth/utils/agyAuthImport.ts +++ b/src/lib/oauth/utils/agyAuthImport.ts @@ -2,7 +2,7 @@ import { getProviderConnections, createProviderConnection, updateProviderConnection, -} from "@/lib/localDb"; +} from "@/lib/db/providers"; import { AGY_CONFIG } from "@/lib/oauth/constants/oauth"; import { getAntigravityContentHeaders, diff --git a/src/lib/oauth/utils/claudeAuthFile.ts b/src/lib/oauth/utils/claudeAuthFile.ts index 681c0eaab06e..0f265e90399d 100644 --- a/src/lib/oauth/utils/claudeAuthFile.ts +++ b/src/lib/oauth/utils/claudeAuthFile.ts @@ -1,6 +1,6 @@ import fs from "fs/promises"; import path from "path"; -import { getCachedProviderConnectionById } from "@/lib/localDb"; +import { getCachedProviderConnectionById } from "@/lib/db/readCache"; import { createBackup } from "@/shared/services/backupService"; import { getCliConfigPaths } from "@/shared/services/cliRuntime"; import { @@ -172,7 +172,9 @@ export function buildClaudeAuthPayload(connection: ClaudeConnectionLike): Claude } async function resolveFreshClaudeConnection(connectionId: string): Promise { - const connection = (await getCachedProviderConnectionById(connectionId)) as ClaudeConnectionLike | null; + const connection = (await getCachedProviderConnectionById( + connectionId + )) as ClaudeConnectionLike | null; if (!connection) { throw new ClaudeAuthFileError("Connection not found", 404, "not_found"); } diff --git a/src/lib/oauth/utils/claudeAuthImport.ts b/src/lib/oauth/utils/claudeAuthImport.ts index fb0a6aa000d6..2a5a44443663 100644 --- a/src/lib/oauth/utils/claudeAuthImport.ts +++ b/src/lib/oauth/utils/claudeAuthImport.ts @@ -3,7 +3,7 @@ import { getProviderConnections, createProviderConnection, updateProviderConnection, -} from "@/lib/localDb"; +} from "@/lib/db/providers"; import { getClaudeCodeUserAgent } from "@/shared/constants/claudeCodeClient"; import { ClaudeAuthFileError } from "@/lib/oauth/utils/claudeAuthFile"; diff --git a/src/lib/oauth/utils/codexAuthFile.ts b/src/lib/oauth/utils/codexAuthFile.ts index e17875766eff..8b505d7b3857 100644 --- a/src/lib/oauth/utils/codexAuthFile.ts +++ b/src/lib/oauth/utils/codexAuthFile.ts @@ -1,6 +1,6 @@ import fs from "fs/promises"; import path from "path"; -import { getCachedProviderConnectionById } from "@/lib/localDb"; +import { getCachedProviderConnectionById } from "@/lib/db/readCache"; import { createBackup } from "@/shared/services/backupService"; import { getCliConfigPaths } from "@/shared/services/cliRuntime"; import { @@ -197,7 +197,9 @@ function buildCodexAuthPayload(connection: CodexConnectionLike): CodexAuthFilePa } async function resolveFreshCodexConnection(connectionId: string): Promise { - const connection = (await getCachedProviderConnectionById(connectionId)) as CodexConnectionLike | null; + const connection = (await getCachedProviderConnectionById( + connectionId + )) as CodexConnectionLike | null; if (!connection) { throw new CodexAuthFileError("Connection not found", 404, "not_found"); } @@ -377,7 +379,11 @@ export type CodexAuthWriteDecision = export async function writeCodexAuthFileToLocalCliIfNeeded( connectionId: string, options: { force?: boolean } = {} -): Promise<{ decision: CodexAuthWriteDecision; authPath: string | null; result?: Awaited> }> { +): Promise<{ + decision: CodexAuthWriteDecision; + authPath: string | null; + result?: Awaited>; +}> { const paths = getCliConfigPaths("codex"); const authPath = paths?.auth ?? null; diff --git a/src/lib/oauth/utils/codexAuthImport.ts b/src/lib/oauth/utils/codexAuthImport.ts index 3ac22e09ca6c..4d16a3d1e638 100644 --- a/src/lib/oauth/utils/codexAuthImport.ts +++ b/src/lib/oauth/utils/codexAuthImport.ts @@ -2,7 +2,7 @@ import { getProviderConnections, createProviderConnection, updateProviderConnection, -} from "@/lib/localDb"; +} from "@/lib/db/providers"; import { CodexAuthFileError } from "@/lib/oauth/utils/codexAuthFile"; import { pickCodexConnectionForUser } from "@/lib/oauth/utils/codexConnectionSelection"; diff --git a/src/lib/providerModels/managedAvailableModels.ts b/src/lib/providerModels/managedAvailableModels.ts index b193fa4066cc..407022644c74 100644 --- a/src/lib/providerModels/managedAvailableModels.ts +++ b/src/lib/providerModels/managedAvailableModels.ts @@ -2,9 +2,9 @@ import { deleteModelAlias, getModelAliases, getModelIsHidden, - getProviderNodeById, setModelAlias, -} from "@/lib/localDb"; +} from "@/lib/db/models"; +import { getProviderNodeById } from "@/lib/db/providers"; import { getProviderAlias, isAnthropicCompatibleProvider, diff --git a/src/lib/proxyEgress.ts b/src/lib/proxyEgress.ts index 1996bec7633b..af7ab5ded531 100644 --- a/src/lib/proxyEgress.ts +++ b/src/lib/proxyEgress.ts @@ -313,7 +313,7 @@ export async function diagnoseAllEgressIps(deps?: { const getConnections = deps?.getConnections ?? (async () => { - const { getProviderConnections } = await import("./localDb"); + const { getProviderConnections } = await import("@/lib/db/providers"); return (await getProviderConnections({ authType: "oauth" })) as Array<{ id: string; provider: string; diff --git a/src/lib/proxyHealth/scheduler.ts b/src/lib/proxyHealth/scheduler.ts index dc30453a3495..8bf026ac3310 100644 --- a/src/lib/proxyHealth/scheduler.ts +++ b/src/lib/proxyHealth/scheduler.ts @@ -22,7 +22,7 @@ * same threshold, not independently tunable. */ -import { deleteProxyById, listProxies, updateProxy } from "@/lib/localDb"; +import { deleteProxyById, listProxies, updateProxy } from "@/lib/db/proxies"; import { isProxyLogIncludeIps } from "@/lib/proxyLogger"; import { getRecentEgressSharingSummary, diff --git a/src/lib/quota/connectionProvider.ts b/src/lib/quota/connectionProvider.ts index 3255bffad6ed..df81842aa74d 100644 --- a/src/lib/quota/connectionProvider.ts +++ b/src/lib/quota/connectionProvider.ts @@ -18,7 +18,7 @@ export async function resolveConnectionProvider(connectionId: string): Promise { try { // Lazy import — avoids circular deps and keeps the module loadable without a full DB. - const { getCachedProviderConnectionById } = await import("@/lib/localDb"); + const { getCachedProviderConnectionById } = await import("@/lib/db/readCache"); if (typeof getCachedProviderConnectionById === "function") { const conn = await getCachedProviderConnectionById(connectionId); if (conn && typeof (conn as { provider?: string }).provider === "string") { diff --git a/src/lib/quota/planResolver.ts b/src/lib/quota/planResolver.ts index a8a02647a7ae..aa04f3199b1c 100644 --- a/src/lib/quota/planResolver.ts +++ b/src/lib/quota/planResolver.ts @@ -12,7 +12,7 @@ * Part of: Group B — Quota Sharing Engine (plan 22, frente F6). */ -import { getProviderPlan } from "@/lib/localDb"; +import { getPlan as getProviderPlan } from "@/lib/db/providerPlans"; import { getKnownPlan } from "./planRegistry"; import type { ProviderPlan } from "./dimensions"; diff --git a/src/lib/quota/quotaCombos.ts b/src/lib/quota/quotaCombos.ts index 39dbe75a4a15..07b7e12a2855 100644 --- a/src/lib/quota/quotaCombos.ts +++ b/src/lib/quota/quotaCombos.ts @@ -12,7 +12,7 @@ import { getPool } from "@/lib/db/quotaPools"; import { getGroupName } from "@/lib/db/quotaGroups"; -import { getCachedProviderConnectionById } from "@/lib/localDb"; +import { getCachedProviderConnectionById } from "@/lib/db/readCache"; import { getCombos, createCombo, diff --git a/src/lib/quota/quotaKey.ts b/src/lib/quota/quotaKey.ts index 7207798bcc01..9f8006812082 100644 --- a/src/lib/quota/quotaKey.ts +++ b/src/lib/quota/quotaKey.ts @@ -8,7 +8,7 @@ */ import { getPool, getPoolsByGroup } from "@/lib/db/quotaPools"; -import { getCachedProviderConnectionById } from "@/lib/localDb"; +import { getCachedProviderConnectionById } from "@/lib/db/readCache"; import { getApiKeyById, updateApiKeyPermissions } from "@/lib/db/apiKeys"; import { quotaGroupSlug } from "./quotaModelNaming"; import { getGroupName } from "@/lib/db/quotaGroups"; @@ -163,7 +163,7 @@ export async function reconcilePoolExclusivity( poolId: string, prevApiKeyIds: string[], nextApiKeyIds: string[], - exclusive: boolean, + exclusive: boolean ): Promise { const affectedIds = new Set([...prevApiKeyIds, ...nextApiKeyIds]); @@ -173,7 +173,7 @@ export async function reconcilePoolExclusivity( if (!keyRow) continue; const currentQuotas: string[] = Array.isArray( - (keyRow as Record).allowedQuotas, + (keyRow as Record).allowedQuotas ) ? ((keyRow as Record).allowedQuotas as string[]) : []; diff --git a/src/lib/quota/redisQuotaStore.ts b/src/lib/quota/redisQuotaStore.ts index d9d17309eda7..7ff3588a1e22 100644 --- a/src/lib/quota/redisQuotaStore.ts +++ b/src/lib/quota/redisQuotaStore.ts @@ -16,10 +16,7 @@ * Part of: Group B — Quota Sharing Engine (plan 22, frente F6). */ -import { - getPool, - listAllocationsForApiKey, -} from "@/lib/localDb"; +import { getPool, listAllocationsForApiKey } from "@/lib/db/quotaPools"; import { WINDOW_MS, dimensionKeyToString } from "./dimensions"; import type { DimensionKey } from "./dimensions"; import type { QuotaStore, PoolUsageSnapshot } from "./types"; diff --git a/src/lib/quota/saturationSignals.ts b/src/lib/quota/saturationSignals.ts index 93d3d42690a9..0eee4331dcdb 100644 --- a/src/lib/quota/saturationSignals.ts +++ b/src/lib/quota/saturationSignals.ts @@ -306,10 +306,7 @@ async function fetchCodexSaturation( return Math.min(1, Math.max(0, quota.percentUsed ?? 0)); } -async function fetchBailianSaturation( - connectionId: string, - dim: DimensionSpec -): Promise { +async function fetchBailianSaturation(connectionId: string, dim: DimensionSpec): Promise { const mod = await import("@omniroute/open-sse/services/bailianQuotaFetcher"); const quota = await mod.fetchBailianQuota(connectionId); if (!quota) return 0; @@ -318,13 +315,13 @@ async function fetchBailianSaturation( let pct = 0; switch (dim.window) { case "5h": - pct = (q.window5h as Record)?.percentUsed as number ?? 0; + pct = ((q.window5h as Record)?.percentUsed as number) ?? 0; break; case "weekly": - pct = (q.windowWeekly as Record)?.percentUsed as number ?? 0; + pct = ((q.windowWeekly as Record)?.percentUsed as number) ?? 0; break; case "monthly": - pct = (q.windowMonthly as Record)?.percentUsed as number ?? 0; + pct = ((q.windowMonthly as Record)?.percentUsed as number) ?? 0; break; default: pct = (q.percentUsed as number) ?? 0; @@ -361,15 +358,13 @@ interface AnthropicSaturationDeps { let _anthropicDepsOverride: AnthropicSaturationDeps | null = null; /** Test-only: inject ({loadConnection, fetchUsage}); pass null to restore. */ -export function __setAnthropicSaturationDepsForTests( - deps: AnthropicSaturationDeps | null -): void { +export function __setAnthropicSaturationDepsForTests(deps: AnthropicSaturationDeps | null): void { _anthropicDepsOverride = deps; } async function defaultAnthropicDeps(): Promise { const [localDbMod, usageMod] = await Promise.all([ - import("@/lib/localDb"), + import("@/lib/db/readCache"), import("@omniroute/open-sse/services/usage"), ]); return { @@ -423,10 +418,7 @@ function planUtilizationFromUsage(usage: unknown, window: QuotaWindow): number | return Math.min(1, Math.max(0, used / 100)); } -async function fetchAnthropicSaturation( - connectionId: string, - dim: DimensionSpec -): Promise { +async function fetchAnthropicSaturation(connectionId: string, dim: DimensionSpec): Promise { // Try the REAL plan-window utilization first (5h / weekly), via the same // /api/oauth/usage path usage.ts already uses. This is the signal fairShare // actually needs for Claude Pro/Max — the per-minute request headers do not @@ -478,19 +470,13 @@ export function __setGenericUsageFetcherForTests(fetcher: GenericUsageFetcher | _genericUsageFetcherOverride = fetcher; } -async function defaultGenericUsageFetch( - connectionId: string, - provider: string -): Promise { +async function defaultGenericUsageFetch(connectionId: string, provider: string): Promise { const mod = await import("@omniroute/open-sse/services/usage"); const conn = { id: connectionId, provider } as Parameters[0]; return mod.getUsageForProvider(conn); } -async function fetchGenericSaturation( - connectionId: string, - provider: string -): Promise { +async function fetchGenericSaturation(connectionId: string, provider: string): Promise { // 1. Real usage percent is authoritative when present (a provider that // actually reports utilization beats the burst-window token headers). try { @@ -501,9 +487,8 @@ async function fetchGenericSaturation( // Prefer the normalized quota shape (handles nested `quotas` map for // Antigravity / Claude / etc.). Fall back to legacy top-level fields. - const { convertUsageToQuotaInfo } = await import( - "@omniroute/open-sse/services/genericQuotaFetcher" - ); + const { convertUsageToQuotaInfo } = + await import("@omniroute/open-sse/services/genericQuotaFetcher"); const quota = convertUsageToQuotaInfo(result); if (quota && Number.isFinite(quota.percentUsed)) { return Math.min(1, Math.max(0, quota.percentUsed)); @@ -570,7 +555,10 @@ export async function getSaturation( break; } } catch (err) { - log.warn({ err: (err as Error)?.message, connectionId, provider }, "saturation fetch failed — failing open with 0"); + log.warn( + { err: (err as Error)?.message, connectionId, provider }, + "saturation fetch failed — failing open with 0" + ); value = 0; } diff --git a/src/lib/quota/sqliteQuotaStore.ts b/src/lib/quota/sqliteQuotaStore.ts index dab40da91fea..8685e68c8599 100644 --- a/src/lib/quota/sqliteQuotaStore.ts +++ b/src/lib/quota/sqliteQuotaStore.ts @@ -15,7 +15,8 @@ * Part of: Group B — Quota Sharing Engine (plan 22, frente F6). */ -import { getPool, getBucket, incrementBucket, getPair, sumPoolDimension } from "@/lib/localDb"; +import { getPool } from "@/lib/db/quotaPools"; +import { getBucket, incrementBucket, getPair, sumPoolDimension } from "@/lib/db/quotaConsumption"; import { WINDOW_MS, dimensionKeyToString } from "./dimensions"; import type { DimensionKey } from "./dimensions"; import type { QuotaStore, PoolUsageSnapshot } from "./types"; diff --git a/src/lib/services/quotaAutoPing.ts b/src/lib/services/quotaAutoPing.ts index 4a9ce8fe3e85..21c6466b52a5 100644 --- a/src/lib/services/quotaAutoPing.ts +++ b/src/lib/services/quotaAutoPing.ts @@ -25,7 +25,8 @@ import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error.ts"; import { getExecutor } from "@omniroute/open-sse/executors/index.ts"; import type { BaseExecutor } from "@omniroute/open-sse/executors/base"; import { getCodexUsage } from "@omniroute/open-sse/services/usage/codex.ts"; -import { getSettings, getProviderConnections, updateProviderConnection } from "@/lib/localDb"; +import { getSettings } from "@/lib/db/settings"; +import { getProviderConnections, updateProviderConnection } from "@/lib/db/providers"; import { isConnectionUnavailableToAuxiliaryActivity } from "@/lib/exclusiveLeaseIsolation"; import { refreshAndUpdateCredentials } from "@/lib/usage/providerLimits"; import { getCircuitBreaker } from "@/shared/utils/circuitBreaker"; @@ -57,17 +58,12 @@ export interface QuotaAutoPingConnection { export interface QuotaAutoPingDeps { getSettings: () => Promise; - getProviderConnections: ( - filter: JsonRecord - ) => Promise; + getProviderConnections: (filter: JsonRecord) => Promise; updateProviderConnection: (id: string, data: JsonRecord) => Promise; refreshAndUpdateCredentials: ( connection: QuotaAutoPingConnection ) => Promise<{ connection: QuotaAutoPingConnection }>; - getCodexUsage: ( - accessToken?: string, - providerSpecificData?: JsonRecord - ) => Promise; + getCodexUsage: (accessToken?: string, providerSpecificData?: JsonRecord) => Promise; getExecutor: (provider: string) => Promise; canExecuteProvider: (provider: string) => boolean; isConnectionUnavailableToAuxiliaryActivity: (connectionId: string) => Promise; @@ -152,11 +148,7 @@ function wasPingedRecently( return Number.isFinite(lastPingAtMs) && nowMs - lastPingAtMs < intervalMs; } -function shouldSkipAfterFailure( - state: QuotaAutoPingState, - key: string, - nowMs: number -): boolean { +function shouldSkipAfterFailure(state: QuotaAutoPingState, key: string, nowMs: number): boolean { const failedAt = state.failureCache[key]; return Boolean(failedAt) && nowMs - failedAt < QUOTA_AUTOPING_FAILURE_COOLDOWN_MS; } @@ -272,8 +264,8 @@ async function isPingCandidateBlocked( // resetAt — the guard is preserved here for that case. return Boolean( !providerConfig.pingWhenResetAtSlides && - cachedReset && - nowMs < new Date(cachedReset).getTime() - QUOTA_AUTOPING_REFRESH_AHEAD_MS + cachedReset && + nowMs < new Date(cachedReset).getTime() - QUOTA_AUTOPING_REFRESH_AHEAD_MS ); } @@ -331,7 +323,18 @@ async function pingConnection( ): Promise { const key = cacheKey(provider, connection.id); const cachedReset = state.resetCache[key]; - if (await isPingCandidateBlocked(connection, provider, providerConfig, deps, state, key, cachedReset, nowMs)) { + if ( + await isPingCandidateBlocked( + connection, + provider, + providerConfig, + deps, + state, + key, + cachedReset, + nowMs + ) + ) { return; } @@ -346,7 +349,9 @@ async function pingConnection( state.resetCache[key] = resetAt; const resetKey = normalizeResetKey(resetAt); - if (!shouldSendPing(providerConfig, quotas, quota, cachedReset, resetAt, current, resetKey, nowMs)) { + if ( + !shouldSendPing(providerConfig, quotas, quota, cachedReset, resetAt, current, resetKey, nowMs) + ) { return; } @@ -371,8 +376,7 @@ function getEnabledConnectionIds( ): Record { return ( ((settings[providerConfig.settingsKey] as JsonRecord | undefined)?.connections as - | Record - | undefined) || {} + Record | undefined) || {} ); } diff --git a/src/lib/sync/bundle.ts b/src/lib/sync/bundle.ts index 3f13edf8fa68..46e5100b3acf 100644 --- a/src/lib/sync/bundle.ts +++ b/src/lib/sync/bundle.ts @@ -1,13 +1,11 @@ import { createHash } from "crypto"; -import { - getApiKeys, - getCombos, - getModelAliases, - getProviderConnections, - getCachedProviderNodes, - getSettings, - getReasoningRoutingRules, -} from "@/lib/localDb"; +import { getApiKeys } from "@/lib/db/apiKeys"; +import { getCombos } from "@/lib/db/combos"; +import { getModelAliases } from "@/lib/db/models"; +import { getProviderConnections } from "@/lib/db/providers"; +import { getCachedProviderNodes } from "@/lib/db/readCache"; +import { getSettings } from "@/lib/db/settings"; +import { getReasoningRoutingRules } from "@/lib/db/reasoningRoutingRules"; type JsonRecord = Record; diff --git a/src/lib/tokenHealthCheck.ts b/src/lib/tokenHealthCheck.ts index b3a4e2e074a0..2820a28e2340 100644 --- a/src/lib/tokenHealthCheck.ts +++ b/src/lib/tokenHealthCheck.ts @@ -11,13 +11,9 @@ * updates the DB, and logs the result. */ -import { - getProviderConnections, - getCachedProviderConnectionById, - updateProviderConnection, - getSettings, - resolveProxyForConnection, -} from "@/lib/localDb"; +import { getProviderConnections, updateProviderConnection } from "@/lib/db/providers"; +import { getCachedProviderConnectionById } from "@/lib/db/readCache"; +import { getSettings, resolveProxyForConnection } from "@/lib/db/settings"; import { getAccessToken, getDeprecationNotice, diff --git a/src/lib/tokenHealthCheckCopilot.ts b/src/lib/tokenHealthCheckCopilot.ts index f2c58ef362c2..bd1a9ea0788b 100644 --- a/src/lib/tokenHealthCheckCopilot.ts +++ b/src/lib/tokenHealthCheckCopilot.ts @@ -10,7 +10,7 @@ * frozen file-size budget (see config/quality/file-size-baseline.json). */ -import { getProviderConnectionById, updateProviderConnection } from "@/lib/localDb"; +import { getProviderConnectionById, updateProviderConnection } from "@/lib/db/providers"; import { refreshCopilotToken } from "@omniroute/open-sse/services/tokenRefresh.ts"; type HealthCheckLogger = { @@ -30,8 +30,17 @@ export async function refreshGithubCopilotSubTokenIfNeeded(params: { getConnectionLogLabel: (conn: { name?: string; email?: string; id?: string }) => string; logPrefix: string; }): Promise { - const { conn, result, proxyConfig, healthCheckLog, log, logWarn, logError, getConnectionLogLabel, logPrefix } = - params; + const { + conn, + result, + proxyConfig, + healthCheckLog, + log, + logWarn, + logError, + getConnectionLogLabel, + logPrefix, + } = params; if (String(conn.provider || "").toLowerCase() !== "github") return; @@ -58,7 +67,11 @@ export async function refreshGithubCopilotSubTokenIfNeeded(params: { log(`${logPrefix} Refreshing GitHub Copilot sub-token for ${getConnectionLogLabel(conn)}`); try { - const copilotResult = await refreshCopilotToken(accessTokenForCopilot, healthCheckLog, proxyConfig); + const copilotResult = await refreshCopilotToken( + accessTokenForCopilot, + healthCheckLog, + proxyConfig + ); if (copilotResult?.token) { await updateProviderConnection(conn.id, { providerSpecificData: { @@ -69,7 +82,9 @@ export async function refreshGithubCopilotSubTokenIfNeeded(params: { }); log(`${logPrefix} ✓ GitHub Copilot sub-token refreshed for ${getConnectionLogLabel(conn)}`); } else { - logWarn(`${logPrefix} ✗ GitHub Copilot sub-token refresh failed for ${getConnectionLogLabel(conn)}`); + logWarn( + `${logPrefix} ✗ GitHub Copilot sub-token refresh failed for ${getConnectionLogLabel(conn)}` + ); } } catch (copilotErr) { logError(`${logPrefix} Error refreshing Copilot sub-token:`, copilotErr?.message || copilotErr); diff --git a/src/lib/usage/apiKeySelfService.ts b/src/lib/usage/apiKeySelfService.ts index 1e7237e12c60..2e9dc9ffa70a 100644 --- a/src/lib/usage/apiKeySelfService.ts +++ b/src/lib/usage/apiKeySelfService.ts @@ -1,7 +1,4 @@ -import { - hasSelfAccountQuotaScope, - hasSelfUsageScope, -} from "@/shared/constants/selfServiceScopes"; +import { hasSelfAccountQuotaScope, hasSelfUsageScope } from "@/shared/constants/selfServiceScopes"; import { USAGE_SUPPORTED_PROVIDERS } from "@/shared/constants/providers"; type JsonRecord = Record; @@ -363,7 +360,7 @@ async function normalizeDeps(deps: ApiKeySelfServiceDeps): Promise const localDb = deps.getProviderConnectionById && deps.getProviderConnections ? null - : await import("@/lib/localDb"); + : await import("@/lib/db/providers"); const providerLimits = deps.fetchAndPersistProviderLimits ? null : await import("@/lib/usage/providerLimits"); @@ -396,11 +393,11 @@ export async function buildApiKeySelfServiceStatus( const tokens = aggregateTokens( resolvedDeps.getDbInstance() as DbLike, metadata.id, - cost.periodStartAt ?? new Date(getCurrentMonthWindow(resolvedDeps.now()).periodStartAt).toISOString() + cost.periodStartAt ?? + new Date(getCurrentMonthWindow(resolvedDeps.now()).periodStartAt).toISOString() ); const accountQuotas = await resolveAccountQuotas(metadata, resolvedDeps); - const accountQuota = - accountQuotas && accountQuotas.length === 1 ? accountQuotas[0] : undefined; + const accountQuota = accountQuotas && accountQuotas.length === 1 ? accountQuotas[0] : undefined; return { apiKey: { diff --git a/src/lib/usage/callLogs.ts b/src/lib/usage/callLogs.ts index 5e3c878c0c0a..029bc7ee2f12 100644 --- a/src/lib/usage/callLogs.ts +++ b/src/lib/usage/callLogs.ts @@ -140,7 +140,7 @@ async function resolveAccountName(connectionId: string | null | undefined) { } try { - const { getProviderConnections } = await import("@/lib/localDb"); + const { getProviderConnections } = await import("@/lib/db/providers"); const connections = await getProviderConnections(); const conn = connections.find((item) => item.id === connectionId); if (conn) { @@ -160,7 +160,7 @@ async function resolveAccountName(connectionId: string | null | undefined) { async function resolveProviderPrefix(providerId: string): Promise { if (!providerId) return null; try { - const { getProviderNodeById } = await import("@/lib/localDb"); + const { getProviderNodeById } = await import("@/lib/db/providers"); const node = await getProviderNodeById(providerId); if (node && typeof node.prefix === "string" && node.prefix.trim().length > 0) { return node.prefix.trim(); diff --git a/src/lib/usage/codexResetCredits.ts b/src/lib/usage/codexResetCredits.ts index ab54b40a9d09..9a341101b4bf 100644 --- a/src/lib/usage/codexResetCredits.ts +++ b/src/lib/usage/codexResetCredits.ts @@ -1,4 +1,5 @@ -import { getProviderConnectionById, resolveProxyForConnection } from "@/lib/localDb"; +import { getProviderConnectionById } from "@/lib/db/providers"; +import { resolveProxyForConnection } from "@/lib/db/settings"; import { isConnectionUnavailableToAuxiliaryActivity } from "@/lib/exclusiveLeaseIsolation"; import { fetchAndPersistProviderLimits, diff --git a/src/lib/usage/costCalculator.ts b/src/lib/usage/costCalculator.ts index 08151ac1aadf..106b7f8c4cc1 100644 --- a/src/lib/usage/costCalculator.ts +++ b/src/lib/usage/costCalculator.ts @@ -187,7 +187,7 @@ export async function calculateCost( if (exactCostUsd !== null) return exactCostUsd; try { - const { getPricingForModel } = await import("@/lib/localDb"); + const { getPricingForModel } = await import("@/lib/db/settings"); // Try exact match first, then normalized model name let pricing = await getPricingForModel(provider, model); @@ -303,7 +303,7 @@ export async function calculateModalCost( ): Promise { if (!provider || !model) return 0; try { - const { getPricingForModel } = await import("@/lib/localDb"); + const { getPricingForModel } = await import("@/lib/db/settings"); let pricing = await getPricingForModel(provider, model); if (!pricing) { const normalized = normalizeModelName(model); diff --git a/src/lib/usage/internalUsageCommand.ts b/src/lib/usage/internalUsageCommand.ts index 37f5f009d1c2..e106cced5188 100644 --- a/src/lib/usage/internalUsageCommand.ts +++ b/src/lib/usage/internalUsageCommand.ts @@ -99,7 +99,7 @@ async function normalizeDeps(deps: InternalUsageCommandDeps = {}): Promise { const [{ getCachedSettings }, { resolveResilienceSettings }] = await Promise.all([ - import("@/lib/localDb"), + import("@/lib/db/readCache"), import("@/lib/resilience/settings"), ]); const resilience = resolveResilienceSettings(await getCachedSettings()); @@ -829,7 +829,10 @@ export async function handleInternalUsageCommandHttpRequest( if (!apiKey || !(await resolvedDeps.isValidApiKey(apiKey))) { if (json) { return Response.json( - { allowed: false, error: { message: USAGE_COMMAND_AUTH_REQUIRED_MESSAGE } } satisfies UsageCommandJson, + { + allowed: false, + error: { message: USAGE_COMMAND_AUTH_REQUIRED_MESSAGE }, + } satisfies UsageCommandJson, { status: 401 } ); } @@ -840,7 +843,10 @@ export async function handleInternalUsageCommandHttpRequest( if (!metadata?.id) { if (json) { return Response.json( - { allowed: false, error: { message: USAGE_COMMAND_AUTH_REQUIRED_MESSAGE } } satisfies UsageCommandJson, + { + allowed: false, + error: { message: USAGE_COMMAND_AUTH_REQUIRED_MESSAGE }, + } satisfies UsageCommandJson, { status: 401 } ); } @@ -850,7 +856,10 @@ export async function handleInternalUsageCommandHttpRequest( if (metadata.allowUsageCommand !== true) { if (json) { return Response.json( - { allowed: false, error: { message: USAGE_COMMAND_DISABLED_MESSAGE } } satisfies UsageCommandJson, + { + allowed: false, + error: { message: USAGE_COMMAND_DISABLED_MESSAGE }, + } satisfies UsageCommandJson, { status: 403 } ); } diff --git a/src/lib/usage/usageStats.ts b/src/lib/usage/usageStats.ts index b4be4d1967e6..573f66b2c026 100644 --- a/src/lib/usage/usageStats.ts +++ b/src/lib/usage/usageStats.ts @@ -285,7 +285,7 @@ export async function getUsageStats() { const sourceSql = buildUsageSourceSql(aggregationEnabled); const sourceParams = aggregationEnabled && cutoffDate ? [cutoffDate, cutoffDate] : []; - const { getProviderConnections } = await import("@/lib/localDb"); + const { getProviderConnections } = await import("@/lib/db/providers"); let allConnections: unknown[] = []; try { const loadedConnections = await getProviderConnections(); diff --git a/src/lib/usageAnalytics.ts b/src/lib/usageAnalytics.ts index 386a3d6e34f2..459d8e189574 100644 --- a/src/lib/usageAnalytics.ts +++ b/src/lib/usageAnalytics.ts @@ -149,7 +149,7 @@ export async function computeAnalytics( // Pre-fetch pricing for all unique (provider, model) pairs — one DB round-trip // per unique pair instead of one per entry, then compute costs synchronously. - const { getPricingForModel } = await import("@/lib/localDb"); + const { getPricingForModel } = await import("@/lib/db/settings"); const pricingCache = new Map | null>(); const uniquePairs = new Set(entries.map((e) => `${e.provider}|||${e.model}`)); await Promise.all( diff --git a/src/lib/ws/handshake.ts b/src/lib/ws/handshake.ts index 8fafc39b53e5..454fbe94576b 100644 --- a/src/lib/ws/handshake.ts +++ b/src/lib/ws/handshake.ts @@ -1,5 +1,5 @@ import { jwtVerify } from "jose"; -import { getSettings } from "@/lib/localDb"; +import { getSettings } from "@/lib/db/settings"; import { validateApiKey } from "@/lib/db/apiKeys"; export const DEFAULT_WS_PATH = "/v1/ws"; From 7d8c365ea10fb7bb50dcda0021a361f2b2e15fb9 Mon Sep 17 00:00:00 2001 From: WebPerson Date: Sat, 29 Aug 2026 13:29:20 -0500 Subject: [PATCH 03/22] fix(barrel): migrate open-sse, src/shared, src/sse, src/models, src/domain off the localDb barrel (#59) Replace all 31 remaining barrel imports with direct imports from the owning @/lib/db/* modules, per AGENTS.md G14. Covers static imports, dynamic imports (lazy semantics kept), the multi-module Promise.all destructure pattern (rateLimitManager, comboContextCache), and the src/models/index.ts re-export file split into per-module re-exports. Mechanical codemod only - no behavior change. Type-only imports stay type-only and the volumeDetector test vi.mock re-points at the real module. Prune the 20 now-satisfied no-restricted-imports suppressions. Verified: typecheck:core green, eslint changed files green, check:cycles green, open-sse test suite passes (107/107 in focused batch). --- config/quality/eslint-suppressions.json | 88 ------------------- open-sse/handlers/chatCore.ts | 19 ++-- .../handlers/chatCore/comboContextCache.ts | 6 +- .../services/__tests__/volumeDetector.test.ts | 2 +- open-sse/services/batchProcessor.ts | 12 ++- open-sse/services/combo.ts | 10 +-- .../services/combo/applyStrategyOrdering.ts | 2 +- open-sse/services/combo/concurrencyCaps.ts | 2 +- .../services/combo/quotaExhaustionCutoff.ts | 5 +- .../services/combo/resolveAutoStrategy.ts | 2 +- open-sse/services/combo/targetSorters.ts | 2 +- open-sse/services/keyGroupAuth.ts | 2 +- open-sse/services/model.ts | 6 +- open-sse/services/payloadRules.ts | 2 +- open-sse/services/rateLimitManager.ts | 5 +- open-sse/services/tokenLimitCounter.ts | 5 +- open-sse/services/volumeDetector.ts | 2 +- open-sse/utils/networkProxy.ts | 2 +- open-sse/utils/proxyFallback.ts | 3 +- src/domain/quotaCache.ts | 3 +- src/models/index.ts | 15 ++-- src/shared/services/apiKeyResolver.ts | 2 +- src/shared/services/cloudSyncScheduler.ts | 2 +- src/shared/services/initializeCloudSync.ts | 3 +- src/shared/services/modelSyncScheduler.ts | 4 +- src/shared/utils/apiAuth.ts | 2 +- src/shared/utils/apiKeyPolicy.ts | 8 +- src/sse/handlers/autoRouting.ts | 2 +- src/sse/handlers/chatHelpers.ts | 8 +- src/sse/services/model.ts | 14 +-- src/sse/services/noAuthProviderSettings.ts | 2 +- src/sse/services/tokenRefresh.ts | 8 +- 32 files changed, 77 insertions(+), 173 deletions(-) diff --git a/config/quality/eslint-suppressions.json b/config/quality/eslint-suppressions.json index 74a52b6a5536..f865a7ba3bdb 100644 --- a/config/quality/eslint-suppressions.json +++ b/config/quality/eslint-suppressions.json @@ -230,11 +230,6 @@ "count": 1 } }, - "open-sse/handlers/chatCore/comboContextCache.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "open-sse/handlers/chatCore/executorHelpers.ts": { "@typescript-eslint/no-unused-vars": { "count": 1 @@ -465,9 +460,6 @@ "@typescript-eslint/no-explicit-any": { "count": 17 }, - "no-restricted-imports": { - "count": 2 - }, "no-restricted-syntax": { "count": 1 } @@ -510,21 +502,11 @@ "count": 21 } }, - "open-sse/services/combo/concurrencyCaps.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "open-sse/services/combo/providerWildcard.ts": { "@typescript-eslint/no-unused-vars": { "count": 1 } }, - "open-sse/services/combo/quotaExhaustionCutoff.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "open-sse/services/comboAgentMiddleware.ts": { "@typescript-eslint/no-unused-vars": { "count": 1 @@ -621,11 +603,6 @@ "count": 2 } }, - "open-sse/services/keyGroupAuth.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "open-sse/services/manifestAdapter.ts": { "@typescript-eslint/no-unused-vars": { "count": 3 @@ -689,9 +666,6 @@ "open-sse/services/tokenLimitCounter.ts": { "@typescript-eslint/no-unused-vars": { "count": 1 - }, - "no-restricted-imports": { - "count": 1 } }, "open-sse/services/tokenRefresh.ts": { @@ -827,11 +801,6 @@ "count": 1 } }, - "open-sse/utils/proxyFallback.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "open-sse/utils/setupPolyfill.ts": { "@typescript-eslint/no-explicit-any": { "count": 5 @@ -1516,11 +1485,6 @@ "count": 1 } }, - "src/domain/quotaCache.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/hooks/useLiveDashboard.ts": { "@typescript-eslint/no-unused-vars": { "count": 1 @@ -2035,11 +1999,6 @@ "count": 3 } }, - "src/models/index.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/server/ws/liveServer.ts": { "@typescript-eslint/no-unused-vars": { "count": 5 @@ -2156,11 +2115,6 @@ "count": 1 } }, - "src/shared/services/apiKeyResolver.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/shared/services/backupService.ts": { "@typescript-eslint/no-unused-vars": { "count": 1 @@ -2169,27 +2123,11 @@ "src/shared/services/cloudSyncScheduler.ts": { "@typescript-eslint/no-unused-vars": { "count": 1 - }, - "no-restricted-imports": { - "count": 1 } }, "src/shared/services/initializeCloudSync.ts": { "@typescript-eslint/no-unused-vars": { "count": 1 - }, - "no-restricted-imports": { - "count": 1 - } - }, - "src/shared/services/modelSyncScheduler.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/shared/utils/apiAuth.ts": { - "no-restricted-imports": { - "count": 1 } }, "src/shared/utils/apiKey.ts": { @@ -2200,9 +2138,6 @@ "src/shared/utils/apiKeyPolicy.ts": { "@typescript-eslint/no-unused-vars": { "count": 2 - }, - "no-restricted-imports": { - "count": 1 } }, "src/shared/utils/cloud.ts": { @@ -2300,11 +2235,6 @@ "count": 11 } }, - "src/sse/handlers/autoRouting.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/sse/handlers/chat.ts": { "@typescript-eslint/no-unused-vars": { "count": 9 @@ -2313,24 +2243,6 @@ "src/sse/handlers/chatHelpers.ts": { "@typescript-eslint/no-unused-vars": { "count": 3 - }, - "no-restricted-imports": { - "count": 1 - } - }, - "src/sse/services/model.ts": { - "no-restricted-imports": { - "count": 2 - } - }, - "src/sse/services/noAuthProviderSettings.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/sse/services/tokenRefresh.ts": { - "no-restricted-imports": { - "count": 1 } }, "tests/benchmarks/pipeline-accuracy.test.ts": { diff --git a/open-sse/handlers/chatCore.ts b/open-sse/handlers/chatCore.ts index b82bf41cb872..d2e1785c343d 100644 --- a/open-sse/handlers/chatCore.ts +++ b/open-sse/handlers/chatCore.ts @@ -1427,7 +1427,7 @@ export async function handleChatCore({ }; if ((isCombo && comboName) || routingComboId) { try { - const { getComboByName } = await import("../../src/lib/localDb"); + const { getComboByName } = await import("@/lib/db/combos"); let comboConfig = await getComboByName(comboName); if (!comboConfig && comboName?.startsWith("combo/")) { comboConfig = await getComboByName(comboName.substring(6)); @@ -1918,7 +1918,7 @@ export async function handleChatCore({ if (isCombo && comboName) { log?.info?.("CONTEXT", `Attempting to resolve combo limits for comboName=${comboName}`); try { - const { getComboByName } = await import("../../src/lib/localDb"); + const { getComboByName } = await import("@/lib/db/combos"); const { resolveComboTargets } = await import("../services/combo.ts"); let comboConfig = await getComboByName(comboName); if (!comboConfig && comboName.startsWith("combo/")) { @@ -4292,13 +4292,14 @@ export async function handleChatCore({ let quotaCooldownMs = kimiRateLimitResetAt ? Math.max(new Date(kimiRateLimitResetAt).getTime() - Date.now(), 0) : retryAfterMs || COOLDOWN_MS.rateLimit; - const deferAntigravityQuotaStateToCaller = - shouldDeferAntigravityQuotaStateToCaller( - provider, - typeof onStreamFailure === "function" - ); - const isAntigravityQuotaFamily = - shouldDeferAntigravityQuotaStateToCaller(provider, true); + const deferAntigravityQuotaStateToCaller = shouldDeferAntigravityQuotaStateToCaller( + provider, + typeof onStreamFailure === "function" + ); + const isAntigravityQuotaFamily = shouldDeferAntigravityQuotaStateToCaller( + provider, + true + ); let coreOwnedAntigravityLockout: { cooldownMs: number; failureCount: number; diff --git a/open-sse/handlers/chatCore/comboContextCache.ts b/open-sse/handlers/chatCore/comboContextCache.ts index 1a4f2c5b2c00..266591b976a7 100644 --- a/open-sse/handlers/chatCore/comboContextCache.ts +++ b/open-sse/handlers/chatCore/comboContextCache.ts @@ -1,5 +1,4 @@ -import { getUpstreamProxyConfig } from "@/lib/localDb"; -import type { FallbackBackend } from "@/lib/db/upstreamProxy"; +import { FallbackBackend, getUpstreamProxyConfig } from "@/lib/db/upstreamProxy"; /** * Module-level cache for upstream proxy config (shared across all requests). @@ -29,7 +28,8 @@ const COMBOS_CACHE_TTL = 10_000; export async function getCombosCached(): Promise { const now = Date.now(); - const { getCombos, getCombosCacheVersion } = await import("@/lib/localDb"); + const { getCombos } = await import("@/lib/db/combos"); + const { getCombosCacheVersion } = await import("@/lib/db/readCache"); const version = getCombosCacheVersion(); // A combo write (create/update/delete/reorder) bumps the shared version via // invalidateDbCache("combos"); when it no longer matches our snapshot we drop diff --git a/open-sse/services/__tests__/volumeDetector.test.ts b/open-sse/services/__tests__/volumeDetector.test.ts index 7b7128947878..37e278a82c3b 100644 --- a/open-sse/services/__tests__/volumeDetector.test.ts +++ b/open-sse/services/__tests__/volumeDetector.test.ts @@ -3,7 +3,7 @@ import { describe, it, expect, vi } from "vitest"; // Mock the DB so recommendStrategyOverride sees adaptiveVolumeRouting = true. // Without this the real getSettings() throws (no SQLite in test env), the // catch block fires, and the function returns noOverride before any rule runs. -vi.mock("@/lib/localDb", () => ({ +vi.mock("@/lib/db/settings", () => ({ getSettings: vi.fn().mockResolvedValue({ adaptiveVolumeRouting: true }), })); diff --git a/open-sse/services/batchProcessor.ts b/open-sse/services/batchProcessor.ts index e9fe915afd87..7b242e4851bc 100644 --- a/open-sse/services/batchProcessor.ts +++ b/open-sse/services/batchProcessor.ts @@ -1,22 +1,20 @@ import { v4 as uuidv4 } from "uuid"; -import type { BatchItemCheckpoint, BatchRecord } from "@/lib/localDb"; import { + type BatchItemCheckpoint, + type BatchRecord, countBatchItemCheckpoints, - createFile, - deleteFile, ensureBatchItemCheckpoints, - getApiKeyById, getBatch, - getFileContent, getPendingBatches, getTerminalBatches, listBatchItemCheckpoints, - listFiles, markBatchItemError, markBatchItemProcessing, markBatchItemResult, updateBatch, -} from "@/lib/localDb"; +} from "@/lib/db/batches"; +import { createFile, deleteFile, getFileContent, listFiles } from "@/lib/db/files"; +import { getApiKeyById } from "@/lib/db/apiKeys"; import { dispatch } from "@/lib/batches/dispatch"; import type { SupportedBatchEndpoint } from "@/shared/constants/batchEndpoints"; import { DEFAULT_BATCH_EXPIRATION_SECONDS } from "@/shared/constants/batch"; diff --git a/open-sse/services/combo.ts b/open-sse/services/combo.ts index ffc5f3e73b58..907bef152564 100644 --- a/open-sse/services/combo.ts +++ b/open-sse/services/combo.ts @@ -400,7 +400,7 @@ export async function buildAutoCandidates( // apply, so auto-routing behavior is unchanged. const quotaCutoffEnabled = (resilienceSettings ?? resolveResilienceSettings(null))?.quotaPreflight?.enabled === true; - const { getPricingForModel } = await import("../../src/lib/localDb"); + const { getPricingForModel } = await import("@/lib/db/settings"); const quotaPromises = new Map>(); let historicalLatencyStats: Record = {}; try { @@ -1937,7 +1937,7 @@ async function handleComboChatInner({ const connId = effectiveConnectionId || undefined; void (async () => { try { - const { setLKGP } = await import("../../src/lib/localDb"); + const { setLKGP } = await import("@/lib/db/settings"); await Promise.all([ setLKGP(combo.name, target.executionKey, provider, connId), setLKGP(combo.name, combo.id || combo.name, provider, connId), @@ -2418,7 +2418,7 @@ async function handleComboChatInner({ // so nothing else clears this stale pin. void (async () => { try { - const { clearLKGP } = await import("../../src/lib/localDb"); + const { clearLKGP } = await import("@/lib/db/settings"); await Promise.all([ clearLKGP(combo.name, target.executionKey), clearLKGP(combo.name, combo.id || combo.name), @@ -3535,7 +3535,7 @@ async function handleRoundRobinCombo({ const connId = effectiveConnectionId || undefined; void (async () => { try { - const { setLKGP } = await import("../../src/lib/localDb"); + const { setLKGP } = await import("@/lib/db/settings"); await Promise.all([ setLKGP(combo.name, target.executionKey, provider, connId), setLKGP(combo.name, combo.id || combo.name, provider, connId), @@ -3768,7 +3768,7 @@ async function handleRoundRobinCombo({ // by a request-scoped failure class like a stream-readiness timeout). void (async () => { try { - const { clearLKGP } = await import("../../src/lib/localDb"); + const { clearLKGP } = await import("@/lib/db/settings"); await Promise.all([ clearLKGP(combo.name, target.executionKey), clearLKGP(combo.name, combo.id || combo.name), diff --git a/open-sse/services/combo/applyStrategyOrdering.ts b/open-sse/services/combo/applyStrategyOrdering.ts index e077082c5e03..43fb1dd964fd 100644 --- a/open-sse/services/combo/applyStrategyOrdering.ts +++ b/open-sse/services/combo/applyStrategyOrdering.ts @@ -67,7 +67,7 @@ export async function applyStrategyOrdering( if (strategy === "lkgp") { try { - const { getLKGP } = await import("../../../src/lib/localDb"); + const { getLKGP } = await import("@/lib/db/settings"); const lkgpProvider = await getLKGP(combo.name, combo.id || combo.name); if (lkgpProvider) { diff --git a/open-sse/services/combo/concurrencyCaps.ts b/open-sse/services/combo/concurrencyCaps.ts index 3384352abbc8..f37cb3de307d 100644 --- a/open-sse/services/combo/concurrencyCaps.ts +++ b/open-sse/services/combo/concurrencyCaps.ts @@ -19,7 +19,7 @@ * shrinking (Quality Gate / #3501). */ -import { getCachedProviderConnectionById } from "@/lib/localDb"; +import { getCachedProviderConnectionById } from "@/lib/db/readCache"; import { effectiveMaxConcurrency } from "./comboPredicates.ts"; import type { ResolvedComboTarget } from "./types.ts"; diff --git a/open-sse/services/combo/quotaExhaustionCutoff.ts b/open-sse/services/combo/quotaExhaustionCutoff.ts index 016e78ab6337..2dc78dbe1bbe 100644 --- a/open-sse/services/combo/quotaExhaustionCutoff.ts +++ b/open-sse/services/combo/quotaExhaustionCutoff.ts @@ -19,7 +19,7 @@ import { type PreflightQuotaThresholds, type QuotaInfo, } from "../quotaPreflight.ts"; -import { getCachedProviderConnectionById } from "@/lib/localDb"; +import { getCachedProviderConnectionById } from "@/lib/db/readCache"; import { resolveResilienceSettings, type ResilienceSettings, @@ -109,8 +109,7 @@ export async function resolveQuotaExhaustionCutoffForTarget( let connection: Record | undefined; try { connection = (await getCachedProviderConnectionById(connectionId)) as - | Record - | undefined; + Record | undefined; } catch { connection = undefined; } diff --git a/open-sse/services/combo/resolveAutoStrategy.ts b/open-sse/services/combo/resolveAutoStrategy.ts index 45ef4b5c6059..84ee43c43bf2 100644 --- a/open-sse/services/combo/resolveAutoStrategy.ts +++ b/open-sse/services/combo/resolveAutoStrategy.ts @@ -245,7 +245,7 @@ export async function resolveAutoStrategyOrder( let lastKnownGoodProvider: string | undefined; try { - const { getLKGP } = await import("../../../src/lib/localDb"); + const { getLKGP } = await import("@/lib/db/settings"); const lkgp = await getLKGP(combo.name, combo.id || combo.name); if (lkgp) lastKnownGoodProvider = lkgp.provider; } catch (err) { diff --git a/open-sse/services/combo/targetSorters.ts b/open-sse/services/combo/targetSorters.ts index 7ddd3e45098f..384b776bf385 100644 --- a/open-sse/services/combo/targetSorters.ts +++ b/open-sse/services/combo/targetSorters.ts @@ -65,7 +65,7 @@ export function orderTargetsForWeightedFallback { try { - const { getPricingForModel } = await import("../../../src/lib/localDb"); + const { getPricingForModel } = await import("@/lib/db/settings"); const withCost = await Promise.all( models.map(async (modelStr) => { const parsed = parseModel(modelStr); diff --git a/open-sse/services/keyGroupAuth.ts b/open-sse/services/keyGroupAuth.ts index e6c34559a217..831af4d866b8 100644 --- a/open-sse/services/keyGroupAuth.ts +++ b/open-sse/services/keyGroupAuth.ts @@ -12,7 +12,7 @@ * - Provider-specific rules: "openai/gpt-4" */ -import { checkKeyModelAccess, getKeyGroupsForApiKey } from "@/lib/localDb"; +import { checkKeyModelAccess, getKeyGroupsForApiKey } from "@/lib/db/apiKeyGroups"; export interface KeyGroupAuthResult { /** Whether the request is authorized */ diff --git a/open-sse/services/model.ts b/open-sse/services/model.ts index 2c28b609539b..ec0080ef8387 100644 --- a/open-sse/services/model.ts +++ b/open-sse/services/model.ts @@ -332,7 +332,7 @@ function getProviderIdFromConnection(connection: unknown) { async function getActiveProviderSet() { try { - const { getCachedProviderConnections } = await import("@/lib/localDb"); + const { getCachedProviderConnections } = await import("@/lib/db/readCache"); const conns = (await getCachedProviderConnections()) as unknown[]; const providers = conns .map(getProviderIdFromConnection) @@ -345,7 +345,7 @@ async function getActiveProviderSet() { async function getActiveSyncedProvidersForModel(modelId: string) { try { - const { getActiveProvidersWithSyncedModel } = await import("@/lib/localDb"); + const { getActiveProvidersWithSyncedModel } = await import("@/lib/db/models"); const providers = await getActiveProvidersWithSyncedModel(modelId); return providers .map(resolveProviderAlias) @@ -403,7 +403,7 @@ function isTruthyEnv(value: string | undefined) { async function getPreferClaudeCodeForUnprefixedClaudeModels() { try { - const { getCachedSettings } = await import("@/lib/localDb"); + const { getCachedSettings } = await import("@/lib/db/readCache"); const settings = (await getCachedSettings()) as Record; if (typeof settings.preferClaudeCodeForUnprefixedClaudeModels === "boolean") { return settings.preferClaudeCodeForUnprefixedClaudeModels; diff --git a/open-sse/services/payloadRules.ts b/open-sse/services/payloadRules.ts index ad9a6415ad9c..651ae95f24bb 100644 --- a/open-sse/services/payloadRules.ts +++ b/open-sse/services/payloadRules.ts @@ -223,7 +223,7 @@ export function clearPayloadRulesConfigOverride() { // silently reverting to the (usually empty) file config. async function loadPayloadRulesFromSettings(): Promise { try { - const { getCachedSettings } = await import("@/lib/localDb"); + const { getCachedSettings } = await import("@/lib/db/readCache"); const settings = (await getCachedSettings()) as { payloadRules?: unknown }; const raw = settings?.payloadRules; if (raw === null || raw === undefined) return null; diff --git a/open-sse/services/rateLimitManager.ts b/open-sse/services/rateLimitManager.ts index bd793ae1caed..a8d99d0480bd 100644 --- a/open-sse/services/rateLimitManager.ts +++ b/open-sse/services/rateLimitManager.ts @@ -338,7 +338,8 @@ export async function initializeRateLimits() { applyBottleneckHeartbeatPatch(); try { - const { getCachedProviderConnections, getSettings } = await import("@/lib/localDb"); + const { getCachedProviderConnections } = await import("@/lib/db/readCache"); + const { getSettings } = await import("@/lib/db/settings"); const [connections, settings] = await Promise.all([ getCachedProviderConnections(), getSettings(), @@ -385,7 +386,7 @@ export async function applyRequestQueueSettings(nextSettings: RequestQueueSettin currentRequestQueueSettings = { ...nextSettings }; // Global policy changes invalidate snapshots from the previous generation. preservedReplacementSettings.clear(); - const { getCachedProviderConnections } = await import("@/lib/localDb"); + const { getCachedProviderConnections } = await import("@/lib/db/readCache"); const connections = await getCachedProviderConnections(); // Also discard any snapshot created while the asynchronous DB read yielded. preservedReplacementSettings.clear(); diff --git a/open-sse/services/tokenLimitCounter.ts b/open-sse/services/tokenLimitCounter.ts index cd69a9385bcf..707658238cec 100644 --- a/open-sse/services/tokenLimitCounter.ts +++ b/open-sse/services/tokenLimitCounter.ts @@ -20,7 +20,7 @@ import { getTokenLimitsForRequest, logTokenLimitReset, type TokenLimit, -} from "@/lib/localDb"; +} from "@/lib/db/tokenLimits"; interface CacheEntry { windowStart: string; @@ -295,8 +295,7 @@ export function recordTokenUsage( ORDER BY window_start DESC LIMIT 1` ) .get(limit.id, windowStart) as - | { window_start?: string; tokens_used?: number } - | undefined; + { window_start?: string; tokens_used?: number } | undefined; const prevTokens = priorRow && typeof priorRow.tokens_used === "number" ? priorRow.tokens_used : 0; if (prevTokens > 0) { diff --git a/open-sse/services/volumeDetector.ts b/open-sse/services/volumeDetector.ts index 56a1e7845016..9bb16d1335aa 100644 --- a/open-sse/services/volumeDetector.ts +++ b/open-sse/services/volumeDetector.ts @@ -155,7 +155,7 @@ export async function recommendStrategyOverride( // Check if adaptive routing is enabled globally try { - const { getSettings } = await import("@/lib/localDb"); + const { getSettings } = await import("@/lib/db/settings"); const settings = await getSettings(); if (!settings.adaptiveVolumeRouting) { return noOverride; diff --git a/open-sse/utils/networkProxy.ts b/open-sse/utils/networkProxy.ts index 82a95c5e0a50..1499a918e645 100644 --- a/open-sse/utils/networkProxy.ts +++ b/open-sse/utils/networkProxy.ts @@ -20,7 +20,7 @@ async function getConfig() { if (_cachedConfig && now < _cacheExpiry) return _cachedConfig; try { - const { getProxyConfig } = await import("../../src/lib/localDb"); + const { getProxyConfig } = await import("@/lib/db/settings"); _cachedConfig = await getProxyConfig(); _cacheExpiry = now + 30_000; // Cache for 30s return _cachedConfig; diff --git a/open-sse/utils/proxyFallback.ts b/open-sse/utils/proxyFallback.ts index 6d7590034cbb..5590dbd16a1f 100644 --- a/open-sse/utils/proxyFallback.ts +++ b/open-sse/utils/proxyFallback.ts @@ -10,7 +10,8 @@ import { fetch as undiciFetch } from "undici"; import { createProxyDispatcher, normalizeProxyUrl } from "./proxyDispatcher.ts"; -import { resolveProxyForScopeFromRegistry, listProxies, listOneproxyProxies } from "@/lib/localDb"; +import { resolveProxyForScopeFromRegistry, listProxies } from "@/lib/db/proxies"; +import { listOneproxyProxies } from "@/lib/db/oneproxy"; import { isFeatureFlagEnabled } from "@/shared/utils/featureFlags"; // --------------------------------------------------------------------------- diff --git a/src/domain/quotaCache.ts b/src/domain/quotaCache.ts index 3d5cf1a2e58a..377512ee1306 100644 --- a/src/domain/quotaCache.ts +++ b/src/domain/quotaCache.ts @@ -17,7 +17,8 @@ */ import { getUsageForProvider } from "@omniroute/open-sse/services/usage.ts"; -import { getCachedProviderConnectionById, resolveProxyForConnection } from "@/lib/localDb"; +import { getCachedProviderConnectionById } from "@/lib/db/readCache"; +import { resolveProxyForConnection } from "@/lib/db/settings"; import { runWithProxyContext } from "@omniroute/open-sse/utils/proxyFetch.ts"; import { safePercentage } from "@/shared/utils/formatting"; import { diff --git a/src/models/index.ts b/src/models/index.ts index 90b3c78b11e0..39e8828c1215 100755 --- a/src/models/index.ts +++ b/src/models/index.ts @@ -1,4 +1,4 @@ -// Database Models - Export all from localDb +// Database Models - Export all from the owning db modules export { getProviderConnections, getProviderConnectionsCount, @@ -15,17 +15,16 @@ export { updateProviderNode, deleteProviderNode, deleteProviderConnectionsByProvider, +} from "@/lib/db/providers"; +export { getModelAliases, setModelAlias, deleteModelAlias, deleteModelAliasesForProvider, getMitmAlias, setMitmAliasAll, - getApiKeys, - createApiKey, - deleteApiKey, - validateApiKey, - isCloudEnabled, - resolveProxyForProvider, getHiddenModelsByProvider, -} from "@/lib/localDb"; +} from "@/lib/db/models"; +export { getApiKeys, createApiKey, deleteApiKey, validateApiKey } from "@/lib/db/apiKeys"; +export { isCloudEnabled } from "@/lib/db/settings"; +export { resolveProxyForProvider } from "@/lib/db/proxies"; diff --git a/src/shared/services/apiKeyResolver.ts b/src/shared/services/apiKeyResolver.ts index e79bc9bd994c..003be6443fcd 100644 --- a/src/shared/services/apiKeyResolver.ts +++ b/src/shared/services/apiKeyResolver.ts @@ -1,4 +1,4 @@ -import { getApiKeyById, createApiKey } from "@/lib/localDb"; +import { getApiKeyById, createApiKey } from "@/lib/db/apiKeys"; import { getConsistentMachineId } from "@/shared/utils/machineId"; export async function resolveApiKey( diff --git a/src/shared/services/cloudSyncScheduler.ts b/src/shared/services/cloudSyncScheduler.ts index d56e5cd23741..ec2605d0f93b 100644 --- a/src/shared/services/cloudSyncScheduler.ts +++ b/src/shared/services/cloudSyncScheduler.ts @@ -1,5 +1,5 @@ import { getConsistentMachineId } from "@/shared/utils/machineId"; -import { isCloudEnabled } from "@/lib/localDb"; +import { isCloudEnabled } from "@/lib/db/settings"; import { getRuntimePorts } from "@/lib/runtime/ports"; const { dashboardPort } = getRuntimePorts(); diff --git a/src/shared/services/initializeCloudSync.ts b/src/shared/services/initializeCloudSync.ts index 78fdc7b4ad2a..631b4391ae59 100644 --- a/src/shared/services/initializeCloudSync.ts +++ b/src/shared/services/initializeCloudSync.ts @@ -1,5 +1,6 @@ import { getCloudSyncScheduler } from "@/shared/services/cloudSyncScheduler"; -import { isCloudEnabled, cleanupProviderConnections } from "@/lib/localDb"; +import { isCloudEnabled } from "@/lib/db/settings"; +import { cleanupProviderConnections } from "@/lib/db/providers"; /** * Initialize cloud sync scheduler diff --git a/src/shared/services/modelSyncScheduler.ts b/src/shared/services/modelSyncScheduler.ts index f45bc194f444..c63a5e50ac11 100644 --- a/src/shared/services/modelSyncScheduler.ts +++ b/src/shared/services/modelSyncScheduler.ts @@ -10,7 +10,7 @@ import { randomUUID } from "node:crypto"; import { Agent, buildConnector, fetch as undiciFetch, type Dispatcher } from "undici"; -import { getSettings, updateSettings } from "@/lib/localDb"; +import { getSettings, updateSettings } from "@/lib/db/settings"; import { isConnectionUnavailableToAuxiliaryActivity } from "@/lib/exclusiveLeaseIsolation"; import { getRuntimePorts } from "@/lib/runtime/ports"; @@ -151,7 +151,7 @@ async function getAutoSyncConnections(): Promise< Array<{ id: string; provider: string; name?: string }> > { try { - const { getProviderConnections } = await import("@/lib/localDb"); + const { getProviderConnections } = await import("@/lib/db/providers"); const connections = await getProviderConnections(); const autoSyncConnections: Array<{ id: string; provider: string; name?: string }> = []; for (const conn of connections) { diff --git a/src/shared/utils/apiAuth.ts b/src/shared/utils/apiAuth.ts index 8a40c493deae..d53c72ea9e63 100644 --- a/src/shared/utils/apiAuth.ts +++ b/src/shared/utils/apiAuth.ts @@ -9,7 +9,7 @@ import { jwtVerify } from "jose"; import { cookies } from "next/headers"; -import { getSettings } from "@/lib/localDb"; +import { getSettings } from "@/lib/db/settings"; import { isPublicApiRoute } from "@/shared/constants/publicApiRoutes"; import { extractApiKey } from "@/sse/services/auth"; diff --git a/src/shared/utils/apiKeyPolicy.ts b/src/shared/utils/apiKeyPolicy.ts index ebd23c9eadf0..49cb628bb08e 100644 --- a/src/shared/utils/apiKeyPolicy.ts +++ b/src/shared/utils/apiKeyPolicy.ts @@ -9,12 +9,8 @@ */ import { extractApiKey } from "@/sse/services/auth"; -import { - getApiKeyMetadata, - getComboByName, - isModelAllowedForKey, - getApiKeyById, -} from "@/lib/localDb"; +import { getApiKeyMetadata, isModelAllowedForKey, getApiKeyById } from "@/lib/db/apiKeys"; +import { getComboByName } from "@/lib/db/combos"; import { isDashboardSessionAuthenticated } from "./apiAuth"; import { resolveComboForModel } from "@/lib/db/modelComboMappings"; import { checkBudget } from "@/domain/costRules"; diff --git a/src/sse/handlers/autoRouting.ts b/src/sse/handlers/autoRouting.ts index 8920c71d334f..d76b1e2340ef 100644 --- a/src/sse/handlers/autoRouting.ts +++ b/src/sse/handlers/autoRouting.ts @@ -14,7 +14,7 @@ import { isValidModelFamily, type ModelFamily, } from "@omniroute/open-sse/services/autoCombo/modelFamily.ts"; -import { getCachedSettings } from "@/lib/localDb"; +import { getCachedSettings } from "@/lib/db/readCache"; import * as log from "../utils/logger"; export type AutoRoutingState = { diff --git a/src/sse/handlers/chatHelpers.ts b/src/sse/handlers/chatHelpers.ts index f1db55f6e82c..958d93457332 100644 --- a/src/sse/handlers/chatHelpers.ts +++ b/src/sse/handlers/chatHelpers.ts @@ -1,4 +1,8 @@ -import { getModelInfo, getComboForModel, getModelInfoOrRetirementResponse } from "../services/model"; +import { + getModelInfo, + getComboForModel, + getModelInfoOrRetirementResponse, +} from "../services/model"; import { clearAccountError, markAccountUnavailable } from "../services/auth"; import { connectionHasExtraKeys } from "@omniroute/open-sse/services/apiKeyRotator.ts"; import { createBuiltinAutoCombo } from "@omniroute/open-sse/services/autoCombo/builtinCatalog.ts"; @@ -26,7 +30,7 @@ import { isTlsFingerprintActive, type AppliedProxySink, } from "@omniroute/open-sse/utils/proxyFetch.ts"; -import { resolveProxyForConnection } from "@/lib/localDb"; +import { resolveProxyForConnection } from "@/lib/db/settings"; import { hasBlockingProxyAssignment } from "@/lib/db/proxies"; import { CircuitBreakerOpenError, diff --git a/src/sse/services/model.ts b/src/sse/services/model.ts index b28e3e5d2f7d..a1fe37f238bb 100644 --- a/src/sse/services/model.ts +++ b/src/sse/services/model.ts @@ -1,15 +1,9 @@ // Re-export from open-sse with localDb integration -import { - getModelAliases, - getComboByName, - getComboById, - getComboByNameInsensitive, - getCachedProviderNodes, - getCustomModels, -} from "@/lib/localDb"; +import { getModelAliases, getCustomModels } from "@/lib/db/models"; +import { getComboByName, getComboById, getComboByNameInsensitive } from "@/lib/db/combos"; +import { getCachedProviderNodes, getCachedSettings } from "@/lib/db/readCache"; import { getSyncedAutoAliases } from "@/lib/providerModels/syncedAutoAliases.ts"; -import { getCachedSettings } from "@/lib/localDb"; import { getActiveSyncedCatalog } from "@/lib/db/models/activeSyncedCatalog"; import { getModelCompatOverrides } from "@/lib/db/models/compat"; import { getNoAuthHydrationProviderIds } from "./noAuthProviderSiblings"; @@ -660,7 +654,7 @@ export async function getComboForModel(modelStr) { // 2. NEW — check model-combo mappings table (pattern match) try { - const { resolveComboForModel } = await import("@/lib/localDb"); + const { resolveComboForModel } = await import("@/lib/db/modelComboMappings"); const mapped = await resolveComboForModel(baseModelStr || modelStr); if (mapped && (mapped as any).models?.length > 0) { return mapped; diff --git a/src/sse/services/noAuthProviderSettings.ts b/src/sse/services/noAuthProviderSettings.ts index dee365c5f1e2..f700e2dc6f65 100644 --- a/src/sse/services/noAuthProviderSettings.ts +++ b/src/sse/services/noAuthProviderSettings.ts @@ -1,4 +1,4 @@ -import { getSettings } from "@/lib/localDb"; +import { getSettings } from "@/lib/db/settings"; import { isProviderBlockedByIdOrAlias } from "@/shared/utils/noAuthProviders"; import * as log from "../utils/logger"; diff --git a/src/sse/services/tokenRefresh.ts b/src/sse/services/tokenRefresh.ts index 4955cb8247f0..f7e65b11733e 100755 --- a/src/sse/services/tokenRefresh.ts +++ b/src/sse/services/tokenRefresh.ts @@ -1,10 +1,8 @@ // Re-export from open-sse with local logger import * as log from "../utils/logger"; -import { - updateProviderConnection, - resolveProxyForConnection, - resolveProxyForProvider, -} from "@/lib/localDb"; +import { updateProviderConnection } from "@/lib/db/providers"; +import { resolveProxyForConnection } from "@/lib/db/settings"; +import { resolveProxyForProvider } from "@/lib/db/proxies"; import { TOKEN_EXPIRY_BUFFER_MS as BUFFER_MS, getRefreshLeadMs as _getRefreshLeadMs, From f02e6d88737f0b5a3af9686a9e54bb31f905b304 Mon Sep 17 00:00:00 2001 From: WebPerson Date: Sat, 29 Aug 2026 14:14:15 -0500 Subject: [PATCH 04/22] fix(barrel): delete the src/lib/localDb barrel now every consumer uses direct db imports (#59) Final phase of the #11795 migration. With all src/app, src/lib, open-sse, src/shared, src/sse, src/models, and src/domain consumers migrated in Phases 2-4, migrate the last 61 test files off the barrel (namespace and destructured dynamic imports of @/lib/localDb, including .ts-suffixed relative paths) and delete src/lib/localDb.ts entirely. Mechanical only - no behavior change: - Namespace test imports (const localDb = await import(...)) become per-module imports resolved via the TS-compiler export map, with a merged localDb facade object preserved so existing call sites (localDb.x(...)) stay valid - Destructured dynamic imports split into per-module imports; unused barrel bootstrap import removed (traffic-inspector-hosts) - AGENTS.md (root + src/lib/db) and inline doc comments updated to reflect that the barrel no longer exists - eslint-import-boundaries test (which lints inline fixture strings against the no-restricted-imports rule) intentionally keeps its localDb strings - Prune the now-satisfied no-restricted-imports suppressions revealed by eslint --prune-suppressions Verified: typecheck:core green, eslint on all changed files green (0 errors), check:cycles green, 68 migrated test files parse clean, focused unit + quota integration tests pass (166/167; the sole failure in cli-tools-crush is pre-existing, identical on clean HEAD). --- AGENTS.md | 8 +- config/quality/eslint-suppressions.json | 18 - src/lib/copilot/systemPrompt.ts | 2 +- src/lib/db/AGENTS.md | 10 +- src/lib/localDb.ts | 850 ------------------ src/lib/tokenHealthCheckCursor.ts | 2 +- tests/integration/all-statuses-route.test.ts | 3 +- tests/integration/api-keys.test.ts | 3 +- tests/integration/api-routes-critical.test.ts | 5 +- .../cli-settings-codewhale.test.ts | 3 +- .../cli-settings-deepseek-tui.test.ts | 3 +- tests/integration/cli-settings-forge.test.ts | 3 +- .../cli-settings-grok-build.test.ts | 3 +- tests/integration/cli-settings-jcode.test.ts | 3 +- tests/integration/cli-settings-letta.test.ts | 3 +- tests/integration/cli-settings-omp.test.ts | 3 +- tests/integration/cli-settings-pi.test.ts | 3 +- tests/integration/cli-settings-smelt.test.ts | 3 +- .../combo-live/cost-and-fusion.live.test.ts | 576 ++++++------ tests/integration/files-api.test.ts | 2 +- .../memory-embedding-providers.test.ts | 3 +- .../integration/memory-engine-status.test.ts | 3 +- tests/integration/memory-reindex.test.ts | 3 +- .../memory-retrieve-preview.test.ts | 3 +- tests/integration/memory-route-put.test.ts | 3 +- tests/integration/memory-summarize.test.ts | 3 +- .../provider-journey.contract.test.ts | 5 +- tests/integration/qdrant-routes.test.ts | 4 +- tests/integration/quota-plans-crud.test.ts | 3 +- ...ota-pool-usage-provider-resolution.test.ts | 4 +- tests/integration/quota-pools-crud.test.ts | 3 +- tests/integration/quota-pools-usage.test.ts | 5 +- tests/integration/quota-preview.test.ts | 5 +- .../integration/quota-store-settings.test.ts | 3 +- .../traffic-inspector-hosts.test.ts | 1 - tests/unit/acp-agents-route.test.ts | 3 +- tests/unit/api-auth.test.ts | 3 +- tests/unit/api-key-usage-limits.test.ts | 3 +- .../unit/api-keys-create-no-hang-6570.test.ts | 3 +- ...pi-models-v1-models-mismatch-10615.test.ts | 3 +- .../auto-combo-hidden-models-4558.test.ts | 2 +- tests/unit/batch-deletion.test.ts | 11 +- tests/unit/batch-file-download.test.ts | 3 +- tests/unit/batch-processor.test.ts | 20 +- tests/unit/batch_api.test.ts | 16 +- tests/unit/batch_results.test.ts | 14 +- tests/unit/cli-tools-crush.test.ts | 3 +- tests/unit/cloud-write-auth.test.ts | 14 +- tests/unit/evals-route.test.ts | 4 +- tests/unit/file-deletion.test.ts | 4 +- tests/unit/file-expiration-policy.test.ts | 2 +- tests/unit/fixes-p1.test.ts | 3 +- ...hidden-models-leak-v1-models-11300.test.ts | 2 +- tests/unit/live-ws-public-url.test.ts | 3 +- tests/unit/managed-available-models.test.ts | 3 +- tests/unit/managed-model-import.test.ts | 3 +- tests/unit/model-alias-route.test.ts | 3 +- ...el-aliases-settings-route-selfheal.test.ts | 3 +- tests/unit/model-sync-route.test.ts | 4 +- tests/unit/oidc-callback.test.ts | 3 +- tests/unit/oidc-login-state.test.ts | 3 +- ...sist-429-cooldown-account-fallback.test.ts | 3 +- tests/unit/provider-window-costs.test.ts | 3 +- tests/unit/proxy-egress-route-summary.test.ts | 3 +- ...custom-models-preserve-hidden-5086.test.ts | 2 +- tests/unit/route-edge-coverage.test.ts | 5 +- ...ialty-model-hidden-openrouter-9293.test.ts | 2 +- tests/unit/sync-routes.test.ts | 3 +- tests/unit/synced-model-delete-resync.test.ts | 4 +- .../synced-model-hide-persist-3782.test.ts | 2 +- .../usage-account-analytics-route.test.ts | 3 +- tests/unit/usage-analytics-route.test.ts | 3 +- tests/unit/usage-analytics.test.ts | 3 +- tests/unit/v1-ws-route.test.ts | 3 +- 74 files changed, 482 insertions(+), 1252 deletions(-) delete mode 100755 src/lib/localDb.ts diff --git a/AGENTS.md b/AGENTS.md index 627064d5b14b..d468ae5848be 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -289,8 +289,7 @@ When creating _any_ validation tests or one-off logic scripts, default to `scrip ### Database - **Always** go through `src/lib/db/` domain modules — **never** write raw SQL in routes or handlers -- **Never** add logic to `src/lib/localDb.ts` (re-export layer only) -- **Never** barrel-import from `localDb.ts` — import specific `db/` modules instead +- **Never** barrel-import from `localDb.ts` — import specific `src/lib/db/*` modules - DB singleton: `getDbInstance()` from `src/lib/db/core.ts` (WAL journaling) - Migrations: `src/lib/db/migrations/` — versioned SQL files, idempotent, run in transactions @@ -355,8 +354,7 @@ Documentation must describe verified behavior, not plausible behavior. 1. Create `src/lib/db/yourModule.ts` — import `getDbInstance` from `./core.ts` 2. Export CRUD functions for your domain table(s) 3. Add migration in `src/lib/db/migrations/` if new tables needed -4. Re-export from `src/lib/localDb.ts` (add to the re-export list only) -5. Write tests +4. Write tests ### Adding a New MCP Tool @@ -668,7 +666,7 @@ the stale-enforcement added in Fase 6A.3. ## Hard Rules 1. Never commit secrets or credentials -2. Never add logic to `localDb.ts` +2. Never barrel-import from `localDb.ts` — import specific `src/lib/db/*` modules 3. Never use `eval()` / `new Function()` / implied eval 4. Never commit directly to `main` 5. Never write raw SQL in routes — use `src/lib/db/` modules diff --git a/config/quality/eslint-suppressions.json b/config/quality/eslint-suppressions.json index f865a7ba3bdb..4c8a059b954b 100644 --- a/config/quality/eslint-suppressions.json +++ b/config/quality/eslint-suppressions.json @@ -2436,11 +2436,6 @@ "count": 2 } }, - "tests/integration/files-api.test.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "tests/integration/fingerprint-expansion.test.ts": { "@typescript-eslint/no-unused-vars": { "count": 1 @@ -2588,11 +2583,6 @@ "count": 1 } }, - "tests/integration/traffic-inspector-hosts.test.ts": { - "@typescript-eslint/no-unused-vars": { - "count": 1 - } - }, "tests/integration/upstream-cli-smoke.int.test.ts": { "@typescript-eslint/no-unused-vars": { "count": 1 @@ -2870,11 +2860,6 @@ "count": 1 } }, - "tests/unit/batch-deletion.test.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "tests/unit/batch-page-static.test.ts": { "@typescript-eslint/no-unused-vars": { "count": 1 @@ -4101,9 +4086,6 @@ }, "@typescript-eslint/no-unused-vars": { "count": 1 - }, - "no-restricted-imports": { - "count": 1 } }, "tests/unit/fix-tool-adjacency.test.ts": { diff --git a/src/lib/copilot/systemPrompt.ts b/src/lib/copilot/systemPrompt.ts index 442e77939432..d8626bab7807 100644 --- a/src/lib/copilot/systemPrompt.ts +++ b/src/lib/copilot/systemPrompt.ts @@ -64,7 +64,7 @@ Client → API Route (/v1/chat/completions) - \`src/lib/db/\`: 45+ domain-specific modules - \`core.ts\`: Singleton better-sqlite3 with WAL journaling - \`migrationRunner.ts\`: Versioned SQL migrations (55+ files) -- \`localDb.ts\`: Re-export layer only — no logic +- \`src/lib/db/*\`: domain modules only — no barrel layer ### Key Modules - **open-sse/**: Core streaming engine (handlers, executors, translator) diff --git a/src/lib/db/AGENTS.md b/src/lib/db/AGENTS.md index 4b5a3eb545e7..c133d0b90b5e 100644 --- a/src/lib/db/AGENTS.md +++ b/src/lib/db/AGENTS.md @@ -11,7 +11,7 @@ Live count: `ls src/lib/db/*.ts | wc -l` (currently 117). Migrations: `ls src/li - **`core.ts`** — `getDbInstance()` returns singleton `better-sqlite3` with WAL journaling. Exports `rowToCamel()` (snake_case → camelCase), `encryptConnectionFields()` for provider credentials at rest. `SCHEMA_SQL` defines **17 base tables** (verify: `grep -c "CREATE TABLE" src/lib/db/core.ts` minus 1 for `_omniroute_migrations`). - **`migrationRunner.ts`** — Applies versioned SQL files from `db/migrations/` inside transactions. Tracks applied migrations in `_omniroute_migrations`. Each migration is idempotent. - **`db/migrations/`** — 148 SQL files (`001_initial_schema.sql` → `153_radar_local_model_state.sql`; numbering has intentional gaps). Each runs in a transaction, never fails partially. -- **`localDb.ts`** — Re-export layer only. Never add logic here. +- The old `localDb.ts` barrel has been removed — consumers must import from the owning named module below. ## Key Domain Modules @@ -56,15 +56,13 @@ Full list: `ls src/lib/db/*.ts | wc -l` (115 files). Drift detection: `npm run c ## Adding a New Domain Module 1. Create `src/lib/db/[module].ts` with CRUD functions -2. Export from `src/lib/localDb.ts` (add re-export) -3. If new tables: create migration in `db/migrations/NNN_[description].sql` -4. Migration runs automatically at startup via `migrationRunner.ts` -5. Add unit tests in `tests/unit/db/` +2. If new tables: create migration in `db/migrations/NNN_[description].sql` +3. Migration runs automatically at startup via `migrationRunner.ts` +4. Add unit tests in `tests/unit/db/` ## Anti-Patterns - Raw SQL in routes — always use domain module functions - Direct `prepare()` statements outside `db/` — breaks modularity -- Adding logic to `localDb.ts` — re-export layer only - Barrel-importing from `localDb.ts` — import specific modules instead - Skipping migrations for schema changes — all changes go through `db/migrations/` diff --git a/src/lib/localDb.ts b/src/lib/localDb.ts deleted file mode 100755 index 761e2db4ebc7..000000000000 --- a/src/lib/localDb.ts +++ /dev/null @@ -1,850 +0,0 @@ -/** - * localDb.js — Re-export layer for backward compatibility. - * - * All 27+ consumer files import from "@/lib/localDb". - * This thin layer re-exports everything from the domain-specific DB modules, - * so zero consumer changes are needed. - */ - -export { - // Provider Connections - getProviderConnections, - getProviderConnectionsCount, - getProviderConnectionById, - createProviderConnection, - updateProviderConnection, - resetConnectionBackoff, - clearConnectionErrorIfUnchanged, - touchConnectionLastUsed, - deleteProviderConnection, - deleteProviderConnections, - deleteProviderConnectionsByProvider, - reorderProviderConnections, - cleanupProviderConnections, - getProviderNodes, - getProviderNodesCount, - getProviderNodeById, - resolveProviderNodeForConnection, - createProviderNode, - updateProviderNode, - deleteProviderNode, - // T05: Rate-limit DB persistence (survives token refresh) - setConnectionRateLimitUntil, - isConnectionRateLimited, - getRateLimitedConnections, - clearStaleCrashCooldowns, - // T13: Stale quota display fix (zero out usage after window resets) - getEffectiveQuotaUsage, - formatResetCountdown, -} from "./db/providers"; - -export { - // Model Aliases - getModelAliases, - setModelAlias, - deleteModelAlias, - deleteModelAliasesForProvider, - - // MITM Alias - getMitmAlias, - setMitmAliasAll, - - // Custom Models - getCustomModels, - getAllCustomModels, - addCustomModel, - replaceCustomModels, - removeCustomModel, - updateCustomModel, - getModelCompatOverrides, - mergeModelCompatOverride, - removeModelCompatOverride, - getModelNormalizeToolCallId, - getModelPreserveOpenAIDeveloperRole, - getModelUpstreamExtraHeaders, - getModelIsHidden, - setModelIsHidden, - getHiddenModelsByProvider, - // Synced Available Models - getSyncedAvailableModels, - getAllSyncedAvailableModels, - getActiveProvidersWithSyncedModel, - replaceSyncedAvailableModelsForConnection, - deleteSyncedAvailableModelsForConnection, - deleteSyncedAvailableModelsForProvider, - removeSyncedAvailableModel, -} from "./db/models"; - -export type { ModelCompatPerProtocol, ModelCompatPatch, SyncedAvailableModel } from "./db/models"; - -export { - // Combos - getCombos, - getCombosCount, - getComboById, - getComboByName, - getComboByNameInsensitive, - createCombo, - updateCombo, - reorderCombos, - deleteCombo, -} from "./db/combos"; -export * from "./db/ccrBlocks"; -export * from "./db/compressionCacheStats"; -export * from "./db/compressionCombos"; -export * from "./db/compressionContextBudget"; -export * from "./db/compressionRunTelemetry"; -export * from "./db/jobRegistryDb"; -export * from "./db/modelContextOverrides"; -export * from "./db/responsesContinuationStore"; - -export { - getApiKeys, - getApiKeysCount, - getApiKeyById, - createApiKey, - deleteApiKey, - validateApiKey, - getApiKeyMetadata, - updateApiKeyPermissions, - regenerateApiKey, - isModelAllowedForKey, - pickApiKeyForInternalUse, - clearApiKeyCaches, - resetApiKeyState, - ApiKeyPolicyInvariantError, -} from "./db/apiKeys"; - -export { - // Evals - saveEvalRun, - listEvalRuns, - getEvalScorecard, - listCustomEvalSuites, - getCustomEvalSuite, - saveCustomEvalSuite, - deleteCustomEvalSuite, - serializeEvalTargetKey, -} from "./db/evals"; - -export type { - EvalCaseRecord, - EvalSuiteRecord, - EvalTargetType, - EvalTargetDescriptor, - EvalRunSummary, - PersistedEvalRun, -} from "./db/evals"; - -export { - // Settings - getSettings, - getSettingsRevision, - updateSettings, - isCloudEnabled, - - // LKGP (Last Known Good Provider) (#919) - getLKGP, - setLKGP, - clearLKGP, - - // Pricing - getPricing, - getPricingWithSources, - getPricingForModel, - updatePricing, - resetPricing, - resetAllPricing, - - // Proxy Config - getProxyConfig, - getProxyForLevel, - setProxyForLevel, - deleteProxyForLevel, - resolveProxyForConnection, - setProxyConfig, -} from "./db/settings"; - -export type { PricingSource, PricingSourceMap } from "./db/settings"; - -export { - getDatabaseSettings, - getUserDatabaseSettings, - updateDatabaseSettings, -} from "./db/databaseSettings"; - -export type { UserDatabaseSettings } from "./db/databaseSettings"; - -export * from "./db/exclusiveConnectionLeases"; - -export { - // Proxy Registry - listProxies, - getProxyById, - createProxy, - createProxyAndAssign, - updateProxy, - updateProxyAndAssign, - upsertProxy, - deleteProxyById, - getProxyAssignments, - getProxyWhereUsed, - assignProxyToScope, - addProxyToScopePool, - removeProxyFromScopePool, - getScopeProxyPool, - setScopeRotationStrategy, - getScopeRotationStrategy, - resolveProxyForConnectionFromRegistry, - resolveProxyForProvider, - resolveProxyForScopeFromRegistry, - migrateLegacyProxyConfigToRegistry, - getProxyHealthStats, - bulkAssignProxyToScope, -} from "./db/proxies"; - -export { - // Pricing Sync - getSyncedPricing, - saveSyncedPricing, - clearSyncedPricing, - syncPricingFromSources, - getSyncStatus, - initPricingSync, - startPeriodicSync, - stopPeriodicSync, -} from "./pricingSync"; - -export { - // Backup Management - backupDbFile, - cleanupDbBackups, - getDbBackupMaxFiles, - setDbBackupMaxFiles, - getDbBackupRetentionDays, - setDbBackupRetentionDays, - listDbBackups, - restoreDbBackup, - // Export-All / Import helpers (#3500 slice 5) - exportAllSummaryRows, - getTableNamesFromAdapter, - countImportedRows, -} from "./db/backup"; - -export type { ExportAllRows } from "./db/backup"; - -export { - // Skills DB operations (#3500 slice 5) - updateSkill, -} from "./db/skills"; - -export type { SkillPatch } from "./db/skills"; - -export { - // Read Cache (cached wrappers for hot-read paths) - getCachedSettings, - getCachedPricing, - getCachedProviderConnections, - getCachedRawProviderConnections, - getCachedProviderConnectionById, - getCachedProviderNodes, - getCachedLKGP, - setCachedLKGP, - invalidateDbCache, - getCombosCacheVersion, -} from "./db/readCache"; - -export { - // Registered Keys Provisioning (#464) - issueRegisteredKey, - getRegisteredKey, - listRegisteredKeys, - revokeRegisteredKey, - validateRegisteredKey, - incrementRegisteredKeyUsage, - checkQuota, - setProviderKeyLimit, - setAccountKeyLimit, - getProviderKeyLimit, - getAccountKeyLimit, -} from "./db/registeredKeys"; - -export type { - RegisteredKey, - RegisteredKeyWithSecret, - ProviderKeyLimit, - AccountKeyLimit, - QuotaCheckResult, - IssueKeyParams, -} from "./db/registeredKeys"; - -export { - // Model-Combo Mappings (#563) - getModelComboMappings, - getModelComboMappingById, - createModelComboMapping, - updateModelComboMapping, - deleteModelComboMapping, - resolveComboForModel, -} from "./db/modelComboMappings"; - -export { - // Files - createFile, - getFile, - getFileContent, - listFiles, - countFiles, - formatFileResponse, - deleteFile, -} from "./db/files"; - -export { - // Batches - createBatch, - getBatch, - updateBatch, - listBatches, - countBatches, - getPendingBatches, - getTerminalBatches, - ensureBatchItemCheckpoints, - countBatchItemCheckpoints, - listBatchItemCheckpoints, - markBatchItemProcessing, - markBatchItemResult, - markBatchItemError, - deleteBatch, - deleteCompletedBatches, -} from "./db/batches"; - -export type { FileRecord } from "./db/files"; -export type { BatchItemCheckpoint, BatchRecord } from "./db/batches"; - -export type { ModelComboMapping } from "./db/modelComboMappings"; -export * from "./db/reasoningRoutingRules"; -export * from "./db/autoCandidateOverrides"; -export { - // Webhooks - getWebhooks, - getWebhook, - getEnabledWebhooks, - createWebhook, - updateWebhook as updateWebhookRecord, - deleteWebhook, - recordWebhookDelivery, - disableWebhooksWithHighFailures, -} from "./db/webhooks"; - -export type { Webhook, WebhookKind } from "./db/webhooks"; - -export { insertDelivery, getDeliveries } from "./db/webhookDeliveries"; - -export { - upsertDiscoveryResult, - getDiscoveryResults, - getDiscoveryResultById, - markVerified, - deleteDiscoveryResult, -} from "./db/discoveryResults"; - -export type { - DiscoveryResult, - DiscoveryMethod, - DiscoveryAuthType, - DiscoveryRiskLevel, - DiscoveryStatus, -} from "./db/discoveryResults"; -export type { WebhookDelivery } from "./db/webhookDeliveries"; - -export { - saveQuotaSnapshot, - getQuotaSnapshots, - getAggregatedSnapshots, - cleanupOldSnapshots, -} from "./db/quotaSnapshots"; - -export * from "./db/sessionAccountAffinity"; -export * from "./db/quotaResetEvents"; - -export type { QuotaSnapshotRow, ProviderUtilizationPoint } from "@/shared/types/utilization"; - -export { - getVersionManagerStatus, - getVersionManagerTool, - upsertVersionManagerTool, - updateVersionManagerTool, - deleteVersionManagerTool, - updateToolHealth, - updateToolVersion, - setToolStatus, - getServiceRow, - updateServiceField, -} from "./db/versionManager"; - -export { - listSyncTokens, - getSyncTokenById, - getSyncTokenByHash, - createSyncTokenRecord, - revokeSyncToken, - touchSyncTokenLastUsed, -} from "./db/syncTokens"; - -export { - getUpstreamProxyConfigs, - getUpstreamProxyConfig, - upsertUpstreamProxyConfig, - updateUpstreamProxyConfig, - deleteUpstreamProxyConfig, - getProvidersByMode, - getFallbackChainForProvider, - validateProxyUrl, -} from "./db/upstreamProxy"; - -export { - getProviderLimitsCache, - getAllProviderLimitsCache, - setProviderLimitsCache, - setProviderLimitsCacheBatch, - deleteProviderLimitsCache, -} from "./db/providerLimits"; - -export type { ProviderLimitsCacheEntry } from "./db/providerLimits"; - -export { - getPersistedCreditBalance, - getAllPersistedCreditBalances, - persistCreditBalance, -} from "./db/creditBalance"; - -export { - insertCompressionAnalyticsRow, - getCompressionAnalyticsSummary, -} from "./db/compressionAnalytics"; - -export type { - CompressionAnalyticsRow, - CompressionAnalyticsSummary, -} from "./db/compressionAnalytics"; - -export { - // Reasoning Replay Cache (#1628) - setReasoningCache, - getReasoningCache, - deleteReasoningCache, - clearAllReasoningCache, -} from "./db/reasoningCache"; - -export type { ReasoningCacheEntry, ReasoningCacheStats } from "./db/reasoningCache"; - -export { - // 1proxy Integration (#1788) - listOneproxyProxies, - getOneproxyStats, - upsertOneproxyProxy, - getOneproxyProxyById, - deleteOneproxyProxy, - clearAllOneproxyProxies, - getOneproxyProxyForRotation, - markOneproxyProxyFailed, -} from "./db/oneproxy"; - -export type { OneproxyProxyRecord, OneproxyStats } from "./db/oneproxy"; - -export { - getSessionAccountAffinity, - upsertSessionAccountAffinity, - touchSessionAccountAffinity, - deleteSessionAccountAffinity, - evictSessionAccountAffinityForConnection, - cleanupStaleSessionAccountAffinities, - startSessionAccountAffinityCleanup, - stopSessionAccountAffinityCleanupForTests, -} from "./db/sessionAccountAffinity"; - -export { - // Gamification & Leaderboard - updateScore, - getRank, - getTopN, - addXp, - getXp, - updateLevel, - unlockBadge, - hasBadge, - getBadges, - getBadgeDefinitions, - transferTokens, - getBalance, - getHistory, - createInviteToken, - getInviteByCode, - redeemInvite, - revokeInvite, - connectServer, - disconnectServer, - listServers, - getConnectedServerByKeyHash, -} from "./db/gamification"; - -export type { - LeaderboardRow, - UserLevelRow, - BadgeDefinition, - UserBadge, - XpAuditLogEntry, - TokenLedgerEntry, - InviteToken, - CommunityServer, -} from "./db/gamification"; - -export * from "./db/featureFlags"; - -export { - upsertHandoff, - getHandoff, - deleteHandoff, - cleanupExpiredHandoffs, - hasActiveHandoff, - recordSessionModelUsage, - getLastSessionModel, -} from "./db/contextHandoffs"; - -export type { HandoffPayload } from "./db/contextHandoffs"; - -export { - getAllMiddlewareHooks, - getEnabledMiddlewareHooks, - getComboMiddlewareHooks, - getMiddlewareHook, - createMiddlewareHook, - updateMiddlewareHook, - deleteMiddlewareHook, - recordHookExecution, - insertHookLog, - getHookLogs, - cleanupHookLogs, -} from "./db/middleware"; - -export { - getAllKeyGroups, - getKeyGroup, - getKeyGroupWithPermissions, - createKeyGroup, - updateKeyGroup, - deleteKeyGroup, - getGroupPermissions, - addGroupPermission, - removeGroupPermission, - clearGroupPermissions, - getGroupMembers, - getKeyGroupsForApiKey, - addKeyToGroup, - removeKeyFromGroup, - checkKeyModelAccess, -} from "./db/apiKeyGroups"; - -export { - createRelayToken, - getRelayTokens, - getRelayToken, - getRelayTokenByHash, - updateRelayToken, - deleteRelayToken, - toggleRelayToken, - checkRateLimit, - recordRelayUsage, - getRelayUsage, - getRelayLogs, -} from "./db/relayProxies"; - -export type { - RelayToken, - RelayTokenRow, - RelayLogRow, - CreateRelayTokenInput, - RelayTokenWithSecret, -} from "./db/relayProxies"; - -export { - upsertFreeProxy, - listFreeProxies, - countFreeProxies, - listFreeProxiesBySource, - getFreeProxyById, - markFreeProxyInPool, - promoteFreeProxyToPool, - deleteFreeProxy, - clearFreeProxiesBySource, - pruneStaleFreeProxies, - getFreeProxyStats, - recordFreeProxySync, - recordFreeProxySyncErrors, - clearFreeProxySyncErrors, - getFreeProxySyncErrors, -} from "./db/freeProxies"; - -export type { FreeProxyRecord, FreeProxyStats, FreeProxySyncErrors } from "./db/freeProxies"; - -export { - listPlaygroundPresets, - getPlaygroundPreset, - createPlaygroundPreset, - updatePlaygroundPreset, - deletePlaygroundPreset, -} from "./db/playgroundPresets"; - -export type { PlaygroundPresetListItem } from "./db/playgroundPresets"; -// Plan 21 — Memory Engine Redesign -export { - getMemoryVecMeta, - setMemoryVecMeta, - markMemoryNeedsReindex, - markAllMemoriesNeedReindex, - getMemoryReindexQueue, - countMemoryReindexPending, - type MemoryVecMeta, -} from "./db/memoryVec"; -// T-A-F2: AgentBridge state/mappings/bypass + Inspector custom hosts/sessions -export * from "./db/agentBridgeState"; -export * from "./db/agentBridgeMappings"; -export * from "./db/agentBridgeBypass"; -export * from "./db/inspectorCustomHosts"; -export * from "./db/inspectorSessions"; -export * from "./db/omp"; -// Quota Sharing — Group B (planos 16+22) -export { - listPools, - getPool, - getPoolsByGroup, - ensurePool, - createPool, - updatePool, - deletePool, - upsertAllocations, - listAllocationsForApiKey, -} from "./db/quotaPools"; -// Quota per-(key, model) caps — Group B Fase 3 #7 -export { getModelCap, listModelCaps, setModelCap, deleteModelCap } from "./db/quotaModelCaps"; - -export { - // Quota Groups (B2) - createGroup, - getGroup, - getGroupName, - listGroups, - renameGroup, - deleteGroup, -} from "./db/quotaGroups"; - -export type { QuotaGroup } from "./db/quotaGroups"; -export { - getBucket, - incrementBucket, - getPair, - sumPoolDimension, - gcOlderThan as gcQuotaConsumption, -} from "./db/quotaConsumption"; -export { - getPlan as getProviderPlan, - listPlans as listProviderPlans, - upsertPlan as upsertProviderPlan, - deletePlan as deleteProviderPlan, -} from "./db/providerPlans"; - -export { - // Per-API-Key Token Limits (migration 073) - upsertTokenLimit, - listTokenLimits, - getTokenLimitsForRequest, - deleteTokenLimit, - getWindowUsage, - incrementWindowTokens, - resetWindowIfElapsed, - logTokenLimitReset, -} from "./db/tokenLimits"; - -export type { - TokenLimit, - TokenLimitScopeType, - UpsertTokenLimitInput, - TokenWindowState, -} from "./db/tokenLimits"; - -export { - insertPlugin, - getPluginById, - getPluginByName, - listPlugins, - updatePluginStatus, - updatePluginConfig, - deletePlugin, - pluginExists, -} from "./db/plugins"; - -export type { PluginRow, PluginCreateInput } from "./db/plugins"; - -export { - getApiKeyContextSource, - setApiKeyContextSource, - deleteApiKeyContextSource, - listApiKeyContextSources, -} from "./db/apiKeyContextSources"; -export type { ApiKeyContextSource } from "./db/apiKeyContextSources"; -export * from "./db/localCorpus"; -export { sumUsageTokensThisMonth } from "./db/usageSummary"; - -export { - // Model Intelligence (task-fitness scores) - getModelIntelligence, - getModelIntelligenceBySource, - upsertModelIntelligence, - deleteModelIntelligence, - deleteExpiredIntelligence, - deleteModelIntelligenceBySource, - listModelIntelligence, - bulkUpsertModelIntelligence, - getResolvedTaskFitness, - setUserFitnessOverrideEntry, - deleteUserFitnessOverrideEntry, -} from "./db/modelIntelligence"; - -export type { ModelIntelligenceEntry } from "./db/modelIntelligence"; - -export { - getProviderMetrics, - getSearchProviderStats, - getRecentSearchLogs, - getSearchAggregateStats, - getSearchProviderCounts, - getFallbackStats, -} from "./db/callLogStats"; -export type { - ProviderMetricRow, - SearchProviderStatRow, - SearchRecentRow, - SearchAggregateStats, - SearchProviderCountRow, - FallbackStatsRow, -} from "./db/callLogStats"; - -export { - buildUnifiedSource, - buildPresetUnifiedSource, - getUsageSummary, - getDailyUsage, - getDailyCostRows, - getHeatmapRows, - getModelUsageRows, - getProviderCostRows, - getProviderUsageRows, - getAccountCostRows, - getAccountUsageRows, - getApiKeyUsageRows, - getServiceTierUsageRows, - getApiKeyMetadataRows, - getWeeklyPatternRows, - getPresetCostModelRows, - getAllUsageHistory, - getAllDomainCostHistory, - getAllDomainBudgets, -} from "./db/usageAnalytics"; -export type { - AnalyticsParams, - BuildUnifiedSourceOptions, - UnifiedSourceResult, - UsageSummaryRow, - DailyUsageRow, - DailyCostRow, - HeatmapRow, - ModelUsageRow, - ProviderCostRow, - ProviderUsageRow, - AccountCostRow, - AccountUsageRow, - ApiKeyUsageRow, - ServiceTierUsageRow, - ApiKeyMetadataRow, - WeeklyPatternRow, - PresetCostModelRow, -} from "./db/usageAnalytics"; - -// --------------------------------------------------------------------------- -// call_logs auto-routing analytics (#3500 slice 4) -// --------------------------------------------------------------------------- -export { - getAutoRoutingTotalCount, - getAutoRoutingVariantBreakdown, - getAutoRoutingTopProviders, -} from "./db/usageLogs"; -export type { - AutoRoutingTotalResult, - AutoRoutingVariantRow, - AutoRoutingTopProviderRow, -} from "./db/usageLogs"; - -// --------------------------------------------------------------------------- -// semantic_cache — cache entries CRUD (#3500 slice 4) -// --------------------------------------------------------------------------- -export { - listSemanticCacheEntries, - deleteSemanticCacheBySignature, - deleteSemanticCacheByModel, -} from "./db/semanticCache"; -export type { - SemanticCacheEntry, - SemanticCacheListOptions, - SemanticCacheListResult, - DeleteSemanticCacheBySignatureResult, - DeleteSemanticCacheByModelResult, -} from "./db/semanticCache"; - -// --------------------------------------------------------------------------- -// proxy_logs — export query (#3500 slice 4) -// --------------------------------------------------------------------------- -export { exportProxyLogsSince } from "./db/proxyLogs"; -// --------------------------------------------------------------------------- -// Per-connection 429 cooldown wrappers (#5957 / #5958 — Issue 1 follow-ups) -// Logic lives in db/providers/rateLimit.ts (Hard Rule #2 — localDb is re-export -// only); re-exported here for the historical localDb import contract. -// --------------------------------------------------------------------------- -export { markConnectionRateLimitedUntil, clearConnectionRateLimit } from "./db/providers"; -// Provider param filters — denylist/allowlist config per provider/model (#6625) -export * from "./db/paramFilters"; -export * from "./db/interceptionRules"; // Per-model web-search/web-fetch interception rules (#3384) -export * from "./db/relayProbeStats"; // Relay probe latency/health stats (#6909) -export * from "./db/ccDiscoveryAliases"; // Claude Code discovery-alias gate (flag + per-provider/model overrides) -export * from "./db/ccDiscoveryMetrics"; // Claude Code discovery-alias usage counters (alias requests + discovery hits) -export * from "./db/functionalGatewayMirrors"; // Functional-gateway mirror gate (flag + per-provider/model overrides) -// Radar client — local feed cache + settings (opt-in, encrypted supporter key) -export { - getRadarCache, - setRadarCache, - getRadarSettings, - setRadarOptIn, - setRadarKey, - getRadarReferralsCache, - setRadarReferralsCache, - getRadarOffersCache, - setRadarOffersCache, - getRadarIntelCache, - setRadarIntelCache, - listRadarLocalModelState, - setRadarLocalModelOverride, - clearRadarLocalModelOverride, - setRadarModelTombstone, - getRadarLocalMergeState, -} from "./db/radar"; -export type { - RadarCache, - RadarSettings, - RadarReferralsCache, - RadarOffersCache, - RadarIntelCache, - RadarLocalModelState, - RadarLocalModelOverridePatch, - RadarLocalMergeState, -} from "./db/radar"; -export * from "./db/conductorBridge"; // OmniConductor hub mirror — SSE cursor (PRD Conductor RF1) -export * from "./db/agenticConversations"; // Multi-turn conversation id tracking (X-ConversationId) diff --git a/src/lib/tokenHealthCheckCursor.ts b/src/lib/tokenHealthCheckCursor.ts index beee15255427..3950062a05f4 100644 --- a/src/lib/tokenHealthCheckCursor.ts +++ b/src/lib/tokenHealthCheckCursor.ts @@ -8,7 +8,7 @@ * import technique — tokenHealthCheck.ts-private helpers passed as params * rather than imported) but greenfield: type-checked normally, no * @ts-nocheck, and imports updateProviderConnection directly from its - * owning module rather than the localDb barrel (Hard Rule #2). + * owning db/ module (Hard Rule #2). */ import { updateProviderConnection } from "@/lib/db/providers"; diff --git a/tests/integration/all-statuses-route.test.ts b/tests/integration/all-statuses-route.test.ts index 4c3924020c0c..dcda79eb2d95 100644 --- a/tests/integration/all-statuses-route.test.ts +++ b/tests/integration/all-statuses-route.test.ts @@ -20,7 +20,8 @@ process.env.API_KEY_SECRET = "test-all-statuses-secret"; // Import DB modules after setting DATA_DIR const core = await import("../../src/lib/db/core.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updateSettings } = await import("@/lib/db/settings"); +const localDb = { updateSettings }; const apiKeysDb = await import("../../src/lib/db/apiKeys.ts"); // Import cliTools modules (batchStatusCache for cache tests) diff --git a/tests/integration/api-keys.test.ts b/tests/integration/api-keys.test.ts index 51e667e1bd86..4e82c15874ad 100644 --- a/tests/integration/api-keys.test.ts +++ b/tests/integration/api-keys.test.ts @@ -12,7 +12,8 @@ process.env.CLOUD_URL = "http://cloud.example"; const core = await import("../../src/lib/db/core.ts"); const apiKeysDb = await import("../../src/lib/db/apiKeys.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updateSettings } = await import("@/lib/db/settings"); +const localDb = { updateSettings }; const compliance = await import("../../src/lib/compliance/index.ts"); const listRoute = await import("../../src/app/api/keys/route.ts"); const keyRoute = await import("../../src/app/api/keys/[id]/route.ts"); diff --git a/tests/integration/api-routes-critical.test.ts b/tests/integration/api-routes-critical.test.ts index 7f04c9245bb7..ccc8df4fc278 100644 --- a/tests/integration/api-routes-critical.test.ts +++ b/tests/integration/api-routes-critical.test.ts @@ -11,7 +11,10 @@ process.env.API_KEY_SECRET = "test-api-key-secret"; const core = await import("../../src/lib/db/core.ts"); const apiKeysDb = await import("../../src/lib/db/apiKeys.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updateSettings } = await import("@/lib/db/settings"); +const { assignProxyToScope, createProxy } = await import("@/lib/db/proxies"); +const { createProviderConnection } = await import("@/lib/db/providers"); +const localDb = { updateSettings, assignProxyToScope, createProviderConnection, createProxy }; const proxiesRoute = await import("../../src/app/api/v1/management/proxies/route.ts"); const settingsProxyRoute = await import("../../src/app/api/settings/proxy/route.ts"); const settingsMitmRoute = await import("../../src/app/api/settings/mitm/route.ts"); diff --git a/tests/integration/cli-settings-codewhale.test.ts b/tests/integration/cli-settings-codewhale.test.ts index ed843a1373fc..a9f2e4789e3d 100644 --- a/tests/integration/cli-settings-codewhale.test.ts +++ b/tests/integration/cli-settings-codewhale.test.ts @@ -20,7 +20,8 @@ process.env.API_KEY_SECRET = "test-api-key-secret-codewhale"; process.env.JWT_SECRET = "test-jwt-secret-codewhale"; const core = await import("../../src/lib/db/core.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updateSettings } = await import("@/lib/db/settings"); +const localDb = { updateSettings }; const { GET, POST, DELETE } = await import("../../src/app/api/cli-tools/codewhale-settings/route.ts"); diff --git a/tests/integration/cli-settings-deepseek-tui.test.ts b/tests/integration/cli-settings-deepseek-tui.test.ts index a4d7ffee60de..256051175082 100644 --- a/tests/integration/cli-settings-deepseek-tui.test.ts +++ b/tests/integration/cli-settings-deepseek-tui.test.ts @@ -14,7 +14,8 @@ process.env.API_KEY_SECRET = "test-api-key-secret-deepseek-tui"; process.env.JWT_SECRET = "test-jwt-secret-deepseek-tui"; const core = await import("../../src/lib/db/core.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updateSettings } = await import("@/lib/db/settings"); +const localDb = { updateSettings }; const { GET, POST, DELETE } = await import("../../src/app/api/cli-tools/deepseek-tui-settings/route.ts"); diff --git a/tests/integration/cli-settings-forge.test.ts b/tests/integration/cli-settings-forge.test.ts index 8cb4b7538e31..a0d30d92a5fa 100644 --- a/tests/integration/cli-settings-forge.test.ts +++ b/tests/integration/cli-settings-forge.test.ts @@ -16,7 +16,8 @@ process.env.JWT_SECRET = "test-jwt-secret-forge"; // Import DB reset helpers (must be before route import) const core = await import("../../src/lib/db/core.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updateSettings } = await import("@/lib/db/settings"); +const localDb = { updateSettings }; // Import route handlers const { GET, POST, DELETE } = await import("../../src/app/api/cli-tools/forge-settings/route.ts"); diff --git a/tests/integration/cli-settings-grok-build.test.ts b/tests/integration/cli-settings-grok-build.test.ts index 8ab679d9c27e..31c696aa700f 100644 --- a/tests/integration/cli-settings-grok-build.test.ts +++ b/tests/integration/cli-settings-grok-build.test.ts @@ -29,7 +29,8 @@ process.env.OMNIROUTE_ALLOW_CONTAINER_CONFIG_WRITE = "1"; // Import DB reset helpers (must be before route import) const core = await import("../../src/lib/db/core.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updateSettings } = await import("@/lib/db/settings"); +const localDb = { updateSettings }; const apiKeysDb = await import("../../src/lib/db/apiKeys.ts"); // Import route handlers diff --git a/tests/integration/cli-settings-jcode.test.ts b/tests/integration/cli-settings-jcode.test.ts index 6d1930aa83ea..d05002813a34 100644 --- a/tests/integration/cli-settings-jcode.test.ts +++ b/tests/integration/cli-settings-jcode.test.ts @@ -14,7 +14,8 @@ process.env.API_KEY_SECRET = "test-api-key-secret-jcode"; process.env.JWT_SECRET = "test-jwt-secret-jcode"; const core = await import("../../src/lib/db/core.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updateSettings } = await import("@/lib/db/settings"); +const localDb = { updateSettings }; const { GET, POST, DELETE } = await import("../../src/app/api/cli-tools/jcode-settings/route.ts"); diff --git a/tests/integration/cli-settings-letta.test.ts b/tests/integration/cli-settings-letta.test.ts index d6a07ddc69b5..53c950d3da48 100644 --- a/tests/integration/cli-settings-letta.test.ts +++ b/tests/integration/cli-settings-letta.test.ts @@ -20,7 +20,8 @@ process.env.API_KEY_SECRET = "test-api-key-secret-letta"; process.env.JWT_SECRET = "test-jwt-secret-letta"; const core = await import("../../src/lib/db/core.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updateSettings } = await import("@/lib/db/settings"); +const localDb = { updateSettings }; const { GET, POST, DELETE } = await import("../../src/app/api/cli-tools/letta-settings/route.ts"); diff --git a/tests/integration/cli-settings-omp.test.ts b/tests/integration/cli-settings-omp.test.ts index c07715ff4fbc..75ce683f23f8 100644 --- a/tests/integration/cli-settings-omp.test.ts +++ b/tests/integration/cli-settings-omp.test.ts @@ -22,7 +22,8 @@ process.env.API_KEY_SECRET = "test-api-key-secret-omp"; process.env.JWT_SECRET = "test-jwt-secret-omp"; const core = await import("../../src/lib/db/core.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updateSettings } = await import("@/lib/db/settings"); +const localDb = { updateSettings }; const { GET, POST, DELETE } = await import("../../src/app/api/cli-tools/omp-settings/route.ts"); diff --git a/tests/integration/cli-settings-pi.test.ts b/tests/integration/cli-settings-pi.test.ts index 29133efd51eb..71984da16da5 100644 --- a/tests/integration/cli-settings-pi.test.ts +++ b/tests/integration/cli-settings-pi.test.ts @@ -14,7 +14,8 @@ process.env.API_KEY_SECRET = "test-api-key-secret-pi"; process.env.JWT_SECRET = "test-jwt-secret-pi"; const core = await import("../../src/lib/db/core.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updateSettings } = await import("@/lib/db/settings"); +const localDb = { updateSettings }; const { GET, POST, DELETE } = await import("../../src/app/api/cli-tools/pi-settings/route.ts"); diff --git a/tests/integration/cli-settings-smelt.test.ts b/tests/integration/cli-settings-smelt.test.ts index 8395b4f1eb95..4290e12a1897 100644 --- a/tests/integration/cli-settings-smelt.test.ts +++ b/tests/integration/cli-settings-smelt.test.ts @@ -14,7 +14,8 @@ process.env.API_KEY_SECRET = "test-api-key-secret-smelt"; process.env.JWT_SECRET = "test-jwt-secret-smelt"; const core = await import("../../src/lib/db/core.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updateSettings } = await import("@/lib/db/settings"); +const localDb = { updateSettings }; const { GET, POST, DELETE } = await import("../../src/app/api/cli-tools/smelt-settings/route.ts"); diff --git a/tests/integration/combo-live/cost-and-fusion.live.test.ts b/tests/integration/combo-live/cost-and-fusion.live.test.ts index 8a1b59e04e38..525b0491379b 100644 --- a/tests/integration/combo-live/cost-and-fusion.live.test.ts +++ b/tests/integration/combo-live/cost-and-fusion.live.test.ts @@ -61,10 +61,7 @@ const PROVIDER_DEFAULT_MODELS: Record = { async function isHealthy(conn: LiveConnection): Promise { if (!h.LIVE_ENABLED) return false; - const model = - conn.model ?? - PROVIDER_DEFAULT_MODELS[conn.provider] ?? - `${conn.provider}/default`; + const model = conn.model ?? PROVIDER_DEFAULT_MODELS[conn.provider] ?? `${conn.provider}/default`; const directModel = `${conn.provider}/${model}`; const controller = new AbortController(); const timer = setTimeout(() => controller.abort(), WARMUP_TIMEOUT_MS); @@ -90,14 +87,18 @@ async function isHealthy(conn: LiveConnection): Promise { /** * Pick up to `n` confirmed-healthy connections, preferring fast/cheap providers. */ -async function pickConfirmedHealthy( - n: number, - preferred?: string[] -): Promise { +async function pickConfirmedHealthy(n: number, preferred?: string[]): Promise { if (!h.LIVE_ENABLED) return []; const conns = await (h as any).listLiveConnections(); - const PREFERRED_ORDER = - preferred ?? ["groq", "cerebras", "opencode-go", "deepseek", "gemini", "together", "openrouter"]; + const PREFERRED_ORDER = preferred ?? [ + "groq", + "cerebras", + "opencode-go", + "deepseek", + "gemini", + "together", + "openrouter", + ]; const sorted = [...conns].sort((a: LiveConnection, b: LiveConnection) => { const ai = PREFERRED_ORDER.indexOf(a.provider); const bi = PREFERRED_ORDER.indexOf(b.provider); @@ -118,7 +119,7 @@ async function pickConfirmedHealthy( */ async function resolveInputCost(provider: string, model: string): Promise { try { - const { getPricingForModel } = await import("../../../src/lib/localDb.ts"); + const { getPricingForModel } = await import("@/lib/db/settings"); const pricing = await getPricingForModel(provider, model); const cost = Number((pricing as any)?.input); return Number.isFinite(cost) ? cost : Infinity; @@ -164,317 +165,344 @@ after(async () => { // Test 1: cost-optimized — cheaper real provider served first // --------------------------------------------------------------------------- -test("live cost-optimized — cheaper real provider served first", { - skip: !h.LIVE_ENABLED && "RUN_COMBO_LIVE!=1", -}, async () => { - if (!h.LIVE_ENABLED) return; - - // We prefer groq+deepseek: distinct model names (easy to confirm served), - // and deepseek has a known non-zero price ($0.28/M) while groq free models - // land at $0 in the registry. The cost sorter puts $0 before $0.28, so if - // we list deepseek FIRST and groq SECOND, a correct sort gives us groq first. - // - // We fall back to cerebras if groq is unhealthy. - const candidates = await pickConfirmedHealthy(2, [ - "groq", "cerebras", "deepseek", "opencode-go", - ]); - - // We need exactly 2 healthy connections with DISTINCT providers. - const seen = new Set(); - const uniqueCandidates = candidates.filter((c: LiveConnection) => { - if (seen.has(c.provider)) return false; - seen.add(c.provider); - return true; - }); - - if (uniqueCandidates.length < 2) { - console.log( - `[cost-optimized skip] Only ${uniqueCandidates.length} distinct healthy provider(s) — need ≥2. Skipping.` - ); - return; - } - - const [a, b] = uniqueCandidates; - const aModel = a.model ?? PROVIDER_DEFAULT_MODELS[a.provider] ?? `${a.provider}/default`; - const bModel = b.model ?? PROVIDER_DEFAULT_MODELS[b.provider] ?? `${b.provider}/default`; +test( + "live cost-optimized — cheaper real provider served first", + { + skip: !h.LIVE_ENABLED && "RUN_COMBO_LIVE!=1", + }, + async () => { + if (!h.LIVE_ENABLED) return; + + // We prefer groq+deepseek: distinct model names (easy to confirm served), + // and deepseek has a known non-zero price ($0.28/M) while groq free models + // land at $0 in the registry. The cost sorter puts $0 before $0.28, so if + // we list deepseek FIRST and groq SECOND, a correct sort gives us groq first. + // + // We fall back to cerebras if groq is unhealthy. + const candidates = await pickConfirmedHealthy(2, [ + "groq", + "cerebras", + "deepseek", + "opencode-go", + ]); + + // We need exactly 2 healthy connections with DISTINCT providers. + const seen = new Set(); + const uniqueCandidates = candidates.filter((c: LiveConnection) => { + if (seen.has(c.provider)) return false; + seen.add(c.provider); + return true; + }); - // Resolve pricing from the live DB snapshot. - const aCost = await resolveInputCost(a.provider, aModel); - const bCost = await resolveInputCost(b.provider, bModel); + if (uniqueCandidates.length < 2) { + console.log( + `[cost-optimized skip] Only ${uniqueCandidates.length} distinct healthy provider(s) — need ≥2. Skipping.` + ); + return; + } - console.log( - `[cost-optimized] Candidates: ${a.provider}/${aModel} ($${aCost}/M) vs ${b.provider}/${bModel} ($${bCost}/M)` - ); + const [a, b] = uniqueCandidates; + const aModel = a.model ?? PROVIDER_DEFAULT_MODELS[a.provider] ?? `${a.provider}/default`; + const bModel = b.model ?? PROVIDER_DEFAULT_MODELS[b.provider] ?? `${b.provider}/default`; - // If both are Infinity (no pricing data for either), we can't prove cost ordering. - if (!Number.isFinite(aCost) && !Number.isFinite(bCost)) { - console.log( - `[cost-optimized skip] Neither ${a.provider} nor ${b.provider} has resolvable catalog ` + - `pricing in the live DB snapshot — cannot prove cost ordering. Skipping.` - ); - return; - } + // Resolve pricing from the live DB snapshot. + const aCost = await resolveInputCost(a.provider, aModel); + const bCost = await resolveInputCost(b.provider, bModel); - // If both are equal (including both $0), we can't prove reordering. - if (aCost === bCost) { console.log( - `[cost-optimized skip] Both providers have equal pricing ($${aCost}/M each) — ` + - `cannot prove reordering. Skipping.` + `[cost-optimized] Candidates: ${a.provider}/${aModel} ($${aCost}/M) vs ${b.provider}/${bModel} ($${bCost}/M)` ); - return; - } - - // Identify cheap vs pricey. - const [cheapConn, priceyConn] = - aCost <= bCost ? [a, b] : [b, a]; - const [cheapCost, priceyCost] = - aCost <= bCost ? [aCost, bCost] : [bCost, aCost]; - const cheapModel = - cheapConn.model ?? PROVIDER_DEFAULT_MODELS[cheapConn.provider] ?? `${cheapConn.provider}/default`; - const priceyModel = - priceyConn.model ?? PROVIDER_DEFAULT_MODELS[priceyConn.provider] ?? `${priceyConn.provider}/default`; - - console.log( - `[cost-optimized] Cheap: ${cheapConn.provider}/${cheapModel} ($${cheapCost}/M), ` + - `Pricey: ${priceyConn.provider}/${priceyModel} ($${priceyCost}/M)` - ); - - // Create combo with PRICEY first — a correct cost sorter must reorder to CHEAP first. - const comboName = `__live-smoke-cost-opt-${Date.now()}__`; - const combo = await (h as any).combosDb.createCombo({ - name: comboName, - strategy: "cost-optimized", - // Pricey listed FIRST: proves the sorter reorders, not just uses the given order. - models: [(h as any).comboModelFor(priceyConn), (h as any).comboModelFor(cheapConn)], - config: { maxRetries: 0, retryDelayMs: 0, stickyRoundRobinLimit: 1 }, - }); - try { - const response = await (h as any).handleChat( - (h as any).buildRequest({ - body: (h as any).liveBody(comboName, { - messages: [{ role: "user", content: `ping ${uniqueNonce("cost-opt")}` }], - }), - }) - ); + // If both are Infinity (no pricing data for either), we can't prove cost ordering. + if (!Number.isFinite(aCost) && !Number.isFinite(bCost)) { + console.log( + `[cost-optimized skip] Neither ${a.provider} nor ${b.provider} has resolvable catalog ` + + `pricing in the live DB snapshot — cannot prove cost ordering. Skipping.` + ); + return; + } - assert.equal(response.status, 200, `Expected HTTP 200, got ${response.status}`); - const text = await (h as any).readCompletionText(response); - assert.ok(text.length > 0, "Expected non-empty completion text from cost-optimized combo"); + // If both are equal (including both $0), we can't prove reordering. + if (aCost === bCost) { + console.log( + `[cost-optimized skip] Both providers have equal pricing ($${aCost}/M each) — ` + + `cannot prove reordering. Skipping.` + ); + return; + } - // Collect served-provider signals. - const headerProvider = (h as any).servedProvider(response); - const bodyProvider = await (h as any).servedProviderFromBody(response); - const rawModel = await readResponseModel(response); + // Identify cheap vs pricey. + const [cheapConn, priceyConn] = aCost <= bCost ? [a, b] : [b, a]; + const [cheapCost, priceyCost] = aCost <= bCost ? [aCost, bCost] : [bCost, aCost]; + const cheapModel = + cheapConn.model ?? + PROVIDER_DEFAULT_MODELS[cheapConn.provider] ?? + `${cheapConn.provider}/default`; + const priceyModel = + priceyConn.model ?? + PROVIDER_DEFAULT_MODELS[priceyConn.provider] ?? + `${priceyConn.provider}/default`; console.log( - `[cost-optimized] served: header=${headerProvider ?? "(absent)"}, ` + - `body=${bodyProvider ?? "(absent)"}, model="${rawModel ?? "(n/a)"}"` + `[cost-optimized] Cheap: ${cheapConn.provider}/${cheapModel} ($${cheapCost}/M), ` + + `Pricey: ${priceyConn.provider}/${priceyModel} ($${priceyCost}/M)` ); - // PRIMARY ASSERTION: served provider must be the cheap one, not the pricey one. - // The cost sorter should have put the cheap provider first despite it being listed second. - // - // We use three signals in priority order: - // 1. X-OmniRoute-Selected-Connection-Id header (fallback paths only — may be absent on 200). - // 2. Body model field provider prefix (e.g. "groq/model" → "groq"). - // 3. Raw model string comparison (model name matches cheap provider's known model). - - // Signal 1: header. - if (headerProvider !== undefined) { - assert.equal( - headerProvider, - cheapConn.provider, - `[header] Expected cheap provider "${cheapConn.provider}" to serve, got "${headerProvider}". ` + - `Cost sorter may not have reordered: cheap=$${cheapCost}/M, pricey=$${priceyCost}/M` + // Create combo with PRICEY first — a correct cost sorter must reorder to CHEAP first. + const comboName = `__live-smoke-cost-opt-${Date.now()}__`; + const combo = await (h as any).combosDb.createCombo({ + name: comboName, + strategy: "cost-optimized", + // Pricey listed FIRST: proves the sorter reorders, not just uses the given order. + models: [(h as any).comboModelFor(priceyConn), (h as any).comboModelFor(cheapConn)], + config: { maxRetries: 0, retryDelayMs: 0, stickyRoundRobinLimit: 1 }, + }); + + try { + const response = await (h as any).handleChat( + (h as any).buildRequest({ + body: (h as any).liveBody(comboName, { + messages: [{ role: "user", content: `ping ${uniqueNonce("cost-opt")}` }], + }), + }) ); - console.log( - `[cost-optimized PASS via header] ${cheapConn.provider} served (cost $${cheapCost}/M < $${priceyCost}/M)` - ); - return; - } - // Signal 2: body provider prefix. - if (bodyProvider !== undefined) { - assert.equal( - bodyProvider, - cheapConn.provider, - `[body prefix] Expected cheap provider "${cheapConn.provider}" to serve, got "${bodyProvider}". ` + - `Cost sorter may not have reordered: cheap=$${cheapCost}/M, pricey=$${priceyCost}/M` - ); + assert.equal(response.status, 200, `Expected HTTP 200, got ${response.status}`); + const text = await (h as any).readCompletionText(response); + assert.ok(text.length > 0, "Expected non-empty completion text from cost-optimized combo"); + + // Collect served-provider signals. + const headerProvider = (h as any).servedProvider(response); + const bodyProvider = await (h as any).servedProviderFromBody(response); + const rawModel = await readResponseModel(response); + console.log( - `[cost-optimized PASS via body prefix] ${cheapConn.provider} served (cost $${cheapCost}/M < $${priceyCost}/M)` + `[cost-optimized] served: header=${headerProvider ?? "(absent)"}, ` + + `body=${bodyProvider ?? "(absent)"}, model="${rawModel ?? "(n/a)"}"` ); - return; - } - // Signal 3: raw model string. - if (rawModel !== undefined) { - // If the response model matches the cheap provider's model name, the cheap provider served. - if (rawModel === cheapModel || rawModel.endsWith(`/${cheapModel}`)) { + // PRIMARY ASSERTION: served provider must be the cheap one, not the pricey one. + // The cost sorter should have put the cheap provider first despite it being listed second. + // + // We use three signals in priority order: + // 1. X-OmniRoute-Selected-Connection-Id header (fallback paths only — may be absent on 200). + // 2. Body model field provider prefix (e.g. "groq/model" → "groq"). + // 3. Raw model string comparison (model name matches cheap provider's known model). + + // Signal 1: header. + if (headerProvider !== undefined) { + assert.equal( + headerProvider, + cheapConn.provider, + `[header] Expected cheap provider "${cheapConn.provider}" to serve, got "${headerProvider}". ` + + `Cost sorter may not have reordered: cheap=$${cheapCost}/M, pricey=$${priceyCost}/M` + ); + console.log( + `[cost-optimized PASS via header] ${cheapConn.provider} served (cost $${cheapCost}/M < $${priceyCost}/M)` + ); + return; + } + + // Signal 2: body provider prefix. + if (bodyProvider !== undefined) { + assert.equal( + bodyProvider, + cheapConn.provider, + `[body prefix] Expected cheap provider "${cheapConn.provider}" to serve, got "${bodyProvider}". ` + + `Cost sorter may not have reordered: cheap=$${cheapCost}/M, pricey=$${priceyCost}/M` + ); console.log( - `[cost-optimized PASS via model field] model="${rawModel}" matches cheap provider ` + - `${cheapConn.provider}/${cheapModel} (cost $${cheapCost}/M < $${priceyCost}/M)` + `[cost-optimized PASS via body prefix] ${cheapConn.provider} served (cost $${cheapCost}/M < $${priceyCost}/M)` ); return; } - // If the response model matches the pricey provider's model name, the cost sort failed. - if (rawModel === priceyModel || rawModel.endsWith(`/${priceyModel}`)) { - assert.fail( - `[model field] Pricey provider "${priceyConn.provider}" (model="${priceyModel}", ` + - `$${priceyCost}/M) served BEFORE cheap "${cheapConn.provider}" (model="${cheapModel}", ` + - `$${cheapCost}/M) — cost sorter did not reorder correctly.` + // Signal 3: raw model string. + if (rawModel !== undefined) { + // If the response model matches the cheap provider's model name, the cheap provider served. + if (rawModel === cheapModel || rawModel.endsWith(`/${cheapModel}`)) { + console.log( + `[cost-optimized PASS via model field] model="${rawModel}" matches cheap provider ` + + `${cheapConn.provider}/${cheapModel} (cost $${cheapCost}/M < $${priceyCost}/M)` + ); + return; + } + + // If the response model matches the pricey provider's model name, the cost sort failed. + if (rawModel === priceyModel || rawModel.endsWith(`/${priceyModel}`)) { + assert.fail( + `[model field] Pricey provider "${priceyConn.provider}" (model="${priceyModel}", ` + + `$${priceyCost}/M) served BEFORE cheap "${cheapConn.provider}" (model="${cheapModel}", ` + + `$${cheapCost}/M) — cost sorter did not reorder correctly.` + ); + } + + // Model field present but does not match either known model (provider echoes an + // aliased or prefixed name). We cannot distinguish which provider served. + console.warn( + `[cost-optimized] Signal ambiguous: rawModel="${rawModel}" does not match ` + + `"${cheapModel}" or "${priceyModel}". Got 200 + non-empty text; cannot confirm ` + + `cheap provider served. Recording as diagnostic-pass (cost gap confirmed: ` + + `$${cheapCost}/M vs $${priceyCost}/M).` ); + return; } - // Model field present but does not match either known model (provider echoes an - // aliased or prefixed name). We cannot distinguish which provider served. + // No signal at all — 200 + non-empty but we cannot confirm which provider served. console.warn( - `[cost-optimized] Signal ambiguous: rawModel="${rawModel}" does not match ` + - `"${cheapModel}" or "${priceyModel}". Got 200 + non-empty text; cannot confirm ` + - `cheap provider served. Recording as diagnostic-pass (cost gap confirmed: ` + - `$${cheapCost}/M vs $${priceyCost}/M).` + `[cost-optimized] All provider signals absent (header=absent, body=absent, model=absent). ` + + `Got HTTP 200 + non-empty text. Cost gap confirmed ($${cheapCost}/M vs $${priceyCost}/M) ` + + `but serving provider not identifiable. Recording as diagnostic-pass.` ); - return; - } - - // No signal at all — 200 + non-empty but we cannot confirm which provider served. - console.warn( - `[cost-optimized] All provider signals absent (header=absent, body=absent, model=absent). ` + - `Got HTTP 200 + non-empty text. Cost gap confirmed ($${cheapCost}/M vs $${priceyCost}/M) ` + - `but serving provider not identifiable. Recording as diagnostic-pass.` - ); - } finally { - if (typeof combo?.id === "string") { - await (h as any).combosDb.deleteCombo(combo.id as string); + } finally { + if (typeof combo?.id === "string") { + await (h as any).combosDb.deleteCombo(combo.id as string); + } } } -}); +); // --------------------------------------------------------------------------- // Test 2: fusion — panel fans out + judge synthesizes one answer // --------------------------------------------------------------------------- -test("live fusion — panel fans out and judge synthesizes one answer", { - skip: !h.LIVE_ENABLED && "RUN_COMBO_LIVE!=1", -}, async () => { - if (!h.LIVE_ENABLED) return; +test( + "live fusion — panel fans out and judge synthesizes one answer", + { + skip: !h.LIVE_ENABLED && "RUN_COMBO_LIVE!=1", + }, + async () => { + if (!h.LIVE_ENABLED) return; + + // Cost guard: pick ≤3 panel providers. Use cheapest/most reliable. + const candidates = await pickConfirmedHealthy(3, [ + "groq", + "cerebras", + "opencode-go", + "deepseek", + "gemini", + "together", + ]); + + // Need at least 2 distinct healthy providers to run a real fusion. + const seen = new Set(); + const panelConns = candidates + .filter((c: LiveConnection) => { + if (seen.has(c.provider)) return false; + seen.add(c.provider); + return true; + }) + .slice(0, 3); // cap at 3 to limit cost - // Cost guard: pick ≤3 panel providers. Use cheapest/most reliable. - const candidates = await pickConfirmedHealthy(3, [ - "groq", "cerebras", "opencode-go", "deepseek", "gemini", "together", - ]); + if (panelConns.length < 2) { + console.log( + `[fusion skip] Only ${panelConns.length} distinct healthy provider(s) confirmed — ` + + `need ≥2 for a real panel. Skipping.` + ); + return; + } - // Need at least 2 distinct healthy providers to run a real fusion. - const seen = new Set(); - const panelConns = candidates - .filter((c: LiveConnection) => { - if (seen.has(c.provider)) return false; - seen.add(c.provider); - return true; - }) - .slice(0, 3); // cap at 3 to limit cost + // Judge = first (cheapest) panel provider — cheap enough for a 16-token synthesis call. + const judgeConn = panelConns[0]; + const judgeModel = + judgeConn.model ?? + PROVIDER_DEFAULT_MODELS[judgeConn.provider] ?? + `${judgeConn.provider}/default`; + const judgeModelStr = `${judgeConn.provider}/${judgeModel}`; - if (panelConns.length < 2) { + const panelModels = panelConns.map((c: LiveConnection) => (h as any).comboModelFor(c)); console.log( - `[fusion skip] Only ${panelConns.length} distinct healthy provider(s) confirmed — ` + - `need ≥2 for a real panel. Skipping.` + `[fusion] Panel (${panelConns.length}): ${panelConns.map((c: LiveConnection) => c.provider).join(", ")} | ` + + `judge: ${judgeModelStr}` ); - return; - } - // Judge = first (cheapest) panel provider — cheap enough for a 16-token synthesis call. - const judgeConn = panelConns[0]; - const judgeModel = - judgeConn.model ?? - PROVIDER_DEFAULT_MODELS[judgeConn.provider] ?? - `${judgeConn.provider}/default`; - const judgeModelStr = `${judgeConn.provider}/${judgeModel}`; - - const panelModels = panelConns.map((c: LiveConnection) => (h as any).comboModelFor(c)); - console.log( - `[fusion] Panel (${panelConns.length}): ${panelConns.map((c: LiveConnection) => c.provider).join(", ")} | ` + - `judge: ${judgeModelStr}` - ); - - const comboName = `__live-smoke-fusion-${Date.now()}__`; - const combo = await (h as any).combosDb.createCombo({ - name: comboName, - strategy: "fusion", - models: panelModels, - config: { - maxRetries: 0, - retryDelayMs: 0, - judgeModel: judgeModelStr, - fusionTuning: { - minPanel: 2, - panelHardTimeoutMs: 90_000, + const comboName = `__live-smoke-fusion-${Date.now()}__`; + const combo = await (h as any).combosDb.createCombo({ + name: comboName, + strategy: "fusion", + models: panelModels, + config: { + maxRetries: 0, + retryDelayMs: 0, + judgeModel: judgeModelStr, + fusionTuning: { + minPanel: 2, + panelHardTimeoutMs: 90_000, + }, }, - }, - }); - - try { - const response = await (h as any).handleChat( - (h as any).buildRequest({ - body: (h as any).liveBody(comboName, { - messages: [{ role: "user", content: `ping ${uniqueNonce("fusion")} — reply in one short sentence` }], - // max_tokens:16 is the harness default via liveBody(); the judge call will - // also be bounded, keeping the panel + judge calls cheap. - }), - }) - ); + }); + + try { + const response = await (h as any).handleChat( + (h as any).buildRequest({ + body: (h as any).liveBody(comboName, { + messages: [ + { + role: "user", + content: `ping ${uniqueNonce("fusion")} — reply in one short sentence`, + }, + ], + // max_tokens:16 is the harness default via liveBody(); the judge call will + // also be bounded, keeping the panel + judge calls cheap. + }), + }) + ); - assert.equal(response.status, 200, `Expected HTTP 200 from fusion combo, got ${response.status}`); + assert.equal( + response.status, + 200, + `Expected HTTP 200 from fusion combo, got ${response.status}` + ); - const text = await (h as any).readCompletionText(response); - assert.ok( - text.length > 0, - "Expected a non-empty synthesized completion from the fusion judge" - ); + const text = await (h as any).readCompletionText(response); + assert.ok( + text.length > 0, + "Expected a non-empty synthesized completion from the fusion judge" + ); - // The fusion response comes from the JUDGE call. - // The body's `model` field should reflect the judge model, confirming the judge ran. - const rawModel = await readResponseModel(response); - const bodyProvider = await (h as any).servedProviderFromBody(response); + // The fusion response comes from the JUDGE call. + // The body's `model` field should reflect the judge model, confirming the judge ran. + const rawModel = await readResponseModel(response); + const bodyProvider = await (h as any).servedProviderFromBody(response); - console.log( - `[fusion] synthesized text (first 80 chars): "${text.slice(0, 80)}" | ` + - `model="${rawModel ?? "(n/a)"}" | body provider=${bodyProvider ?? "(absent)"}` - ); + console.log( + `[fusion] synthesized text (first 80 chars): "${text.slice(0, 80)}" | ` + + `model="${rawModel ?? "(n/a)"}" | body provider=${bodyProvider ?? "(absent)"}` + ); - // SIGNAL ANALYSIS — panel/judge evidence. - // The judge model string is judgeModelStr (e.g. "groq/llama-3.1-8b-instant"). - // If rawModel matches the judge's model name, the judge ran. - if (rawModel !== undefined) { - const judgeRan = - rawModel === judgeModel || - rawModel === judgeModelStr || - rawModel.endsWith(`/${judgeModel}`); - if (judgeRan) { - console.log( - `[fusion EVIDENCE] Judge confirmed: response model="${rawModel}" matches judge ${judgeModelStr}` - ); - } else { - // Model field doesn't match judge — may be aliased or provider returns own name. - console.warn( - `[fusion] response model="${rawModel}" does not exactly match judge "${judgeModelStr}". ` + - `Panel synthesis still assumed from HTTP 200 + non-empty text.` - ); + // SIGNAL ANALYSIS — panel/judge evidence. + // The judge model string is judgeModelStr (e.g. "groq/llama-3.1-8b-instant"). + // If rawModel matches the judge's model name, the judge ran. + if (rawModel !== undefined) { + const judgeRan = + rawModel === judgeModel || + rawModel === judgeModelStr || + rawModel.endsWith(`/${judgeModel}`); + if (judgeRan) { + console.log( + `[fusion EVIDENCE] Judge confirmed: response model="${rawModel}" matches judge ${judgeModelStr}` + ); + } else { + // Model field doesn't match judge — may be aliased or provider returns own name. + console.warn( + `[fusion] response model="${rawModel}" does not exactly match judge "${judgeModelStr}". ` + + `Panel synthesis still assumed from HTTP 200 + non-empty text.` + ); + } } - } - // The fusion panel had ≥2 members — at least 2 distinct upstream calls happened - // before the judge synthesized. We can't easily introspect individual panel calls - // from the test layer (they're internal to fusion.ts / combo.ts). The 200 + non-empty - // text from the judge is the authoritative proof of fusion completing. - console.log( - `[fusion PASS] Panel(${panelConns.length}) → judge(${judgeModelStr}) synthesis: HTTP 200, ` + - `${text.length} chars. Provider signal from body: ${bodyProvider ?? "absent (model name has no slash prefix)"}.` - ); - } finally { - if (typeof combo?.id === "string") { - await (h as any).combosDb.deleteCombo(combo.id as string); + // The fusion panel had ≥2 members — at least 2 distinct upstream calls happened + // before the judge synthesized. We can't easily introspect individual panel calls + // from the test layer (they're internal to fusion.ts / combo.ts). The 200 + non-empty + // text from the judge is the authoritative proof of fusion completing. + console.log( + `[fusion PASS] Panel(${panelConns.length}) → judge(${judgeModelStr}) synthesis: HTTP 200, ` + + `${text.length} chars. Provider signal from body: ${bodyProvider ?? "absent (model name has no slash prefix)"}.` + ); + } finally { + if (typeof combo?.id === "string") { + await (h as any).combosDb.deleteCombo(combo.id as string); + } } } -}); +); diff --git a/tests/integration/files-api.test.ts b/tests/integration/files-api.test.ts index e83cf5c840eb..914aae6f2ac5 100644 --- a/tests/integration/files-api.test.ts +++ b/tests/integration/files-api.test.ts @@ -1,6 +1,6 @@ import { describe, it, afterEach } from "node:test"; import assert from "node:assert"; -import { createFile, listFiles, deleteFile, getFile } from "@/lib/localDb"; +import { createFile, listFiles, deleteFile, getFile } from "@/lib/db/files"; describe("Files API - Integration Tests", () => { afterEach(() => { diff --git a/tests/integration/memory-embedding-providers.test.ts b/tests/integration/memory-embedding-providers.test.ts index 74bf51fe799e..9f26b588f6f1 100644 --- a/tests/integration/memory-embedding-providers.test.ts +++ b/tests/integration/memory-embedding-providers.test.ts @@ -19,7 +19,8 @@ process.env.DATA_DIR = TEST_DATA_DIR; process.env.API_KEY_SECRET = "test-secret-embedding-providers"; const core = await import("../../src/lib/db/core.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updateSettings } = await import("@/lib/db/settings"); +const localDb = { updateSettings }; // Import route AFTER setting DATA_DIR const embeddingProvidersRoute = diff --git a/tests/integration/memory-engine-status.test.ts b/tests/integration/memory-engine-status.test.ts index 3cff56385bec..8bd612a3478f 100644 --- a/tests/integration/memory-engine-status.test.ts +++ b/tests/integration/memory-engine-status.test.ts @@ -19,7 +19,8 @@ process.env.DATA_DIR = TEST_DATA_DIR; process.env.API_KEY_SECRET = "test-secret-engine-status"; const core = await import("../../src/lib/db/core.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updateSettings } = await import("@/lib/db/settings"); +const localDb = { updateSettings }; // Import route AFTER setting DATA_DIR const engineStatusRoute = await import("../../src/app/api/memory/engine-status/route.ts"); diff --git a/tests/integration/memory-reindex.test.ts b/tests/integration/memory-reindex.test.ts index 4eb2188c9890..199416bfc800 100644 --- a/tests/integration/memory-reindex.test.ts +++ b/tests/integration/memory-reindex.test.ts @@ -20,7 +20,8 @@ process.env.DATA_DIR = TEST_DATA_DIR; process.env.API_KEY_SECRET = "test-secret-reindex"; const core = await import("../../src/lib/db/core.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updateSettings } = await import("@/lib/db/settings"); +const localDb = { updateSettings }; const memoryStore = await import("../../src/lib/memory/store.ts"); const reindexRoute = await import("../../src/app/api/memory/reindex/route.ts"); diff --git a/tests/integration/memory-retrieve-preview.test.ts b/tests/integration/memory-retrieve-preview.test.ts index 36c4437d0504..efa8483cdbdc 100644 --- a/tests/integration/memory-retrieve-preview.test.ts +++ b/tests/integration/memory-retrieve-preview.test.ts @@ -19,7 +19,8 @@ process.env.DATA_DIR = TEST_DATA_DIR; process.env.API_KEY_SECRET = "test-secret-retrieve-preview"; const core = await import("../../src/lib/db/core.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updateSettings } = await import("@/lib/db/settings"); +const localDb = { updateSettings }; // Import route AFTER setting DATA_DIR const retrieveRoute = await import("../../src/app/api/memory/retrieve-preview/route.ts"); diff --git a/tests/integration/memory-route-put.test.ts b/tests/integration/memory-route-put.test.ts index 3d952cb00234..42be96ecc481 100644 --- a/tests/integration/memory-route-put.test.ts +++ b/tests/integration/memory-route-put.test.ts @@ -19,7 +19,8 @@ process.env.DATA_DIR = TEST_DATA_DIR; process.env.API_KEY_SECRET = "test-secret-for-memory-put"; const core = await import("../../src/lib/db/core.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updateSettings } = await import("@/lib/db/settings"); +const localDb = { updateSettings }; // ── Dynamic import of route module (after DATA_DIR set) ── const memoryIdRoute = await import("../../src/app/api/memory/[id]/route.ts"); diff --git a/tests/integration/memory-summarize.test.ts b/tests/integration/memory-summarize.test.ts index fa8c3b131e9e..812c99dca813 100644 --- a/tests/integration/memory-summarize.test.ts +++ b/tests/integration/memory-summarize.test.ts @@ -19,7 +19,8 @@ process.env.DATA_DIR = TEST_DATA_DIR; process.env.API_KEY_SECRET = "test-secret-summarize"; const core = await import("../../src/lib/db/core.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updateSettings } = await import("@/lib/db/settings"); +const localDb = { updateSettings }; const memoryStore = await import("../../src/lib/memory/store.ts"); const summarizeRoute = await import("../../src/app/api/memory/summarize/route.ts"); diff --git a/tests/integration/provider-journey.contract.test.ts b/tests/integration/provider-journey.contract.test.ts index 655f6f7b8fbc..9e8bb53743cf 100644 --- a/tests/integration/provider-journey.contract.test.ts +++ b/tests/integration/provider-journey.contract.test.ts @@ -56,7 +56,10 @@ process.env.DISABLE_SQLITE_AUTO_BACKUP = "true"; process.env.INITIAL_PASSWORD = "provider-journey-bootstrap"; const core = await import("../../src/lib/db/core.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updateSettings } = await import("@/lib/db/settings"); +const { updateProviderConnection } = await import("@/lib/db/providers"); +const { getCachedProviderNodes } = await import("@/lib/db/readCache"); +const localDb = { updateSettings, updateProviderConnection, getCachedProviderNodes }; const modelsDb = await import("../../src/lib/db/models.ts"); const providerNodesRoute = await import("../../src/app/api/provider-nodes/route.ts"); const providersRoute = await import("../../src/app/api/providers/route.ts"); diff --git a/tests/integration/qdrant-routes.test.ts b/tests/integration/qdrant-routes.test.ts index bc6c291adccf..4a7636b9c28b 100644 --- a/tests/integration/qdrant-routes.test.ts +++ b/tests/integration/qdrant-routes.test.ts @@ -26,7 +26,9 @@ process.env.DATA_DIR = TEST_DATA_DIR; process.env.API_KEY_SECRET = "test-secret-qdrant-routes"; const core = await import("../../src/lib/db/core.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updateSettings, getSettings } = await import("@/lib/db/settings"); +const { createProviderConnection } = await import("@/lib/db/providers"); +const localDb = { updateSettings, getSettings, createProviderConnection }; const memorySettings = await import("../../src/lib/memory/settings.ts"); // ── Route imports ── diff --git a/tests/integration/quota-plans-crud.test.ts b/tests/integration/quota-plans-crud.test.ts index 77c294330395..fb4a391c096d 100644 --- a/tests/integration/quota-plans-crud.test.ts +++ b/tests/integration/quota-plans-crud.test.ts @@ -23,7 +23,8 @@ process.env.DATA_DIR = TEST_DATA_DIR; process.env.API_KEY_SECRET = "test-quota-plans-secret"; const core = await import("../../src/lib/db/core.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updateSettings } = await import("@/lib/db/settings"); +const localDb = { updateSettings }; const compliance = await import("../../src/lib/compliance/index.ts"); const plansRoute = await import("../../src/app/api/quota/plans/route.ts"); const planIdRoute = await import("../../src/app/api/quota/plans/[connectionId]/route.ts"); diff --git a/tests/integration/quota-pool-usage-provider-resolution.test.ts b/tests/integration/quota-pool-usage-provider-resolution.test.ts index 533ddb642f32..5aec4f4945e4 100644 --- a/tests/integration/quota-pool-usage-provider-resolution.test.ts +++ b/tests/integration/quota-pool-usage-provider-resolution.test.ts @@ -27,8 +27,8 @@ process.env.API_KEY_SECRET = "test-quota-usage-provider-secret"; process.env.QUOTA_STORE_DRIVER = "sqlite"; const core = await import("../../src/lib/db/core.ts"); -const localDb = await import("../../src/lib/localDb.ts"); -const { createPool, upsertAllocations, createProviderConnection } = localDb; +const { createPool, upsertAllocations } = await import("@/lib/db/quotaPools"); +const { createProviderConnection } = await import("@/lib/db/providers"); const { resetQuotaStoreSingleton } = await import("../../src/lib/quota/QuotaStore.ts"); const usageRoute = await import("../../src/app/api/quota/pools/[id]/usage/route.ts"); diff --git a/tests/integration/quota-pools-crud.test.ts b/tests/integration/quota-pools-crud.test.ts index da3d3e8bb262..0b8fa5165717 100644 --- a/tests/integration/quota-pools-crud.test.ts +++ b/tests/integration/quota-pools-crud.test.ts @@ -26,7 +26,8 @@ process.env.API_KEY_SECRET = "test-quota-pools-secret"; // Import in dependency order to ensure migrations run before routes const core = await import("../../src/lib/db/core.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updateSettings } = await import("@/lib/db/settings"); +const localDb = { updateSettings }; const compliance = await import("../../src/lib/compliance/index.ts"); const poolsRoute = await import("../../src/app/api/quota/pools/route.ts"); const poolIdRoute = await import("../../src/app/api/quota/pools/[id]/route.ts"); diff --git a/tests/integration/quota-pools-usage.test.ts b/tests/integration/quota-pools-usage.test.ts index 9672958e25fb..1da02998cd90 100644 --- a/tests/integration/quota-pools-usage.test.ts +++ b/tests/integration/quota-pools-usage.test.ts @@ -26,9 +26,10 @@ process.env.API_KEY_SECRET = "test-quota-usage-secret"; process.env.QUOTA_STORE_DRIVER = "sqlite"; const core = await import("../../src/lib/db/core.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updateSettings } = await import("@/lib/db/settings"); +const { createPool, upsertAllocations } = await import("@/lib/db/quotaPools"); +const localDb = { updateSettings }; const compliance = await import("../../src/lib/compliance/index.ts"); -const { createPool, upsertAllocations } = localDb; const { getSqliteQuotaStore } = await import("../../src/lib/quota/sqliteQuotaStore.ts"); const { resetQuotaStoreSingleton } = await import("../../src/lib/quota/QuotaStore.ts"); const poolsRoute = await import("../../src/app/api/quota/pools/route.ts"); diff --git a/tests/integration/quota-preview.test.ts b/tests/integration/quota-preview.test.ts index 867d95ba7da5..49a75f26bd99 100644 --- a/tests/integration/quota-preview.test.ts +++ b/tests/integration/quota-preview.test.ts @@ -25,9 +25,10 @@ process.env.API_KEY_SECRET = "test-quota-preview-secret"; process.env.QUOTA_STORE_DRIVER = "sqlite"; const core = await import("../../src/lib/db/core.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updateSettings } = await import("@/lib/db/settings"); +const { createPool, upsertAllocations } = await import("@/lib/db/quotaPools"); +const localDb = { updateSettings }; const compliance = await import("../../src/lib/compliance/index.ts"); -const { createPool, upsertAllocations } = localDb; const { getSqliteQuotaStore } = await import("../../src/lib/quota/sqliteQuotaStore.ts"); const { resetQuotaStoreSingleton } = await import("../../src/lib/quota/QuotaStore.ts"); const previewRoute = await import("../../src/app/api/quota/preview/route.ts"); diff --git a/tests/integration/quota-store-settings.test.ts b/tests/integration/quota-store-settings.test.ts index aceaaca3e3fb..833b8389c8d5 100644 --- a/tests/integration/quota-store-settings.test.ts +++ b/tests/integration/quota-store-settings.test.ts @@ -26,7 +26,8 @@ delete process.env.QUOTA_STORE_REDIS_URL; process.env.QUOTA_STORE_DRIVER = "sqlite"; const core = await import("../../src/lib/db/core.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updateSettings } = await import("@/lib/db/settings"); +const localDb = { updateSettings }; const compliance = await import("../../src/lib/compliance/index.ts"); const { resetQuotaStoreSingleton } = await import("../../src/lib/quota/QuotaStore.ts"); const settingsRoute = await import("../../src/app/api/settings/quota-store/route.ts"); diff --git a/tests/integration/traffic-inspector-hosts.test.ts b/tests/integration/traffic-inspector-hosts.test.ts index f6efcc57a66c..f493f8edac23 100644 --- a/tests/integration/traffic-inspector-hosts.test.ts +++ b/tests/integration/traffic-inspector-hosts.test.ts @@ -16,7 +16,6 @@ process.env.DATA_DIR = TEST_DATA_DIR; // Boot DB so migrations run const { resetDbInstance } = await import("../../src/lib/db/core.ts"); -const localDb = await import("../../src/lib/localDb.ts"); const hostsRoute = await import("../../src/app/api/tools/traffic-inspector/hosts/route.ts"); const hostDetailRoute = diff --git a/tests/unit/acp-agents-route.test.ts b/tests/unit/acp-agents-route.test.ts index a64a2f0f9d94..b50032d9c110 100644 --- a/tests/unit/acp-agents-route.test.ts +++ b/tests/unit/acp-agents-route.test.ts @@ -10,7 +10,8 @@ process.env.DATA_DIR = TEST_DATA_DIR; process.env.API_KEY_SECRET = process.env.API_KEY_SECRET || "acp-agents-route-api-key-secret"; const core = await import("../../src/lib/db/core.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updateSettings } = await import("@/lib/db/settings"); +const localDb = { updateSettings }; const routeModule = await import("../../src/app/api/acp/agents/route.ts"); const ORIGINAL_INITIAL_PASSWORD = process.env.INITIAL_PASSWORD; diff --git a/tests/unit/api-auth.test.ts b/tests/unit/api-auth.test.ts index a472e6db4d5a..9784a688a140 100644 --- a/tests/unit/api-auth.test.ts +++ b/tests/unit/api-auth.test.ts @@ -10,7 +10,8 @@ process.env.DATA_DIR = TEST_DATA_DIR; process.env.API_KEY_SECRET = "test-api-key-secret"; const core = await import("../../src/lib/db/core.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updateSettings } = await import("@/lib/db/settings"); +const localDb = { updateSettings }; const apiKeysDb = await import("../../src/lib/db/apiKeys.ts"); const apiAuth = await import("../../src/shared/utils/apiAuth.ts"); const { requireManagementAuth } = await import("../../src/lib/api/requireManagementAuth.ts"); diff --git a/tests/unit/api-key-usage-limits.test.ts b/tests/unit/api-key-usage-limits.test.ts index 7221cc883c7a..8fe8dfcb33ec 100644 --- a/tests/unit/api-key-usage-limits.test.ts +++ b/tests/unit/api-key-usage-limits.test.ts @@ -9,7 +9,8 @@ process.env.DATA_DIR = TEST_DATA_DIR; process.env.API_KEY_SECRET = process.env.API_KEY_SECRET || "usage-limit-test-secret"; const core = await import("../../src/lib/db/core.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updatePricing } = await import("@/lib/db/settings"); +const localDb = { updatePricing }; const apiKeysDb = await import("../../src/lib/db/apiKeys.ts"); const usageHistory = await import("../../src/lib/usage/usageHistory.ts"); const usageLimits = await import("../../src/lib/usage/apiKeyUsageLimits.ts"); diff --git a/tests/unit/api-keys-create-no-hang-6570.test.ts b/tests/unit/api-keys-create-no-hang-6570.test.ts index 3a5d5bd53628..034107256eec 100644 --- a/tests/unit/api-keys-create-no-hang-6570.test.ts +++ b/tests/unit/api-keys-create-no-hang-6570.test.ts @@ -27,7 +27,8 @@ process.env.CLOUD_URL = "http://cloud.example"; const core = await import("../../src/lib/db/core.ts"); const apiKeysDb = await import("../../src/lib/db/apiKeys.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updateSettings, getSettings } = await import("@/lib/db/settings"); +const localDb = { updateSettings, getSettings }; const listRoute = await import("../../src/app/api/keys/route.ts"); async function resetStorage() { diff --git a/tests/unit/api-models-v1-models-mismatch-10615.test.ts b/tests/unit/api-models-v1-models-mismatch-10615.test.ts index f41976e3464d..aed82060e8fa 100644 --- a/tests/unit/api-models-v1-models-mismatch-10615.test.ts +++ b/tests/unit/api-models-v1-models-mismatch-10615.test.ts @@ -9,7 +9,8 @@ process.env.DATA_DIR = TEST_DATA_DIR; const core = await import("../../src/lib/db/core.ts"); const providersDb = await import("../../src/lib/db/providers.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { replaceSyncedAvailableModelsForConnection } = await import("@/lib/db/models"); +const localDb = { replaceSyncedAvailableModelsForConnection }; const modelsRoute = await import("../../src/app/api/models/route.ts"); const v1ModelsCatalog = await import("../../src/app/api/v1/models/catalog.ts"); diff --git a/tests/unit/auto-combo-hidden-models-4558.test.ts b/tests/unit/auto-combo-hidden-models-4558.test.ts index f66da030fd20..03e0e9292e84 100644 --- a/tests/unit/auto-combo-hidden-models-4558.test.ts +++ b/tests/unit/auto-combo-hidden-models-4558.test.ts @@ -33,7 +33,7 @@ const { getModelIsHidden, mergeModelCompatOverride, updateCustomModel, -} = await import("../../src/lib/localDb.ts"); +} = await import("@/lib/db/models"); const { resetDbInstance } = await import("../../src/lib/db/core.ts"); before(() => { diff --git a/tests/unit/batch-deletion.test.ts b/tests/unit/batch-deletion.test.ts index 4a0043e7217b..1149c07730d3 100644 --- a/tests/unit/batch-deletion.test.ts +++ b/tests/unit/batch-deletion.test.ts @@ -1,14 +1,7 @@ import { describe, it } from "node:test"; import assert from "node:assert"; -import { - createFile, - createBatch, - getBatch, - deleteBatch, - deleteCompletedBatches, - getFile, - deleteFile, -} from "@/lib/localDb"; +import { createFile, getFile, deleteFile } from "@/lib/db/files"; +import { createBatch, getBatch, deleteBatch, deleteCompletedBatches } from "@/lib/db/batches"; describe("deleteBatch", () => { it("should delete a single batch and its associated files", () => { diff --git a/tests/unit/batch-file-download.test.ts b/tests/unit/batch-file-download.test.ts index 5cc891cb43a6..f67574a5097c 100644 --- a/tests/unit/batch-file-download.test.ts +++ b/tests/unit/batch-file-download.test.ts @@ -18,7 +18,8 @@ process.env.DATA_DIR = TEST_DATA_DIR; process.env.API_KEY_SECRET = "test-secret-file-dl"; const core = await import("../../src/lib/db/core.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { createFile, deleteFile } = await import("@/lib/db/files"); +const localDb = { createFile, deleteFile }; const fileContentRoute = await import("../../src/app/api/files/[id]/content/route.ts"); async function resetStorage() { diff --git a/tests/unit/batch-processor.test.ts b/tests/unit/batch-processor.test.ts index 243fa6f90408..f863f1b8ce9c 100644 --- a/tests/unit/batch-processor.test.ts +++ b/tests/unit/batch-processor.test.ts @@ -12,7 +12,25 @@ process.env.API_KEY_SECRET = "test-secret"; // We import these as modules to allow mocking const core = await import("@/lib/db/core.ts"); -const localDb = await import("@/lib/localDb"); +const { createFile, getFileContent } = await import("@/lib/db/files"); +const { + createBatch, + ensureBatchItemCheckpoints, + getBatch, + markBatchItemProcessing, + markBatchItemResult, + updateBatch, +} = await import("@/lib/db/batches"); +const localDb = { + createFile, + getFileContent, + createBatch, + ensureBatchItemCheckpoints, + getBatch, + markBatchItemProcessing, + markBatchItemResult, + updateBatch, +}; const { dispatch } = await import("@/lib/batches/dispatch"); const batchProcessor = await import("../../open-sse/services/batchProcessor.ts"); const { waitForAllBatches, getCachedHeaders, resetCachedHeaders } = batchProcessor; diff --git a/tests/unit/batch_api.test.ts b/tests/unit/batch_api.test.ts index 357e084bf66c..f010b3a79c29 100644 --- a/tests/unit/batch_api.test.ts +++ b/tests/unit/batch_api.test.ts @@ -8,22 +8,18 @@ const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-batch-api process.env.DATA_DIR = TEST_DATA_DIR; process.env.API_KEY_SECRET = process.env.API_KEY_SECRET || "test-secret-123"; +const { createFile, getFileContent, getFile, listFiles, formatFileResponse, deleteFile } = + await import("@/lib/db/files"); const { - createFile, createBatch, getBatch, - getFileContent, updateBatch, - createProviderConnection, - createApiKey, - getFile, - listFiles, - formatFileResponse, - deleteFile, getTerminalBatches, ensureBatchItemCheckpoints, markBatchItemResult, -} = await import("../../src/lib/localDb.ts"); +} = await import("@/lib/db/batches"); +const { createProviderConnection } = await import("@/lib/db/providers"); +const { createApiKey } = await import("@/lib/db/apiKeys"); const { getDbInstance } = await import("../../src/lib/db/core.ts"); const { initBatchProcessor, @@ -580,7 +576,7 @@ test("List batches pagination and response format", async () => { const batchIds = batchOrder.map((entry) => entry.id); // 2. Test listBatches logic (direct DB call) - const { listBatches } = await import("../../src/lib/localDb"); + const { listBatches } = await import("@/lib/db/batches"); const allBatches = listBatches(apiKey.id, 10); assert.strictEqual(allBatches.length, 5); assert.strictEqual(allBatches[0].id, batchIds[0]); diff --git a/tests/unit/batch_results.test.ts b/tests/unit/batch_results.test.ts index 35eabb0c03bf..8f2d1f4e5763 100644 --- a/tests/unit/batch_results.test.ts +++ b/tests/unit/batch_results.test.ts @@ -8,14 +8,10 @@ const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-batch-res process.env.DATA_DIR = TEST_DATA_DIR; process.env.API_KEY_SECRET = process.env.API_KEY_SECRET || "test-secret-123"; -const { - createFile, - createBatch, - getBatch, - getFileContent, - createProviderConnection, - createApiKey, -} = await import("../../src/lib/localDb.ts"); +const { createFile, getFileContent } = await import("@/lib/db/files"); +const { createBatch, getBatch } = await import("@/lib/db/batches"); +const { createProviderConnection } = await import("@/lib/db/providers"); +const { createApiKey } = await import("@/lib/db/apiKeys"); const { initBatchProcessor, stopBatchProcessor, waitForAllBatches, processPendingBatches } = await import("../../open-sse/services/batchProcessor.ts"); @@ -127,7 +123,7 @@ test("Batch processor produces output file for successful items", async () => { assert.ok(obj.response && typeof obj.response.status_code === "number"); // Output file should have an expiration timestamp set (30 days default) - const { getFile } = await import("../../src/lib/localDb.ts"); + const { getFile } = await import("@/lib/db/files"); const fileRow = getFile(currentBatch.outputFileId!); assert.ok(fileRow?.expiresAt && typeof fileRow.expiresAt === "number"); } diff --git a/tests/unit/cli-tools-crush.test.ts b/tests/unit/cli-tools-crush.test.ts index 4dbdbfde5e21..7e58ce77f69b 100644 --- a/tests/unit/cli-tools-crush.test.ts +++ b/tests/unit/cli-tools-crush.test.ts @@ -56,7 +56,8 @@ process.env.API_KEY_SECRET = "test-api-key-secret-crush"; process.env.JWT_SECRET = "test-jwt-secret-crush"; const core = await import("../../src/lib/db/core.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updateSettings } = await import("@/lib/db/settings"); +const localDb = { updateSettings }; const { GET, POST, DELETE } = await import("../../src/app/api/cli-tools/crush-settings/route.ts"); diff --git a/tests/unit/cloud-write-auth.test.ts b/tests/unit/cloud-write-auth.test.ts index 68c1ce3ca92a..9c486d244ea6 100644 --- a/tests/unit/cloud-write-auth.test.ts +++ b/tests/unit/cloud-write-auth.test.ts @@ -20,7 +20,19 @@ type ProviderConnectionRecord = { }; const core = await import("../../src/lib/db/core.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { resetApiKeyState, createApiKey } = await import("@/lib/db/apiKeys"); +const { updateSettings } = await import("@/lib/db/settings"); +const { createProviderConnection, getProviderConnections } = await import("@/lib/db/providers"); +const { setModelAlias, getModelAliases } = await import("@/lib/db/models"); +const localDb = { + resetApiKeyState, + updateSettings, + createApiKey, + createProviderConnection, + getProviderConnections, + setModelAlias, + getModelAliases, +}; const credentialsRoute = await import("../../src/app/api/cloud/credentials/update/route.ts"); const aliasRoute = await import("../../src/app/api/cloud/models/alias/route.ts"); diff --git a/tests/unit/evals-route.test.ts b/tests/unit/evals-route.test.ts index c9e5b83e8c13..399ddd157152 100644 --- a/tests/unit/evals-route.test.ts +++ b/tests/unit/evals-route.test.ts @@ -17,7 +17,9 @@ interface EvalsRoutePayload { } const core = await import("../../src/lib/db/core.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { resetApiKeyState, createApiKey } = await import("@/lib/db/apiKeys"); +const { saveCustomEvalSuite, saveEvalRun } = await import("@/lib/db/evals"); +const localDb = { resetApiKeyState, createApiKey, saveCustomEvalSuite, saveEvalRun }; const evalsRoute = await import("../../src/app/api/evals/route.ts"); const evalSuitesRoute = await import("../../src/app/api/evals/suites/route.ts"); const evalSuiteByIdRoute = await import("../../src/app/api/evals/suites/[suiteId]/route.ts"); diff --git a/tests/unit/file-deletion.test.ts b/tests/unit/file-deletion.test.ts index e74f480ef16c..7553d57eb8b1 100644 --- a/tests/unit/file-deletion.test.ts +++ b/tests/unit/file-deletion.test.ts @@ -8,8 +8,8 @@ const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-file-dele const ORIGINAL_DATA_DIR = process.env.DATA_DIR; process.env.DATA_DIR = TEST_DATA_DIR; -const { createFile, deleteFile, listFiles, createBatch, getBatch, updateBatch } = - await import("@/lib/localDb"); +const { createFile, deleteFile, listFiles } = await import("@/lib/db/files"); +const { createBatch, getBatch, updateBatch } = await import("@/lib/db/batches"); const { getDbInstance, resetDbInstance } = await import("@/lib/db/core"); after(() => { diff --git a/tests/unit/file-expiration-policy.test.ts b/tests/unit/file-expiration-policy.test.ts index 11082877308d..94fc2adb1957 100644 --- a/tests/unit/file-expiration-policy.test.ts +++ b/tests/unit/file-expiration-policy.test.ts @@ -1,6 +1,6 @@ import { describe, it, before, afterEach } from "node:test"; import assert from "node:assert"; -import { createFile, getFile, listFiles, deleteFile } from "@/lib/localDb"; +import { createFile, getFile, listFiles, deleteFile } from "@/lib/db/files"; import { getDbInstance } from "@/lib/db/core.ts"; describe("File Expiration Policy", () => { diff --git a/tests/unit/fixes-p1.test.ts b/tests/unit/fixes-p1.test.ts index 93d76898d20c..8da6e7058c6c 100644 --- a/tests/unit/fixes-p1.test.ts +++ b/tests/unit/fixes-p1.test.ts @@ -24,7 +24,8 @@ const backupDb = await import("../../src/lib/db/backup.ts"); const providersDb = await import("../../src/lib/db/providers.ts"); const combosDb = await import("../../src/lib/db/combos.ts"); const settingsDb = await import("../../src/lib/db/settings.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { createProxy } = await import("@/lib/db/proxies"); +const localDb = { createProxy }; const tokenRefresh = await import("../../open-sse/services/tokenRefresh.ts"); const proxyFetch = await import("../../open-sse/utils/proxyFetch.ts"); const proxyDispatcher = await import("../../open-sse/utils/proxyDispatcher.ts"); diff --git a/tests/unit/hidden-models-leak-v1-models-11300.test.ts b/tests/unit/hidden-models-leak-v1-models-11300.test.ts index 1de38b5b6f11..f9ae10780e1d 100644 --- a/tests/unit/hidden-models-leak-v1-models-11300.test.ts +++ b/tests/unit/hidden-models-leak-v1-models-11300.test.ts @@ -31,7 +31,7 @@ process.env.DATA_DIR = TEST_DATA_DIR; const core = await import("../../src/lib/db/core.ts"); const providersDb = await import("../../src/lib/db/providers.ts"); const modelsDb = await import("../../src/lib/db/models.ts"); -const { mergeModelCompatOverride } = await import("../../src/lib/localDb.ts"); +const { mergeModelCompatOverride } = await import("@/lib/db/models"); const v1ModelsCatalog = await import("../../src/app/api/v1/models/catalog.ts"); async function resetStorage() { diff --git a/tests/unit/live-ws-public-url.test.ts b/tests/unit/live-ws-public-url.test.ts index 16fd8f7bb6b8..23f547cfce38 100644 --- a/tests/unit/live-ws-public-url.test.ts +++ b/tests/unit/live-ws-public-url.test.ts @@ -14,7 +14,8 @@ process.env.API_KEY_SECRET = process.env.API_KEY_SECRET || "test-live-ws-public- const core = await import("../../src/lib/db/core.ts"); const apiKeysDb = await import("../../src/lib/db/apiKeys.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updateSettings } = await import("@/lib/db/settings"); +const localDb = { updateSettings }; const wsRoute = await import("../../src/app/api/v1/ws/route.ts"); function resetStorage() { diff --git a/tests/unit/managed-available-models.test.ts b/tests/unit/managed-available-models.test.ts index db79cbcab7e7..0b5475de935d 100644 --- a/tests/unit/managed-available-models.test.ts +++ b/tests/unit/managed-available-models.test.ts @@ -9,7 +9,8 @@ process.env.DATA_DIR = TEST_DATA_DIR; const core = await import("../../src/lib/db/core.ts"); const modelsDb = await import("../../src/lib/db/models.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { getModelAliases, setModelAlias } = await import("@/lib/db/models"); +const localDb = { getModelAliases, setModelAlias }; const { compatibleProviderSupportsModelImport, getCompatibleFallbackModels } = await import("../../src/lib/providers/managedAvailableModels.ts"); const { diff --git a/tests/unit/managed-model-import.test.ts b/tests/unit/managed-model-import.test.ts index a3f1e1942db7..acbae44a6f9d 100644 --- a/tests/unit/managed-model-import.test.ts +++ b/tests/unit/managed-model-import.test.ts @@ -9,7 +9,8 @@ process.env.DATA_DIR = TEST_DATA_DIR; const core = await import("../../src/lib/db/core.ts"); const modelsDb = await import("../../src/lib/db/models.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { getModelAliases, setModelAlias } = await import("@/lib/db/models"); +const localDb = { getModelAliases, setModelAlias }; const { importManagedModels } = await import("../../src/lib/providerModels/managedModelImport.ts"); const { mergeProviderModelListing } = await import("../../src/lib/providers/mergeProviderModelListing.ts"); diff --git a/tests/unit/model-alias-route.test.ts b/tests/unit/model-alias-route.test.ts index 6bf42b4db322..a82562eaaa85 100644 --- a/tests/unit/model-alias-route.test.ts +++ b/tests/unit/model-alias-route.test.ts @@ -12,7 +12,8 @@ process.env.JWT_SECRET = process.env.JWT_SECRET || "model-alias-route-jwt"; const core = await import("../../src/lib/db/core.ts"); const modelsDb = await import("../../src/lib/db/models.ts"); const providersDb = await import("../../src/lib/db/providers.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updateSettings } = await import("@/lib/db/settings"); +const localDb = { updateSettings }; const route = await import("../../src/app/api/models/alias/route.ts"); const catalogRoute = await import("../../src/app/api/models/catalog/route.ts"); const v1Catalog = await import("../../src/app/api/v1/models/catalog.ts"); diff --git a/tests/unit/model-aliases-settings-route-selfheal.test.ts b/tests/unit/model-aliases-settings-route-selfheal.test.ts index 2655a43ecd8c..da994f1487d7 100644 --- a/tests/unit/model-aliases-settings-route-selfheal.test.ts +++ b/tests/unit/model-aliases-settings-route-selfheal.test.ts @@ -22,7 +22,8 @@ process.env.DATA_DIR = TEST_DATA_DIR; process.env.JWT_SECRET = process.env.JWT_SECRET || "model-aliases-selfheal-jwt"; const core = await import("../../src/lib/db/core.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updateSettings } = await import("@/lib/db/settings"); +const localDb = { updateSettings }; const modelDeprecation = await import("../../open-sse/services/modelDeprecation.ts"); const route = await import("../../src/app/api/settings/model-aliases/route.ts"); diff --git a/tests/unit/model-sync-route.test.ts b/tests/unit/model-sync-route.test.ts index 73f199a1ec4b..d4a144561649 100644 --- a/tests/unit/model-sync-route.test.ts +++ b/tests/unit/model-sync-route.test.ts @@ -12,7 +12,9 @@ if (!process.env.API_KEY_SECRET) { } const core = await import("../../src/lib/db/core.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updateSettings } = await import("@/lib/db/settings"); +const { setModelAlias, getModelAliases } = await import("@/lib/db/models"); +const localDb = { updateSettings, setModelAlias, getModelAliases }; const apiKeysDb = await import("../../src/lib/db/apiKeys.ts"); const providersDb = await import("../../src/lib/db/providers.ts"); const modelsDb = await import("../../src/lib/db/models.ts"); diff --git a/tests/unit/oidc-callback.test.ts b/tests/unit/oidc-callback.test.ts index 0659edf58c31..bccf32369497 100644 --- a/tests/unit/oidc-callback.test.ts +++ b/tests/unit/oidc-callback.test.ts @@ -15,7 +15,8 @@ process.env.JWT_SECRET = "test-jwt-secret-for-oidc-callback"; // @ts-ignore - intentional for test harness timing (see note at top) const core = await import("../../src/lib/db/core.ts"); // @ts-ignore - intentional for test harness timing -const localDb = await import("../../src/lib/localDb.ts"); +const { updateSettings } = await import("@/lib/db/settings"); +const localDb = { updateSettings }; // @ts-ignore - intentional for test harness timing const callbackRoute = await import("../../src/app/api/auth/oidc/callback/route.ts"); diff --git a/tests/unit/oidc-login-state.test.ts b/tests/unit/oidc-login-state.test.ts index 341ba53932c0..e06a26bfab35 100644 --- a/tests/unit/oidc-login-state.test.ts +++ b/tests/unit/oidc-login-state.test.ts @@ -14,7 +14,8 @@ process.env.JWT_SECRET = "test-jwt-secret-for-oidc-login"; // @ts-ignore - intentional for test harness timing (see note at top) const core = await import("../../src/lib/db/core.ts"); // @ts-ignore - intentional for test harness timing -const localDb = await import("../../src/lib/localDb.ts"); +const { updateSettings } = await import("@/lib/db/settings"); +const localDb = { updateSettings }; // @ts-ignore - intentional for test harness timing const loginRoute = await import("../../src/app/api/auth/oidc/login/route.ts"); diff --git a/tests/unit/persist-429-cooldown-account-fallback.test.ts b/tests/unit/persist-429-cooldown-account-fallback.test.ts index 82eeab204197..cc96643f6ef4 100644 --- a/tests/unit/persist-429-cooldown-account-fallback.test.ts +++ b/tests/unit/persist-429-cooldown-account-fallback.test.ts @@ -34,7 +34,8 @@ const core = await import("../../src/lib/db/core.ts"); const providersDb = await import("../../src/lib/db/providers.ts"); import { applyErrorState, resetAccountState } from "../../open-sse/services/accountFallback.ts"; -import { markConnectionRateLimitedUntil, clearConnectionRateLimit } from "../../src/lib/localDb.ts"; +const { markConnectionRateLimitedUntil, clearConnectionRateLimit } = + await import("@/lib/db/providers"); test.after(() => { core.resetDbInstance(); diff --git a/tests/unit/provider-window-costs.test.ts b/tests/unit/provider-window-costs.test.ts index 46f42f5e7cd5..8abc7a5a56f4 100644 --- a/tests/unit/provider-window-costs.test.ts +++ b/tests/unit/provider-window-costs.test.ts @@ -11,7 +11,8 @@ process.env.API_KEY_SECRET = "provider-window-costs-test-secret"; const core = await import("../../src/lib/db/core.ts"); const apiKeys = await import("../../src/lib/db/apiKeys.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updatePricing } = await import("@/lib/db/settings"); +const localDb = { updatePricing }; const providerLimits = await import("../../src/lib/db/providerLimits.ts"); const usageHistory = await import("../../src/lib/usage/usageHistory.ts"); const costRules = await import("../../src/domain/costRules.ts"); diff --git a/tests/unit/proxy-egress-route-summary.test.ts b/tests/unit/proxy-egress-route-summary.test.ts index c96862fd6e3f..fb1003bc880d 100644 --- a/tests/unit/proxy-egress-route-summary.test.ts +++ b/tests/unit/proxy-egress-route-summary.test.ts @@ -17,7 +17,8 @@ process.env.API_KEY_SECRET = "test-api-key-secret"; const core = await import("../../src/lib/db/core.ts"); const proxyLogger = await import("../../src/lib/proxyLogger.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updateSettings } = await import("@/lib/db/settings"); +const localDb = { updateSettings }; const apiKeysDb = await import("../../src/lib/db/apiKeys.ts"); const route = await import("../../src/app/api/settings/proxies/egress/route.ts"); diff --git a/tests/unit/replace-custom-models-preserve-hidden-5086.test.ts b/tests/unit/replace-custom-models-preserve-hidden-5086.test.ts index 6ee7ff114afa..9c8e3b31aefb 100644 --- a/tests/unit/replace-custom-models-preserve-hidden-5086.test.ts +++ b/tests/unit/replace-custom-models-preserve-hidden-5086.test.ts @@ -26,7 +26,7 @@ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-test-replace-cus process.env.DATA_DIR = tmpDir; const { replaceCustomModels, mergeModelCompatOverride, getModelIsHidden, getModelCompatOverrides } = - await import("../../src/lib/localDb.ts"); + await import("@/lib/db/models"); const { resetDbInstance } = await import("../../src/lib/db/core.ts"); before(() => { diff --git a/tests/unit/route-edge-coverage.test.ts b/tests/unit/route-edge-coverage.test.ts index 80ab12de0278..c03e2b0b8f9a 100644 --- a/tests/unit/route-edge-coverage.test.ts +++ b/tests/unit/route-edge-coverage.test.ts @@ -15,7 +15,10 @@ const apiKeysDb = await import("../../src/lib/db/apiKeys.ts"); const compliance = await import("../../src/lib/compliance/index.ts"); const providersDb = await import("../../src/lib/db/providers.ts"); const modelsDb = await import("../../src/lib/db/models.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updateSettings } = await import("@/lib/db/settings"); +const { createProxy, assignProxyToScope } = await import("@/lib/db/proxies"); +const { invalidateDbCache } = await import("@/lib/db/readCache"); +const localDb = { updateSettings, createProxy, assignProxyToScope, invalidateDbCache }; const listKeysRoute = await import("../../src/app/api/keys/route.ts"); const settingsProxyRoute = await import("../../src/app/api/settings/proxy/route.ts"); const managementProxiesRoute = await import("../../src/app/api/v1/management/proxies/route.ts"); diff --git a/tests/unit/specialty-model-hidden-openrouter-9293.test.ts b/tests/unit/specialty-model-hidden-openrouter-9293.test.ts index 360dfe3758d3..fc4d8ff4ff71 100644 --- a/tests/unit/specialty-model-hidden-openrouter-9293.test.ts +++ b/tests/unit/specialty-model-hidden-openrouter-9293.test.ts @@ -26,7 +26,7 @@ process.env.API_KEY_SECRET = process.env.API_KEY_SECRET || "9293-test-secret"; const core = await import("../../src/lib/db/core.ts"); const providersDb = await import("../../src/lib/db/providers.ts"); const apiKeysDb = await import("../../src/lib/db/apiKeys.ts"); -const { mergeModelCompatOverride, getModelIsHidden } = await import("../../src/lib/localDb.ts"); +const { mergeModelCompatOverride, getModelIsHidden } = await import("@/lib/db/models"); const v1ModelsCatalog = await import("../../src/app/api/v1/models/catalog.ts"); async function resetStorage() { diff --git a/tests/unit/sync-routes.test.ts b/tests/unit/sync-routes.test.ts index f863c04f6b2b..acaf60ff6b28 100644 --- a/tests/unit/sync-routes.test.ts +++ b/tests/unit/sync-routes.test.ts @@ -20,7 +20,8 @@ const compliance = await import("../../src/lib/compliance/index.ts"); const syncTokensRoute = await import("../../src/app/api/sync/tokens/route.ts"); const syncTokenByIdRoute = await import("../../src/app/api/sync/tokens/[id]/route.ts"); const syncBundleRoute = await import("../../src/app/api/sync/bundle/route.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updateSettings } = await import("@/lib/db/settings"); +const localDb = { updateSettings }; function resetStorage() { apiKeysDb.resetApiKeyState(); diff --git a/tests/unit/synced-model-delete-resync.test.ts b/tests/unit/synced-model-delete-resync.test.ts index 4aaf79b1671d..84da271a0e5c 100644 --- a/tests/unit/synced-model-delete-resync.test.ts +++ b/tests/unit/synced-model-delete-resync.test.ts @@ -14,7 +14,7 @@ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-test-synced-del- process.env.DATA_DIR = tmpDir; const { replaceSyncedAvailableModelsForConnection, getSyncedAvailableModels } = - await import("../../src/lib/localDb.ts"); + await import("@/lib/db/models"); const { resetDbInstance } = await import("../../src/lib/db/core.ts"); before(() => { @@ -38,7 +38,7 @@ test("a deleted synced model is restored when upstream advertises it again", asy let synced = (await getSyncedAvailableModels(provider)).map((m) => m.id); assert.ok(synced.includes("model-del"), "both models present after first sync"); - const { removeSyncedAvailableModel } = await import("../../src/lib/localDb.ts"); + const { removeSyncedAvailableModel } = await import("@/lib/db/models"); assert.equal(await removeSyncedAvailableModel(provider, "model-del"), true); synced = (await getSyncedAvailableModels(provider)).map((m) => m.id); assert.ok(!synced.includes("model-del"), "delete removes the current synced row"); diff --git a/tests/unit/synced-model-hide-persist-3782.test.ts b/tests/unit/synced-model-hide-persist-3782.test.ts index e85ef0973b09..3232eb061595 100644 --- a/tests/unit/synced-model-hide-persist-3782.test.ts +++ b/tests/unit/synced-model-hide-persist-3782.test.ts @@ -24,7 +24,7 @@ const { getSyncedAvailableModels, mergeModelCompatOverride, getModelIsHidden, -} = await import("../../src/lib/localDb.ts"); +} = await import("@/lib/db/models"); const { resetDbInstance } = await import("../../src/lib/db/core.ts"); before(() => { diff --git a/tests/unit/usage-account-analytics-route.test.ts b/tests/unit/usage-account-analytics-route.test.ts index f05a40342d0d..c8eef83dde5b 100644 --- a/tests/unit/usage-account-analytics-route.test.ts +++ b/tests/unit/usage-account-analytics-route.test.ts @@ -9,7 +9,8 @@ process.env.DATA_DIR = TEST_DATA_DIR; process.env.API_KEY_SECRET = "test-account-analytics-secret"; const core = await import("../../src/lib/db/core.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updatePricing } = await import("@/lib/db/settings"); +const localDb = { updatePricing }; const providersDb = await import("../../src/lib/db/providers.ts"); const usageHistory = await import("../../src/lib/usage/usageHistory.ts"); const { resolveUsageAccountIdentity } = await import("../../src/lib/usage/accountIdentity.ts"); diff --git a/tests/unit/usage-analytics-route.test.ts b/tests/unit/usage-analytics-route.test.ts index ed897b6733c2..d6e41df005f6 100644 --- a/tests/unit/usage-analytics-route.test.ts +++ b/tests/unit/usage-analytics-route.test.ts @@ -10,7 +10,8 @@ const ORIGINAL_API_KEY_SECRET = process.env.API_KEY_SECRET; process.env.API_KEY_SECRET = "test-usage-analytics-secret"; const core = await import("../../src/lib/db/core.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updatePricing } = await import("@/lib/db/settings"); +const localDb = { updatePricing }; const apiKeysDb = await import("../../src/lib/db/apiKeys.ts"); const providersDb = await import("../../src/lib/db/providers.ts"); const usageHistory = await import("../../src/lib/usage/usageHistory.ts"); diff --git a/tests/unit/usage-analytics.test.ts b/tests/unit/usage-analytics.test.ts index e0c1d2509225..89cc77ae8eb8 100644 --- a/tests/unit/usage-analytics.test.ts +++ b/tests/unit/usage-analytics.test.ts @@ -8,7 +8,8 @@ const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-usage-ana process.env.DATA_DIR = TEST_DATA_DIR; const core = await import("../../src/lib/db/core.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updatePricing } = await import("@/lib/db/settings"); +const localDb = { updatePricing }; const apiKeysDb = await import("../../src/lib/db/apiKeys.ts"); const providersDb = await import("../../src/lib/db/providers.ts"); const usageHistory = await import("../../src/lib/usage/usageHistory.ts"); diff --git a/tests/unit/v1-ws-route.test.ts b/tests/unit/v1-ws-route.test.ts index 55e68c165d20..87179ccdb1ce 100644 --- a/tests/unit/v1-ws-route.test.ts +++ b/tests/unit/v1-ws-route.test.ts @@ -13,7 +13,8 @@ process.env.API_KEY_SECRET = process.env.API_KEY_SECRET || "test-v1-ws-route-sec const core = await import("../../src/lib/db/core.ts"); const apiKeysDb = await import("../../src/lib/db/apiKeys.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updateSettings } = await import("@/lib/db/settings"); +const localDb = { updateSettings }; const wsRoute = await import("../../src/app/api/v1/ws/route.ts"); function resetStorage() { From 2b8d3a8291094bb03c0d1c9d99d07262c8b896fb Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza Date: Sat, 29 Aug 2026 17:37:26 -0300 Subject: [PATCH 05/22] fix(radar): restore D12 public boundary (#12057) --- open-sse/utils/imageNormalize.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/open-sse/utils/imageNormalize.ts b/open-sse/utils/imageNormalize.ts index 0f528874d93a..ccbbaf6364c4 100644 --- a/open-sse/utils/imageNormalize.ts +++ b/open-sse/utils/imageNormalize.ts @@ -1,7 +1,7 @@ /** * Optional-sharp image normalization. * - * Rationale (migrated from freellmapi `server/src/lib/image-normalize.ts:40-58`): + * Rationale: * OpenAI resizes images to a long-edge cap of 2048px server-side, Anthropic applies * a similar cap. Downscaling client-side before upload reduces tokens/latency without * changing model behavior. `sharp` is loaded via dynamic import so that a platform From 6c33ef033aa76cb55aff20a3319226b4dc6b39c7 Mon Sep 17 00:00:00 2001 From: WebPerson Date: Sat, 29 Aug 2026 14:45:06 -0500 Subject: [PATCH 06/22] fix(tests): update quota-key-models route test to assert new direct imports The route was migrated off the @/lib/localDb barrel to import getApiKeyById from @/lib/db/apiKeys and getCombos from @/lib/db/combos (Phase 2, #11795). The source-scan test still asserted the old barrel path, failing CI shard 1/4 on PR #12051. Assert the new direct-import sources instead. --- tests/unit/quota-key-models-route.test.ts | 46 ++++++++++------------- 1 file changed, 20 insertions(+), 26 deletions(-) diff --git a/tests/unit/quota-key-models-route.test.ts b/tests/unit/quota-key-models-route.test.ts index bb71a649ecb6..e1303de99844 100644 --- a/tests/unit/quota-key-models-route.test.ts +++ b/tests/unit/quota-key-models-route.test.ts @@ -46,7 +46,7 @@ const src = readFileSync(ROUTE_PATH, "utf8"); test("quota/keys/[id]/models: imports requireManagementAuth", () => { assert.ok( src.includes("requireManagementAuth"), - "route must import and call requireManagementAuth", + "route must import and call requireManagementAuth" ); }); @@ -66,20 +66,17 @@ test("quota/keys/[id]/models: GET returns early when authError is truthy", () => const getBody = src.slice(getIdx); assert.ok( getBody.includes("if (authError) return authError"), - "GET must return authError immediately — 401 without auth", + "GET must return authError immediately — 401 without auth" ); }); // ── Error sanitization ──────────────────────────────────────────────────────── test("quota/keys/[id]/models: imports buildErrorBody from @omniroute/open-sse/utils/error", () => { - assert.ok( - src.includes("buildErrorBody"), - "route must use buildErrorBody — Hard Rule #12", - ); + assert.ok(src.includes("buildErrorBody"), "route must use buildErrorBody — Hard Rule #12"); assert.ok( src.includes("@omniroute/open-sse/utils/error"), - "route must import buildErrorBody from @omniroute/open-sse/utils/error", + "route must import buildErrorBody from @omniroute/open-sse/utils/error" ); }); @@ -99,43 +96,43 @@ test("quota/keys/[id]/models: does NOT leak raw err.message outside buildErrorBo test("quota/keys/[id]/models: imports resolveQuotaKeyScope from @/lib/quota/quotaKey", () => { assert.ok( src.includes("resolveQuotaKeyScope"), - "route must call resolveQuotaKeyScope to compute pool scope", + "route must call resolveQuotaKeyScope to compute pool scope" ); assert.ok( src.includes("@/lib/quota/quotaKey"), - "route must import resolveQuotaKeyScope from @/lib/quota/quotaKey", + "route must import resolveQuotaKeyScope from @/lib/quota/quotaKey" ); }); test("quota/keys/[id]/models: imports filterModelsToQuotaPools from @/lib/quota/quotaCombos", () => { assert.ok( src.includes("filterModelsToQuotaPools"), - "route must call filterModelsToQuotaPools to filter combo candidates", + "route must call filterModelsToQuotaPools to filter combo candidates" ); assert.ok( src.includes("@/lib/quota/quotaCombos"), - "route must import filterModelsToQuotaPools from @/lib/quota/quotaCombos", + "route must import filterModelsToQuotaPools from @/lib/quota/quotaCombos" ); }); test("quota/keys/[id]/models: passes scope.poolSlugs to filterModelsToQuotaPools", () => { assert.ok( src.includes("scope.poolSlugs"), - "route must pass scope.poolSlugs to filterModelsToQuotaPools (same as catalog.ts approach)", + "route must pass scope.poolSlugs to filterModelsToQuotaPools (same as catalog.ts approach)" ); }); // ── DB imports from localDb ─────────────────────────────────────────────────── -test("quota/keys/[id]/models: imports getApiKeyById from @/lib/localDb", () => { +test("quota/keys/[id]/models: imports getApiKeyById from @/lib/db/apiKeys", () => { assert.ok(src.includes("getApiKeyById"), "route must call getApiKeyById"); assert.ok( - src.includes("@/lib/localDb"), - "route must import from @/lib/localDb (not from @/lib/db/apiKeys directly)", + src.includes("@/lib/db/apiKeys"), + "route must import getApiKeyById from @/lib/db/apiKeys (not from the localDb barrel)" ); }); -test("quota/keys/[id]/models: imports getCombos from @/lib/localDb", () => { +test("quota/keys/[id]/models: imports getCombos from @/lib/db/combos", () => { assert.ok(src.includes("getCombos"), "route must call getCombos to build combo candidates"); }); @@ -144,14 +141,14 @@ test("quota/keys/[id]/models: imports getCombos from @/lib/localDb", () => { test("quota/keys/[id]/models: GET returns { models } shape", () => { assert.ok( src.includes("{ models }") || src.includes("{models}") || src.includes("models:"), - "GET must return { models } in the response body", + "GET must return { models } in the response body" ); }); test("quota/keys/[id]/models: maps filtered combos to model id strings (.map(m => m.id))", () => { assert.ok( src.includes(".map(") && src.includes("m.id"), - "route must map filtered combo entries to their id strings", + "route must map filtered combo entries to their id strings" ); }); @@ -160,7 +157,7 @@ test("quota/keys/[id]/models: maps filtered combos to model id strings (.map(m = test("quota/keys/[id]/models: returns buildErrorBody(404, ...) when key not found", () => { assert.ok( src.includes("buildErrorBody(404"), - "route must use buildErrorBody(404, ...) when the API key is not found", + "route must use buildErrorBody(404, ...) when the API key is not found" ); assert.ok(src.includes("404"), "route must return HTTP 404 on missing key"); }); @@ -170,7 +167,7 @@ test("quota/keys/[id]/models: returns buildErrorBody(404, ...) when key not foun test("quota/keys/[id]/models: has dynamic = 'force-dynamic' export", () => { assert.ok( src.includes('dynamic = "force-dynamic"') || src.includes("dynamic = 'force-dynamic'"), - "route must export dynamic = 'force-dynamic'", + "route must export dynamic = 'force-dynamic'" ); }); @@ -179,22 +176,19 @@ test("quota/keys/[id]/models: has dynamic = 'force-dynamic' export", () => { test("quota/keys/[id]/models: reads id via await params (Next 16 pattern)", () => { assert.ok( src.includes("await params"), - "route must use await params — Next 16 async params pattern", + "route must use await params — Next 16 async params pattern" ); }); test("quota/keys/[id]/models: params typed as Promise<{ id: string }>", () => { assert.ok( src.includes("Promise<") && src.includes("id: string"), - "params type must be Promise<{ id: string }> — matching groups/[id] pattern", + "params type must be Promise<{ id: string }> — matching groups/[id] pattern" ); }); // ── exports GET ─────────────────────────────────────────────────────────────── test("quota/keys/[id]/models: exports GET handler", () => { - assert.ok( - src.includes("export async function GET"), - "route must export the GET handler", - ); + assert.ok(src.includes("export async function GET"), "route must export the GET handler"); }); From 11153954afcef40d483149069f17beb61d8df105 Mon Sep 17 00:00:00 2001 From: WebPerson Date: Sat, 29 Aug 2026 15:55:58 -0500 Subject: [PATCH 07/22] fix(barrel): repair Phase 5 CI gates after deleting the localDb barrel (#11795) The Phase 5 PR (delete src/lib/localDb.ts) broke four gates that still assumed the barrel existed: - check-db-rules: the gate crashed reading the deleted barrel and enforced the obsolete re-export-completeness/no-logic rules. Rewrite it to keep only the raw-SQL gate (Hard Rule #5) and its stale-enforcement; update its unit tests and delete the obsolete INTENTIONALLY_INTERNAL classification regression test. - dead-code: deleting the barrel unmasked genuinely-dead exports in src/lib/db/* (the barrel's blanket re-exports were counting as usage). Remove the 11 zero-reference exports; the remaining 6 (oneproxy) are still referenced by dead files and stay. Prune the now-satisfied eslint suppressions they carried. - docs: ARCHITECTURE.md, CODEBASE_DOCUMENTATION.md and CONTRIBUTION_GOLDEN_PATH.md still referenced src/lib/localDb.ts; update them to describe direct db-module imports. - tests: rebase the stale quota-key-models source-scan test onto the Phase 2 fix (imports now assert @/lib/db/apiKeys + @/lib/db/combos). Verified: typecheck:core green, eslint clean on all changed files, dead-code 414 <= 416 baseline, check-db-rules + check-fabricated-docs gates pass, 81 affected unit tests pass. --- config/quality/eslint-suppressions.json | 10 - docs/architecture/ARCHITECTURE.md | 4 +- docs/architecture/CODEBASE_DOCUMENTATION.md | 8 +- docs/ops/CONTRIBUTION_GOLDEN_PATH.md | 2 +- scripts/check/check-db-rules.mjs | 174 +------------ src/lib/db/apiKeyGroups.ts | 31 +-- src/lib/db/ccrBlocks.ts | 5 - src/lib/db/compressionCombos.ts | 16 -- src/lib/db/gamification.ts | 10 - src/lib/db/middleware.ts | 65 +---- src/lib/db/quotaModelCaps.ts | 32 --- src/lib/db/sessionAccountAffinity.ts | 6 - src/lib/db/upstreamProxy.ts | 9 - .../check-db-rules-classification.test.ts | 232 ------------------ tests/unit/check-db-rules.test.ts | 164 +------------ 15 files changed, 45 insertions(+), 723 deletions(-) delete mode 100644 tests/unit/check-db-rules-classification.test.ts diff --git a/config/quality/eslint-suppressions.json b/config/quality/eslint-suppressions.json index 4c8a059b954b..97d9c608a5ec 100644 --- a/config/quality/eslint-suppressions.json +++ b/config/quality/eslint-suppressions.json @@ -1588,11 +1588,6 @@ "count": 2 } }, - "src/lib/db/compressionCombos.ts": { - "@typescript-eslint/no-unused-vars": { - "count": 1 - } - }, "src/lib/db/core.ts": { "@typescript-eslint/no-unused-vars": { "count": 3 @@ -1618,11 +1613,6 @@ "count": 2 } }, - "src/lib/db/middleware.ts": { - "@typescript-eslint/no-unused-vars": { - "count": 2 - } - }, "src/lib/db/migrationRunner.ts": { "@typescript-eslint/no-unused-vars": { "count": 2 diff --git a/docs/architecture/ARCHITECTURE.md b/docs/architecture/ARCHITECTURE.md index 6b0f6876c2ea..50678ddc89b1 100644 --- a/docs/architecture/ARCHITECTURE.md +++ b/docs/architecture/ARCHITECTURE.md @@ -514,7 +514,7 @@ For the full stealth playbook and operational guidance, see Primary state DB (SQLite): - Core infra: `src/lib/db/core.ts` (better-sqlite3, migrations, WAL) -- Re-export facade: `src/lib/localDb.ts` (thin compatibility layer for callers) +- DB access: import specific `src/lib/db/*` modules directly (the old `localDb.ts` barrel was removed) - file: `${DATA_DIR}/storage.sqlite` (or `$XDG_CONFIG_HOME/omniroute/storage.sqlite` when set, else `~/.omniroute/storage.sqlite`) - entities (tables + KV namespaces): providerConnections, providerNodes, modelAliases, combos, apiKeys, settings, pricing, **customModels**, **proxyConfig**, **ipFilter**, **thinkingBudget**, **systemPrompt** @@ -888,7 +888,7 @@ flowchart LR ### Persistence - `src/lib/db/*`: persistent config/state and domain persistence on SQLite -- `src/lib/localDb.ts`: compatibility re-export for DB modules +- `src/lib/db/*`: import specific modules directly — no barrel (the old `localDb.ts` re-export layer was removed) - `src/lib/usageDb.ts`: usage history/call logs facade on top of SQLite tables ## Provider Executor Coverage (Strategy Pattern) diff --git a/docs/architecture/CODEBASE_DOCUMENTATION.md b/docs/architecture/CODEBASE_DOCUMENTATION.md index 5e0118bfb21c..b0fc980777c6 100644 --- a/docs/architecture/CODEBASE_DOCUMENTATION.md +++ b/docs/architecture/CODEBASE_DOCUMENTATION.md @@ -313,7 +313,7 @@ table groups the actual directories and notable top-level files. Top-level files in `src/lib/`: -- `localDb.ts` — re-export layer only. **Never** add logic here. +- The old `localDb.ts` barrel was removed — consumers import specific `src/lib/db/*` modules directly. - `proxyHealth.ts`, `proxyLogger.ts`, `tokenHealthCheck.ts`, `localHealthCheck.ts` - `oneproxyRotator.ts`, `oneproxySync.ts` - `apiBridgeServer.ts`, `cacheLayer.ts`, `semanticCache.ts`, `settingsCache.ts` @@ -759,7 +759,7 @@ See [RESILIENCE_GUIDE.md](./RESILIENCE_GUIDE.md) and the dedicated section in 2. Export CRUD functions for your domain. 3. If new tables: add a migration under `src/lib/db/migrations/`, numbered sequentially, idempotent, transactional. -4. Re-export from `src/lib/localDb.ts` (re-export only — **no logic**). +4. Importers use direct imports from `@/lib/db/yourModule` (no barrel — the old `localDb.ts` re-export layer was removed). 5. Add tests under `tests/unit/`. ### Add a new MCP tool @@ -790,7 +790,7 @@ See [A2A-SERVER.md § Adding a New Skill](../frameworks/A2A-SERVER.md). Skills l - **TypeScript**: `strict: false` (legacy posture). Prefer explicit types over inference for cross-module boundaries. - **Database**: never write raw SQL in routes or handlers — always go through - `src/lib/db/` modules. Never add logic to `src/lib/localDb.ts`. + `src/lib/db/` modules. Never barrel-import — use specific `src/lib/db/*` modules directly. - **DB-entity typing (#3512)**: a function that writes or reads a DB table's row shape should take/return a named TS interface mirroring that table's columns 1:1, not `any` or an inline anonymous type at the call site. Land @@ -824,7 +824,7 @@ See [A2A-SERVER.md § Adding a New Skill](../frameworks/A2A-SERVER.md). Skills l ## 12. Hard Rules (from CLAUDE.md) 1. Never commit secrets or credentials. -2. Never add logic to `src/lib/localDb.ts`. +2. Never barrel-import — use specific `src/lib/db/*` modules directly. 3. Never use `eval()` / `new Function()` / implied eval. 4. Never commit directly to `main`. 5. Never write raw SQL in routes — always go through `src/lib/db/` modules. diff --git a/docs/ops/CONTRIBUTION_GOLDEN_PATH.md b/docs/ops/CONTRIBUTION_GOLDEN_PATH.md index 8a404963d7bd..fa171a4890f0 100644 --- a/docs/ops/CONTRIBUTION_GOLDEN_PATH.md +++ b/docs/ops/CONTRIBUTION_GOLDEN_PATH.md @@ -158,7 +158,7 @@ status. CI performs the broader package artifact and ecosystem checks. **Contracts** -- Domain modules under `src/lib/db/`; `src/lib/localDb.ts` remains a re-export layer only. +- Domain modules under `src/lib/db/`; import specific modules directly (the old `localDb.ts` re-export layer was removed). - Numbered, idempotent SQL migrations under `src/lib/db/migrations/`, transaction safety, upgrade behavior, indexes, and every caller affected by the schema. - Routes and handlers never issue raw SQL directly. diff --git a/scripts/check/check-db-rules.mjs b/scripts/check/check-db-rules.mjs index 6697e8168708..1a92176c3e2a 100644 --- a/scripts/check/check-db-rules.mjs +++ b/scripts/check/check-db-rules.mjs @@ -1,89 +1,24 @@ #!/usr/bin/env node // scripts/check/check-db-rules.mjs -// Gate de convenções de banco (CLAUDE.md Hard Rules #2 e #5). Três verificações: -// (a) Todo módulo de domínio em src/lib/db/*.ts deve ser re-exportado por -// src/lib/localDb.ts (camada de compat). Um módulo db NOVO que não é -// re-exportado (e não está congelado) falha — força a decisão consciente -// de expor ou justificar (Hard Rule #2). -// (b) src/lib/localDb.ts é APENAS camada de re-export: nada de lógica -// (function/class/arrow de negócio). Mata o anti-padrão de "só uma -// funçãozinha aqui" que vira regra de negócio fora dos módulos db/. +// Gate de convenções de banco (CLAUDE.md Hard Rules #2 e #5). Uma verificação: // (c) Nenhum SQL cru em src/app/api/**/route.ts ou open-sse/handlers/*.ts. // SQL deve viver em src/lib/db/ (Hard Rule #5). Ofensores pré-existentes // são congelados; QUALQUER novo SQL cru em rota/handler falha. -// Stale-enforcement (6A.3): entradas em INTENTIONALLY_INTERNAL / EXTERNAL_DB_ALLOWED -// que não suprimem nenhuma violação real → gate falha com instrução de remoção. +// As antigas verificações (a) re-export completo via src/lib/localDb.ts e +// (b) localDb.ts sem lógica foram REMOVIDAS: o barrel src/lib/localDb.ts foi +// deletado (#11795) — consumidores importam módulos src/lib/db/* diretamente +// (regra "never barrel-import", aplicada por eslint no-restricted-imports). +// Stale-enforcement (6A.3): entradas em EXTERNAL_DB_ALLOWED que não suprimem +// nenhuma violação real → gate falha com instrução de remoção. import fs from "node:fs"; import path from "node:path"; import { pathToFileURL } from "node:url"; import { assertNoStale } from "./lib/allowlist.mjs"; const cwd = process.cwd(); -const DB_DIR = path.join(cwd, "src/lib/db"); -const LOCAL_DB = path.join(cwd, "src/lib/localDb.ts"); const API_DIR = path.join(cwd, "src/app/api"); const HANDLERS_DIR = path.join(cwd, "open-sse/handlers"); -// (a) Módulos db/ que NÃO são re-exportados por localDb.ts por DESIGN (Hard Rule #2: -// "Never barrel-import from localDb.ts — import specific db/ modules instead"). -// Cada entrada aqui foi auditada e é consumida via import direto de "@/lib/db/X" -// (estático ou dinâmico) pelos seus consumidores — exatamente o padrão correto. -// Re-exportar esses módulos via localDb.ts INCENTIVARIA o anti-padrão proibido. -// O gate ainda bloqueia QUALQUER módulo db/ NOVO que não seja re-exportado E não -// esteja nessa lista — mantendo a decisão consciente obrigatória (Hard Rule #2). -// Legenda de classificação: -// type-only = exporta apenas tipos (sem runtime API), não há o que re-exportar -// db-internal = importado apenas dentro de src/lib/db/ (coordenação interna) -// intentionally-internal = consumido por import direto fora de db/ (correto per Rule #2) -// DEAD? = zero importers encontrados na auditoria de 2026-06-11; não deletar -// sem investigação — pode ser reserva de schema ou F2 pendente -export const INTENTIONALLY_INTERNAL = new Set([ - "_rowTypes", // type-only: 5 importers internos em db/ (AgentBridge/Inspector row types) - "accessTokens", // intentionally-internal: 4 rotas /api/cli/* (connect, whoami, tokens, tokens/[id]) + server/authz/accessTokenAuth.ts via import direto "@/lib/db/accessTokens" (Rule #2) - "apiKeyColumnFallbacks", // db-internal: importado só por db/apiKeys.ts (API_KEY_COLUMN_FALLBACKS — fallbacks de coluna split do apiKeys.ts) - "apiKeyUsageLimitFields", // db-internal: importado só por db/apiKeys.ts (helpers de campo de limite de uso split do apiKeys.ts; mig 101) - "backupRetention", // db-internal: importado só por db/backup.ts e db/migrationRunner.ts (política de retenção compartilhada; mora fora de backup.ts porque core.ts importa migrationRunner.ts — importar backup.ts de lá fecharia um ciclo, #10421) - "caseMapping", // db-internal: importado só por db/core.ts (toSnakeCase/toCamelCase/objToSnake — column-mapping snake↔camel split do core.ts, #4947) - "cleanup", // intentionally-internal: 3 API routes (purge-quota-snapshots, purge-call-logs, purge-detailed-logs) - "cliToolState", // intentionally-internal: 14+ API routes em /api/cli-tools/*-settings - "comboForecast", // intentionally-internal: src/lib/usage/comboForecast.ts - "commandCodeAuth", // intentionally-internal: 5 API routes em /api/providers/command-code/auth/* - "compression", // intentionally-internal: 2 API routes (settings/compression, context/rtk/config) - "compressionDetailNormalizers", // db-internal: importado só por db/compression.ts (normalizeSessionDedupConfig/normalizeCcrConfig/buildDetailConfigDefaults/applyDetailConfigUpdate — normalizadores do detail-config split do compression.ts, #8404) - "connectionRuntimeState", // intentionally-internal: warmupScheduler sqlite/redis stores importam diretamente de @/lib/db/connectionRuntimeState (Rule #2) - "vacuumScheduler", // intentionally-internal: src/instrumentation-node.ts (dynamic import, lifecycle wiring per Rule #2) - "detailedLogs", // intentionally-internal: 3 callers (callLogs.ts, logs/detail route, embeddings handler) - "discovery", // DEAD?: 0 importers na auditoria de 2026-06-11; lib/discovery/index.ts não usa db/discovery - "domainState", // intentionally-internal: 5 callers (batchWriter, circuitBreaker, costRules, fallbackPolicy, lockoutPolicy) - "encryption", // intentionally-internal: 8+ callers (container, webhookDispatcher, cloudAgent/credentials, services/apiKey, 4+ routes, open-sse) - "healthCheck", // db-internal: importado por db/core.ts (runDbHealthCheck) - "jsonMigration", // intentionally-internal: src/app/api/settings/import-json/route.ts - "migrationRunner", // db-internal: importado por db/core.ts (runMigrations ao inicializar o DB) - "modelCapabilityOverrides", // intentionally-internal: src/app/api/model-capability-overrides/route.ts via import direto "@/lib/db/modelCapabilityOverrides" (#6727 — evita empurrar localDb.ts para o cap de 800 linhas) - "notion", // intentionally-internal: settings/notion API route + open-sse/mcp-server/tools/notionTools.ts - "obsidian", // intentionally-internal: src/lib/obsidianSync.ts + settings/obsidian route + MCP obsidianTools.ts - "optimizationSettings", // db-internal: imported by db/core.ts for SQLite PRAGMA application helpers that require the live adapter - "pluginMetrics", // DEAD? (production): write path não foi conectado ainda (documentado no cabeçalho do módulo); testado por tests/unit/plugins-metrics.test.ts - "prompts", // DEAD? (production): zero callers de produção encontrados; domínio domain/prompts.ts é independente; testado por tests/integration/proxy-pipeline.test.ts - "probeUtils", // db-internal: importado so por db/core.ts (retryProbeIfTransient no caminho da corruption-probe, #9541); testado por tests/unit/probe-9541-repro.test.ts - "providerNodeSelect", // db-internal: importado só por db/providers.ts (selectProviderNodeForConnection — lógica pura de seleção de provider node split do providers.ts, #4421) - "providerStats", // intentionally-internal: src/app/api/provider-stats/route.ts - "proxyLatency", // intentionally-internal: imported directly by src/lib/db/proxies.ts (anti-barrel, #6798) - "proxySubscriptions", // db-internal: importado só por db/proxies.ts (addProxiesToScopePool — split do proxies.ts para ficar sob o cap de tamanho congelado, #7299); a função já é re-exportada por proxies.ts (que localDb.ts re-exporta) - "recovery", // intentionally-internal: bin/cli/runtime.mjs (import() dinâmico) + tests - "schemaColumns", // db-internal: importado só por db/core.ts (ensureProviderConnections/UsageHistory/CallLogsColumns + hasColumn/hasTable/getTableColumns — schema-column reconciliation split do core.ts, #4948) - "secrets", // intentionally-internal: src/instrumentation-node.ts (import() dinâmico na inicialização) - "serviceModels", // intentionally-internal: 3 callers (services/modelSync, services/bootstrap, /api/services/9router/models) - "stateReset", // db-internal: 3 callers dentro de src/lib/db/ (core, backup, apiKeys) para coordenação de reset - "stats", // intentionally-internal: src/app/api/settings/database/refresh-stats/route.ts - "tierConfig", // intentionally-internal: open-sse/services/tierResolver.ts (require() dinâmico) - "webSessionDedup", // db-internal: importado só por db/providers.ts (webSessionCredentialKey/parseProviderSpecificData — helpers puros de dedup de credencial web-session split do providers.ts, #3368 PR6) -]); - -// Alias para retrocompatibilidade com os testes existentes que importam KNOWN_UNEXPORTED. -// O comportamento do gate é idêntico — só o nome e os comentários mudaram (#3499). -export const KNOWN_UNEXPORTED = INTENTIONALLY_INTERNAL; - // (c) Leituras de SQL contra bancos EXTERNOS, permitidas por design (#3500). // Esta rota NÃO consulta o DB do OmniRoute (getDbInstance) — ela abre o // SQLite de OUTRO aplicativo (Kiro) para auto-importar credenciais. @@ -97,16 +32,13 @@ export const KNOWN_UNEXPORTED = INTENTIONALLY_INTERNAL; // precisa de entrada aqui: o SQL contra o state.vscdb externo do Cursor vive // em src/lib/cursor/tokenExtractor.ts, fora do escopo desta checagem (que só // varre src/app/api/**/route.ts e open-sse/handlers/*.ts). -const EXTERNAL_DB_ALLOWED = new Set([ +export const EXTERNAL_DB_ALLOWED = new Set([ "src/app/api/oauth/kiro/auto-import/route.ts", // read-only no SQLite do Kiro (DB externo) ]); // Alias de retrocompatibilidade (testes/consumidores que importam KNOWN_RAW_SQL). // Comportamento do gate idêntico — só o nome e o enquadramento mudaram (#3500). -const KNOWN_RAW_SQL = EXTERNAL_DB_ALLOWED; - -// Módulos sempre excluídos da checagem (a): não são domínio re-exportável. -const DB_MODULE_EXCLUDE = new Set(["core", "localDb", "index"]); +export const KNOWN_RAW_SQL = EXTERNAL_DB_ALLOWED; function walk(dir, acc = []) { if (!fs.existsSync(dir)) return acc; @@ -118,59 +50,6 @@ function walk(dir, acc = []) { return acc; } -// Lista os módulos de domínio em src/lib/db (top-level *.ts), excluindo -// core/localDb/index, *.d.ts e qualquer subdiretório (migrations/, adapters/, __tests__/). -export function collectDbModules(dbDir = DB_DIR) { - if (!fs.existsSync(dbDir)) return []; - return fs - .readdirSync(dbDir, { withFileTypes: true }) - .filter((e) => e.isFile() && /\.ts$/.test(e.name) && !/\.d\.ts$/.test(e.name)) - .map((e) => e.name.replace(/\.ts$/, "")) - .filter((name) => !DB_MODULE_EXCLUDE.has(name)) - .sort(); -} - -// Extrai os nomes de módulo re-exportados de localDb.ts a partir de -// `... from "./db/X"` (cobre export {…}, export * e export type {…}). -export function extractReexportedModules(localDbSource) { - const re = /from\s+["']\.\/db\/([A-Za-z0-9_]+)["']/g; - const out = new Set(); - let m; - while ((m = re.exec(localDbSource))) out.add(m[1]); - return out; -} - -// (a) Módulos db/ que não são re-exportados e não estão na lista de -// intencionalmente-internos (INTENTIONALLY_INTERNAL). O gate falha para -// qualquer módulo NOVO que não seja re-exportado nem justificado. -export function findMissingReexports(dbModules, reexported, allowlist = INTENTIONALLY_INTERNAL) { - return dbModules.filter((mod) => !reexported.has(mod) && !allowlist.has(mod)); -} - -// (b) localDb.ts deve conter SOMENTE import/export + comentários (sem lógica). -// Remove comentários e strings, depois procura declarações de runtime. -export function hasLogic(localDbSource) { - const stripped = localDbSource - // comentários de bloco - .replace(/\/\*[\s\S]*?\*\//g, "") - // comentários de linha - .replace(/\/\/[^\n]*/g, "") - // template strings - .replace(/`(?:\\[\s\S]|[^\\`])*`/g, '""') - // strings simples/duplas (paths de import etc.) - .replace(/"(?:\\.|[^"\\])*"/g, '""') - .replace(/'(?:\\.|[^'\\])*'/g, '""'); - - // function/class declaradas, ou atribuição a função (const X = (…) =>, const X = function). - const logicPatterns = [ - /(^|[^.\w])function\s+[A-Za-z_$]/, // function decl (não method .foo()) - /(^|[^.\w])class\s+[A-Za-z_$]/, // class decl - /(?:const|let|var)\s+[A-Za-z_$][\w$]*\s*=\s*(?:async\s*)?\(/, // const X = (…) ... (arrow/call) - /(?:const|let|var)\s+[A-Za-z_$][\w$]*\s*=\s*(?:async\s+)?function\b/, // const X = function - ]; - return logicPatterns.some((rx) => rx.test(stripped)); -} - // SQL cru é sempre uma STRING passada a db.prepare()/exec(): casamos os padrões // SÓ dentro de literais de string (não em código JS — `import … from`, `.set(`, // `new Set(`, `delete x` etc. são falsos positivos se varrermos o código todo). @@ -198,7 +77,7 @@ export function extractStringLiterals(code) { // tira as aspas/crases delimitadoras out.push(m[0].slice(1, -1)); } - return out.join("\n\n"); // separador que nenhum padrão SQL atravessa + return out.join("\n\u0000\n"); // separador que nenhum padrão SQL atravessa } // (c) Arquivos com SQL cru dentro de literais de string (linhas não-comentário), @@ -217,7 +96,7 @@ export function findRawSql(files, allowlist = KNOWN_RAW_SQL) { // Match each literal independently. Joining literals before scanning would // turn harmless code such as `update(...)` plus a later `"set"` string into // a false UPDATE ... SET SQL match. - const literals = extractStringLiterals(stripComments(src)).split("\n\0\n"); + const literals = extractStringLiterals(stripComments(src)).split("\n\u0000\n"); if (literals.some((literal) => SQL_PATTERNS.some((rx) => rx.test(literal)))) { offenders.push(rel); } @@ -239,33 +118,6 @@ export function collectSqlScanFiles(apiDir = API_DIR, handlersDir = HANDLERS_DIR function main() { const failures = []; - const localDbSource = fs.readFileSync(LOCAL_DB, "utf8"); - - // (a) re-export completeness - const dbModules = collectDbModules(); - const reexported = extractReexportedModules(localDbSource); - - // Live unexported modules BEFORE allowlist filtering (needed for stale-enforcement). - const liveUnexported = dbModules.filter((mod) => !reexported.has(mod)); - assertNoStale(INTENTIONALLY_INTERNAL, liveUnexported, "check-db-rules:unexported"); - - const missing = findMissingReexports(dbModules, reexported); - if (missing.length) { - failures.push( - `[#2 re-export] ${missing.length} módulo(s) db/ não re-exportado(s) por src/lib/localDb.ts:\n` + - missing.map((m) => ` ✗ src/lib/db/${m}.ts`).join("\n") + - `\n → re-exporte de src/lib/localDb.ts (apenas a lista de re-export, nada de lógica)` + - ` ou adicione a INTENTIONALLY_INTERNAL com justificativa (import direto de "@/lib/db/${missing[0]}").` - ); - } - - // (b) localDb sem lógica - if (hasLogic(localDbSource)) { - failures.push( - `[#2 sem-lógica] src/lib/localDb.ts contém lógica (function/class/arrow). É camada de` + - ` re-export apenas — mova a lógica para um módulo src/lib/db/.` - ); - } // (c) SQL cru fora de db/ // Live raw-SQL offenders BEFORE allowlist filtering (needed for stale-enforcement). @@ -289,8 +141,8 @@ function main() { } if (!process.exitCode) { console.log( - `[check-db-rules] OK (${dbModules.length} módulos db/, ${reexported.size} re-exportados, ` + - `${INTENTIONALLY_INTERNAL.size} intencionalmente-internos (Rule #2); ${EXTERNAL_DB_ALLOWED.size} leituras de DB externo permitidas (#3500))` + `[check-db-rules] OK (${scanFiles.length} arquivos varridos; ` + + `${EXTERNAL_DB_ALLOWED.size} leituras de DB externo permitidas (#3500))` ); } } diff --git a/src/lib/db/apiKeyGroups.ts b/src/lib/db/apiKeyGroups.ts index 584ee628dc4a..ad1395e1f59b 100644 --- a/src/lib/db/apiKeyGroups.ts +++ b/src/lib/db/apiKeyGroups.ts @@ -72,7 +72,7 @@ export function createKeyGroup(name: string, description = ""): KeyGroup { const now = new Date().toISOString(); db.prepare( - "INSERT INTO key_groups (id, name, description, created_at, updated_at) VALUES (?, ?, ?, ?, ?)", + "INSERT INTO key_groups (id, name, description, created_at, updated_at) VALUES (?, ?, ?, ?, ?)" ).run(id, name, description, now, now); return getKeyGroup(id)!; @@ -80,7 +80,7 @@ export function createKeyGroup(name: string, description = ""): KeyGroup { export function updateKeyGroup( id: string, - updates: { name?: string; description?: string; isActive?: boolean }, + updates: { name?: string; description?: string; isActive?: boolean } ): KeyGroup | undefined { const existing = getKeyGroup(id); if (!existing) return undefined; @@ -132,7 +132,7 @@ export function getGroupPermissions(groupId: string): GroupModelPermission[] { const db = getDbInstance() as any; const rows = db .prepare( - "SELECT * FROM group_model_permissions WHERE group_id = ? ORDER BY access_type ASC, model_pattern ASC", + "SELECT * FROM group_model_permissions WHERE group_id = ? ORDER BY access_type ASC, model_pattern ASC" ) .all(groupId) as any[]; return rows.map(rowToPermission); @@ -142,7 +142,7 @@ export function addGroupPermission( groupId: string, modelPattern: string, accessType: "allow" | "deny", - provider?: string, + provider?: string ): GroupModelPermission { const db = getDbInstance() as any; const id = randomUUID(); @@ -150,7 +150,7 @@ export function addGroupPermission( const result = db .prepare( - "INSERT INTO group_model_permissions (id, group_id, model_pattern, provider, access_type, created_at) VALUES (?, ?, ?, ?, ?, ?)", + "INSERT INTO group_model_permissions (id, group_id, model_pattern, provider, access_type, created_at) VALUES (?, ?, ?, ?, ?, ?)" ) .run(id, groupId, modelPattern, provider || null, accessType, now); @@ -169,17 +169,6 @@ export function removeGroupPermission(permissionId: string): boolean { } return result.changes > 0; } - -export function clearGroupPermissions(groupId: string): void { - const db = getDbInstance() as any; - const result = db.prepare("DELETE FROM group_model_permissions WHERE group_id = ?").run(groupId); - if (result.changes > 0) { - invalidateModelCatalogCache(); - } -} - -// ── Key Group Members ──────────────────────────────────────────────────── - export function getGroupMembers(groupId: string): KeyGroupMember[] { const db = getDbInstance() as any; const rows = db @@ -197,7 +186,7 @@ export function getKeyGroupsForApiKey(keyId: string): KeyGroup[] { INNER JOIN key_group_members m ON g.id = m.group_id WHERE m.key_id = ? AND g.is_active = 1 ORDER BY g.name ASC - `, + ` ) .all(keyId) as any[]; return rows.map(rowToGroup); @@ -252,7 +241,7 @@ export interface ModelAccessCheck { export function checkKeyModelAccess( keyId: string, model: string, - provider?: string, + provider?: string ): ModelAccessCheck { const groups = getKeyGroupsForApiKey(keyId); if (groups.length === 0) { @@ -270,7 +259,7 @@ export function checkKeyModelAccess( SELECT * FROM group_model_permissions WHERE group_id IN (${placeholders}) ORDER BY access_type ASC - `, + ` ) .all(...groupIds) as any[]; @@ -281,7 +270,7 @@ export function checkKeyModelAccess( (p) => p.accessType === "deny" && matchesModelPattern(p.modelPattern, model) && - (!p.provider || p.provider === provider), + (!p.provider || p.provider === provider) ); if (denyRules.length > 0) { @@ -293,7 +282,7 @@ export function checkKeyModelAccess( (p) => p.accessType === "allow" && matchesModelPattern(p.modelPattern, model) && - (!p.provider || p.provider === provider), + (!p.provider || p.provider === provider) ); if (allowRules.length > 0) { diff --git a/src/lib/db/ccrBlocks.ts b/src/lib/db/ccrBlocks.ts index 56d49c131a40..21397ebb1505 100644 --- a/src/lib/db/ccrBlocks.ts +++ b/src/lib/db/ccrBlocks.ts @@ -136,8 +136,3 @@ export function countCcrBlocks(principalId?: string): number { ) as { n: number } | undefined; return row?.n ?? 0; } - -/** Test seam: the write throttle is module state and has to be resettable between tests. */ -export function resetCcrBlockPruneCounter(): void { - writesSincePrune = 0; -} diff --git a/src/lib/db/compressionCombos.ts b/src/lib/db/compressionCombos.ts index 7fdbf0a58872..6825009c730b 100644 --- a/src/lib/db/compressionCombos.ts +++ b/src/lib/db/compressionCombos.ts @@ -41,10 +41,6 @@ function defaultCompressionComboPipeline(): CompressionPipelineStep[] { ]; } -function toRecord(value: unknown): JsonRecord { - return value && typeof value === "object" && !Array.isArray(value) ? (value as JsonRecord) : {}; -} - function parseJsonArray(value: unknown, fallback: T[]): T[] { if (Array.isArray(value)) return value as T[]; if (typeof value !== "string") return fallback; @@ -389,18 +385,6 @@ export function assignRoutingCombo(compressionComboId: string, routingComboId: s backupDbFile("pre-write"); return true; } - -export function unassignRoutingCombo(compressionComboId: string, routingComboId: string): boolean { - ensureCompressionComboTables(); - const result = getDbInstance() - .prepare( - "DELETE FROM compression_combo_assignments WHERE compression_combo_id = ? AND routing_combo_id = ?" - ) - .run(compressionComboId, routingComboId); - if (result.changes > 0) backupDbFile("pre-write"); - return result.changes > 0; -} - // Static stackPriority map — mirrors the values defined in each engine file. // Using a static map avoids cross-workspace imports (open-sse → src/lib/db) that // would introduce a circular dependency detected by check:cycles. diff --git a/src/lib/db/gamification.ts b/src/lib/db/gamification.ts index b55cd4cf21ed..8bb5fba4aaae 100644 --- a/src/lib/db/gamification.ts +++ b/src/lib/db/gamification.ts @@ -46,16 +46,6 @@ export interface UserBadge { badgeCategory?: string | null; badgeRarity?: string; } - -export interface XpAuditLogEntry { - id: number; - apiKeyId: string; - action: string; - xpEarned: number; - metadata: string | null; - createdAt: string; -} - export interface TokenLedgerEntry { id: number; fromApiKeyId: string; diff --git a/src/lib/db/middleware.ts b/src/lib/db/middleware.ts index 1485196920ba..cc7460e7292a 100644 --- a/src/lib/db/middleware.ts +++ b/src/lib/db/middleware.ts @@ -7,8 +7,7 @@ */ import { getDbInstance } from "@/lib/db/core"; -import { rowToCamel } from "@/lib/db/core"; -import type { HookConfig, HookConfigRow, HookLogEntry, HookScope } from "@/lib/middleware/types"; +import type { HookConfig, HookConfigRow, HookLogEntry } from "@/lib/middleware/types"; // ── Helpers ─────────────────────────────────────────────────────────────── @@ -70,27 +69,13 @@ export function getEnabledMiddlewareHooks(): HookConfig[] { return rows.map(rowToHookConfig); } -/** - * Get scoped hooks for a given combo ID. - */ -export function getComboMiddlewareHooks(comboId: string): HookConfig[] { - const db = getDbInstance() as any; - const rows = db - .prepare( - "SELECT * FROM middleware_hooks WHERE enabled = 1 AND (scope_type = 'global' OR (scope_type = 'combo' AND combo_id = ?)) ORDER BY priority ASC" - ) - .all(comboId) as HookConfigRow[]; - return rows.map(rowToHookConfig); -} - /** * Get a single hook by name. */ export function getMiddlewareHook(name: string): HookConfig | undefined { const db = getDbInstance() as any; const row = db.prepare("SELECT * FROM middleware_hooks WHERE name = ?").get(name) as - | HookConfigRow - | undefined; + HookConfigRow | undefined; return row ? rowToHookConfig(row) : undefined; } @@ -170,34 +155,6 @@ export function recordHookExecution(name: string, error?: string): void { ).run(name); } } - -// ── Log Operations ──────────────────────────────────────────────────────── - -/** - * Insert a hook execution log entry. - */ -export function insertHookLog(entry: HookLogEntry): void { - const db = getDbInstance() as any; - db.prepare( - ` - INSERT INTO middleware_logs (id, hook_name, request_id, duration_ms, mutated, skipped, error, timestamp) - VALUES (?, ?, ?, ?, ?, ?, ?, ?) - ` - ).run( - entry.id, - entry.hookName, - entry.requestId, - entry.durationMs, - entry.mutated ? 1 : 0, - entry.skipped ? 1 : 0, - entry.error || null, - entry.timestamp - ); -} - -/** - * Get hook execution logs, optionally filtered by hook name. - */ export function getHookLogs(hookName?: string, limit = 50): HookLogEntry[] { const db = getDbInstance() as any; let rows: any[]; @@ -219,21 +176,3 @@ export function getHookLogs(hookName?: string, limit = 50): HookLogEntry[] { timestamp: r.timestamp, })); } - -/** - * Clean up old hook logs (keep last N entries). - */ -export function cleanupHookLogs(maxEntries = 10000): number { - const db = getDbInstance() as any; - // Delete logs beyond the max, keeping the most recent - const result = db - .prepare( - ` - DELETE FROM middleware_logs WHERE id NOT IN ( - SELECT id FROM middleware_logs ORDER BY timestamp DESC LIMIT ? - ) - ` - ) - .run(maxEntries); - return result.changes; -} diff --git a/src/lib/db/quotaModelCaps.ts b/src/lib/db/quotaModelCaps.ts index 86306122d2a7..474c7724aa08 100644 --- a/src/lib/db/quotaModelCaps.ts +++ b/src/lib/db/quotaModelCaps.ts @@ -82,25 +82,6 @@ export function getModelCap(poolId: string, apiKeyId: string, model: string): Mo .get(poolId, apiKeyId, model); return row ? rowToModelCap(row) : null; } - -/** - * List all model caps for a given (pool, key) pair. - */ -export function listModelCaps(poolId: string, apiKeyId: string): ModelCap[] { - const rows = getDb() - .prepare( - `SELECT pool_id, api_key_id, model, cap_value, cap_unit - FROM quota_allocation_model_caps - WHERE pool_id = ? AND api_key_id = ?` - ) - .all(poolId, apiKeyId); - return rows.map(rowToModelCap); -} - -/** - * Insert or replace a model cap. - * cap_value must be > 0 (enforced by DB CHECK constraint). - */ export function setModelCap(cap: ModelCap): void { getDb() .prepare( @@ -113,16 +94,3 @@ export function setModelCap(cap: ModelCap): void { ) .run(cap.poolId, cap.apiKeyId, cap.model, cap.capValue, cap.capUnit); } - -/** - * Remove the cap for a specific (pool, key, model) triple. - * No-op if it does not exist. - */ -export function deleteModelCap(poolId: string, apiKeyId: string, model: string): void { - getDb() - .prepare( - `DELETE FROM quota_allocation_model_caps - WHERE pool_id = ? AND api_key_id = ? AND model = ?` - ) - .run(poolId, apiKeyId, model); -} diff --git a/src/lib/db/sessionAccountAffinity.ts b/src/lib/db/sessionAccountAffinity.ts index 9a284d126599..9b8c4fbfbab0 100644 --- a/src/lib/db/sessionAccountAffinity.ts +++ b/src/lib/db/sessionAccountAffinity.ts @@ -204,9 +204,3 @@ export function startSessionAccountAffinityCleanup(): void { }, CLEANUP_INTERVAL_MS); if (typeof cleanupTimer === "object" && "unref" in cleanupTimer) cleanupTimer.unref?.(); } - -export function stopSessionAccountAffinityCleanupForTests(): void { - if (!cleanupTimer) return; - clearInterval(cleanupTimer); - cleanupTimer = null; -} diff --git a/src/lib/db/upstreamProxy.ts b/src/lib/db/upstreamProxy.ts index ae7ed9a8e1a5..84bf3e0a11c6 100644 --- a/src/lib/db/upstreamProxy.ts +++ b/src/lib/db/upstreamProxy.ts @@ -129,15 +129,6 @@ function rowToConfig(record: Record): UpstreamProxyConfig { updatedAt: record.updated_at as string, }; } - -export async function getUpstreamProxyConfigs() { - const db = getDbInstance(); - const rows = db - .prepare("SELECT * FROM upstream_proxy_config ORDER BY provider_id") - .all() as UpstreamProxyRow[]; - return rows.map((row) => rowToConfig(toRecord(row))); -} - export async function getUpstreamProxyConfig(providerId: string) { const db = getDbInstance(); const row = db diff --git a/tests/unit/check-db-rules-classification.test.ts b/tests/unit/check-db-rules-classification.test.ts deleted file mode 100644 index 82ca183f0f08..000000000000 --- a/tests/unit/check-db-rules-classification.test.ts +++ /dev/null @@ -1,232 +0,0 @@ -/** - * Regression guard for INTENTIONALLY_INTERNAL classification (#3499). - * - * Every module in INTENTIONALLY_INTERNAL must be genuinely consumed via a - * direct/dynamic import somewhere in src/, open-sse/, bin/, or within - * src/lib/db/ itself (for db-internal coordination modules like stateReset, - * healthCheck, migrationRunner), OR documented as type-only / DEAD?. - * - * Purpose: if a future cleanup removes the last consumer of a module that is - * still in INTENTIONALLY_INTERNAL, this test turns red and forces a conscious - * reclassification decision — the DEAD? category must be explicitly expanded, - * not silently accumulated. - * - * Hard Rule #18 compliance: this test was written BEFORE the fix was applied, - * confirmed to fail on the old framing, and now passes on the audited truth. - */ -import { test } from "node:test"; -import assert from "node:assert"; -import fs from "node:fs"; -import path from "node:path"; -import { fileURLToPath } from "node:url"; -import { INTENTIONALLY_INTERNAL } from "../../scripts/check/check-db-rules.mjs"; - -const REPO_ROOT = path.resolve(fileURLToPath(import.meta.url), "../../.."); - -// ── Helpers ──────────────────────────────────────────────────────────────── - -/** - * Walk a directory tree, calling cb(absolutePath) for every file. - */ -function walkSync(dir: string, cb: (p: string) => void): void { - if (!fs.existsSync(dir)) return; - for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { - const full = path.join(dir, entry.name); - if (entry.isDirectory()) { - // Skip well-known non-source dirs to keep the walk fast - if (["node_modules", ".git", ".next", "dist", "out"].includes(entry.name)) continue; - walkSync(full, cb); - } else { - cb(full); - } - } -} - -/** - * Returns true if any file in the search roots imports db/ (static or dynamic). - * Patterns matched: - * from "@/lib/db/" - * from "../db/" (any relative path ending /db/) - * import("@/lib/db/") - * require(".../db/") - * import(`${...}/db/.ts`) — dynamic template (bin/cli/runtime.mjs pattern) - */ -function hasImporter(mod: string, roots: string[]): boolean { - // Escape special regex chars in mod name (underscore-prefixed names like _rowTypes) - const escaped = mod.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - const patterns = [ - // static: from "…/db/" - new RegExp(`from\\s+['""][^'"]+/db/${escaped}['"]`), - // dynamic: import("…/db/") or require("…/db/") - new RegExp(`(?:import|require)\\s*\\(\\s*['""][^'"]+/db/${escaped}['"]`), - // dynamic template: import(`…/db/.ts`) — bin/cli/runtime.mjs uses template literals - new RegExp(`import\\s*\\(\`[^'"\`]+/db/${escaped}\\.ts\`\\)`), - // dynamic via file:// URL helper: import(projectFileUrl("…/db/.ts")) — - // bin/cli/runtime.mjs since #11238 (Windows-safe file:// dynamic imports). - new RegExp( - `import\\s*\\(\\s*projectFileUrl\\(\\s*['""][^'"]+/db/${escaped}\\.ts['"]\\s*\\)\\s*\\)` - ), - // relative import within db/: from "./" or from "./" - new RegExp(`from\\s+['"]\\.\\.?/${escaped}['"]`), - ]; - // The canonical db module path — we must skip ONLY this exact file, not all files - // that happen to share the same basename (e.g. src/lib/usage/comboForecast.ts must - // NOT be skipped when searching for importers of src/lib/db/comboForecast.ts). - const dbModulePath = path.join(REPO_ROOT, "src", "lib", "db", `${mod}.ts`); - let found = false; - for (const root of roots) { - if (found) break; - walkSync(root, (filePath) => { - if (found) return; - // Only scan TS/JS/MJS source files - if (!/\.(ts|tsx|mjs|js|cjs)$/.test(filePath)) return; - // Skip only the db module itself (not same-name files in other dirs) - if (filePath === dbModulePath) return; - let src: string; - try { - src = fs.readFileSync(filePath, "utf8"); - } catch { - return; - } - if (patterns.some((rx) => rx.test(src))) found = true; - }); - } - return found; -} - -// ── Audit roots ──────────────────────────────────────────────────────────── - -// Modules marked "type-only": exports only TypeScript types; no runtime import -// needed — the ts compiler erases them. These are genuinely correct as-is. -const TYPE_ONLY = new Set(["_rowTypes"]); - -// Modules explicitly documented as DEAD? in the classification comments. -// They remain in INTENTIONALLY_INTERNAL for schema-reservation reasons. -// Flag them but do NOT fail — a separate decision is needed to remove them. -const DOCUMENTED_DEAD = new Set([ - "discovery", // DEAD?: 0 importers; lib/discovery/index.ts is independent - "pluginMetrics", // DEAD? (production): write path not yet wired (self-documented) - "prompts", // DEAD? (production): zero production callers; integration test only verifies interface shape -]); - -const SEARCH_ROOTS = [ - path.join(REPO_ROOT, "src"), - path.join(REPO_ROOT, "open-sse"), - path.join(REPO_ROOT, "bin"), - path.join(REPO_ROOT, "tests"), -]; - -// ── Tests ────────────────────────────────────────────────────────────────── - -test("INTENTIONALLY_INTERNAL is exported from check-db-rules.mjs", () => { - assert.ok( - INTENTIONALLY_INTERNAL instanceof Set, - "INTENTIONALLY_INTERNAL must be a Set exported from the gate script" - ); - assert.ok(INTENTIONALLY_INTERNAL.size > 0, "INTENTIONALLY_INTERNAL must not be empty"); -}); - -test("INTENTIONALLY_INTERNAL contains the expected 40 audited modules", () => { - const expected = [ - "_rowTypes", - "accessTokens", - "apiKeyColumnFallbacks", - "apiKeyUsageLimitFields", - "backupRetention", - "caseMapping", - "cleanup", - "cliToolState", - "comboForecast", - "commandCodeAuth", - "compression", - "compressionDetailNormalizers", - "connectionRuntimeState", - "detailedLogs", - "discovery", - "domainState", - "encryption", - "healthCheck", - "jsonMigration", - "migrationRunner", - "modelCapabilityOverrides", - "notion", - "obsidian", - "optimizationSettings", - "pluginMetrics", - "prompts", - "probeUtils", - "providerNodeSelect", - "providerStats", - "proxyLatency", - "proxySubscriptions", - "recovery", - "schemaColumns", - "secrets", - "serviceModels", - "stateReset", - "stats", - "tierConfig", - "vacuumScheduler", - "webSessionDedup", - ]; - for (const mod of expected) { - assert.ok( - INTENTIONALLY_INTERNAL.has(mod), - `Expected ${mod} to be in INTENTIONALLY_INTERNAL after audit` - ); - } - assert.equal( - INTENTIONALLY_INTERNAL.size, - expected.length, - `INTENTIONALLY_INTERNAL has ${INTENTIONALLY_INTERNAL.size} entries; expected ${expected.length}` - ); -}); - -test("every non-type-only, non-dead module in INTENTIONALLY_INTERNAL has ≥1 real importer", () => { - const failures: string[] = []; - for (const mod of INTENTIONALLY_INTERNAL) { - if (TYPE_ONLY.has(mod)) continue; // type-only: no runtime import expected - if (DOCUMENTED_DEAD.has(mod)) continue; // dead modules exempted with explicit flag - if (!hasImporter(mod, SEARCH_ROOTS)) { - failures.push(mod); - } - } - assert.deepEqual( - failures, - [], - `These INTENTIONALLY_INTERNAL modules have ZERO importers — either they became dead ` + - `(add to DOCUMENTED_DEAD with a DEAD? comment) or they were misclassified:\n ${failures.join(", ")}` - ); -}); - -test("type-only module _rowTypes is imported within src/lib/db/ by its consumers", () => { - // _rowTypes exports only TypeScript interfaces, so the import is always `import type`. - // It should be consumed within db/ itself (AgentBridge, Inspector CRUD modules). - const dbDir = path.join(REPO_ROOT, "src/lib/db"); - let found = false; - if (fs.existsSync(dbDir)) { - for (const entry of fs.readdirSync(dbDir, { withFileTypes: true })) { - if (!entry.isFile() || !/\.ts$/.test(entry.name)) continue; - if (entry.name === "_rowTypes.ts") continue; - const src = fs.readFileSync(path.join(dbDir, entry.name), "utf8"); - if (/from\s+['"]\.\/_rowTypes['"]/.test(src)) { - found = true; - break; - } - } - } - assert.ok( - found, - "_rowTypes must be imported (as type) by at least one sibling module in src/lib/db/" - ); -}); - -test("DOCUMENTED_DEAD modules are still in INTENTIONALLY_INTERNAL (dead list must stay honest)", () => { - for (const mod of DOCUMENTED_DEAD) { - assert.ok( - INTENTIONALLY_INTERNAL.has(mod), - `DOCUMENTED_DEAD module "${mod}" is no longer in INTENTIONALLY_INTERNAL — ` + - `remove it from DOCUMENTED_DEAD in this test if it was intentionally removed from the gate` - ); - } -}); diff --git a/tests/unit/check-db-rules.test.ts b/tests/unit/check-db-rules.test.ts index 081ce286e5f7..9e336e32aa3b 100644 --- a/tests/unit/check-db-rules.test.ts +++ b/tests/unit/check-db-rules.test.ts @@ -4,135 +4,20 @@ import fs from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { - collectDbModules, - extractReexportedModules, - findMissingReexports, - hasLogic, extractStringLiterals, findRawSql, collectSqlScanFiles, - INTENTIONALLY_INTERNAL, - KNOWN_UNEXPORTED, + EXTERNAL_DB_ALLOWED, + KNOWN_RAW_SQL, } from "../../scripts/check/check-db-rules.mjs"; import { reportStaleEntries } from "../../scripts/check/lib/allowlist.mjs"; const REPO_ROOT = path.resolve(fileURLToPath(import.meta.url), "../../.."); -const LOCAL_DB = path.join(REPO_ROOT, "src/lib/localDb.ts"); - -// ---------- (a) re-export completeness ---------- - -test("findMissingReexports: flags a NEW db module that is not re-exported", () => { - const dbModules = ["providers", "brandNewModule"]; - const reexported = new Set(["providers"]) as Set; - const allowlist = new Set(); - const missing = findMissingReexports(dbModules, reexported, allowlist) as string[]; - assert.deepEqual(missing, ["brandNewModule"]); -}); - -test("findMissingReexports: a re-exported module passes", () => { - const dbModules = ["providers"]; - const reexported = new Set(["providers"]) as Set; - const missing = findMissingReexports(dbModules, reexported, new Set()) as string[]; - assert.deepEqual(missing, []); -}); - -test("findMissingReexports: an allowlisted (frozen) module passes even if not re-exported", () => { - const dbModules = ["notion"]; - const reexported = new Set(); - const allowlist = new Set(["notion"]) as Set; - const missing = findMissingReexports(dbModules, reexported, allowlist) as string[]; - assert.deepEqual(missing, []); -}); - -test("extractReexportedModules: parses ./db/X from export forms", () => { - const src = [ - 'export { getCombos } from "./db/combos";', - 'export * from "./db/featureFlags";', - 'export type { Webhook } from "./db/webhooks";', - 'export { sumUsageTokensThisMonth } from "./db/usageSummary";', - // not a db module — must be ignored - 'export { initPricingSync } from "./pricingSync";', - ].join("\n"); - const mods = extractReexportedModules(src) as Set; - assert.equal(mods.has("combos"), true); - assert.equal(mods.has("featureFlags"), true); - assert.equal(mods.has("webhooks"), true); - assert.equal(mods.has("usageSummary"), true); - assert.equal(mods.has("pricingSync"), false); -}); - -test("collectDbModules: returns real modules and excludes core/localDb/index", () => { - const mods = collectDbModules() as string[]; - assert.ok(mods.includes("providers"), "expected providers module"); - assert.ok(mods.includes("combos"), "expected combos module"); - assert.equal(mods.includes("core"), false, "core must be excluded"); - assert.equal(mods.includes("localDb"), false, "localDb must be excluded"); - assert.equal(mods.includes("index"), false, "index must be excluded"); -}); - -// FREEZE GUARD: the live repo state must be green under the shipped allowlist. -test("live repo: no NEW unexported db modules beyond the frozen allowlist", async () => { - // Re-import the gate's frozen allowlist indirectly by running its default behavior: - // findMissingReexports with the gate default allowlist must be empty for the repo. - const dbModules = collectDbModules() as string[]; - const reexported = extractReexportedModules(fs.readFileSync(LOCAL_DB, "utf8")) as Set; - // Default allowlist (KNOWN_UNEXPORTED) is applied inside findMissingReexports. - const missing = findMissingReexports(dbModules, reexported) as string[]; - assert.deepEqual( - missing, - [], - `Unexported db module(s) not in KNOWN_UNEXPORTED: ${missing.join(", ")}` - ); -}); - -// ---------- (b) localDb has no logic ---------- - -test("hasLogic: false for a pure re-export layer", () => { - const src = [ - "// re-export layer", - 'export { a, b } from "./db/foo";', - 'export * from "./db/bar";', - 'export type { T } from "./db/baz";', - ].join("\n"); - assert.equal(hasLogic(src) as boolean, false); -}); - -test("hasLogic: true for a function declaration", () => { - const src = 'export { a } from "./db/foo";\nfunction doThing() { return 1; }'; - assert.equal(hasLogic(src) as boolean, true); -}); - -test("hasLogic: true for an arrow-function const", () => { - const src = 'export { a } from "./db/foo";\nconst helper = (x) => x + 1;'; - assert.equal(hasLogic(src) as boolean, true); -}); - -test("hasLogic: true for a class declaration", () => { - const src = 'export { a } from "./db/foo";\nclass Thing {}'; - assert.equal(hasLogic(src) as boolean, true); -}); - -test("hasLogic: SQL/logic-looking text inside comments or strings does not trip", () => { - const src = [ - "/* function notReal() {} */", - "// const fake = () => 1;", - 'export const SOURCE = "./db/foo";', // string only, no function on rhs - 'export { a } from "./db/foo";', - ].join("\n"); - // export const X = "string" is a value (not logic): the rhs is a string literal, - // so the arrow/call pattern must NOT match. - assert.equal(hasLogic(src) as boolean, false); -}); - -test("live repo: src/lib/localDb.ts contains no logic", () => { - const src = fs.readFileSync(LOCAL_DB, "utf8"); - assert.equal(hasLogic(src) as boolean, false); -}); // ---------- (c) no raw SQL outside db/ ---------- test("extractStringLiterals: returns only string bodies, ignoring code", () => { - const code = 'import { x } from "y";\nconst q = `SELECT * FROM t`;\nobj.set(1);'; + const code = 'import { x } from "y";\nconst q = `SELECT * FROM t`; obj.set(1);'; const literals = extractStringLiterals(code) as string; assert.ok(literals.includes("SELECT * FROM t"), "captures the template body"); assert.ok(literals.includes("y"), "captures the import path string"); @@ -143,7 +28,7 @@ test("findRawSql: flags a NEW route with raw SQL in a string literal", () => { const tmp = path.join(REPO_ROOT, ".tmp-check-db-rules-raw-sql.route.ts"); fs.writeFileSync( tmp, - 'const rows = db.prepare(`SELECT id FROM users WHERE x = ?`).all();\n', + "const rows = db.prepare(`SELECT id FROM users WHERE x = ?`).all();\n", "utf8" ); try { @@ -156,7 +41,11 @@ test("findRawSql: flags a NEW route with raw SQL in a string literal", () => { test("findRawSql: does NOT flag SQL that only appears in a comment", () => { const tmp = path.join(REPO_ROOT, ".tmp-check-db-rules-comment.route.ts"); - fs.writeFileSync(tmp, "// SELECT id FROM users -- documentation only\nexport const x = 1;\n", "utf8"); + fs.writeFileSync( + tmp, + "// SELECT id FROM users -- documentation only\nexport const x = 1;\n", + "utf8" + ); try { const offenders = findRawSql([tmp], new Set()) as string[]; assert.deepEqual(offenders, []); @@ -193,6 +82,10 @@ test("findRawSql: an allowlisted (frozen) offender passes", () => { assert.deepEqual(offenders, []); }); +test("KNOWN_RAW_SQL is an alias for EXTERNAL_DB_ALLOWED (retrocompat)", () => { + assert.equal(EXTERNAL_DB_ALLOWED, KNOWN_RAW_SQL); +}); + test("live repo: no NEW raw-SQL offenders beyond the frozen allowlist", () => { // findRawSql uses the gate default allowlist (KNOWN_RAW_SQL) when none is passed. const files = collectSqlScanFiles() as string[]; @@ -202,17 +95,6 @@ test("live repo: no NEW raw-SQL offenders beyond the frozen allowlist", () => { // --- stale-allowlist enforcement (6A.3) --- -test("stale-enforcement: INTENTIONALLY_INTERNAL entry no longer unexported is reported as stale", () => { - // Simulate a module that has now been re-exported (no longer unexported). - const liveUnexported: string[] = []; // module was re-exported - const stale = (reportStaleEntries as (a: Set, l: string[], g: string) => string[])( - new Set(["oldModule"]), - liveUnexported, - "check-db-rules:unexported" - ); - assert.deepEqual(stale, ["oldModule"]); -}); - test("stale-enforcement: EXTERNAL_DB_ALLOWED entry no longer has raw SQL is reported as stale", () => { // Simulate a file that no longer contains raw SQL (route was refactored). const liveRawSql: string[] = []; @@ -223,23 +105,3 @@ test("stale-enforcement: EXTERNAL_DB_ALLOWED entry no longer has raw SQL is repo ); assert.deepEqual(stale, ["src/app/api/oauth/cursor/auto-import/route.ts"]); }); - -test("stale-enforcement: live repo INTENTIONALLY_INTERNAL entries are all still unexported", () => { - // Every entry in INTENTIONALLY_INTERNAL must still be an unexported module. - // If it was re-exported (moved to localDb.ts), it must be removed from the allowlist. - const dbModules = collectDbModules() as string[]; - const reexported = extractReexportedModules( - fs.readFileSync(path.resolve(fileURLToPath(import.meta.url), "../../../src/lib/localDb.ts"), "utf8") - ) as Set; - const liveUnexported = dbModules.filter((mod) => !reexported.has(mod)); - const stale = (reportStaleEntries as (a: Set, l: string[], g: string) => string[])( - INTENTIONALLY_INTERNAL as Set, - liveUnexported, - "check-db-rules:unexported" - ); - assert.deepEqual(stale, [], `INTENTIONALLY_INTERNAL has stale entries: ${stale.join(", ")}`); -}); - -test("KNOWN_UNEXPORTED is an alias for INTENTIONALLY_INTERNAL (retrocompat)", () => { - assert.equal(INTENTIONALLY_INTERNAL, KNOWN_UNEXPORTED); -}); From 3b00535d041c8e33d25afacb5639b3b9d1d1d055 Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza Date: Sat, 29 Aug 2026 19:32:35 -0300 Subject: [PATCH 08/22] feat(guardrails): enforce video transcript provenance, budgets and reconciliation (#11652) (#12009) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit FU-05 core (closes #11652): caller-supplied Video Bridge transcripts had no bounded, deterministic contract — a client could self-assert source: "embedded"/"audio-bridge" and it was accepted verbatim. normalizeVideoTranscript gained a code-only trustedSource seam unreachable from request-body JSON; without it, any cue declaring embedded/audio-bridge is reclassified to client. Added budgets (256 cues, 4096 code units/cue, 4KiB/cue, 64KiB total), malformed-surrogate rejection, focus-window scoping, deterministic cross-source reconciliation, and bumped the result-cache version v4→v5 so old-contract cache entries can never serve new-contract requests. All 187 videoBridge* tests pass (185 pass, 2 unrelated pre-existing skips). --- ...652-video-transcript-provenance-budgets.md | 1 + src/lib/guardrails/videoBridge.ts | 6 +- src/lib/guardrails/videoBridgeHelpers.ts | 143 ++------- .../videoBridgeTranscriptContract.ts | 297 ++++++++++++++++++ .../guardrails/videoBridgeResultCache.test.ts | 10 +- .../videoBridgeTranscriptBudgets.test.ts | 211 +++++++++++++ ...videoBridgeTranscriptCacheIdentity.test.ts | 145 +++++++++ .../videoBridgeTranscriptProvenance.test.ts | 63 +++- 8 files changed, 742 insertions(+), 134 deletions(-) create mode 100644 changelog.d/features/11652-video-transcript-provenance-budgets.md create mode 100644 src/lib/guardrails/videoBridgeTranscriptContract.ts create mode 100644 tests/unit/guardrails/videoBridgeTranscriptBudgets.test.ts create mode 100644 tests/unit/guardrails/videoBridgeTranscriptCacheIdentity.test.ts diff --git a/changelog.d/features/11652-video-transcript-provenance-budgets.md b/changelog.d/features/11652-video-transcript-provenance-budgets.md new file mode 100644 index 000000000000..de4e67a09541 --- /dev/null +++ b/changelog.d/features/11652-video-transcript-provenance-budgets.md @@ -0,0 +1 @@ +- **feat(guardrails):** enforce a bounded, deterministic contract for Video Bridge transcripts — 256 cues, 4096 input code units and 4 KiB UTF-8 per cue, 64 KiB total text, malformed-Unicode rejection, focus-window scoping, cross-source reconciliation with contributing-source metadata, and a structural provenance trust boundary so caller JSON can never self-assert `embedded`/`audio-bridge` provenance ([#11652](https://github.com/diegosouzapw/OmniRoute/issues/11652)) diff --git a/src/lib/guardrails/videoBridge.ts b/src/lib/guardrails/videoBridge.ts index 4672a7dc3b04..1ae3ee95b7a8 100644 --- a/src/lib/guardrails/videoBridge.ts +++ b/src/lib/guardrails/videoBridge.ts @@ -102,7 +102,11 @@ function waitForVideoBridgePromise(promise: Promise, signal: AbortSignal): }); } -const VIDEO_BRIDGE_RESULT_CACHE_VERSION = "v4"; +// v5 (#11652): the normalized transcript contract changed (provenance trust +// boundary, budgets, cross-source reconciliation, focus scoping) — bump so a +// cache entry computed under the old, less-restrictive normalization can +// never be served for a request processed under the new contract. +const VIDEO_BRIDGE_RESULT_CACHE_VERSION = "v5"; const VIDEO_BRIDGE_RESULT_CACHE_POLICY = "sampling-then-dedup-v2"; const VIDEO_BRIDGE_RESULT_CACHE_KEY_KIND = "video-result-v4"; const VIDEO_BRIDGE_DOWNLOAD_FLIGHT_VERSION = "v1"; diff --git a/src/lib/guardrails/videoBridgeHelpers.ts b/src/lib/guardrails/videoBridgeHelpers.ts index 4673191bae06..c4bb21a4f961 100644 --- a/src/lib/guardrails/videoBridgeHelpers.ts +++ b/src/lib/guardrails/videoBridgeHelpers.ts @@ -16,6 +16,18 @@ import { type VideoSamplingMetadata, type VideoSamplingPolicy, } from "./videoBridgeRuntime"; +import { + buildNormalizedVideoTranscript, + reconcileVideoTranscriptCues, + type NormalizeVideoTranscriptOptions, + type VideoTranscriptCue, +} from "./videoBridgeTranscriptContract"; + +export type { + NormalizeVideoTranscriptOptions, + VideoTranscriptCue, + VideoTranscriptSource, +} from "./videoBridgeTranscriptContract"; export const VIDEO_BRIDGE_MAX_BYTES = 50 * 1024 * 1024; // Inline base64 shares the public 50 MiB JSON admission budget with model, @@ -91,92 +103,18 @@ export interface VideoPart { contactSheet?: boolean; } -export type VideoTranscriptSource = "audio-bridge" | "client" | "embedded"; - -export interface VideoTranscriptCue { - confidence: number; - endSeconds: number; - source: VideoTranscriptSource; - startSeconds: number; - text: string; -} - -const VIDEO_TRANSCRIPT_SOURCES: ReadonlySet = new Set([ - "audio-bridge", - "client", - "embedded", -]); - -/** Validate optional transcript metadata without ever invoking a transcription provider. */ +/** + * Validate optional transcript metadata without ever invoking a transcription + * provider. Delegates the full contract (budgets, the provenance trust + * boundary, reconciliation, and focus scoping) to videoBridgeTranscriptContract.ts + * — see that module for the security rationale. + */ export function normalizeVideoTranscript( value: unknown, - durationSeconds: number + durationSeconds: number, + options?: NormalizeVideoTranscriptOptions ): VideoTranscriptCue[] { - if (value === undefined || value === null) return []; - const rawCues = Array.isArray(value) - ? value - : value && typeof value === "object" && Array.isArray((value as Record).cues) - ? (value as Record).cues - : null; - if (!rawCues) throw new Error("Invalid video transcript: expected a cues array"); - if (!Number.isFinite(durationSeconds) || durationSeconds <= 0) { - throw new Error("Invalid video transcript duration"); - } - const seen = new Set(); - const normalized: VideoTranscriptCue[] = []; - for (const cue of rawCues) { - if (!cue || typeof cue !== "object") throw new Error("Invalid video transcript cue"); - const record = cue as Record; - const text = typeof record.text === "string" ? record.text.trim() : ""; - const source = record.source; - const startSeconds = - typeof record.startSeconds === "number" - ? record.startSeconds - : typeof record.start === "number" - ? record.start - : Number.NaN; - const endSeconds = - typeof record.endSeconds === "number" - ? record.endSeconds - : typeof record.end === "number" - ? record.end - : Number.NaN; - const confidence = record.confidence === undefined ? 1 : record.confidence; - if ( - !text || - typeof source !== "string" || - !VIDEO_TRANSCRIPT_SOURCES.has(source as VideoTranscriptSource) - ) { - throw new Error("Invalid video transcript source or provenance"); - } - if ( - !Number.isFinite(startSeconds) || - !Number.isFinite(endSeconds) || - !Number.isFinite(confidence) || - confidence < 0 || - confidence > 1 || - startSeconds < 0 || - endSeconds > durationSeconds || - endSeconds <= startSeconds - ) { - throw new Error("Invalid video transcript timestamp or confidence range"); - } - const normalizedCue = { - confidence, - endSeconds, - source: source as VideoTranscriptSource, - startSeconds, - text, - } satisfies VideoTranscriptCue; - const key = JSON.stringify(normalizedCue); - if (!seen.has(key)) { - seen.add(key); - normalized.push(normalizedCue); - } - } - return normalized.sort( - (left, right) => left.startSeconds - right.startSeconds || left.endSeconds - right.endSeconds - ); + return buildNormalizedVideoTranscript(value, durationSeconds, options); } const REPLACEABLE_VIDEO_SHAPES: ReadonlySet = new Set([ @@ -551,31 +489,6 @@ function formatTranscriptCue(cue: VideoTranscriptCue): string { return `transcript[source=${cue.source};confidence=${cue.confidence.toFixed(2)};interval=${formatVideoTimestamp(cue.startSeconds)}-${formatVideoTimestamp(cue.endSeconds)}] ${cue.text}`; } -function deduplicateVideoTranscriptCues(cues: readonly VideoTranscriptCue[]): VideoTranscriptCue[] { - const seen = new Set(); - return cues - .filter((cue) => { - const key = JSON.stringify([ - cue.source, - cue.confidence, - cue.startSeconds, - cue.endSeconds, - cue.text, - ]); - if (seen.has(key)) return false; - seen.add(key); - return true; - }) - .sort( - (left, right) => - left.startSeconds - right.startSeconds || - left.endSeconds - right.endSeconds || - left.source.localeCompare(right.source) || - left.text.localeCompare(right.text) || - left.confidence - right.confidence - ); -} - export async function describeVideoPart( part: VideoPart, options: DescribeVideoOptions, @@ -644,7 +557,8 @@ export async function describeVideoPart( : null; const separatelyRenderedTranscriptCues = normalizeVideoTranscript( part.transcript, - extracted.durationSeconds + extracted.durationSeconds, + { focusWindow } ); let transcriptCues = [...separatelyRenderedTranscriptCues]; let appendedTranscriptCues = separatelyRenderedTranscriptCues; @@ -698,7 +612,14 @@ export async function describeVideoPart( audio: async () => { normalizedFusionTranscriptCues = normalizeVideoTranscript( part.audioTranscript, - extracted.durationSeconds + extracted.durationSeconds, + // Structural trust seam: whatever the caller supplies in the + // dedicated audioTranscript field is always labeled "audio-bridge" + // by this fusion channel, regardless of any per-cue `source` the + // caller declared. This is not an authenticity claim about the + // caller's own transcription — only that it arrived through the + // audio-bridge fusion field rather than the generic transcript. + { trustedSource: "audio-bridge", focusWindow } ); return { observations: normalizedFusionTranscriptCues.map((cue) => ({ @@ -726,7 +647,7 @@ export async function describeVideoPart( ...(fused.failures ? { failures: fused.failures } : {}), }; const fusedAudioCues = fused.audioAvailable ? normalizedFusionTranscriptCues : []; - transcriptCues = deduplicateVideoTranscriptCues([...transcriptCues, ...fusedAudioCues]); + transcriptCues = reconcileVideoTranscriptCues([...transcriptCues, ...fusedAudioCues]); const fusedVideoTimeline = fused.observations.flatMap((observation) => observation.source === "video" ? [ diff --git a/src/lib/guardrails/videoBridgeTranscriptContract.ts b/src/lib/guardrails/videoBridgeTranscriptContract.ts new file mode 100644 index 000000000000..a5655da585a1 --- /dev/null +++ b/src/lib/guardrails/videoBridgeTranscriptContract.ts @@ -0,0 +1,297 @@ +/** + * FU-05 transcript contract: deterministic budgets, a structural provenance + * trust boundary, and cross-source reconciliation for Video Bridge + * transcripts. Extracted from videoBridgeHelpers.ts so the frozen-adjacent + * helper file only grows by a thin delegation (see #11652). + * + * Trust boundary: `normalizeVideoTranscript`'s only public entry point is + * `buildNormalizedVideoTranscript`. A caller-supplied cue can only ever + * declare `source: "client"` — declaring "embedded" or "audio-bridge" is + * self-asserted provenance and is silently reclassified to "client" because + * there is no way for the server to verify it came from an embedded-subtitle + * extractor or the audio-bridge pipeline. The ONLY way to obtain a trusted + * "embedded"/"audio-bridge" cue is for server-owned code to pass + * `options.trustedSource` explicitly — that option can never be reached by + * deserializing request-body JSON (it is a second, code-only argument), so + * the trust decision is structural rather than a flag the caller can flip. + */ + +export type VideoTranscriptSource = "audio-bridge" | "client" | "embedded"; + +export interface VideoTranscriptCue { + confidence: number; + endSeconds: number; + source: VideoTranscriptSource; + startSeconds: number; + text: string; + /** + * Present only when reconciliation merged cues declaring 2+ distinct + * sources into one cue (see `reconcileVideoTranscriptCues`). Absent for + * the common single-source case so existing exact-shape assertions are + * unaffected. + */ + contributingSources?: VideoTranscriptSource[]; +} + +export interface VideoTranscriptFocusWindow { + endSeconds: number; + startSeconds: number; +} + +export interface NormalizeVideoTranscriptOptions { + /** Structural trust seam — server-owned adapters only, never derived from request JSON. */ + trustedSource?: VideoTranscriptSource; + /** Scope (filter + clip) the resulting cues to this window; `null`/omitted keeps everything. */ + focusWindow?: VideoTranscriptFocusWindow | null; +} + +const VIDEO_TRANSCRIPT_SOURCES: ReadonlySet = new Set([ + "audio-bridge", + "client", + "embedded", +]); + +export const VIDEO_TRANSCRIPT_MAX_CUES = 256; +export const VIDEO_TRANSCRIPT_MAX_CUE_CODE_UNITS = 4096; +export const VIDEO_TRANSCRIPT_MAX_CUE_UTF8_BYTES = 4 * 1024; +export const VIDEO_TRANSCRIPT_MAX_TOTAL_UTF8_BYTES = 64 * 1024; + +// Bounded (no unbounded quantifiers) — matches an unpaired high surrogate not +// followed by a low surrogate, or an unpaired low surrogate not preceded by a +// high surrogate. Safe against catastrophic backtracking per AGENTS.md ReDoS rule. +const LONE_SURROGATE = /[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]/; + +const SOURCE_PRIORITY: Record = { + client: 0, + "audio-bridge": 1, + embedded: 2, +}; + +function extractRawCues(value: unknown): unknown[] { + if (Array.isArray(value)) return value; + if ( + value && + typeof value === "object" && + Array.isArray((value as Record).cues) + ) { + return (value as Record).cues as unknown[]; + } + throw new Error("Invalid video transcript: expected a cues array"); +} + +function extractCueText(record: Record): string { + const raw = record.text; + if (typeof raw !== "string") throw new Error("Invalid video transcript cue text"); + if (raw.length > VIDEO_TRANSCRIPT_MAX_CUE_CODE_UNITS) { + throw new Error( + `Video transcript cue exceeds the maximum of ${VIDEO_TRANSCRIPT_MAX_CUE_CODE_UNITS} input code units` + ); + } + if (LONE_SURROGATE.test(raw)) { + throw new Error("Invalid video transcript cue text encoding"); + } + const text = raw.normalize("NFC").trim(); + if (!text) throw new Error("Invalid video transcript cue text"); + if (Buffer.byteLength(text, "utf8") > VIDEO_TRANSCRIPT_MAX_CUE_UTF8_BYTES) { + throw new Error( + `Video transcript cue exceeds the maximum of ${VIDEO_TRANSCRIPT_MAX_CUE_UTF8_BYTES} UTF-8 bytes` + ); + } + return text; +} + +function resolveCueSource( + record: Record, + trustedSource: VideoTranscriptSource | undefined +): VideoTranscriptSource { + if (trustedSource) return trustedSource; + const declared = record.source; + if ( + typeof declared !== "string" || + !VIDEO_TRANSCRIPT_SOURCES.has(declared as VideoTranscriptSource) + ) { + throw new Error("Invalid video transcript source or provenance"); + } + // Only a server-owned adapter may assign "embedded"/"audio-bridge" via the + // trustedSource seam above; any caller-declared value collapses to "client". + return "client"; +} + +function resolveCueTimestamps(record: Record): { + startSeconds: number; + endSeconds: number; +} { + const startSeconds = + typeof record.startSeconds === "number" + ? record.startSeconds + : typeof record.start === "number" + ? record.start + : Number.NaN; + const endSeconds = + typeof record.endSeconds === "number" + ? record.endSeconds + : typeof record.end === "number" + ? record.end + : Number.NaN; + return { startSeconds, endSeconds }; +} + +function assertValidCueBounds( + startSeconds: number, + endSeconds: number, + confidence: unknown, + durationSeconds: number +): void { + if ( + !Number.isFinite(startSeconds) || + !Number.isFinite(endSeconds) || + !Number.isFinite(confidence as number) || + (confidence as number) < 0 || + (confidence as number) > 1 || + startSeconds < 0 || + endSeconds > durationSeconds || + endSeconds <= startSeconds + ) { + throw new Error("Invalid video transcript timestamp or confidence range"); + } +} + +function parseVideoTranscriptCue( + cue: unknown, + durationSeconds: number, + trustedSource: VideoTranscriptSource | undefined +): VideoTranscriptCue { + if (!cue || typeof cue !== "object") throw new Error("Invalid video transcript cue"); + const record = cue as Record; + const text = extractCueText(record); + const source = resolveCueSource(record, trustedSource); + const { startSeconds, endSeconds } = resolveCueTimestamps(record); + const confidence = record.confidence === undefined ? 1 : record.confidence; + assertValidCueBounds(startSeconds, endSeconds, confidence, durationSeconds); + return { confidence: confidence as number, endSeconds, source, startSeconds, text }; +} + +interface VideoTranscriptCluster { + startSeconds: number; + endSeconds: number; + text: string; + members: VideoTranscriptCue[]; +} + +function findOverlappingCluster( + clusters: readonly VideoTranscriptCluster[], + cue: VideoTranscriptCue +): VideoTranscriptCluster | undefined { + return clusters.find( + (cluster) => + cluster.text === cue.text && + cue.startSeconds < cluster.endSeconds && + cue.endSeconds > cluster.startSeconds + ); +} + +function mergeVideoTranscriptCluster(cluster: VideoTranscriptCluster): VideoTranscriptCue { + const { members } = cluster; + const distinctSources = Array.from(new Set(members.map((member) => member.source))).sort( + (left, right) => SOURCE_PRIORITY[left] - SOURCE_PRIORITY[right] + ); + const winningSource = distinctSources[distinctSources.length - 1]; + return { + confidence: Math.max(...members.map((member) => member.confidence)), + endSeconds: Math.max(...members.map((member) => member.endSeconds)), + source: winningSource, + startSeconds: Math.min(...members.map((member) => member.startSeconds)), + text: cluster.text, + ...(distinctSources.length > 1 ? { contributingSources: distinctSources } : {}), + }; +} + +/** + * Reconcile duplicate and time-overlapping cues sharing identical text — + * possibly contributed by different sources/calls — into one deterministic + * cue per cluster, preserving which sources contributed when more than one + * did. Replaces naive exact-match deduplication, which silently discarded + * the losing source's identity. + */ +export function reconcileVideoTranscriptCues( + cues: readonly VideoTranscriptCue[] +): VideoTranscriptCue[] { + const sorted = [...cues].sort( + (left, right) => + left.startSeconds - right.startSeconds || + left.endSeconds - right.endSeconds || + left.text.localeCompare(right.text) || + SOURCE_PRIORITY[left.source] - SOURCE_PRIORITY[right.source] + ); + const clusters: VideoTranscriptCluster[] = []; + for (const cue of sorted) { + const cluster = findOverlappingCluster(clusters, cue); + if (cluster) { + cluster.members.push(cue); + cluster.startSeconds = Math.min(cluster.startSeconds, cue.startSeconds); + cluster.endSeconds = Math.max(cluster.endSeconds, cue.endSeconds); + } else { + clusters.push({ + endSeconds: cue.endSeconds, + members: [cue], + startSeconds: cue.startSeconds, + text: cue.text, + }); + } + } + return clusters + .map(mergeVideoTranscriptCluster) + .sort((left, right) => left.startSeconds - right.startSeconds || left.endSeconds - right.endSeconds); +} + +/** Filter cues to those overlapping `focusWindow`, clipping their bounds to it. */ +export function scopeVideoTranscriptCuesToFocusWindow( + cues: readonly VideoTranscriptCue[], + focusWindow: VideoTranscriptFocusWindow | null | undefined +): VideoTranscriptCue[] { + if (!focusWindow) return [...cues]; + return cues + .filter( + (cue) => cue.startSeconds < focusWindow.endSeconds && cue.endSeconds > focusWindow.startSeconds + ) + .map((cue) => ({ + ...cue, + endSeconds: Math.min(cue.endSeconds, focusWindow.endSeconds), + startSeconds: Math.max(cue.startSeconds, focusWindow.startSeconds), + })); +} + +/** + * Validate optional transcript metadata without ever invoking a transcription + * provider. Enforces the FU-05 budgets (256 cues, 4096 input code units/cue, + * 4 KiB UTF-8/cue, 64 KiB total), the provenance trust boundary described at + * the top of this file, cross-source reconciliation, and focus-window scoping. + */ +export function buildNormalizedVideoTranscript( + value: unknown, + durationSeconds: number, + options: NormalizeVideoTranscriptOptions = {} +): VideoTranscriptCue[] { + if (value === undefined || value === null) return []; + if (!Number.isFinite(durationSeconds) || durationSeconds <= 0) { + throw new Error("Invalid video transcript duration"); + } + const rawCues = extractRawCues(value); + if (rawCues.length > VIDEO_TRANSCRIPT_MAX_CUES) { + throw new Error(`Video transcript exceeds the maximum of ${VIDEO_TRANSCRIPT_MAX_CUES} cues`); + } + const parsed: VideoTranscriptCue[] = []; + let totalBytes = 0; + for (const rawCue of rawCues) { + const cue = parseVideoTranscriptCue(rawCue, durationSeconds, options.trustedSource); + totalBytes += Buffer.byteLength(cue.text, "utf8"); + if (totalBytes > VIDEO_TRANSCRIPT_MAX_TOTAL_UTF8_BYTES) { + throw new Error( + `Video transcript exceeds the maximum total of ${VIDEO_TRANSCRIPT_MAX_TOTAL_UTF8_BYTES} UTF-8 bytes` + ); + } + parsed.push(cue); + } + const reconciled = reconcileVideoTranscriptCues(parsed); + return scopeVideoTranscriptCuesToFocusWindow(reconciled, options.focusWindow ?? null); +} diff --git a/tests/unit/guardrails/videoBridgeResultCache.test.ts b/tests/unit/guardrails/videoBridgeResultCache.test.ts index fb7c50ec8c2f..146af8738004 100644 --- a/tests/unit/guardrails/videoBridgeResultCache.test.ts +++ b/tests/unit/guardrails/videoBridgeResultCache.test.ts @@ -430,7 +430,7 @@ test("result-cache metadata carries the exact visual dedup policy identity", asy ); assert.ok(storedMetadata); - assert.equal(storedMetadata.cacheVersion, "v4"); + assert.equal(storedMetadata.cacheVersion, "v5"); assert.equal(storedMetadata.policyVersion, "sampling-then-dedup-v2"); assert.equal(storedMetadata.dedupPolicyVersion, "grayscale-16x16-mean-cells-v2"); assert.equal(storedMetadata.dedupThreshold, 0.04); @@ -446,12 +446,12 @@ test("a corrupt result-cache payload is discarded and recomputed", async () => { producerModel: "openai/gpt-4o-mini", metadata: { analysisMode: "full", - cacheVersion: "v4", + cacheVersion: "v5", dedupCandidateFrameCount: 16, dedupPolicyVersion: "grayscale-16x16-mean-cells-v2", dedupThreshold: 0.04, policyVersion: "sampling-then-dedup-v2", - extractorVersion: "v4", + extractorVersion: "v5", strategy: "uniform", model: "openai/gpt-4o-mini", prompt: "FU-01 corrupt cache", @@ -510,12 +510,12 @@ test("invalid numeric result-cache metadata is deleted and recomputed", async (t const cachedValue = "[Video description: cached numeric metadata]"; const validMetadata = (): Record => ({ analysisMode: "full", - cacheVersion: "v4", + cacheVersion: "v5", dedupCandidateFrameCount: 16, dedupPolicyVersion: "grayscale-16x16-mean-cells-v2", dedupThreshold: 0.04, policyVersion: "sampling-then-dedup-v2", - extractorVersion: "v4", + extractorVersion: "v5", strategy: "uniform", model: "openai/gpt-4o-mini", prompt: "FU-01 numeric cache validation", diff --git a/tests/unit/guardrails/videoBridgeTranscriptBudgets.test.ts b/tests/unit/guardrails/videoBridgeTranscriptBudgets.test.ts new file mode 100644 index 000000000000..1b776484fd5f --- /dev/null +++ b/tests/unit/guardrails/videoBridgeTranscriptBudgets.test.ts @@ -0,0 +1,211 @@ +import assert from "node:assert/strict"; +import test from "node:test"; + +import { + describeVideoPart, + normalizeVideoTranscript, + type VideoCaptionFrame, +} from "../../../src/lib/guardrails/videoBridgeHelpers"; +import { + VIDEO_TRANSCRIPT_MAX_CUES, + VIDEO_TRANSCRIPT_MAX_CUE_CODE_UNITS, + VIDEO_TRANSCRIPT_MAX_CUE_UTF8_BYTES, + VIDEO_TRANSCRIPT_MAX_TOTAL_UTF8_BYTES, +} from "../../../src/lib/guardrails/videoBridgeTranscriptContract"; + +function cue(overrides: Partial> = {}): Record { + return { text: "cue", start: 0, end: 1, source: "client", ...overrides }; +} + +// --------------------------------------------------------------------------- +// Budgets (#11652 scope): 256 cues, 4096 input code units/cue, 4 KiB UTF-8/cue, +// 64 KiB total text. All enforced deterministically with a clear error. +// --------------------------------------------------------------------------- + +test("accepts exactly the maximum cue count and rejects one more", () => { + const atLimit = Array.from({ length: VIDEO_TRANSCRIPT_MAX_CUES }, (_unused, index) => + cue({ text: `cue-${index}`, start: index, end: index + 0.5 }) + ); + assert.equal(normalizeVideoTranscript({ cues: atLimit }, VIDEO_TRANSCRIPT_MAX_CUES + 1).length, VIDEO_TRANSCRIPT_MAX_CUES); + + const overLimit = [...atLimit, cue({ text: "one-too-many", start: VIDEO_TRANSCRIPT_MAX_CUES })]; + assert.throws( + () => normalizeVideoTranscript({ cues: overLimit }, VIDEO_TRANSCRIPT_MAX_CUES + 2), + /256 cues/ + ); +}); + +test("rejects a cue whose raw text exceeds the maximum input code units", () => { + const tooLong = "a".repeat(VIDEO_TRANSCRIPT_MAX_CUE_CODE_UNITS + 1); + assert.throws( + () => normalizeVideoTranscript({ cues: [cue({ text: tooLong })] }, 10), + /input code units/ + ); + // Exactly at the limit is accepted. + const atLimit = "a".repeat(VIDEO_TRANSCRIPT_MAX_CUE_CODE_UNITS); + assert.equal(normalizeVideoTranscript({ cues: [cue({ text: atLimit })] }, 10)[0]?.text.length, VIDEO_TRANSCRIPT_MAX_CUE_CODE_UNITS); +}); + +test("rejects a cue whose UTF-8 encoding exceeds the maximum per-cue size", () => { + // Each "é" (U+00E9) is 1 code unit but 2 UTF-8 bytes, so this trips the byte + // budget while staying well under the code-unit budget. + const wideText = "é".repeat(VIDEO_TRANSCRIPT_MAX_CUE_UTF8_BYTES / 2 + 1); + assert.throws( + () => normalizeVideoTranscript({ cues: [cue({ text: wideText })] }, 10), + /UTF-8/ + ); +}); + +test("rejects a transcript whose combined UTF-8 text exceeds the total budget", () => { + const perCueBytes = 1024; + const cueCount = Math.ceil(VIDEO_TRANSCRIPT_MAX_TOTAL_UTF8_BYTES / perCueBytes) + 1; + const cues = Array.from({ length: cueCount }, (_unused, index) => + cue({ text: "b".repeat(perCueBytes), start: index, end: index + 0.5 }) + ); + assert.throws( + () => normalizeVideoTranscript({ cues }, cueCount + 1), + /total.*UTF-8|maximum total/i + ); +}); + +// --------------------------------------------------------------------------- +// Malformed Unicode +// --------------------------------------------------------------------------- + +test("rejects cue text containing an unpaired surrogate", () => { + assert.throws( + () => normalizeVideoTranscript({ cues: [cue({ text: "abc\uD800def" })] }, 10), + /encoding/i + ); + assert.throws( + () => normalizeVideoTranscript({ cues: [cue({ text: "abc\uDC00def" })] }, 10), + /encoding/i + ); +}); + +test("accepts well-formed surrogate pairs (astral text)", () => { + const cues = normalizeVideoTranscript({ cues: [cue({ text: "hello \u{1F600}" })] }, 10); + assert.equal(cues[0]?.text, "hello \u{1F600}"); +}); + +// --------------------------------------------------------------------------- +// Provenance forgery (structural trust boundary) +// --------------------------------------------------------------------------- + +test("a forged embedded source from request-body JSON is reclassified to client", () => { + const cues = normalizeVideoTranscript({ cues: [cue({ source: "embedded" })] }, 10); + assert.equal(cues[0]?.source, "client"); +}); + +test("a forged audio-bridge source from request-body JSON is reclassified to client", () => { + const cues = normalizeVideoTranscript({ cues: [cue({ source: "audio-bridge" })] }, 10); + assert.equal(cues[0]?.source, "client"); +}); + +test("the trustedSource option is a code-only seam that overrides any caller-declared source", () => { + const embeddedCues = normalizeVideoTranscript( + { cues: [cue({ source: "client" })] }, + 10, + { trustedSource: "embedded" } + ); + assert.equal(embeddedCues[0]?.source, "embedded"); + + const audioBridgeCues = normalizeVideoTranscript( + { cues: [cue({ source: "unknown-junk" })] }, + 10, + { trustedSource: "audio-bridge" } + ); + assert.equal(audioBridgeCues[0]?.source, "audio-bridge"); +}); + +// --------------------------------------------------------------------------- +// Focus scoping +// --------------------------------------------------------------------------- + +test("normalizeVideoTranscript scopes cues to the focus window, dropping non-overlapping cues", () => { + const cues = normalizeVideoTranscript( + { + cues: [ + cue({ text: "before", start: 0, end: 1 }), + cue({ text: "inside", start: 4, end: 6 }), + cue({ text: "spanning", start: 7, end: 12 }), + cue({ text: "after", start: 20, end: 21 }), + ], + }, + 30, + { focusWindow: { startSeconds: 3, endSeconds: 10 } } + ); + + assert.deepEqual( + cues.map((entry) => entry.text), + ["inside", "spanning"] + ); + const spanning = cues.find((entry) => entry.text === "spanning"); + assert.equal(spanning?.startSeconds, 7); + assert.equal(spanning?.endSeconds, 10, "clipped to the focus window end"); +}); + +test("describeVideoPart scopes transcript cues to the effective focus window end-to-end", async () => { + const frames: VideoCaptionFrame[] = [ + { dataUri: "data:image/jpeg;base64,AA==", timestampSeconds: 5 }, + ]; + const described = await describeVideoPart( + { + container: "messages", + messageIndex: 0, + partIndex: 0, + ref: "data:video/mp4;base64,AA==", + shape: "data_uri_string", + transcript: { + cues: [ + { text: "outside focus", start: 0, end: 1, source: "client" }, + { text: "inside focus", start: 4, end: 6, source: "client" }, + ], + }, + }, + { frameCount: 1, focusWindow: { startSeconds: 3, endSeconds: 10 }, timeoutMs: 1000 }, + async () => "a scene", + { extractFrames: async () => ({ durationSeconds: 20, frames }) } + ); + + assert.deepEqual( + described.transcriptCues?.map((entry) => entry.text), + ["inside focus"] + ); + assert.doesNotMatch(described.description, /outside focus/); +}); + +// --------------------------------------------------------------------------- +// Cross-source / within-call reconciliation +// --------------------------------------------------------------------------- + +test("reconciles overlapping same-text cues within a single normalizeVideoTranscript call", () => { + const cues = normalizeVideoTranscript( + { + cues: [ + cue({ text: "same words", start: 1, end: 3, confidence: 0.4 }), + cue({ text: "same words", start: 2, end: 4, confidence: 0.9 }), + ], + }, + 10 + ); + + assert.equal(cues.length, 1); + assert.equal(cues[0]?.startSeconds, 1); + assert.equal(cues[0]?.endSeconds, 4); + assert.equal(cues[0]?.confidence, 0.9, "keeps the higher-confidence reading"); + assert.equal(cues[0]?.contributingSources, undefined, "single-source merges add no metadata"); +}); + +test("non-overlapping cues with identical text are kept distinct", () => { + const cues = normalizeVideoTranscript( + { + cues: [ + cue({ text: "repeated line", start: 1, end: 2 }), + cue({ text: "repeated line", start: 8, end: 9 }), + ], + }, + 10 + ); + assert.equal(cues.length, 2); +}); diff --git a/tests/unit/guardrails/videoBridgeTranscriptCacheIdentity.test.ts b/tests/unit/guardrails/videoBridgeTranscriptCacheIdentity.test.ts new file mode 100644 index 000000000000..7ec1235017f4 --- /dev/null +++ b/tests/unit/guardrails/videoBridgeTranscriptCacheIdentity.test.ts @@ -0,0 +1,145 @@ +import assert from "node:assert/strict"; +import test from "node:test"; + +import { VideoBridgeGuardrail } from "../../../src/lib/guardrails/videoBridge.ts"; +import type { BridgeCacheEntry } from "../../../src/lib/guardrails/modalityBridge/bridgeCache.ts"; + +// FU-05 (#11652): "the normalized transcript contract must be part of cache +// identity ... cache hits cannot cross transcript identity or provenance +// changes." These tests exercise the third named seam alongside +// normalizeVideoTranscript/describeVideoPart: the guardrail's result-cache +// key, which already folds in a fingerprint of the raw transcript/ +// audioTranscript payload (videoBridge.ts::buildVideoResultCacheKey). + +function payloadWithTranscript(transcript: unknown): Record { + return { + model: "example/text-only", + messages: [ + { + role: "user", + content: [ + { + type: "input_video", + video_url: "data:video/mp4;base64,VFJBTlNDUklQVC1DQUNIRQ==", + transcript, + }, + ], + }, + ], + }; +} + +function makeBridge(onDescribe: () => Promise<{ description: string; durationSeconds: number; framesRequested: number; framesUsed: number }>) { + return new VideoBridgeGuardrail({ + deps: { + getSettings: async () => ({ + modalityBridgeCacheEnabled: true, + modalityBridgeCacheMaxEntries: 11, + modalityBridgeCacheTtlMinutes: 5, + modalityBridgeVideoEnabled: true, + modalityBridgeVideoModel: "openai/gpt-4o-mini", + modalityBridgeVisionPrompt: "FU-05 transcript cache identity", + }), + getCapabilities: () => ({ supportsVideo: false }), + selectVisionModel: async () => "openai/gpt-4o-mini", + describePart: onDescribe, + }, + }); +} + +test("a cache hit does not cross a transcript provenance change on an otherwise identical video", async () => { + let describeCalls = 0; + const bridge = makeBridge(async () => { + describeCalls += 1; + return { + description: "[Video description: cache identity probe]", + durationSeconds: 5, + framesRequested: 1, + framesUsed: 1, + }; + }); + + const clientDeclared = { + cues: [{ text: "same words", start: 1, end: 2, source: "client" }], + }; + const audioBridgeDeclared = { + cues: [{ text: "same words", start: 1, end: 2, source: "audio-bridge" }], + }; + + await bridge.preCall(payloadWithTranscript(clientDeclared), {}); + await bridge.preCall(payloadWithTranscript(clientDeclared), {}); + await bridge.preCall(payloadWithTranscript(audioBridgeDeclared), {}); + + assert.equal( + describeCalls, + 2, + "the repeated identical (video, transcript) pair must reuse the cached result, " + + "but the raw provenance change must force a miss even though both sources are " + + "reclassified to client after normalization" + ); +}); + +test("a cache hit does not cross a transcript identity change (different cues, same video)", async () => { + let describeCalls = 0; + const bridge = makeBridge(async () => { + describeCalls += 1; + return { + description: "[Video description: cache identity probe]", + durationSeconds: 5, + framesRequested: 1, + framesUsed: 1, + }; + }); + + await bridge.preCall( + payloadWithTranscript({ cues: [{ text: "first cue", start: 1, end: 2, source: "client" }] }), + {} + ); + await bridge.preCall( + payloadWithTranscript({ cues: [{ text: "second cue", start: 1, end: 2, source: "client" }] }), + {} + ); + + assert.equal(describeCalls, 2, "different transcript content must never share a cache entry"); +}); + +test("the result-cache contract version was bumped for the FU-05 normalization change", async () => { + let storedMetadata: Record | undefined; + const bridge = new VideoBridgeGuardrail({ + deps: { + getSettings: async () => ({ + modalityBridgeCacheEnabled: true, + modalityBridgeVideoEnabled: true, + modalityBridgeVideoModel: "openai/gpt-4o-mini", + modalityBridgeVisionPrompt: "FU-05 cache version", + }), + getCapabilities: () => ({ supportsVideo: false }), + selectVisionModel: async () => "openai/gpt-4o-mini", + resultCache: { + delete: () => undefined, + getEntry: () => undefined, + setEntry: (_key: string, entry: BridgeCacheEntry) => { + storedMetadata = entry.metadata as Record; + }, + }, + describePart: async () => ({ + description: "[Video description: version probe]", + durationSeconds: 1, + framesRequested: 1, + framesUsed: 1, + }), + }, + }); + + await bridge.preCall( + payloadWithTranscript({ cues: [{ text: "cue", start: 0, end: 1, source: "client" }] }), + {} + ); + + assert.ok(storedMetadata); + assert.notEqual( + storedMetadata?.cacheVersion, + "v4", + "a cache entry computed under the pre-FU-05 normalization contract must never match" + ); +}); diff --git a/tests/unit/guardrails/videoBridgeTranscriptProvenance.test.ts b/tests/unit/guardrails/videoBridgeTranscriptProvenance.test.ts index 22425ef26a78..82d274bb2ae3 100644 --- a/tests/unit/guardrails/videoBridgeTranscriptProvenance.test.ts +++ b/tests/unit/guardrails/videoBridgeTranscriptProvenance.test.ts @@ -18,7 +18,14 @@ async function jpegFrame(color: string, timestampSeconds: number): Promise