mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-06 23:32:12 +03:00
3c6f71776e1c712e4691234ab96fe754265b88eb
378 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
2ddbbc61a6 |
[v3.8.50] feat(memory): MemoryBackend provider pattern with generic HTTP connector (#8752)
Validated in local merge-train T7 (ungrouped batch 2) |
||
|
|
e7f6b1d130 |
feat(radar): flag-gated signed free-model catalog overlay (#9515)
* feat(dashboard): add RADAR_ENABLED flag (default off) * feat(db): radar feed cache + settings with encrypted supporter key * feat(radar): signed feed sync with pinned key and version floor - feedSchema.ts: Zod v4 schema mirroring the server feed format (discriminated union on budget.kind, enum constraints, etc.) - pinnedKeys.ts: Ed25519 SPKI-DER pinned key + env override for forks - verify.ts: signature verification over exact wire bytes, never throws - sync.ts: full download/verify/validate/cache pipeline with injectable deps, feature-flag gate, opt-in gate, version floor (numeric compare), and sanitized error reasons (no stack traces) - 40 tests covering: contract hash, key handling, sig verification, schema validation, version compare, all sync paths (disabled, opt_out, invalid_signature, invalid_schema, stale, updated, error), auth header injection, and cache-untouched assertions for every failure mode * feat(radar): read-time overlay merge rules over the free catalog Pure function applyFeed() merges the cached Radar feed over the static baseline catalog at read time, honoring 4 rules: 1. Feed never overwrites a local override field. 2. enabled:false disables the entry with disabledBy:"radar" provenance. 3. User-added entry NOT in the feed survives untouched. 4. User deletion tombstone prevents feed resurrection. getRadarCatalog() accessor in index.ts: flag off / no cache / corrupt payload all fall back to baseline. Valid cache applies the overlay and returns feed metadata (version, tier, fetchedAt). TDD: 19 tests (4 rules + dedup + origin + accessor flag/cache/corrupt/ valid/bad-feed + baselineToMergedEntries converter). * feat(dashboard): radar catalog and guided setup screens - API routes: GET /api/radar/catalog, POST /api/radar/sync, POST /api/radar/settings - All gated on RADAR_ENABLED flag (404 when off) - Error responses via buildErrorBody(), never raw stack/message - Settings never echoes clear supporter key (masked omr_****<last4>) - Sync delegates to syncRadar() server-side, never proxies feed URL - Dashboard pages: - /dashboard/radar: 4 states (flag off, opt-in pending, empty, populated) - /dashboard/radar/setup?provider=X: guided setup with steps, key URL, test connection - Uses existing Card component and next-intl patterns - Sidebar: radar entry in costs group with icon - i18n: pt-BR and en keys for radarPage and radarSetupPage namespaces - Tests: - radar-api-routes.test.ts: 11 tests (flag-off 404, flag-on shape, error sanitization) - radar-page-state.test.ts: 5 tests (pure state logic) - All 90 radar tests pass (including prior 74) * docs(radar): module doc and flag-off inertia test Add docs/frameworks/RADAR.md covering the flag gate, the separate data-sync opt-in and privacy promise, the Ed25519 signature/pinned-key security model, tiers, the read-time overlay merge rules, and the self-hosting env vars — plus index entries in CLAUDE.md/AGENTS.md/docs/README.md/REPOSITORY_MAP.md. Document RADAR_FEED_URL and RADAR_FEED_PUBKEY in .env.example and docs/reference/ENVIRONMENT.md to satisfy check:env-doc-sync, which was failing on this branch since the sync.ts commit added the reads. Add tests/unit/radar-inertia.test.ts as the single canonical place asserting the "RADAR_ENABLED off => zero behavioral delta" claim end to end: the three /api/radar/* routes 404, the flag resolves to the definition default with no override, getRadarCatalog() returns exactly the baseline without touching the cache, and computeFreeModelTotals() keeps its pinned values with the Radar module imported alongside it. * fix(db): renumber radar migration to 135 after collision with 134 The base branch introduced 134_proxy_logs_egress_ip while this branch carried 134_radar_cache_settings; the migration runner rejects duplicate numeric prefixes. This migration has never been applied to a real database (the PR is unmerged), so no retroactive isSchemaAlreadyApplied guard is needed. * i18n(radar): translate radar catalog and setup strings to all locales The UI-coverage ratchet measures (present - placeholder) / total_en, so the __MISSING__ sentinels that i18n:sync-ui writes do not count as covered — only real translations restore the metric. Scoped to this PR's namespaces (radarPage, radarSetupPage, sidebar.radar*) instead of a bulk sync, which would have pulled ~978 unrelated pending keys into this diff. Placeholders and code identifiers verified preserved across all 1682 strings. * fix(radar): trust the served-tier header instead of the signed body field The signed feed body always carries tier:"live" by design (one signed artifact per version — rewriting the field server-side per request would break the exact-bytes Ed25519 signature). The server now returns the tier ACTUALLY served via the x-omniroute-feed-tier response header, so free users on a delayed community snapshot no longer see "Ao vivo (tempo real)" in the UI. sync.ts now reads and validates that header (falling back to the body's tier only when the header is absent or holds an unrecognized value) and stores the served tier in the cache; index.ts already surfaces cache.tier to the UI unchanged. * test(combo): shorten an assert message that exceeded the line limit The assertion added by #9507 was 104 chars, so prettier reformatted it into five lines on the next commit that touched the file, pushing it past its frozen size (3449) and failing check:file-size. The message is shortened (the issue reference stays in the comment directly above); the assertion itself is unchanged, and the file is back to 3448 lines and prettier-clean. * i18n(radar): use the canonical zh-TW glossary terms The machine translation produced retired renderings the glossary gate blocks: 供應商 for provider (canonical 提供者) and 文檔 for documentation (canonical 文件). Fixed across the 11 affected radar strings; tests/unit/i18n-glossary-consistency-check.test.ts is back to 17/17. * fix(radar): point the default feed URL at the domain that exists radar.omniroute.dev was a placeholder for a domain that was never registered, so an out-of-the-box sync would fail DNS resolution for every user. The live feed is served from radar.omniroute.online (the subdomain the design always specified), now behind Cloudflare TLS. Forks still override it via RADAR_FEED_URL. --------- Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com> |
||
|
|
04683029a6 |
fix(build): exec native esbuild binary directly in prepublish (dast-smoke base-red) (#9558)
* fix(build): exec native tool binaries directly in runBuildTool #8858 routed every resolved local bin through process.execPath to avoid Windows .cmd shims — but esbuild >=0.25 ships bin/esbuild as the NATIVE platform executable (ELF on Linux), so Node parsed machine code as JS and build:cli died with 'SyntaxError: Invalid or unexpected token', turning dast-smoke red for every PR. runBuildTool now sniffs the entry's magic bytes (ELF / Mach-O / PE) and execs native binaries directly; JS entries keep going through this Node binary (the .cmd-shim avoidance #8858 wanted). Validation (RED->GREEN on this box): - RED: node node_modules/esbuild/bin/esbuild --version -> SyntaxError (ELF) - GREEN: the exact failing CI step reproduced via the new logic bundles open-sse/mcp-server/server.ts successfully (4.2MB output, 1.3s). * fix(docs): add MDX frontmatter to the 20 remaining docs without it Same failure class as AGENTROUTER_WAF (#9503) and DOCKER_RELEASE_CHANNELS (this run's dast-smoke red): any doc without frontmatter breaks the fumadocs MDX loader during next build, killing build:cli/dast-smoke for every PR. Swept ALL of docs/ (i18n mirrors excluded) in one pass so this class cannot recur one file at a time. * docs(env): document OMNIROUTE_INTERNAL_SERVICE_TOKEN(+_FILE), OPENROUTER_PROVIDER_STATS_* and embedded-Redis binding vars Pre-existing env/docs contract drift from recently merged features made check:env-doc-sync red for any docs-touching PR. Values and defaults read from the defining modules (internalServiceAuth.ts, openrouterProviderStats.ts). * fix(build): resolve bundled npm-cli.js in the standard Unix layout + safe npm fallback off-Windows The opencode-plugin step hard-failed on GitHub runners because resolveBundledNpmEntry only looked next to the node binary (Windows zip layout); hostedtoolcache Node keeps npm at <prefix>/lib/node_modules/npm. Added that candidate, and when neither exists on non-Windows the step now falls back to plain 'npm' — the .cmd-shim hazard #8858 avoids is Windows-only. * test(mutation): register xai-agent-tools-passthrough.test.ts in stryker tap.testFiles The test landed on release/v3.8.50 covering open-sse/handlers/chatCore/passthroughHelpers.ts without the stryker registration, so Fast Quality Gates' drift detection reds any PR that carries it. Mechanical registration so its mutant kills count. --------- Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com> |
||
|
|
e12d2d546b |
fix(build): resolve npm-cli.js on POSIX layouts in the shim-free prepublish resolver (#9553)
* fix(build): resolve npm-cli.js on POSIX layouts in the shim-free prepublish resolver The #8858 resolver only tried <dir(node)>/node_modules/npm/bin — the Windows layout. On POSIX (GitHub hosted runners, nvm, system installs) npm lives at <prefix>/lib/node_modules/npm while node is <prefix>/bin/ node, so resolveBundledNpmEntry returned null and npm run build:cli died installing @omniroute/opencode-plugin deps on every fresh checkout ('npm-cli.js not found next to the running Node binary') — redding Fast Production Build and dast-smoke for the whole PR queue. Extract the resolver to scripts/build/resolveNpmEntry.ts with injectable seams and try, in order: npm_execpath (exported by npm run itself), the Windows beside-the-binary layout, the POSIX <prefix>/lib layout. TDD: tests/unit/build/resolve-npm-entry.test.ts — the POSIX-layout and npm_execpath cases plus a live regression guard fail against the old single-candidate logic (2/5) and pass with the fix (5/5). * docs(env): register the 7 env vars orphaned by the 08-05 merge batch The Docs Gates env/docs contract went red on the release tip: #9260 added OMNIROUTE_INTERNAL_SERVICE_TOKEN(_FILE) and #9324 added OPENROUTER_PROVIDER_STATS_ENABLED/_TTL_MS without .env.example entries, and the #9286 Redis sidecar vars (REDIS_BIND_HOST, REDIS_PORT, OMNIROUTE_REDIS_BIND_HOST) never reached ENVIRONMENT.md. Inherited base-red on every open PR. Defaults and descriptions taken from the consuming source files. --------- Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com> |
||
|
|
9751821338 |
chore(quality): add an RTL layout ratchet (#8828)
Validated in local merge-train T5 (base49+contributors+pacocartones) |
||
|
|
ad82c81c38 |
fix(build): support npm v11 allowScripts for optional native deps (#8877)
Validated in local merge-train T5 (base49+contributors+pacocartones) |
||
|
|
d2f3c1abf5 |
fix(docker): bundle LLMLingua optional dependencies (#9185)
Validated in local merge-train T4 (HouMinXi+Zartharas+Andrian+artickc) |
||
|
|
1b2a72ebc8 |
feat(docker): publish next from active release branches (#9181)
Validated in local merge-train T4 (HouMinXi+Zartharas+Andrian+artickc) |
||
|
|
8180b49ce1 |
fix(quality): 2 production bugs + 24 unit base-reds + measured gate ceilings (#9529)
* fix(quality): resolve net-new lint errors and allowlist #9343 assert rewrite Two `no-explicit-any` errors landed with #9407 and #9320 after the suppressions inventory was generated. Project policy is to fix new violations rather than freeze them, so both are typed instead: - #9407: `executor as unknown as Record<string, unknown>` - #9320: `(k: { name?: string })` Also allowlists the net-assert reduction in web-tools-translation-2820 (39->35). #9343 inverted the contract — bare JSON must no longer be promoted to tool_calls without an explicit <tool> envelope — so the tests were rewritten to assert non-promotion, which costs fewer asserts than validating a promoted object. More restrictive, not weaker. * fix(quality): raise integration ceiling to 40min and unpin codex-cli version in test The integration gate's 20min ceiling killed a healthy run: measured 22m08s hermetic on an idle 16-core box (935 tests across 112 files, strictly serial at --test-concurrency=1 because ~16 of them bind a port or share a DB). The "~3-10min" estimate in the code was stale by ~3x. 40min keeps the ceiling's real purpose — turning a genuine hang into a visible failure — without failing a long-but-healthy suite. Also fixes a base-red in chat-pipeline: |
||
|
|
103bab99d5 |
fix(cli): prefer IPv4 DNS for spawned servers (#9209)
Validated in local merge-train (devbox-vm-06-dev002) @ combined-tip (FAST gates — only pre-existing audit.test.ts flake). |
||
|
|
34251a1c37 |
chore(dev): bump better-sqlite3 and add DB query scripts for provider_connections (Anthropic/Claude debugging) (#9325)
Validated in local merge-train (devbox-vm-06-dev002) @ combined-tip (FAST gates green: static + changed tests + vitest — only pre-existing audit.test.ts flake). Evidence: /home/diegosouzapw/dev/proxys/OmniRoute/.claude/worktrees/merge-train-20260805-213228-suite.log |
||
|
|
19181567d4 |
fix(docker): move entrypoint script to /app to avoid tmpfs masking (#8999)
Validated in local merge-train (devbox-vm-06-dev002) @ combined-tip (FAST gates green: static + changed tests + vitest — only pre-existing audit.test.ts flake). Evidence: /home/diegosouzapw/dev/proxys/OmniRoute/.claude/worktrees/merge-train-20260805-213228-suite.log |
||
|
|
8b6dbe2a67 |
fix: add Termux/Android support for playwright-core and better-sqlite3 (#8922)
Validated in local merge-train (devbox-vm-06-dev002) @ combined-tip (FAST gates green: static + changed tests + vitest — only pre-existing audit.test.ts flake). Evidence: /home/diegosouzapw/dev/proxys/OmniRoute/.claude/worktrees/merge-train-20260805-213228-suite.log |
||
|
|
035512585e |
[v3.8.50] fix(build): prepublish no longer spawns .cmd shims on Windows (#8858)
Validated in local merge-train (devbox-vm-06-dev002) @ combined-tip (FAST gates green: static + changed tests + vitest — only pre-existing audit.test.ts flake). Evidence: /home/diegosouzapw/dev/proxys/OmniRoute/.claude/worktrees/merge-train-20260805-213228-suite.log |
||
|
|
1efb94b102 |
docs: centralize agent instructions in AGENTS.md (CLAUDE/GEMINI point to it) (#9508)
* docs: centralize agent instructions in AGENTS.md; CLAUDE/GEMINI point to it - AGENTS.md becomes the single source of truth: full CLAUDE.md content (main data) merged with the AGENTS.md-only sections (documentation accuracy, repository map, review focus, upstream contributions) and the GEMINI.md-only file-placement/root-hygiene and local-access rules. Adds the base-green check section (PRs must not be born red) and fixes the stale _tasks/release-flow path in Hard Rule 21. - CLAUDE.md: @AGENTS.md pointer + Claude-Code-only operational deltas (EnterWorktree, subagent stash-ban replication, superpowers path overrides, base-green/sweep-reds pointers). - GEMINI.md: pointer + Gemini-only notes; the stale 10-item hard-rule mirror is removed (source is the 22-rule list in AGENTS.md). - ci(release-green): label the not-green tracking issue with base-red. * docs: retarget docs-sync provider/MCP claims to AGENTS.md and fix test placeholder --------- Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com> |
||
|
|
51efc71af5 |
fix(docker): ship MITM _internal/ shims and selfsigned package in standalone bundle (#9451)
Closes #9451 |
||
|
|
7d5e8235da |
fix(quality): add base-relative file-size check so inherited drift does not red innocent PRs (#8522) (#9355)
Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com> |
||
|
|
6b0e11e378 |
refactor: update quality baseline and test masking allowlist
- Updated the quality baseline to set eslintWarnings value to 5000, reflecting the migration to TypeScript 7 and the new warning thresholds. - Modified the test masking allowlist to account for removed tests and sources, ensuring proper tracking of deprecated features. - Enhanced ESLint configuration to ignore additional directories containing non-source files. - Removed the .npmignore file as its contents are now managed in package.json. - Adjusted KimiWeb model configuration to correctly map K3 to the K2D5 scenario, reflecting changes in the underlying logic. - Updated artifact packing policy to prevent nested node_modules from being published, ensuring a leaner package size. - Added tests to verify the exclusion of node_modules from published artifacts and to ensure the integrity of the package.json files array. |
||
|
|
712910612b |
fix(db): bundle and verify the sql.js fallback (#9044)
Co-authored-by: nguyenha935 <208228297+nguyenha935@users.noreply.github.com> |
||
|
|
2e5854906d | docs: slim AGENTS.md (#8839) | ||
|
|
b38f3a4c02 |
feat(test:scoped): add TIA-based local test runner (#8084 D1) (#9143)
- npm run test:scoped: runs only tests impacted by your changes - npm run test:scoped:staged: for staged changes (pre-commit) - Uses select-impacted-tests.mjs with impact map when available - Falls back to heuristic (changed test files) when no map - Hub file changes suggest full suite - 7 unit tests for the selection logic Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com> |
||
|
|
0ef50886ef |
feat(g1): rewrite combo-strategy check to runtime-import approach (#9131)
G1 (v3.8.51): section (2) of check-known-symbols no longer regex-scans strategy === "..." literals from combo source. The handled set now comes from a runtime-imported dispatch registry (open-sse/services/combo/ strategyDispatch.ts) that imports the real ordering functions and enumerates which strategies they implement. This keeps the canonical-not-handled gate correct under the upcoming R0.3 registry dispatch, which removes the strategy === branches the regex relied on. - Adds HANDLED_COMBO_STRATEGIES registry (all 20 canonical strategies) + binds the real dispatch leaves (applyStrategyOrdering, resolveAutoStrategyOrder, tryFusionDispatch, tryPipelineDispatch, resolveComboTargetPipeline). - main() imports the registry instead of reading/sourcing combo files. - extractHandledStrategies + diffComboStrategies stay exported (pure, tested). - New TDD test proves the runtime enumeration covers canonical exactly. Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com> |
||
|
|
9b3efef806 |
fix(ci): five workflow defects, one of them shipping the wrong dmg to Intel Macs (#8988)
* fix(ci): five workflow defects, one of them shipping the wrong dmg to Intel Macs
Gaps 31, 19, 16, 30 and 12 of the v3.8.49 process dossier.
## 31 — LIVE BUG: an Intel Mac downloads the ARM dmg
electron-builder runs once per macOS job and each run emits its own
`latest-mac.yml` listing only its own dmg — measured at 338 and 350 bytes,
different content, identical filename. `download-artifact` with
`merge-multiple: true` resolves that collision by ARRIVAL ORDER, so one silently
overwrites the other. arm64 won in the published v3.8.48.
Why that breaks Intel, from electron-updater's own selection code
(out/providers/Provider.js):
files.find(it => [...].some(n => n.includes(process.arch))) ?? files.shift()
The Intel dmg is `OmniRoute-X.Y.Z.dmg` — no arch suffix. On Intel `process.arch`
is "x64", nothing matches, and the fallback takes the FIRST entry. With an
arm64-only manifest that is the ARM build.
So ORDER is the fix, not tidiness: the un-suffixed entry must be first, because
it is the only one reachable through that fallback. `merge-multiple` is now off
(per-artifact subdirectories) and a new
`scripts/release/merge-mac-update-manifest.mjs` merges them deliberately. It
refuses to write when the inputs disagree on version — a manifest stitched from
two builds points at files that were never published together, which is worse
than no manifest.
Validated against the REAL v3.8.49 manifests, not just fixtures: the script
reproduces byte-for-byte the manifest I hand-merged and published, including
both sha512 values and the newer releaseDate.
## 19 — one variable, two opposite machines
`USE_VPS_RUNNER` governed the build and the test jobs together. The build needs
the .113's RAM; the tests need the hosted runner's link. Measured 2026-07-29:
`actions/setup-node` took 20m06s on .113 with 4 concurrent runners versus 16s
hosted (npm cache restore saturating the link), while the tests themselves tied
— 2m54 vs 2m31.
Self-hosted is therefore strictly worse for tests, so rather than add a second
variable to configure, `test-unit`, `test-vitest`, `fast-unit` and `fast-vitest`
are pinned to `ubuntu-latest`. `quality.yml`'s `fast-gates` deliberately keeps
the variable — I have no measurement for it, and guessing is what produced this
gap.
## 16 — a flaky shard sent the publish into the 40-minute build
The artifact reuse filter required `conclusion == "success"` on the whole run, so
any unrelated red shard discarded a perfectly good tree. The artifact is only
uploaded if the Build job succeeded, so its PRESENCE is the accurate signal. Now
it takes the 5 most recent candidate runs and tries each download until one
works. `head_repository.full_name == env.REPO` stays — that clause is the
artifact-poisoning guard, not a filter refinement.
## 30 — the gate that could be bypassed at merge
`check:agent-skills-sync` lived only in quality.yml's PR-only Merge-integrity
job, because the CHANGELOG half of that job needs a base to diff against. This
half does not. Keeping it PR-only left a real hole: this cycle's merge trains
landed with `--admin`, which bypasses required checks, so three SKILL.md files
drifted, rode the release squash into `main`, and the sync-back turned them into
a base-red blocking EVERY PR into release/v3.8.50 until #8954. It now also runs
in ci.yml's lint job, which runs on push to `main`.
## 12 — a cancelled gate reads like a passing one
The dashboard already renders `⚫ CANCELLED` per job, so my dossier entry was
imprecise: they do not vanish, they sit buried mid-table. A cancelled job
reported no verdict at all, and this cycle the Vitest job was cancelled in rounds
1, 2 and 3 — it finished only in round 4, revealing a suite broken the whole
cycle plus two production bugs. The summary now opens with a banner naming every
cancelled job and saying plainly that nothing was checked.
node --import tsx/esm --test tests/unit/mac-update-manifest-merge.test.ts # 11 pass
merge against the real v3.8.49 manifests → both dmgs, Intel first
all four workflows parse; check:workflows --ratchet → 178, baseline 190
* docs(changelog): fragment for #8988
* test(ci): align the artifact-provenance guard with the gap-16 criterion
My own assertion from #8953 encoded the criterion this PR deliberately removes:
it required `.conclusion == "success"` on the whole CI run, which discarded a
perfectly good build tree whenever any unrelated shard went red — pushing the
publish into the 40-minute build the fast path exists to avoid.
Inverted rather than deleted, and the replacement is strictly stronger. It now
pins three things where the old one pinned one: that the loose criterion is gone,
that the step actually probes for the artifact (the accurate signal, since it is
only uploaded when the Build job succeeded), and that it probes MORE THAN ONE
candidate run — without which a single miss still falls back to a full build.
The provenance clause it was originally written to protect
(head_repository.full_name == env.REPO) is untouched and still asserted above.
* fix(ci): finish gap 19 — pin fast-gates and give USE_VPS_RUNNER one meaning
This was left deliberately partial because `fast-gates` had never been measured,
and guessing is what produced gap 19 in the first place. Measured now, and the
evidence is cleaner than expected:
fast-gates, 160 quality.yml runs .... ZERO self-hosted samples
every non-skipped one is "GitHub Actions NNNN"
median duration, 72 successful runs .. 5.6 min hosted
The classifier is not at fault — in the same window ci.yml's Build demonstrably
ran on omniroute-113-7 and omniroute-113-6, so self-hosted runs are visible when
they happen. The USE_VPS_RUNNER expression on this job was dead configuration.
And had it ever fired it would have inherited the measured penalty, because this
job's first two steps are exactly the bottleneck:
actions/setup-node on .113 with 4 concurrent runners .... 20m06s
actions/setup-node hosted .............................. 16s
So it is pinned rather than switched, and the second variable the gap proposed
(USE_VPS_RUNNER_BUILD / _TESTS) turns out to be unnecessary. After this the
variable governs exactly five jobs, all of them build-like:
ci.yml:build · quality.yml:build · npm-publish:publish
nightly-release-green: release-green, main-green
One variable, one meaning: "this job needs the .113's memory". A guard test pins
that — it fails if the variable is ever attached to a test-like job again, and it
also asserts the build KEEPS it, so nobody closes this gap by removing the
variable outright.
node --import tsx/esm --test tests/unit/vps-runner-variable-scope.test.ts # 3 pass
check:workflows --ratchet → 178, baseline 190
---------
Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
|
||
|
|
494b1c961a |
fix(ci): close six release-process gaps from the v3.8.49 run (#8985)
* fix(ci): stop the reconciliation range and the fragment sweep from hiding work Two release-tooling defects found during the v3.8.49 run (gaps 4 and 7 of the process dossier). Both fail by hiding work rather than announcing themselves, which is why each one had already cost a real mistake. ## The reconciliation range was 62× too wide `list-uncovered-commits.mjs` bounded its scan with `git describe --tags`. Releases reach `main` by SQUASH, so no commit on a release branch is ever an ancestor of the tag, and `vPREV..HEAD` re-lists the un-squashed history of every earlier cycle. Measured on release/v3.8.50 @ |
||
|
|
7eca04fd12 |
feat(ci): gate the publish on clean-install AND upgrade-over-previous (#8953)
* feat(ci): gate the publish on clean-install AND upgrade-over-previous
`check:pack-boot` proves a fresh install boots. It does not prove the path that actually
broke us: installing over an existing version, where ~110 SQLite migrations run against a
populated database. v3.8.48 shipped as a hotfix because the published 3.8.47 crashed on
boot, and the v3.8.49 upgrade path was only ever exercised end-to-end by hand — on VPS .16,
against a real 3.8.48 install with a 165 MB database, AFTER publishing. That is backwards.
New gate (`scripts/check/check-install-upgrade.mjs`), wired into npm-publish.yml as step 12,
BEFORE `npm stage publish` — so a broken upgrade never reaches the registry and a staged
package that is never approved simply expires, with no `npm deprecate` needed:
- Phase A: fresh prefix + fresh DATA_DIR, install the packed tarball, boot, health.
- Phase B: fresh prefix + fresh DATA_DIR, install the PREVIOUS published version, boot it
(creates + migrates the DB), stop, install the tarball over the SAME prefix, boot against
the SAME DATA_DIR. Asserts no table present before the upgrade was dropped.
- Schema convergence, and its DIRECTION is the whole point:
fresh − upgraded ≠ ∅ → FAIL. Structure a clean install creates but an upgrade does not
means every existing user is missing it. Not allowlistable.
upgraded − fresh ≠ ∅ → residue; fails only when NEW (allowlist carries the known ones).
A naive symmetric check would either block every release on harmless residue or, if relaxed,
let the dangerous direction through. Measured on VPS .16 (2026-07-30): a real 3.8.48 install
upgraded to 3.8.49 ended with 117 tables against 116 for a clean 3.8.49 install — the extra
being `cache_metrics`, recorded in config/quality/install-upgrade-allowlist.json with the
measurement. Both installs healthy, zero `no such table` in 150 log lines.
`evaluateConvergence` is exported and pure so the asymmetry is testable without packing,
installing or booting anything (same reason check-test-masking exports its helpers):
tests/unit/check-install-upgrade-convergence.test.ts, 8 cases, ~6ms.
A previous version that fails to boot degrades to a warning — a historically bad publish
must not block the current one. Uses node:sqlite (Node 24, already the publish job's
runtime): no new dependency.
* fix(ci): require the reused next-build artifact to come from this repository
CodeQL raised actions/artifact-poisoning/critical on the `next-build` fast path
this PR builds on (#8941). The finding is real and it sits on the path that
produces the published npm tarball.
The step picks a CI run by querying the runs API for `head_sha` and filtering on
`name == "CI" and conclusion == "success"`. That query also returns
`pull_request` runs from FORKS: they execute in this repository's context and
upload their own `next-build`, built from fork-controlled source. Measured
today, 57 runs in this repo have a `head_repository` other than the repo itself.
So the selection trusted bytes by coincidence of commit SHA — anything that made
a fork's head commit coincide with the publish commit could put attacker-built
bytes on npm.
Adds `and .head_repository.full_name == env.REPO` to the selection. Provenance
is now explicit; `head_sha` still carries tree-equality. Verified against the
live API using the expression extracted from the workflow itself — the same
single run (30518663668) is selected either way for the current tip, so the fast
path keeps working while every fork run is excluded.
Not a dismissal (hard rule #14) — the clause removes the flagged trust.
node --import tsx/esm --test tests/unit/npm-publish-artifact-provenance.test.ts
# 3 pass, 0 fail (base: 2 pass, 1 fail)
---------
Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
|
||
|
|
ed6a19e05b |
fix(ci): raise the git ls-files buffer in check:tracked-artifacts (#8844)
* fix(ci): raise the git ls-files buffer in check:tracked-artifacts `execFileSync` defaults to a 1 MiB stdout buffer and throws ENOBUFS past it. `git ls-files -s` on this repo is already at 1,042,494 bytes across 11,091 tracked files — 6,082 bytes from the ceiling. Any PR adding roughly sixty files crosses it. That matters more than a failing script: the check runs on pre-commit, so once the listing crosses 1 MiB, committing breaks for everyone working the repo, not just for the change that happened to cross it. It is not a hypothetical — the private EE fork hit it this week when a sync landed ~214 translation files and pushed the listing 504 bytes over; every commit there failed the hook until this same fix landed. Both call sites now share a GIT_LS_OPTS with a 64 MiB ceiling — far above any plausible tree, rather than just above today's, since the listing only grows. * docs(changelog): add fragment for #8844 Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> |
||
|
|
ff168ab086 |
fix(electron): use NEXT_DIST_DIR when stripping stale native modules (#8794)
removeNativeModules() was called with a hardcoded ".next" path while the actual
distDir is NEXT_DIST_DIR (".build/next" by default). Because the function
early-returns when the directory does not exist, the cleanup silently no-opped
and the plain-Node-ABI better-sqlite3 copy produced by `next build` survived
into the packaged app.
At runtime the standalone server runs under ELECTRON_RUN_AS_NODE, so it needs
the Electron ABI (148 for electron 43). Loading the ABI-137 copy fails with
ERR_DLOPEN_FAILED, the app falls back to the sql.js WASM driver, the connection
is closed and retried in a loop, WASM memory is never reclaimed and the process
OOMs -> HTTP 500 on every route.
Also adds assertNoStaleHashedNatives() so a wrong baseDir fails the build
instead of silently shipping a broken installer. This has regressed at least
twice (#1497 with ABI 127 vs 145, #7082/#7681 with 137 vs 148).
Refs #7082, #7681, #1497, #8792. Supersedes the abandoned #7123.
|
||
|
|
0eeb8f45c0 |
refactor(sse): extract combo target resolution into combo/targetResolution.ts (#8592)
* refactor(sse): extract combo dispatch prelude into combo/dispatchPrelude.ts
Pure move, no behaviour change. First of ~7 PRs decomposing the combo.ts
god-file (#3501).
handleComboChat evaluates a series of dispatch branches before it ever
reaches target resolution or the sequential attempt loop. None of them
iterate targets in priority order or need the failover/retry/credential
gate machinery that follows, so they move to a leaf:
- context-cache pin routing (Fix #679), including the
pinIsDurablyUnhealthy / isPinnedModelDurablyUnhealthy health gate
- fusion panel dispatch + the #6455 misconfiguration warn
- pipeline chaining
- nested combo-ref execute-mode runtime-unit dispatch
Only the chaos and round-robin hand-offs stay inline (11 and 13 lines);
extracting those would be pure indirection.
open-sse/services/combo.ts 3642 -> 3341 (-301)
open-sse/services/combo/dispatchPrelude.ts: 619 (under the 800 cap)
Each helper keeps the fall-through protocol the inline blocks had: return
a Response to OWN the request, return null to fall through. A flipped
null/Response would silently bypass the whole combo strategy, so the new
tests pin both directions for every branch.
combo.ts re-exports pinIsDurablyUnhealthy so combo-pin-health-gate.test.ts
keeps resolving. The leaf takes handleComboChat as a `runCombo` parameter
instead of importing it, so combo/ keeps zero back-edges into combo.ts.
Complexity-neutral: the first cut added +3 violations (two
max-lines-per-function, one complexity) inside the new leaf, so
evaluatePinnedResponse, orderRuntimeUnits, recordRuntimeUnitStickySuccess
and buildBaseOptions were split out. check:complexity now measures 2169
and check:cognitive-complexity 956 — identical to the pristine base.
* test(sse): close the dispatch-prelude coverage holes found by mutation testing
An adversarial mutation audit of the suite added in the previous commit
found it guarded the fall-through protocol well but asserted almost
nothing about what the helpers do once they OWN the request. 5 of 12
seeded mutations survived. Worst case: deleting the pinned-model
dispatch call outright left all 12 tests green.
Three holes, now closed (8 tests -> 20):
Hole A — the honored-pin path had zero coverage. Both existing pin tests
DROP the pin, so the dispatch, the 200-but-empty quality gate, the
[408, 429, 500, 502, 503, 504] failover list and the catch(pinErr)
branch were unguarded — exactly the logic the 2026-06-21 / 2026-06-22
incident comments call load-bearing. Adds five tests over a seeded
healthy provider connection so the pin is actually honored.
Hole B — orderRuntimeUnits was only ever driven with `priority`, which
is a no-op through it. Four of five strategy branches could be deleted
with nothing failing. Adds round-robin rotation and weighted sticky
ordering tests.
Hole C — recordRuntimeUnitStickySuccess never did anything under test:
both its guards need weighted/round-robin, so an early return changed
nothing. Covered by the new sticky-batch test.
Verified by re-running the mutations rather than assuming: all 7 that
previously survived (delete-pin-dispatch, serve-despite-failed-quality,
never-fail-over-on-transient, rr-counter-not-advanced, rotation-removed,
weighted-sticky-skipped, sticky-recording-no-op) are now killed.
The first sticky-batch test I wrote was itself vacuous — asserting "same
unit twice" holds equally when the recording helper is stubbed out, since
nothing advances the counter either. It now asserts the batch runs out
and rotation resumes on the third dispatch, which is what actually
distinguishes the two.
Also restores API_KEY_SECRET in test.after; it was set at module load and
never put back, inconsistent with the DATA_DIR handling beside it.
* fix(ci): teach known-symbols gate the relocated fusion/pipeline dispatch
The combo sub-check of check:known-symbols asserts every canonical routing
strategy has a real dispatch branch. It scanned a hardcoded file list and
matched only `strategy === "..."`, so the prelude extraction tripped it twice:
[combo] 2 estratégia(s) canônica(s) sem branch de despacho em combo.ts:
✗ fusion
✗ pipeline
Both branches are still wired — they just moved to combo/dispatchPrelude.ts and
took the early-return guard form `if (strategy !== "fusion") return null;` that
extracting a branch into a `tryXDispatch()` leaf naturally produces.
Two changes, both extending existing precedent (the list already carries the
Block J leaves for the same reason):
- register combo/dispatchPrelude.ts in comboDispatchFiles
- widen the extractor to `strategy [!=]== "..."` so the inverted guard counts
Loose `==`/`!=` stay rejected, and no `handledNotCanonical` fallout: the gate
now reports 20 canonical strategies, all 20 via despacho.
* chore(ci): register combo-dispatch-prelude test in stryker tap.testFiles
check:mutation-test-coverage --strict failed once the known-symbols fix let
Fast Quality Gates advance to it:
✗ 2 covering unit test(s) across 2 module(s) are missing from
stryker.conf.json tap.testFiles
open-sse/services/combo/comboStructure.ts
open-sse/services/combo/rrState.ts
The new tests/unit/combo-dispatch-prelude.test.ts exercises both modules, and
both are already in stryker's mutate list, so without the registration its
mutant kills would not have counted toward the nightly mutation gate.
Note (unchanged, still out of scope): combo/dispatchPrelude.ts itself is not in
stryker's `mutate` list. Adding it would widen the nightly mutation surface,
which is a separate call from fixing this drift.
* docs(changelog): add fragment for #8582 combo dispatch prelude
* refactor(sse): extract combo target resolution into combo/targetResolution.ts
Pure move, no behaviour change. Lifts the target-resolution stage of
handleComboChat — everything between the dispatch prelude and the attempt
loop — into a new leaf, open-sse/services/combo/targetResolution.ts.
Moved verbatim: provider-wildcard expansion, weighted step-group resolution
+ sticky-weighted eligibility, request-tag routing, the known-context-overflow
early return, the smart/pipeline-enabled auto dispatch, auto-strategy
ordering, per-strategy ordering, cache-strategy affinity, session stickiness,
eval scores, request-compatibility + context-requirement filters, task-aware
reordering, prompt-cache affinity, and the priority-strategy pre-screen.
The three early exits become an { earlyResponse } result so the host decides
to return them (same pattern as resolveAutoStrategyOrder). The values the
attempt loop still reads — orderedTargets, stickyWeightedLimit,
getWeightedStepKeyForTarget, the session-stickiness result and preScreenMap —
are returned instead of closed over. Loop config (maxRetries, retryDelayMs,
fallbackDelayMs, maxSetRetries, setRetryDelayMs) stays in combo.ts.
buildAutoCandidates is dependency-injected because it lives in combo.ts, so
the leaf keeps zero back-edges into its host.
combo.ts 3640 -> 3321 lines; new leaf 484 lines (under the 800 cap).
Part of the #3501 god-file decomposition campaign.
* refactor(sse): split targetResolution into stage helpers, ratchet combo.ts file-size baseline
Follow-up to the target-resolution extraction: the moved region landed as one
311-line function, which converted inline code inside the (already-violating)
handleComboChat into a NEW separately-counted violating function — check:complexity
2169 -> 2171 and check:cognitive-complexity 956 -> 957.
Split resolveComboTargetPipeline along its natural stage boundaries into 14
helpers (wildcard expansion, weighted eviction/eligibility/sticky-key/selection,
step-key mapper, context-overflow response, pool-size log, smart-pipeline dispatch
and its fall-through logger, strategy ordering, continuity filters, task-aware
ordering, prompt-cache enablement/first-target protection/affinity stage). Each
stage takes the previous stage's output and returns the next; still a pure move.
The leaf now contributes ZERO complexity, max-lines-per-function and
cognitive-complexity violations. Both ratchets are back at base
|
||
|
|
bb5cb51f3e |
fix(docker): honor OMNIROUTE_BASE_PATH behind reverse-proxy subpaths (#8615)
* fix(docker): honor OMNIROUTE_BASE_PATH behind reverse-proxy subpaths Next.js basePath is compile-time state; Docker now records the baked value, forwards the env var as a build-arg, patches root-path images at container start when needed, and probes health under the active subpath. Hard Rule #13: scripts/docker/patch-basepath.sh and the entrypoint invoke Node with a fixed argv; OMNIROUTE_BASE_PATH is read from process.env only — never interpolated into sed/awk. Closes #8600 * fix(docs): unblock CI for Docker basePath guide Describe the build-time basePath marker as a sentinel file instead of a fabricated env var, and replace the unsupported ```env fence with bash so fumadocs/Shiki can compile DOCKER_GUIDE.md during DAST smoke. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(docker): add changelog fragment for #8615 basePath bundle patch Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> |
||
|
|
b676c5f826 |
feat(ci): automate ratchet shrink-banking so caps stop outliving their files (#8584) (#8612)
* feat(ci): automate ratchet shrink-banking on the release branch (#8584)
The quality ratchet is only half automatic, and it is the wrong half. Raising a
cap is a manual JSON edit that takes ten seconds and is the fastest way to unblock
a red PR. Lowering one requires someone to run `--update` and commit the result —
and no workflow does: grepping `.github/workflows/` for `--update` finds only
wiki-sync.yml (unrelated) and ci.yml's check-quality-ratchet.mjs --require-tighten
(a different script against a different metric).
Measured on release/v3.8.49 at
|
||
|
|
df9550fce9 |
fix(cli): prepare Next.js cache dir on Android/Termux before serve (#8593)
* fix(cli): ensure `~/.cache` is created and `XDG_CACHE_HOME` is set before Next.js loads on Android/Termux to prevent silent HTTP 500 errors due to instrumentation hook failures (#8519) * chore(quality): ignore XDG_CACHE_HOME in the env/docs contract scanner XDG_CACHE_HOME is an XDG Base Directory spec variable set by the OS or the operator, never OmniRoute product config — the same reason XDG_CONFIG_HOME is already ignored. The Android/Termux cache-dir preparation added here reads it to honor an operator-set cache location, which made check-env-doc-sync demand an .env.example/ENVIRONMENT.md entry for a variable we do not own. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * build(pack): require bin/cli/utils/ensureAndroidCacheDir.mjs in the tarball bin/omniroute.mjs imports this module at startup to prepare the Next.js cache dir before serve on Android/Termux. bin/cli/ is only an allowlist PREFIX, so a file missing from the tarball would not fail the unexpected-paths check — it would ship a CLI that throws ERR_MODULE_NOT_FOUND on the very platform this change targets. Registering it makes the absence loud, same guard class as storageKeyProvision.mjs and versionFastPath.mjs. Caught by tests/unit/pack-artifact-entrypoint-closures.test.ts in the v3.8.49 merge-train. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> |
||
|
|
1d44ee00f8 |
refactor(sse): extract combo dispatch prelude into combo/dispatchPrelude.ts (#8582)
* refactor(sse): extract combo dispatch prelude into combo/dispatchPrelude.ts Pure move, no behaviour change. First of ~7 PRs decomposing the combo.ts god-file (#3501). handleComboChat evaluates a series of dispatch branches before it ever reaches target resolution or the sequential attempt loop. None of them iterate targets in priority order or need the failover/retry/credential gate machinery that follows, so they move to a leaf: - context-cache pin routing (Fix #679), including the pinIsDurablyUnhealthy / isPinnedModelDurablyUnhealthy health gate - fusion panel dispatch + the #6455 misconfiguration warn - pipeline chaining - nested combo-ref execute-mode runtime-unit dispatch Only the chaos and round-robin hand-offs stay inline (11 and 13 lines); extracting those would be pure indirection. open-sse/services/combo.ts 3642 -> 3341 (-301) open-sse/services/combo/dispatchPrelude.ts: 619 (under the 800 cap) Each helper keeps the fall-through protocol the inline blocks had: return a Response to OWN the request, return null to fall through. A flipped null/Response would silently bypass the whole combo strategy, so the new tests pin both directions for every branch. combo.ts re-exports pinIsDurablyUnhealthy so combo-pin-health-gate.test.ts keeps resolving. The leaf takes handleComboChat as a `runCombo` parameter instead of importing it, so combo/ keeps zero back-edges into combo.ts. Complexity-neutral: the first cut added +3 violations (two max-lines-per-function, one complexity) inside the new leaf, so evaluatePinnedResponse, orderRuntimeUnits, recordRuntimeUnitStickySuccess and buildBaseOptions were split out. check:complexity now measures 2169 and check:cognitive-complexity 956 — identical to the pristine base. * test(sse): close the dispatch-prelude coverage holes found by mutation testing An adversarial mutation audit of the suite added in the previous commit found it guarded the fall-through protocol well but asserted almost nothing about what the helpers do once they OWN the request. 5 of 12 seeded mutations survived. Worst case: deleting the pinned-model dispatch call outright left all 12 tests green. Three holes, now closed (8 tests -> 20): Hole A — the honored-pin path had zero coverage. Both existing pin tests DROP the pin, so the dispatch, the 200-but-empty quality gate, the [408, 429, 500, 502, 503, 504] failover list and the catch(pinErr) branch were unguarded — exactly the logic the 2026-06-21 / 2026-06-22 incident comments call load-bearing. Adds five tests over a seeded healthy provider connection so the pin is actually honored. Hole B — orderRuntimeUnits was only ever driven with `priority`, which is a no-op through it. Four of five strategy branches could be deleted with nothing failing. Adds round-robin rotation and weighted sticky ordering tests. Hole C — recordRuntimeUnitStickySuccess never did anything under test: both its guards need weighted/round-robin, so an early return changed nothing. Covered by the new sticky-batch test. Verified by re-running the mutations rather than assuming: all 7 that previously survived (delete-pin-dispatch, serve-despite-failed-quality, never-fail-over-on-transient, rr-counter-not-advanced, rotation-removed, weighted-sticky-skipped, sticky-recording-no-op) are now killed. The first sticky-batch test I wrote was itself vacuous — asserting "same unit twice" holds equally when the recording helper is stubbed out, since nothing advances the counter either. It now asserts the batch runs out and rotation resumes on the third dispatch, which is what actually distinguishes the two. Also restores API_KEY_SECRET in test.after; it was set at module load and never put back, inconsistent with the DATA_DIR handling beside it. * fix(ci): teach known-symbols gate the relocated fusion/pipeline dispatch The combo sub-check of check:known-symbols asserts every canonical routing strategy has a real dispatch branch. It scanned a hardcoded file list and matched only `strategy === "..."`, so the prelude extraction tripped it twice: [combo] 2 estratégia(s) canônica(s) sem branch de despacho em combo.ts: ✗ fusion ✗ pipeline Both branches are still wired — they just moved to combo/dispatchPrelude.ts and took the early-return guard form `if (strategy !== "fusion") return null;` that extracting a branch into a `tryXDispatch()` leaf naturally produces. Two changes, both extending existing precedent (the list already carries the Block J leaves for the same reason): - register combo/dispatchPrelude.ts in comboDispatchFiles - widen the extractor to `strategy [!=]== "..."` so the inverted guard counts Loose `==`/`!=` stay rejected, and no `handledNotCanonical` fallout: the gate now reports 20 canonical strategies, all 20 via despacho. * chore(ci): register combo-dispatch-prelude test in stryker tap.testFiles check:mutation-test-coverage --strict failed once the known-symbols fix let Fast Quality Gates advance to it: ✗ 2 covering unit test(s) across 2 module(s) are missing from stryker.conf.json tap.testFiles open-sse/services/combo/comboStructure.ts open-sse/services/combo/rrState.ts The new tests/unit/combo-dispatch-prelude.test.ts exercises both modules, and both are already in stryker's mutate list, so without the registration its mutant kills would not have counted toward the nightly mutation gate. Note (unchanged, still out of scope): combo/dispatchPrelude.ts itself is not in stryker's `mutate` list. Adding it would widen the nightly mutation surface, which is a separate call from fixing this drift. * docs(changelog): add fragment for #8582 combo dispatch prelude |
||
|
|
803e7373de |
feat(ci): block stale UI translations when an English value is rewritten (#8574)
Closes the gap that let #8463 ship. `oauthModal.googleOAuthWarning`'s English value was rewritten when the Antigravity login helper landed (#5203); 39 of 43 locales kept a translation of the PREVIOUS English, which told operators to "copy the full URL and paste it below" — a flow that cannot complete for that provider family. Non-English users read confident, wrong instructions for months and no gate noticed. None of the three existing gates can see this class: - `sync-ui-keys.mjs` only backfills keys that are ABSENT, never ones that are STALE; - `check-ui-keys-coverage.mjs` counts key PRESENCE, so a stale translation scores as fully covered (all 43 locales sat at 99.6% throughout); - `check-translation-drift.mjs` tracks the `docs/i18n/<locale>/**.md` documentation mirrors — it never reads `src/i18n/messages/*.json` at all. (Its `.i18n-state.json` is also absent, so it self-skips, but bootstrapping it would not have helped: wrong surface.) New gate `scripts/i18n/check-ui-value-drift.mjs` is DIFF-AWARE rather than baseline-backed: it compares `en.json` at the merge base against the working tree, and for every key whose English value changed, reports any locale still holding an untouched translation. That choice deliberately freezes pre-existing debt — a diff cannot reveal which old English a long-standing translation came from, so the gate judges only what the current change touches, and unrelated PRs never pay for historical drift. The alternative, a per-key hash baseline over 11207 keys, would have cost a ~600 KB generated file (3x the largest existing baseline) churning on every i18n PR. Two ways to satisfy it: refresh the translations, or set them to `__MISSING__:<new english>` so the runtime serves the corrected English (#7258) while the key queues for translation. When the string's MEANING changes, renaming the key is better still — a new key cannot inherit a stale translation, which is what #8463 did. Wired blocking into the `i18n-ui-coverage` job (the `i18n` job is `continue-on-error: true`, so a gate there could not block anything). That job gains `fetch-depth: 0` because the gate needs the base ref; without it the gate self-skips with `base-unresolved`, mirroring `check-openapi-breaking`. `BASE_REF` is passed via `env:` and reaches git only through `execFileSync` argv — never a shell string. Verified against the real defect: rewriting an English value with translations left behind reports exactly 39 stale locales and exits 1; `--warn` exits 0; an unresolvable base exits 0 with `SKIP reason=base-unresolved`. Co-authored-by: ikelvingo <im.kelvinwong@gmail.com> |
||
|
|
82bed08404 |
docs(podman): clarify Podman Machine deployment (#8569)
* docs(podman): clarify Podman Machine deployment * style(podman): format guidance and regression test |
||
|
|
1cbe8c44f5 |
Train 1D: merge via --admin on .113 validation
Squash merge from local merge-train (Hard Rule owner-approved). Tip 029cdf4215cf465f0e1716ac9f84a84692b1e881 validated on 192.168.0.113: 26631/26653 pass. |
||
|
|
4c9292e66e |
chore(i18n): normalize zh-TW terminology and sync stale README figures (#8554)
The zh-TW catalog was machine-translated with mainland-habit vocabulary and simplified->traditional conversions that picked the wrong homophone, and its README still advertised the v3.7-era figures. Terminology (docs/i18n/zh-TW/ + src/i18n/messages/zh-TW.json): - wrong-character conversions: 上遊->上游, 後臺->後台, 儀錶板->儀表板 - mainland habits: 默認->預設, 緩存->快取, 模塊->模組, 調用->呼叫, 字符串->字串, 全局->全域, 文檔->文件, 響應->回應 - consistency: 供應商/提供商->提供者 (提供者 was already 71% dominant), 型別->類型 for UI labels, 不活躍->未啟用 README figures synced to the English source: 231->290 providers, 17->19 routing strategies, 1.6B->1.53B free tokens, 50+->90+ free tiers, 11->40+ free forever, 87->104 MCP tools, 30->31 scopes. Root cause — the generator's post-translation pass was a hardcoded list that duplicated the glossary and was wired only into the deprecated generate-multilang.mjs, so the active run-translation.mjs pipeline applied nothing. Worse, its blanket /代碼/g -> 程式碼 rule would corrupt 控制代碼 (handle), 語系代碼 (locale code) and 錯誤代碼 (error code) on the next regeneration. Both scripts and the drift gate now share scripts/i18n/glossary-normalize.mjs, driven by scripts/i18n/glossary/<locale>.json as the single source of truth. Ambiguous terms carry blockedPrefixes so 型別->類型 can stay enforced without mangling 模型別名 (model alias) or 基本型別 (a programming data type); terms whose synonym is also a legitimate rendering elsewhere (代碼, 項目) are seeded with no synonyms and documented instead of blanket-rewritten. CI now runs the glossary gate for zh-TW alongside zh-CN. |
||
|
|
4bf47c9b80 |
chore(perf): add deterministic request-body heap benchmark (#7847) (#8549)
#7847 reports a 3.05 MiB request (729 messages / 86 tools) reaching ~12,282 MiB of V8 heap, and asks for "a regression benchmark that records peak heap for representative 500-800-message, tool-rich requests" before any fix lands. There is currently no memory baseline in the repo at all (bench:compression is the only benchmark), so a clone-reduction change could neither be justified nor regression-guarded. npm run bench:heap-body attributes retained heap to each copy the chat path makes: | mechanism | call site | retained | x wire | | cloneLogPayload (unbounded) | chat.ts buildClientRawRequest | 3.18 MiB | 1.04x | | cloneBoundedForLog (bounded) | requestLogger.logClientRawRequest | 0.04 MiB | 0.01x | | structuredClone x3 (combo targets) | combo.ts attemptBody | 9.53 MiB | 3.12x | | JSON.stringify (token estimate) | combo.ts estimateTokens | 3.06 MiB | 1.00x | | per request (sum) | |15.81 MiB | 5.17x | It measures the real production helpers rather than reimplementations, so a change to the log bounds or the clone strategy is reflected directly. Design notes: - Deterministic: fixed-seed LCG, no Math.random(). Verified byte-identical across three consecutive runs — without that, a before/after delta measures noise, not the change. - Corpus lives in its own side-effect-free module so the unit test can import it without booting SQLite (requestLogger transitively opens the DB at import time). - Hermetic: DATA_DIR is redirected to a temp dir before importing, so the benchmark never touches the operator's real ~/.omniroute store. - Node, not bun: --expose-gc and V8 heap accounting are the measurement; another engine's heap number would not describe the production runtime. - --max-retained-mib exits non-zero, so this can become a CI gate once a target is agreed. Reports only; wires nothing into CI and changes no production code. |
||
|
|
c447be4329 |
fix(db): classify compressionDetailNormalizers as db-internal in check-db-rules (#8534)
* fix(db): classify compressionDetailNormalizers as db-internal in check-db-rules check:db-rules fails on release/v3.8.49 at its own HEAD: the module added by #8404 is neither re-exported from localDb.ts nor listed in INTENTIONALLY_INTERNAL, so the gate blocks every PR->release run and tests/unit/check-db-rules.test.ts fails its live-repo case. Its only importer is its sibling src/lib/db/compression.ts, via a relative import inside src/lib/db/ — the db-internal classification the list already uses for apiKeyColumnFallbacks and caseMapping. Re-exporting it from localDb.ts would instead advertise pure normalizer helpers as part of the compat surface, which Hard Rule #2 discourages. * test(db): mirror compressionDetailNormalizers in the INTENTIONALLY_INTERNAL audit The classification guard asserts the exact audited set. Adding the module to check-db-rules.mjs without the mirror left the exact-list/exact-size assertion red; both assertions stay exact (37 entries). Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com> --------- Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com> |
||
|
|
1930b09c6a |
chore(sse): drop deprecated baseUrl from open-sse tsconfig for TS 7.0 (#8473)
TypeScript 6.x raises TS5101 on `open-sse/tsconfig.json`: `baseUrl` is deprecated and stops functioning in TypeScript 7.0. It was paired with `ignoreDeprecations: "5.0"`, which no longer silences it under TS 6 (the compiler now demands "6.0"). Remove `baseUrl: ".."` and rewrite the `paths` mappings relative to the tsconfig's own directory, which is how TypeScript resolves them with no baseUrl set: "@/*" ./src/* -> ../src/* "@omniroute/open-sse" ./open-sse -> ../open-sse "@omniroute/open-sse/*" ./open-sse/* -> ../open-sse/* `ignoreDeprecations` goes with it — baseUrl was the only deprecated option it was suppressing. Verified by diffing the full tsc error set against the previous config (the base run used `ignoreDeprecations: "6.0"` so compilation proceeds past the config error, which otherwise aborts type-checking and masks everything): zero new errors, 28 fewer. All 28 were in `electron/*.js`, which `baseUrl: ".."` had been dragging into the open-sse program via root-relative resolution. Scoping the program back to open-sse also moves `check:type-coverage` from 92.17% to 94.01%; the ratchet direction is up so the gate passes, and the baseline is deliberately left alone because the gain is a measurement-scope change rather than new typing work. The guard test asserts no tsconfig reintroduces `baseUrl` or `ignoreDeprecations`, and that every `paths` target still resolves to a real directory — the second half is the part that matters, since dropping baseUrl silently changes what those mappings point at. |
||
|
|
875de01de7 |
chore(quality): drop stale muse-spark-web allowlist entry + sync sidebar order snapshots (#8383)
Two independent "code is right, bookkeeping lagged" base-reds: 1. #8233 made open-sse/executors/muse-spark-web.ts import sanitizeErrorMessage from utils/error.ts (a real Rule #12 fix), but left its KNOWN_MISSING_ERROR_HELPER allowlist entry in scripts/check/check-error-helper.mjs in place. The gate's own stale-allowlist enforcement (assertNoStale) correctly flagged the now -obsolete entry: `npm run check:error-helper` failed with "1 entrada(s) obsoleta(s)", and tests/unit/check-error-helper.test.ts's "the shipped allowlist freezes exactly the known current violators" test expected an empty Set. Removed the entry (kept the assertNoStale machinery and the general scope-header comments untouched). 2. #8064 added the "compression-exclusions" sidebar item right after "compression-studio" in COMPRESSION_CONTEXT_GROUP (deliberate, complete feature) but didn't update two order-snapshot tests written before that item existed: - tests/unit/sidebar-visibility.test.ts expected the "omni-proxy" section's flattened id list to end the compression block at "compression-studio". - tests/unit/ui/sidebar-engine-items.test.ts asserted "Studio must be last" in COMPRESSION_CONTEXT_GROUP. Updated both to the real, intentional order: Settings -> Combos -> engines -> Studio -> Exclusions (Studio now second-to-last, Exclusions last). Validation (red -> green): - check:error-helper gate: red ("1 entrada(s) obsoleta(s)") -> green ("OK (898 files scanned, 0 known-missing frozen)") - tests/unit/check-error-helper.test.ts: 31/32 -> 32/32 - tests/unit/sidebar-visibility.test.ts: 6/7 -> 7/7 - tests/unit/ui/sidebar-engine-items.test.ts: 13/14 -> 14/14 Refs #8233 Refs #8064 |
||
|
|
b84f86ad4f | fix(runtime): isolate unique 8177 repairs (#8298) | ||
|
|
d43e71613e |
optimize(chaos+ponytail): i18n ponytail, dedupl dispatch, provider diversity (#8264)
* feat(chaos+ponytail): parallel chaos-mode dispatch + ponytail output style (rebased on v3.8.49)
- Chaos mode: new auto/chaos variant fans the prompt out to the top-N
stable models in parallel and returns a single merged SSE stream.
- Progressive streaming: each panel model's answer is enqueued as it
lands (omni-chaos-part event), instead of awaiting the whole panel.
- withTimeout now aborts the underlying request (modelAbortSignal) on
timeout so the connection is released, not leaked.
- concatSseText parses both OpenAI and Anthropic SSE wire formats.
- autoPrefix/modePacks add the chaos-mode weight pack; virtualFactory
materializes auto/chaos with fusion strategy + chaos config flag.
- Ponytail (lazy-senior-dev mode) integrated into the existing
OUTPUT_STYLE_CATALOG registry (id 'ponytail') so it rides the production
output-style injector, instead of a bespoke duplicate module. Dev-only
scripts and the duplicate ponytail/ module are removed.
- Tests: chaosEngine/chaosVirtualCombo cover panel dispatch, progressive
broadcast, timeout abort, and Anthropic parsing; autoCombo pack count
updated to 6.
Rebased onto release/v3.8.49 (no provider-registry or validation changes —
those are split out per review).
* optimize(chaos+ponytail): i18n ponytail, dedupl chaos dispatch, provider diversity
- Ponytail: add vi/ja/pt-BR/id i18n with lite/full/ultra levels
- chaosEngine: extract dispatchOnePanelModel (shared), add onResult for
progressive SSE streaming, fix withTimeout anti-pattern
- virtualFactory: deduplicate chaos panel by provider, add tuning overrides
- dispatchChaosFromCombo: accept ChaosTuning, enforce minPanel
- Add/port 8 node-runner tests for ponytail i18n + catalog integrity
- Add muse-spark-web.ts to KNOWN_MISSING_ERROR_HELPER (pre-existing)
* fix(8264): use HandleSingleModel type in chaosEngine dispatch (base-drift)
---------
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
|
||
|
|
fbb8d45757 |
docs(db): add reproducible SQLite coupling inventory (#8262)
Co-authored-by: 千乘妍 (Xiaoyaner) <xiaoyaner0201@users.noreply.github.com> |
||
|
|
18f1f667bf | fix(claude-web): align session transport and fallback (#8230) | ||
|
|
4ea08f520b |
fix(sse): Gemini malformed function-call handling + tool_choice translation (#8211)
* fix(sse): synthesize tool_calls for Gemini's malformed function-call abort reasons Live incident (dashboard log id 1784489701456-d8c0e9): Gemini terminates a stream with finishReason MALFORMED_FUNCTION_CALL/UNEXPECTED_TOOL_CALL when its own parser rejects an attempted tool call — there's no real functionCall part, only a human-readable finishMessage. gemini-to-openai.ts passed this through raw as finish_reason (9router#2462's fix, correctly keeping it off a clean "stop"/Claude end_turn), but a raw "malformed_function_call" isn't one of OpenAI's 5 documented finish_reason values, so a real OpenAI-format client (OpenClaw) has no handling for it at all and silently never notices the turn failed — confirmed live via tests/integration/live-gemini-workload.test.ts's [28] streaming case after the Gemini TPM/rebase work on this branch. Fix: synthesize a tool_calls entry (arguments carry the error code + Gemini's finishMessage, valid JSON) and finish_reason: "tool_calls" instead, routing the failure into the ordinary "tool call arguments didn't parse" path every OpenAI-compatible agent loop already handles. Defers to a real tool call if one already completed earlier in the same turn — the real call wins, no synthetic entry piles on top of it. Tests (TDD, each confirmed red-before-green): - 5 new unit tests in the existing 9router#2462 regression file, covering the synthesis itself, UNEXPECTED_TOOL_CALL, the real-tool-call-wins edge case, and no-regression on a clean STOP. - New fixture (tests/fixtures/translation/gemini-malformed-function-call-stream.json): the real 6-chunk event series from the live incident, sanitized (personal paths/URLs replaced with generic placeholders, structure preserved exactly). - New integration test chains the real translator into the real Responses API transformer using that same fixture, proving correct behavior on BOTH /v1/chat/completions and /v1/responses from one shared ground-truth event series. Co-authored-by: Markus Hartung <markus.hartung@gmail.com> * fix(sse): don't drop a malformed tool-call failure when it lands beside a real one Live incident (dashboard log id 1784589106014-2a42f8), analyzing why the prior malformed-function-call fix (3568c7259) still wasn't reaching the client in this case: Gemini can emit a REAL, valid functionCall AND finish the SAME candidate with MALFORMED_FUNCTION_CALL — the model attempted multiple tool calls in one turn (here: a real status-check call plus a malformed "exec"+"cron" multi-call attempt), one parsed cleanly and the other didn't. The first fix version skipped synthesizing a failure signal whenever a real tool call already existed (state.toolCalls.size > 0), on the assumption that meant the model was retrying a LATER, separate attempt after an earlier one already succeeded. That's indistinguishable, from the translator's state, from this same-turn case — so it silently discarded the malformed attempt's information entirely: the client saw the real call succeed and never learned the other tool calls were attempted and rejected. Fix: always synthesize the failure entry when a malformed abort reason is seen, appending it alongside any real tool call rather than skipping it. Multiple tool_calls in one response is normal, well-supported OpenAI behavior (parallel tool calls), so this adds the failure as an additional entry instead of replacing or hiding the real one. Tests (TDD, confirmed red-before-green): - Rewrote the unit test that encoded the old (wrong) assumption to assert both the real and synthesized calls are present. - New fixture (gemini-malformed-function-call-parallel-real-call-stream.json): the real event series from this incident, sanitized. - New integration tests (same file as 3568c7259's) prove both /v1/chat/completions and /v1/responses surface both tool calls correctly from this fixture. Co-authored-by: Markus Hartung <markus.hartung@gmail.com> * feat(sse): honor tool_choice when translating OpenAI requests to Gemini Investigating a live report that gemini-3.1-flash-lite frequently narrates an intended tool call in plain text instead of actually emitting one (dashboard log id 1784591483850-49c408 — 9 raw provider chunks, all plain text, zero functionCall parts, clean finishReason STOP): body.tool_choice was never read anywhere in the OpenAI->Gemini request translator. result.toolConfig was unconditionally hardcoded to { functionCallingConfig: { mode: "VALIDATED" } } whenever tools were present, regardless of what the caller sent. VALIDATED lets the model respond with plain text OR a schema-validated function call at its own discretion — it never forces a call the way OpenAI's tool_choice: "required" (Gemini's ANY mode) does, so a caller had no way to compel a tool call even when explicitly requesting one. Added convertOpenAIToolChoiceToGemini(), mirroring the existing convertOpenAIToolChoice() in openai-to-claude.ts for the same OpenAI tool_choice shapes (string "auto"/"none"/"required", or {type:"function",function:{name}} to force one specific tool): - unset/"auto" -> VALIDATED (unchanged default, no regression) - "required"/"any" -> ANY (forces a call) - "none" -> NONE (disables function calling) - {type:"function",...} -> ANY + allowedFunctionNames: [name] Wired into both Gemini request paths: the direct/base translator (openaiToGeminiBase) and the Antigravity/Cloud Code envelope (wrapInCloudCodeEnvelope), which now reuses the base translator's already- computed toolConfig instead of re-deriving its own hardcoded VALIDATED. This unblocks (but does not itself resolve) the live question — a tool_choice: "required" A/B test against gemini-3.1-flash-lite follows to confirm ANY mode actually changes the narrate-vs-act behavior in practice. Also updates the T11 any-budget allowlist for this file: the "any" string comparisons (tool_choice value "any", not a TypeScript type) are the same documented false-positive pattern already carved out for executors/base.ts. Tests (TDD, confirmed red-before-green): 9 new unit tests covering all tool_choice shapes on both the direct and Antigravity/Cloud Code paths, plus the no-tools and unset-default no-regression cases. Co-authored-by: Markus Hartung <markus.hartung@gmail.com> * chore(quality): file-size baseline for own-growth (#8211) Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: Markus Hartung <markus.hartung@gmail.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> |
||
|
|
40ee0847d9 |
feat(sre): add tcp-close-analyzer.py for debugging client-vs-server TCP close order (#8208)
Dependency-free (stdlib-only) libpcap/Ethernet/IPv4/TCP parser that answers one question: does OmniRoute or the far end (Caddy, on behalf of whichever client it's proxying) close the TCP connection first? Dashboard-level 499s only tell us OmniRoute detected a dropped connection, not which side's FIN/ RST actually landed first -- this settles it from the raw packets. Handles classic Ethernet and both "Linux cooked" linktypes (SLL/SLL2, what `tcpdump -i any` produces) since rootless Podman has no host-visible bridge interface to capture on directly -- the capture instructions in the script document the nsenter-into-container-netns workaround. Adds --find to grep every reassembled stream for a literal marker string -- in practice the reliable way to locate one specific request (the x-correlation-id header isn't echoed on every hop) is dropping a fresh UUID into an actual chat message and searching for it, then cross-referencing the matched stream's timing against data/call_logs/<date>/*.json. Co-authored-by: Markus Hartung <markus.hartream@gmail.com> |
||
|
|
08129cfb0c |
fix(ci): merge-train --fast mirrors test:unit subdir allowlist (#7688)
The fast bucket fed every changed tests/unit file to node:test; vitest-only
subdirs (autoCombo) always fail under that runner and redden the train. The
classifier now carries the same {api,auth,…} allowlist as package.json's
test:unit (guarded by a sync test) and stops excluding ui/*.test.ts, which
test:unit does run.
|
||
|
|
f23d7770ec |
feat: zh-CN terminology glossary + consistency gate + normalization pass (#8038) (#8166)
One-shot 提供商->提供者 normalization across src/i18n/messages/zh-CN.json (679 substitutions) and bin/cli/locales/zh-CN.json (54), mirroring #8024's zh-TW pass. Adds a versioned terminology glossary (scripts/i18n/glossary/zh-CN.json), a protected-names list (scripts/i18n/glossary/protected-terms.json), and a pure-function consistency check (scripts/i18n/check-glossary-consistency.mjs, npm run i18n:check-glossary) wired into CI as the i18n-glossary-zhcn job. zh-CN added to the visual-QA harness default locales. Complements the existing parity (check-ui-keys-coverage.mjs) and ICU (validate_translation.py) gates without replacing them. |
||
|
|
98b1aa34b5 |
fix(sse): run compression pipeline per turn in Codex Responses WS bridge (#8052) (#8154)
The Codex Responses-over-WebSocket bridge bypassed the whole prompt-compression pipeline (and its analytics writes) that the HTTP/SSE path (chatCore.ts) runs on every request, via two gaps: 1. prepare() in codex-responses-ws/route.ts never called anything from open-sse/services/compression/* — it authenticated, injected memory, applied reasoning-routing, then went straight to executor.transformRequest(). 2. scripts/dev/responses-ws-proxy.mjs memoized the upstream connection in ensureUpstream() and only called the internal "prepare" action on the FIRST response.create of a WS session — every subsequent turn on a reused connection bypassed prepare() (and therefore compression) entirely. Fix: a new compression.ts module wires the core compression pipeline (settings resolution -> selectCompressionStrategy -> applyCompressionAsync -> compression_analytics/compression_engine_breakdown writes, reusing adaptBodyForCompression's existing Responses-API input[] adapter) into prepare(); responses-ws-proxy.mjs now re-runs prepare() (via a new shared runPrepare() helper) for every logical response.create turn on a reused connection, not just the first, without recreating the upstream socket. Regression test: tests/unit/responses-ws-proxy-compression-parity.test.ts proves the reused-connection bypass by execution (RED: 1 prepare call for 2 turns; GREEN after the fix: 2 prepare calls for 2 turns). |
||
|
|
287802cf86 |
fix: repair pre-existing red gates on the release/v3.8.49 tip (#8055)
* fix(dashboard): resolve Kimi banner casing collision + shrink frozen test file (release tip) - Rename src/app/(dashboard)/dashboard/kimiSponsorBanner.ts to kimiSponsorBannerGate.ts so it no longer differs from KimiSponsorBanner.tsx only by the first letter's case (breaks next build on case-insensitive filesystems). Updates the sole importer (KimiSponsorBanner.tsx) and the two tests that reference it. - Extract the 8 Kimi/Moonshot featured-ordering tests out of the frozen tests/unit/providers-page-utils.test.ts (grown 3 lines past its 1294 cap by #8039's rebrand-comment update) into a new sibling file tests/unit/providers-page-utils-kimi.test.ts. No assertions dropped; both files pass in full (24 + 8 = 32 tests). * fix(sse): register PromptQlExecutor in the executor registry (release tip) getExecutor("promptql") had no entry in open-sse/executors/index.ts, so it silently fell through to DefaultExecutor's provider fallback, which issues a raw fetch() and returns the bare upstream Response instead of the executor wrapper shape {response, url, headers, transformedBody}. The real PromptQlExecutor class (open-sse/executors/promptql.ts) already honors the contract correctly — it was just never wired into the registry. Fixes tests/unit/executor-web-cookie-sweep.test.ts "promptql executor returns wrapper shape". * fix(i18n): backfill 2220 missing pt-BR keys to restore en.json parity (release tip) pt-BR.json fell behind after #7935 restored +2220 keys into en.json and vi.json but left pt-BR.json unmodified. Translated all missing entries to Brazilian Portuguese, preserving ICU/interpolation placeholders and existing terminology, and merged them mirroring en.json's key order so the diff is additions-only (the small comma-only deletions are pure JSON reformatting from new sibling keys). * fix(providers): repair 4 pre-existing catalog/registry reds on release tip - providers-constants-split.test.ts: APIKEY_PROVIDERS grew 182->187 (PR #7887 added 5 free-tier providers: ainative/aion/sealion/routeway/nara). Verified no dup/loss (6-family partition sums exactly to 187) and updated the stale expected count + comment trail to match. - cline registry: added the missing minimax/minimax-m3 free OpenRouter entry (#3321) and fixed the neighbouring nemotron-3-ultra-550b-a55b entry, which carried a stray ":free" id suffix and an imprecise 1_000_000 contextLength instead of the 1_048_576 the test (and every sibling 1M-context entry in this catalog) expects. - promptqlModels.ts / registry/promptql/index.ts: PROMPTQL_FALLBACK_MODELS's minimax-m3 entry was missing supportsVision, and the registry mapping dropped it entirely (only id/name were passed through) — it was the sole minimax-m3 entry across the whole registry not flagged multimodal, despite every other provider (minimax, minimax-cn, ollama-cloud, trae, bazaarlink, clinepass, codebuddy-cn, opencode-zen/go, synthetic, huggingchat, lmarena) agreeing MiniMax-M3 supports vision. Added the field to the PromptQlModel type and threaded it through. - tests/snapshots/provider/translate-path.json: regenerated the golden via UPDATE_GOLDEN=1. Diffed old vs new — zero providers removed, 5 added (ainative/aion/nara/routeway/sealion, matching #7887), and the only changed entry (cline) reflects the already-merged #7914 ClinePass header protocol change (Cline/<version> User-Agent + X-Task-ID) that a prior narrow golden touch-up missed capturing. * fix(docs): repair docs-sync/env-sync/repo-contract gates (release tip) Six pre-existing reds on release/v3.8.49, all "repo drifted from its own documented contract": - check-docs-counts-sync: free-tier headline was stale (~1.4B/~2.0B) vs the live catalog (~1.53B steady / ~2.15B first month, 43 pools). Updated README.md and docs/reference/FREE_TIERS.md to the live numbers and added a v3.8.49 correction note explaining the pool-count delta (39->43, #7840). Also fixed a soft executors-count drift in ARCHITECTURE.md (84->86, 268->271 providers) while touching that line. - release-green-docs-drift-7253: docs/proxy-subscriptions.md referenced a fabricated migration filename (123_proxy_subscriptions.sql); the real file is 131_proxy_subscriptions.sql. Fixed all 3 occurrences. - check-env-doc-sync + issue-7793-env-doc-sync-repro: OMNIROUTE_DATA_DIR (DATA_DIR fallback alias read by open-sse/executors/promptql/threadSticky.ts) was undocumented. Added to .env.example and docs/reference/ENVIRONMENT.md. - check-db-rules: src/lib/db/proxySubscriptions.ts (#7299) is a db-internal split of proxies.ts (kept under the frozen file-size cap) whose one export is already re-exported via proxies.ts -> localDb.ts. Added it to INTENTIONALLY_INTERNAL with the same db-internal justification used for identical split modules (apiKeyColumnFallbacks, providerNodeSelect, webSessionDedup) rather than a redundant direct re-export from localDb.ts. - mcp-server-hollow-dist-deps: the sanity test expected better-sqlite3 among the MCP bundle's static top-level external imports. That's been stale since the pre-#7878 migration to a cascading SqliteAdapter driver factory (createRequire()-based lazy require, not a static import); better-sqlite3 already has its own native-asset copy guarantee in assembleStandalone.mjs, unrelated to this test's EXTRA_MODULE_ENTRIES concern. Updated the assertion to a still-genuinely-static external (zod) with a comment explaining the change. No production runtime behavior changed — docs, .env.example, and a checker allowlist/test-expectation only. * fix(dashboard): repair stale UI component-shape test assertions (release tip) Two pre-existing reds in the dashboard UI component-contract cluster were caused by test assertions that had gone stale after intentional, correct refactors — not by real defects in the components: - quota-pool-wizard-multi.test.ts: the step-3 preview assertion required the literal single-line substring "connectionIds.map((cid)". Prettier (100-char width, project config) legitimately breaks the connectionIds.map(...).filter(...) chain across lines because of the multi-line callback body, so the literal never matches. PoolWizard.tsx still builds previewByProvider correctly by mapping over connectionIds; updated the assertion to a regex that tolerates the line break. - v388-phase1-screen-fixes.test.ts: the shared Select placeholder-guard assertion required the literal "!children && placeholder". An earlier, intentional i18n commit changed the hardcoded "Select an option" default to a translated fallback (`placeholder ?? t("selectOption")`), which requires parens around the ?? expression for operator precedence. The guard behavior is unchanged (still gated on !children); updated the assertion to match the current, correct guard shape. Both fixes are read-only test-file changes; no production behavior changed. review-reviews-v3814-fixes.test.ts still has one pre-existing, unrelated red (LEDGER-4: minimax-m3 registry entries missing supportsVision) that requires editing the promptql provider registry/catalog — out of this cluster's scope, left untouched and reported separately. * fix(providers): reconcile cline catalog contradictions + deterministic golden (release tip) The first tip-green pass introduced 3 regressions caught by CI on sibling guard tests: - clinepass-provider + cline-catalog-models-3321 encoded OPPOSITE expectations of the same cline model list (minimax presence, nvidia :free suffix). Reference upstream (OpenRouter free lineup) confirms nvidia/nemotron-3-ultra-550b-a55b:free (with :free, 1M ctx) is correct, so restore that id and fix #3321's stale no-:free assertion; add minimax/minimax-m3 (the real #3321 gap) to clinepass-provider's list. - check-db-rules-classification froze INTENTIONALLY_INTERNAL at 35; proxySubscriptions was the intentional 36th entry — add it + bump the count. - provider-translate-path golden stored a LITERAL Cline/3.8.49: clineAuth resolves the version from APP_CONFIG.version (stable), but the golden sanitizer collapsed only process.env.npm_package_version (unset under `node`, set under `npm run`) — so the golden was shard-dependent. Resolve APP_VERSION from APP_CONFIG.version like clineAuth and regenerate; now Cline/<APP> normalizes identically in every shard. * fix(services): type execFile signal/killed in classifyError + ratchet dashboard baseline (release tip) Pre-existing base-red on the tip's Fast Quality Gates (dashboard-typecheck), missed in the first inventory: - src/lib/services/installers/utils.ts TS2339 — `err.signal` was read off a value typed as NodeJS.ErrnoException, which @types/node does not declare `signal`/`killed` on (those belong to execFile's ExecFileException). Widen classifyError's param to type both, and drop the now-redundant `(err as … { killed })` cast. - Ratchet config/quality/dashboard-typecheck-baseline.json down: 5 baselined errors were fixed by already-merged PRs but never ratcheted (OAuthModal TS2769 4→3 / TS2345 4→3, CliproxyModelMappingEditor TS2339, CompressionPreviewAccordion TS4104, MonacoEditor TS2307). Baseline now 254, matching live — gate exits 0. |