mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-31 04:12:10 +03:00
fix(quality): resolve base-reds da release — db-rules allowlist + task-aware router precedence (#4973)
Dois base-reds pré-existentes que reprovavam o CI da release v3.8.36 (Fast Quality Gates + Unit Tests fast-path), independentes de qualquer feature em voo: 1. check:db-rules / allowlist: os módulos db-internal caseMapping (#4947) e schemaColumns (#4948), extraídos de db/core.ts e importados só por ele, não estavam em INTENTIONALLY_INTERNAL. Registrados na allowlist (correção canônica — são internos legítimos, não re-exportados pelo localDb). 2. auto-strategy honra LKGP/cost (combo-routing-engine.test.ts, 2 testes): o task-aware reordering (#4945, reorderByTaskWeight) roda para strategy "auto" e era aplicado DEPOIS do router explícito (selectWithStrategy: lkgp/cost), sobrescrevendo o orderedTargets[0] que o operador escolheu. Instrumentação provou: post-filter [0]=claude (LKGP) → post-task [0]=gpt-oss. Correção: quando o auto usa router explícito, preserva o [0] dele e deixa o task-aware refinar só a cauda de fallback. gpt-oss-120b PERMANECE tool-capable (não é mudança de catálogo; o model-capabilities-registry test segue verde). Validado: 121 testes (combo-routing-engine + combo-task-aware + registry) verdes, red-check confirmado, db-rules/file-size/typecheck/lint/prettier OK. Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>
This commit is contained in:
committed by
GitHub
parent
a9ddef1302
commit
028ed4ea7b
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"_comment": "Catraca de tamanho (check-file-size.mjs). frozen so pode encolher; arquivos novos <= cap. --update ratcheta.",
|
||||
"_rebaseline_2026_06_24_4945_task_aware_router_precedence": "Base-red fix: the #4945 task-aware reordering (reorderByTaskWeight) runs for strategy \"auto\" and was applied AFTER the auto router (selectWithStrategy: lkgp/cost/etc) had already pinned orderedTargets[0], silently defeating the operator's explicit LKGP/cost choice (proven: tests/unit/combo-routing-engine.test.ts 'honors LKGP after filtering to tool-capable models' + 'falls back to the full pool when tool filtering empties candidates' selected gpt-oss-120b — the 1st target — instead of the LKGP/cost winner; instrumentation showed orderedTargets[0]=claude post-filter then gpt-oss post-task-reorder). open-sse/services/combo.ts 3306->3321 (+15 = a one-line `autoUsedExplicitRouter` flag set when the explicit router succeeds, and at the task-aware step: when that flag is set, pin orderedTargets[0] and let task-aware refine only the fallback tail — reorderByTaskWeight returns the same objects so identity filtering is safe). gpt-oss-120b correctly REMAINS tool-capable (model-capabilities-registry.test.ts asserts true), so this is NOT a tool-capability change. Irreducible chokepoint wiring at the existing auto-route boundary; not extractable. Covered by the two combo-routing-engine.test.ts cases above + combo-task-aware.test.ts (task-aware still active for non-router strategies).",
|
||||
"_rebaseline_2026_06_24_per_connection_max_concurrent": "Feature (per-connection max_concurrent enforcement, issue #9 follow-up) own growth: open-sse/services/combo.ts 3225->3238 (+13 net). Provider connections already carry a maxConcurrent ceiling (provider_connections.max_concurrent, migration 029, set in the UI/API/DB) but routing ignored it, so low-concurrency subscription accounts (GLM/MiniMax ~1) got flooded -> 429 + cooldown, worse under quota-share where keys share one account. The new wiring lives at the two combo dispatch chokepoints: (1) the strategy===\"quota-share\" branch resolves each target connection's cap and passes it into selectQuotaShareTarget so an at-cap connection is deprioritized (fail-open, never hard-blocked); (2) handleRoundRobinCombo resolves the target connection's cap and uses it as the semaphore maxConcurrency (fallback = combo-level concurrency). ALL extractable logic was moved into the new pure leaf open-sse/services/combo/concurrencyCaps.ts (resolveMaxConcurrentByConnection + makeConnectionConcurrencyResolver, DB reads deduped per connectionId, fail-open) and the pure effectiveMaxConcurrency resolver into combo/comboPredicates.ts; combo.ts keeps only the irreducible chokepoint wiring (a 5-line import + the two call-site bindings). Cohesive at the existing dispatch boundaries, not further extractable. Structural shrink of combo.ts tracked in #3501. Covered by tests/unit/quota-share-strategy.test.ts (per-connection maxConcurrent gating cases) + tests/unit/combo/effective-max-concurrency.test.ts.",
|
||||
"_rebaseline_2026_06_24_combo_cooldown_wait_quota_share": "Feature quota-share combo cooldown-aware retry (Variante A) own growth: open-sse/services/combo.ts 3225->3293 (+68 = the cooldown-wait wrap inside handleComboChat's quota-share path. The existing setTry loop body is LEFT at its original indentation: instead of an outer `while (true)` (which would re-indent ~1600 lines and bloat the review), the setTry loop is hoisted into a small recursive closure `dispatchWithCooldownRetry`, and a wait+redispatch is a tail `return dispatchWithCooldownRetry()` — re-running ONLY the set loop (exactly the prior continue-to-top-of-set-loop semantics) while selection/shadow-routing/setup above stay untouched. At the 429 crystallization point the lock reason is resolved via getModelLockoutInfo, the decision via the new pure resolveComboCooldownWaitDecision, then await waitForCooldownAwareRetry (499 on abort), decrement the budget, recurse. Gated to strategy==='quota-share' && comboCooldownWait.enabled. `git diff` == `git diff -w` for combo.ts (zero re-indentation noise). The gating policy + reason resolution are extracted to the new pure leaf open-sse/services/combo/comboCooldownRetry.ts (<cap, unit-tested) to keep combo.ts thin; only the wait orchestration (one getModelLockoutInfo lookup + one await) stays at the chokepoint, not extractable. The quota_exhausted/auth/not-found exclusions defend against isRetryableModelLockoutReason (auth.ts:533) treating quota_exhausted as retryable (locked-until-midnight). Covered by tests/unit/combo-cooldown-retry.test.ts (pure helper, all branches) + tests/unit/combo-quota-share-cooldown-wait.test.ts (integration: rate_limit waits+recovers, quota_exhausted no-wait, abort=499, non-quota-share unchanged) + tests/unit/resilience-settings-combo-cooldown-wait.test.ts (settings round-trip/clamp). Also src/app/(dashboard)/dashboard/settings/components/ResilienceTab.tsx 983->1098 (+115 = the ComboCooldownWaitCard exposing enabled/maxWaitMs/maxAttempts/budgetMs in Settings > Resilience, mirroring WaitForCooldownCard; wired through GET/PATCH in src/app/api/resilience/route.ts + comboCooldownWaitSettingsSchema in src/shared/validation/schemas/settings.ts; new UI labels use the t(key)||English-fallback pattern, en-only). Structural shrink of combo.ts + ResilienceTab tracked in #3501.",
|
||||
"_rebaseline_2026_06_23_4774_combo_legacy_strip": "PR #4774 (KooshaPari, #4382 round-trip) own growth: src/app/(dashboard)/dashboard/combos/page.tsx 4434->4456 (+22 = the client-side LEGACY_COMBO_RESILIENCE_KEYS Set gains queueTimeoutMs + the 12 v3.8.31-era removed keys (queueDepth/fallbackDelayMs/handoffProviders/maxComboDepth/manifestRouting/complexityAwareRouting/pipeline_enabled/pipelineConcurrency/shadowRouting/evalRouting/resetAwareEnabled/resetAwareWindow) with explanatory comments, mirroring the server-side strip list in src/app/api/combos/[id]/route.ts so the modal never re-introduces removed keys on Save). Functional strip list, not a movable block; combos/page.tsx structural shrink tracked in #3501. Covered by tests/unit/combo-config.test.ts (auto-promote + passthrough + legacy-key round-trip).",
|
||||
@@ -145,7 +146,7 @@
|
||||
"_rebaseline_2026_06_24_headroom_strategy": "Headroom-aware connection selection (dario technique): combo.ts 3168->3180 (+12 = a new `else if (strategy === \"headroom\")` dispatch branch in handleComboChat that delegates to orderTargetsByHeadroom + its log line, plus the import). The actual logic lives OUT of the god-file: the pure ranker rankByHeadroom/computeHeadroom is the new leaf open-sse/services/combo/headroomRanking.ts (91 LOC, <cap) and the async orderer orderTargetsByHeadroom is appended to the existing open-sse/services/combo/quotaStrategies.ts (<cap) next to its sibling reset-aware/reset-window orderers (reuses their connection-expansion machinery). headroom = 1 - max(util_5h, util_7d) from getSaturation (src/lib/quota/saturationSignals.ts), prefers the connection with the most free capacity. Only the dispatch wiring is irreducible at the existing combo strategy chokepoint (mirrors the reset-aware/reset-window/context-optimized branches); not extractable without hiding the call site. fill-first stays default; all existing strategies untouched. Covered by tests/unit/combo-headroom-ranking.test.ts (pure helper) + tests/unit/combo-headroom-strategy.test.ts (orderer, saturation injected). Structural shrink of combo.ts tracked in #3501.",
|
||||
"_rebaseline_2026_06_24_quota_share_strategy": "Dedicated quota-share strategy (Phase 3 #9): combo.ts 3180->3190 (+10 = one new `else if (strategy === \"quota-share\")` dispatch branch in handleComboChat that delegates 100% to selectQuotaShareTarget + its log line, plus the import). All the new logic lives OUT of the god-file in two new leaves under open-sse/services/combo/: quotaShareInflight.ts (in-flight counter with TTL/lease, ~150 LOC <cap) and quotaShareStrategy.ts (per-model bucket gating via isBucketSaturated + DRR proportional to weight + P2C over in-flight, ~240 LOC <cap). Only the dispatch wiring is irreducible at the existing combo strategy chokepoint (mirrors the headroom/reset-aware/reset-window/context-optimized branches); not extractable without hiding the call site. ZERO existing strategy cases were modified — only this branch was added, and the qtSd/ combos switched from fill-first to quota-share in src/lib/quota/quotaCombos.ts. Covered by tests/unit/quota-share-strategy.test.ts (gating, DRR fairness, P2C in-flight, fail-open, activation). Structural shrink of combo.ts tracked in #3501.",
|
||||
"_rebaseline_2026_06_24_task_aware_routing": "Task-aware routing strategy (port PR #2045, OmniRoute #4945): combo.ts 3190->3225 (+35) = one new `else if (strategy === \"task-aware\")` dispatch branch delegating 100% to selectTaskAwareTarget + its imports/log lines. All scoring/classification logic lives OUT of the god-file in the new leaf open-sse/services/taskAwareRouting.ts (553 LOC <cap). Only the dispatch wiring is irreducible at the existing combo strategy chokepoint (mirrors quota-share/headroom/reset-aware branches). ZERO existing strategy cases modified. Covered by tests/unit/combo-task-aware.test.ts (35 tests). Structural shrink of combo.ts tracked in #3501.",
|
||||
"open-sse/services/combo.ts": 3306,
|
||||
"open-sse/services/combo.ts": 3321,
|
||||
"open-sse/services/compression/strategySelector.ts": 848,
|
||||
"open-sse/services/rateLimitManager.ts": 1035,
|
||||
"open-sse/services/tokenRefresh.ts": 2070,
|
||||
|
||||
@@ -1183,6 +1183,11 @@ export async function handleComboChat({
|
||||
}
|
||||
}
|
||||
|
||||
// #4945 regression guard: when an "auto" combo uses an EXPLICIT router
|
||||
// (routingStrategy lkgp/cost/etc, not the default "rules" scorer), that router
|
||||
// pins orderedTargets[0]. The task-aware reordering below must then refine only
|
||||
// the fallback order, never override the router's primary choice.
|
||||
let autoUsedExplicitRouter = false;
|
||||
if (strategy === "auto") {
|
||||
const requestHasTools = Array.isArray(body?.tools) && body.tools.length > 0;
|
||||
let eligibleTargets = [...orderedTargets];
|
||||
@@ -1332,6 +1337,7 @@ export async function handleComboChat({
|
||||
selectedProvider = decision.provider;
|
||||
selectedModel = decision.model;
|
||||
selectionReason = decision.reason;
|
||||
autoUsedExplicitRouter = true;
|
||||
} catch (err) {
|
||||
log.warn(
|
||||
"COMBO",
|
||||
@@ -1604,17 +1610,26 @@ export async function handleComboChat({
|
||||
const task = classifyTask(body);
|
||||
const conversationCacheKey = getConversationCacheKey(body);
|
||||
const taskReordered = reorderByTaskWeight(orderedTargets, task);
|
||||
if (taskReordered[0]?.modelStr !== orderedTargets[0]?.modelStr) {
|
||||
// #4945 regression guard: when an explicit auto router (lkgp/cost/…) pinned
|
||||
// orderedTargets[0], keep that primary choice and let task-aware refine only
|
||||
// the fallback tail — otherwise task weighting silently defeats the operator's
|
||||
// chosen LKGP/cost selection. reorderByTaskWeight returns the same target
|
||||
// objects (no clone), so identity filtering is safe.
|
||||
const pinnedFirst = autoUsedExplicitRouter ? orderedTargets[0] : undefined;
|
||||
const nextOrder = pinnedFirst
|
||||
? [pinnedFirst, ...taskReordered.filter((t) => t !== pinnedFirst)]
|
||||
: taskReordered;
|
||||
if (nextOrder[0]?.modelStr !== orderedTargets[0]?.modelStr) {
|
||||
const reasons =
|
||||
Array.isArray(task.reasons) && task.reasons.length > 0
|
||||
? ` (${task.reasons.join(",")})`
|
||||
: "";
|
||||
log.info(
|
||||
"COMBO",
|
||||
`task-route task=${task.level}${reasons} cacheKey=${conversationCacheKey ?? "none"} → ${taskReordered[0]?.modelStr}`
|
||||
`task-route task=${task.level}${reasons} cacheKey=${conversationCacheKey ?? "none"} → ${nextOrder[0]?.modelStr}`
|
||||
);
|
||||
}
|
||||
orderedTargets = taskReordered;
|
||||
orderedTargets = nextOrder;
|
||||
}
|
||||
|
||||
// Parallel pre-screen: check provider profiles and model availability for all targets
|
||||
|
||||
@@ -42,6 +42,7 @@ export const INTENTIONALLY_INTERNAL = new Set([
|
||||
"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)
|
||||
"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
|
||||
@@ -63,6 +64,7 @@ export const INTENTIONALLY_INTERNAL = new Set([
|
||||
"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
|
||||
"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
|
||||
|
||||
@@ -121,12 +121,13 @@ test("INTENTIONALLY_INTERNAL is exported from check-db-rules.mjs", () => {
|
||||
assert.ok(INTENTIONALLY_INTERNAL.size > 0, "INTENTIONALLY_INTERNAL must not be empty");
|
||||
});
|
||||
|
||||
test("INTENTIONALLY_INTERNAL contains the expected 30 audited modules", () => {
|
||||
test("INTENTIONALLY_INTERNAL contains the expected 32 audited modules", () => {
|
||||
const expected = [
|
||||
"_rowTypes",
|
||||
"accessTokens",
|
||||
"apiKeyColumnFallbacks",
|
||||
"apiKeyUsageLimitFields",
|
||||
"caseMapping",
|
||||
"cleanup",
|
||||
"cliToolState",
|
||||
"comboForecast",
|
||||
@@ -147,6 +148,7 @@ test("INTENTIONALLY_INTERNAL contains the expected 30 audited modules", () => {
|
||||
"providerNodeSelect",
|
||||
"providerStats",
|
||||
"recovery",
|
||||
"schemaColumns",
|
||||
"secrets",
|
||||
"serviceModels",
|
||||
"stateReset",
|
||||
|
||||
Reference in New Issue
Block a user