Commit Graph

26 Commits

Author SHA1 Message Date
Bob.Hou
54f19c7742 feat(providers): fetch live xAI catalog for xai-oauth (#13518) 2026-09-16 08:08:49 -03:00
Dizzle
997cd4d509 fix(sse): stop retry wave on rate-limited 429 and drain 429 once (#13657)
The opencode executor classifies rate-limited 429 bodies (`classify429`, with real tests) and, when a whole account wave is exhausted, returns the last real upstream 429 — status, body, `Retry-After` and quota headers intact — so the provider error rules (monthly-quota cooldown) keep working.

Maintainer rework before merge (kept the idea, no default behavior change):
- The original stopped the cross-account wave at the first classified 429 and replaced the response with a synthetic one that dropped the body and headers; stopping early is now opt-in behind `OPENCODE_RATE_LIMITED_429_EARLY_STOP` (default off), the rate-limited account is still cooled down, the body is read as a bounded 8 KiB prefix from a clone and the original is never consumed, and the unused `status` input is gone.

Validated first on the combined board of all 38 PRs of this batch (10 merged as-is, 28 after the maintainer rework) on top of release/v3.8.51 c0f92ec: typecheck:core, check:open-sse-typecheck and check:dashboard-typecheck clean; ESLint clean on every changed file; file-size (rebaselined for the combined growth), complexity, cognitive-complexity, changelog-integrity, docs-counts, docs-sync, migration-numbering and i18n new-key gates green; 735 focused node:test cases with the only batch-caused failure (a flag-count assertion) fixed. Then re-validated alone on the fresh release tip right before this merge: ESLint on the changed files, typecheck:core, check:open-sse-typecheck, the file-size/complexity/changelog gates and this PR's own tests.

Thanks @maxmad64bis!
2026-09-15 22:01:56 -03:00
Dizzle
94d27e44fe fix(providers): stop parking Mistral connections on a bare 401 with no clear auth failure (#13609)
Behind the new `MISTRAL_AMBIGUOUS_401_SOFT_LOCKOUT` flag (default off), a bare Mistral 401 (`{"detail":"Unauthorized"}`, identical for a revoked key and an exhausted quota) gets a retryable cooldown instead of parking the connection as `expired`; after three soft strikes within an hour the next bare 401 parks it, so revocation still converges.

Maintainer rework before merge (kept the idea, no default behavior change):
- The predicate is shared with the connection-test module instead of duplicated; the squeezed 139-char line that dodged the file-size gate is formatted normally and the growth is rebaselined honestly with an annotation.

Validated first on the combined board of all 38 PRs of this batch (10 merged as-is, 28 after the maintainer rework) on top of release/v3.8.51 c0f92ec: typecheck:core, check:open-sse-typecheck and check:dashboard-typecheck clean; ESLint clean on every changed file; file-size (rebaselined for the combined growth), complexity, cognitive-complexity, changelog-integrity, docs-counts, docs-sync, migration-numbering and i18n new-key gates green; 735 focused node:test cases with the only batch-caused failure (a flag-count assertion) fixed. Then re-validated alone on the fresh release tip right before this merge: ESLint on the changed files, typecheck:core, check:open-sse-typecheck, the file-size/complexity/changelog gates and this PR's own tests.

Thanks @maxmad64bis!
2026-09-15 21:19:47 -03:00
Dizzle
13f44af6e5 fix(sse): pause failover dispatch after repeated transient upstream failures (#13615)
Behind the new `OPENCODE_TRANSIENT_FAILOVER_BACKOFF` flag (default off), after two consecutive transient upstream failures the opencode rotation pauses before each later account (1.5s, 3s, 6s, capped at 10s per request) instead of hammering the upstream.

Maintainer rework before merge (kept the idea, no default behavior change):
- The pause honors the client abort signal (no dispatch after a disconnect), the failed attempt's body is cancelled before sleeping, `transientRetryDelayMs` now uses its arguments, and the sleep is injectable so the tests run without real timers.

Validated first on the combined board of all 38 PRs of this batch (10 merged as-is, 28 after the maintainer rework) on top of release/v3.8.51 c0f92ec: typecheck:core, check:open-sse-typecheck and check:dashboard-typecheck clean; ESLint clean on every changed file; file-size (rebaselined for the combined growth), complexity, cognitive-complexity, changelog-integrity, docs-counts, docs-sync, migration-numbering and i18n new-key gates green; 735 focused node:test cases with the only batch-caused failure (a flag-count assertion) fixed. Then re-validated alone on the fresh release tip right before this merge: ESLint on the changed files, typecheck:core, check:open-sse-typecheck, the file-size/complexity/changelog gates and this PR's own tests.

Thanks @maxmad64bis!
2026-09-15 20:58:12 -03:00
Dizzle
e325888d78 fix(sse): fail over opencode request on refused-route 403 (#13498)
Behind the new `OPENCODE_USER_BLOCKED_ROTATION` flag (default off), a 403 or 451 carrying `user_blocked` on a proxied opencode account rotates at most once to the next account instead of being returned as-is.

Maintainer rework before merge (kept the idea, no default behavior change):
- 403 and 451 are handled by one predicate (the original returned 451 without rotation), the refused account gets a cooldown and joins the tried-set, and the response body of the attempt rotated away from is cancelled.

Validated first on the combined board of all 38 PRs of this batch (10 merged as-is, 28 after the maintainer rework) on top of release/v3.8.51 c0f92ec: typecheck:core, check:open-sse-typecheck and check:dashboard-typecheck clean; ESLint clean on every changed file; file-size (rebaselined for the combined growth), complexity, cognitive-complexity, changelog-integrity, docs-counts, docs-sync, migration-numbering and i18n new-key gates green; 735 focused node:test cases with the only batch-caused failure (a flag-count assertion) fixed. Then re-validated alone on the fresh release tip right before this merge: ESLint on the changed files, typecheck:core, check:open-sse-typecheck, the file-size/complexity/changelog gates and this PR's own tests.

Thanks @maxmad64bis!
2026-09-15 20:39:39 -03:00
Dizzle
d8c0448293 fix(opencode): rotate once when a Responses stream stalls before its first byte (#13484)
Behind the new `OPENCODE_RESPONSES_STALL_ROTATION` flag (default off): a streamed Responses reply with no first body byte within `RESPONSES_FIRST_BYTE_TIMEOUT_MS` (15s) cools the account and rotates once; a second stall fails fast instead of waiting the 80s readiness timeout.

Maintainer rework before merge (kept the idea, no default behavior change):
- The TLS first-byte watchdog from #12656 is restored byte for byte (the PR had changed its pump, timer and cancel); the stall guard lives in its own module.
- Proxy-less multi-account setups now rotate the same way as proxied ones (the original threw for them), a client abort during the wait rethrows instead of rotating, and the env var is documented as flag-only.

Validated first on the combined board of all 38 PRs of this batch (10 merged as-is, 28 after the maintainer rework) on top of release/v3.8.51 c0f92ec: typecheck:core, check:open-sse-typecheck and check:dashboard-typecheck clean; ESLint clean on every changed file; file-size (rebaselined for the combined growth), complexity, cognitive-complexity, changelog-integrity, docs-counts, docs-sync, migration-numbering and i18n new-key gates green; 735 focused node:test cases with the only batch-caused failure (a flag-count assertion) fixed. Then re-validated alone on the fresh release tip right before this merge: ESLint on the changed files, typecheck:core, check:open-sse-typecheck, the file-size/complexity/changelog gates and this PR's own tests.

Thanks @maxmad64bis!
2026-09-15 20:10:57 -03:00
Dizzle
5cf4316b67 fix(proxy-health): refused probe responses reset the consecutive-failure streak (#13608)
Behind the new `PROXY_HEALTH_BLOCKED_RESETS_STREAK` flag (default off), a probe the target refuses (401/403/429) resets the proxy's consecutive-failure streak, so a proxy that clearly relays is not marked dead by spaced-out real failures.

Maintainer rework before merge (kept the idea, no default behavior change):
- The original reversed the deliberate #10654 policy for everyone; with the flag off a refusal stays neutral, and the existing assertions are restored. The stale JSDoc and the wrong "any relayed response resets" comment are fixed (5xx stays inconclusive).
- The source-grep test became a real sweep test: a local relay answering 403 drives fail → blocked → fail with auto-disable, in both flag modes.

Validated first on the combined board of all 38 PRs of this batch (10 merged as-is, 28 after the maintainer rework) on top of release/v3.8.51 c0f92ec: typecheck:core, check:open-sse-typecheck and check:dashboard-typecheck clean; ESLint clean on every changed file; file-size (rebaselined for the combined growth), complexity, cognitive-complexity, changelog-integrity, docs-counts, docs-sync, migration-numbering and i18n new-key gates green; 735 focused node:test cases with the only batch-caused failure (a flag-count assertion) fixed. Then re-validated alone on the fresh release tip right before this merge: ESLint on the changed files, typecheck:core, check:open-sse-typecheck, the file-size/complexity/changelog gates and this PR's own tests.

Thanks @maxmad64bis!
2026-09-15 20:01:25 -03:00
Dizzle
ca312d57aa feat(proxies): show how many egress IPs actually served a proxy pool (#13581)
Behind the new `PROXY_POOL_EGRESS_OBSERVATION` flag (default off): a line under each proxy pool showing how many distinct egress IPs actually served it over 24h, backed by `GET /api/settings/proxies/pool/egress-observation`.

Maintainer rework before merge (kept the idea, no default behavior change):
- The route validates its query with Zod (unknown `scope` → 400 instead of silently `global`), error bodies go through `errorResponse()`, the OpenAPI entry documents security, parameters and responses, and the three UI strings exist in every locale.

Validated first on the combined board of all 38 PRs of this batch (10 merged as-is, 28 after the maintainer rework) on top of release/v3.8.51 c0f92ec: typecheck:core, check:open-sse-typecheck and check:dashboard-typecheck clean; ESLint clean on every changed file; file-size (rebaselined for the combined growth), complexity, cognitive-complexity, changelog-integrity, docs-counts, docs-sync, migration-numbering and i18n new-key gates green; 735 focused node:test cases with the only batch-caused failure (a flag-count assertion) fixed. Then re-validated alone on the fresh release tip right before this merge: ESLint on the changed files, typecheck:core, check:open-sse-typecheck, the file-size/complexity/changelog gates and this PR's own tests.

Thanks @maxmad64bis!
2026-09-15 19:48:52 -03:00
Dizzle
29d66cbf8c feat(proxies): stop re-serving a proxy that just failed (#13578)
Behind the new `PROXY_SKIP_RECENTLY_FAILED` flag (default off), pool rotation and the opencode account rotation remember a proxy that just failed (refused probe or 429) and skip it for a doubling cooldown instead of re-serving it immediately.

Maintainer rework before merge (kept the idea, no default behavior change):
- The original was on by default and re-queried the DB on every request while a member was set aside; selection now caches a refusal sequence number and re-runs the cascade once per set-aside event.
- `src/lib/db` no longer imports the heavy dispatcher for key normalization (a parity test guarantees the same key as `proxyConfigToUrl()`); `.env.example` and `ENVIRONMENT.md` document the default as false.

Validated first on the combined board of all 38 PRs of this batch (10 merged as-is, 28 after the maintainer rework) on top of release/v3.8.51 c0f92ec: typecheck:core, check:open-sse-typecheck and check:dashboard-typecheck clean; ESLint clean on every changed file; file-size (rebaselined for the combined growth), complexity, cognitive-complexity, changelog-integrity, docs-counts, docs-sync, migration-numbering and i18n new-key gates green; 735 focused node:test cases with the only batch-caused failure (a flag-count assertion) fixed. Then re-validated alone on the fresh release tip right before this merge: ESLint on the changed files, typecheck:core, check:open-sse-typecheck, the file-size/complexity/changelog gates and this PR's own tests.

Thanks @maxmad64bis!
2026-09-15 18:09:02 -03:00
Dizzle
87d9d82b37 fix(sse): fail over to sibling connection on stream early EOF (#13153)
Behind `STREAM_EARLY_EOF_SIBLING_FAILOVER_ENABLED` (default off): after the bounded same-connection retry is spent, a stream that closed early fails over exactly once to a sibling connection.

Maintainer rework before merge (kept the idea, no default behavior change):
- The PR's own failover test was red on its head: the `/v1/chat/completions` route's early-stream keepalive dropped the `X-OmniRoute-Selected-Connection-Id` header on the first cold request. Tests now drive `handleChat()` directly; the assertion was kept.
- "One hop" was one hop per connection (a 3-connection pool made 4 dispatches); it is now a single sibling hop per request, and when the pool runs out the original `STREAM_EARLY_EOF` 502 is returned instead of a generic `bad_gateway`, so combo-level detection keeps working. The source-regex timeout test became a behavioral one; flag description in all 59 locales.

Validated first on the combined board of all 38 PRs of this batch (10 merged as-is, 28 after the maintainer rework) on top of release/v3.8.51 c0f92ec: typecheck:core, check:open-sse-typecheck and check:dashboard-typecheck clean; ESLint clean on every changed file; file-size (rebaselined for the combined growth), complexity, cognitive-complexity, changelog-integrity, docs-counts, docs-sync, migration-numbering and i18n new-key gates green; 735 focused node:test cases with the only batch-caused failure (a flag-count assertion) fixed. Then re-validated alone on the fresh release tip right before this merge: ESLint on the changed files, typecheck:core, check:open-sse-typecheck, the file-size/complexity/changelog gates and this PR's own tests.

Thanks @maxmad64bis!
2026-09-15 16:49:06 -03:00
Dizzle
53ed8c4745 fix(stream-recovery): order-aware in-flight tool-call detection behind off-by-default flag (#13633)
Behind `STREAM_RECOVERY_TOOLCALL_ORDER_FIX` (default off), mid-stream continuation becomes tool-call safe: any tool call seen in the stream — in flight or finished — blocks a continuation, and an empty continuation stops after one attempt.

Maintainer rework before merge (kept the idea, no default behavior change):
- The empty-continuation short-circuit also ran with the flag off; it is now gated, so the flag-off path uses the whole budget exactly as before (regression test added).
- The latch re-arm that let a continuation fire after a completed `finish_reason: tool_calls` is gone; index-less tool calls on multi-choice payloads are now blocked too; ~150 lines of dead trace plumbing removed.

Validated first on the combined board of all 38 PRs of this batch (10 merged as-is, 28 after the maintainer rework) on top of release/v3.8.51 c0f92ec: typecheck:core, check:open-sse-typecheck and check:dashboard-typecheck clean; ESLint clean on every changed file; file-size (rebaselined for the combined growth), complexity, cognitive-complexity, changelog-integrity, docs-counts, docs-sync, migration-numbering and i18n new-key gates green; 735 focused node:test cases with the only batch-caused failure (a flag-count assertion) fixed. Then re-validated alone on the fresh release tip right before this merge: ESLint on the changed files, typecheck:core, check:open-sse-typecheck, the file-size/complexity/changelog gates and this PR's own tests.

Thanks @maxmad64bis!
2026-09-15 16:10:52 -03:00
Dizzle
611342609f fix(combo): return 502 for non-quota protected-priority stops (#13439)
Behind the new `PROTECTED_PRIORITY_INFRA_502_ENABLED` flag (default off), protected-priority combo stops caused by provably non-quota infrastructure (provider circuit open, predictive-TTFT latency) surface as 502 instead of a quota-looking 503.

Maintainer rework before merge (kept the idea, no default behavior change):
- The original branch made 502 the default for every stop, including model lockouts and cooldowns, and removed the #8133/#1731 provider-wide skip for 401/5xx without a connection id; both are restored with their regression tests untouched.
- Nineteen cases cover eight gate causes plus predictive latency, flag off and on.

Validated first on the combined board of all 38 PRs of this batch (10 merged as-is, 28 after the maintainer rework) on top of release/v3.8.51 c0f92ec: typecheck:core, check:open-sse-typecheck and check:dashboard-typecheck clean; ESLint clean on every changed file; file-size (rebaselined for the combined growth), complexity, cognitive-complexity, changelog-integrity, docs-counts, docs-sync, migration-numbering and i18n new-key gates green; 735 focused node:test cases with the only batch-caused failure (a flag-count assertion) fixed. Then re-validated alone on the fresh release tip right before this merge: ESLint on the changed files, typecheck:core, check:open-sse-typecheck, the file-size/complexity/changelog gates and this PR's own tests.

Thanks @maxmad64bis!
2026-09-15 15:46:57 -03:00
Dizzle
ac52d4d9ea fix(sse): omit synthetic Retry-After and mark retry provenance on drain path (#13672)
Behind the new `RETRY_AFTER_PROVENANCE_ENABLED` flag (default off): `unavailableResponse` omits the synthetic `Retry-After: 1` when there is no real retry signal, marks `retry_after_provenance` on its bodies, and both combo drain readers parse prose retry hints from plain-text bodies too. With the flag off, headers and bodies are exactly as before.

Maintainer rework before merge (kept the idea, no default behavior change):
- A past `Retry-After` date is no longer labelled as an upstream signal with `Retry-After: 1`; non-JSON bodies (HTML 502 pages) log at debug instead of warning on every request.
- The provenance claim is narrowed to responses built by `unavailableResponse`, documented in the flag row.

Validated first on the combined board of all 38 PRs of this batch (10 merged as-is, 28 after the maintainer rework) on top of release/v3.8.51 c0f92ec: typecheck:core, check:open-sse-typecheck and check:dashboard-typecheck clean; ESLint clean on every changed file; file-size (rebaselined for the combined growth), complexity, cognitive-complexity, changelog-integrity, docs-counts, docs-sync, migration-numbering and i18n new-key gates green; 735 focused node:test cases with the only batch-caused failure (a flag-count assertion) fixed. Then re-validated alone on the fresh release tip right before this merge: ESLint on the changed files, typecheck:core, check:open-sse-typecheck, the file-size/complexity/changelog gates and this PR's own tests.

Thanks @maxmad64bis!
2026-09-15 15:25:43 -03:00
Dizzle
45c54ca8aa fix(dashboard): require provider free-tier for the Free badge (#13645)
The provider-page Free badge can require a free tier the provider actually honors: behind the new `FREE_BADGE_REQUIRES_PROVIDER_FREE_TIER` flag (default off) the display-name "free" heuristic, non-boolean `free` fields and `:free` suffixes on registered providers without a documented free tier no longer light the badge. With the flag off the historical rule is unchanged.

Maintainer rework before merge (kept the idea, no default behavior change):
- `:free` models on free-tier providers and on compatible nodes (OpenRouter-style endpoints) keep the badge in both modes — the original change dropped them.
- Test D derives its provider set from `FREE_MODEL_BUDGETS` instead of a hard-coded allowlist; a vitest render covers both sections with the flag endpoint on, off and erroring.

Validated first on the combined board of all 38 PRs of this batch (10 merged as-is, 28 after the maintainer rework) on top of release/v3.8.51 c0f92ec: typecheck:core, check:open-sse-typecheck and check:dashboard-typecheck clean; ESLint clean on every changed file; file-size (rebaselined for the combined growth), complexity, cognitive-complexity, changelog-integrity, docs-counts, docs-sync, migration-numbering and i18n new-key gates green; 735 focused node:test cases with the only batch-caused failure (a flag-count assertion) fixed. Then re-validated alone on the fresh release tip right before this merge: ESLint on the changed files, typecheck:core, check:open-sse-typecheck, the file-size/complexity/changelog gates and this PR's own tests.

Thanks @maxmad64bis!
2026-09-15 13:57:39 -03:00
Dizzle
cb420db64b fix(call-logs): hide search rows without a live provider (#13641)
Search stats and recent searches stop surfacing ghost rows: NULL and `-` providers are always hidden, and, behind the new `SEARCH_STATS_HIDE_DELETED_CONNECTIONS` flag (default off), traffic of a keyed provider whose connection was deleted is hidden too. Totals use the same guard as the per-provider rows, so they always agree.

Maintainer rework before merge (kept the idea, no default behavior change):
- Keyless providers from the search registry (`duckduckgo-free`, `searxng-search`, anonymous `context7`) and providers served through a credential fallback (`perplexity-search` on a `perplexity` key) stay visible in both modes — the original filter dropped them because they have no `provider_connections` row.
- Tests use real registry ids and cover flag off (historical stats) and flag on, including the analytics route; #13281's changelog fragment restored.

Validated first on the combined board of all 38 PRs of this batch (10 merged as-is, 28 after the maintainer rework) on top of release/v3.8.51 c0f92ec: typecheck:core, check:open-sse-typecheck and check:dashboard-typecheck clean; ESLint clean on every changed file; file-size (rebaselined for the combined growth), complexity, cognitive-complexity, changelog-integrity, docs-counts, docs-sync, migration-numbering and i18n new-key gates green; 735 focused node:test cases with the only batch-caused failure (a flag-count assertion) fixed. Then re-validated alone on the fresh release tip right before this merge: ESLint on the changed files, typecheck:core, check:open-sse-typecheck, the file-size/complexity/changelog gates and this PR's own tests.

Thanks @maxmad64bis!
2026-09-15 12:48:06 -03:00
Paco Cartones
2e10346f45 docs(reference): sync FEATURE_FLAGS.md with featureFlagDefinitions (#12552)
The doc promised a 1:1 catalog and had drifted to 20 missing keys, two rows that were not flags at all, and a port number that disagreed with the code. Good call adding a static sync test — it caught its own drift immediately: I added the `SERVER_OWNED_TOOL_LOOP_ENABLED` row the base gained after you wrote this.

---

Validated in one consolidated worktree cut from `release/v3.8.51`, boarded with the rest of this batch — zero conflicts between the 19 PRs.

- `typecheck:core` clean; `check:dashboard-typecheck` OK (206 pre-existing, all within the frozen baseline); `check:changelog-integrity` OK
- complexity 2802 / baseline 3218 and cognitive-complexity 1267 / baseline 1437 — both under baseline
- 226 of 228 focused assertions green across the batch's 23 test files. The 2 remaining belong to #12551, which is held separately.

Two batch-owned defects were found and fixed in flight, both pure base drift: `173_xp_action_counts.sql` collided with `173_call_logs_video_content_removed.sql` (renumbered to 176 on #12651 — it aborted every DB open, which is what 53 of the first run's failures were), and the feature-flag catalog was missing the `SERVER_OWNED_TOOL_LOOP_ENABLED` row the base gained after #12552 was written.

⚠️ base-red inherited: #12732 — `Docs Gates`, `Merge integrity`, `No new ESLint warnings`, `Unit Tests fast-path` and `Fast Quality Gates` reproduce on the pure tip (provider count 356 vs the 358 the modules define, SKILL.md drift, and `open-sse/utils/stream.ts` at 3115 > frozen 3098, which this batch does not touch).

Thanks @pacocartones — the `file:line` citations and the explicit out-of-scope notes on every one of these made a 19-PR batch reviewable in one pass.
2026-09-11 17:41:43 -03:00
Diego Rodrigues de Sa e Souza
412298b624 chore(proxy): purge the legacy 1proxy residue — sync/rotator modules, dead DB exports, dead settings tab and flag, docs (#12091) (#12290)
The 1proxy marketplace integration was decommissioned in v3.8.4; the code
survived only through the localDb barrel, deleted in #12055. Everything below
had zero consumers (grep-proven across src/, open-sse/, bin/, electron/,
scripts/ and tests/):

- src/lib/oneproxySync.ts and src/lib/oneproxyRotator.ts deleted.
- src/lib/db/oneproxy.ts: upsertOneproxyProxy, getOneproxyProxyById,
  getOneproxyProxyForRotation and markOneproxyProxyFailed removed (their only
  consumers were the two deleted modules); listOneproxyProxies and the record
  interface stay — open-sse/utils/proxyFallback.ts still uses them.
- src/shared/validation/oneproxySchemas.ts and the unmounted
  settings/components/OneproxyTab.tsx deleted (no importer anywhere; the live
  UI is the FreePool* tabs over /api/settings/free-proxies).
- ONEPROXY_ENABLED feature flag removed (readerless since oneproxySync died —
  the toggle no longer controlled anything); flag-count contract test aligned
  54 → 53.
- Docs: PROXY_GUIDE (component rows, env rows, the three omniroute/
  oneproxyRotator snippet sections), CODEBASE_DOCUMENTATION, REPOSITORY_MAP,
  ENVIRONMENT (ONEPROXY_* rows), FEATURE_FLAGS, .env.example — canonical +
  pl/zh-CN/zh-TW mirrors.
- The 308 compat redirects under /api/settings/oneproxy/ stay (deliberate API
  compat), as do the live free-proxy provider and proxy_registry rows.

check:dead-code drops 424 → 417 (baseline kept at the velocity-phase 500 —
banking shrinks is paused until v4.0, headroom grows to 16.6%).
check:docs-all, check:env-doc-sync, typecheck:core and the 8 free-proxy/
proxy-fallback test files are green.

Closes #12091
2026-09-01 09:22:26 -03:00
Dizzle
464ccb3142 fix(providers): remove ALLOW_MULTI_CONNECTIONS_PER_COMPAT_NODE, a flag that governs nothing (#10974)
5 — Remove ALLOW_MULTI_CONNECTIONS_PER_COMPAT_NODE, flag morta desde 5b5e21a99 (que removeu o guard que a lia, resolvendo #1566). Zero mudança de comportamento; EXPECTED_FEATURE_FLAG_COUNT é o regression guard. Sibling de #10973 (mesma raiz).
2026-08-21 13:59:00 -03:00
Diego Rodrigues de Sa e Souza
f71f3da08b feat(routing): add DISABLE_CONTEXT_WINDOW_CHECKS bypass for the direct-request input/context check (#10927)
Rescoped from #10606 — see PR body for the full rationale (combo-routing half made moot by #10162's advisory-only architecture, chatCore.ts hard-reject bypass retains real value).

Validated in an isolated worktree boarded onto origin/release/v3.8.50:
- 65/65 focused unit tests pass (chatcore-model-output-cap-wiring + feature-flags-settings).
- check-file-size, check-changelog-integrity: OK.
- typecheck:core: clean.
- check-complexity / check-cognitive-complexity: OK, both under baseline.

Co-authored-by: JxnLexn <10897478+JxnLexn@users.noreply.github.com>
2026-08-20 22:04:03 -03:00
Diego Rodrigues de Sa e Souza
cd9a631464 feat: Claude Code discovery aliases (surface non-Claude models in the /model picker) (#8666)
* feat(db): cc discovery alias gate storage + EXPOSE_CC_DISCOVERY_ALIASES flag

Adds the gate for claude/<provider>/<model> discovery-alias mirror ids on
the /v1/models catalog: a new runtime feature flag (env forces on and wins
over the dashboard DB override), per-provider and per-model "on"/"off"/null
overrides stored in key_value under the ccDiscoveryAliases namespace, and a
pure precedence resolver (model > provider > global). Catalog wiring is a
separate follow-up task; this only lands the gate + storage.

* feat(sse): synthesize claude/ discovery aliases for the model catalog

* feat(api): advertise cc discovery aliases on /v1/models behind the 3-level gate

* feat(sse): resolve claude/ discovery aliases on the request path

* fix(sse): import getComboByName from db/combos, not the localDb barrel

* fix(sse): cover custom-node prefixes and the Codex WS bridge in cc discovery alias resolution

* feat(dashboard): cc discovery alias toggles + flag-screen env warning

Adds the operator-facing UI/API layer for the Claude Code discovery-alias
gate (claude/<provider>/<model> mirror ids on /v1/models): REST endpoint
for provider/model overrides, a provider-detail card with 3-state
(inherit/on/off) toggles, an info button on the Claude Code tool card
linking to Feature Flags, and an env-source warning on the
EXPOSE_CC_DISCOVERY_ALIASES flag card when it's forced on via env.

* feat(api): cc discovery usage metrics

* fix(api): record cc alias metric in the production wrapper + atomic counter upsert

* docs: document cc discovery aliases (Claude Code guide + feature flag catalog)

* fix(sse): don't mirror built-in auto/* combos as discovery aliases (advertised-but-unroutable)

* i18n(vi): translate the discovery-alias strings instead of shipping placeholders

vi is the one locale with a strict "no internal missing markers" test, so the 17
__MISSING__ entries this branch added (the provider ccAlias panel, the info
button, the feature-flag description and the env warning) would have turned that
test red the moment the base itself was repaired. Translated, keeping every ICU
placeholder ({modelId}, {error}) and the literal claude/<provider>/<model> id
shape intact.

* chore(quality): raise the frozen caps this feature legitimately grows

catalog.ts 1615 -> 1639: the alias synthesis is wired into the catalog builder,
which is where the per-key-filtered list is assembled — the only place the mirror
entries can be appended after model hiding has been applied.

localDb.ts 808 -> 810: two re-export lines for the new ccDiscoveryAliases db
module, which is exactly what the "Adding a New DB Module" recipe prescribes.

* refactor(dashboard,api): keep the complexity ratchets flat

The feature added four cyclomatic violations and one cognitive one, which the
ratchets reject — the baseline only moves when a metric improves. Split the new
code instead:

- appendCcDiscoveryAliases: the four skip-guards become isMirrorableId().
- resolveCcDiscoveryAliasStripWith: alias parsing and gate resolution become
  parseCcAliasTarget() and resolveGateFor(), replacing a chain of ternaries that
  each re-tested isComboAlias.
- FeatureFlagCard: the env-precedence warning becomes its own component instead
  of a conditional branch inside an already-large render.
- ProviderCcAliasSection: the loader moves to useCcAliasData(), and the override
  list and add-row become ModelOverrideList / AddOverrideRow, bringing both
  oversized functions back under the 80-line rule.

Behavior unchanged — the 74 discovery-alias tests pass untouched. Both ratchets
now sit exactly at baseline (2188 / 971).
2026-07-26 17:19:19 -03:00
Ravi Tharuma
4fd1f0f15c fix(guardrails): align INPUT_SANITIZER request masking gate (#8093) (#8124)
Scoped to guardrails/security: dropped the unrelated js-yaml/tar/shell-quote/
brace-expansion override bumps, and isolated sanitizer-residual-policy.test.ts
to a tmp DATA_DIR so it no longer touches the real storage.sqlite.

Co-authored-by: RaviTharuma <RaviTharuma@users.noreply.github.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
Co-authored-by: rafaumeu <rafael.zendron22@gmail.com>
2026-07-23 04:50:43 -03:00
Ravi Tharuma
7ae17168db fix(security): decouple request PII redaction from injection mode (#8102)
* fix(security): decouple request PII redaction from injection mode

PII_REDACTION_ENABLED now rewrites request PII independently of
INPUT_SANITIZER_MODE, so the enterprise recipe (MODE=block + PII on)
actually redacts. Also cover Responses API string input/prompt shapes
and correct docs that claimed MODE=redact strips injection text.

Refs: #8092 #8093 #8094 #8096 #8097

* test(security): drop no-explicit-any in sanitizer unit tests

Unblocks CI lint/quality ratchet on the PII redaction PR by typing
chat-like payloads instead of casting to any.

---------

Co-authored-by: Ravi Tharuma <RaviTharuma@users.noreply.github.com>
2026-07-22 17:38:20 -03:00
Diego Rodrigues de Sa e Souza
7c23dab64d Release v3.8.40
v3.8.40 cycle integration → main. All test gates green (Unit/Integration/Coverage/Node-compat/Quality-Ratchet). The only red check, 'PR Test Policy', is the test-masking heuristic firing on the cumulative ~57-commit release diff (legitimate assert consolidations already reviewed per-PR — Gemini CLI removal #5246, retired GPT models #5280, provider catalog refreshes); overridden with --admin per the documented release-PR convention. CodeQL/SonarQube advisory scans non-blocking; #5278's code already passed CodeQL on main. Homologated on VPS 192.168.0.15 (v3.8.40 healthy).
2026-06-29 08:40:06 -03:00
Diego Rodrigues de Sa e Souza
7b139fdb5e Release v3.8.38 (#5078)
* chore(release): open v3.8.38 development cycle

* fix(executors): strip client_metadata for cerebras and mistral (#4727)

Integrated into release/v3.8.38 (leva 5)

* fix(codebuddy): only send reasoning params when client requests reasoning (#5019)

Integrated into release/v3.8.38 (leva 5)

* fix(sse): keep streaming for forceStream providers when client requests JSON (#5021)

Integrated into release/v3.8.38 (leva 5)

* fix(sse): guard non-JSON SSE lines and duplicate [DONE] (#4937)

Integrated into release/v3.8.38 (leva 5)

* feat(blackbox): refresh provider model catalog (#4935)

Integrated into release/v3.8.38 (leva 5)

* fix(sse): dedupe case-variant Anthropic version/beta headers (#4846)

Integrated into release/v3.8.38 (leva 5)

* feat(sse): Kiro inline <thinking> stream splitter (#4911)

Integrated into release/v3.8.38 (leva 5)

* feat(cursor): parse Composer DeepSeek-style inline tool calls (#4912)

Integrated into release/v3.8.38 (leva 5)

* feat(proxy): auth-less host:port batch import (#4938)

Integrated into release/v3.8.38 (leva 5)

* fix(oauth): support Kiro IDC (organization) token import (#4944)

Integrated into release/v3.8.38 (leva 5)

* fix(translator): preserve cache_control for DashScope OpenAI-compat providers (port from 9router#2069) (#5013)

Integrated into release/v3.8.38 (leva 5)

* fix(tts): resolve Gemini TTS models from catalog (#4934)

Integrated into release/v3.8.38 (leva 5)

* fix(sse): don't cool down the connection on a self-inflicted upstream timeout (504) (#5064)

Integrated into release/v3.8.38 (leva 5)

* fix(sse): robust Anthropic /v1/messages streaming — real ping keepalive + client-disconnect guard (#5063)

Integrated into release/v3.8.38 (leva 5)

* feat(video): add Alibaba DashScope (wan2.7-t2v) provider (#5051)

Integrated into release/v3.8.38 (leva 5)

* fix: preserve model hidden flags (isHidden) across model sync (#5086)

Integrated into release/v3.8.38 (leva 5)

* fix(models): derive model discovery config from registry modelsUrl (#5087)

Integrated into release/v3.8.38 (leva 5)

* fix(compression): replace fileURLToPath(import.meta.url) with runtime anchors for standalone bundle (#5089)

Integrated into release/v3.8.38 (leva 5)

* feat(cc): add summarized thinking display toggle (#5055)

Integrated into release/v3.8.38 (leva 5)

* Harden selected API error responses (#5032)

Integrated into release/v3.8.38 (leva 5)

* chore(quality): rebaseline file-size for leva 5 PR batch drift

6 frozen files grew from merged leva-5 PRs (cursor #4912, kiro #4911,
videoGeneration #5051, default #4727, base #4846, chat #5064); all covered
by per-PR tests. See _rebaseline_2026_06_26_leva5 in the baseline.

* feat(compression): compression playground (Play + Compare tabs) in the studio (#5080)

Integrated into release/v3.8.38

* fix(combo): fail over on empty-content 502 instead of exhausting the provider (#5085) (#5104)

* fix(dashboard): surface detailed credential-validation error in add-connection modal (#5088) (#5106)

* feat(providers): allow local/private provider URLs by default with scoped metadata-safe guard (#5066) (#5107)

* fix(diagnostics): treat non-streaming Claude messages shape as valid output (#5108) (#5116)

* fix(db): translate pt-BR SQLite driver-fallback log lines to English (#5103) (#5115)

* fix(sse): repair release base-reds — malformed-response false positives + header casing + stale tests (#5117)

Repairs the release/v3.8.38 base-reds; unblocks #5078.

* chore(quality): rebaseline file-size for responseSanitizer (#5117) + AddApiKeyModal drift

* fix(translator): forward image tool_result blocks as image_url (#5100)

Base-reds fixed (#5117); image tool_result→image_url. Integrated into release/v3.8.38.

* fix(responses): default text.format for openai-compatible responses providers (#5101)

Base-reds fixed (#5117); default text.format + file-size rebaseline. Integrated into release/v3.8.38.

* feat(dashboard): expose Fusion judgeModel + fusionTuning in the combo editor (#5074)

Base-reds fixed (#5117); Fusion editor + file-size rebaseline. Integrated into release/v3.8.38.

* feat(quota): add opt-in Codex/Claude auto-ping keepalive (#5102)

Base-reds fixed (#5117); auto-ping keepalive + file-size rebaseline. Integrated into release/v3.8.38.

* test(release): relocate 2 orphan test files into the collected flat tests/unit dir (#5120)

Unblocks Lint (test-discovery) on #5078. Integrated into release/v3.8.38.

* fix(translator): preserve reasoning-replay reasoning_content + repair 3 release-green test reds (#5122)

Repairs 3 release-green test reds + test-masking; unblocks #5078.

* test(golden): redact live Node version from provider translate-path snapshot (#5125)

Final golden unblock for #5078.

* test(golden): redact OmniRoute app version from translate-path snapshot (#5126)

Coverage shard golden unblock for #5078.

* Ignore disconnect races during in-band stream error handling (#5007)

Integrated into release/v3.8.38

* Track final connection IDs in failover logs (#5016)

Integrated into release/v3.8.38

* fix(sse): convert Gemini body to OpenAI format in antigravity MITM handler (#4845)

Integrated into release/v3.8.38 (rebased on tip, CHANGELOG re-injected)

* feat(providers): add ZenMux Free session-cookie provider (#5105)

Integrated into release/v3.8.38 (rebased on tip, CHANGELOG re-injected)

* feat(dashboard): click-to-edit model alias in provider page (#5119)

Integrated into release/v3.8.38 (rebased on tip, i18n scope verified, CHANGELOG re-injected)

* feat(mcp): web-session robustness — cookie dedup (PR6) + browser-pool observability (PR7) (#3368) (#5121)

Integrated into release/v3.8.38 (rebased on tip; cookie-dedup branch extracted to findExistingCookieConnection helper → complexity-neutral; CHANGELOG added)

* fix(usage): dedupe request-usage logging and debounce stats (#4940)

Integrated into release/v3.8.38 (rebased on tip; DB-handle hang was stale-base artifact — resetDbInstance already closes the handle, test green 5/5; file-size drift consolidated at release; CHANGELOG re-injected)

* fix(dashboard): key model visibility toggle on canonical providerId (#5091)

Integrated into release/v3.8.38 (retargeted main→release; .tsx visibility-key test green 2/2)

* chore(deps): bump actions/cache from 5.0.5 to 6.0.0 (#5112)

Integrated into release/v3.8.38 (retargeted main→release; workflow-only actions/cache bump — unit failures were stale main base-reds)

* fix(streaming): harden long OpenAI-compatible SSE streams (#5124)

Integrated into release/v3.8.38 (rebased on tip; streamHandler conflict with #5007 disconnect-guard resolved — both coexist, stream-handler 22/22 green)

* feat: Add Grok Build (xAI) provider with OAuth import-token flow (#5020)

Integrated into release/v3.8.38 (rebased on tip; Hard Rule #11 fix — Grok public client_id now via resolvePublicCred(grok_id), 3 literals removed; grok-oauth 7/7 + check:public-creds green)

* feat(providers): add Factory (factory.ai) as a subscription gateway provider (#5065)

Integrated into release/v3.8.38 (rebased on tip; added factory registry test for PR Test Policy + fixed check:env-doc-sync phantom FACTORY_API_KEY; factory loads in PROVIDERS, no Zod issue — that flag was a false positive)

* chore(test): reconcile golden snapshot + apikey count for new providers

#5020 (grok-cli), #5065 (factory), #5105 (zenmux-free) added providers but did
not regenerate tests/snapshots/provider/translate-path.json (now +3 entries) nor
bump the APIKEY_PROVIDERS count (159->160 for the factory gateway). Test-only
reconciliation; no production change.

* fix(resilience): harden quota and model lockout edge cases (#5093)

Integrated into release/v3.8.38 (rebased on tip). TRUST-BUT-VERIFY: dropped the PR's 0dd7df641 'fix unit gates' commit which reverted #5122 reasoning-replay (preserveReasoningContent) + re-introduced #4849 O(n^2) growth, and restored 5 tests it had realigned. Kept only the 3 declared resilience fixes (quota cutoff guard, gemini MIME, model-lockout maxCooldownMs); 23/23 green.

* Hydrate quota cache and scope auto combo candidates (#5015)

Integrated into release/v3.8.38 (rebased on tip). Kept core quota-cache hydration + auto-combo candidate scoping + combos UI; dropped out-of-scope toolCloaking refactor (conflicted with #4813 stripEnumDescriptions — took tip) and the unrelated sse-auth test split. Added quota-cache-hydrate-5015 regression test (Rule #18); combo-account-allowlist 8/8 + hydration 2/2 green.

* chore(quality): reconcile complexity + file-size baselines for v3.8.38 owner-PR batch

complexity 1972->1978 (+6) and file-size providers.ts 1093->1107 / usageHistory.ts
934->983 — drift from the /review-prs merge batch (#4845/#5105/#5020/#4940/#5093/
#5015 + #5121 cookie-dedup helper extraction). check:complexity/check:file-size do
not run on the PR->release fast-path, so the branch accrued unmeasured; all legit
feature/fix growth, not regression. See per-key justifications in each baseline.

* fix(security): exact-host Anthropic baseUrl check (CodeQL js/incomplete-url-substring-sanitization #674) (#5130)

The anthropic-compatible Bearer-fallback gate decided whether a configured baseUrl
targeted the official api.anthropic.com host via a substring `.includes("api.anthropic.com")`.
A look-alike upstream such as `https://api.anthropic.com.evil.test` or
`https://evil.test/?x=api.anthropic.com` matched the substring and was wrongly treated as
official, suppressing the Bearer fallback meant for third-party gateways
(CodeQL #674, js/incomplete-url-substring-sanitization, high).

Replace the substring test with an exported `isOfficialAnthropicBaseUrl()` helper that
parses the URL and compares the hostname for exact equality. Empty baseUrl stays official;
scheme-less hosts are parsed with an assumed https://; an unparseable baseUrl falls back to
third-party (Bearer emitted) as the safer default. Behavior for legitimate official/third-party
baseUrls is unchanged.

Adds tests/unit/anthropic-official-baseurl-host.test.ts covering official, look-alike,
scheme-less, and unparseable inputs plus a static guard that the substring pattern is gone.

* fix(proxy): repair one-click Deno & Cloudflare relay deployments (#5128) (#5132)

* fix(services): embed WS proxy honours LIVE_WS_HOST; reject empty messages early (#5110) (#5133)

* fix(api): resolve /v1/models/{id} case-insensitively (#5082) (#5135)

* fix(providers): add MiniMax M3 & Nemotron 3 Ultra to Cline catalog (#3321) (#5136)

* fix(proxy): make SOCKS5 handshake timeout tunable via SOCKS_HANDSHAKE_TIMEOUT_MS (#5109) (#5137)

* feat(sidebar): add support for colored menu icons (#3812)

Integrated into release/v3.8.38 (recreated on tip — fork had unrelated history; added getSidebarIconAccent regression test, Rule #18). Clean 2-file UI feature.

* fix(providers): complete grok-cli OAuth wiring + zenmux-free web-session metadata

Base-red repair for #5020 (grok-cli) and #5105 (zenmux-free), surfaced by the
full CI on the release PR (#5078) — the PR->release fast-path does not run the
oauth-providers-config / web-session-credentials / provider-consistency gates.

- grok-cli: register in OAUTH_PROVIDERS (providers.ts canonical list, fixes
  check:provider-consistency), add OAUTH_PROVIDER_IDS.GROK_CLI + GROK_CLI_CONFIG
  in oauth constants (provider config now sourced there, not a local literal),
  align oauth-providers-config.test.ts (EXPECTED_PROVIDER_KEYS + config map).
- zenmux-free: declare its web-session credential requirement (full Cookie header)
  in WEB_SESSION_CREDENTIAL_REQUIREMENTS.

Local: oauth-providers-config 27/27, web-session-credentials 4/4, grok-cli-oauth
7/7, check:provider-consistency OK, +115 OAUTH_PROVIDERS tests green.

* Fix resilience settings page response mapping (#5139)

Integrated into release/v3.8.38. Thanks @rdself for the fix and the regression test.

* fix(kiro): retire claude-sonnet-4.5 from catalog + pin 400 model-unavailable test (#5140)

Extracted the real change from #5140 (the bot PR regenerated the entire
freeModelCatalog.data.ts + touched package-lock.json; only the targeted
edits are kept here):
- remove claude-sonnet-4.5 from the Kiro registry entry
- remove the matching kiro free-model catalog row
- pin Kiro's verbatim 400 "Invalid model..." to isModelUnavailableError

Closes #4484

* fix(sidebar): drop orphan `settings` accent color (typecheck:core red) (#5142)

SIDEBAR_ICON_ACCENTS is typed Partial<Record<HideableSidebarItemId, string>>,
but `settings` is not a hideable item id (only `settings-general`,
`settings-appearance`, … and `context-settings` exist; there is no item with
`id: "settings"`), so the accent was unreachable. It broke `typecheck:core`
on the release tip ("'settings' does not exist in type …", introduced by
#3812 colored menu icons). Removing the orphan key restores a clean
typecheck:core (rc=0).

* feat: salvage batch 2 — diagnostics null-guard (#5096) + observed quota reset windows (#5025) (#5141)

* fix(diagnostics): null-guard content blocks in detectMalformedNonStream

A null (or non-object) entry in a Claude-native `content` array made the
non-stream classifier throw `TypeError: Cannot read properties of null
(reading 'type')`, crashing the malformed-response detection path. Guard
before type-asserting each block: a null/non-object block is simply skipped.

Two regression tests added (null block among valid blocks → null; only-null
blocks → empty_choices).

Salvaged from closed PR #5096 (base-stale; only the defensive guard — the
Claude-shape recognition it also carried already landed via #5108).

Co-authored-by: herjarsa <herjarsa@users.noreply.github.com>

* feat(quota): persist observed provider quota reset windows

Adds `provider_quota_reset_events` (migration 108) + `db/quotaResetEvents.ts`
to record real upstream weekly-quota window transitions whenever a quota
refresh shows the reset rolling to a new cycle (different day, later resetAt).
`apiKeyUsageLimits` now prefers the observed window start over the inferred
`resetAt − 7d`, falling back to snapshot inference when no event is recorded
yet. `quotaCache.setQuotaCache` records the transition opportunistically.

`recordProviderQuotaResetEventIfChanged` only fires for the primary weekly
window (not daily/sonnet), is idempotent (INSERT OR IGNORE on the unique
window key), and no-ops when the reset didn't actually roll. 4 unit tests
(tests/unit/lib/quota-reset-events.test.ts).

Salvaged from closed PR #5025 (which bundled this with two unrelated
features + a colliding migration 104). Renumbered to 108; module re-exported
from localDb (Rule #2).

Co-authored-by: Witroch4 <175152067+Witroch4@users.noreply.github.com>

---------

Co-authored-by: herjarsa <herjarsa@users.noreply.github.com>
Co-authored-by: Witroch4 <175152067+Witroch4@users.noreply.github.com>

* docs(i18n): sync 3.8.38 CHANGELOG section to 41 mirrors (unblock docs-accuracy) (#5144)

The root CHANGELOG [3.8.38] section grew with this cycle's merged PRs, but the
docs/i18n/<lang>/CHANGELOG.md mirrors were not re-synced — drifting >25% in body
size and failing check:docs-sync (the "Docs accuracy" fast-gate step) for every
open PR against the release.

Ran scripts/release/sync-changelog-i18n.mjs 3.8.38 3.8.37 to copy the root
[3.8.38] section into all 41 mirrors. check:docs-all now passes (exit 0).

Sections are copied verbatim; the per-language translation pass runs at release
time via i18n:run — this only restores the size-sync the gate enforces.

* feat(compression): pure per-step fidelity checker (4 invariants, fail-open)

* feat(compression): fidelityGate config + rejected breakdown fields

* feat(compression): wire per-step fidelity gate into stacked pipeline (opt-in)

* feat(compression): preview route accepts fidelityGate flag (playground)

* feat(compression): playground fidelity-gate toggle + lane rejection display

* docs(compression): note fidelityGate advanced thresholds are intentionally API-omitted

* refactor(compression): extract fidelity-gate step helpers to shrink strategySelector (file-size gate)

bodyToText and gateAdvance moved to fidelityGateStep.ts; StackAccumulator exported.
strategySelector: 889->854 (-35). Residual +6 vs pre-Milestone-B frozen 848 is the
irreducible StackOptions.fidelityGate field + two stacked-loop dispatch reads + import.
Baseline updated to 854 with justification. No cycle introduced (import type only).
940 compression tests pass; typecheck clean.

* test(usage): wire usageHistoryDedup under unit runner brace-list (#5145)

Integrated into release/v3.8.38.

* feat: salvage batch from closed stale PRs (#5038, #5057, #5076) (#5138)

Integrated into release/v3.8.38.

* test(combo): deterministic routing-decision matrix for all 17 strategies (#5146)

Integrated into release/v3.8.38.

* feat(compression): fuzzy near-duplicate dedup (session-dedup 2nd pass + playground toggle) (#5143)

Integrated into release/v3.8.38.

* chore(quality): rebaseline file-size for sidebarVisibility.ts + chat.ts drift (#5147)

Mid-cycle drift on release/v3.8.38 from already-merged PRs that the fast-path
(PR->release skips check:file-size) let accumulate without a bump:

- src/shared/constants/sidebarVisibility.ts 1100->1198 (#3812 colored menu
  icons, per-item accent map; #5142 dropped one orphan, net still above frozen)
- src/sse/handlers/chat.ts 1560->1575 (#5064 self-inflicted-timeout cooldown
  skip + #5124 long OpenAI-compatible SSE hardening + #5110 embed-WS
  LIVE_WS_HOST honour / early empty-message reject)

Each covered by its own PR tests; structural shrink of chat.ts tracked in #3501.
Unblocks the Fast Quality Gates for PRs targeting release/v3.8.38.

* chore(release): finalize v3.8.38 CHANGELOG + cycle reconciliation

- Reconcile [3.8.38]: +18 bullets (compression fidelity-gate/fuzzy-dedup #5143,
  quota keepalive #5102, web-session robustness #5121, MiniMax/Nemotron #5136,
  model-visibility #5091, failover logs #5016, disconnect races #5007, sidebar
  orphan #5142, SRE playbooks salvage #5138, new Security #5130 + Maintenance roll-up)
- Credit salvaged-PR authors (@JxnLexn / @KooshaPari / @herjarsa / @Witroch4)
- Remove phantom bullet for CLOSED-not-merged #5092 (setup aggregator never landed)
- Fix isHidden bullet PR citation #4389 -> #5086 (@herjarsa)
- Back-fill forgotten v3.8.36 bullet: #5026 crypto.randomUUID ID-gen (@hamsa0x7)
- Sync 41 i18n CHANGELOG mirrors; README What's New -> v3.8.38
- Rebaseline cycle drift: eslint 3987->4002, cognitive 833->841, dead-exports
  345->346, cyclomatic 1978->1980 (file-size handled by #5147)

* fix(i18n): add missing English UI labels (#5153)

Integrated into release/v3.8.38

* Preserve non-stream reasoning fields for compatible clients (#5155)

Integrated into release/v3.8.38

* feat(compression): ionizer engine — lossy JSON-array sampling reversible via CCR (#5148)

Integrated into release/v3.8.38

* test(combo): gated live smoke for combo strategies (in-process + VPS HTTP) (#5151)

Integrated into release/v3.8.38

* test: refresh release expectations to match current code (#5150)

Integrated into release/v3.8.38 (test-only base-red alignment extracted from #5150)

---------

Co-authored-by: Éder Costa <eder.almeida.costa@gmail.com>
Co-authored-by: José Victor Ferreira <root@josevictor.me>
Co-authored-by: Hernan Javier Ardila Sanchez <hjasgr@gmail.com>
Co-authored-by: fulorgnas <46461624+fulorgnas@users.noreply.github.com>
Co-authored-by: Randi <55005611+rdself@users.noreply.github.com>
Co-authored-by: Jan Leon <Jan.gaschler@gmail.com>
Co-authored-by: R. Beltran <rbeltran8000@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: KooshaPari <42529354+KooshaPari@users.noreply.github.com>
Co-authored-by: Ramel Tecnologia - Rafa Martins <146174365+rafacpti23@users.noreply.github.com>
Co-authored-by: herjarsa <herjarsa@users.noreply.github.com>
Co-authored-by: Witroch4 <175152067+Witroch4@users.noreply.github.com>
2026-06-27 09:07:12 -03:00
Diego Rodrigues de Sa e Souza
fa367dd99e Release v3.8.27 (#3968)
* chore(release): open v3.8.27 development cycle

* fix(security): polynomial ReDoS in comboAgentMiddleware regex (#3982)

* fix(security): eliminate polynomial ReDoS in comboAgentMiddleware <omniModel> regex (CodeQL js/polynomial-redos)

CACHE_TAG_PATTERN wrapped the tag in an unbounded `(?:\\n|\n|\r)*` prefix/suffix.
On an unanchored `.test()`/`.exec()` that is O(n²) on inputs with many newlines
(CodeQL js/polynomial-redos, alerts #612/#613). The surrounding runs are irrelevant
to detecting/capturing the tag, so the detection pattern now matches only the core
`<omniModel>([^<]+)</omniModel>`; the global strip pattern still consumes the
wrapping newlines (combo.ts streaming, #531) but BOUNDED ({0,16}) so it stays linear.

Behavior preserved: detection, model extraction, multi-tag stripping (#454) and
blank-line cleanup all unchanged (107 related tests green). Adds ReDoS-safety
regression tests (50k-newline inputs complete in <1ms).

* docs(changelog): add #3982 ReDoS fix to [3.8.27]

* ci(security): harden workflows — artipacked persist-credentials + cache-poisoning + SC2086 (#3965)

* Refine provider quota card display (#3969)

Integrated into release/v3.8.27

* feat: add sidebar group separator toggles (#3971)

Integrated into release/v3.8.27

* Gate control-plane proxy direct fallback (#3963)

Integrated into release/v3.8.27

* Capture actual upstream provider requests (#3941)

Integrated into release/v3.8.27

* ci(quality): flip require-tighten + osv + Trivy to blocking (v3.8.27 cycle-end) (#3984)

* fix(resilience): respect connection cooldown stored as numeric epoch (#3954) (#3995)

rate_limited_until is a TEXT column, but setConnectionRateLimitUntil (Antigravity full-quota path) persists a raw epoch number that SQLite coerces to a numeric string ("1781696905131.0"). The selection predicate isAccountUnavailable then did new Date("1781696905131.0") -> NaN, so the cooling connection was never skipped and the router kept dispatching to rate-limited accounts. Normalize numeric-epoch strings (and number/Date/ISO) via a shared cooldownUntilMs() helper in isAccountUnavailable / getEarliestRateLimitedUntil / filterAvailableAccounts / parseFutureDateMs. ISO behavior preserved.

* fix(providers): fetch live /models for LLM7 and BytePlus (#3976) (#3996)

llm7 and byteplus carry a real modelsUrl but were not classified by any live-fetch branch of the model-import route, so their hardcoded 4-entry registry catalog was served (source local_catalog) instead of the upstream catalog. Add both to NAMED_OPENAI_STYLE_PROVIDERS so the route probes <baseUrl>/models and serves the live list, falling back to the local catalog only on fetch failure.

* fix(dashboard): logs auto-refresh reads live visibility, not a stale mount ref (#3972) (#3997)

The auto-refresh interval gated each tick on visibleRef, seeded once at mount and updated only by a visibilitychange event. A tab mounted while document.visibilityState is 'hidden' (background load, bfcache, embedded/proxied webviews) with no later visibilitychange left the ref false forever, so the interval ticked but never fetched — only the manual button worked. Read the live document.visibilityState in the tick instead.

* feat(compression): add Indonesian caveman rules and language pack (#3975)

Integrated into release/v3.8.27

(cherry picked from commit c9b5b1a892)

* fix(combo): shuffle strict-random fallback remainder to spread load (#3959) (#3998)

strict-random shuffled only the deck-selected slot 0 and left the fallback remainder in fixed priority order, so after a failing deck pick the chain always fell through to the same top-priority model — a persistently-failing model was retried on essentially every request and fallback load never spread across peers. Shuffle the remainder too (like the random strategy).

* Add provider auth visibility controls (#3953)

Integrated into release/v3.8.27

* fix(claude): forward client tool-search-tool anthropic-beta on the Claude OAuth path (#3974) (#3999)

The client-negotiated anthropic-beta: tool-search-tool-2025-10-19 was dropped on both Claude code paths (default executor rebuilt from static ANTHROPIC_BETA_CLAUDE_OAUTH; selectBetaFlags only read the client beta to gate thinking/effort), so claude.ai rejected deferred-tool requests with 400 'Tool reference not found'. Add an allowlist-merge (mergeClientAnthropicBeta) that unions the client's allowlisted betas into the outbound set on both paths, preserving #3415 (no forced thinking/effort).

* feat(providers): add model search filter to provider dashboard (#3950)

Integrated into release/v3.8.27

* fix(vision-bridge): force bridge for tokenrouter deepseek models (#3946)

Integrated into release/v3.8.27

* fix(executor): strip stream_options on non-streaming requests (#3884) (#4000)

Clients that send stream_options:{include_usage:true} regardless of stream (e.g. the OpenAI Python SDK) had it passed through on non-streaming calls; NVIDIA NIM rejected it with 400 'Stream options can only be defined when stream=True'. DefaultExecutor.transformRequest only injected/cleared stream_options on the streaming branch and never stripped a client-sent value when stream=false. Add a !stream strip branch; the streaming injection path is unchanged. Global to openai-compat providers.

* fix(qwen-web): cookie validation false-positive - check response body for user object (#3958)

Integrated into release/v3.8.27

* fix(db): persist backup retention days (#3970)

Integrated into release/v3.8.27

* 大量UI显示和i18n优化 (#3973)

Integrated into release/v3.8.27

* deps: bump the npm_and_yarn group across 1 directory with 2 updates (#3943)

Integrated into release/v3.8.27

* deps: bump form-data from 4.0.5 to 4.0.6 (#3944)

Integrated into release/v3.8.27

* deps: bump vite from 8.0.5 to 8.0.16 (#3942)

Integrated into release/v3.8.27

* chore(quality): re-baseline validation.ts 4407->4428 (#3958 qwen body-check)

The qwen-web validation body-check merged in #3958 pushed validation.ts past its
frozen size on the integrated release tip. Bump the baseline with justification;
no logic is separately extractable from the existing qwen-web validation branch.

* deps: bump the production group with 13 updates (#3915)

Integrated into release/v3.8.27 — low-risk group (playwright 1.60→1.61 minor + transitive patches; fumadocs-core 16.9→16.10 minor).

* chore(deps): ignore jscpd major bumps (v5 Rust rewrite breaks the duplication gate)

Our duplication ratchet (scripts/check/check-duplication.mjs) is pinned to jscpd@4
and parses jscpd-report.json against a frozen baseline. jscpd v5 is a native Rust
binary with no Node.js API and a different report/bin, so a major bump would break
the gate. Migrate deliberately, not via dependabot. Closes the noise from #3916.

* fix(perplexity-web): parse schematized diff_block stream so answers aren't empty (#4001)

Integrated into release/v3.8.27 — schematized diff_block parsing follow-up to #3938.

* refactor: modularize providerRegistry.ts into 159 individual provider plugins (#3993)

Modularize provider registry (#3594). Integrated into release/v3.8.27 after rebase + behavior-preservation verification (provider-consistency gate 159/232/0, typecheck, registry tests, build 556/556).

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>

* fix(registry): restore byteplus + mimocode dropped by #3993 modularization

The provider-registry modularization (#3993) was cut from a base predating the
byteplus (#3877) and mimocode (#3837) registry entries, so merging it silently
dropped both providers (getRegistryEntry returned undefined → validation reported
'not supported'). Re-add them as registry modules in the new structure; registered
count 159→161, provider-consistency 161/232/0.

Also align the pre-existing qwen-web validator test to #3958: since the validator
now requires a real `user` object in the 200 body, the mock must carry one.

* refactor: modularize schemas (non-stacked) (#3988)

Modularize validation schemas (#3594). Integrated into release/v3.8.27 after rebase (reconciled the merged hiddenSidebarGroupLabels #3971 + intelligenceSyncRequestSchema into the new modules) + behavior verification (typecheck, 195 schema/settings/validation tests, build 556/556).

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>

* fix(default-executor): honor custom providerSpecificData.baseUrl for OpenAI-format providers (#4002)

Integrated into release/v3.8.27 — honor custom providerSpecificData.baseUrl in DefaultExecutor (openai-format), tested.

* feat(openai): honor custom base URL in model discovery + complete openai/codex pricing (#4005)

Integrated into release/v3.8.27 — openai model-discovery honors custom base URL (SSRF-guarded) + pricing rows for new openai/codex models. Tested + baselines bumped.

* fix(live-ws): bridge sidecar events to dashboard (#4004)

Integrated into release/v3.8.27 — repair LiveWS sidecar (startup, same-origin /live-ws, main→sidecar compression.completed bridge, early-msg queue). Fixed the cookie-parse regex (\s) + added a focused unit test; baseline bumped for the non-blocking chatCore bridge.

* docs(troubleshooting): note MITM proxy cannot intercept Windows-host apps under WSL (#4003)

Integrated into release/v3.8.27 — MITM/WSL troubleshooting note.

* fix(repo): untrack accidentally-committed root node_modules symlink + gitignore it

A worktree node_modules symlink (-> the main checkout's node_modules) was staged by a
`git add -A` during the #3988 merge and committed into 05213ac6a. The symlink points
at the repo's own node_modules path, so checking it out turns the main checkout's
node_modules into a self-referential symlink (breaking tsx/all node ops). Untrack it and
add a root-anchored /node_modules ignore so the symlink form can't be re-committed (the
existing 'node_modules/' only matches directories).

* fix(quality): allowlist socks dep (declared by #4004, never allowlisted)

socks@^2.8.7 was added to package.json in #4004 (LiveWS sidecar, 02302131f)
as a phantom-dep cleanup but never added to dependency-allowlist.json, so
check:deps has been red on the release tip ever since. socks is the standard
SOCKS proxy client (dep of fetch-socks), legitimate and years old.

* feat(sse): real LLMLingua-2 ONNX compression engine (stable) (#4014)

Integrated into release/v3.8.27.

Adjustments before merge:
- Synced with the current release tip (was 11 commits behind).
- Added the 3 LLMLingua-2 ONNX optional-runtime deps to dependency-allowlist.json
  (@atjsh/llmlingua-2, @tensorflow/tfjs, js-tiktoken) — the only gate that was red.
- socks was allowlisted directly on release (separate fix d7db5c73d; it was declared
  by #4004 but never allowlisted, leaving check:deps red release-wide).

Verified locally: check:deps OK, file-size OK, public-creds OK, provider-consistency
161/232/0, typecheck:core clean, 24/24 LLMLingua tests pass. The only remaining Fast-QG
red is the pre-existing #3972 orphan test (request-logger-autorefresh-visibility-3972.test.tsx),
which is release-wide and unrelated to this PR.

* test(dashboard): rehome #3972 logs auto-refresh test so a runner collects it

tests/unit/request-logger-autorefresh-visibility-3972.test.tsx (added by #3972
via #3997) sat at the top level of tests/unit/ as a .tsx vitest test, which NO
runner collects: the node runner only globs *.test.ts, and test:vitest:ui only
runs tests/unit/ui. So the #3972 regression guard never executed in CI and
check:test-discovery was red release-wide. Move it under tests/unit/ui/ (the
collected vitest:ui path) and fix the relative import depth. Verified: the test
now runs and passes (2/2), and check:test-discovery is green.

* feat(compression): capture per-engine analytics (#3960) + Lite schema fix (#3952) (#4018)

Captures the net-new value from #3960 (per-engine breakdown analytics) and #3952 (Lite engine schema fix) onto release/v3.8.27. Fast QG green; 622/622 compression+analytics tests pass.

* fix(sse): guard model-less registry entries in getUnsupportedParams (mimocode) (#4015)

Real bugfix: guard model-less registry entries (mimocode) in getUnsupportedParams so handleChatCore no longer throws 'entry.models is not iterable' / reports 'All models failed' for unrelated requests. Includes a regression test. Fast QG green.

* feat(ci): Quality Gate v2 — Onda 0 + Onda 1 (gate flips, TIA, SAST, DAST-smoke, mutation infra) (#4016)

* docs(ops): add quality-gate assessment + replication playbook (Fase 9 foundation)

* feat(ci): flip oasdiff breaking-change gate to blocking (ratchet)

* docs(ops): deliver main branch-protection ruleset for owner to apply

* fix(ci): run typecheck:core in PR->release fast-gates (close fast-gates hole, part 1)

* perf(mutation): enable Stryker incremental mode + cache (scales the 60/80 rollout)

* feat(ci): commit CodeQL advanced config (security-extended), replacing default-setup

* feat(ci): version semgrep SAST workflow (owasp/secrets), advisory

* feat(quality): TIA test-impact map builder (import-graph; map built at runtime, gitignored)

* feat(quality): TIA impacted-test selector with run-all fail-safe

* fix(ci): run TIA-impacted unit tests in PR->release fast-gates (build map at runtime, fail-safe full)

* feat(ci): DAST-smoke per-PR (schemathesis subset + promptfoo injection-guard, blocking)

* fix(ci): unbreak Fase 9 PR CI (MDX frontmatter, CodeQL conflict, dast-smoke advisory)

- Add MDX frontmatter to docs/ops/{BRANCH_PROTECTION_MAIN,QUALITY_GATE_PLAYBOOK}.md.
  fumadocs rejects frontmatter-less docs -> 'npm run build' failed -> broke dast-smoke's
  build step (the release fast-gates never runs build, so this only surfaced on the PR).
- codeql.yml: workflow_dispatch-only until the owner switches repo CodeQL Default->Advanced
  (advanced configs cannot be processed while default setup is enabled; documented inline).
- dast-smoke.yml: job-level continue-on-error (advisory) so this brand-new gate matures
  before it blocks (repo convention: advisory -> blocking).

* ci(quality): make TIA unit-test step advisory until release test-debt is cleared

release/v3.8.27 carries ~17 pre-existing failing unit tests (budget #3537, apiKey
#3552, several Zod schemas, Puter/Qwen executors, mimocode entry, etc.) unrelated to
this PR — the new 'run tests on PR->release' gate surfaced them. Per the repo's
advisory->blocking convention, this step enters advisory (it still runs + reports)
so pre-existing debt doesn't block the gate program. typecheck:core stays blocking.
Flip to blocking (remove continue-on-error) once the release suite is green.

* fix(sse): preserve Kiro streaming finish_reason tool_calls (#3980) (#4025)

* fix(guardrails): preserve original image when vision-bridge describe fails (#4012) (#4026)

* feat(api): advertise combo capabilities on import surfaces (#3979) (#4027)

* feat(sse): delegated Anthropic Context Editing for Claude (clear_tool_uses) (#4021)

Opt-in Claude-only delegated compression: injects context_management.clear_tool_uses_20250919 at the Claude pre-serialization chokepoint (composes with clear_thinking, thinking first), threaded via ExecuteInput from handleChatCore. Pure edit-builder + 11 tests (7 unit + 4 e2e fetch-capture). Beta context-management-2025-06-27 already advertised; allowlist done. Telemetry/400-fallback/claude-web coverage deferred.

* fix(opencode): map x-session-affinity to x-opencode-session for custom providers (#4022) (#4028)

* fix(dashboard): Playground Compare tab loading + HTTP method guard (#4024)

randomUUID non-HTTPS fallback + static CompareTab import; raw HTTP TRACE->405 method guard wired into dev + standalone servers. Integrated into release/v3.8.27.

* refactor(dashboard): settings UI layout + API Keys naming (#4020)

Presentation/relabel refactor of the Settings dashboard (API Manager -> API Keys), card relocations, Toggle adoption, present-but-disabled engine steps. Auth-file changes are string/comment-only (no behavior change). Integrated into release/v3.8.27.

* fix: restore unit regressions dropped by lossy schema/registry modularizations (#4030)

Restores schema fields (combo reasoningTokenBuffer, budget-0 #3537, openrouter preset, proxy family #3777, resilience degradation/providerCooldown), qwen-web v2 endpoint+catalog, mimocode models key — all dropped by #3988/#3993 — and aligns 3 tests to #3941/#3993. Verified: 8 failing regression tests on release tip -> 131/131 green on this branch. Integrated into release/v3.8.27.

* fix(api): return 400 (not 500) for malformed JSON on /api/auth/login (#4031)

Wrap request.json() so a malformed/non-JSON login body returns a structured 400 instead of falling through to the 500 catch. Fixes the schemathesis high-risk-endpoint DAST finding (verified: schemathesis step now passes). +TDD test. Integrated into release/v3.8.27.

* feat(dashboard): real circuit-breaker state in the Combo Live cascade (U1b) (#4029)

Overlays real provider circuit-breaker state (GET /api/monitoring/health) onto the Combo Live cascade as a 'CB: OPEN · 41s' badge. Pure enrichRunWithBreakers + fail-soft useProviderBreakerHealth poll; graceful when health is absent. +13 tests. Integrated into release/v3.8.27.

* Fix promptfoo security assertion parsing (#4032)

* chore(deps): dependabot security bumps + drop unused gray-matter (#4036)

Integrated into release/v3.8.27 — dependabot security bumps (form-data/js-yaml/protobufjs/dompurify/hono) + drop unused gray-matter. Unblocks the npm audit:deps gate (Lint) branch-wide.

* fix(ci): scope TIA to node:test unit files only (mirror test:unit glob) (#4035)

Integrated into release/v3.8.27 — scopes the advisory TIA step to the test:unit node:test glob, fixing the 99 false failures. +4 TDD.

* Refine compression settings, storage labels, and sidebar grouping (#4033)

Integrated into release/v3.8.27 — relocate Token Saver into Compression Settings (controlled component), reorder Security/Authz tabs, storage labels + i18n relabel. Thanks @rdself!

* [codex] add per-key local usage command (#4034)

Integrated into release/v3.8.27 — per-key local @@om-usage command (cached quota, no upstream routing). Rebased onto modularized schemas/keys.ts + file-size rebaseline. Thanks @Witroch4!

* chore(release): reconcile v3.8.27 CHANGELOG + i18n mirrors

* ci(quality): unblock v3.8.27 release gates (zizmor pin + test-masking allowlist)

- zizmor ratchet (151→139, no regression): SHA-pin every action ref ADDED this
  cycle — codeql/dast-smoke/semgrep (3 new workflows) + trivy-action (docker-publish)
  + actions/cache (nightly-mutation). Pre-existing tag refs keep the repo convention.
- test-masking: add config/quality/test-masking-allowlist.json + allowlist support in
  check-test-masking.mjs (exempts ONLY the net-assert-reduction signal; tautology/skip/
  deletion still fire). Allowlists 2 verified-legitimate reductions:
  appearance-widget-settings-schema (#4033 removed showTokenSaverOnEndpoint field) and
  dashboard-shell-tabs (#3973 tabs→redirect refactor, asserts replaced). +4 gate tests.

* test(quality): reword test-masking self-test comments to avoid literal masking patterns

The added allowlist-test comments contained the literal strings 'assert.ok(true)' and
'.skip' which the masking detector's own regexes match as text — making the gate flag
its own test file (net +1 tautology/skip/extended-tautology vs main). Reworded to plain
prose ('a new tautology', 'a new skip marker'); test logic unchanged (24/24 pass).

* fix(quality): unblock v3.8.27 release — align 3 stale tests + restore modularized settings-schema parity

Release-PR full CI surfaced 3 deterministic test failures (no live product regression),
all stale vs legitimate cycle changes:

- settings-schema parity (#3988): the modularized updateSettingsSchema barrel
  (schemas/settings.ts) had diverged from the canonical settingsSchemas.ts (45 vs 85
  fields — 40 dropped + 6 extra), a lossy-modularization dead-code copy. Re-export from
  the canonical source so the barrel can never diverge again (runtime already uses
  canonical). Parity test now passes.
- api-manager permissions modal: #4034 added a 4th self-service switch (per-key usage
  allowance); a11y invariant (every switch type="button") still holds. Updated the
  static count 3 -> 4.
- pack-artifact policy: dist/http-method-guard.cjs became a required runtime path;
  added it to the test's expected missing-paths list.

Also documents the gate gap for Fase 9 (QUALITY_GATE_PLAYBOOK Parte 6): G1 run the
deterministic unit layer + test-masking on PR->release (not just PR->main), G2 a
modularization-parity gate (would have caught the #3988 drop at its PR), G3 flake
quarantine. Env flakes (LiveWS startup timeout, integration server-startup cascade)
are pre-existing/CI-env, triaged separately.

---------

Co-authored-by: Randi <55005611+rdself@users.noreply.github.com>
Co-authored-by: Veier04 <118300867+Veier04@users.noreply.github.com>
Co-authored-by: Felipe Sartori <felipesartori.ti@gmail.com>
Co-authored-by: WormAlien <164898390+WormAlien@users.noreply.github.com>
Co-authored-by: thezukiru <121331256+thezukiru@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: NOXX - Commiter <artur1992123@mail.ru>
Co-authored-by: Paijo <14921983+oyi77@users.noreply.github.com>
Co-authored-by: Demiurge The Single <megamen932@gmail.com>
Co-authored-by: Witroch4 <witalo_rocha@hotmail.com>
2026-06-17 02:43:21 -03:00
Diego Rodrigues de Sa e Souza
81a37b67ed Release v3.8.26 (#3875)
OmniRoute v3.8.26 — see CHANGELOG.md [3.8.26] for the full notes.

Highlights: Vertex AI media generation (#3929), GLM-5.2 effort-tier routing (#3885),
sticky round-robin combos (#3846), OpenRouter connection presets (#3878), compression
prompt-cache fix (#3936/#3890), and a security pass (form-data/vite + workflow hardening, #3949).

Co-authored-by: artickc <artickc@users.noreply.github.com>
Co-authored-by: rdself <rdself@users.noreply.github.com>
Co-authored-by: herjarsa <herjarsa@users.noreply.github.com>
Co-authored-by: Jack Smith <16862258+YunyunZhai@users.noreply.github.com>
Co-authored-by: dhaern <dhaern@users.noreply.github.com>
Co-authored-by: adivekar-utexas <adivekar-utexas@users.noreply.github.com>
Co-authored-by: megamen32 <megamen32@users.noreply.github.com>
Co-authored-by: zhiru <zhiru@users.noreply.github.com>
Co-authored-by: insoln <insoln@users.noreply.github.com>
Co-authored-by: diego-anselmo <diego-anselmo@users.noreply.github.com>
2026-06-16 01:00:40 -03:00