diff --git a/.env.example b/.env.example
index 7d92b7cad5..a2156902a6 100644
--- a/.env.example
+++ b/.env.example
@@ -65,6 +65,12 @@ INITIAL_PASSWORD=CHANGEME
# OMNIROUTE_RELEASE_REF=origin/main
# OMNIROUTE_ALLOW_CANARY_BUILD=1
+# Build-phase signal (#10060). Set to 1 by scripts/build/build-next-isolated.mjs and
+# inherited by every spawned build worker so the DB layer returns a no-op stub instead
+# of loading the native better-sqlite3 addon (which aborts the worker on exit).
+# Never set this for the running server. Used by: src/lib/buildPhase.ts, src/lib/db/core.ts
+# OMNIROUTE_BUILDING=1
+
# Encryption key for SQLite database encryption at rest.
# Used by: src/lib/db/encryption.ts — encrypts the entire SQLite database.
# Generate: openssl rand -hex 32 | Leave empty to disable DB encryption.
diff --git a/docs/reference/ENVIRONMENT.md b/docs/reference/ENVIRONMENT.md
index 2681ba5478..e9ef0df9c2 100644
--- a/docs/reference/ENVIRONMENT.md
+++ b/docs/reference/ENVIRONMENT.md
@@ -88,6 +88,7 @@ OmniRoute uses **SQLite** (via `better-sqlite3`) for all persistence. These vari
| `OMNIROUTE_RELEASE_REF` | `origin/main` | `scripts/build/buildProvenance.ts` | Ref the pack-artifact provenance gate checks the build SHA against (#10427). |
| `OMNIROUTE_ALLOW_CANARY_BUILD` | _(unset)_ | `scripts/build/buildProvenance.ts` | Set to `1` to allow packing a build whose SHA is not on the release line, recording it as a deliberate canary instead of failing the gate (#10427). |
| `OMNIROUTE_SMOKE_API_KEY` | _(unset)_ | `scripts/ops/deploy-canary.mjs` | API key for the canary-deploy smoke probe, sent as `Authorization: Bearer` on `/v1/chat/completions`. Only used by the deploy script (#10429), never by the server. Not related to the `OMNIROUTE_SMOKE_*` variables of the opt-in CLI smoke harness (`RUN_CLI_SMOKE=1`, `OMNIROUTE_SMOKE_BASE_URL/MODEL/API_KEY_ENV/TARGETS/TIMEOUT_MS` in `tests/integration/upstream-cli-smoke.int.test.ts`) — see [CLI Integrations → Real smoke sweep](../guides/CLI-INTEGRATIONS.md). |
+| `OMNIROUTE_BUILDING` | _(unset)_ | `src/lib/buildPhase.ts` | Build-phase signal (#10060): set to `1` by `scripts/build/build-next-isolated.mjs` and inherited by every spawned build worker so the DB layer returns a no-op stub instead of loading the native better-sqlite3 addon (which aborts the worker on exit). Never set for the running server. |
| `OMNIROUTE_DATA_DIR` | _(unset)_ | `open-sse/executors/promptql/threadSticky.ts` | **Fallback alias** for `DATA_DIR`, checked only when `DATA_DIR` is unset. Used to locate the PromptQL executor's on-disk thread-sticky session cache (`
/promptql-thread-sessions.json`); if neither var is set, the cache stays in-memory only (not persisted across restarts). |
| `STORAGE_ENCRYPTION_KEY` | _(empty = disabled)_ | `src/lib/db/encryption.ts` | AES key for full SQLite database encryption at rest. Generate with `openssl rand -hex 32`. |
| `STORAGE_ENCRYPTION_KEY_VERSION` | `v1` | `scripts/build/bootstrap-env.mjs`, `electron/main.js` | Version label for the encryption key. Increment when performing key rotation to support decryption of old backups. |
diff --git a/stryker.conf.json b/stryker.conf.json
index daec377790..f039eeaca8 100644
--- a/stryker.conf.json
+++ b/stryker.conf.json
@@ -89,7 +89,6 @@
"tests/unit/auth-terminal-status.test.ts",
"tests/unit/authz/discovery-routes-local-only.test.ts",
"tests/unit/authz/oauth-autoimport-local-only.test.ts",
- "tests/unit/quota-exhaustion-cutoff-opencode.test.ts",
"tests/unit/authz/route-guard-local-prefix.test.ts",
"tests/unit/authz/route-guard-skills-collect.test.ts",
"tests/unit/authz/route-guard-version-get-exemption.test.ts",
diff --git a/tests/unit/8134-github-t5-fallback-filter.test.ts b/tests/unit/8134-github-t5-fallback-filter.test.ts
index 6de8f13416..7f54ce4e34 100644
--- a/tests/unit/8134-github-t5-fallback-filter.test.ts
+++ b/tests/unit/8134-github-t5-fallback-filter.test.ts
@@ -6,8 +6,8 @@ import { getRegistryEntry } from "../../open-sse/config/providerRegistry.ts";
const { getNextFamilyFallback } = await import("../../open-sse/services/modelFamilyFallback.ts");
// Regression for #8134 — GitHub Copilot ("github", alias "gh") T5 family fallback
-// returned "claude-opus-4-6" verbatim even though the github registry catalog
-// (Opus 4.8 / 4.8-fast / 4.7 / 4.5) has NO 4.6 tier under any dot/hyphen
+// returned "claude-opus-4-6" verbatim even though the github registry catalog at
+// the time (Opus 4.8 / 4.8-fast / 4.7 / 4.5) had NO 4.6 tier under any dot/hyphen
// notation. getNextFamilyFallback() resolved `supportedIds` from the provider's
// registry but only used it to try notation variants of a candidate, never to
// filter out a candidate that is provably absent from the catalog — so the
@@ -18,35 +18,46 @@ const { getNextFamilyFallback } = await import("../../open-sse/services/modelFam
// skips (continue) any family candidate that has no match in supportedIds
// under ANY notation (hyphen, dot, or a dated-snapshot id with the date
// suffix stripped) instead of returning it unfiltered.
+//
+// Fixture note: #10952 later added claude-opus-4.6 to the github registry, so
+// the provably-absent tier used by the fixture moved to claude-opus-4-6-thinking
+// (the ladder's first candidate after 4.6 — still absent from the catalog).
-test("#8134: github claude-opus-4.8 fallback chain never returns an unsupported tier (claude-opus-4-6)", () => {
+test("#8134: github claude-opus fallback chain never returns an unsupported tier (claude-opus-4-6-thinking)", () => {
const github = getRegistryEntry("github");
assert.ok(github, "expected the github registry entry to resolve");
const githubIds = new Set(github.models.map((m) => m.id));
+ // Fixture assumption: #10952 added claude-opus-4.6 to the github registry, so
+ // the original absent-tier role moved to the 4.6-thinking variant, which the
+ // catalog still does NOT carry under any notation.
assert.ok(
- !githubIds.has("claude-opus-4-6") && !githubIds.has("claude-opus-4.6"),
- "fixture assumption broken: github registry now has a 4.6 tier"
+ !githubIds.has("claude-opus-4-6-thinking") && !githubIds.has("claude-opus-4.6-thinking"),
+ "fixture assumption broken: github registry now has a 4.6-thinking tier"
);
+ // Ladder reality: 4.8 -> 4.7 -> 4.6 -> [4-6-thinking (absent), 4-5-20251101,
+ // sonnet-5]. The absent 4-6-thinking must be SKIPPED — the third hop resolves
+ // to the dated 4.5 snapshot's undated catalog entry, never to 4-6-thinking.
const tried = new Set(["github/claude-opus-4.8"]);
- const first = getNextFamilyFallback("github/claude-opus-4.8", tried);
- assert.ok(first, "expected a first fallback candidate");
- const firstBareId = first.replace(/^github\//, "");
- assert.ok(
- githubIds.has(firstBareId),
- `first fallback "${first}" is not in github's registered model catalog: ${[...githubIds].join(", ")}`
- );
-
- tried.add(first);
- const second = getNextFamilyFallback(first, tried);
- assert.ok(second, "expected a second fallback candidate (family must not be silently exhausted)");
- const secondBareId = second.replace(/^github\//, "");
- assert.ok(
- githubIds.has(secondBareId),
- `second fallback "${second}" is not in github's registered model catalog: ${[...githubIds].join(", ")}`
- );
- assert.notEqual(secondBareId, "claude-opus-4-6");
- assert.notEqual(secondBareId, "claude-opus-4.6");
+ const hops: string[] = [];
+ let current = "github/claude-opus-4.8";
+ for (let hop = 0; hop < 3; hop++) {
+ const next = getNextFamilyFallback(current, tried);
+ assert.ok(next, `hop ${hop + 1}: family must not be silently exhausted`);
+ const bareId = next!.replace(/^github\//, "");
+ assert.ok(
+ githubIds.has(bareId),
+ `hop ${hop + 1}: "${next}" is not in github's registered model catalog: ${[...githubIds].join(", ")}`
+ );
+ assert.notEqual(bareId, "claude-opus-4-6-thinking");
+ assert.notEqual(bareId, "claude-opus-4.6-thinking");
+ tried.add(next!);
+ hops.push(next!);
+ current = next!;
+ }
+ // The skip specifically fired: the 4.6 -> next hop jumped past the absent
+ // 4-6-thinking tier straight to a catalogued model.
+ assert.equal(hops[2].replace(/^github\//, ""), "claude-opus-4.5");
});
test("#8134: getNextFamilyFallback never returns a candidate absent from the resolved provider's catalog", () => {
diff --git a/tests/unit/cli-tools.test.ts b/tests/unit/cli-tools.test.ts
index 123c98e600..4523da96de 100644
--- a/tests/unit/cli-tools.test.ts
+++ b/tests/unit/cli-tools.test.ts
@@ -106,7 +106,9 @@ test("CLI fingerprint preserves Codex executor User-Agent and maps legacy Copilo
{ model: "gpt-4o", messages: [] }
);
- assert.equal(copilot.headers["User-Agent"], "GitHubCopilotChat/0.54.0");
+ // #10952 bumped GITHUB_COPILOT_CLI_VERSION 0.54.0 -> 1.0.81-6; the fingerprint
+ // pin tracks the advertised upstream CLI version.
+ assert.equal(copilot.headers["User-Agent"], "GitHubCopilotChat/1.0.81-6");
});
test("CLI fingerprint keeps legacy Copilot settings functional without exposing duplicate UI toggles", () => {
diff --git a/tests/unit/db-core-init.test.ts b/tests/unit/db-core-init.test.ts
index bb0cd512e5..15ab1b576b 100644
--- a/tests/unit/db-core-init.test.ts
+++ b/tests/unit/db-core-init.test.ts
@@ -497,7 +497,13 @@ test(
}
);
-test("build phase uses an in-memory database without creating sqlite files", serial, async () => {
+test("build phase returns the no-op stub without creating sqlite files", serial, async () => {
+ // Contract changed by #10060 (via #10952): the build phase no longer opens a
+ // real in-memory SQLite with migrations — loading the native better-sqlite3
+ // addon aborts the Next.js build worker on exit (node::
+ // RemoveEnvironmentCleanupHook). getDbInstance() now returns a no-op stub
+ // (pinned by tests/unit/build/10060-build-sqlite-stub.test.ts); queries are
+ // harmless no-ops and no file is touched.
const dataDir = makeTempDir("omniroute-db-build-");
try {
@@ -510,13 +516,15 @@ test("build phase uses an in-memory database without creating sqlite files", ser
const core = await importFresh("src/lib/db/core.ts");
const db = core.getDbInstance();
- assert.ok(
+ assert.notEqual(db.driver, "better-sqlite3");
+ assert.equal(
db
.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?")
- .get("provider_connections")
+ .get("provider_connections"),
+ undefined,
+ "the build stub must answer queries with no-ops, never a real table scan"
);
assert.equal(fs.existsSync(path.join(dataDir, "storage.sqlite")), false);
- assert.equal(db.pragma("journal_mode", { simple: true }), "memory");
core.resetDbInstance();
}