fix(ci): repair release regressions exposed by clean runs (#7812)

This commit is contained in:
backryun
2026-07-20 22:08:25 +09:00
committed by GitHub
parent adbcd2c8bc
commit 34aefcf4e2
16 changed files with 123 additions and 35 deletions

View File

@@ -0,0 +1 @@
- **fix(runtime):** Apply refreshed request-queue settings, restore inter-batch health-check jitter, and invalidate database read caches when the SQLite singleton resets; refresh stale CI baselines and fixtures exposed by clean release runs ([#7812](https://github.com/diegosouzapw/OmniRoute/pull/7812) by [@backryun](https://github.com/backryun)).

View File

@@ -343,7 +343,10 @@ export async function initializeRateLimits() {
try {
const { getCachedProviderConnections, getSettings } = await import("@/lib/localDb");
const [connections, settings] = await Promise.all([getCachedProviderConnections(), getSettings()]);
const [connections, settings] = await Promise.all([
getCachedProviderConnections(),
getSettings(),
]);
const resilience = resolveResilienceSettings(settings);
currentRequestQueueSettings = { ...resilience.requestQueue };
// #6846 Phase 1: operator overrides for header-less providers' static RPM
@@ -383,6 +386,7 @@ export async function initializeRateLimits() {
}
export async function applyRequestQueueSettings(nextSettings: RequestQueueSettings) {
currentRequestQueueSettings = { ...nextSettings };
const { getCachedProviderConnections } = await import("@/lib/localDb");
const connections = await getCachedProviderConnections();
reconcileEnabledConnections(connections as unknown[], currentRequestQueueSettings);

View File

@@ -51,8 +51,8 @@
"ccr": {
"tasks": {
"prose": 129,
"tool-output": 56,
"json": 14
"tool-output": 81,
"json": 64
}
}
}

View File

@@ -7243,6 +7243,7 @@
"restartServerWithNewPassword": "Restart the server - it will use the new password",
"backToLogin": "Back to Login",
"forgotPassword": "Forgot password?",
"continueWithOidc": "Continue with OIDC",
"defaultPasswordHint": "Default password: CHANGEME (unless INITIAL_PASSWORD was set)",
"Authorization": "Authorization",
"Content-Disposition": "Content-Disposition",

View File

@@ -7243,6 +7243,7 @@
"restartServerWithNewPassword": "Reinicie o servidor - ele usará a nova senha",
"backToLogin": "Voltar para o Login",
"forgotPassword": "Esqueceu a senha?",
"continueWithOidc": "Continuar com OIDC",
"defaultPasswordHint": "Default password: CHANGEME (unless INITIAL_PASSWORD was set)",
"Authorization": "Autorização",
"Content-Disposition": "Disposição de conteúdo",

View File

@@ -7275,6 +7275,7 @@
"restartServerWithNewPassword": "Khởi động lại máy chủ - máy chủ sẽ sử dụng mật khẩu mới",
"backToLogin": "Quay lại Đăng nhập",
"forgotPassword": "Quên mật khẩu?",
"continueWithOidc": "Tiếp tục với OIDC",
"defaultPasswordHint": "Mật khẩu mặc định: CHANGEME (trừ khi INITIAL_PASSWORD đã được đặt)",
"Authorization": "Authorization",
"Content-Disposition": "Content-Disposition",

View File

@@ -814,7 +814,6 @@ function offloadLegacyCallLogDetails(db: SqliteDatabase) {
}
}
function shouldRunStartupDbHealthCheck(): boolean {
if (process.env.OMNIROUTE_FORCE_DB_HEALTHCHECK === "1") return true;
return !isAutomatedTestProcess();
@@ -1313,6 +1312,10 @@ export function closeDbInstance(options?: { checkpointMode?: CheckpointMode | nu
*/
export function resetDbInstance() {
closeDbInstance();
// Read caches outlive the SQLite singleton. A reset swaps the backing
// database, so retaining cached rows can leak the previous database's
// connections/settings into the newly opened instance (tests and restore).
invalidateDbCache();
}
// ──────────────── Runtime Driver Info ────────────────

View File

@@ -350,7 +350,10 @@ export async function sweep() {
// prevent sustained bursting while reducing total sweep duration.
if (batchEnd < total) {
if (staggerMs > 0) {
await new Promise((resolve) => setTimeout(resolve, staggerMs));
const jitterMin = parseInt(process.env.HEALTHCHECK_JITTER_MIN_MS || "500", 10);
const jitterMax = parseInt(process.env.HEALTHCHECK_JITTER_MAX_MS || "5000", 10);
const jitter = jitterMin + Math.random() * Math.max(0, jitterMax - jitterMin);
await new Promise((resolve) => setTimeout(resolve, staggerMs + jitter));
}
// Yield a microtask so the event loop can service pending I/O
// (DB contention, network responses) before the next batch starts.

View File

@@ -1876,6 +1876,54 @@
"stream": "https://gemini.google.com/app"
}
},
"ghe-copilot": {
"format": "openai",
"headers": {
"apiKey": {
"Accept": "text/event-stream",
"Authorization": "Bearer <TOK>",
"Content-Type": "application/json",
"X-Initiator": "user",
"copilot-integration-id": "vscode-chat",
"editor-plugin-version": "copilot-chat/0.54.0",
"editor-version": "vscode/1.126.0",
"openai-intent": "conversation-panel",
"user-agent": "GitHubCopilotChat/0.54.0",
"x-github-api-version": "2026-06-01",
"x-vscode-user-agent-library-version": "electron-fetch"
},
"nonStream": {
"Accept": "application/json",
"Authorization": "Bearer <TOK>",
"Content-Type": "application/json",
"X-Initiator": "user",
"copilot-integration-id": "vscode-chat",
"editor-plugin-version": "copilot-chat/0.54.0",
"editor-version": "vscode/1.126.0",
"openai-intent": "conversation-panel",
"user-agent": "GitHubCopilotChat/0.54.0",
"x-github-api-version": "2026-06-01",
"x-vscode-user-agent-library-version": "electron-fetch"
},
"oauth": {
"Accept": "text/event-stream",
"Authorization": "Bearer <TOK>",
"Content-Type": "application/json",
"X-Initiator": "user",
"copilot-integration-id": "vscode-chat",
"editor-plugin-version": "copilot-chat/0.54.0",
"editor-version": "vscode/1.126.0",
"openai-intent": "conversation-panel",
"user-agent": "GitHubCopilotChat/0.54.0",
"x-github-api-version": "2026-06-01",
"x-vscode-user-agent-library-version": "electron-fetch"
}
},
"url": {
"nonStream": "https://api.githubcopilot.com/chat/completions",
"stream": "https://api.githubcopilot.com/chat/completions"
}
},
"gigachat": {
"format": "openai",
"headers": {

View File

@@ -172,7 +172,7 @@ test("connection with both apiKey and refreshToken: refresh path is tried", asyn
);
});
test("sweep processes all connections with stagger + jitter delay", async () => {
test("sweep processes all connections with inter-batch stagger + jitter delay", async () => {
await resetStorage();
// Create multiple connections; set isActive=false so checkConnection
@@ -201,6 +201,16 @@ test("sweep processes all connections with stagger + jitter delay", async () =>
refreshToken: "test-rt",
isActive: false,
});
for (let i = 4; i <= 21; i++) {
await providersDb.createProviderConnection({
provider: "openai",
authType: "oauth",
name: `Stagger Test ${i}`,
email: `t${i}@example.com`,
refreshToken: "test-rt",
isActive: false,
});
}
// Clear any health-check skip config
const origSetting = process.env.HEALTHCHECK_SKIP_PROVIDERS;
@@ -219,13 +229,13 @@ test("sweep processes all connections with stagger + jitter delay", async () =>
await sweep();
const elapsed = Date.now() - start;
// 3 connections -> 2 gaps. Each gap waits
// 21 connections -> two batches (20 + 1) and one inter-batch gap. The gap waits
// HEALTHCHECK_STAGGER_MS (1ms) + HEALTHCHECK_JITTER_MIN_MS (100ms) = 101ms.
// Without jitter: 2 * 1ms = ~2ms. With jitter: 2 * 101ms = ~202ms.
// Without jitter the only gap would be ~1ms.
// The assert proves the jitter is actually applied.
assert.ok(
elapsed >= 190,
`sweep took ${elapsed}ms — expected >= 190ms with jitter applied (jitter-free baseline would be ~2ms)`
elapsed >= 90,
`sweep took ${elapsed}ms — expected >= 90ms with jitter applied (jitter-free baseline would be ~1ms)`
);
// Verify all connections still exist (sweep didn't error out mid-loop)

View File

@@ -171,8 +171,8 @@ test("custom provider auth lookup search pool maps alias prefixes to internal pr
);
assert.match(
authSrc,
/getProviderNodes\(/,
"auth lookup should read provider_nodes to map custom prefixes like 78code/micu back to internal provider ids"
/getCachedProviderNodes\(/,
"auth lookup should read cached provider_nodes to map custom prefixes like 78code/micu back to internal provider ids"
);
assert.match(
authSrc,

View File

@@ -142,6 +142,21 @@ test("getCachedProviderConnections caches only the unfiltered query", async () =
assert.equal((await readCache.getCachedProviderConnections()).length, 2);
});
test("resetDbInstance invalidates provider connection read caches", async () => {
await providersDb.createProviderConnection({
provider: "openai",
authType: "apikey",
name: "Reset Cache Test",
apiKey: "sk-reset-cache",
});
assert.equal((await providersDb.getProviderConnections()).length, 1);
await resetStorage();
assert.equal((await providersDb.getProviderConnections()).length, 0);
});
test("cached LKGP values refresh only after the specific key is invalidated", async () => {
const readCache = await importFresh("src/lib/db/readCache.ts");
const db = core.getDbInstance();

View File

@@ -32,13 +32,14 @@ test("resolveTarget — unknown host returns null", () => {
assert.equal(resolveTarget(""), null);
});
test("ALL_TARGETS — registers exactly nine targets", () => {
assert.equal(ALL_TARGETS.length, 9);
test("ALL_TARGETS — registers exactly ten targets", () => {
assert.equal(ALL_TARGETS.length, 10);
const ids = new Set(ALL_TARGETS.map((t) => t.id));
assert.equal(ids.size, 9);
assert.equal(ids.size, 10);
assert.ok(ids.has("antigravity"));
assert.ok(ids.has("kiro"));
assert.ok(ids.has("copilot"));
assert.ok(ids.has("ghe-copilot"));
assert.ok(ids.has("codex"));
assert.ok(ids.has("cursor"));
assert.ok(ids.has("zed"));

View File

@@ -6,6 +6,8 @@ import {
hasFixedQuotaOrder,
} from "@/app/(dashboard)/dashboard/usage/components/ProviderLimits/quotaParsing";
const quotaName = (quota: { name: string }) => quota.name;
test("#7764 sanity: codex has a fixed quota order (session, weekly)", () => {
assert.equal(hasFixedQuotaOrder("codex"), true);
});
@@ -25,16 +27,10 @@ test("#7764: topQuotas() (collapsed card order) respects hasFixedQuotaOrder inst
};
const parsedA = parseQuotaData("codex", rawA);
const parsedB = parseQuotaData("codex", rawB);
assert.deepEqual(
parsedA.map((q: any) => q.name),
["session", "weekly"]
);
assert.deepEqual(
parsedB.map((q: any) => q.name),
["session", "weekly"]
);
const renderedA = topQuotas(parsedA, 3, "codex").map((q: any) => q.name);
const renderedB = topQuotas(parsedB, 3, "codex").map((q: any) => q.name);
assert.deepEqual(parsedA.map(quotaName), ["session", "weekly"]);
assert.deepEqual(parsedB.map(quotaName), ["session", "weekly"]);
const renderedA = topQuotas(parsedA, 3, "codex").map(quotaName);
const renderedB = topQuotas(parsedB, 3, "codex").map(quotaName);
assert.deepEqual(renderedA, ["session", "weekly"]);
assert.deepEqual(renderedB, ["session", "weekly"]);
});
@@ -45,6 +41,6 @@ test("#7764: providers WITHOUT a fixed order still sort worst-status-first (no r
{ name: "beta", used: 95, total: 100, remainingPercentage: 5 },
{ name: "gamma", used: 50, total: 100, remainingPercentage: 50 },
];
const rendered = topQuotas(quotas, 3, "some-other-provider").map((q: any) => q.name);
const rendered = topQuotas(quotas, 3, "some-other-provider").map(quotaName);
assert.deepEqual(rendered, ["beta", "gamma", "alpha"]);
});

View File

@@ -1202,7 +1202,7 @@ test("embeddings route handles responses provider nodes, invalid local nodes, an
);
assert.equal(localResponse.status, 200);
assert.equal(fetchCalls[0].url, "http://localhost:7790/v1/embeddings");
localDb.invalidateDbCache("nodes");
const remoteResponse = await withPrepareOverride(
"SELECT * FROM provider_nodes",
({ statement }) =>

View File

@@ -21,7 +21,7 @@
* and tests/unit/rule12-error-sanitization-sweep.test.ts). Connections are
* created with healthCheckInterval: 0 so checkConnection() is a fast no-op
* (returns before any network/DB-write work), letting the test control
* timing purely via the inter-connection HEALTHCHECK_STAGGER_MS delay.
* timing purely via the inter-batch HEALTHCHECK_STAGGER_MS delay.
*/
import test from "node:test";
import assert from "node:assert/strict";
@@ -61,13 +61,15 @@ async function resetStorage() {
test.after(async () => {
core.resetDbInstance();
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
delete process.env.HEALTHCHECK_STAGGER_MS;
delete process.env.HEALTHCHECK_JITTER_MIN_MS;
delete process.env.HEALTHCHECK_JITTER_MAX_MS;
});
/** Read the shared health-check global state's `sweeping` flag. */
function isSweeping(): boolean {
const hc = (globalThis as Record<string, unknown>).__omnirouteTokenHC as
| { sweeping?: boolean }
| undefined;
{ sweeping?: boolean } | undefined;
return hc?.sweeping ?? false;
}
@@ -87,10 +89,12 @@ async function createNoOpOauthConnections(count: number, namePrefix: string) {
test("sweep() skips re-entrant calls while a previous sweep is still in flight", async () => {
await resetStorage();
process.env.HEALTHCHECK_STAGGER_MS = "300";
await createNoOpOauthConnections(4, "reentrancy");
process.env.HEALTHCHECK_JITTER_MIN_MS = "0";
process.env.HEALTHCHECK_JITTER_MAX_MS = "0";
await createNoOpOauthConnections(21, "reentrancy");
const seeded = await providersDb.getProviderConnections({ authType: "oauth" });
assert.equal(seeded.length, 4, "precondition: 4 oauth connections exist");
assert.equal(seeded.length, 21, "precondition: 21 oauth connections exist");
assert.equal(isSweeping(), false, "precondition: no sweep in flight yet");
const first = sweep();
@@ -103,9 +107,9 @@ test("sweep() skips re-entrant calls while a previous sweep is still in flight",
await sweep(); // second, concurrent call — must be skipped by the guard
const elapsedMs = Date.now() - start;
// With 4 connections and a 300ms inter-connection stagger, a REAL second
// sweep would take >= 300ms just to clear a single stagger gap (and up to
// ~900ms for all three gaps). The guard must make this call return near-
// With 21 connections and a 300ms inter-batch stagger, a REAL second
// sweep would take >= 300ms to clear the single batch gap. The guard must
// make this call return near-
// instantly instead of re-entering the loop.
assert.ok(
elapsedMs < 250,