From ce55151ca5cae940bbec6e2e506ca1e0456bf082 Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza Date: Mon, 7 Sep 2026 08:35:29 -0300 Subject: [PATCH 01/33] chore(ci): guard commit identity in pre-commit to stop author misattribution (#12772) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore(ci): guard commit identity in pre-commit to stop author misattribution Two windows of commits in this checkout were signed with the wrong identity, both caused by an identity override left behind by an automated session: 2026-08-13..26 (name "Xiangzhe" + @backryun's e-mail, 237 commits) and 2026-08-29..09-02 (name "Markus Hartung" + the maintainer's e-mail, 59 commits). The .mailmap repairs the record after the fact; this gate stops the next window. The gate is opt-in per machine via omniroute.expectedName / expectedEmail — with no config it exits 0, so contributors who clone the repo are never affected. It blocks three things: a committer that is not this machine's identity (which is what BOTH windows looked like — in August neither the name nor the e-mail was the maintainer's, so checking only their e-mail would have missed it), an author carrying the maintainer's e-mail under someone else's name, and any address listed in omniroute.legacyEmail. Crediting a contributor with `git commit --author="Name "` keeps working, since the rule targets the committer and the maintainer's own address. * test(ci): isolate the identity gate's test from the ambient git config The "stays inert when the machine has not opted in" case read the real global config, so on a machine that HAS opted in (omniroute.expectedEmail set — the maintainer's own boxes, where this gate matters most) the gate correctly refused a synthetic contributor identity and the test failed. It only passed on a clean CI runner. Neutralising GIT_CONFIG_GLOBAL/GIT_CONFIG_SYSTEM makes the opt-in state come solely from what the test injects, so the suite is deterministic on both an opted-in and a clean machine. --- .husky/pre-commit | 1 + scripts/check/check-git-identity.sh | 75 +++++++++++++ tests/unit/check-git-identity.test.ts | 145 ++++++++++++++++++++++++++ 3 files changed, 221 insertions(+) create mode 100755 scripts/check/check-git-identity.sh create mode 100644 tests/unit/check-git-identity.test.ts diff --git a/.husky/pre-commit b/.husky/pre-commit index ec14ffcd28..268c9b5f7d 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -7,6 +7,7 @@ fi # Cheap, deterministic local gates (re-enabled). Slower checks (i18n drift, # openapi coverage/security-tiers, env-doc sync) run in CI to keep commits fast. +sh scripts/check/check-git-identity.sh npx lint-staged node scripts/check/check-docs-sync.mjs npm run check:any-budget:t11 diff --git a/scripts/check/check-git-identity.sh b/scripts/check/check-git-identity.sh new file mode 100755 index 0000000000..e0328ffb33 --- /dev/null +++ b/scripts/check/check-git-identity.sh @@ -0,0 +1,75 @@ +#!/usr/bin/env sh +# Guard de identidade de commit — previne misattribution de autoria. +# +# Contexto (ver .mailmap na raiz): este checkout já produziu DUAS janelas de +# commits com autoria trocada, ambas por um override de identidade deixado para +# trás por uma sessão automatizada: +# 1. 2026-08-13..26 — nome "Xiangzhe" + e-mail de @backryun (237 commits) +# 2. 2026-08-29..09-02 — nome "Markus Hartung" + e-mail do mantenedor (59 commits) +# +# Este gate NÃO impõe uma identidade única: contribuidores commitam normalmente +# com a sua, e creditar um contribuidor via `--author` continua funcionando. +# Ele bloqueia apenas as duas assinaturas do defeito: +# (a) um COMMITTER que não é a identidade desta máquina (pega ambas as janelas); +# (b) um AUTHOR com o e-mail do mantenedor sob o nome de outra pessoa; +# (c) um e-mail explicitamente aposentado (`omniroute.legacyEmail`). +# +# Ativação — opcional e por máquina; sem ela o gate é inerte: +# git config --global omniroute.expectedName "diegosouzapw" +# git config --global omniroute.expectedEmail "8016841+diegosouzapw@users.noreply.github.com" +# git config --global --add omniroute.legacyEmail "diegosouzapw@users.noreply.github.com" + +expected_name=$(git config --get omniroute.expectedName 2>/dev/null) +expected_email=$(git config --get omniroute.expectedEmail 2>/dev/null) +legacy_emails=$(git config --get-all omniroute.legacyEmail 2>/dev/null) + +# Sem configuração nesta máquina o gate não opina — contribuidores não são afetados. +[ -z "$expected_email" ] && exit 0 + +an=$(git var GIT_AUTHOR_IDENT 2>/dev/null | sed 's/ <.*//') +ae=$(git var GIT_AUTHOR_IDENT 2>/dev/null | sed 's/.*.*//') +cn=$(git var GIT_COMMITTER_IDENT 2>/dev/null | sed 's/ <.*//') +ce=$(git var GIT_COMMITTER_IDENT 2>/dev/null | sed 's/.*.*//') + +fail=0 + +# (a) O COMMITTER é quem executa o commit — nesta máquina, sempre o dono dela. +# Um override de identidade esquecido por uma sessão aparece exatamente aqui, +# e foi o que passou despercebido nas duas janelas: em agosto NEM o nome NEM +# o e-mail eram do mantenedor, então checar só o e-mail dele não bastaria. +if [ "$ce" != "$expected_email" ] || { [ -n "$expected_name" ] && [ "$cn" != "$expected_name" ]; }; then + echo "🛑 COMMITTER não é a identidade desta máquina: $cn <$ce>" >&2 + fail=1 +fi + +# (b) O AUTHOR pode ser um contribuidor (crédito via --author), mas nunca pode +# carregar o e-mail do mantenedor sob o nome de outra pessoa. +if [ -n "$expected_name" ] && [ "$ae" = "$expected_email" ] && [ "$an" != "$expected_name" ]; then + echo "🛑 AUTHOR combina o e-mail do mantenedor com outro nome: $an <$ae>" >&2 + fail=1 +fi + +# (c) e-mails aposentados que já causaram misattribution. +for legacy in $legacy_emails; do + if [ "$ae" = "$legacy" ]; then + echo "🛑 AUTHOR usa e-mail aposentado: $an <$ae>" >&2 + fail=1 + fi + if [ "$ce" = "$legacy" ]; then + echo "🛑 COMMITTER usa e-mail aposentado: $cn <$ce>" >&2 + fail=1 + fi +done + +[ "$fail" = "0" ] && exit 0 + +cat >&2 < + Corrija com: + git config --global user.name "$expected_name" + git config --global user.email "$expected_email" + Para creditar um contribuidor, use o E-MAIL DELE (nunca o seu): + git commit --author="Nome " +MSG +exit 1 diff --git a/tests/unit/check-git-identity.test.ts b/tests/unit/check-git-identity.test.ts new file mode 100644 index 0000000000..fb31a2b247 --- /dev/null +++ b/tests/unit/check-git-identity.test.ts @@ -0,0 +1,145 @@ +// Guards the commit-identity gate (scripts/check/check-git-identity.sh): a stale +// identity override left behind by an automated session must not be able to sign +// commits with the maintainer's e-mail under someone else's name. +// +// Two real incidents motivate this (see .mailmap at the repo root): +// 1. 2026-08-13..26 — name "Xiangzhe" + @backryun's e-mail (237 commits) +// 2. 2026-08-29..09-02 — name "Markus Hartung" + the maintainer's e-mail (59 commits) +import { test } from "node:test"; +import assert from "node:assert/strict"; +import { spawnSync } from "node:child_process"; +import { fileURLToPath } from "node:url"; + +const SCRIPT_PATH = fileURLToPath( + new URL("../../scripts/check/check-git-identity.sh", import.meta.url) +); + +const OWNER_NAME = "diegosouzapw"; +const OWNER_EMAIL = "8016841+diegosouzapw@users.noreply.github.com"; +const LEGACY_EMAIL = "diegosouzapw@users.noreply.github.com"; + +/** Runs the gate with a synthetic git identity. `configured` toggles the opt-in. */ +function runGate( + identity: { + authorName: string; + authorEmail: string; + committerName: string; + committerEmail: string; + }, + configured = true +) { + const env: Record = { + ...process.env, + // The gate reads its opt-in from git config, so the ambient global/system + // config has to be neutralised: on a machine that HAS opted in (the + // maintainer's own boxes) the "not opted in" case is otherwise impossible + // to simulate and the test fails there while passing on a clean CI runner. + GIT_CONFIG_GLOBAL: "/dev/null", + GIT_CONFIG_SYSTEM: "/dev/null", + GIT_AUTHOR_NAME: identity.authorName, + GIT_AUTHOR_EMAIL: identity.authorEmail, + GIT_COMMITTER_NAME: identity.committerName, + GIT_COMMITTER_EMAIL: identity.committerEmail, + }; + if (configured) { + // GIT_CONFIG_* is inherited by every child `git` call the script makes, + // unlike `git -c`, which would only apply to a single invocation. + Object.assign(env, { + GIT_CONFIG_COUNT: "3", + GIT_CONFIG_KEY_0: "omniroute.expectedName", + GIT_CONFIG_VALUE_0: OWNER_NAME, + GIT_CONFIG_KEY_1: "omniroute.expectedEmail", + GIT_CONFIG_VALUE_1: OWNER_EMAIL, + GIT_CONFIG_KEY_2: "omniroute.legacyEmail", + GIT_CONFIG_VALUE_2: LEGACY_EMAIL, + }); + } + const r = spawnSync("sh", [SCRIPT_PATH], { env, encoding: "utf8" }); + return { status: r.status, stderr: r.stderr ?? "" }; +} + +const owner = { + authorName: OWNER_NAME, + authorEmail: OWNER_EMAIL, + committerName: OWNER_NAME, + committerEmail: OWNER_EMAIL, +}; + +test("stays inert when the machine has not opted in", () => { + // A contributor who cloned the repo must never be blocked by the maintainer's gate. + const r = runGate( + { + authorName: "Some Contributor", + authorEmail: "someone@example.com", + committerName: "Some Contributor", + committerEmail: "someone@example.com", + }, + false + ); + assert.equal(r.status, 0); +}); + +test("accepts the maintainer's own identity", () => { + assert.equal(runGate(owner).status, 0); +}); + +test("rejects the maintainer's e-mail carrying another person's name", () => { + const r = runGate({ + authorName: "Markus Hartung", + authorEmail: OWNER_EMAIL, + committerName: "Markus Hartung", + committerEmail: OWNER_EMAIL, + }); + assert.equal(r.status, 1); + assert.match(r.stderr, /AUTHOR combina o e-mail do mantenedor/); + assert.match(r.stderr, /COMMITTER não é a identidade desta máquina/); +}); + +test("rejects the retired legacy e-mail — the 2026-08-29 window's exact signature", () => { + const r = runGate({ + authorName: "Markus Hartung", + authorEmail: LEGACY_EMAIL, + committerName: "Markus Hartung", + committerEmail: LEGACY_EMAIL, + }); + assert.equal(r.status, 1); + assert.match(r.stderr, /e-mail aposentado/); +}); + +test("allows crediting a contributor through their OWN e-mail", () => { + // `git commit --author="Name "` is the sanctioned credit path and + // must keep working — the gate targets the maintainer's e-mail, not the name. + const r = runGate({ + authorName: "Markus Hartung", + authorEmail: "mail@hartmark.se", + committerName: OWNER_NAME, + committerEmail: OWNER_EMAIL, + }); + assert.equal(r.status, 0); +}); + +test("rejects a committer that is not this machine's identity", () => { + // The committer is whoever RAN the commit, so on the maintainer's machine it is + // always them. A forgotten identity override surfaces here first. + const r = runGate({ + authorName: OWNER_NAME, + authorEmail: OWNER_EMAIL, + committerName: "Bob.Hou", + committerEmail: "houminxi@gmail.com", + }); + assert.equal(r.status, 1); + assert.match(r.stderr, /COMMITTER não é a identidade desta máquina/); +}); + +test("rejects the 2026-08-13 window: neither name nor e-mail is the maintainer's", () => { + // Name "Xiangzhe" (@xz-dev) + @backryun's e-mail. Checking only the maintainer's + // e-mail would MISS this window entirely — hence the committer-identity rule. + const r = runGate({ + authorName: "Xiangzhe", + authorEmail: "bakryun0718@proton.me", + committerName: "Xiangzhe", + committerEmail: "bakryun0718@proton.me", + }); + assert.equal(r.status, 1); + assert.match(r.stderr, /COMMITTER não é a identidade desta máquina/); +}); From d857bd053a44598a33fd5b6660556f2e169415c6 Mon Sep 17 00:00:00 2001 From: "Bob.Hou" Date: Mon, 7 Sep 2026 07:55:45 -0400 Subject: [PATCH 02/33] fix(glm): drop extra 16th arg to SSE transform helper (#12770) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Validado numa worktree combinada com as 16 PRs desta leva sobre `release/v3.8.51`: typecheck:core limpo, check-file-size e check-changelog-integrity OK, complexity 2788/3218 e cognitive 1261/1437 (ambos sob a baseline), ESLint 0 erros nos 152 arquivos alterados, 771 testes unitários focados, 49 de integração e a suíte vitest:ui completa (2149) verdes. O `65536` era o 16º posicional de um helper com 15 parâmetros — `TS2554` vivo no tip (`open-sse/executors/glm.ts:244`, confirmado aqui antes do board). O teste de guarda de aridade é o que impede a reincidência: ele checa a assinatura do helper e o call site, não o comportamento, que é exatamente onde o erro morava. Obrigado por isolar isso do #12711 em vez de deixar o `glm.ts` viajar junto com pin/combo-split/moonshot. --- changelog.d/fixes/glm-sse-transform-arity.md | 1 + open-sse/executors/glm.ts | 9 ++-- tests/unit/glm-sse-transform-arity.test.ts | 51 ++++++++++++++++++++ 3 files changed, 55 insertions(+), 6 deletions(-) create mode 100644 changelog.d/fixes/glm-sse-transform-arity.md create mode 100644 tests/unit/glm-sse-transform-arity.test.ts diff --git a/changelog.d/fixes/glm-sse-transform-arity.md b/changelog.d/fixes/glm-sse-transform-arity.md new file mode 100644 index 0000000000..e8bbf7496d --- /dev/null +++ b/changelog.d/fixes/glm-sse-transform-arity.md @@ -0,0 +1 @@ +- **fix(glm):** drop the extra 16th argument to `createSSETransformStreamWithLogger` that TypeScript rejected (TS2554) and that never reached TransformStream diff --git a/open-sse/executors/glm.ts b/open-sse/executors/glm.ts index ef9f370669..c275e6f290 100644 --- a/open-sse/executors/glm.ts +++ b/open-sse/executors/glm.ts @@ -223,8 +223,8 @@ export function translateSseResponse( suppressThinkClose: boolean = false ): Response { if (!response.body) return response; - // GLM is a high-throughput provider — use a larger stream buffer (64KB) to - // keep provider → client pacing ahead of the model's token emission rate. + // Helper has 15 parameters; a 16th positional (65536) was a TS2554 and + // never reached TransformStream. highWaterMark stays at the helper default. const transform = createSSETransformStreamWithLogger( FORMATS.CLAUDE, FORMATS.OPENAI, @@ -238,10 +238,7 @@ export function translateSseResponse( null, null, false, - suppressThinkClose, - undefined, - undefined, - 65536 + suppressThinkClose ); const headers = cloneHeaders(response.headers); headers.set("content-type", "text/event-stream"); diff --git a/tests/unit/glm-sse-transform-arity.test.ts b/tests/unit/glm-sse-transform-arity.test.ts new file mode 100644 index 0000000000..7fd3807182 --- /dev/null +++ b/tests/unit/glm-sse-transform-arity.test.ts @@ -0,0 +1,51 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { readFileSync } from "node:fs"; +import { fileURLToPath } from "node:url"; +import { dirname, join } from "node:path"; + +/** + * GLM's translateSseResponse used to pass a 16th positional (65536) to + * createSSETransformStreamWithLogger. The helper only has 15 parameters + * (last is requestToolIdentityMap) — tsc reports TS2554 and the number + * never reached TransformStream. + * + * Guard the call site in source: no 65536, last arg is suppressThinkClose. + */ +const root = join(dirname(fileURLToPath(import.meta.url)), "..", ".."); + +function extractParens(src: string, openAt: number): string { + let i = openAt + 1; + let depth = 1; + while (i < src.length && depth > 0) { + const ch = src[i]; + if (ch === "(") depth += 1; + else if (ch === ")") depth -= 1; + i += 1; + } + return src.slice(openAt, i); +} + +test("createSSETransformStreamWithLogger has no highWaterMark slot", () => { + const src = readFileSync(join(root, "open-sse", "utils", "stream.ts"), "utf8"); + const needle = "export function createSSETransformStreamWithLogger("; + const start = src.indexOf(needle); + assert.ok(start >= 0); + const header = extractParens(src, start + needle.length - 1); + assert.equal(/highWaterMark/.test(header), false, header); + assert.match(header, /requestToolIdentityMap/); + assert.match(header, /suppressThinkClose/); +}); + +test("GLM translateSseResponse does not pass a 16th positional to the stream helper", () => { + const src = readFileSync(join(root, "open-sse", "executors", "glm.ts"), "utf8"); + const fnStart = src.indexOf("export function translateSseResponse("); + assert.ok(fnStart >= 0); + const fnEnd = src.indexOf("\nexport class GlmExecutor", fnStart); + const body = src.slice(fnStart, fnEnd); + const callAt = body.indexOf("createSSETransformStreamWithLogger("); + assert.ok(callAt >= 0); + const call = extractParens(body, callAt + "createSSETransformStreamWithLogger".length); + assert.equal(/65536/.test(call), false, `dead 16th arg still present:\n${call}`); + assert.match(call, /suppressThinkClose\s*\)\s*$/); +}); From 25bc16d87eaaa7e29dfa42ccf41a2c3b75a9ff7a Mon Sep 17 00:00:00 2001 From: "Bob.Hou" Date: Mon, 7 Sep 2026 07:56:36 -0400 Subject: [PATCH 03/33] fix(dashboard): batch delete no longer toasts failure after success (#12711) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Validado numa worktree combinada com as 16 PRs desta leva sobre `release/v3.8.51`: typecheck:core limpo, check-file-size e check-changelog-integrity OK, complexity 2788/3218 e cognitive 1261/1437 (ambos sob a baseline), ESLint 0 erros nos 152 arquivos alterados, 771 testes unitários focados, 49 de integração e a suíte vitest:ui completa (2149) verdes. Além do bug do toast, esta PR foi a que derrubou os três base-reds vivos do tip: o fragmento `changelog.d/fixes/reset-aware-model-family.md` sem o `- ` inicial, o registro do `tests/unit/reset-aware-request-scope-12600.test.ts` no `stryker.conf.json` e o `TS2554` do glm. O `check-changelog-integrity` voltou a passar aqui por causa dela. O diagnóstico do MouseEvent é o que dá o valor: `onConfirm` chegava como handler de clique nativo e `handleBatchDeleteConfirm` tratava qualquer primeiro argumento truthy como callback. O cinto (`typeof`) e o suspensório (o wrap no ConfirmModal) juntos estão certos — só um dos dois deixaria a porta aberta para o próximo caller. --- .../fixes/12711-batch-delete-click-event.md | 2 + changelog.d/fixes/reset-aware-model-family.md | 2 +- .../[id]/hooks/useProviderConnections.ts | 5 +- src/shared/components/Modal.tsx | 2 +- stryker.conf.json | 1 + .../unit/ui/batch-delete-click-event.test.tsx | 159 ++++++++++++++++++ 6 files changed, 167 insertions(+), 4 deletions(-) create mode 100644 changelog.d/fixes/12711-batch-delete-click-event.md create mode 100644 tests/unit/ui/batch-delete-click-event.test.tsx diff --git a/changelog.d/fixes/12711-batch-delete-click-event.md b/changelog.d/fixes/12711-batch-delete-click-event.md new file mode 100644 index 0000000000..617f97788d --- /dev/null +++ b/changelog.d/fixes/12711-batch-delete-click-event.md @@ -0,0 +1,2 @@ +- **fix(dashboard):** batch-deleting provider keys no longer toasts failure after a successful delete when the confirm button's click event is forwarded as `onAfter` ([#12711](https://github.com/diegosouzapw/OmniRoute/pull/12711)) +- **fix(glm):** drop the extra 16th argument to `createSSETransformStreamWithLogger` that TypeScript rejected (TS2554) and that never reached the TransformStream diff --git a/changelog.d/fixes/reset-aware-model-family.md b/changelog.d/fixes/reset-aware-model-family.md index 75b65e7613..09fa663182 100644 --- a/changelog.d/fixes/reset-aware-model-family.md +++ b/changelog.d/fixes/reset-aware-model-family.md @@ -1 +1 @@ -Keep Antigravity Gemini usable when the same connection's Claude weekly quota is empty; generic quota cache stays per-connection for every other provider. +- Keep Antigravity Gemini usable when the same connection's Claude weekly quota is empty; generic quota cache stays per-connection for every other provider. diff --git a/src/app/(dashboard)/dashboard/providers/[id]/hooks/useProviderConnections.ts b/src/app/(dashboard)/dashboard/providers/[id]/hooks/useProviderConnections.ts index 8caa9f7aac..6ae2dfe5c8 100644 --- a/src/app/(dashboard)/dashboard/providers/[id]/hooks/useProviderConnections.ts +++ b/src/app/(dashboard)/dashboard/providers/[id]/hooks/useProviderConnections.ts @@ -248,7 +248,7 @@ export interface UseProviderConnectionsReturn { export function useProviderConnections( providerId: string, isCompatible: boolean, - isSearchProvider: boolean + _isSearchProvider: boolean ): UseProviderConnectionsReturn { const t = useTranslations("providers"); const notify = useNotificationStore(); @@ -844,7 +844,8 @@ export function useProviderConnections( setSelectedIds(new Set()); await fetchConnections(); notify.success(t("batchDeleteSuccess", { count })); - if (onAfter) await onAfter(); + // ConfirmModal's onClick forwards a MouseEvent; only a real callback runs. + if (typeof onAfter === "function") await onAfter(); } else { const data = await res.json(); notify.error(data.error || providerText(t, "batchDeleteFailed", "Batch delete failed")); diff --git a/src/shared/components/Modal.tsx b/src/shared/components/Modal.tsx index 8ecf9bb87b..3617d7d73f 100644 --- a/src/shared/components/Modal.tsx +++ b/src/shared/components/Modal.tsx @@ -275,7 +275,7 @@ export function ConfirmModal({ - diff --git a/stryker.conf.json b/stryker.conf.json index c8ff964e77..e28a90fa06 100644 --- a/stryker.conf.json +++ b/stryker.conf.json @@ -349,6 +349,7 @@ "tests/unit/repro-antigravity-404-family-cooldown-hijack.test.ts", "tests/unit/repro-combo-persisted-cooldown-preskip.test.ts", "tests/unit/repro-glm-iso-reset-24h-cap.test.ts", + "tests/unit/reset-aware-request-scope-12600.test.ts", "tests/unit/resilience-connections.test.ts", "tests/unit/responses-handler.test.ts", "tests/unit/responses-passthrough-openai-compatible.test.ts", diff --git a/tests/unit/ui/batch-delete-click-event.test.tsx b/tests/unit/ui/batch-delete-click-event.test.tsx new file mode 100644 index 0000000000..d599dbc605 --- /dev/null +++ b/tests/unit/ui/batch-delete-click-event.test.tsx @@ -0,0 +1,159 @@ +// @vitest-environment jsdom +/** + * ConfirmModal wires onConfirm to