Commit Graph

7894 Commits

Author SHA1 Message Date
diegosouzapw
dbcbffc7bc fix(quality): let check-forgotten-sibling-tests read a 1,000-file diff
The gate shells out to `git diff` through execFileSync with Node's default 1 MB
maxBuffer; the 1,292-file codemod in this PR is the first diff large enough to
overflow it, and the gate died with `spawnSync git ENOBUFS` before comparing
anything. 64 MB is far above any real PR and costs nothing when unused.
2026-08-29 00:31:09 -03:00
diegosouzapw
37ecab3659 test(infra): retry recursive temp-dir removal instead of failing a shard on ENOTEMPTY (#11966)
Two shards on release/v3.8.51 went red in one day with the same signature —
"ENOTEMPTY, Directory not empty: /tmp/omniroute-<test>-XXXXXX" — from
combo-same-provider-cascade (Unit Tests fast-path 4/4, on a PR that touches only
.github/) and auth-policy-embeddings-webfetch-7785 (the 20k-test TIA step). Both pass
alone and on re-run: the cleanup races something still writing into the directory
(SQLite WAL/-shm checkpoint, a worker, the backup) and under a loaded hosted runner
the window opens. 1154 test files do their own cleanup with
fs.rmSync(dir, { recursive: true, force: true }); 57 already asked for retries.

One-shot codemod (scripts/ad-hoc/codemod-rm-maxretries.mjs, kept for the record):
every rm / rmSync / rmdirSync option object with `recursive: true` and no
`maxRetries` gains `maxRetries: 5, retryDelay: 100` — Node itself then retries
ENOTEMPTY/EBUSY/EPERM for up to ~0.5 s before giving up. 2243 call sites in 1292
files under tests/, the shared tests/_setup/isolateDataDir.ts exit hook included.
Only the option object changes: no call site, assertion or import is touched.

Validation: prettier and ESLint (with the frozen suppressions) clean on all 1292
files; a random 20-file sample runs green (quota-redis-store hangs identically on
the untouched tree — it needs a Redis on localhost, an environment matter). The
four unit shards on this PR are the full run.
2026-08-28 23:56:58 -03:00
Diego Rodrigues de Sa e Souza
d7cdfcad43 fix(ci): take the two hosted-runner builds off the PR rail (#11946, option 3) (#11962)
The hosted 7 GB runner cannot build release/v3.8.51 in any profile: `Build App`
(build.yml, push on every branch, full `build:release`) died in 19 of the last 30
runs — the branch tip included — with "The runner has received a shutdown signal"
~8 min into `next build`, swapfile and all; the advisory quality.yml build failed on
8/8 recent fork PRs with the same recipe; and `DAST smoke (PR)`'s backend-only build
died ~7 min in before the server even started, hidden as a permanently red
continue-on-error check. Together they painted every PR into release/** red with
zero signal and, on build.yml, produced an artefact nothing downloads.

- build.yml: workflow_dispatch only. The bundle is validated where a build fits —
  ci.yml `Build` on the self-hosted omni-build pool after every merge to main, and
  nightly-release-green.yml on the same pool for release/**.
- dast-smoke.yml: pull_request into main only (plus workflow_dispatch to smoke a
  release branch by hand); main's tree still builds on the hosted runner in ~5.5 min.
- quality.yml: the fork-only rationale of `Build (advisory)` updated to say why
  own-origin PRs no longer get a hosted build either. Behaviour unchanged.

check:workflows --ratchet: 194 zizmor findings, baseline 194. check-workflows and
backend-only-smoke-workflows suites pass. Trade-off stated in the PR: own-origin PRs
into release/** lose a pre-merge build that was not succeeding anyway; the nightly
rail files a base-red issue within a day if a merge breaks the build.
2026-08-28 22:52:15 -03:00
Diego Rodrigues de Sa e Souza
034314262e docs(agents): sync-back landings are fast-forward, never squash (#11964)
Records the v3.8.50 → v3.8.51 precedent in the single source of truth: a
main → release/vX+1 sync PR lands by fast-forward push so main stays an ancestor
of the release branch (squash re-conflicts the next sync-back on every file main
touched — 551 conflicts this cycle before the two-step merge), plus the two
post-landing checks (ancestry assert; ratchet files carried main's freezes).
The full procedure lives in the generate-release Phase 5 skill.
2026-08-28 22:44:06 -03:00
Diego Rodrigues de Sa e Souza
3d125647c8 chore(ci): cap the unit shards at 30 min and stop restoring stale ESLint caches (#11963)
- quality.yml fast-unit: timeout-minutes: 30. A shard finishes in ~10 min; without
  a ceiling a hung test process holds the PR for GitHub's 6 h default. On
  2026-08-28 shard 1/4 sat 64 min without a line of output — twice at the same spot,
  a timing race that vanished on the third run — while the other three shards were
  long green. A fast red plus a re-run beats a silent multi-hour hold.
- quality.yml lint-guard + the earlier ESLint cache block: drop the
  `restore-keys: eslint-<os>-` fallback (#11600, P-II.1 of the v3.8.50 postmortem).
  The key already hashes the lint config, the suppressions file and the lockfile;
  the fallback restored a cache built under a DIFFERENT configuration and its stale
  per-file verdicts are how 215 pre-existing errors stayed invisible for a cycle.
  Exact key or a cold full lint — never a partial cache from another configuration.

check:workflows --ratchet unchanged (194/194); check-workflows suite 32/32.
2026-08-28 22:43:11 -03:00
Diego Rodrigues de Sa e Souza
d0f69e4c70 chore(quality): re-freeze the ESLint suppressions on release/v3.8.51 from a clean-room run (#11955)
* chore(quality): re-freeze the ESLint suppressions on release/v3.8.51 from a clean-room run

`No new ESLint warnings` failed on every PR against release/v3.8.51 with exit 2:
"There are suppressions left that do not occur anymore". Measured in a depth-1 clone
with `npm ci` from the branch's own lockfile and the job's exact command
(`npm run lint:json -- --max-warnings 0`): 56 errors — 55 `no-explicit-any` in six
files that landed while the base was red (#11843 isFree tests: 22; b7102140d5 socks
connect timeout: 33) plus one `no-unused-vars` — and stale entries for files that no
longer violate. The devbox figure previously quoted in #11924 (280, with 224
react-hooks/*) does not reproduce on the lockfile install and is withdrawn.

- config/quality/eslint-suppressions.json: `--prune-suppressions` (two stale file
  entries removed) and the 55 pre-existing `any` frozen at their exact counts — the
  file is a ratchet, counts only go down; the debt stays tracked in #11924.
- open-sse/services/adobeFireflyCatalog.ts: remove `GPT_SIZE_MAP`, a constant the
  f3d9279b44 split left behind with no reader (the real violation, fixed not frozen).

Verification in the clean room after both changes, same command as CI: exit 0,
0 errors, 0 warnings (1238 files / 5487 suppressions).

* chore(quality): tighten openapiCoverage.pct to the measured 39 (require-tighten)

With ESLint back to 0/0 on this PR, the job's next step (check-quality-ratchet
--require-tighten) started failing: openapiCoverage.pct improved from 38.4 to 39
(delta 0.6 > slack 0.5) and the baseline must be tightened in the same PR. 39 is
the value CI collect-metrics measured on run 33213844112 and a clean-room checkout
of 777d9d1629 reproduces it; the cycle's new routes landed documented in
docs/openapi.yaml. Only this metric moves; annotation follows the file's convention.
2026-08-28 22:07:40 -03:00
diegosouzapw
777d9d1629 test(translator): fix the relative imports of the relocated deferred-finish test
#11940 moved tests/unit/translator/openai-to-claude-trailing-usage.test.ts one level
up so a collector would run it, but kept the ../../../ import path from the old
directory, so the file failed to load and painted Unit Tests fast-path (3/4) red on
every PR since a94fe23e89. The path now matches its new location (5/5 pass).
2026-08-28 19:23:37 -03:00
diegosouzapw
9661611e31 Merge remote-tracking branch 'origin/main' into chore/sync-main-into-3851-20260828c 2026-08-28 19:03:24 -03:00
Diego Rodrigues de Sa e Souza
24c0643a94 test(check): escape the runs-on fixture with JSON.stringify, not a quote-only replace (#11942)
CodeQL js/incomplete-sanitization (alert #888 on #11929): the hand-rolled replace
only escaped double quotes, so a backslash in the fixture would have produced a
malformed YAML scalar. JSON.stringify covers every escape the double-quoted YAML
scalar needs. Test-only change (7/7 pass).
2026-08-28 19:01:54 -03:00
Diego Rodrigues de Sa e Souza
a94fe23e89 fix(release): drain the twelve reds every PR against release/v3.8.51 was born with (#11940)
* fix(release): drain the twelve reds every PR against release/v3.8.51 was born with

Measured on the cycle tip: fifteen unit files were red on every PR. Two came
from the v3.8.50 sync-back (fixed in #11929); the other thirteen predate it and
are the branch's own drift. This sweep clears all of them but the ESLint debt
(#11924), each with the smallest change that keeps the guard honest:

- .env.example + ENVIRONMENT.md: NEXT_PUBLIC_SW_BUILD_ID / OMNIROUTE_SW_BUILD_ID /
  SOURCE_VERSION (#11779 service-worker cache busting) documented — the env/docs
  contract gate was failing on every PR.
- stryker.conf.json: the six tests the mutation gate found covering mutated modules
  (four retirement runtime-block suites, combo connection-aware expansion, tunnel
  error sanitization) registered in tap.testFiles.
- dependency-allowlist: eslint-plugin-react-hooks 7.0.1 approved; its findings are
  tracked in #11924.
- i18n: the six combo.sort.* strings (d5dfcfff58) translated for vi (strict parity)
  and pt-BR.
- docs/providers/CHATGPT_WEB.md: the retirement test is migration-168, not 163.
- g4f gateways: authHint now says member key, which the discontinued-providers
  guard asserts.
- tests realigned to the catalog the branch actually ships: qwen-web (#11713) and
  chatgpt-web (#11720) are retired, so web-session-contract and
  token-health-check-webcookie use perplexity-web, grok-web and chatgpt-web-codex.
- db-core-init: the two minimal legacy fixtures gained the columns migrations 164-168
  UPDATE (error_code, last_error*, test_status) — they exist on every real legacy DB
  (base CREATE TABLE); the fixtures simply never declared them.
- no-js-extension guard: a .js specifier whose target is a genuine JavaScript file
  (open-sse/lib/deepseek-pow-hash.js, shared with a worker) is not the #10674
  defect; the test now skips targets that exist as .js.

All twelve files pass locally; docs-sync, docs-counts, env-doc-sync, the tap
drift gate and the fabricated-docs gates are green on the tree.

* test(release): move the deferred-finish translator test into a collected path

tests/unit/translator/ is not one of the unit collectors (package.json test:unit,
merge-train.sh, build-test-impact-map, check-test-discovery), so the suite that
dd35750e5f added there never ran — check:test-discovery flagged it as a new orphan
on every PR. Relocated next to its sibling openai-to-claude-trailing-usage-11817
under tests/unit/, where the root glob collects it (5/5 pass).

* fix(dashboard): type the four sort-method sites #11812 left red on the dashboard typecheck ratchet

d5dfcfff58 added the combo model sort and raised combos/page.tsx from 23 to 27
scoped TypeScript errors (TS2339 +1, TS2345 +2, TS2322 +1), which fails
check:dashboard-typecheck on every PR against release/v3.8.51:

- initialSortMethod: sanitizeComboRuntimeConfig() is untyped, so config.modelSort is
  unknown; narrow it before reading .method (normalizeSortMethod takes unknown anyway).
- handleAddModels: the batch path passes ComboBuilderDraftModelStep[] to the ComboStep[]
  sort helpers without the cast handleSortChange already uses; mirror it.
- ComboSortSelect expects a translate-with-fallback (k, f) => string, but received
  next-intl's Translator whose second argument is a values object. Pass the page's
  getI18nOrFallback adapter instead of the raw translator — that is also what makes
  the `has()` check and the fallback text actually work at runtime.

Baseline untouched (no widening). Scoped tsc: 0 new/regressed errors.
2026-08-28 18:18:33 -03:00
Diego Rodrigues de Sa e Souza
8dfdd95187 test(release): align five suites with the contracts #11933, #11919 and #11876 shipped on release/v3.8.51 (#11944)
Eleven PRs landed on release/v3.8.51 while the branch carried fifteen base reds, and
nine more red tests hid among them. None is a defect in the shipped code; each test
still encoded the contract that the merged PR deliberately replaced:

- openai-to-claude finish deferral (dd35750e5f, #11933): a finish chunk that carries no
  usage is now held until the end-of-stream flush that production performs
  (open-sse/utils/stream.ts flush -> translateResponse(..., null, state)). The drivers in
  stream-markdown-token-boundary, translator-tool-call-shim and
  gemini-malformed-function-call-finish-reason-2462 fed the finish chunk and asserted
  the terminal events immediately; they now mirror the flush. Assertions unchanged.
- authoritative live catalog (3d2832b836, #11919 fixes #11829): a synced catalog replaces
  the static registry, so model-lifecycle-integration no longer expects the static-only
  gpt-5.6-sol row to survive a sync. The #8627 contract the file guards (stale chat rows
  suppressed, typed media retained) is untouched.
- provider asset provenance (#11876): the unit shards check out with depth 1. The fixture
  pinned a historical commit as auditedCommit (absent on a shallow clone), the
  "binds auditedCommit" case relied on the repository root commit (the grafted HEAD on
  a shallow clone, which matches the physical snapshot), and the real-manifest case
  needs the audited commit fetched. The fixture now audits HEAD, the mismatch case
  builds a dangling empty-tree commit (no ref written), and the real-manifest case
  skips only on a shallow checkout that lacks the commit - the gate itself keeps
  running on both fetch-depth-0 rails, which the next test asserts.

All five files pass locally (30, 11, 38, 3 and 18 tests); lint with the frozen
suppressions is clean.
2026-08-28 18:10:56 -03:00
Diego Rodrigues de Sa e Souza
226538fa27 feat(ci): publish to npm through Trusted Publishing (OIDC) by default (#11931)
* feat(ci): publish to npm through Trusted Publishing (OIDC) by default

npm rejects provenance from self-hosted runners and is retiring tokens that
bypass 2FA; v3.8.49 answered with staged publishing (WS1.3) so a leaked token
could never publish alone — at the price of a manual `npm stage approve` per
release. Trusted Publishing gives the same guarantee with no token at all: the
github-hosted stage-npm job exchanges GitHub's id-token for a credential scoped
to that run, provenance included, and the flow is automatic again as it was up
to v3.8.48.

publish_mode gains `auto` (the default, also the path for the release event);
`staged` now runs only when asked for; `direct` stays as the emergency token
fallback. Until the owner registers the Trusted Publisher on npmjs.com
(diegosouzapw/OmniRoute, workflow npm-publish.yml) the automatic step fails
with ENEEDAUTH and either other mode can be dispatched — documented in
docs/ops/RELEASE_CHECKLIST.md.

* docs(release): date the checklist for the Trusted Publishing change and drop the env-var claim

check-deprecated-versions flags a touched doc whose header still says
2026-06-28 / v3.8.40; the fabricated-docs gate read the backticked NPM_TOKEN as
an environment variable the code never reads (it is a repository secret).
2026-08-28 18:02:51 -03:00
diegosouzapw
fb7445eaa3 test(check): escape the runs-on fixture with JSON.stringify, not a quote-only replace
CodeQL js/incomplete-sanitization (#888): the hand-rolled replace only escaped
double quotes, so a backslash in the fixture would have produced a malformed YAML
scalar. JSON.stringify covers every escape the double-quoted YAML scalar needs.
2026-08-28 17:29:04 -03:00
diegosouzapw
9968e1ce6e Merge remote-tracking branch 'origin/release/v3.8.51' into chore/sync-main-into-3851-20260828b 2026-08-28 17:27:31 -03:00
Diego Rodrigues de Sa e Souza
f907b5ea8e fix(ci): cap heavy builds at two runners with the omni-build label (#11932)
The .113 box (31 GB) holds one next-build (14–16 GB RSS) comfortably and two
at the edge; on 2026-08-28 the kernel killed main's build twice while PR
builds ran beside it. Labels are the runner-side cap: only omniroute-113-5
and omniroute-113-6 carry omni-build (added through the runners API, no
re-registration), and every job that runs a next build — ci.yml build,
npm-publish.yml publish, both nightly-release-green validations — now asks
for that label. A third heavy job queues on GitHub instead of racing for
memory. The six other runners keep omni-release and no longer take builds.
Pairs with the heavy-build-* concurrency lanes (#11901); documented in
docs/ops/RUNNER_BOX.md.
2026-08-28 17:19:45 -03:00
Diego Rodrigues de Sa e Souza
5b38ec717d fix(ci): keep the next-build artefact on disk, not on the runner's tmpfs (#11896)
* fix(ci): keep the next-build artefact on disk, not on the runner's tmpfs

On the .113 pool /tmp is a 12 GB tmpfs — it is RAM. The 1.3 GB next-build
artefact was parked there four times over: the Build job tar'd it to
/tmp/e2e-build.tar.gz (6 min), three E2E jobs downloaded it to /tmp/ and
extracted from there, and npm-publish.yml pulled it with gh run download into
/tmp/next-build. Measured on the v3.8.50 publish runs: that download step took
27 min (9th attempt) and 32 min (10th) — 42% of a 76-minute job — while the
very same bytes upload from disk in 2 min and the box pulls from GitHub at
7.3 MB/s (1.3 GB ≈ 3 min). Network was never the bottleneck; a tmpfs at 75%
under memory pressure was.

Every site now uses $RUNNER_TEMP / ${{ runner.temp }}: per-runner, on disk
(_work/_temp under the runner dir on the pool, /home/runner/work/_temp on
hosted images), and cleaned by the runner between jobs.

It also removes a latent race: e2e-build.tar.gz is a FIXED name under a /tmp
shared by every runner on the box, so two E2E shards on different runners could
overwrite each other's download mid-extraction. RUNNER_TEMP is per runner.

The supply-chain guard in tests/unit/npm-publish-artifact-provenance.test.ts
pins the candidate-run selection and the --name, not the directory; it stays
green. check:workflows --ratchet: zizmor unchanged at the baseline.

* fix(ci): download the next-build artefact to a workspace-relative dir (pwsh has no $RUNNER_TEMP)

The Electron Package Smoke matrix runs on windows-latest, whose default shell
is pwsh: $RUNNER_TEMP is empty there (pwsh spells it $env:RUNNER_TEMP), so the
first cut's tar -xzf "$RUNNER_TEMP/e2e-build.tar.gz" tried to open
'/e2e-build.tar.gz' and failed. A path relative to the workspace works in bash
and pwsh alike, and hosted workspaces are ephemeral. The producer (Build, Linux,
bash) and npm-publish keep $RUNNER_TEMP.
2026-08-28 17:08:58 -03:00
diegosouzapw
5ade9e0851 fix(sync): repair the two regressions the v3.8.50 sync-back left on release/v3.8.51
Fifteen unit files were red on this branch's PRs; running them on the pre-sync
tip (d5dfcfff58) and on the synced one showed thirteen already failed before
the sync — the cycle's own drift — and exactly two regressed:

- open-sse/services/tokenExtractionConfig.ts: git kept BOTH sides' identical
  volcengine-console config (23 entries instead of 22). The duplicate is gone.
- src/lib/usage/providerLimits.ts: the sync took release/v3.8.50's cooldown
  release helper, which is looser than this branch's #11277 contract (it frees
  an extra_usage block when the policy is off and a window with no reset
  evidence). tests/unit/provider-limits-recovery.test.ts pins the contract;
  the pre-sync call site is restored and the unused helper and its imports
  dropped. 20/20 again, siblings unchanged.
2026-08-28 16:41:42 -03:00
Diego Rodrigues de Sa e Souza
33763f06cc chore(changelog): add missing fragments for #11919/#11918/#11916 (#11938)
Adds the 3 missing changelog fragments.
2026-08-28 16:26:32 -03:00
Bob.Hou
6b259812a7 fix(sse): preserve store parameter semantics for openai-compatible responses (#11826) (#11916)
stripStore() now forces store=false for stateless OpenAI-compatible Responses-API targets unless the connection explicitly opts in via providerSpecificData.openaiStoreEnabled, instead of only handling the openai/agentrouter cases — a client-supplied store value previously passed through untouched to backends that don't actually persist responses server-side. Closes #11826. Thanks!
2026-08-28 16:25:19 -03:00
Bob.Hou
dc75a02ca7 fix(models): expose custom node models in canonical prefix mode (#11832) (#11918)
Custom provider-node models (synced, custom, and alias-backed) now appear under their configured prefix in the unified catalog when the operator's model-id prefix mode is canonical, instead of being dropped whenever alias-inclusion was otherwise disabled. Closes #11832. Thanks!
2026-08-28 16:25:05 -03:00
Bob.Hou
3d2832b836 fix(models): suppress static registry models when live catalog is synced (#11829) (#11919)
Suppresses stale static registry models (including effort-tier variants) for any provider whose active connection has an authoritative live synced catalog, not just providers using exclusive-synced-listing — closing a gap where a connection with providerUsesAuthoritativeLiveCatalog kept serving both the live-synced models and the stale static rows side by side. Closes #11829. 4/4 focused tests passing. Thanks!
2026-08-28 16:24:53 -03:00
Diego Rodrigues de Sa e Souza
cea1baa797 fix(ui): guard remaining ProviderIcon lookups against prototype collisions (#11920 port) (#11935)
Ports the 3 still-needed guards from #11920 that #11880 didn't cover. 90/90 + 4/4 focused tests passing.
2026-08-28 16:19:07 -03:00
Diego Rodrigues de Sa e Souza
dd35750e5f fix(sse): defer OpenAI-to-Claude finish emission until real usage arrives (#11915 follow-up on #11883) (#11933)
Merges #11883's already-merged usage-harvesting extraction with #11915's finish-deferral mechanism, verified to fix a real remaining bug: the client-visible message_delta carried stale/zero usage when finish_reason arrived before the trailing usage chunk. 86/86 tests passing across 16 translator regression files.
2026-08-28 16:11:38 -03:00
Diego Rodrigues de Sa e Souza
c661e1c811 port(playground): specific step warnings from #11882, keep #11862's string-step handling (#11930)
Ports the specific-warning improvement from #11882 (combo-ref/provider-wildcard steps get their own message instead of a generic count) onto #11862's already-merged crash fix. 4/4 focused tests passing.
2026-08-28 15:51:37 -03:00
diegosouzapw
529e4415c5 chore(release): sync main into release/v3.8.51 — the five post-release pipeline fixes
Brings e4683cd22d (#11867 Alibaba allowlist time bomb), 09de69edc7 (#11891
config expiry detector), e71be03398 (#11893 runner janitor), 9dc8eab70e
(#11895 provenance × self-hosted lint) and f564b64f7d (#11901 heavy-build
lanes). main is already an ancestor of this branch (v3.8.50 sync-back), so the
merge is exactly these five commits.

# Conflicts:
#	tests/unit/alibaba-free-tier-allowlist.test.ts
2026-08-28 15:49:53 -03:00
NoxzRCW
f08f35d6f0 fix(providers): pass xAI reasoning_effort xhigh through to grok-4.6+ (#11879)
normalizeXaiReasoningEffort() folded xhigh onto high before the request reached xAI, so anyone picking xhigh on grok-4.6 silently got high instead. xhigh is a real xAI tier (grok-4.6+); xAI already degrades it itself on unsupported models, so forwarding verbatim is safe everywhere. Closes #11816. Measured against live grok-4.6: reasoning_tokens 830 (high) vs 1052 (xhigh) — previously indistinguishable. Thanks!
2026-08-28 15:49:42 -03:00
NoxzRCW
d846692c30 fix(dashboard): guard provider icon lookups against prototype collisions (#11880)
getLobeProviderIcon() indexed two plain-object maps with no own-property check — a provider id that lowercases to an Object.prototype member (e.g. constructor) resolved through the prototype chain and threw on the follow-up .color/.mono lookup, surfacing as the misleading 'Failed to load providers, check your connection' error boundary card with a healthy server and clean logs. Thanks for the precise root-cause trace!
2026-08-28 15:49:32 -03:00
NoxzRCW
c5ebbb733c fix(skills): expand shorthand property types in injected tool schemas (#11881)
Every request through a strictly-validating provider (reproduced on opencode-go/glm-5.3-flash) failed with a 400: normalizeInputSchema() wrapped a skill's shorthand property map without expanding string values, so every injected omr_skill_* tool carried an invalid JSON Schema. Closes #11856. Thanks for the root-cause!
2026-08-28 15:49:22 -03:00
NoxzRCW
b8c7ee599d fix(translator): keep upstream usage from trailing empty-choices chunks (#11883)
openaiToClaudeResponse() returned early on !chunk.choices?.[0], dropping the trailing usage-only chunk many OpenAI-compatible upstreams send when stream_options.include_usage is set (confirmed on Fireworks kimi-k3) — state.usage stayed undefined and billing fell back to an uncached token estimate. 154/154 focused assertions across the fix + regression suite. Thanks for tracking down the billing impact!
2026-08-28 15:49:14 -03:00
Diego Rodrigues de Sa e Souza
f564b64f7d fix(ci): give main's build its own lane on the self-hosted pool (#11901)
The .113 box has 31 GB and a single next-build peaks at 14–16 GB RSS: one
build fits with room, two sit at the edge, three take the box down. On
2026-08-28 13:50Z the kernel OOM-killed main's next-build (15.7 GB) while a PR
build ran beside it — five Build jobs had been queued by a burst of PRs — and
the publish lost its artefact, which sends it into the 40-minute rebuild that
OOMs on its own (attempt 5 of this release).

Job-level concurrency on `build`, two lanes:

  heavy-build-main   pushes to main — never contended, never behind PR traffic
  heavy-build-pr     pull requests — serialize among themselves

cancel-in-progress stays false: a running build is never killed by a newer
one. GitHub's own rule for a group is one running + one pending, older pendings
cancelled — so under a burst the third PR build shows "cancelled" and needs a
re-run. That is the trade-off, stated: a cancelled PR check is re-runnable; a
dead main build costs a release.

The proper fix remains a label split (omni-build on two runners, omni-light on
the rest) so the queue lives on the runner side without cancellations — an
operator decision recorded in docs/ops/RUNNER_BOX.md.
2026-08-28 15:44:50 -03:00
Diego Rodrigues de Sa e Souza
9dc8eab70e feat(quality): fail check:workflows on --provenance from a self-hosted runner (#11895)
npm rejects provenance-signed uploads from self-hosted runners:

  422 Unprocessable Entity - Error verifying sigstore provenance bundle:
  Unsupported GitHub Actions runner environment: "self-hosted".
  Only "github-hosted" runners are supported when publishing with provenance.

v3.8.50 learned that at minute 76 of its 10th publish attempt, after the tag,
the GitHub Release and the Docker images were already out. USE_VPS_RUNNER had
routed the job to the .113 pool on 2026-08-02; no release ran between 07-30 and
08-28, so the pairing sat latent for four weeks.

It is pure text — a job whose runs-on resolves to self-hosted and a step whose
run contains --provenance — so the workflow lint now checks it as a hard rule:
reported in plain mode, blocking under --strict and --ratchet (the CI mode),
emitted as provenanceRunnerFindings=<n> next to the other counters.

Against origin/main the rule finds the two real offenders (the staged upload
AND the DIRECT emergency fallback in npm-publish.yml); against the #11877 split
it finds none. --provenance-file is deliberately not matched (different flag,
pre-built bundle) and an opaque runs-on expression with no literal self-hosted
is classified unknown and skipped — the check never guesses.

The unit suite's last case walks the real .github/workflows and asserts zero
findings, so it is red on main until #11877 lands and green after; that is the
regression guard working, not a flake.
2026-08-28 15:44:40 -03:00
Diego Rodrigues de Sa e Souza
e71be03398 chore(ops): make the runner janitor act on what it can prove, not advise (#11893)
* chore(ops): make the runner janitor act on what it can prove, not advise

The .113 janitor already knew the rules and had been shouting them into a log
nobody reads: on 2026-08-28 12:00Z it reported "10 listeners > ceiling 8" and
"disk 85%" — for hours — while 6.7 GB of dead-run leftovers sat on the 12 GB
tmpfs (RAM) because its patterns matched neither e2e-build.tar.gz nor
next-build/, its 24 h fuse is a day too long for memory, and its _work/_temp
base (/home/*/actions-runner*) does not exist on this box (runners live under
/opt). Measured while draining the v3.8.50 npm publish (postmortem, Parte III).

What changes:

- idle is PROVEN before removal, with ONE lsof snapshot filtered to the swept
  bases (lsof +D per path walked whole trees and took minutes; 460 candidates
  grepping a re-printed 83k-line string was the other half). 20 s on the box.
  Without lsof the janitor removes nothing and says why (exit 1).
- tmpfs leftovers go after 3 h, disk _work/_temp after 24 h; both overridable.
  Patterns gain next-build* and e2e-build.tar.gz; /opt/actions-runner* is swept.
- zombie builds: a next-build older than 75 min has no job (a real Build step is
  ~26 min). On 2026-08-27 one ran 70 min after GitHub had declared its job lost,
  holding 3.6 GB. KillMode=mixed on the units covers systemctl stop/restart;
  this covers the lost-connection path.
- prunes 48 h-old checkouts under _work of runners whose unit is STOPPED — an
  active runner is never touched.
- alerts on memory PSI (full/avg60) and reports the listener ceiling with an
  omniroute/other breakdown (the box also hosts OmniHeuris and OmniMind).
  Enforcing the ceiling stays an operator decision (label split), not cron's.
- --dry-run prints exactly what it would do and touches nothing; unknown
  arguments are rejected.

Dry-run on the real box: 460 stale omniroute-* test fixtures (930 MB of RAM) it
would reclaim, 0 busy, 0 false "removed" lines, 20 s. The unit suite drives the
script against a fixture tree with every base redirected; the sweep branch runs
where lsof exists (hosted CI images) and the without-lsof contract everywhere.

docs/ops/RUNNER_BOX.md reconciled to the measured box: 31 GB (it said 16), ten
listeners, the 14 GB next-build ceiling, the KillMode drop-in, and the rule that
nothing is cleaned by hand while a runner is busy.

* docs(ops): restore the frontmatter fumadocs requires on RUNNER_BOX.md

Rewriting the page whole dropped its `title:` frontmatter, and docs/ is
compiled into the Next build by fumadocs-mdx — so Build, Fast Production Build
and dast-smoke all died with "[MDX] invalid frontmatter in
docs/ops/RUNNER_BOX.md". Same block as before, verbatim.
2026-08-28 15:44:29 -03:00
Diego Rodrigues de Sa e Souza
09de69edc7 test(config): fail seven days before a dated config pack lapses (#11891)
config/alibaba-free-tier-allowlist.json carried "validUntil": "2026-08-27".
On the 28th the loader started rejecting it — correctly, that is the design —
and a test that asserted "the shipped pack loads" turned every PR and main red
with no commit involved (#11866). A time bomb: the one class of defect a diff
review can never catch, because there is no diff.

scripts/check/lib/configExpiry.mjs walks config/**/*.json for validUntil /
validTo / expiresAt / expiry / expires (and snake_case forms), parses the dates,
and classifies each as expired / expiring (< 7 days) / ok / unparseable.

The repo-wide test fails on expired or expiring packs unless the file is in a
small allowlist keyed to the issue that owns the renewal — and fails the OTHER
way when an allowlisted pack is no longer expiring, so entries cannot go stale.
A positive anchor requires at least one dated pack to be found, so a renamed
key cannot silently turn the suite into a no-op.

The Alibaba pack is allowlisted against #11866: whether the curated free-tier
list still matches reality is an operator data decision, not a test fix.
Removing that entry makes the suite fail as intended (verified).
2026-08-28 15:44:18 -03:00
Diego Rodrigues de Sa e Souza
e4683cd22d fix(test): stop the Alibaba allowlist test from expiring with the catalog (#11867)
`Unit Tests (1/8)` went red on 2026-08-28 across every PR and on main, with
nothing changed — the clock had moved past the shipped catalog's expiry:

  config/alibaba-free-tier-allowlist.json → "validUntil": "2026-08-27"

isAlibabaFreeTierAllowlistPackValid() compares that against Date.now(), so from
28/08 loadAlibabaFreeTierAllowlistPack() returns null and the old
assert.ok(pack) could never pass again. Refreshing the date would only reschedule
the same break.

Production was never affected: resolveActiveAllowlistPack() falls back to the
embedded list when a pack expires, which is the intended design. The defect was
the test asserting the shipped catalog is currently fresh — a data property, not
a behavioral contract.

The test now writes its own packs to a temp dir with dates it controls, and
pins both halves of the contract:

  - inside the validity window, the pack REPLACES the embedded list (anchored on
    a model that exists nowhere else, so loading alone cannot satisfy it);
  - once expired, the pack is ignored and the embedded list serves.

That second path is what production has been running since 27/08 and had no
coverage at all, which is why the expiry surfaced as a red test rather than as
understood behavior. A third case pins the comparison against an injected
instant, including the no-expiry pack that never goes stale.

Whether the curated free-tier catalog still matches reality — and so deserves a
freshly dated pack — is a data question left to the operator in #11866.

Closes #11866
2026-08-28 15:43:53 -03:00
diegosouzapw
e70bea30e9 docs(counts): bring the documented provider and migration counts to the measured values
release/v3.8.51 retired Raycast, Hailuo, Qwen Web, Designer Web and Felo and
added migrations 163–168 without touching the numbers README, AGENTS.md,
llm.txt (and its 42 mirrors), package.json and the README diagrams quote:
351 providers (was 357/353/350), 166 migrations (was 160). The strict
docs-counts gate was already red on a pristine release/v3.8.51; the v3.8.50
sync-back's release-green pass surfaced it.
2026-08-28 15:20:24 -03:00
diegosouzapw
a32ba0e362 test(security): drop the guard-target set left unused by the sync
The sync-back kept release/v3.8.50's per-handler positive-anchor version of
this suite, which no longer needs the guardDelegatingTargets set the cycle
branch had added — the only ESLint error the merge introduced (281 vs 280 on a
pristine release/v3.8.51).
2026-08-28 15:19:29 -03:00
diegosouzapw
9ec7b72248 chore(release): sync main (v3.8.50 close) into release/v3.8.51 — parallel-cycle sync-back
Merge commit on purpose: origin/main becomes an ancestor of the cycle branch,
so the next sync-back (v3.8.51 close → release/v3.8.52) merges against this
point instead of the July base that turned this one into 551 conflicts.

Tree = release/v3.8.51 + release/v3.8.50 tip (step 1, b68af3f090) + main's
post-tag fixes (step 2, 21c488f210) + main's CHANGELOG verbatim with the
## [3.8.51] — TBD section re-inserted on top + the 42 i18n CHANGELOG mirrors
regenerated by scripts/release/sync-changelog-i18n.mjs.
2026-08-28 14:41:19 -03:00
diegosouzapw
282de841ae revert(models): drop the memo reset hook ported from #10055
The port destabilized tests/unit/modelsDevSync-extended.test.ts (6/14 with it,
18/2 without; 20/0 on a pristine release/v3.8.50). The cycle's own memo, keyed
on the catalog cache version, stays as it was; the remaining failures predate
this sync and are tracked separately.
2026-08-28 14:39:18 -03:00
diegosouzapw
f2825bad75 fix(typecheck): guard the optional connectionId before the reactive model sync
release/v3.8.51 fails typecheck:core on its own (verified on a pristine
checkout): maybeTriggerReactiveModelSync(provider, connectionId: string) is
called with credentials.connectionId, which base.ts declares optional. No
connection row means there is no synced catalog to refresh, so skip instead of
passing undefined. Surfaced by the release-green gate of the v3.8.50 sync-back,
which refuses to push a tree with a hard typecheck failure.
2026-08-28 14:36:34 -03:00
diegosouzapw
70f5d4cbf9 chore(db): renumber model_capabilities migration 163 → 169 (cross-branch collision with 163_radar_feed_cache_generated_at)
#11845 landed on main as 163 while release/v3.8.51 had already used 163–168.
Per the cross-PR collision precedent (#3365/#3371) the later arrival takes the
next free number; the SQL is idempotent, so installs that already ran it as
163 on main are unaffected.
2026-08-28 14:21:46 -03:00
diegosouzapw
21c488f210 chore(release): bring main's post-tag fixes into release/v3.8.51 — sync-back step 2/2
The eighteen commits main carries beyond the cycle branch, and what each one
became here:

  already in release/v3.8.51 by its own PR (no-op, verified by content):
    b090b601a5 / 026e1cadaa  deps: nanoid 3.3.18 equal, dompurify 3.4.14 newer
    918fba5e39  .gitignore: /_tasks already anchored
    5f0a394091  #10026 hide health-check-excluded models — same helper, 5 call sites
    c68cda7dfb  #11075 shared passthrough providers — superseded by #11071/#11078
  superseded, one piece kept:
    ca23eed77c  #10055 memoize models.dev pricing — the cycle memoizes on the
                catalog cache version already; only the resetDbInstance() hook is
                ported, wired to that memo
  applied as-is:
    8778ea7d18  stamp dist/BUILD_SHA before the npm provenance gate (#11721)
    aa52351113  decouple the Bun image from the release manifest (#11724)
    925feb27b8  let the bun digest artifact be absent (#11740)
    b65ef333da  size the install-upgrade gate to a measured run
    0ce21232db  #11845 converge install/upgrade schemas (migration renumbered in
                the next commit: 163 collides with 163_radar_feed_cache_generated_at)
    b7c07edad8  #11855 install-upgrade gate on disk, not tmpfs
    8e2fb04329  #11864 drop *.nft.json from the npm tarball (413)
    dea6bb8b6b  #11877 publish npm from a hosted runner (provenance 422)
  handled by the sync script that follows (CHANGELOG protocol):
    b4ec7807ab  Release v3.8.50 — squash of content this branch already carries
    5458026c21 / c44c0a29e8  CHANGELOG aggregation, stats and top-25
  applied separately (its own commit, ten files):
    65e81158ab  #11088 Ollama capability routing — a 5,094-file squash from a
                stale base; only the Ollama files are the change

Every cherry-pick that touched a file this branch had also changed was
resolved by hand and re-run through the tests both sides own for it.
2026-08-28 14:21:44 -03:00
diegosouzapw
b68af3f090 chore(release): merge release/v3.8.50 tip into release/v3.8.51 — sync-back step 1/2
The v3.8.50 close left 134 post-freeze commits on release/v3.8.50 that never
reached the cycle branch (the freeze cut release/v3.8.51 at 3192eb88d5). A
plain merge of main reproduces all of them through the `Release v3.8.50`
squash against a July merge-base and conflicted on 551 files; merging the
release tip first, against the recent common ancestor, narrows the real
conflicts to 102 (51 generated, 51 judged file by file with a proof each —
see _tasks/postmortems/2026-08-25-release-v3.8.50-pipeline-eficiencia.md,
Parte IV). Step 2 brings main's own post-tag fixes and the finalized
CHANGELOG through scripts/release/sync-next-cycle.mjs.

Resolution rules applied, in order of evidence:
- generated files regenerated with the repo's own generators
  (sync-llm-mirrors, gen-budget-card-svg, gen-provider-reference);
- where release/v3.8.51 already carried the same fix in a newer shape
  (#11524 search sweep, #11551 catalog scheduler, Google BYOP retry, KIE
  Market id map, Docker worker budget measured in #7518) its version stays;
- where release/v3.8.50 carried the newer shape (Volcengine cookie-domain
  CodeQL fix + shared Zod schemas, #11355/#10534 cooldown release helper,
  positive-anchor tests for security-hardening and cli-oneproxy) it wins;
- GPL-retired Raycast/Hailuo (#11691) stay retired: nothing of theirs comes
  back and the public-route test keeps the retired route out;
- the ten changelog.d fragments of v3.8.50 are dropped — they are already
  aggregated in main's CHANGELOG and would double-aggregate at v3.8.51.

Three things git's auto-merge silently produced were caught by a per-line
detector and fixed: providerLimits.ts lost T's imports and the
windowStillExhaustedAfterRealReset helper; catalogCache.ts and
providerLimits.ts kept both sides' identical copies of three declarations;
contextHandoff.ts's new provider-allowlist skip returned undefined against
the #11552 outcome type. Every decision was re-run through the tests both
sides own for it.
2026-08-28 14:07:39 -03:00
Dizzle
d5dfcfff58 feat(combo): choose sort method for combo models (manual/provider/score/name) (#11812)
Lets the combo dashboard builder order models manually/by-provider/by-score/by-name — the choice is stored in config.modelSort and re-applied on load and after adding models. Score-based ordering fetches provider rankings from the existing /api/free-provider-rankings endpoint; the field is inert on execution (client-side hint only). 9/9 focused tests passing (schema, sort logic, and rendered component). Thanks!
2026-08-28 12:37:58 -03:00
Dizzle
51ca7edd97 feat(free): per-model isFree for custom models (catalog + UI) (#11843)
Adds an opt-in customModels[].isFree flag so a self-hosted local model can be marked free-tier without touching the curated free-model catalog (providerHasFreeModels stays curated). 9/9 focused tests passing across the DB round-trip, schema tri-state validation, and free-model detection. Thanks!
2026-08-28 12:37:44 -03:00
Dizzle
b7102140d5 fix(socks): forward Agent.connectTimeout to SocksClient and TLS, unify family null (#11842)
Fixes a SOCKS proxy timeout bypass: Agent.connectTimeout now reaches both the SocksClient.createConnection handshake and the TLS buildConnector phases (previously a stalled/blackholed SOCKS connection could hang past the configured budget), and the fetch-socks family===null path is unified onto createSocksDispatcherWithFamily. Verified against a faux RFC 1928 SOCKS server exercising both pre-grant and post-grant stalls. 6/6 focused tests passing. Thanks!
2026-08-28 12:37:32 -03:00
Bob.Hou
cab9cdc765 fix(pwa): stop serving the stale shell after deploys (#11779)
Fixes the stale-shell PWA lockout after a deploy: navigationFallback now returns
Response.error() instead of replaying a cached shell whose /_next/static chunk
references are dead, and the worker is registered as /sw.js?v=<build-id> so each
deploy is actually observed instead of never updating until a navigation to the
new build first succeeds.

Recreated onto release/v3.8.51 (original base was main, which had diverged too far
for a clean retarget) — both commits cherry-picked and force-pushed to the
contributor's branch (author preserved), then the PR's base edited in place.
4/4 focused tests passing (2 via vitest for the jsdom-environment PwaRegister
suite, 2 via node:test for the service-worker fallback suite). Thanks for the fix!
2026-08-28 12:23:35 -03:00
Bob.Hou
495d10de18 fix(mcp): avoid async initialization inside sync esm wrappers (#11569) (#11849)
Fixes the startup crash SyntaxError: Unexpected reserved word 'await' on Node 24/26 by pinning esbuild to 0.28.2 and preventing async initialization inside synchronous __esm wrappers in the MCP server bundle. Closes #11569. Verified: 2/2 focused tests pass with esbuild 0.28.2 correctly installed (root-caused a stale-node_modules false negative in my own validation pass — resolved with a fresh npm ci, not a PR issue). Thanks!
2026-08-28 12:22:27 -03:00
Bob.Hou
00b7b71bd3 feat(routing): expand connection-aware quota prefilter across combo strategies (#11682) (#11850)
Expands per-connection quota-aware pre-filtering across all 20 combo strategies so exhausted accounts are filtered before strategy resolution instead of causing avoidable upstream errors. Closes #11682. 15/15 + 131/131 + 457/457 (vitest) focused tests passing. Thanks!
2026-08-28 12:22:18 -03:00
Bob.Hou
1b39873ea9 fix(sse): preserve coherent markdown token boundaries across stream translation chunks (#11606) (#11851)
Preserves markdown token boundaries (bold/italic markers, code fences/spans, escape-backslash parity) split across stream translation chunks by tracking boundary state and flushing held buffers correctly instead of emitting broken markdown mid-stream. Closes #11606. 38/38 + 74/74 focused tests passing. Thanks!
2026-08-28 12:22:09 -03:00
Paco Cartones
13afbfafb2 fix(vision-bridge): honor authoritative live catalogs (#11813)
Reconciles Vision Bridge auto-selection with each provider's authoritative live model
catalog, revalidating cached selections and preserving routable aliases / live-catalog
IDs / registered effort variants, with fail-open behavior kept when the catalog is
unavailable or non-authoritative. Closes #11767.

One test-side fix applied before merge: "accepts a registry model whose liveCatalogIds
match upstream" used `cgpt-web` (ChatGPT Web) as its fixture provider — retired by #11754
after this PR was authored, which removed every live registry entry populating
`liveCatalogIds` and made the test's expected model unreachable (null, not the retired
id). Swapped the fixture to a synthetic PROVIDER_MODELS entry (the registry Proxy is
writable and reverted in `finally`) so the same production predicate is exercised without
depending on since-deleted registry data. 16/16 focused tests passing on the current tip.
Thanks for the fix!
2026-08-28 11:19:18 -03:00