diff --git a/AGENTS.md b/AGENTS.md index 627064d5b1..d468ae5848 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 f865a7ba3b..97d9c608a5 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 @@ -2436,11 +2426,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 +2573,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 +2850,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 +4076,6 @@ }, "@typescript-eslint/no-unused-vars": { "count": 1 - }, - "no-restricted-imports": { - "count": 1 } }, "tests/unit/fix-tool-adjacency.test.ts": { diff --git a/docs/architecture/ARCHITECTURE.md b/docs/architecture/ARCHITECTURE.md index 6b0f6876c2..50678ddc89 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 5e0118bfb2..b0fc980777 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 8a404963d7..fa171a4890 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/open-sse/services/combo.ts b/open-sse/services/combo.ts index 6d09b2bea1..30e0afacc3 100644 --- a/open-sse/services/combo.ts +++ b/open-sse/services/combo.ts @@ -370,7 +370,7 @@ export function clearStaleLKGP( ): void { void (async () => { try { - const { clearLKGP } = await import("@/lib/localDb"); + const { clearLKGP } = await import("@/lib/db/settings"); const promises: Promise[] = [clearLKGP(comboName, comboId || comboName)]; if (executionKey) { promises.push(clearLKGP(comboName, executionKey)); diff --git a/scripts/check/check-db-rules.mjs b/scripts/check/check-db-rules.mjs index 6697e81687..1a92176c3e 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/copilot/systemPrompt.ts b/src/lib/copilot/systemPrompt.ts index 442e779394..d8626bab78 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 4b5a3eb545..c133d0b90b 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/db/apiKeyGroups.ts b/src/lib/db/apiKeyGroups.ts index 584ee628dc..ad1395e1f5 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 56d49c131a..21397ebb15 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 7fdbf0a588..6825009c73 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 b55cd4cf21..8bb5fba4aa 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 1485196920..cc7460e729 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 86306122d2..474c7724aa 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 9a284d1265..9b8c4fbfba 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 ae7ed9a8e1..84bf3e0a11 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/src/lib/localDb.ts b/src/lib/localDb.ts deleted file mode 100755 index 761e2db4eb..0000000000 --- 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 beee152554..3950062a05 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 4c3924020c..dcda79eb2d 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 51e667e1bd..4e82c15874 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 7f04c9245b..ccc8df4fc2 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 ed843a1373..a9f2e4789e 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 a4d7ffee60..2560511750 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 8cb4b7538e..a0d30d92a5 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 8ab679d9c2..31c696aa70 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 6d1930aa83..d05002813a 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 d6a07ddc69..53c950d3da 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 c07715ff4f..75ce683f23 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 29133efd51..71984da16d 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 8395b4f1eb..4290e12a18 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 8a1b59e04e..525b049137 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; +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`; - - // Resolve pricing from the live DB snapshot. - const aCost = await resolveInputCost(a.provider, aModel); - const bCost = await resolveInputCost(b.provider, bModel); - - console.log( - `[cost-optimized] Candidates: ${a.provider}/${aModel} ($${aCost}/M) vs ${b.provider}/${bModel} ($${bCost}/M)` - ); - - // 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; - } - - // 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; - } - - // 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")}` }], - }), - }) - ); - - 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] served: header=${headerProvider ?? "(absent)"}, ` + - `body=${bodyProvider ?? "(absent)"}, model="${rawModel ?? "(n/a)"}"` - ); - - // 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 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 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). + // We fall back to cerebras if groq is unhealthy. + const candidates = await pickConfirmedHealthy(2, [ + "groq", + "cerebras", + "deepseek", + "opencode-go", + ]); - // 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` - ); + // 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 PASS via header] ${cheapConn.provider} served (cost $${cheapCost}/M < $${priceyCost}/M)` + `[cost-optimized skip] Only ${uniqueCandidates.length} distinct healthy provider(s) — need ≥2. Skipping.` ); 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` - ); + 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`; + + // Resolve pricing from the live DB snapshot. + const aCost = await resolveInputCost(a.provider, aModel); + const bCost = await resolveInputCost(b.provider, bModel); + + console.log( + `[cost-optimized] Candidates: ${a.provider}/${aModel} ($${aCost}/M) vs ${b.provider}/${bModel} ($${bCost}/M)` + ); + + // 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 PASS via body prefix] ${cheapConn.provider} served (cost $${cheapCost}/M < $${priceyCost}/M)` + `[cost-optimized skip] Neither ${a.provider} nor ${b.provider} has resolvable catalog ` + + `pricing in the live DB snapshot — cannot prove cost ordering. Skipping.` ); 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}`)) { + // 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; + } + + // 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")}` }], + }), + }) + ); + + 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] served: header=${headerProvider ?? "(absent)"}, ` + + `body=${bodyProvider ?? "(absent)"}, model="${rawModel ?? "(n/a)"}"` + ); + + // 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 model field] model="${rawModel}" matches cheap provider ` + - `${cheapConn.provider}/${cheapModel} (cost $${cheapCost}/M < $${priceyCost}/M)` + `[cost-optimized PASS via header] ${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 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 body prefix] ${cheapConn.provider} served (cost $${cheapCost}/M < $${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. - 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; - } + // 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; + } - // 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); + // 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; + } + + // 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); + } } } -}); +); // --------------------------------------------------------------------------- // 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", - ]); + // 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 - - if (panelConns.length < 2) { - console.log( - `[fusion skip] Only ${panelConns.length} distinct healthy provider(s) confirmed — ` + - `need ≥2 for a real panel. Skipping.` - ); - 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, - }, - }, - }); - - 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. - }), + // 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 - assert.equal(response.status, 200, `Expected HTTP 200 from fusion combo, got ${response.status}`); + if (panelConns.length < 2) { + console.log( + `[fusion skip] Only ${panelConns.length} distinct healthy provider(s) confirmed — ` + + `need ≥2 for a real panel. Skipping.` + ); + return; + } - 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); + // 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] synthesized text (first 80 chars): "${text.slice(0, 80)}" | ` + - `model="${rawModel ?? "(n/a)"}" | body provider=${bodyProvider ?? "(absent)"}` + `[fusion] Panel (${panelConns.length}): ${panelConns.map((c: LiveConnection) => c.provider).join(", ")} | ` + + `judge: ${judgeModelStr}` ); - // 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.` - ); + 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. + }), + }) + ); + + 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" + ); + + // 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)"}` + ); + + // 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 e83cf5c840..914aae6f2a 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 74bf51fe79..9f26b588f6 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 3cff56385b..8bd612a347 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 4eb2188c98..199416bfc8 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 36c4437d05..efa8483cdb 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 3d952cb002..42be96ecc4 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 fa8c3b131e..812c99dca8 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 655f6f7b8f..9e8bb53743 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 bc6c291adc..4a7636b9c2 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 77c2943303..fb4a391c09 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 533ddb642f..5aec4f4945 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 da3d3e8bb2..0b8fa51657 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 9672958e25..1da02998cd 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 867d95ba7d..49a75f26bd 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 aceaaca3e3..833b8389c8 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 f6efcc57a6..f493f8edac 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 a64a2f0f9d..b50032d9c1 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 a472e6db4d..9784a688a1 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 7221cc883c..8fe8dfcb33 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 3a5d5bd536..034107256e 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 f41976e346..aed82060e8 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 f66da030fd..03e0e9292e 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 4a0043e721..1149c07730 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 5cc891cb43..f67574a509 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 243fa6f904..f863f1b8ce 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 357e084bf6..f010b3a79c 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 35eabb0c03..8f2d1f4e57 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/check-db-rules-classification.test.ts b/tests/unit/check-db-rules-classification.test.ts deleted file mode 100644 index 82ca183f0f..0000000000 --- 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 081ce286e5..9e336e32aa 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); -}); diff --git a/tests/unit/cli-tools-crush.test.ts b/tests/unit/cli-tools-crush.test.ts index 4dbdbfde5e..7e58ce77f6 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 68c1ce3ca9..9c486d244e 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 c9e5b83e8c..399ddd1571 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 e74f480ef1..7553d57eb8 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 1108287730..94fc2adb19 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 93d76898d2..8da6e7058c 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 1de38b5b6f..f9ae10780e 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 16fd8f7bb6..23f547cfce 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 db79cbcab7..0b5475de93 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 a3f1e1942d..acbae44a6f 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 6bf42b4db3..a82562eaaa 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 2655a43ecd..da994f1487 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 73f199a1ec..d4a1445616 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 0659edf58c..bccf323694 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 341ba53932..e06a26bfab 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 82eeab2041..cc96643f6e 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 46f42f5e7c..8abc7a5a56 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 c96862fd6e..fb1003bc88 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 6ee7ff114a..9c8e3b31ae 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 80ab12de02..c03e2b0b8f 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 360dfe3758..fc4d8ff4ff 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 f863c04f6b..acaf60ff6b 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 4aaf79b167..84da271a0e 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 e85ef0973b..3232eb0615 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 f05a40342d..c8eef83dde 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 ed897b6733..d6e41df005 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 e0c1d25092..89cc77ae8e 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 55e68c165d..87179ccdb1 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() {