diff --git a/config/quality/complexity-baseline.json b/config/quality/complexity-baseline.json index 1ca4a5ce4be..b890b11e5e4 100644 --- a/config/quality/complexity-baseline.json +++ b/config/quality/complexity-baseline.json @@ -1,38 +1,38 @@ -{ - "_comment": "Catraca de complexidade (check-complexity.mjs, ESLint core rules complexity>=15 e max-lines-per-function>80 sobre src+open-sse+electron+bin via eslint.complexity.config.mjs). Conta total de violacoes; so pode cair. --update ratcheta.", - "count": 2035, - "_rebaseline_2026_07_04_v3844_release_close": "2026->2028 (+2). v3.8.44 release close (generate-release Phase 0/1): drift residual do fim do ciclo medido no tip pos-#6155 (merge burst final: #6155 cooling-panel + #6104 Kenari + #6139/#6128 provider-limits). Trust-but-verify: os 2 fixes de codigo do release-captain (model.ts alias boundary, auggie.ts stdin error handlers) adicionam 0 violacoes NOVAS — eslint.complexity direto nos 2 arquivos flagra apenas funcoes que ja estouravam o limite antes (runStreaming/start ja >80 linhas; resolveModelByProviderInference/getModelInfoCore pre-existentes de #5918), e resolveProviderAlias segue abaixo de 15. Logo o +2 e drift herdado do burst. Tighten via --update next cycle.", - "_rebaseline_2026_07_03_v3844_ipfilter_release_green": "2015->2026 (+11). v3.8.44 cycle drift measured on release tip 32e4c906e during the #6131/#5975 release-green rebaseline. Inherited from the merge burst (Quality Ratchet does not run on PR->release fast-gates). route-edge-coverage +7 is my #5975 test comment; the rest is parallel-session drift. Tighten via --update next cycle.", - "_rebaseline_2026_07_03_6007_sidecar_manifest": "2007->2015. PR #6007 (re-cut) adds sidecar/provider-manifest header wiring and the public manifest URL helper; the feature files themselves introduce 0 new complexity violations (check-complexity flags none in providerPluginManifestUrl.ts). The +8 vs the 2007 release baseline is inherited release/v3.8.44 drift absorbed at merge (check:complexity measures 2015 on the current release tip). Tighten via --update next cycle / at /generate-release Phase 0.", - "_rebaseline_2026_07_02_v3844_ci_observed": "2006->2007 (+1). Local Ubuntu measures 2006 on this tree; the GitHub fast-gates runner measures 2007 (same local-vs-CI off-by-one already documented in _rebaseline_2026_06_26_v3838_release_fast_gate). Use the CI-observed value so the gate is deterministic where it actually runs.", - "_rebaseline_2026_07_02_v3844_post_5939": "2003->2006 (+3). Inherited drift from the release/v3.8.44 merges after 3a3d618fe (#5809 audio translations et al.), surfaced by PR fix#5959: check:complexity measures 2006 on the pristine base (cbd08ef78) WITH AND WITHOUT this PR's one-line CLI change (verified by reverting the file and re-measuring) — the PR is complexity-net-zero. Tighten via --update next cycle.", - "_rebaseline_2026_07_02_v3844_merge_burst": "1995->2003 (+8). Inherited v3.8.44 cycle drift surfaced by PR #5939: check:complexity measures 2003 on BOTH the pristine release tip (3a3d618fe) and this PR's merged HEAD — identical, so all +8 came from the 2026-07-02 merge burst into release/v3.8.44 (#5933 codex schema, #5950 OCR, #5904/#5920 combo, #6000/#6008 executor refactors, etc.) merged while the fast-gates queue was base-red (file-size #5933). PR #5939 itself was verified complexity-net-zero during its own CI cycle (DiscoveryPageClient refactored into hooks/sub-components to stay under max-lines-per-function). Tighten via --update next cycle.", - "_rebaseline_2026_07_02_5798_release_green": "1982->1995 (+13). Inherited v3.8.43 cycle drift surfaced by the release-green unblock #5798 / PR #5896: check:complexity measures 1995 on BOTH the pristine release tip (0d3875a98) and this PR's HEAD — identical, so all +13 came from the 2026-07-01/02 merge burst (providers/usage/dashboard fixes merged via --admin while the fast-gates queue was base-red). This PR touches only gate scripts, docs, baselines and test files — 0 production logic. Tighten via --update next cycle.", - "_rebaseline_2026_07_01_v3843_release": "1981->1982 (+1). v3.8.43 cycle drift, surfaced after check:mutation-test-coverage was fixed (it was masked behind that earlier step in the Fast Quality Gates chain). 1982 = the value measured by check:complexity on BOTH fce85136c (release tip) and 6d7060e21 (release + the 5 CI fixes) — identical, so all +1 is inherited cycle drift; the fixes touch only test files + linkify.ts safeHttpHref (cyclomatic ~4, well under the >=15 threshold, 0 new violations) + config JSON. Tighten via --update next cycle.", - "_rebaseline_2026_06_28_v3840_5237_reconcile": "1980->1981 (+1). Inherited release/v3.8.40 drift surfaced while merging PR #5237 (impersonation-UA refresh) — the +1 is present on the pristine release tip (d8a392a47) WITHOUT #5237's changes, so it is #5222 (antigravity fallback-LRU retry) / #5221 (command-code) growth that merged via --admin without ratcheting complexity (the PR->release fast-gates do not run check:complexity). #5237 itself is complexity-net-zero: its only edits are a single UA constant, a regenerated golden snapshot, and baseline JSONs. Structural reduction tracked in #3501.", - "_rebaseline_2026_06_27_v3838_release": "1978->1980 (+2). v3.8.38 cycle-close drift surfaced by the release-green pre-flight (check:complexity does NOT run on PR->release fast-gates). +2 from late-cycle feature/fix merges (compression fidelity-gate steps #5143, SSE hardening). Release-finalize working tree touches ONLY CHANGELOG.md + i18n mirrors + the 2 baseline JSONs — 0 production-code change. Structural reduction tracked in #3501.", - "_rebaseline_2026_06_26_v3838_ownerprs_batch": "1972->1978 (+6). Drift do lote de merges de PRs do dono + contribuidores em release/v3.8.38 (sessao /review-prs): #4845 (antigravity convertGeminiToOpenAI), #5105 (executor zenmux-free), #5020 (executor grok-cli), #4940 (usage dedupe guard), #5093 (resilience: quota cutoff/gemini mime/model-lockout cooldown), #5015 (quota hydration + auto-combo scoping). Cada um e crescimento de feature/fix legitimo recem-TDD'd, nao regressao; o gate check:complexity NAO roda no fast-path PR->release, entao o ramo acumula sem rebaselinar (mesma familia dos rebaselines anteriores). #5121 cookie-dedup foi mantido complexity-NEUTRO via extracao do helper findExistingCookieConnection. Reducao estrutural fica como debt (#3501).", - "_rebaseline_2026_06_26_v3838_release_fast_gate": "1963->1972 (+9). Reconciles inherited release/v3.8.38 drift surfaced by PR #5124. Local origin/release/v3.8.38 measured 1971 and this PR also measured 1971 after refactoring the new JSON-body SSE sniffing path, while the GitHub Ubuntu fast gate measured 1972; use the CI-observed value so the release branch gate is deterministic. The streaming fix is complexity-net-zero relative to the local release base. The release fast-path does not consistently ratchet complexity between release-cycle merges; keep structural reductions as separate debt.", - "_rebaseline_2026_06_25_v3836_release": "Reconciliacao release-volatil 1920->1950 (+30) no fechamento do ciclo v3.8.36, surfada pelo CI da fix-PR #5029 (a catraca de complexidade NAO roda no fast-path PR->release nem foi medida no release PR #4854 — Quality Ratchet foi SKIPPED la — so PR->main, entao o ramo acumulou os 137 commits sem rebaselinar). O +30 e drift de condicionais NOVOS das features legitimas do ciclo: Quota-Share Fase 2/3 (estrategia dedicada DRR+P2C, multi-window buckets, concurrency control, headroom, saturacao proativa — #4885/#4907/#4908/#4927/#4928/#4929/#4939/#4965/#4967/#4970), task-aware + Fusion combo (#4945/#4652), e ramos de provider/translator de contribuidores. A god-file decomposition #3501 e PURA (move codigo p/ leaves, complexity-neutra). Verificado que esta fix-PR (#5029) toca SO scripts/build/pack-artifact-policy.ts (array de strings), tests/integration/resilience-http-e2e.test.ts (2 keys) e os 2 baselines json — contribui 0 ao gate que varre src+open-sse+electron+bin. Mesma familia dos rebaselines anteriores — crescimento de feature legitimo recem-TDD'd, nao regressao; reducao estrutural fica como debt (#3501).", - "_rebaseline_2026_06_23_v3835_release": "Reconciliacao release-volatil 1916->1920 (+4) no fechamento do ciclo v3.8.35, surfada pelo pre-flight check:release-green (a catraca de complexidade NAO roda no fast-path PR->release, so release->main, entao o ramo acumula sem rebaselinar). O +4 e drift de condicionais NOVOS dos merges de contribuidor/feature deste ciclo (Compression Phase 4 #4694/#4707/#4716/#4720, combos auto-promote #4774, tier no-auth #4753, deepseek-web tool-fold #4756, dedupe provider nodes #4768). Verificado que o trabalho de release-finalize desta sessao toca SO docs/*.md (THREAT_MODEL), CHANGELOG.md, baselines e 1 linha de string em scripts/check/check-fabricated-docs.mjs (fora do escopo src+open-sse+electron+bin que o gate varre) — contribui 0. Mesma familia dos rebaselines anteriores — crescimento de feature legitimo recem-TDD'd, nao regressao; reducao estrutural fica como debt (#3501).", - "_rebaseline_2026_06_23_v3834_release": "Reconciliacao release-volatil 1915->1916 (+1) no fechamento do ciclo v3.8.34. check:complexity NAO roda no fast-path PR->release (so release->main), entao o ramo acumula sem rebaselinar; surfou no full CI do release PR (run em c98e7ff6d). O +1 e drift de condicional NOVO de merge de contribuidor do ciclo (features quota/usage/opencode-go/M365). Verificado que o commit de release-finalize NAO adiciona complexity: toca CHANGELOG/baseline/mirrors/3 testes + 1 linha de regex em opencodeOllamaUsage.ts (sem novo ramo) + reorder de dados no reka registry — local mede 1916 com ou sem essa mudanca. Mesma familia dos rebaselines anteriores — crescimento de feature legitimo, nao regressao; reducao estrutural fica como debt (#3501).", - "_rebaseline_2026_06_21_v3833_4537_nested_combo": "Reconciliacao 1913->1915 (+2) do PR #4537 (nestedComboMode execute — black-box combo-ref execution). O +2 vem do branch novo de dispatch nested em handleComboChat (combo.ts: normalizeNestedComboMode + executeModeUnits/hasExecutableComboRef + o ramo simpleExecuteStrategies que roda resolveComboRuntimeUnits com recursion caps depth/cycle/budget) — ramos condicionais no chokepoint de dispatch do combo. Medido com `node scripts/check/check-complexity.mjs` no estado merged. Crescimento de feature legitimo recem-TDD'd, nao regressao; reducao estrutural fica como debt (#3501).", - "_rebaseline_2026_06_21_v3833_reviewprs_r4_owner": "Reconciliacao release-volatil 1911->1913 (+2) apos o lote de PRs do owner desta rodada (#4560 RTK cache_control, #4552 Cursor auto-import macOS, #4551 Codex /responses probe, #4554 Cursor Composer decode + o stack #3501 #4538/#4544/#4548). O fast-path do release nao roda check:complexity (so release->main). As 3 extracoes chatCore #3501 (#4538 recupera 4 leaves, #4544 failureUsage, #4548 claudeSystemRole+upstreamExecuteHeaders) sao PURAS/complexity-neutras (movem codigo p/ leaves sob o teto, chatCore ENCOLHE); o +2 vem dos condicionais NOVOS das features .32-portadas (#4554 decode de bloco visivel do Composer + #4551 probe do endpoint real Codex /responses). Medido com `node scripts/check/check-complexity.mjs` no tip 4b34a75fe. Crescimento de feature legitimo recem-TDD'd, nao regressao; reducao estrutural fica como debt (#3501).", - "_rebaseline_2026_06_21_v3833_reviewprs_r4": "Reconciliacao release-volatil 1906->1911 (+5) apos o lote /review-prs r4 (5 PRs de contribuidores mergeados em release/v3.8.33: #4557 health-polls, #4556 mobile-table, #4545 provider-wildcard, #4558 isHidden, #4489 sticky-weighted) + 1 merge concorrente de sessao paralela (#4565 quota perf). O fast-path do release nao roda check:complexity (so release->main), entao o ramo acumula sem rebaselinar. Condicionais NOVOS legitimos: #4545 (expandProviderWildcardsInCombo/Collection + wildcardMatch glob/registry branching em providerWildcard.ts/wildcardRouter.ts), #4489 (eligibility pass sticky-weighted: resolveWeightedStepGroups + isTargetSelectableForWeighted + renormalizacao em combo.ts), #4558 (filtro isHidden em buildAutoCandidates/virtualFactory) e #4565 (guardas de skip de quota_snapshots idle). Medido com `node scripts/check/check-complexity.mjs` no tip 39b2bbfea. Mesma familia dos rebaselines anteriores — crescimento de feature legitimo recem-TDD'd, nao regressao; reducao estrutural fica como debt (#3501).", - "_rebaseline_2026_06_21_v3833_cycle_open_stranded": "1905 -> 1906 (+1). Abertura do ciclo v3.8.33: o +1 vem dos 4 commits stranded na release/v3.8.32 (pós-merge da v3.8.32) trazidos via cherry-pick para release/v3.8.33 — isolado em #4483 (autoStrategy.ts evaluateQuotaCutoff/quotaPreflight.ts, guardas de quota-cutoff inerentemente ramificadas, opt-in default-OFF). main mede 1905; o .33 branch (main + cherry-picks) mede 1906. Crescimento de feature legitimo recém-mergeado, não regressão; redução fica como debt (#3501).", - "_rebaseline_2026_06_20_reviewprs_mine_r2": "Reconciliacao release-volatil 1900->1905 (+5) apos o lote /review-prs 'apenas minhas' r2 (17 PRs meus mergeados em release/v3.8.32). Breakdown medido com `node scripts/check/check-complexity.mjs` + diff por-arquivo eslint complexity JSON entre o tip pre-lote (9052c5a78 = 1902) e o tip pos-lote (1905): (a) +2 JA latentes no tip pre-lote (1900->1902), drift de merges concorrentes de outras sessoes ANTERIORES a este lote (nao introduzidos por mim) que o fast-path do release nao rebaselina (check:complexity so roda release->main); (b) +3 deste lote, isolados em DOIS arquivos: open-sse/translator/helpers/geminiHelper.ts +1 (convertOpenAIContentToParts passou de 80->93 linhas pela branch de audio do #4426 — max-lines-per-function, funcao de dispatch coesa por tipo de content part) e open-sse/translator/response/openai-to-gemini-sse.ts +2 (translator SSE NOVO do #4453, openAIChunkToGeminiChunk + convertOpenAIResponseToGemini em complexity 19 cada — conversores de chunk SSE inerentemente ramificados, levemente acima de 15). Crescimento de feature legitimo recem-TDD'd, nao regressao; refatorar feature recem-mergeada so para raspar +3 seria over-engineering arriscado. Reducao fica como debt.", - "_rebaseline_2026_06_20_reviewprs_v3831_batch": "Reconciliacao release-volatil 1896->1900 (+4): drift do lote /review-prs v3.8.31 (25 PRs A+B+C + merges concorrentes). Condicionais NOVOS legitimos — sobretudo #4381 (rotas /api/local/redis/{start,stop,status} detectRuntime + guardas + bifrost relay) e #4366 (classificacao de exhaustion de erro entre os 2 dispatchers de combo). Medido 1900 ESTAVEL em fd1391c0b E f46c69f2a com `node scripts/check/check-complexity.mjs` (commit concorrente intermediario foi complexity-neutro). O fast-path do release nao roda check:complexity (so release->main), entao o ramo acumula sem rebaselinar. Mesma familia dos rebaselines anteriores — crescimento de feature legitimo, nao regressao; valor final reconciliado no release->main.", - "_rebaseline_2026_06_20_postlote_concurrent_drift": "Reconciliacao release-volatil: 1895->1896 (+1). Drift de condicional NOVO de PRs mergeados pela sessao concorrente APOS o #4338 ratchetar para 1895 (#4355 pricing gpt-5.x-pro / #4364 cli active-context cred / #4363 compliance cleanup / #4358 mitm mask / #4332 injection-guard-16KB). O fast-path do release nao roda check:complexity (so release->main), entao o ramo acumula sem rebaselinar. Medido com `node scripts/check/check-complexity.mjs` no tip cdfd71c17. Mesma familia — crescimento de feature legitimo, nao regressao.", - "_note_2026_06_20_4371_chatcore_heap_leaf": "PR #4371 (extract checkHeapPressureGuard leaf, god-file decomposition start) e complexity-NEUTRO: handleChatCore so PERDE codigo e o novo heapPressure.ts checkHeapPressureGuard fica sob o teto — a contagem permanece 1896 (medido no tip mesclado com check-complexity.mjs).", - "_ratchet_2026_06_19_phasecombosetup_fix": "1896->1895 (-1, ratchet DOWN — melhoria, NAO reconciliacao). O #4336 reconciliou o drift do lote para 1896 INCLUINDO a violacao que o #4326 (ComboContext) introduziu: phaseComboSetup media complexity 17 (>15) porque a extracao concentrou os condicionais de pinning/ternarios numa funcao que estourava o teto (irônico p/ uma decomposicao). Este PR CORRIGE na origem — extrai resolveContextCachePin (helper do pinning), phaseComboSetup volta a <15 — baixando a contagem 1. Medido com `node scripts/check/check-complexity.mjs` no tip pos-#4336.", - "_rebaseline_2026_06_19_lote3_postdeploy_drift": "Reconciliacao release-volatil pos-merge do lote adicional (6 PRs apos o deploy): 1890->1896 (+6). Drift de condicionais NOVOS de #4327 (per-key USD usage quotas — apiKeyUsageLimits.ts + validation/policy branches), #4334 (cache-aware compression guard) e #4326 (phaseComboSetup extraido). Medido no tip real da release com `node scripts/check/check-complexity.mjs`. Mesma familia/justificativa do _rebaseline_2026_06_19_lote3_merge_drift abaixo — feature legitima, nao regressao.", - "_rebaseline_2026_06_19_lote3_merge_drift": "Reconciliacao release-volatil pos-merge do lote de 13 PRs (release/v3.8.30): 1888->1890 (+2). Drift de condicionais NOVOS trazidos por #4313 (5 harvested features — combo allowlist intersection, serviceKind filter) e #4323 (compression e2e — novos ramos em ultra/aggressive/gcf/strategySelector), merges que entraram DEPOIS do #4318 medir 1888. O fast-path do release nao roda check:complexity (so release->main), entao os ramos acumulam sem rebaselinar. Medido no tip real da release pos-merge com `node scripts/check/check-complexity.mjs`. Mesma familia dos rebaselines anteriores — crescimento de feature legitimo, nao regressao; reducao fica como debt de refactor dedicado.", - "_rebaseline_2026_06_19_bin_scope_wired": "Task 6A.11 (de verdade): o ESLINT_ARGS de check-complexity.mjs passava apenas `src open-sse` — o config eslint.complexity.config.mjs e este baseline JA documentavam o escopo src+open-sse+electron+bin, mas a edicao do scan nunca tinha sido aplicada (fake-green: o gate alegava cobrir bin/electron e nunca os varria). Agora ESLINT_ARGS passa `src open-sse electron bin`, casando o config. Medido: electron+bin contribuem 0 violacoes (electron so tem types.d.ts, ignorado; os 4 .ts de bin/ estao sob os thresholds) — o widening e 0-custo. O +1 (1887->1888) NAO vem do widening: e drift pre-existente em src/open-sse trazido pela base release/v3.8.30 a23d0d678 (merges do ciclo, incl. #4308 cache-hit-cost), que o fast-path do release nao rebaselina (check:complexity so roda no release->main). Mesma familia dos rebaselines anteriores — crescimento de feature legitimo, nao regressao.", - "_rebaseline_2026_06_19_4293_codex_spark_scope": "PR #4293 (isolate Codex Spark quota scope): +2 over the v3.8.30 baseline (1885->1887). Measured on the actual merged tree (release/v3.8.30 + #4293), not the PR's own estimate. The thin requestedModel-scoped Codex quota headroom/preflight branches needed so GPT-5.3-Codex-Spark and normal Codex are evaluated independently add the new conditional cost; heavy parsing/display logic was extracted to leaf helpers under the cap (codexQuotaScopes.ts, codexUsageQuotas.ts, codexFailover.ts). Legitimate feature growth, not regression; structural shrink remains debt.", - "_rebaseline_2026_06_19_v3830": "Re-baseline consciente: drift 1800->1885 (+85) do ciclo v3.8.25->v3.8.29 (round-9, ~130 PRs: combo split D7/D8, chatCore split, novos providers/modelos, cost-telemetry, MITM decrypt, remote-mode CLI). Medido no tip release/v3.8.30 (3e6be4701). Mesma familia dos re-baselines anteriores — crescimento de feature legitima, nao regressao. Reducao fica como debt de refactor dedicado.", - "_rebaseline_2026_06_13_v3825": "Re-baseline consciente: drift 1794->1800 (+6) do ciclo v3.8.24->v3.8.25 (features #3799-#3806). Mesma familia dos re-baselines anteriores — crescimento de feature legitima, nao regressao. Reducao fica como debt de refactor dedicado.", - "_rebaseline_2026_06_10": "Re-baseline consciente: 1739 foi medido na branch das Fases 0-6 (base ~v3.8.17); a v3.8.18 publicada ja carrega 1746 (provado: o commit-base 5f2722bd6, anterior a qualquer commit do ciclo v3.8.19, mede 1746 — funcoes complexas dos reworks RequestLoggerV2/stream/combo). Mesma familia dos re-baselines de eslintWarnings/file-size. Reducao = Fase 6A (2026-06-16).", - "_rebaseline_2026_06_13_6a11": "Re-baseline consciente Task 6A.11: escopo ampliado para src+open-sse+electron+bin (electron/bin contribuem 0 violacoes novas — todos os 4 arquivos .ts em bin/ estao abaixo dos thresholds). Drift 1746→1794 pre-existente de features mergeadas nos ciclos v3.8.22/v3.8.23 (nao causado por esta task). Congelado no valor real medido para destrancar o gate.", - "_rebaseline_2026_06_26_v3837_release": "1950->1963 (+13). v3.8.37 cycle drift surfaced by the release-green pre-flight (the Quality Ratchet does NOT run on PR->release fast-gates, so warnings/complexity accrued unmeasured across this cycle's 76 commits — provider adds DGrid/Pioneer/xAI, headroom proxy lifecycle #4649, ~50 SSE/translator fixes, Engine Combos #5062). Trust-but-verify: this release-finalize working tree touches ONLY CHANGELOG.md, docs/i18n/*/CHANGELOG.md mirrors, and these baselines — 0 production-code change, so all drift is inherited cycle drift (`any` warn-allowed in open-sse/ + tests/). Tighten via --require-tighten next cycle.", - "_rebaseline_2026_07_06_v3845_release_close": "2028->2035 (+7). v3.8.45 release close (generate-release Phase 0): drift herdado do merge burst final do ciclo (#6216 streaming fixes, #6251/#6253 dashboard UX, #6292 zero-width, fixes do pre-flight ce897453 — todos test/config/workflow-neutros em complexidade nova, verificado pelo validador no tip 5ecca12aa5). Tighten via --update next cycle." -} +{ + "_comment": "Catraca de complexidade (check-complexity.mjs, ESLint core rules complexity>=15 e max-lines-per-function>80 sobre src+open-sse+electron+bin via eslint.complexity.config.mjs). Conta total de violacoes; so pode cair. --update ratcheta.", + "count": 2028, + "_rebaseline_2026_07_04_6042_provider_manifest_ci": "2026->2028 (+2). Inherited release-tip drift surfaced while fixing PR #6042 fast-gates. Verified with the standalone ESLint complexity scan on BOTH the parent commit d35278b39 and this PR HEAD 82eedc4a4: each measures 2028, so the DAST/OpenAPI/test reconciliation commits are complexity-neutral. Tighten via --update next cycle.", + "_rebaseline_2026_07_03_v3844_ipfilter_release_green": "2015->2026 (+11). v3.8.44 cycle drift measured on release tip 32e4c906e during the #6131/#5975 release-green rebaseline. Inherited from the merge burst (Quality Ratchet does not run on PR->release fast-gates). route-edge-coverage +7 is my #5975 test comment; the rest is parallel-session drift. Tighten via --update next cycle.", + "_rebaseline_2026_07_03_6007_sidecar_manifest": "2007->2015. PR #6007 (re-cut) adds sidecar/provider-manifest header wiring and the public manifest URL helper; the feature files themselves introduce 0 new complexity violations (check-complexity flags none in providerPluginManifestUrl.ts). The +8 vs the 2007 release baseline is inherited release/v3.8.44 drift absorbed at merge (check:complexity measures 2015 on the current release tip). Tighten via --update next cycle / at /generate-release Phase 0.", + "_rebaseline_2026_07_02_v3844_ci_observed": "2006->2007 (+1). Local Ubuntu measures 2006 on this tree; the GitHub fast-gates runner measures 2007 (same local-vs-CI off-by-one already documented in _rebaseline_2026_06_26_v3838_release_fast_gate). Use the CI-observed value so the gate is deterministic where it actually runs.", + "_rebaseline_2026_07_02_v3844_post_5939": "2003->2006 (+3). Inherited drift from the release/v3.8.44 merges after 3a3d618fe (#5809 audio translations et al.), surfaced by PR fix#5959: check:complexity measures 2006 on the pristine base (cbd08ef78) WITH AND WITHOUT this PR's one-line CLI change (verified by reverting the file and re-measuring) — the PR is complexity-net-zero. Tighten via --update next cycle.", + "_rebaseline_2026_07_02_v3844_merge_burst": "1995->2003 (+8). Inherited v3.8.44 cycle drift surfaced by PR #5939: check:complexity measures 2003 on BOTH the pristine release tip (3a3d618fe) and this PR's merged HEAD — identical, so all +8 came from the 2026-07-02 merge burst into release/v3.8.44 (#5933 codex schema, #5950 OCR, #5904/#5920 combo, #6000/#6008 executor refactors, etc.) merged while the fast-gates queue was base-red (file-size #5933). PR #5939 itself was verified complexity-net-zero during its own CI cycle (DiscoveryPageClient refactored into hooks/sub-components to stay under max-lines-per-function). Tighten via --update next cycle.", + "_rebaseline_2026_07_02_5798_release_green": "1982->1995 (+13). Inherited v3.8.43 cycle drift surfaced by the release-green unblock #5798 / PR #5896: check:complexity measures 1995 on BOTH the pristine release tip (0d3875a98) and this PR's HEAD — identical, so all +13 came from the 2026-07-01/02 merge burst (providers/usage/dashboard fixes merged via --admin while the fast-gates queue was base-red). This PR touches only gate scripts, docs, baselines and test files — 0 production logic. Tighten via --update next cycle.", + "_rebaseline_2026_07_01_v3843_release": "1981->1982 (+1). v3.8.43 cycle drift, surfaced after check:mutation-test-coverage was fixed (it was masked behind that earlier step in the Fast Quality Gates chain). 1982 = the value measured by check:complexity on BOTH fce85136c (release tip) and 6d7060e21 (release + the 5 CI fixes) — identical, so all +1 is inherited cycle drift; the fixes touch only test files + linkify.ts safeHttpHref (cyclomatic ~4, well under the >=15 threshold, 0 new violations) + config JSON. Tighten via --update next cycle.", + "_rebaseline_2026_06_28_v3840_5237_reconcile": "1980->1981 (+1). Inherited release/v3.8.40 drift surfaced while merging PR #5237 (impersonation-UA refresh) — the +1 is present on the pristine release tip (d8a392a47) WITHOUT #5237's changes, so it is #5222 (antigravity fallback-LRU retry) / #5221 (command-code) growth that merged via --admin without ratcheting complexity (the PR->release fast-gates do not run check:complexity). #5237 itself is complexity-net-zero: its only edits are a single UA constant, a regenerated golden snapshot, and baseline JSONs. Structural reduction tracked in #3501.", + "_rebaseline_2026_06_27_v3838_release": "1978->1980 (+2). v3.8.38 cycle-close drift surfaced by the release-green pre-flight (check:complexity does NOT run on PR->release fast-gates). +2 from late-cycle feature/fix merges (compression fidelity-gate steps #5143, SSE hardening). Release-finalize working tree touches ONLY CHANGELOG.md + i18n mirrors + the 2 baseline JSONs — 0 production-code change. Structural reduction tracked in #3501.", + "_rebaseline_2026_06_26_v3838_ownerprs_batch": "1972->1978 (+6). Drift do lote de merges de PRs do dono + contribuidores em release/v3.8.38 (sessao /review-prs): #4845 (antigravity convertGeminiToOpenAI), #5105 (executor zenmux-free), #5020 (executor grok-cli), #4940 (usage dedupe guard), #5093 (resilience: quota cutoff/gemini mime/model-lockout cooldown), #5015 (quota hydration + auto-combo scoping). Cada um e crescimento de feature/fix legitimo recem-TDD'd, nao regressao; o gate check:complexity NAO roda no fast-path PR->release, entao o ramo acumula sem rebaselinar (mesma familia dos rebaselines anteriores). #5121 cookie-dedup foi mantido complexity-NEUTRO via extracao do helper findExistingCookieConnection. Reducao estrutural fica como debt (#3501).", + "_rebaseline_2026_06_26_v3838_release_fast_gate": "1963->1972 (+9). Reconciles inherited release/v3.8.38 drift surfaced by PR #5124. Local origin/release/v3.8.38 measured 1971 and this PR also measured 1971 after refactoring the new JSON-body SSE sniffing path, while the GitHub Ubuntu fast gate measured 1972; use the CI-observed value so the release branch gate is deterministic. The streaming fix is complexity-net-zero relative to the local release base. The release fast-path does not consistently ratchet complexity between release-cycle merges; keep structural reductions as separate debt.", + "_rebaseline_2026_06_25_v3836_release": "Reconciliacao release-volatil 1920->1950 (+30) no fechamento do ciclo v3.8.36, surfada pelo CI da fix-PR #5029 (a catraca de complexidade NAO roda no fast-path PR->release nem foi medida no release PR #4854 — Quality Ratchet foi SKIPPED la — so PR->main, entao o ramo acumulou os 137 commits sem rebaselinar). O +30 e drift de condicionais NOVOS das features legitimas do ciclo: Quota-Share Fase 2/3 (estrategia dedicada DRR+P2C, multi-window buckets, concurrency control, headroom, saturacao proativa — #4885/#4907/#4908/#4927/#4928/#4929/#4939/#4965/#4967/#4970), task-aware + Fusion combo (#4945/#4652), e ramos de provider/translator de contribuidores. A god-file decomposition #3501 e PURA (move codigo p/ leaves, complexity-neutra). Verificado que esta fix-PR (#5029) toca SO scripts/build/pack-artifact-policy.ts (array de strings), tests/integration/resilience-http-e2e.test.ts (2 keys) e os 2 baselines json — contribui 0 ao gate que varre src+open-sse+electron+bin. Mesma familia dos rebaselines anteriores — crescimento de feature legitimo recem-TDD'd, nao regressao; reducao estrutural fica como debt (#3501).", + "_rebaseline_2026_06_23_v3835_release": "Reconciliacao release-volatil 1916->1920 (+4) no fechamento do ciclo v3.8.35, surfada pelo pre-flight check:release-green (a catraca de complexidade NAO roda no fast-path PR->release, so release->main, entao o ramo acumula sem rebaselinar). O +4 e drift de condicionais NOVOS dos merges de contribuidor/feature deste ciclo (Compression Phase 4 #4694/#4707/#4716/#4720, combos auto-promote #4774, tier no-auth #4753, deepseek-web tool-fold #4756, dedupe provider nodes #4768). Verificado que o trabalho de release-finalize desta sessao toca SO docs/*.md (THREAT_MODEL), CHANGELOG.md, baselines e 1 linha de string em scripts/check/check-fabricated-docs.mjs (fora do escopo src+open-sse+electron+bin que o gate varre) — contribui 0. Mesma familia dos rebaselines anteriores — crescimento de feature legitimo recem-TDD'd, nao regressao; reducao estrutural fica como debt (#3501).", + "_rebaseline_2026_06_23_v3834_release": "Reconciliacao release-volatil 1915->1916 (+1) no fechamento do ciclo v3.8.34. check:complexity NAO roda no fast-path PR->release (so release->main), entao o ramo acumula sem rebaselinar; surfou no full CI do release PR (run em c98e7ff6d). O +1 e drift de condicional NOVO de merge de contribuidor do ciclo (features quota/usage/opencode-go/M365). Verificado que o commit de release-finalize NAO adiciona complexity: toca CHANGELOG/baseline/mirrors/3 testes + 1 linha de regex em opencodeOllamaUsage.ts (sem novo ramo) + reorder de dados no reka registry — local mede 1916 com ou sem essa mudanca. Mesma familia dos rebaselines anteriores — crescimento de feature legitimo, nao regressao; reducao estrutural fica como debt (#3501).", + "_rebaseline_2026_06_21_v3833_4537_nested_combo": "Reconciliacao 1913->1915 (+2) do PR #4537 (nestedComboMode execute — black-box combo-ref execution). O +2 vem do branch novo de dispatch nested em handleComboChat (combo.ts: normalizeNestedComboMode + executeModeUnits/hasExecutableComboRef + o ramo simpleExecuteStrategies que roda resolveComboRuntimeUnits com recursion caps depth/cycle/budget) — ramos condicionais no chokepoint de dispatch do combo. Medido com `node scripts/check/check-complexity.mjs` no estado merged. Crescimento de feature legitimo recem-TDD'd, nao regressao; reducao estrutural fica como debt (#3501).", + "_rebaseline_2026_06_21_v3833_reviewprs_r4_owner": "Reconciliacao release-volatil 1911->1913 (+2) apos o lote de PRs do owner desta rodada (#4560 RTK cache_control, #4552 Cursor auto-import macOS, #4551 Codex /responses probe, #4554 Cursor Composer decode + o stack #3501 #4538/#4544/#4548). O fast-path do release nao roda check:complexity (so release->main). As 3 extracoes chatCore #3501 (#4538 recupera 4 leaves, #4544 failureUsage, #4548 claudeSystemRole+upstreamExecuteHeaders) sao PURAS/complexity-neutras (movem codigo p/ leaves sob o teto, chatCore ENCOLHE); o +2 vem dos condicionais NOVOS das features .32-portadas (#4554 decode de bloco visivel do Composer + #4551 probe do endpoint real Codex /responses). Medido com `node scripts/check/check-complexity.mjs` no tip 4b34a75fe. Crescimento de feature legitimo recem-TDD'd, nao regressao; reducao estrutural fica como debt (#3501).", + "_rebaseline_2026_06_21_v3833_reviewprs_r4": "Reconciliacao release-volatil 1906->1911 (+5) apos o lote /review-prs r4 (5 PRs de contribuidores mergeados em release/v3.8.33: #4557 health-polls, #4556 mobile-table, #4545 provider-wildcard, #4558 isHidden, #4489 sticky-weighted) + 1 merge concorrente de sessao paralela (#4565 quota perf). O fast-path do release nao roda check:complexity (so release->main), entao o ramo acumula sem rebaselinar. Condicionais NOVOS legitimos: #4545 (expandProviderWildcardsInCombo/Collection + wildcardMatch glob/registry branching em providerWildcard.ts/wildcardRouter.ts), #4489 (eligibility pass sticky-weighted: resolveWeightedStepGroups + isTargetSelectableForWeighted + renormalizacao em combo.ts), #4558 (filtro isHidden em buildAutoCandidates/virtualFactory) e #4565 (guardas de skip de quota_snapshots idle). Medido com `node scripts/check/check-complexity.mjs` no tip 39b2bbfea. Mesma familia dos rebaselines anteriores — crescimento de feature legitimo recem-TDD'd, nao regressao; reducao estrutural fica como debt (#3501).", + "_rebaseline_2026_06_21_v3833_cycle_open_stranded": "1905 -> 1906 (+1). Abertura do ciclo v3.8.33: o +1 vem dos 4 commits stranded na release/v3.8.32 (pós-merge da v3.8.32) trazidos via cherry-pick para release/v3.8.33 — isolado em #4483 (autoStrategy.ts evaluateQuotaCutoff/quotaPreflight.ts, guardas de quota-cutoff inerentemente ramificadas, opt-in default-OFF). main mede 1905; o .33 branch (main + cherry-picks) mede 1906. Crescimento de feature legitimo recém-mergeado, não regressão; redução fica como debt (#3501).", + "_rebaseline_2026_06_20_reviewprs_mine_r2": "Reconciliacao release-volatil 1900->1905 (+5) apos o lote /review-prs 'apenas minhas' r2 (17 PRs meus mergeados em release/v3.8.32). Breakdown medido com `node scripts/check/check-complexity.mjs` + diff por-arquivo eslint complexity JSON entre o tip pre-lote (9052c5a78 = 1902) e o tip pos-lote (1905): (a) +2 JA latentes no tip pre-lote (1900->1902), drift de merges concorrentes de outras sessoes ANTERIORES a este lote (nao introduzidos por mim) que o fast-path do release nao rebaselina (check:complexity so roda release->main); (b) +3 deste lote, isolados em DOIS arquivos: open-sse/translator/helpers/geminiHelper.ts +1 (convertOpenAIContentToParts passou de 80->93 linhas pela branch de audio do #4426 — max-lines-per-function, funcao de dispatch coesa por tipo de content part) e open-sse/translator/response/openai-to-gemini-sse.ts +2 (translator SSE NOVO do #4453, openAIChunkToGeminiChunk + convertOpenAIResponseToGemini em complexity 19 cada — conversores de chunk SSE inerentemente ramificados, levemente acima de 15). Crescimento de feature legitimo recem-TDD'd, nao regressao; refatorar feature recem-mergeada so para raspar +3 seria over-engineering arriscado. Reducao fica como debt.", + "_rebaseline_2026_06_20_reviewprs_v3831_batch": "Reconciliacao release-volatil 1896->1900 (+4): drift do lote /review-prs v3.8.31 (25 PRs A+B+C + merges concorrentes). Condicionais NOVOS legitimos — sobretudo #4381 (rotas /api/local/redis/{start,stop,status} detectRuntime + guardas + bifrost relay) e #4366 (classificacao de exhaustion de erro entre os 2 dispatchers de combo). Medido 1900 ESTAVEL em fd1391c0b E f46c69f2a com `node scripts/check/check-complexity.mjs` (commit concorrente intermediario foi complexity-neutro). O fast-path do release nao roda check:complexity (so release->main), entao o ramo acumula sem rebaselinar. Mesma familia dos rebaselines anteriores — crescimento de feature legitimo, nao regressao; valor final reconciliado no release->main.", + "_rebaseline_2026_06_20_postlote_concurrent_drift": "Reconciliacao release-volatil: 1895->1896 (+1). Drift de condicional NOVO de PRs mergeados pela sessao concorrente APOS o #4338 ratchetar para 1895 (#4355 pricing gpt-5.x-pro / #4364 cli active-context cred / #4363 compliance cleanup / #4358 mitm mask / #4332 injection-guard-16KB). O fast-path do release nao roda check:complexity (so release->main), entao o ramo acumula sem rebaselinar. Medido com `node scripts/check/check-complexity.mjs` no tip cdfd71c17. Mesma familia — crescimento de feature legitimo, nao regressao.", + "_note_2026_06_20_4371_chatcore_heap_leaf": "PR #4371 (extract checkHeapPressureGuard leaf, god-file decomposition start) e complexity-NEUTRO: handleChatCore so PERDE codigo e o novo heapPressure.ts checkHeapPressureGuard fica sob o teto — a contagem permanece 1896 (medido no tip mesclado com check-complexity.mjs).", + "_ratchet_2026_06_19_phasecombosetup_fix": "1896->1895 (-1, ratchet DOWN — melhoria, NAO reconciliacao). O #4336 reconciliou o drift do lote para 1896 INCLUINDO a violacao que o #4326 (ComboContext) introduziu: phaseComboSetup media complexity 17 (>15) porque a extracao concentrou os condicionais de pinning/ternarios numa funcao que estourava o teto (irônico p/ uma decomposicao). Este PR CORRIGE na origem — extrai resolveContextCachePin (helper do pinning), phaseComboSetup volta a <15 — baixando a contagem 1. Medido com `node scripts/check/check-complexity.mjs` no tip pos-#4336.", + "_rebaseline_2026_06_19_lote3_postdeploy_drift": "Reconciliacao release-volatil pos-merge do lote adicional (6 PRs apos o deploy): 1890->1896 (+6). Drift de condicionais NOVOS de #4327 (per-key USD usage quotas — apiKeyUsageLimits.ts + validation/policy branches), #4334 (cache-aware compression guard) e #4326 (phaseComboSetup extraido). Medido no tip real da release com `node scripts/check/check-complexity.mjs`. Mesma familia/justificativa do _rebaseline_2026_06_19_lote3_merge_drift abaixo — feature legitima, nao regressao.", + "_rebaseline_2026_06_19_lote3_merge_drift": "Reconciliacao release-volatil pos-merge do lote de 13 PRs (release/v3.8.30): 1888->1890 (+2). Drift de condicionais NOVOS trazidos por #4313 (5 harvested features — combo allowlist intersection, serviceKind filter) e #4323 (compression e2e — novos ramos em ultra/aggressive/gcf/strategySelector), merges que entraram DEPOIS do #4318 medir 1888. O fast-path do release nao roda check:complexity (so release->main), entao os ramos acumulam sem rebaselinar. Medido no tip real da release pos-merge com `node scripts/check/check-complexity.mjs`. Mesma familia dos rebaselines anteriores — crescimento de feature legitimo, nao regressao; reducao fica como debt de refactor dedicado.", + "_rebaseline_2026_06_19_bin_scope_wired": "Task 6A.11 (de verdade): o ESLINT_ARGS de check-complexity.mjs passava apenas `src open-sse` — o config eslint.complexity.config.mjs e este baseline JA documentavam o escopo src+open-sse+electron+bin, mas a edicao do scan nunca tinha sido aplicada (fake-green: o gate alegava cobrir bin/electron e nunca os varria). Agora ESLINT_ARGS passa `src open-sse electron bin`, casando o config. Medido: electron+bin contribuem 0 violacoes (electron so tem types.d.ts, ignorado; os 4 .ts de bin/ estao sob os thresholds) — o widening e 0-custo. O +1 (1887->1888) NAO vem do widening: e drift pre-existente em src/open-sse trazido pela base release/v3.8.30 a23d0d678 (merges do ciclo, incl. #4308 cache-hit-cost), que o fast-path do release nao rebaselina (check:complexity so roda no release->main). Mesma familia dos rebaselines anteriores — crescimento de feature legitimo, nao regressao.", + "_rebaseline_2026_06_19_4293_codex_spark_scope": "PR #4293 (isolate Codex Spark quota scope): +2 over the v3.8.30 baseline (1885->1887). Measured on the actual merged tree (release/v3.8.30 + #4293), not the PR's own estimate. The thin requestedModel-scoped Codex quota headroom/preflight branches needed so GPT-5.3-Codex-Spark and normal Codex are evaluated independently add the new conditional cost; heavy parsing/display logic was extracted to leaf helpers under the cap (codexQuotaScopes.ts, codexUsageQuotas.ts, codexFailover.ts). Legitimate feature growth, not regression; structural shrink remains debt.", + "_rebaseline_2026_06_19_v3830": "Re-baseline consciente: drift 1800->1885 (+85) do ciclo v3.8.25->v3.8.29 (round-9, ~130 PRs: combo split D7/D8, chatCore split, novos providers/modelos, cost-telemetry, MITM decrypt, remote-mode CLI). Medido no tip release/v3.8.30 (3e6be4701). Mesma familia dos re-baselines anteriores — crescimento de feature legitima, nao regressao. Reducao fica como debt de refactor dedicado.", + "_rebaseline_2026_06_13_v3825": "Re-baseline consciente: drift 1794->1800 (+6) do ciclo v3.8.24->v3.8.25 (features #3799-#3806). Mesma familia dos re-baselines anteriores — crescimento de feature legitima, nao regressao. Reducao fica como debt de refactor dedicado.", + "_rebaseline_2026_06_10": "Re-baseline consciente: 1739 foi medido na branch das Fases 0-6 (base ~v3.8.17); a v3.8.18 publicada ja carrega 1746 (provado: o commit-base 5f2722bd6, anterior a qualquer commit do ciclo v3.8.19, mede 1746 — funcoes complexas dos reworks RequestLoggerV2/stream/combo). Mesma familia dos re-baselines de eslintWarnings/file-size. Reducao = Fase 6A (2026-06-16).", + "_rebaseline_2026_06_13_6a11": "Re-baseline consciente Task 6A.11: escopo ampliado para src+open-sse+electron+bin (electron/bin contribuem 0 violacoes novas — todos os 4 arquivos .ts em bin/ estao abaixo dos thresholds). Drift 1746→1794 pre-existente de features mergeadas nos ciclos v3.8.22/v3.8.23 (nao causado por esta task). Congelado no valor real medido para destrancar o gate.", + "_rebaseline_2026_06_26_v3837_release": "1950->1963 (+13). v3.8.37 cycle drift surfaced by the release-green pre-flight (the Quality Ratchet does NOT run on PR->release fast-gates, so warnings/complexity accrued unmeasured across this cycle's 76 commits — provider adds DGrid/Pioneer/xAI, headroom proxy lifecycle #4649, ~50 SSE/translator fixes, Engine Combos #5062). Trust-but-verify: this release-finalize working tree touches ONLY CHANGELOG.md, docs/i18n/*/CHANGELOG.md mirrors, and these baselines — 0 production-code change, so all drift is inherited cycle drift (`any` warn-allowed in open-sse/ + tests/). Tighten via --require-tighten next cycle." +} + diff --git a/docs/reference/PROVIDER_PLUGIN_MANIFEST.md b/docs/reference/PROVIDER_PLUGIN_MANIFEST.md index 9f91dc39eda..fabb8815fcd 100644 --- a/docs/reference/PROVIDER_PLUGIN_MANIFEST.md +++ b/docs/reference/PROVIDER_PLUGIN_MANIFEST.md @@ -15,6 +15,10 @@ executor code, OAuth defaults, headers, or process environment state. The same manifest is available over HTTP at `GET /api/v1/provider-plugin-manifest` for sidecars that run out-of-process. +Code that needs to consume the manifest over HTTP should use +`open-sse/config/providerPluginManifestClient.ts` instead of hard-coding the +route. The client resolves `OMNIROUTE_PROVIDER_MANIFEST_URL`, then an explicit +base URL, then the local OmniRoute API default. OmniRoute advertises that URL to Bifrost and CLIProxyAPI via the `X-OmniRoute-Provider-Manifest-Url` request header. Set diff --git a/open-sse/config/providerPluginManifestClient.ts b/open-sse/config/providerPluginManifestClient.ts new file mode 100644 index 00000000000..411941b63c6 --- /dev/null +++ b/open-sse/config/providerPluginManifestClient.ts @@ -0,0 +1,87 @@ +import type { + ProviderPluginManifest, + ProviderPluginManifestEntry, +} from "./providerPluginManifest.ts"; + +export const PROVIDER_PLUGIN_MANIFEST_PATH = "/api/v1/provider-plugin-manifest"; +export const PROVIDER_PLUGIN_MANIFEST_ENV = "OMNIROUTE_PROVIDER_MANIFEST_URL"; + +export interface ProviderPluginManifestClientOptions { + baseUrl?: string | null; + manifestUrl?: string | null; + fetchImpl?: typeof fetch; + signal?: AbortSignal | null; +} + +function trimTrailingSlash(value: string): string { + return value.replace(/\/+$/, ""); +} + +export function resolveProviderPluginManifestUrl( + options: Pick = {}, +): string { + const explicitUrl = options.manifestUrl?.trim(); + if (explicitUrl) return explicitUrl; + + const envUrl = process.env[PROVIDER_PLUGIN_MANIFEST_ENV]?.trim(); + if (envUrl) return envUrl; + + const baseUrl = options.baseUrl?.trim(); + if (baseUrl) { + return `${trimTrailingSlash(baseUrl)}${PROVIDER_PLUGIN_MANIFEST_PATH}`; + } + + const host = process.env.HOST || "127.0.0.1"; + const port = process.env.PORT || process.env.DASHBOARD_PORT || process.env.API_PORT || "20128"; + const protocol = process.env.OMNIROUTE_PUBLIC_PROTOCOL || "http"; + return `${protocol}://${host}:${port}${PROVIDER_PLUGIN_MANIFEST_PATH}`; +} + +export async function fetchProviderPluginManifest( + options: ProviderPluginManifestClientOptions = {}, +): Promise { + const fetcher = options.fetchImpl ?? fetch; + const url = resolveProviderPluginManifestUrl(options); + const response = await fetcher(url, { + headers: { Accept: "application/json" }, + signal: options.signal ?? undefined, + }); + + if (!response.ok) { + throw new Error(`Provider plugin manifest request failed: HTTP ${response.status}`); + } + + const manifest = (await response.json()) as ProviderPluginManifest; + if (manifest.schemaVersion !== 1 || !Array.isArray(manifest.providers)) { + throw new Error("Provider plugin manifest response is not schemaVersion 1"); + } + + return manifest; +} + +export function getProviderPluginManifestEntryForModelFromManifest( + manifest: ProviderPluginManifest, + model: string | undefined, +): ProviderPluginManifestEntry | null { + if (!model) return null; + + const providerPrefix = model.includes("/") ? model.split("/", 1)[0] : ""; + if (providerPrefix) { + const prefixed = manifest.providers.find( + (provider) => provider.id === providerPrefix || provider.alias === providerPrefix, + ); + if (prefixed) return prefixed; + } + + return manifest.providers.find((provider) => + provider.models.some((candidate) => candidate.id === model), + ) ?? null; +} + +export async function fetchProviderPluginManifestEntryForModel( + model: string | undefined, + options: ProviderPluginManifestClientOptions = {}, +): Promise { + const manifest = await fetchProviderPluginManifest(options); + return getProviderPluginManifestEntryForModelFromManifest(manifest, model); +} diff --git a/package.json b/package.json index 8f62f43fa33..a95ce86bc90 100644 --- a/package.json +++ b/package.json @@ -189,6 +189,7 @@ "test:heap": "node --expose-gc --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit tests/integration/heap-growth.test.ts", "test:chaos": "cross-env RUN_CHAOS_INT=1 node --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=1 tests/integration/resilience-chaos.test.ts", "test:e2e": "node scripts/dev/run-playwright-tests.mjs test tests/e2e/*.spec.ts", + "test:fingerprint:e2e": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --import tsx --import ./open-sse/utils/setupPolyfill.ts --test --test-force-exit --test-concurrency=1 tests/e2e/fingerprint-expansion.test.ts", "test:protocols:e2e": "node scripts/dev/run-protocol-clients-tests.mjs", "test:vitest": "vitest run --config vitest.mcp.config.ts", "test:vitest:ui": "vitest run --config vitest.config.ts tests/unit/ui", diff --git a/scripts/check/check-complexity.mjs b/scripts/check/check-complexity.mjs index 2e0c89938bc..3d1b9540d64 100644 --- a/scripts/check/check-complexity.mjs +++ b/scripts/check/check-complexity.mjs @@ -23,6 +23,8 @@ const BASELINE_PATH = path.resolve( ); const UPDATE = process.argv.includes("--update"); const CONFIG_PATH = path.join(ROOT, "eslint.complexity.config.mjs"); +const ESLINT_BIN = path.join(ROOT, "node_modules/eslint/bin/eslint.js"); +const NPX_BIN = process.platform === "win32" ? "npx.cmd" : "npx"; // Exported for the gate's own unit test (tests/unit/build/check-complexity.test.ts), which // locks the scan scope to the one documented in eslint.complexity.config.mjs `files` and in // complexity-baseline.json. The positional paths MUST match that scope (src+open-sse+electron+bin) @@ -52,9 +54,14 @@ export function evaluateComplexity(current, baseline) { function measureComplexityCount() { let stdout; try { - stdout = execFileSync("npx", ["--yes", ...ESLINT_ARGS], { + const command = fs.existsSync(ESLINT_BIN) ? process.execPath : NPX_BIN; + const args = fs.existsSync(ESLINT_BIN) + ? [ESLINT_BIN, ...ESLINT_ARGS.slice(1)] + : ["--yes", ...ESLINT_ARGS]; + stdout = execFileSync(command, args, { encoding: "utf8", maxBuffer: 64 * 1024 * 1024, + shell: !fs.existsSync(ESLINT_BIN) && process.platform === "win32", }); } catch (err) { // ESLint sai com código !=0 quando há erros (e nossas regras são "error"); o relatório diff --git a/scripts/check/check-test-discovery.mjs b/scripts/check/check-test-discovery.mjs index 299e38b2079..9e1abfbc104 100644 --- a/scripts/check/check-test-discovery.mjs +++ b/scripts/check/check-test-discovery.mjs @@ -94,6 +94,8 @@ export const COLLECTORS = [ { glob: "tests/integration/combo-live/*.live.test.ts", sources: ["package.json"] }, // Node native runner — test:system { glob: "tests/e2e/system-failover.test.ts", sources: ["package.json"] }, + // Node native runner — test:fingerprint:e2e + { glob: "tests/e2e/fingerprint-expansion.test.ts", sources: ["package.json"] }, // vitest.mcp.config.ts — test:vitest { glob: "open-sse/mcp-server/__tests__/**/*.test.ts", sources: ["vitest.mcp.config.ts"] }, { glob: "open-sse/services/autoCombo/__tests__/**/*.test.ts", sources: ["vitest.mcp.config.ts"] }, diff --git a/src/app/(dashboard)/dashboard/HomePageClient.tsx b/src/app/(dashboard)/dashboard/HomePageClient.tsx index 1f376c147aa..1970c25a236 100644 --- a/src/app/(dashboard)/dashboard/HomePageClient.tsx +++ b/src/app/(dashboard)/dashboard/HomePageClient.tsx @@ -1092,8 +1092,7 @@ export default function HomePageClient({ machineId }: HomePageClientProps) {

{t.rich("step1Desc", { endpoint: (chunks) => ( - {chunks} diff --git a/src/app/(dashboard)/dashboard/providers/[id]/components/CoolingConnectionsPanel.tsx b/src/app/(dashboard)/dashboard/providers/[id]/components/CoolingConnectionsPanel.tsx index 63df0643f38..5940500fced 100644 --- a/src/app/(dashboard)/dashboard/providers/[id]/components/CoolingConnectionsPanel.tsx +++ b/src/app/(dashboard)/dashboard/providers/[id]/components/CoolingConnectionsPanel.tsx @@ -1,94 +1,95 @@ -"use client"; - -/** - * CoolingConnectionsPanel — Dashboard readout of connections currently in a - * persisted 429 cooldown. Sourced from `useProviderConnections().connections` - * filtered on `rateLimitedUntil`. Live human-readable countdown via the - * client-safe `formatResetCountdown` helper in `@/shared/utils/formatting`. - * - * Why this exists: Fix A (per-account 429 cascade not persisting) writes the - * cooldown to `provider_connections.rate_limited_until` so the cascade - * survives the request boundary and process restart. Without a visible - * indicator the user has no way to see "OmniRoute learned that this key is - * exhausted — and for how long". This panel makes the lesson visible. - * - * Acceptance criteria (Issue #1, fix scope D): - * 1. Filters `connections` to those with a future `rateLimitedUntil`. - * 2. Shows connection name + reset countdown. - * 3. Re-evaluates every second so countdowns tick down. - * 4. Renders nothing when no connection is cooling. - * 5. Uses the same connection-shape type as ConnectionRow so the data flow - * stays consistent with the rest of the dashboard. - */ - -import { useEffect, useState } from "react"; -import { formatResetCountdown } from "@/shared/utils/formatting"; +"use client"; + +/** + * CoolingConnectionsPanel — Dashboard readout of connections currently in a + * persisted 429 cooldown. Sourced from `useProviderConnections().connections` + * filtered on `rateLimitedUntil`. Live human-readable countdown via the + * existing `formatResetCountdown` helper re-exported by `@/lib/localDb`. + * + * Why this exists: Fix A (per-account 429 cascade not persisting) writes the + * cooldown to `provider_connections.rate_limited_until` so the cascade + * survives the request boundary and process restart. Without a visible + * indicator the user has no way to see "OmniRoute learned that this key is + * exhausted — and for how long". This panel makes the lesson visible. + * + * Acceptance criteria (Issue #1, fix scope D): + * 1. Filters `connections` to those with a future `rateLimitedUntil`. + * 2. Shows connection name + reset countdown. + * 3. Re-evaluates every second so countdowns tick down. + * 4. Renders nothing when no connection is cooling. + * 5. Uses the same connection-shape type as ConnectionRow so the data flow + * stays consistent with the rest of the dashboard. + */ + +import { useEffect, useState } from "react"; +import Card from "@/shared/components/Card"; +import { formatResetCountdown } from "@/lib/localDb"; import type { ConnectionRowConnection } from "./ConnectionRow"; export interface CoolingConnectionsPanelProps { readonly connections: readonly ConnectionRowConnection[]; } - -function isCoolingNow(connection: ConnectionRowConnection, now: number): boolean { - if (!connection.rateLimitedUntil) return false; - const until = new Date(connection.rateLimitedUntil).getTime(); - return Number.isFinite(until) && until > now; -} - -export default function CoolingConnectionsPanel(props: CoolingConnectionsPanelProps) { + +function isCoolingNow(connection: ConnectionRowConnection, now: number): boolean { + if (!connection.rateLimitedUntil) return false; + const until = new Date(connection.rateLimitedUntil).getTime(); + return Number.isFinite(until) && until > now; +} + +export default function CoolingConnectionsPanel( + props: CoolingConnectionsPanelProps, +) { const { connections } = props; - // Tick once per second so the human-readable countdown updates. - const [now, setNow] = useState(() => Date.now()); - useEffect(() => { - const id = setInterval(() => setNow(Date.now()), 1000); - return () => clearInterval(id); - }, []); - - const cooling = connections.filter((c) => isCoolingNow(c, now)); - if (cooling.length === 0) return null; - - return ( -

-
- -

- Currently cooling ({cooling.length}) -

-
-

- These connections returned a 429 (rate-limit) on their last request. OmniRoute will skip - them until the timer expires — no manual disable required. -

-
    - {cooling.map((c) => { - const until = c.rateLimitedUntil!; - const label = - c.displayName || - c.name || - c.email || - (c.id ? `connection ${c.id.slice(0, 8)}` : "connection"); - return ( -
  • - {label} - - {formatResetCountdown(until)} - -
  • - ); - })} -
-
- ); + // Tick once per second so the human-readable countdown updates. + const [now, setNow] = useState(() => Date.now()); + useEffect(() => { + const id = setInterval(() => setNow(Date.now()), 1000); + return () => clearInterval(id); + }, []); + + const cooling = connections.filter((c) => isCoolingNow(c, now)); + if (cooling.length === 0) return null; + + return ( + +
+ +

+ Currently cooling ({cooling.length}) +

+
+

+ These connections returned a 429 (rate-limit) on their last request. + OmniRoute will skip them until the timer expires — no manual disable + required. +

+
    + {cooling.map((c) => { + const until = c.rateLimitedUntil!; + const label = + c.displayName || c.name || c.email || (c.id ? `connection ${c.id.slice(0, 8)}` : "connection"); + return ( +
  • + {label} + + {formatResetCountdown(until)} + +
  • + ); + })} +
+
+ ); } diff --git a/tests/unit/dast-method-not-allowed.test.ts b/tests/unit/dast-method-not-allowed.test.ts index 409b776e417..20b69783a21 100644 --- a/tests/unit/dast-method-not-allowed.test.ts +++ b/tests/unit/dast-method-not-allowed.test.ts @@ -32,6 +32,12 @@ test("raw HTTP guard rejects high-risk unsupported methods before Next.js handle url: "/api/keys/0", allow: "GET, PATCH, DELETE", }, + { + label: "key devices TRACE", + method: "TRACE", + url: "/api/keys/0/devices", + allow: "GET", + }, ]; for (const testCase of cases) { @@ -77,6 +83,10 @@ test("raw HTTP guard allows documented methods through", () => { maybeHandleDisallowedMethod({ method: "OPTIONS", url: "/api/keys" }, response), false ); + assert.equal( + maybeHandleDisallowedMethod({ method: "GET", url: "/api/keys/0/devices" }, response), + false + ); assert.equal( maybeHandleDisallowedMethod({ method: "QUERY", url: "/api/health/ping" }, response), false @@ -122,6 +132,15 @@ test("OpenAPI documents high-risk route auth and setup responses", () => { assert.match(apiKeyDetail, /"401":\n\s+description: Authentication required/); assert.match(apiKeyDetail, /"404":\n\s+description: Key not found/); + const apiKeyDevicesStart = spec.indexOf(" /api/keys/{id}/devices:"); + const apiKeyDevicesEnd = spec.indexOf("\n /api/settings/purge-usage-history:", apiKeyDevicesStart); + const apiKeyDevices = spec.slice(apiKeyDevicesStart, apiKeyDevicesEnd); + assert.match(apiKeyDevices, /\n get:/); + assert.match( + apiKeyDevices, + /"401":\n\s+\$ref: "#\/components\/responses\/ManagementAuthenticationRequired"/ + ); + const loginStart = spec.indexOf(" /api/auth/login:"); const loginEnd = spec.indexOf("\n /api/auth/logout:", loginStart); const login = spec.slice(loginStart, loginEnd); diff --git a/tests/unit/provider-plugin-manifest-client.test.ts b/tests/unit/provider-plugin-manifest-client.test.ts new file mode 100644 index 00000000000..4b7b538ba31 --- /dev/null +++ b/tests/unit/provider-plugin-manifest-client.test.ts @@ -0,0 +1,150 @@ +import assert from "node:assert/strict"; +import test from "node:test"; + +import { + fetchProviderPluginManifest, + fetchProviderPluginManifestEntryForModel, + getProviderPluginManifestEntryForModelFromManifest, + PROVIDER_PLUGIN_MANIFEST_ENV, + PROVIDER_PLUGIN_MANIFEST_PATH, + resolveProviderPluginManifestUrl, +} from "../../open-sse/config/providerPluginManifestClient.ts"; +import type { ProviderPluginManifest } from "../../open-sse/config/providerPluginManifest.ts"; + +const ORIGINAL_ENV = { ...process.env }; + +function restoreEnv() { + process.env = { ...ORIGINAL_ENV }; +} + +test.afterEach(() => { + restoreEnv(); +}); + +test("provider plugin manifest client resolves explicit and env URLs first", () => { + process.env[PROVIDER_PLUGIN_MANIFEST_ENV] = "http://env.example/manifest"; + + assert.equal( + resolveProviderPluginManifestUrl({ manifestUrl: "http://explicit.example/manifest" }), + "http://explicit.example/manifest", + ); + assert.equal( + resolveProviderPluginManifestUrl({ baseUrl: "http://local.example:20128/" }), + "http://env.example/manifest", + ); +}); + +test("provider plugin manifest client resolves base and default local URLs", () => { + delete process.env[PROVIDER_PLUGIN_MANIFEST_ENV]; + process.env.HOST = "0.0.0.0"; + process.env.PORT = "20129"; + + assert.equal( + resolveProviderPluginManifestUrl({ baseUrl: "http://127.0.0.1:20128/" }), + `http://127.0.0.1:20128${PROVIDER_PLUGIN_MANIFEST_PATH}`, + ); + assert.equal( + resolveProviderPluginManifestUrl(), + `http://0.0.0.0:20129${PROVIDER_PLUGIN_MANIFEST_PATH}`, + ); +}); + +test("provider plugin manifest client fetches and validates schemaVersion 1", async () => { + const manifest: ProviderPluginManifest = { + schemaVersion: 1, + generatedFrom: "open-sse/config/providers", + providers: [], + }; + const calls: Array<{ url: string; init?: RequestInit }> = []; + const fetchImpl: typeof fetch = async (url, init) => { + calls.push({ url: String(url), init }); + return new Response(JSON.stringify(manifest), { + status: 200, + headers: { "Content-Type": "application/json" }, + }); + }; + + const result = await fetchProviderPluginManifest({ + manifestUrl: "http://sidecar.local/manifest", + fetchImpl, + }); + + assert.deepEqual(result, manifest); + assert.equal(calls[0]?.url, "http://sidecar.local/manifest"); + assert.equal((calls[0]?.init?.headers as Record).Accept, "application/json"); +}); + +test("provider plugin manifest client rejects failed HTTP and malformed responses", async () => { + await assert.rejects( + fetchProviderPluginManifest({ + manifestUrl: "http://sidecar.local/manifest", + fetchImpl: async () => new Response("nope", { status: 503 }), + }), + /HTTP 503/, + ); + + await assert.rejects( + fetchProviderPluginManifest({ + manifestUrl: "http://sidecar.local/manifest", + fetchImpl: async () => new Response(JSON.stringify({ providers: [] }), { status: 200 }), + }), + /schemaVersion 1/, + ); +}); + +test("provider plugin manifest client resolves model entries from fetched manifests", async () => { + const manifest: ProviderPluginManifest = { + schemaVersion: 1, + generatedFrom: "open-sse/config/providers", + providers: [ + { + id: "anthropic", + alias: "claude", + format: "anthropic", + executor: "default", + auth: { type: "apikey", header: "x-api-key" }, + endpoints: { baseUrl: "https://api.anthropic.com" }, + capabilities: ["apikey", "sidecar-candidate"], + passthroughModels: false, + models: [{ id: "claude-sonnet-4.6", name: "Claude Sonnet 4.6" }], + sidecar: { eligible: true, reasons: [] }, + }, + { + id: "openai", + format: "openai", + executor: "default", + auth: { type: "apikey", header: "Authorization", prefix: "Bearer" }, + endpoints: { baseUrl: "https://api.openai.com/v1" }, + capabilities: ["apikey", "sidecar-candidate"], + passthroughModels: false, + models: [{ id: "gpt-4.1", name: "GPT-4.1" }], + sidecar: { eligible: true, reasons: [] }, + }, + ], + }; + const fetchImpl: typeof fetch = async () => + new Response(JSON.stringify(manifest), { + status: 200, + headers: { "Content-Type": "application/json" }, + }); + + assert.equal( + getProviderPluginManifestEntryForModelFromManifest(manifest, "openai/gpt-4.1")?.id, + "openai", + ); + assert.equal( + getProviderPluginManifestEntryForModelFromManifest(manifest, "claude/claude-sonnet-4.6")?.id, + "anthropic", + ); + assert.equal( + getProviderPluginManifestEntryForModelFromManifest(manifest, "gpt-4.1")?.id, + "openai", + ); + assert.equal( + await fetchProviderPluginManifestEntryForModel("missing-model", { + manifestUrl: "http://sidecar.local/manifest", + fetchImpl, + }), + null, + ); +});