Compare commits

...

23 Commits

Author SHA1 Message Date
diegosouzapw
7826fb8c4c Merge remote-tracking branch 'origin/release/v3.8.50' into HEAD 2026-08-09 00:46:38 -03:00
Diego Rodrigues de Sa e Souza
e0ce95c592 fix(ci): close remaining release-green gaps (#9835)
Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
2026-08-08 23:24:11 -03:00
Diego Rodrigues de Sa e Souza
13dcbfd117 Merge pull request #9786 from diegosouzapw/fix/escalated-quality-validation-benign-error
fix(combo): ignore benign empty error fields in streaming quality validation (502 false-positive on opencode tool calls)
2026-08-08 22:40:51 -03:00
Diego Rodrigues de Sa e Souza
ec02945d97 Merge pull request #9820 from diegosouzapw/fix/release-v3.8.50-base-reds-9737-final
fix(tests): clear #9737 release gate blockers
2026-08-08 22:37:35 -03:00
diegosouzapw
cd121844ce fix(pack): retain isolated build runtime dependencies 2026-08-08 22:06:15 -03:00
diegosouzapw
8b29e73b27 Merge remote-tracking branch 'origin/release/v3.8.50' into fix/release-v3.8.50-base-reds-9737-final
# Conflicts:
#	tests/unit/adobe-firefly.test.ts
#	tests/unit/claude-code-rendering-fixes.test.ts
#	tests/unit/telemetry-auto-cleanup-6848.test.ts
#	tests/unit/triage-bugs-2026-08-02.test.ts
2026-08-08 21:45:42 -03:00
diegosouzapw
b7864cdb6c fix(ci): clear coverage pack and unit gate regressions 2026-08-08 21:33:11 -03:00
Diego Rodrigues de Sa e Souza
77cce62357 fix(ci): restore current release test integrity (#9819)
* fix(ci): restore base test integrity

* fix(quality): clear inherited fast gate drift

* fix(ci): clear inherited unit test reds

* fix(i18n): preserve Vietnamese catalog parity

* fix(i18n): preserve Portuguese catalog parity

---------

Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
2026-08-08 19:32:39 -03:00
diegosouzapw
c5f0ce01bc fix(tests): clear #9737 release gate blockers 2026-08-08 18:24:33 -03:00
Diego Rodrigues de Sa e Souza
00b79b5507 test(combo): guard auto/best-free never leaks the combo name as a model (#7754)
Refs: base-red #9737
2026-08-08 13:52:44 -03:00
Diego Rodrigues de Sa e Souza
b5e17bdbde fix(resilience): set rateLimitedUntil on connection test failure for recovery (#9623)
Refs: base-red #9737
2026-08-08 13:52:37 -03:00
Diego Rodrigues de Sa e Souza
aefa2b665b fix(playground): surface provider model loading errors in LlmChatCard (#9626)
Refs: base-red #9737
2026-08-08 13:52:30 -03:00
Diego Rodrigues de Sa e Souza
df1ea5bd77 fix(db): align domain_cost_history cleanup cutoff with millisecond column (#9625)
Refs: base-red #9737
2026-08-08 13:52:23 -03:00
Diego Rodrigues de Sa e Souza
a90c5e5aba fix(db): wire telemetry cleanup scheduler in Next.js startup path (#9624)
Refs: base-red #9737
2026-08-08 13:52:17 -03:00
Diego Rodrigues de Sa e Souza
c88b96244f fix(providers): classify 400 out of extra usage as quota_exhausted for Anthropic OAuth (#9486)
Refs: base-red #9737
2026-08-08 13:52:11 -03:00
Diego Rodrigues de Sa e Souza
93ee4dce9f fix(build): add build-next-isolated.mjs sibling imports to package.json files array (#9633)
Refs: base-red #9737
2026-08-08 13:52:04 -03:00
Diego Rodrigues de Sa e Souza
6c95e2b354 fix(build): include better-sqlite3 prebuilds in standalone bun bundle (#8847)
Refs: base-red #9737
2026-08-08 13:51:57 -03:00
Diego Rodrigues de Sa e Souza
3835f318d0 fix(cli): use process.execPath for macOS launchd autostart (#9156)
Refs: base-red #9737
2026-08-08 13:51:51 -03:00
diegosouzapw
69647b3b94 fix(combo): ignore benign empty error fields in streaming quality validation
isStreamingUpstreamError used a key-presence check (parsed.error != null)
which false-positives on benign values some backends emit on every chunk
({}, '', false, 0). When opencode issues a tool-call turn, the upstream SSE
opens with role-only frames (no recognized content) and a later chunk that
carries real tool_calls content PLUS a benign empty error field. The error
gate runs BEFORE content recognizers, so that single frame short-circuits
to 'error' -> 502 'streaming upstream error'. Same combo via kilocode works
because its wire format never emits the empty error field.

Fix: isSubstantiveError() helper — only treat error as real when it carries
non-empty string, non-empty object, or explicit true. Empty object {}, empty
string '', false, and 0 are benign.

TDD: tests/unit/quality-validation-benign-error.test.ts proves tool_calls
chunk with error:{} or error:'' is valid (was 502), while a real error
{message, code} still correctly fails.
2026-08-08 11:03:11 -03:00
diegosouzapw
bf1681727e Merge remote-tracking branch 'origin/release/v3.8.50' into feat/9544-muse-code-cli-provider 2026-08-08 08:47:38 -03:00
diegosouzapw
b80a11e98a fix(providers): register muse-code canonical provider + golden snapshot
- Add muse-code to APIKEY_PROVIDERS_FRONTIER so check:provider-consistency passes
- Regenerate translate-path golden snapshot to include the muse-code entry
  (20 additive lines, no other providers changed)
2026-08-08 08:47:18 -03:00
diegosouzapw
ea7866ae80 Merge remote-tracking branch 'origin/release/v3.8.50' into feat/9544-muse-code-cli-provider 2026-08-07 17:01:16 -03:00
diegosouzapw
ef236934c0 feat(providers): add Muse Code CLI provider preset (#9544) 2026-08-06 21:29:32 -03:00
58 changed files with 1604 additions and 159 deletions

View File

@@ -52,8 +52,11 @@ export class ServerSupervisor {
// silently, so a boot that never becomes ready looked like a dead hang with zero
// output even at APP_LOG_LEVEL=debug. Pipe stdout too and buffer it alongside
// stderr so a readiness timeout can surface what the child actually printed.
// #9156: macOS launchd cannot resolve bare "node" because its PATH is
// minimal. Always use process.execPath (the absolute path to the running
// Node.js binary) so the supervisor never depends on PATH resolution.
this.child = spawn(
process.versions.bun ? process.execPath : "node",
process.execPath,
process.versions.bun
? [this.serverPath]
: buildNodeRuntimeArgs(process.env, this.memoryLimit, this.serverPath),

View File

@@ -0,0 +1 @@
- feat(providers): add Muse Code CLI provider preset (#9544)

View File

@@ -0,0 +1 @@
- test(combo): guard auto/best-free never leaks the combo name as a model (#7754)

View File

@@ -0,0 +1 @@
- fix(build): include better-sqlite3 prebuilds in standalone bun bundle

View File

@@ -0,0 +1 @@
- fix(cli): use process.execPath for macOS launchd autostart

View File

@@ -0,0 +1 @@
- fix(providers): classify 400 out of extra usage as quota_exhausted for Anthropic OAuth

View File

@@ -0,0 +1 @@
- fix(resilience): failed connection test now sets a short cooldown so connections recover after transient outages (#9623)

View File

@@ -0,0 +1 @@
- fix(db): wire telemetry cleanup scheduler in Next.js startup path (#9624)

View File

@@ -0,0 +1 @@
- fix(db): align domain_cost_history cleanup cutoff with millisecond column (#9625)

View File

@@ -0,0 +1 @@
- fix(playground): surface provider model loading errors and offer retry (#9626)

View File

@@ -0,0 +1 @@
- fix(build): add build-next-isolated.mjs sibling imports to package.json files array

View File

@@ -149,6 +149,18 @@ export const ERROR_RULES: ErrorRule[] = [
backoff: true,
reason: "quota_exhausted",
},
{
id: "out_of_extra_usage",
text: "out of extra usage",
backoff: true,
reason: "quota_exhausted",
},
{
id: "extra_usage_required",
text: "extra usage required",
backoff: true,
reason: "quota_exhausted",
},
{ id: "capacity", text: "capacity", backoff: true, reason: "model_capacity" },
{ id: "overloaded", text: "overloaded", backoff: true, reason: "model_capacity" },
{ id: "high_demand", text: "high demand", backoff: true, reason: "model_capacity" },

View File

@@ -225,6 +225,7 @@ import { digitaloceanProvider } from "./registry/digitalocean/index.ts";
import { hcnsecProvider } from "./registry/hcnsec/index.ts";
import { promptqlProvider } from "./registry/promptql/index.ts";
import { hyperagentProvider } from "./registry/hyperagent/index.ts";
import { muse_codeProvider } from "./registry/muse-code/index.ts";
export const REGISTRY: Record<string, RegistryEntry> = {
aimlapi: aimlapiProvider,
@@ -451,5 +452,6 @@ export const REGISTRY: Record<string, RegistryEntry> = {
hcnsec: hcnsecProvider,
promptql: promptqlProvider,
hyperagent: hyperagentProvider,
"muse-code": muse_codeProvider,
unorouter: unorouterProvider,
};

View File

@@ -0,0 +1,106 @@
import type { RegistryEntry } from "../../shared.ts";
import { buildOpenAiCompatibleRegistryEntry } from "../../shared.ts";
/**
* Muse Code CLI — Meta's agentic coding tool.
*
* Wire format: OpenAI Responses API (POST /responses).
* Auth: Bearer token from META_API_KEY env var.
* Reasoning efforts: xhigh/ultra -> high (handled generically).
*
* @see https://github.com/joymadhu49/muse-openrouter-shim
*/
export const muse_codeProvider: RegistryEntry = buildOpenAiCompatibleRegistryEntry({
id: "muse-code",
alias: "mc",
passthroughModels: true,
defaultContextLength: 200000,
models: [
{
id: "llama-4-maverick",
name: "Llama 4 Maverick",
contextLength: 1048576,
maxOutputTokens: 131072,
supportsReasoning: true,
supportsXHighEffort: true,
toolCalling: true,
supportsVision: true,
targetFormat: "openai-responses",
unsupportedParams: ["logprobs", "topLogprobs", "logitBias"],
},
{
id: "llama-4-scout",
name: "Llama 4 Scout",
contextLength: 1048576,
maxOutputTokens: 131072,
supportsReasoning: true,
supportsXHighEffort: true,
toolCalling: true,
supportsVision: true,
targetFormat: "openai-responses",
unsupportedParams: ["logprobs", "topLogprobs", "logitBias"],
},
{
id: "llama-3.3-70b",
name: "Llama 3.3 70B",
contextLength: 131072,
maxOutputTokens: 32768,
supportsReasoning: false,
toolCalling: true,
targetFormat: "openai-responses",
unsupportedParams: ["logprobs", "topLogprobs"],
},
{
id: "llama-3.1-405b",
name: "Llama 3.1 405B",
contextLength: 131072,
maxOutputTokens: 32768,
supportsReasoning: false,
toolCalling: true,
targetFormat: "openai-responses",
unsupportedParams: ["logprobs", "topLogprobs"],
},
{
id: "llama-3.1-70b",
name: "Llama 3.1 70B",
contextLength: 131072,
maxOutputTokens: 32768,
supportsReasoning: false,
toolCalling: true,
targetFormat: "openai-responses",
unsupportedParams: ["logprobs", "topLogprobs"],
},
{
id: "llama-3.1-8b",
name: "Llama 3.1 8B",
contextLength: 131072,
maxOutputTokens: 32768,
supportsReasoning: false,
toolCalling: true,
targetFormat: "openai-responses",
unsupportedParams: ["logprobs", "topLogprobs"],
},
{
id: "llama-3.2-90b-vision",
name: "Llama 3.2 90B Vision",
contextLength: 131072,
maxOutputTokens: 32768,
supportsReasoning: false,
toolCalling: true,
supportsVision: true,
targetFormat: "openai-responses",
unsupportedParams: ["logprobs", "topLogprobs"],
},
{
id: "llama-3.2-11b-vision",
name: "Llama 3.2 11B Vision",
contextLength: 131072,
maxOutputTokens: 32768,
supportsReasoning: false,
toolCalling: true,
supportsVision: true,
targetFormat: "openai-responses",
unsupportedParams: ["logprobs", "topLogprobs"],
},
],
});

View File

@@ -190,10 +190,26 @@ function isRecord(value: unknown): value is Record<string, unknown> {
return !!value && typeof value === "object" && !Array.isArray(value);
}
/**
* Whether an `error` field carries a real failure signal. A key-presence check
* (`!= null`) false-positives on benign values some backends emit on every
* chunk (`{}`, `""`, `false`, `0`) — e.g. tool-call turns where a chunk with
* real tool_calls content also carries `"error": {}`. Only substantive values
* are treated as upstream failures.
*/
function isSubstantiveError(value: unknown): boolean {
if (value === null || value === undefined) return false;
if (typeof value === "string") return value.trim().length > 0;
if (typeof value === "object" && !Array.isArray(value)) {
return Object.keys(value as Record<string, unknown>).length > 0;
}
return value === true;
}
function isStreamingUpstreamError(parsed: unknown, eventType: string): boolean {
if (eventType === "response.failed" || eventType === "error") return true;
if (!isRecord(parsed)) return false;
if (parsed.error != null) return true;
if (isSubstantiveError(parsed.error)) return true;
const nestedResponse = isRecord(parsed.response) ? parsed.response : null;
return nestedResponse?.status === "failed" && nestedResponse.error != null;

View File

@@ -34,8 +34,11 @@
"scripts/dev/tls-options.mjs",
"scripts/check/check-supported-node-runtime.ts",
"scripts/dev/sync-env.mjs",
"scripts/build/native-binary-compat.mjs",
"scripts/build/assembleStandalone.mjs",
"scripts/build/backendOnlyPages.mjs",
"scripts/build/build-next-isolated.mjs",
"scripts/build/build-tproxy-native.mjs",
"scripts/build/native-binary-compat.mjs",
"scripts/build/runtime-env.mjs",
"README.md",
"LICENSE",

View File

@@ -89,6 +89,15 @@ const NATIVE_ASSET_ENTRIES = [
src: ["node_modules", "better-sqlite3", "build"],
dest: ["node_modules", "better-sqlite3", "build"],
},
{
// #8847: Bun (and npx -g global installs) resolve better-sqlite3's native
// binary from prebuilds/ instead of build/Release/, so the compiled build/
// copy alone leaves a hollow package that falls back to sql.js (OOM under
// Bun). Ship the prebuilds alongside the compiled binary.
label: "better-sqlite3 prebuilds (Bun / global installs)",
src: ["node_modules", "better-sqlite3", "prebuilds"],
dest: ["node_modules", "better-sqlite3", "prebuilds"],
},
{
// TPROXY IP_TRANSPARENT addon (Fase 3 / Epic A). Built by build-tproxy-native
// before assembly; Linux-only + opt-in, so the source is absent on non-Linux

View File

@@ -121,6 +121,9 @@ export const PACK_ARTIFACT_ROOT_ALLOWED_EXACT_PATHS: string[] = [
// shipped via package.json "files", so it must be allowed in the tarball.
"open-sse/utils/setupPolyfill.ts",
"package.json",
"scripts/build/assembleStandalone.mjs",
"scripts/build/backendOnlyPages.mjs",
"scripts/build/build-tproxy-native.mjs",
"scripts/build/build-next-isolated.mjs",
"scripts/check/check-supported-node-runtime.ts",
"scripts/build/native-binary-compat.mjs",

View File

@@ -609,14 +609,6 @@ async function main() {
args: ["run", "check:pack-artifact"],
timeout: 20 * 60 * 1000,
});
// WS1.2 (#7065 class): boot the REAL packed tarball from a clean install —
// the runtime gate structure checks cannot provide. Reuses the same dist/ build.
slow.push({
id: "pack-boot",
label: "Tarball boot-smoke (installed CLI serves /health)",
args: ["run", "check:pack-boot"],
timeout: 15 * 60 * 1000,
});
}
slow.forEach((g) => announce(`${g.label} [parallel]`));
const slowResults = await Promise.all(
@@ -633,6 +625,41 @@ async function main() {
detail: code === 0 ? "pass" : firstFailureLine(out),
});
});
if (WITH_BUILD) {
// WS1.2 (#7065 class): boot the REAL packed tarball from a clean install.
// check:pack-artifact is the builder for dist/ when staging is absent, so the
// boot smoke MUST run after it completes. Running both in the parallel wave
// races check:pack-boot against dist/server.js creation on clean worktrees.
const packArtifactIndex = slow.findIndex((g) => g.id === "pack-artifact");
const packArtifactResult = slowResults[packArtifactIndex];
const bootLabel = "Tarball boot-smoke (installed CLI serves /health)";
if (!packArtifactResult || packArtifactResult.code !== 0) {
const out = "skipped because package-artifact did not produce a valid dist/ build";
saveGateLog("pack-boot", out);
record({
id: "pack-boot",
label: bootLabel,
kind: "hard",
ok: false,
detail: out,
});
} else {
announce(bootLabel);
const { code, out } = await runAsync(npmCmd, ["run", "check:pack-boot"], {
timeout: 15 * 60 * 1000,
});
saveGateLog("pack-boot", out);
record({
id: "pack-boot",
label: bootLabel,
kind: "hard",
ok: code === 0,
detail: code === 0 ? "pass" : firstFailureLine(out),
});
}
}
} else if (WITH_BUILD) {
// --with-build without the suites (--quick): still verify the package artifact.
const { code, out } = await runAsync(npmCmd, ["run", "check:pack-artifact"], {

View File

@@ -134,7 +134,7 @@ export function LlmChatCard({
}: Props) {
const t = useTranslations("miniPlayground");
const { keys } = useApiKey();
const { models } = useProviderModels(providerId);
const { models, loading, error, retry } = useProviderModels(providerId);
const [internalSelectedKey, setInternalSelectedKey] = useState<string>("");
const [internalModel, setInternalModel] = useState<string>(initialModel ?? "");
@@ -392,15 +392,31 @@ export function LlmChatCard({
<select
value={model || firstModel}
onChange={(e) => setModel(e.target.value)}
className="min-w-0 flex-1 rounded-md border border-border bg-bg-subtle text-xs px-2 py-1 text-text-main focus:outline-none focus:ring-1 focus:ring-primary"
disabled={loading}
className="min-w-0 flex-1 rounded-md border border-border bg-bg-subtle text-xs px-2 py-1 text-text-main focus:outline-none focus:ring-1 focus:ring-primary disabled:opacity-60"
>
{modelOptions.length === 0 && <option value="">{initialModel || "—"}</option>}
{modelOptions.length === 0 && !loading && <option value="">{initialModel || "—"}</option>}
{loading && <option value="">{t("loading") ?? "Loading…"}</option>}
{modelOptions.map((m) => (
<option key={m.id} value={m.id}>
{m.id}
</option>
))}
</select>
{error && (
<span className="text-xs text-red-500 flex items-center gap-1" role="alert">
<span className="truncate max-w-[180px]" title={String(error)}>
{String(error)}
</span>
<button
type="button"
onClick={retry}
className="shrink-0 text-xs text-primary hover:text-primary-strong underline"
>
{t("retry") ?? "Retry"}
</button>
</span>
)}
</div>
{/* Key select */}
{keys.length > 0 && (

View File

@@ -1,6 +1,6 @@
"use client";
import { useState, useEffect } from "react";
import { useState, useEffect, useCallback, useRef } from "react";
export interface ProviderModel {
id: string;
@@ -18,6 +18,8 @@ interface UseProviderModelsResult {
models: ProviderModel[];
loading: boolean;
error: string | null;
/** Re-runs the model fetch for the current provider. Useful for a Retry action. */
retry: () => void;
}
/**
@@ -32,15 +34,14 @@ export function useProviderModels(providerId: string): UseProviderModelsResult {
const [models, setModels] = useState<ProviderModel[]>([]);
const [loading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<string | null>(null);
// Cancels any in-flight load (component unmount or a retry superseding the
// previous request) so a stale response never overwrites a newer one.
const cleanupRef = useRef<(() => void) | null>(null);
useEffect(() => {
if (!providerId) {
setLoading(false);
return;
}
const load = useCallback(() => {
cleanupRef.current?.();
let cancelled = false;
const load = async () => {
const run = async () => {
setLoading(true);
setError(null);
try {
@@ -109,11 +110,33 @@ export function useProviderModels(providerId: string): UseProviderModelsResult {
if (!cancelled) setLoading(false);
}
};
void load();
return () => {
void run();
const cleanup = () => {
cancelled = true;
};
cleanupRef.current = cleanup;
return cleanup;
}, [providerId]);
return { models, loading, error };
useEffect(() => {
if (!providerId) {
setLoading(false);
return;
}
return load();
}, [providerId, load]);
// Release the current in-flight cleanup on unmount so no state updates leak.
useEffect(() => {
return () => {
cleanupRef.current?.();
};
}, []);
const retry = useCallback(() => {
if (!providerId) return;
load();
}, [providerId, load]);
return { models, loading, error, retry };
}

View File

@@ -701,6 +701,17 @@ export async function testSingleConnection(connectionId: string, validationModel
? makeDiagnosis("ok", "local", null, null)
: classifyFailure({ error: result.error, statusCode: result.statusCode, provider }));
// #9623: a failed connection test must not paint the connection permanently red.
// Previously a non-terminal failure wrote `testStatus: "error"` with
// `rateLimitedUntil: null` — since the cooldown filter only ever skips entries
// whose rateLimitedUntil is in the future, a null cooldown left the connection
// permanently unavailable after a transient outage. Give non-terminal test
// failures a short cooldown so the lazy-recovery path retries them.
const terminalTestStatuses = new Set(["banned", "expired", "credits_exhausted"]);
const isTerminalFailure =
!result.valid && terminalTestStatuses.has(String(diagnosis.code ?? diagnosis.type ?? "").toLowerCase());
const testFailureCooldownMs = result.valid ? 0 : 30_000; // 30s retry window
const updateData: Record<string, any> = {
testStatus: result.valid ? "active" : "error",
lastError: result.valid ? null : result.error,
@@ -709,7 +720,12 @@ export async function testSingleConnection(connectionId: string, validationModel
lastErrorType: result.valid ? null : diagnosis.type,
lastErrorSource: result.valid ? null : diagnosis.source,
errorCode: result.valid ? null : diagnosis.code || result.statusCode || null,
rateLimitedUntil: result.valid ? null : connection.rateLimitedUntil || null,
rateLimitedUntil:
result.valid || isTerminalFailure
? result.valid
? null
: connection.rateLimitedUntil || null
: new Date(Date.now() + testFailureCooldownMs).toISOString(),
};
if (result.valid) {

View File

@@ -0,0 +1,87 @@
/**
* Muse Code CLI proprietary model catalog endpoint.
*
* Muse CLI calls GET /muse-code/models (or --base-url/muse-code/models)
* to discover available models. Returns the proprietary Muse format:
*
* { object: "list", data: [{ id, object, created, owned_by, metadata }] }
*
* Each model's metadata includes: name, family, reasoning, tool_call,
* modalities, limit, cost.
*/
import { muse_codeProvider } from "@omniroute/open-sse/config/providers/registry/muse-code/index.ts";
const MUSECODE_TIMESTAMP = Math.floor(Date.now() / 1000);
interface MuseCodeModel {
id: string;
object: "model";
created: number;
owned_by: string;
metadata: {
name: string;
family: string;
reasoning: boolean;
tool_call: boolean;
modalities: string[];
limit: number;
cost: number;
};
}
function buildModelCatalog(): MuseCodeModel[] {
const data: MuseCodeModel[] = [];
for (const model of muse_codeProvider.models) {
let family = "llama";
if (model.id.includes("llama-4")) family = "llama-4";
else if (model.id.includes("llama-3.3")) family = "llama-3.3";
else if (model.id.includes("llama-3.2")) family = "llama-3.2";
else if (model.id.includes("llama-3.1")) family = "llama-3.1";
const modalities: string[] = ["text"];
if (model.supportsVision) modalities.push("image");
data.push({
id: model.id,
object: "model",
created: MUSECODE_TIMESTAMP,
owned_by: "meta",
metadata: {
name: model.name,
family,
reasoning: !!model.supportsReasoning,
tool_call: !!model.toolCalling,
modalities,
limit: model.contextLength ?? 200_000,
cost: model.id.includes("maverick") || model.id.includes("405b") ? 3 : 1,
},
});
}
return data;
}
// Cache the catalog for the lifetime of the process — model list is static.
const CATALOG = buildModelCatalog();
const CATALOG_PAYLOAD = JSON.stringify({ object: "list", data: CATALOG }, null, 2);
export async function OPTIONS() {
return new Response(null, {
headers: {
"Access-Control-Allow-Methods": "GET, OPTIONS",
"Access-Control-Allow-Headers": "*",
},
});
}
export async function GET() {
return new Response(CATALOG_PAYLOAD, {
status: 200,
headers: {
"content-type": "application/json",
"cache-control": "public, max-age=3600",
},
});
}

View File

@@ -10191,6 +10191,8 @@
"copied": "Copied!",
"run": "Run",
"running": "Running...",
"loading": "Loading...",
"retry": "Retry",
"response": "Response",
"tunnel": "Tunnel",
"send": "Send",

View File

@@ -10191,6 +10191,8 @@
"copied": "Copiado!",
"run": "Executar",
"running": "Executando...",
"loading": "Carregando...",
"retry": "Tentar novamente",
"response": "Resposta",
"tunnel": "Tunnel",
"send": "Enviar",

View File

@@ -10191,6 +10191,8 @@
"copied": "Đã sao chép!",
"run": "Chạy",
"running": "Đang chạy...",
"loading": "Đang tải...",
"retry": "Thử lại",
"response": "Phản hồi",
"tunnel": "Đường hầm",
"send": "Gửi",

View File

@@ -306,6 +306,7 @@ export async function registerNodejs(): Promise<void> {
{ applyRuntimeSettings },
{ startRuntimeConfigHotReload },
{ startSpendBatchWriter },
{ startCleanupScheduler },
{ registerDefaultGuardrails },
{ ensurePersistentManagementPasswordHash },
{ skillExecutor },
@@ -320,6 +321,7 @@ export async function registerNodejs(): Promise<void> {
import("@/lib/config/runtimeSettings"),
import("@/lib/config/hotReload"),
import("@/lib/spend/batchWriter"),
import("@/lib/db/cleanup"),
import("@/lib/guardrails"),
import("@/lib/auth/managementPassword"),
import("@/lib/skills/executor"),
@@ -489,6 +491,17 @@ export async function registerNodejs(): Promise<void> {
console.warn("[STARTUP] Could not initialize vacuum scheduler (non-fatal):", msg);
}
// Retention cleanup scheduler (#4691/#6988, #9624): runs the general retention
// cleanup once after startup and then every 6 hours. Previously this was only
// wired into the unused src/server-init.ts, so telemetry tables grew unboundedly
// even with retention.autoCleanupEnabled=true. Idempotent (guarded internally).
try {
startCleanupScheduler();
} catch (err: unknown) {
const msg = err instanceof Error ? err.message : String(err);
console.warn("[STARTUP] Could not start cleanup scheduler (non-fatal):", msg);
}
// Warm the model catalog's durable, apiKey-independent sub-caches at
// startup — see warmModelCatalogCache() for why the top-level Response
// cache alone doesn't deliver this. Fire-and-forget, non-fatal.

View File

@@ -253,14 +253,15 @@ export async function cleanupMemoryEntries(): Promise<CleanupResult> {
/**
* Clean up old domain_cost_history based on retention settings. (#6848)
* Uses unix-epoch `timestamp` column (INTEGER).
* The `timestamp` column stores epoch milliseconds (saveCostEntry default
* is Date.now()), so the cutoff must be in milliseconds to match. (#9625)
*/
export async function cleanupDomainCostHistory(): Promise<CleanupResult> {
const db = getDbInstance();
const retention = getRetentionSettings();
const retentionDays = retention.domainCostHistory;
const cutoffEpoch = Math.floor(Date.now() / 1000) - retentionDays * 86_400;
const cutoffEpoch = Date.now() - retentionDays * 86_400_000;
const result: CleanupResult = { deleted: 0, errors: 0 };

View File

@@ -17,12 +17,6 @@ import { getDbInstance } from "./core";
const quotaComboMaintenance = new Map<string, Promise<unknown>>();
const deletingPools = new Set<string>();
/** Reset module-level state for test isolation. Call in test.after() hooks. */
export function resetQuotaPoolsModuleState(): void {
deletingPools.clear();
quotaComboMaintenance.clear();
}
function serializeQuotaComboMaintenance<T>(
poolId: string,
operation: () => Promise<T>

View File

@@ -51,6 +51,13 @@ const GEMINI_CLI_PROFILE: ClientIdentityProfile = Object.freeze({
"User-Agent": "GeminiCLI/0.1.0 (linux; x64)",
}),
});
const MUSE_CLI_PROFILE: ClientIdentityProfile = Object.freeze({
id: "muse-cli",
label: "Muse Code CLI",
headers: Object.freeze({
"User-Agent": "MuseCodeCLI/0.1.0 (linux; x64)",
}),
});
/** Ordered so `CLIENT_IDENTITY_PROFILE_OPTIONS` renders "Default" first. */
export const CLIENT_IDENTITY_PROFILES: Readonly<Record<string, ClientIdentityProfile>> =
@@ -59,6 +66,7 @@ export const CLIENT_IDENTITY_PROFILES: Readonly<Record<string, ClientIdentityPro
"claude-cli": CLAUDE_CLI_PROFILE,
"codex-cli": CODEX_CLI_PROFILE,
"gemini-cli": GEMINI_CLI_PROFILE,
"muse-cli": MUSE_CLI_PROFILE,
});
export const CLIENT_IDENTITY_PROFILE_IDS: readonly string[] = Object.keys(CLIENT_IDENTITY_PROFILES);

View File

@@ -275,4 +275,19 @@ export const APIKEY_PROVIDERS_FRONTIER = {
"Writer Palmyra is OpenAI-compatible at https://api.writer.com/v1. palmyra-x5 offers a 1M-token context window.",
hasFree: false,
},
"muse-code": {
id: "muse-code",
alias: "mc",
name: "Muse Code (Meta)",
icon: "auto_awesome",
color: "#0866FF",
textIcon: "MC",
website: "https://github.com/meta-llama/llama-stack",
authHint:
"Use your META_API_KEY env var as a Bearer token. Muse Code CLI uses the OpenAI Responses API wire format (POST /responses).",
apiHint:
"Muse Code is OpenAI-compatible. OmniRoute routes chat traffic through the Responses API and exposes the proprietary model catalog at /v1/muse-code/models.",
passthroughModels: true,
hasFree: false,
},
};

View File

@@ -64,7 +64,9 @@
"tests/unit/anthropic-thinking-signature-recovery.test.ts",
"tests/unit/antigravity-429-quota-tdd.test.ts",
"tests/unit/antigravity-prefer-stored-project.test.ts",
"tests/unit/api-key-policy-noauth-allowed-connections.test.ts",
"tests/unit/api-key-rotator-health.test.ts",
"tests/unit/api-key-policy-noauth-allowed-connections.test.ts",
"tests/unit/appearance-widget-settings-schema.test.ts",
"tests/unit/auth-antigravity-account-retry-v2.test.ts",
"tests/unit/auth-clear-account-error.test.ts",
@@ -213,7 +215,9 @@
"tests/unit/executor-web-cookie-sweep.test.ts",
"tests/unit/format-provider-error-cause.test.ts",
"tests/unit/forwarded-header-budget.test.ts",
"tests/unit/gemini-web-capabilities-9356.test.ts",
"tests/unit/gemini-web-missing-browser-3516.test.ts",
"tests/unit/gemini-web-capabilities-9356.test.ts",
"tests/unit/grok-cli-oauth.test.ts",
"tests/unit/guardrails-api-3496.test.ts",
"tests/unit/headroom-codex-quota-snapshot-6379.test.ts",
@@ -273,7 +277,9 @@
"tests/unit/rate-limit-manager.test.ts",
"tests/unit/rate-limit-queue-timeout-lockout.test.ts",
"tests/unit/repro-7503-no-choices.test.ts",
"tests/unit/repro-9486.test.ts",
"tests/unit/repro-9630-combo-false-503.test.ts",
"tests/unit/repro-9486.test.ts",
"tests/unit/repro-antigravity-404-family-cooldown-hijack.test.ts",
"tests/unit/responses-handler.test.ts",
"tests/unit/rotation-config-omniroute.test.ts",

View File

@@ -3405,6 +3405,26 @@
"stream": "https://api.morphllm.com/v1/chat/completions"
}
},
"muse-code": {
"format": "openai",
"headers": {
"apiKey": {
"Accept": "text/event-stream",
"Authorization": "Bearer <TOK>",
"Content-Type": "application/json"
},
"nonStream": {
"Authorization": "Bearer <TOK>",
"Content-Type": "application/json"
},
"oauth": {
"Accept": "text/event-stream",
"Authorization": "Bearer <TOK>",
"Content-Type": "application/json"
}
},
"url": {}
},
"muse-spark-web": {
"format": "openai",
"headers": {

View File

@@ -255,10 +255,7 @@ test("buildAdobeImagePayload attaches referenceBlobs like live adobe_atach_image
{ id: "2a4f1025-e0dc-4671-a11a-7dfd3c07bd94", usage: "general" },
{ id: "84c11d1a-e798-4300-a63e-c06504ca2068", usage: "general" },
]);
assert.equal(
(nano.generationMetadata as Record<string, unknown>).module,
"text2image"
);
assert.equal((nano.generationMetadata as Record<string, unknown>).module, "text2image");
const gpt = buildAdobeImagePayload({
prompt: "edit me",
@@ -270,10 +267,7 @@ test("buildAdobeImagePayload attaches referenceBlobs like live adobe_atach_image
assert.deepEqual(gpt.referenceBlobs, [
{ id: "aaaaaaaa-bbbb-4ccc-8ddd-eeeeeeeeeeee", usage: "subject" },
]);
assert.equal(
(gpt.generationMetadata as Record<string, unknown>).module,
"image2image"
);
assert.equal((gpt.generationMetadata as Record<string, unknown>).module, "image2image");
// gpt-image: only first 2 subject refs survive (extra screenshots hang colligo).
const gptMany = buildAdobeImagePayload({
@@ -312,10 +306,7 @@ test("adobeFireflyMaxImageRefs + adaptive image timeout", () => {
DEFAULT_IMAGE_TIMEOUT_MS + 2 * ADOBE_FIREFLY_IMAGE_TIMEOUT_PER_REF_MS
);
assert.equal(adobeFireflyImageTimeoutMs({ timeoutMs: 120_000, refCount: 5 }), 120_000);
assert.equal(
adobeFireflyImageTimeoutMs({ refCount: 99 }),
ADOBE_FIREFLY_IMAGE_TIMEOUT_MAX_MS
);
assert.equal(adobeFireflyImageTimeoutMs({ refCount: 99 }), ADOBE_FIREFLY_IMAGE_TIMEOUT_MAX_MS);
});
test("extractAdobeSourceImageSources reads Media page image fields", () => {
@@ -369,10 +360,10 @@ test("resolveAdobeSourceImageIds uploads data URLs then returns blob ids", async
const headers = init?.headers as Record<string, string>;
assert.match(String(headers["content-type"] || headers["Content-Type"] || ""), /image\//);
assert.ok(init?.body);
return new Response(
JSON.stringify({ images: [{ id: `blob-${uploadCalls}` }] }),
{ status: 200, headers: { "content-type": "application/json" } }
);
return new Response(JSON.stringify({ images: [{ id: `blob-${uploadCalls}` }] }), {
status: 200,
headers: { "content-type": "application/json" },
});
}
throw new Error(`unexpected fetch ${u}`);
};
@@ -476,8 +467,7 @@ test("buildAdobeSubmitNonce is sha256(user_id + prompt[:256])", async () => {
type: "access_token",
client_id: "clio-playground-web",
})
)
.toString("base64url");
).toString("base64url");
const header = Buffer.from(JSON.stringify({ alg: "none" })).toString("base64url");
const token = `${header}.${payload}.${"x".repeat(40)}`;
// Pad token length for looksLikeAdobeJwt (>=80)
@@ -509,8 +499,7 @@ test("buildAdobeSubmitNonce is sha256(user_id + prompt[:256])", async () => {
});
test("normalizeAdobePollUrl rewrites firefly-epo jobs/result to BKS", () => {
const raw =
"https://firefly-epo855232.adobe.io/jobs/result/4ae9fd2a-0864-46dd-9834-cfc16e91faa6";
const raw = "https://firefly-epo855232.adobe.io/jobs/result/4ae9fd2a-0864-46dd-9834-cfc16e91faa6";
const out = normalizeAdobePollUrl(raw);
assert.match(out, /^https:\/\/bks-epo8552\.adobe\.io\/v2\/jobs\/result\/4ae9fd2a/);
assert.match(out, /host=firefly-epo855232\.adobe\.io/);
@@ -592,9 +581,9 @@ test("fallback catalog has image and video entries from get_models capture", ()
test("extractAdobeAccountIdFromToken reads user_id claim", () => {
// {"user_id":"0EB@AdobeID"} base64url
const payload = Buffer.from(JSON.stringify({ user_id: "0EB@AdobeID", type: "access_token" })).toString(
"base64url"
);
const payload = Buffer.from(
JSON.stringify({ user_id: "0EB@AdobeID", type: "access_token" })
).toString("base64url");
const jwt = `eyJhbGciOiJub25lIn0.${payload}.sig`;
assert.equal(extractAdobeAccountIdFromToken(jwt), "0EB@AdobeID");
});
@@ -602,18 +591,7 @@ test("extractAdobeAccountIdFromToken reads user_id claim", () => {
// --- Handlers (mocked fetch) ----------------------------------------------
function jsonResponse(status: number, body: unknown, headerMap: Record<string, string> = {}) {
return {
ok: status >= 200 && status < 300,
status,
headers: {
get: (name: string) => {
const key = Object.keys(headerMap).find((k) => k.toLowerCase() === name.toLowerCase());
return key ? headerMap[key] : null;
},
},
json: async () => body,
text: async () => JSON.stringify(body),
} as unknown as Response;
return new Response(JSON.stringify(body) ?? null, { status, headers: headerMap });
}
test("handleAdobeFireflyImageGeneration returns 400 when prompt is missing", async () => {
@@ -779,13 +757,19 @@ test("guest JWT without AdobeID is detected", () => {
const emptyPayload = Buffer.from("{}").toString("base64url");
const guestJwt = `eyJhbGciOiJub25lIn0.${emptyPayload}.sig`;
// Pad to lookLikeAdobeJwt length if needed
const longGuest = `eyJhbGciOiJSUzI1NiJ9.${Buffer.from(JSON.stringify({ client_id: "clio-playground-web" })).toString("base64url")}.` + "x".repeat(40);
const longGuest =
`eyJhbGciOiJSUzI1NiJ9.${Buffer.from(JSON.stringify({ client_id: "clio-playground-web" })).toString("base64url")}.` +
"x".repeat(40);
assert.equal(isAdobeGuestAccessToken(longGuest), true);
const userJwt =
`eyJhbGciOiJSUzI1NiJ9.` +
Buffer.from(JSON.stringify({ user_id: "0EB@AdobeID", type: "access_token", client_id: "clio-playground-web" })).toString(
"base64url"
) +
Buffer.from(
JSON.stringify({
user_id: "0EB@AdobeID",
type: "access_token",
client_id: "clio-playground-web",
})
).toString("base64url") +
`.` +
"y".repeat(40);
assert.equal(isAdobeGuestAccessToken(userJwt), false);
@@ -832,7 +816,13 @@ test("cookie exchange rejects guest IMS tokens", async () => {
});
test("isAdobeTransientSubmitError detects 408 system under load", () => {
assert.equal(isAdobeTransientSubmitError(408, '{"error_code":"timeout_error","message":"system under load"}'), true);
assert.equal(
isAdobeTransientSubmitError(
408,
'{"error_code":"timeout_error","message":"system under load"}'
),
true
);
assert.equal(isAdobeTransientSubmitError(429, "rate"), true);
assert.equal(isAdobeTransientSubmitError(400, "bad request"), false);
assert.ok(generateAdobeNonce().length === 64);
@@ -880,11 +870,7 @@ test("image submit retries on 408 then succeeds", async () => {
if (submits < 3) {
return jsonResponse(408, { error_code: "timeout_error", message: "system under load" });
}
return jsonResponse(
200,
{ links: { result: { href: "https://poll.example/job/r1" } } },
{}
);
return jsonResponse(200, { links: { result: { href: "https://poll.example/job/r1" } } }, {});
}
if (u.includes("poll.example")) {
return jsonResponse(200, {
@@ -909,7 +895,11 @@ test("adobeFireflyGenerateImage cookie path exchanges IMS token first", async ()
const userTok =
`eyJhbGciOiJSUzI1NiJ9.` +
Buffer.from(
JSON.stringify({ user_id: "0EB@AdobeID", type: "access_token", client_id: "clio-playground-web" })
JSON.stringify({
user_id: "0EB@AdobeID",
type: "access_token",
client_id: "clio-playground-web",
})
).toString("base64url") +
`.` +
"s".repeat(40);
@@ -931,11 +921,7 @@ test("adobeFireflyGenerateImage cookie path exchanges IMS token first", async ()
? (init.headers as Record<string, string>).Authorization
: auth;
assert.equal(headerAuth, `Bearer ${userTok}`);
return jsonResponse(
200,
{},
{ "x-override-status-link": "https://poll.example/job/c1" }
);
return jsonResponse(200, {}, { "x-override-status-link": "https://poll.example/job/c1" });
}
if (String(url).includes("poll.example")) {
return jsonResponse(200, {

View File

@@ -1,10 +1,25 @@
import test from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-claude-rendering-"));
const previousDataDir = process.env.DATA_DIR;
process.env.DATA_DIR = TEST_DATA_DIR;
const { openaiResponsesToOpenAIResponse } =
await import("../../open-sse/translator/response/openai-responses.ts");
const { FORMATS } = await import("../../open-sse/translator/formats.ts");
const { createSSETransformStreamWithLogger } = await import("../../open-sse/utils/stream.ts");
const { resetDbInstance } = await import("../../src/lib/db/core.ts");
test.after(() => {
resetDbInstance();
if (previousDataDir === undefined) delete process.env.DATA_DIR;
else process.env.DATA_DIR = previousDataDir;
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
});
test("Responses->Chat: output_item.done emits arguments when no delta chunks were sent", () => {
const state = {
@@ -38,7 +53,7 @@ test("Responses->Chat: output_item.done emits arguments when no delta chunks wer
assert.equal(state.toolCallIndex, 1);
});
test("Responses->Chat: output_item.done does not re-emit arguments already streamed via deltas", () => {
test("Responses->Chat: buffered argument deltas emit once at output_item.done", () => {
const state = {
started: true,
chatId: "chatcmpl-test",
@@ -46,9 +61,19 @@ test("Responses->Chat: output_item.done does not re-emit arguments already strea
toolCallIndex: 0,
finishReasonSent: false,
currentToolCallId: "call_abc",
currentToolCallArgsBuffer: '{"query":"search"}',
currentToolCallArgsBuffer: "",
};
const deltaResult = openaiResponsesToOpenAIResponse(
{
type: "response.function_call_arguments.delta",
delta: '{"query":"search"}',
},
state
);
assert.equal(deltaResult, null);
const chunk = {
type: "response.output_item.done",
item: {
@@ -62,7 +87,8 @@ test("Responses->Chat: output_item.done does not re-emit arguments already strea
const result = openaiResponsesToOpenAIResponse(chunk, state);
assert.equal(result, null);
assert.ok(result);
assert.equal(result.choices[0].delta.tool_calls[0].function.arguments, '{"query":"search"}');
assert.equal(state.toolCallIndex, 1);
});

View File

@@ -69,7 +69,7 @@ test("ServerSupervisor starts Node with IPv4-first DNS", async () => {
assert.deepEqual(spawnCalls, [
{
command: "node",
command: process.execPath,
args: ["--dns-result-order=ipv4first", "--max-old-space-size=2048", "/app/server.js"],
},
]);

View File

@@ -80,6 +80,8 @@ test("#7849: the two-message pathological pair stays bounded", () => {
Date.now() - started < 4000,
"the pathological pair must stay fast; quadratic work would take seconds"
);
assert.strictEqual(result.body, body, "bounded processing must preserve the input body");
assert.equal(result.compressed, false, "the non-deduplicable pair must fail open");
assert.ok(Array.isArray((result.body as { messages?: unknown[] }).messages));
});

View File

@@ -0,0 +1,81 @@
/**
* Tests for Muse Code CLI model catalog endpoint.
*
* Verifies GET /v1/muse-code/models returns the proprietary Muse format.
*/
import test from "node:test";
import assert from "node:assert/strict";
import { muse_codeProvider } from "../../open-sse/config/providers/registry/muse-code/index.ts";
// ── Model catalog shape ─────────────────────────────────────────────────────
test("muse-code provider has at least one model", () => {
assert.ok(muse_codeProvider.models.length >= 1);
});
test("muse-code models have unique ids", () => {
const ids = muse_codeProvider.models.map((m) => m.id);
const unique = new Set(ids);
assert.equal(unique.size, ids.length, "model IDs must be unique");
});
test("muse-code models include llama-4-maverick", () => {
const ids = muse_codeProvider.models.map((m) => m.id);
assert.ok(ids.includes("llama-4-maverick"), "must include llama-4-maverick");
});
test("muse-code models include llama-4-scout", () => {
const ids = muse_codeProvider.models.map((m) => m.id);
assert.ok(ids.includes("llama-4-scout"), "must include llama-4-scout");
});
test("muse-code models include llama-3.3-70b", () => {
const ids = muse_codeProvider.models.map((m) => m.id);
assert.ok(ids.includes("llama-3.3-70b"), "must include llama-3.3-70b");
});
test("llama-4 models have supportsXHighEffort", () => {
const maverick = muse_codeProvider.models.find((m) => m.id === "llama-4-maverick");
assert.ok(maverick, "llama-4-maverick must exist");
assert.equal(maverick.supportsXHighEffort, true);
const scout = muse_codeProvider.models.find((m) => m.id === "llama-4-scout");
assert.ok(scout, "llama-4-scout must exist");
assert.equal(scout.supportsXHighEffort, true);
});
test("llama-3.3-70b does not support reasoning", () => {
const model = muse_codeProvider.models.find((m) => m.id === "llama-3.3-70b");
assert.ok(model, "llama-3.3-70b must exist");
assert.equal(model.supportsReasoning, false);
});
test("non-reasoning models do not declare supportsXHighEffort", () => {
for (const model of muse_codeProvider.models) {
if (!model.supportsReasoning) {
assert.equal(
model.supportsXHighEffort,
undefined,
`${model.id} is not a reasoning model but has supportsXHighEffort`
);
}
}
});
// ── Vision models ───────────────────────────────────────────────────────────
test("vision models have supportsVision: true", () => {
const expectedVision = [
"llama-4-maverick",
"llama-4-scout",
"llama-3.2-90b-vision",
"llama-3.2-11b-vision",
];
for (const model of muse_codeProvider.models) {
if (expectedVision.includes(model.id)) {
assert.equal(model.supportsVision, true, `${model.id} should have supportsVision`);
}
}
});

View File

@@ -0,0 +1,91 @@
/**
* Tests for Muse Code CLI provider registry entry.
*
* Verifies the provider entry loads correctly with expected config.
*/
import test from "node:test";
import assert from "node:assert/strict";
import { muse_codeProvider } from "../../open-sse/config/providers/registry/muse-code/index.ts";
import { getRegistryEntry } from "../../open-sse/config/providerRegistry.ts";
// ── Registry entry structure ────────────────────────────────────────────────
test("muse-code provider entry has id", () => {
assert.equal(muse_codeProvider.id, "muse-code");
});
test("muse-code provider entry has alias", () => {
assert.equal(muse_codeProvider.alias, "mc");
});
test("muse-code provider uses openai format", () => {
assert.equal(muse_codeProvider.format, "openai");
});
test("muse-code provider uses apikey auth", () => {
assert.equal(muse_codeProvider.authType, "apikey");
assert.equal(muse_codeProvider.authHeader, "bearer");
});
test("muse-code provider has passthroughModels enabled", () => {
assert.equal(muse_codeProvider.passthroughModels, true);
});
// ── Model entries ───────────────────────────────────────────────────────────
test("muse-code provider has curated models", () => {
assert.ok(muse_codeProvider.models.length > 0);
});
test("all muse-code models have contextLength", () => {
for (const model of muse_codeProvider.models) {
assert.ok(
typeof model.contextLength === "number" && model.contextLength > 0,
`${model.id} must have positive contextLength`
);
}
});
test("all muse-code models have toolCalling: true", () => {
for (const model of muse_codeProvider.models) {
assert.equal(model.toolCalling, true, `${model.id} must have toolCalling enabled`);
}
});
test("all muse-code models have targetFormat: openai-responses", () => {
for (const model of muse_codeProvider.models) {
assert.equal(
model.targetFormat,
"openai-responses",
`${model.id} must use openai-responses target format`
);
}
});
test("reasoning models have supportsXHighEffort", () => {
for (const model of muse_codeProvider.models) {
if (model.supportsReasoning) {
assert.equal(
model.supportsXHighEffort,
true,
`${model.id} is a reasoning model but missing supportsXHighEffort`
);
}
}
});
// ── Registry discovery ──────────────────────────────────────────────────────
test("muse-code is discoverable via getRegistryEntry", () => {
const entry = getRegistryEntry("muse-code");
assert.ok(entry, "getRegistryEntry must return muse-code entry");
assert.equal(entry.id, "muse-code");
});
test("muse-code is discoverable via alias", () => {
const entry = getRegistryEntry("mc");
assert.ok(entry, "getRegistryEntry must find muse-code by alias mc");
assert.equal(entry.id, "muse-code");
});

View File

@@ -97,6 +97,21 @@ test("package.json files[] excludes nested node_modules from the published packa
);
});
test("build-next-isolated sibling imports are allowed in the published package", () => {
const buildDependencies = [
"scripts/build/assembleStandalone.mjs",
"scripts/build/backendOnlyPages.mjs",
"scripts/build/build-tproxy-native.mjs",
];
const unexpectedPaths = findUnexpectedArtifactPaths(buildDependencies, {
exactPaths: PACK_ARTIFACT_ALLOWED_EXACT_PATHS,
prefixPaths: PACK_ARTIFACT_ALLOWED_PATH_PREFIXES,
});
assert.deepEqual(unexpectedPaths, []);
});
test("webdav-handler.mjs is allowed in staging dist/ (server-ws.mjs dependency, missed in 3.8.22 build)", () => {
const unexpectedPaths = findUnexpectedArtifactPaths(["webdav-handler.mjs"], {
exactPaths: APP_STAGING_ALLOWED_EXACT_PATHS,

View File

@@ -212,7 +212,7 @@ test("#8969: mocked execute posts Chat Completions with Bearer, no Cookie, strip
globalThis.fetch = (async (input: RequestInfo | URL, init?: RequestInit) => {
const headers = new Headers(init?.headers);
const rawBody = typeof init?.body === "string" ? init.body : "{}";
const rawBody = await new Request(input, init).text();
seen.push({
url: String(input),
method: (init?.method || "GET").toUpperCase(),

View File

@@ -0,0 +1,167 @@
/**
* TDD regression guard — quality validation false-positive on benign `error`
* fields in streaming SSE chunks.
*
* `isStreamingUpstreamError` treats ANY non-null `error` field as an upstream
* failure: `parsed.error != null` is true for `{}`, `""`, `false`, and `0`.
* When a client like opencode issues a tool-call turn, the upstream SSE opens
* with role-only frames (no recognized content) and a later chunk that carries
* real tool_calls content PLUS a benign empty `error` field (a field some
* backends emit on every chunk). The error gate runs BEFORE the content
* recognizers, so that single frame short-circuits to "error" → 502
* "streaming upstream error" — while the same combo via kilocode (different
* wire format) never emits the empty `error` field and works fine.
*/
import test from "node:test";
import assert from "node:assert/strict";
const { validateResponseQuality } = await import("../../open-sse/services/combo.ts");
const encoder = new TextEncoder();
const silentLog = { warn: () => {} };
function openAiSseStream(events: string[]): ReadableStream<Uint8Array> {
const body = events.join("\n") + "\n";
return new ReadableStream<Uint8Array>({
start(controller) {
controller.enqueue(encoder.encode(body));
controller.close();
},
});
}
/**
* OpenAI-compatible tool-call stream that ALSO carries a benign empty `error`
* field on the tool_calls chunk. Some backends emit `"error": {}` or
* `"error": ""` alongside every chunk; that is not a real upstream failure.
* The frame must be treated as CONTENT (valid), not ERROR.
*/
function makeToolCallStreamWithBenignError(): Response {
const events = [
// role-only first chunk — no recognized content, widens the peek window
`data: ${JSON.stringify({
id: "chatcmpl_1",
object: "chat.completion.chunk",
created: 123,
model: "gpt-4o",
choices: [{ index: 0, delta: { role: "assistant" }, finish_reason: null }],
})}`,
"",
// tool_calls delta + benign empty `error` field (the bug trigger)
`data: ${JSON.stringify({
id: "chatcmpl_2",
object: "chat.completion.chunk",
created: 123,
model: "gpt-4o",
choices: [
{
index: 0,
delta: {
tool_calls: [
{ index: 0, id: "call_1", type: "function", function: { name: "Bash", arguments: "" } },
],
},
finish_reason: null,
},
],
error: {},
})}`,
"",
`data: [DONE]`,
"",
];
return new Response(openAiSseStream(events), {
status: 200,
headers: { "content-type": "text/event-stream" },
});
}
test("OpenAI stream with tool_calls + benign empty error:{} field is VALID (not 502)", async () => {
const res = makeToolCallStreamWithBenignError();
const out = await validateResponseQuality(res, true, silentLog);
assert.equal(
out.valid,
true,
`expected valid for tool_calls chunk with benign error:{}, got valid=false (reason: ${out.reason})`
);
assert.ok(out.clonedResponse, "clonedResponse must be present for valid streaming response");
});
test("OpenAI stream with tool_calls + benign empty error:'' field is VALID", async () => {
const events = [
`data: ${JSON.stringify({
id: "chatcmpl_3",
object: "chat.completion.chunk",
created: 123,
model: "gpt-4o",
choices: [{ index: 0, delta: { role: "assistant" }, finish_reason: null }],
})}`,
"",
`data: ${JSON.stringify({
id: "chatcmpl_4",
object: "chat.completion.chunk",
created: 123,
model: "gpt-4o",
choices: [
{
index: 0,
delta: {
tool_calls: [
{ index: 0, id: "call_2", type: "function", function: { name: "Read", arguments: "" } },
],
},
finish_reason: null,
},
],
error: "",
})}`,
"",
`data: [DONE]`,
"",
];
const res = new Response(openAiSseStream(events), {
status: 200,
headers: { "content-type": "text/event-stream" },
});
const out = await validateResponseQuality(res, true, silentLog);
assert.equal(
out.valid,
true,
`expected valid for tool_calls chunk with benign error:"", got valid=false (reason: ${out.reason})`
);
});
test("Stream with a REAL non-empty error object is still flagged as invalid", async () => {
const events = [
`data: ${JSON.stringify({
id: "chatcmpl_5",
object: "chat.completion.chunk",
created: 123,
model: "gpt-4o",
choices: [{ index: 0, delta: { role: "assistant" }, finish_reason: null }],
})}`,
"",
`data: ${JSON.stringify({
id: "chatcmpl_6",
object: "chat.completion.chunk",
created: 123,
model: "gpt-4o",
choices: [{ index: 0, delta: {}, finish_reason: null }],
error: { message: "upstream quota exceeded", code: "rate_limit_exceeded" },
})}`,
"",
`data: [DONE]`,
"",
];
const res = new Response(openAiSseStream(events), {
status: 200,
headers: { "content-type": "text/event-stream" },
});
const out = await validateResponseQuality(res, true, silentLog);
assert.equal(
out.valid,
false,
`expected invalid for real error object, got valid=true (reason: ${out.reason})`
);
assert.match(out.reason ?? "", /streaming upstream error/, "reason should mention the upstream error");
});

View File

@@ -26,6 +26,12 @@ function wait(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
// Leave enough scheduling headroom for a loaded CI/devbox while keeping the
// executing callback longer than the queue-only budget. The actual queued-job
// case stays short because it controls dispatch deterministically.
const DISPATCHED_QUEUE_BUDGET_MS = 2_000;
const QUEUED_QUEUE_BUDGET_MS = 250;
test.afterEach(async () => {
await rateLimitManager.__resetRateLimitManagerForTests();
});
@@ -43,14 +49,23 @@ async function triggerQueueTimeout() {
concurrentRequests: 1,
requestsPerMinute: 100000,
minTimeBetweenRequestsMs: 0,
maxWaitMs: 40,
maxWaitMs: DISPATCHED_QUEUE_BUDGET_MS,
});
rateLimitManager.enableRateLimitProtection("conn-queue-timeout");
const connectionId = "conn-dispatched-timeout";
rateLimitManager.enableRateLimitProtection(connectionId);
return rateLimitManager.withRateLimit("openai", "conn-queue-timeout", "gpt-4o", async () => {
await wait(400); // > maxWaitMs (40ms) → Bottleneck fails the job
return "should-not-reach";
});
let dispatched = false;
const result = await rateLimitManager.withRateLimit(
"test-provider",
connectionId,
null,
async () => {
dispatched = true;
await wait(DISPATCHED_QUEUE_BUDGET_MS + 250);
return "should-not-reach";
}
);
return { dispatched, result };
}
async function triggerQueuedTimeout() {
@@ -60,7 +75,7 @@ async function triggerQueuedTimeout() {
concurrentRequests: 1,
requestsPerMinute: 0,
minTimeBetweenRequestsMs: 0,
maxWaitMs: 40,
maxWaitMs: QUEUED_QUEUE_BUDGET_MS,
});
const connectionId = "conn-queued-timeout";
rateLimitManager.enableRateLimitProtection(connectionId);
@@ -79,13 +94,12 @@ async function triggerQueuedTimeout() {
await firstExecuting;
let caught: unknown;
let queuedDispatched = false;
try {
await rateLimitManager.withRateLimit(
"test-provider",
connectionId,
null,
async () => "should-not-dispatch"
);
await rateLimitManager.withRateLimit("test-provider", connectionId, null, async () => {
queuedDispatched = true;
return "should-not-dispatch";
});
assert.fail("expected the queued job to expire");
} catch (error) {
caught = error;
@@ -93,16 +107,20 @@ async function triggerQueuedTimeout() {
releaseFirst();
await first;
}
return caught;
return { caught, queuedDispatched };
}
test("#4165 a dispatched provider call is not killed by the queue budget", async () => {
const result = await triggerQueueTimeout();
assert.equal(result, "should-not-reach");
const execution = await triggerQueueTimeout();
assert.equal(execution.dispatched, true, "the callback must enter execution");
assert.equal(execution.result, "should-not-reach");
});
test("#4165 queue expiry surfaces a clear local error", async () => {
const caught = (await triggerQueuedTimeout()) as Error & { code?: string };
const result = await triggerQueuedTimeout();
assert.ok(result.caught instanceof Error, "queue expiry must reject with an Error");
assert.equal(result.queuedDispatched, false, "an expired queued callback must never dispatch");
const caught = result.caught as Error & { code?: string };
assert.equal(caught.code, "RATE_LIMIT_QUEUE_TIMEOUT");
assert.match(caught.message, /maxWaitMs/);
assert.match(caught.message, /not an upstream/i);

View File

@@ -0,0 +1,53 @@
import test from "node:test";
import assert from "node:assert/strict";
import { createBuiltinAutoCombo } from "@omniroute/open-sse/services/autoCombo/builtinCatalog.ts";
// #7754: `auto/best-free` combo name must never leak downstream as the model id.
// When the free-tier candidate pool resolves non-empty, every model in the combo
// must carry a concrete `<provider>/<model>` id — never the literal combo name.
test("#7754 auto/best-free never leaks the combo name as a model", async () => {
const combo = await createBuiltinAutoCombo("auto/best-free", "best-free");
const models = combo.models || [];
// The combo id is the modelStr by design (routing resolves it back), but the
// models array must never contain it as a target model.
const leak = models.filter(
(m) =>
(m.id || "") === "auto/best-free" ||
(m.model || "") === "auto/best-free" ||
(m.modelStr || "") === "auto/best-free"
);
assert.equal(leak.length, 0, `combo name leaked as a target model: ${JSON.stringify(leak)}`);
});
test("#7754 every auto/best-free model carries a concrete provider/model", async () => {
const combo = await createBuiltinAutoCombo("auto/best-free", "best-free");
const models = combo.models || [];
for (const m of models) {
assert.ok(
m.model && m.model !== "auto/best-free",
`model missing concrete id: ${JSON.stringify(m)}`
);
assert.ok(
m.providerId && m.providerId !== "auto",
`model missing concrete provider: ${JSON.stringify(m)}`
);
}
});
test("#7754 empty free-tier pool degrades with a clear 503, not a name leak", async () => {
// When NO free-tier candidate exists, createBuiltinAutoCombo must either
// return an empty models[] (which the #6458 route check converts to a clear
// 503) or throw — never synthesize a target whose model is the combo name.
const combo = await createBuiltinAutoCombo("auto/best-free", "best-free");
const models = combo.models || [];
if (models.length === 0) {
// Empty pool is fine — the route layer (#6458) converts it to a clear 503.
assert.equal(combo.candidatePool?.length || 0, 0);
} else {
// Non-empty pool must not leak.
const leak = models.filter((m) => (m.model || "") === "auto/best-free");
assert.equal(leak.length, 0);
}
});

View File

@@ -9,15 +9,29 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
const repoRoot = resolve(__dirname, "../..");
const WORKFLOW = resolve(repoRoot, ".github/workflows/quality.yml");
function loadWorkflow(): any {
return parse(readFileSync(WORKFLOW, "utf8"));
interface WorkflowStep {
name?: string;
run?: string;
"continue-on-error"?: boolean;
}
interface WorkflowDocument {
jobs?: Record<string, { steps?: WorkflowStep[] }>;
}
function loadWorkflow(): WorkflowDocument {
return parse(readFileSync(WORKFLOW, "utf8")) as WorkflowDocument;
}
function invokesGate(run: string): boolean {
if (!run) return false;
return /npm run (check:|typecheck:)/.test(run) || /npm run "check/.test(run) || /npm run \\"check/.test(run);
return (
/npm run (check:|typecheck:)/.test(run) ||
/npm run "check/.test(run) ||
/npm run \\"check/.test(run)
);
}
function stepCanFail(step: any): boolean {
function stepCanFail(step: WorkflowStep): boolean {
return step?.["continue-on-error"] !== true;
}
@@ -25,7 +39,7 @@ test("repro #8542: fast-gates must not fail-fast into a later gate", () => {
const wf = loadWorkflow();
const job = wf.jobs?.["fast-gates"];
assert.ok(job, "fast-gates job must exist");
const steps: any[] = job.steps ?? [];
const steps: WorkflowStep[] = job.steps ?? [];
assert.ok(steps.length >= 5, `fast-gates must have >=5 steps, got ${steps.length}`);
const gateSteps = steps.map((s, i) => ({ s, i })).filter(({ s }) => invokesGate(s?.run ?? ""));
@@ -51,4 +65,4 @@ test("repro #8542: fast-gates must not fail-fast into a later gate", () => {
maskedPairs.slice(0, 12).join("\n") +
(maskedPairs.length > 12 ? `\n... (+${maskedPairs.length - 12} more)` : "")
);
});
});

View File

@@ -0,0 +1,70 @@
import { test } from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { syncStandaloneNativeAssets } from "../../scripts/build/assembleStandalone.mjs";
/**
* Repro #8847: better-sqlite3 prebuilds are not included in the standalone
* bundle, so the bundled app fails when the platform's prebuild is needed
* (e.g. under Bun, which resolves the native binary via prebuilds/ rather
* than build/Release/).
*
* The test creates a synthetic node_modules/better-sqlite3/ tree with both
* the compiled build/Release/ binary AND the prebuilds/ directory, then
* confirms that syncStandaloneNativeAssets copies both into the standalone
* output. On the unfixed code this fails because NATIVE_ASSET_ENTRIES only
* lists better-sqlite3/build/.
*/
test("repro-8847: better-sqlite3 prebuilds are bundled alongside the compiled binary", async () => {
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "repro-8847-"));
const projectRoot = path.join(tmp, "src-root");
// Seed better-sqlite3 with both build/Release/ and prebuilds/.
const bsqlDir = path.join(projectRoot, "node_modules", "better-sqlite3");
fs.mkdirSync(path.join(bsqlDir, "build", "Release"), { recursive: true });
fs.writeFileSync(
path.join(bsqlDir, "build", "Release", "better_sqlite3.node"),
"// native binary placeholder"
);
fs.mkdirSync(path.join(bsqlDir, "prebuilds"), { recursive: true });
for (const target of [
"darwin-arm64.node",
"darwin-x64.node",
"linux-arm64.node",
"linux-x64.node",
"linuxmusl-arm64.node",
"linuxmusl-x64.node",
"win32-arm64.node",
"win32-x64.node",
]) {
fs.writeFileSync(path.join(bsqlDir, "prebuilds", target), `// ${target}`);
}
const outDir = path.join(tmp, "standalone");
fs.mkdirSync(outDir, { recursive: true });
// Act: copy native assets into the standalone output.
await syncStandaloneNativeAssets(projectRoot, fs.promises, { log() {} }, outDir);
// Assert: the compiled build/Release/ binary was copied.
assert.ok(
fs.existsSync(
path.join(outDir, "node_modules", "better-sqlite3", "build", "Release", "better_sqlite3.node")
),
"compiled native binary (build/Release/) must be in the standalone bundle"
);
// Assert: the prebuilds/ directory was also copied.
const prebuildsDir = path.join(outDir, "node_modules", "better-sqlite3", "prebuilds");
assert.ok(fs.existsSync(prebuildsDir), "prebuilds/ directory must be in the standalone bundle");
// Assert: at least one prebuild file was copied.
assert.ok(
fs.existsSync(path.join(prebuildsDir, "linux-x64.node")),
"linux-x64 prebuild must be in the standalone bundle"
);
fs.rmSync(tmp, { recursive: true, force: true });
});

View File

@@ -0,0 +1,111 @@
import test from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs";
import path from "node:path";
// #9156: macOS launchd autostart fails because the supervisor spawns the child
// with bare "node", but launchd's PATH cannot resolve it. process.execPath is
// always the absolute path to the running Node.js binary and is always resolvable.
//
// We verify the fix via:
// 1. Static source analysis — the spawn() call must use process.execPath
// unconditionally (no fallback to bare "node"). This runs without any
// experimental flags so it serves as the permanent regression guard.
// 2. Runtime test via mock.module (requires --experimental-test-module-mocks)
// that captures the actual spawn arguments.
const __filename = new URL(import.meta.url).pathname;
const __dirname = path.dirname(__filename);
const SUPERVISOR_PATH = path.resolve(
__dirname,
"../../bin/cli/runtime/processSupervisor.mjs"
);
const supervisorSrc = fs.readFileSync(SUPERVISOR_PATH, "utf8");
// ---------------------------------------------------------------------------
// 1. Source-level verification (no experimental flag required)
// ---------------------------------------------------------------------------
test("spawn() uses process.execPath unconditionally, no bare 'node' fallback (#9156)", () => {
// Must NOT contain the old conditional that falls back to bare "node"
assert.ok(
!supervisorSrc.includes('process.versions.bun ? process.execPath : "node"'),
"must NOT have a conditional fallback to bare 'node'"
);
// Must use process.execPath as the first argument to spawn()
const execPathPattern = /spawn\(\s*process\.execPath\s*,/;
assert.ok(
execPathPattern.test(supervisorSrc),
"spawn() must receive process.execPath as first argument"
);
});
test("process.execPath is an absolute path to the running Node.js binary", () => {
assert.ok(
path.isAbsolute(process.execPath),
`process.execPath must be absolute, got: ${process.execPath}`
);
assert.ok(
fs.existsSync(process.execPath),
`process.execPath must exist: ${process.execPath}`
);
});
// ---------------------------------------------------------------------------
// 2. Runtime test via mock.module (requires --experimental-test-module-mocks)
// ---------------------------------------------------------------------------
//
// Run manually: node --experimental-test-module-mocks --import tsx/esm --test tests/unit/repro-9156.test.ts
import { mock } from "node:test";
if (typeof mock.module === "function") {
test("(runtime) ServerSupervisor.start() spawns with process.execPath (#9156)", async () => {
let spawnExecutable: string | undefined;
const { EventEmitter } = await import("node:events");
const mockChild = Object.assign(new EventEmitter(), {
pid: 12345,
stdout: null,
stderr: null,
kill: () => {},
});
mock.module("node:child_process", {
exports: {
spawn: (...args: unknown[]) => {
spawnExecutable = args[0] as string;
return mockChild;
},
},
});
process.env.PORT = "0";
const { ServerSupervisor } = await import(
"../../bin/cli/runtime/processSupervisor.mjs"
);
const supervisor = new ServerSupervisor({
serverPath: "/fake/server.js",
env: {},
maxRestarts: 0,
});
spawnExecutable = undefined;
supervisor.start();
assert.ok(spawnExecutable, "spawn() must have been called");
assert.equal(
spawnExecutable,
process.execPath,
`expected process.execPath, got: ${spawnExecutable}`
);
assert.notEqual(spawnExecutable, "node", "must not be bare 'node'");
mockChild.removeAllListeners();
delete process.env.PORT;
});
}

View File

@@ -0,0 +1,71 @@
/**
* Issue #9486 — Anthropic OAuth returns HTTP 400 with "out of extra usage" in
* the error body when a tool-carrying request exceeds the account's usage quota.
* This should be classified as quota_exhausted (not generic bad_request), so the
* account fallback mechanism applies a proper cooldown and combo routing can
* skip to another target.
*/
import test from "node:test";
import assert from "node:assert/strict";
const { matchErrorRuleByText, findMatchingErrorRule, ERROR_RULES } =
await import("../../open-sse/config/errorConfig.ts");
const { checkFallbackError, classifyErrorText } =
await import("../../open-sse/services/accountFallback.ts");
const { RateLimitReason } = await import("../../open-sse/config/constants.ts");
test("#9486 ERROR_RULES has a text rule for 'out of extra usage' → quota_exhausted", () => {
const rule = ERROR_RULES.find((r) => r.text === "out of extra usage");
assert.ok(rule, "expected a rule for 'out of extra usage'");
assert.equal(rule!.reason, "quota_exhausted");
// Should use backoff so the fallback path applies exponential scaling
assert.equal(rule!.backoff, true);
});
test("#9486 matchErrorRuleByText finds 'out of extra usage' rule", () => {
const rule = matchErrorRuleByText("out of extra usage");
assert.ok(rule, "expected a matching rule");
assert.equal(rule!.reason, "quota_exhausted");
});
test("#9486 matchErrorRuleByText finds rule in a longer error message", () => {
const rule = matchErrorRuleByText(
"Error: 400 - out of extra usage. You have exceeded your usage quota for this billing period."
);
assert.ok(rule, "expected a matching rule from longer message");
assert.equal(rule!.reason, "quota_exhausted");
});
test("#9486 findMatchingErrorRule with 400 + 'out of extra usage' returns quota_exhausted", () => {
const rule = findMatchingErrorRule(400, "out of extra usage");
assert.ok(rule, "expected a matching rule");
assert.equal(rule!.reason, "quota_exhausted");
});
test("#9486 checkFallbackError returns quota_exhausted for 400 + 'out of extra usage'", () => {
const out = checkFallbackError(400, "out of extra usage", 0, null, "claude");
assert.equal(out.shouldFallback, true);
assert.equal(out.reason, RateLimitReason.QUOTA_EXHAUSTED);
// Should get a non-zero cooldown (quota exhaustion is not transient)
assert.ok(out.cooldownMs > 0, `expected positive cooldown, got ${out.cooldownMs}ms`);
});
test("#9486 checkFallbackError handles 'Extra usage required' (same class)", () => {
// Anthropic sometimes returns "Extra usage required" instead of "out of extra usage"
const out = checkFallbackError(400, "Extra usage required", 0, null, "claude");
assert.equal(out.shouldFallback, true);
assert.equal(out.reason, RateLimitReason.QUOTA_EXHAUSTED);
});
test("#9486 classifyErrorText flags 'out of extra usage' as QUOTA_EXHAUSTED", () => {
const out = classifyErrorText("out of extra usage");
assert.equal(out, RateLimitReason.QUOTA_EXHAUSTED);
});
test("#9486 generic 400 without quota text still gets no fallback (regression guard)", () => {
// Regression guard: a plain 400 with no quota-related text must NOT trigger
// fallback, preserving the existing behavior for non-quota 400 errors.
const out = checkFallbackError(400, "Bad request: invalid JSON", 0, null, "claude");
assert.equal(out.shouldFallback, false);
assert.equal(out.reason, RateLimitReason.UNKNOWN);
});

View File

@@ -0,0 +1,52 @@
import test from "node:test";
import assert from "node:assert/strict";
// #9623: Failed connection test leaves testStatus=error with no recovery path.
// Previously the route wrote testStatus:"error" + rateLimitedUntil:null, which the
// lazy-recovery cooldown filter never matches (it only skips FUTURE rateLimitedUntil),
// leaving the connection permanently unavailable after a transient outage.
// Fix: non-terminal test failures now get a short future cooldown (30s) so they recover.
test("#9623 fix: non-terminal test failure sets a future rateLimitedUntil", () => {
// Simulate the fixed updateData logic
const now = Date.now();
const terminalTestStatuses = new Set(["banned", "expired", "credits_exhausted"]);
const valid = false;
const diagnosis = { code: "network_error", type: "upstream" }; // non-terminal
const isTerminalFailure = terminalTestStatuses.has(String(diagnosis.code).toLowerCase());
const testFailureCooldownMs = 30_000;
const rateLimitedUntil =
valid || isTerminalFailure
? valid
? null
: null
: new Date(now + testFailureCooldownMs).toISOString();
assert.ok(
rateLimitedUntil !== null,
"non-terminal failure should set a future rateLimitedUntil"
);
const cooldownTime = new Date(rateLimitedUntil as string).getTime();
assert.ok(
cooldownTime > now,
"rateLimitedUntil must be in the future so the lazy-recovery path retries"
);
assert.ok(
cooldownTime <= now + 30_000,
"cooldown should be bounded (30s)"
);
});
test("#9623 guard: terminal failures stay terminal (no fake recovery)", () => {
const terminalTestStatuses = new Set(["banned", "expired", "credits_exhausted"]);
const diagnosis = { code: "banned", type: "terminal" };
const isTerminalFailure = terminalTestStatuses.has(String(diagnosis.code).toLowerCase());
assert.equal(isTerminalFailure, true, "banned must be terminal");
});
test("#9623: success resets cooldown to null", () => {
const valid = true;
const rateLimitedUntil = valid ? null : new Date(Date.now() + 30_000).toISOString();
assert.equal(rateLimitedUntil, null, "successful test clears cooldown");
});

View File

@@ -0,0 +1,52 @@
import { describe, it } from "node:test";
import { strict as assert } from "node:assert";
import { readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import { dirname, resolve } from "node:path";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const INSTRUMENTATION_NODE_PATH = resolve(
__dirname,
"../../src/instrumentation-node.ts"
);
describe("repro-9624: startCleanupScheduler wired in Next.js startup path", () => {
it("should import startCleanupScheduler from cleanup", () => {
const source = readFileSync(INSTRUMENTATION_NODE_PATH, "utf-8");
// instrumentation-node.ts loads all startup modules via dynamic imports in a
// Promise.all destructure, e.g.:
// const [{ startCleanupScheduler }, ...] = await Promise.all([
// import("@/lib/db/cleanup"), ...
// ]);
// So the binding and the module import appear separately in the file.
const cleanupModuleImported = /import\(\s*["']@\/lib\/db\/cleanup["']\s*\)/.test(
source
);
const schedulerBound = /\bstartCleanupScheduler\b/.test(source);
assert.ok(
cleanupModuleImported,
"@/lib/db/cleanup should be imported (dynamic import) in instrumentation-node.ts"
);
assert.ok(
schedulerBound,
"startCleanupScheduler should be bound in instrumentation-node.ts"
);
});
it("should call startCleanupScheduler() during startup", () => {
const source = readFileSync(INSTRUMENTATION_NODE_PATH, "utf-8");
// Check that startCleanupScheduler is called (as a function call).
// It can be called directly or as part of a conditional.
const hasCall = /\bstartCleanupScheduler\s*\(/.test(source);
assert.ok(
hasCall,
"startCleanupScheduler() should be called in instrumentation-node.ts"
);
});
});

View File

@@ -0,0 +1,92 @@
/**
* Issue #9625 — domain_cost_history cleanup cutoff unit mismatch.
*
* cleanupDomainCostHistory() computes the cutoff in epoch seconds
* (Math.floor(Date.now() / 1000)) but the timestamp column stores
* epoch milliseconds (Date.now()), as inserted by saveCostEntry().
*
* This test seeds data using the same format as the production code
* (milliseconds), then asserts that cleanupDomainCostHistory() correctly
* deletes rows older than the retention window.
*
* Before the fix, the cutoff in seconds was ~1000× smaller than the
* stored timestamps, so the DELETE WHERE timestamp < cutoff would
* never match old rows — the cleanup was effectively a no-op.
*/
import test from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-9625-"));
process.env.DATA_DIR = TEST_DATA_DIR;
const { cleanupDomainCostHistory } = await import("../../src/lib/db/cleanup.ts");
const { getDbInstance, resetDbInstance } = await import("../../src/lib/db/core.ts");
test.after(() => {
resetDbInstance();
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
});
const DAY_MS = 86_400_000; // milliseconds
test("#9625 cleanupDomainCostHistory: cutoff in ms matches production timestamps", async () => {
const db = getDbInstance()!;
const now = Date.now(); // milliseconds — same as saveCostEntry() default
const insert = db.prepare(
"INSERT INTO domain_cost_history (api_key_id, cost, timestamp) VALUES (?, ?, ?)"
);
// Seed data using millisecond timestamps (production format).
// 3 old rows: 40 days ago (should be deleted)
// 2 recent rows: 5 days ago (should be kept)
insert.run("key1", 1.0, now - 40 * DAY_MS);
insert.run("key1", 2.0, now - 40 * DAY_MS);
insert.run("key1", 3.0, now - 40 * DAY_MS);
insert.run("key1", 4.0, now - 5 * DAY_MS);
insert.run("key1", 5.0, now - 5 * DAY_MS);
const result = await cleanupDomainCostHistory();
// Before the fix, cutoff was in seconds (~1.7e9) while timestamps
// are in milliseconds (~1.7e12). The comparison `WHERE ts < 1.7e9`
// would never match rows with ts ~1.7e12, so nothing was deleted.
assert.strictEqual(result.deleted, 3, "Should delete 3 old rows (40 days old)");
assert.strictEqual(result.errors, 0);
const remaining = db.prepare("SELECT COUNT(*) as cnt FROM domain_cost_history").get() as {
cnt: number;
};
assert.strictEqual(remaining.cnt, 2, "Should keep 2 recent rows (5 days old)");
});
test("#9625 unit mismatch: seconds cutoff would NOT match ms timestamps", () => {
// Demonstrate the arithmetic bug: a cutoff in seconds is ~1000×
// smaller than a millisecond timestamp, so the WHERE clause never
// matches production data.
const nowMs = Date.now();
const nowSec = Math.floor(nowMs / 1000);
const retentionDays = 30;
const cutoffSec = nowSec - retentionDays * 86_400; // seconds
const cutoffMs = nowMs - retentionDays * 86_400_000; // milliseconds
// A row inserted 40 days ago with a millisecond timestamp:
const oldRowMs = nowMs - 40 * 86_400_000; // ~1.7e12
// With seconds cutoff: oldRowMs (1.7e12) < cutoffSec (1.7e9) is FALSE
// because 1.7e12 > 1.7e9 — the row is never matched.
assert.ok(
oldRowMs > cutoffSec,
"Bug: ms timestamp is NOT less than seconds cutoff, so row is never deleted"
);
// With milliseconds cutoff: oldRowMs (1.7e12) < cutoffMs (1.7e12) is TRUE
assert.ok(
oldRowMs < cutoffMs,
"Fix: ms timestamp IS less than ms cutoff, so row is correctly deleted"
);
});

View File

@@ -0,0 +1,62 @@
import test from "node:test";
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { join } from "node:path";
const root = join(import.meta.dirname, "../..");
const llmChatCardPath =
"src/app/(dashboard)/dashboard/media-providers/components/LlmChatCard.tsx";
const src = readFileSync(join(root, llmChatCardPath), "utf8");
const DISABLED_ON_LOADING = /disabled\s*=\s*\{\s*loading\s*\}/;
const MODELS_LOADING_MARKER = /modelsLoading|Loading…|Loading\.\.\./;
const ERROR_BRANCH = /error\s*&&/;
const RETRY_ACTION = /onClick\s*=\s*\{[^}]*retry|retry[A-Za-z]*\s*\(\)|const\s+\[reload/i;
const NO_MODELS_AFTER_EMPTY = /modelOptions\.length\s*===?\s*0|models\.length\s*===?\s*0/;
test("LlmChatCard destructures loading and error from useProviderModels (#9626)", () => {
const match = src.match(/const\s*\{\s*([^}]+)\s*\}\s*=\s*useProviderModels\(/);
assert.ok(match, "Expected to find a destructuring of useProviderModels");
const destructured = match[1];
assert.ok(
destructured.includes("loading"),
"loading state must be destructured from useProviderModels"
);
assert.ok(destructured.includes("error"), "error state must be destructured from useProviderModels");
});
test("LlmChatCard disables the model selector while models are loading (#9626)", () => {
assert.ok(
DISABLED_ON_LOADING.test(src),
"The model <select> must be disabled while the models request is pending"
);
});
test("LlmChatCard shows a visible loading label while models are pending (#9626)", () => {
assert.ok(
MODELS_LOADING_MARKER.test(src),
"A visible loading text (e.g. 'Loading…') must appear while the models request is pending"
);
});
test("LlmChatCard surfaces the provider model error in the UI (#9626)", () => {
assert.ok(
ERROR_BRANCH.test(src),
"An error branch that renders the captured error message must exist"
);
});
test("LlmChatCard offers a retry action when the model request fails (#9626)", () => {
assert.ok(
RETRY_ACTION.test(src),
"A retry action must be offered next to the model error"
);
});
test("LlmChatCard keeps the empty-state message distinct from an error (#9626)", () => {
assert.ok(
NO_MODELS_AFTER_EMPTY.test(src),
"The empty-state (no models) message must only be shown for a successful empty response"
);
});

View File

@@ -0,0 +1,27 @@
import test from "node:test";
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
const pkg = JSON.parse(readFileSync("package.json", "utf-8"));
const files = pkg.files || [];
// #9633: `build-next-isolated.mjs` is published (fixed in #1126), but three of
// its sibling modules it imports were missing from the `files` whitelist, so
// `npm run build` on a globally-installed package crashed with ERR_MODULE_NOT_FOUND.
// The dynamic import of `build-tproxy-native.mjs` (~line 308) and the static
// imports of `assembleStandalone.mjs` / `backendOnlyPages.mjs` must ship too.
const NEEDED = [
"scripts/build/assembleStandalone.mjs",
"scripts/build/backendOnlyPages.mjs",
"scripts/build/build-tproxy-native.mjs",
"scripts/build/colocateOptionals.mjs",
];
test("#9633: build-next-isolated.mjs sibling imports present in package.json files[]", () => {
for (const needed of NEEDED) {
assert.ok(
files.some((f) => typeof f === "string" && f === needed),
`${needed} is not in package.json files[]`
);
}
});

View File

@@ -452,7 +452,13 @@ test("v1 search POST returns 400 when auto-select finds no configured provider (
const body = (await response.json()) as any;
assert.equal(response.status, 400);
assert.equal(capturedUrl, "", "fallback-only SearXNG must not receive an upstream request");
assert.ok(body.error?.message || body.error);
assert.match(
String(body.error?.message ?? body.error),
/provider|configured/i,
"the response must explain that no provider was selected"
);
} finally {
globalThis.fetch = originalFetch;
}

View File

@@ -47,7 +47,8 @@ test.after(() => {
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
});
const DAY = 86_400; // seconds
const DAY_SECONDS = 86_400;
const DAY_MS = 86_400_000;
/** Ensure compression_run_telemetry table exists (created lazily in production). */
function ensureTelemetryTable(): void {
@@ -71,21 +72,34 @@ function ensureTelemetryTable(): void {
`);
}
test.beforeEach(() => {
ensureTelemetryTable();
const db = getDbInstance()!;
for (const table of [
"domain_cost_history",
"compression_cache_stats",
"xp_audit_log",
"compression_run_telemetry",
]) {
db.exec(`DELETE FROM ${table}`);
}
});
// ─── Tests ───────────────────────────────────────────────────────────────
test("#6848 cleanupDomainCostHistory: deletes rows older than retention window", async () => {
const db = getDbInstance()!;
const now = Math.floor(Date.now() / 1000);
const now = Date.now();
const insert = db.prepare(
"INSERT INTO domain_cost_history (api_key_id, cost, timestamp) VALUES (?, ?, ?)"
);
// 3 old (40 days ago), 2 recent (5 days ago)
insert.run("key1", 1.0, now - 40 * DAY);
insert.run("key1", 2.0, now - 40 * DAY);
insert.run("key1", 3.0, now - 40 * DAY);
insert.run("key1", 4.0, now - 5 * DAY);
insert.run("key1", 5.0, now - 5 * DAY);
insert.run("key1", 1.0, now - 40 * DAY_MS);
insert.run("key1", 2.0, now - 40 * DAY_MS);
insert.run("key1", 3.0, now - 40 * DAY_MS);
insert.run("key1", 4.0, now - 5 * DAY_MS);
insert.run("key1", 5.0, now - 5 * DAY_MS);
const result = await cleanupDomainCostHistory();
@@ -100,8 +114,8 @@ test("#6848 cleanupDomainCostHistory: deletes rows older than retention window",
test("#6848 cleanupCompressionCacheStats: deletes rows older than retention window", async () => {
const db = getDbInstance()!;
const oldDate = new Date(Date.now() - 40 * DAY * 1000).toISOString();
const recentDate = new Date(Date.now() - 5 * DAY * 1000).toISOString();
const oldDate = new Date(Date.now() - 40 * DAY_MS).toISOString();
const recentDate = new Date(Date.now() - 5 * DAY_MS).toISOString();
const insert = db.prepare(
"INSERT INTO compression_cache_stats (provider, compression_mode, created_at) VALUES (?, ?, ?)"
);
@@ -123,8 +137,8 @@ test("#6848 cleanupCompressionCacheStats: deletes rows older than retention wind
test("#6848 cleanupXpAuditLog: deletes rows older than retention window", async () => {
const db = getDbInstance()!;
const oldDate = new Date(Date.now() - 40 * DAY * 1000).toISOString();
const recentDate = new Date(Date.now() - 5 * DAY * 1000).toISOString();
const oldDate = new Date(Date.now() - 40 * DAY_MS).toISOString();
const recentDate = new Date(Date.now() - 5 * DAY_MS).toISOString();
const insert = db.prepare(
"INSERT INTO xp_audit_log (api_key_id, action, xp_earned, created_at) VALUES (?, ?, ?, ?)"
);
@@ -146,14 +160,15 @@ test("#6848 cleanupXpAuditLog: deletes rows older than retention window", async
test("#6848 cleanupCompressionRunTelemetry: deletes rows older than retention window", async () => {
ensureTelemetryTable();
const db = getDbInstance()!;
const now = Math.floor(Date.now() / 1000);
const now = Date.now();
const nowSeconds = Math.floor(now / 1000);
const insert = db.prepare(
"INSERT INTO compression_run_telemetry (timestamp, tokens_before, tokens_after) VALUES (?, ?, ?)"
);
insert.run(now - 40 * DAY, 1000, 500);
insert.run(now - 40 * DAY, 2000, 800);
insert.run(now - 5 * DAY, 1500, 600);
insert.run(nowSeconds - 40 * DAY_SECONDS, 1000, 500);
insert.run(nowSeconds - 40 * DAY_SECONDS, 2000, 800);
insert.run(nowSeconds - 5 * DAY_SECONDS, 1500, 600);
const result = await cleanupCompressionRunTelemetry();
@@ -169,13 +184,14 @@ test("#6848 cleanupCompressionRunTelemetry: deletes rows older than retention wi
test("#6848 no rows deleted when all data is within retention window (calls all 4 real functions)", async () => {
ensureTelemetryTable();
const db = getDbInstance()!;
const now = Math.floor(Date.now() / 1000);
const nowSeconds = Math.floor(Date.now() / 1000);
const nowMilliseconds = Date.now();
const recentISO = new Date().toISOString();
db.prepare("INSERT INTO domain_cost_history (api_key_id, cost, timestamp) VALUES (?, ?, ?)").run(
"k",
1,
now - DAY
nowMilliseconds - DAY_MS
);
db.prepare(
"INSERT INTO compression_cache_stats (provider, compression_mode, created_at) VALUES (?, ?, ?)"
@@ -185,7 +201,7 @@ test("#6848 no rows deleted when all data is within retention window (calls all
).run("k", "a", 5, recentISO);
db.prepare(
"INSERT INTO compression_run_telemetry (timestamp, tokens_before, tokens_after) VALUES (?, ?, ?)"
).run(now - DAY, 100, 50);
).run(nowSeconds - DAY_SECONDS, 100, 50);
const r1 = await cleanupDomainCostHistory();
const r2 = await cleanupCompressionCacheStats();

View File

@@ -200,6 +200,16 @@ test("Claude -> Gemini omits unsigned functionCall instead of injecting a fake t
false,
"signature-less tool_use must not become a native functionCall"
);
assert.equal(
JSON.stringify(result).includes('"thoughtSignature"'),
false,
"the translator must not synthesize a fake thought signature"
);
assert.equal(
JSON.stringify(result).includes("read_file"),
false,
"the omitted unsigned call must not leak its tool payload elsewhere"
);
});
test("Claude -> Gemini sanitizes long tool names and exposes a restore map", () => {

View File

@@ -1,6 +1,7 @@
import test from "node:test";
import assert from "node:assert/strict";
import { openaiResponsesToOpenAIRequest } from "../../open-sse/translator/request/openai-responses.ts";
import { detectSupportedThinkingEfforts } from "../../src/lib/providerModels/modelDiscovery.ts";
function asRecord(value: unknown): Record<string, unknown> {
return value as Record<string, unknown>;
@@ -42,7 +43,6 @@ test("non-GPT-5.6 models still get max downgraded to xhigh", () => {
)
);
assert.equal(translated.reasoning_effort, "xhigh");
<<<<<<< HEAD
});
// ─────────────────────────────────────────────────────────────────────
@@ -61,12 +61,9 @@ test("#9142 Anthropic top-level system prompts must trigger background detection
"system_prompt_pattern"
);
});
=======
// #9140 — VS Code routes filter out built-in auto models
const { isUsableChatModel } = await import(
"../../src/app/api/v1/vscode/[token]/usableChatModel.ts"
);
const { isUsableChatModel } =
await import("../../src/app/api/v1/vscode/[token]/usableChatModel.ts");
test("#9140 VS Code listing must accept built-in auto routing entries", () => {
assert.equal(
@@ -79,14 +76,9 @@ test("#9140 VS Code listing must accept built-in auto routing entries", () => {
false,
"operator-created combo should still be rejected"
);
>>>>>>> origin/release/v3.8.50
});
// ── #9160 model discovery: capabilities.effort_tiers ────────────────────────
// #9160: model discovery must ingest capabilities.effort_tiers
test("#9160 model discovery must ingest capabilities.effort_tiers", () => {
assert.deepEqual(
detectSupportedThinkingEfforts({
@@ -103,4 +95,4 @@ test("#9160 capabilities.effort_tiers with duplicate and synonym", () => {
}),
["low", "xhigh"]
);
});

View File

@@ -22,16 +22,12 @@ const extract = extractCiGates as (
) => { id: string; job: string; args: string[]; env?: Record<string, string> }[];
test("eslintCounts sums errors + warnings across files", () => {
const parsed = [
{ errorCount: 2, warningCount: 5 },
{ errorCount: 0, warningCount: 3 },
{},
];
const parsed = [{ errorCount: 2, warningCount: 5 }, { errorCount: 0, warningCount: 3 }, {}];
assert.deepEqual(eslintCounts(parsed), { errors: 2, warnings: 8 });
});
test("parseEslintJson tolerates a leading non-JSON banner", () => {
const out = "npm warn something\n[{\"errorCount\":0,\"warningCount\":1}]";
const out = 'npm warn something\n[{"errorCount":0,"warningCount":1}]';
assert.deepEqual(parseEslintJson(out), [{ errorCount: 0, warningCount: 1 }]);
assert.equal(parseEslintJson("no json here"), null);
});
@@ -52,8 +48,14 @@ test("parseEslintJson tolerates ESLint's trailing unpruned-suppressions stderr s
});
test("parseCognitiveCount reads the gate's count (en + pt)", () => {
assert.equal(parseCognitiveCount("[cognitive-complexity] 797 function(s) exceed the threshold (15)."), 797);
assert.equal(parseCognitiveCount("[cognitive-complexity] REGRESSÃO — 801 violações > baseline 797"), 801);
assert.equal(
parseCognitiveCount("[cognitive-complexity] 797 function(s) exceed the threshold (15)."),
797
);
assert.equal(
parseCognitiveCount("[cognitive-complexity] REGRESSÃO — 801 violações > baseline 797"),
801
);
assert.equal(parseCognitiveCount("no number"), null);
});
@@ -175,8 +177,16 @@ test("pre-flight wires the test-masking PR-context gate against origin/main (v3.
);
// run() must honor a per-gate env override so GITHUB_BASE_REF actually reaches the child
// (routed through buildGateEnv since the --hermetic scrub was added).
assert.match(src, /env:\s*buildGateEnv\(opts\.env\)/, "run() must merge opts.env into the child env");
assert.match(src, /\.\.\.\(extra \|\| \{\}\)/, "buildGateEnv must spread the per-gate env override");
assert.match(
src,
/env:\s*buildGateEnv\(opts\.env\)/,
"run() must merge opts.env into the child env"
);
assert.match(
src,
/\.\.\.\(extra \|\| \{\}\)/,
"buildGateEnv must spread the per-gate env override"
);
});
test("pre-flight --hermetic scrubs the live-test trigger vars (2026-07-05 false-positive fix)", async () => {
@@ -214,6 +224,27 @@ test("pre-flight runs the slow suites CONCURRENTLY (v3.8.45 perf — was ~1h ser
assert.match(src, /slow\.forEach\([\s\S]*?saveGateLog\(g\.id/, "each slow gate persists its log");
});
test("pre-flight runs tarball boot only after the package artifact builder completes", async () => {
const fs = await import("node:fs");
const src = fs.readFileSync(
new URL("../../scripts/quality/validate-release-green.mjs", import.meta.url),
"utf8"
);
const parallelWave = src.indexOf("const slowResults = await Promise.all");
const packBoot = src.indexOf('id: "pack-boot"');
assert.ok(parallelWave >= 0, "the parallel slow-gate wave must exist");
assert.ok(
packBoot > parallelWave,
"pack-boot must be declared after the parallel artifact build"
);
assert.match(
src,
/packArtifactResult[\s\S]*?check:pack-boot/,
"pack-boot must be explicitly sequenced from the package-artifact result"
);
});
// ─── --full-ci gate extraction (P0, v3.8.46 post-mortem) ─────────────────────
const CI_FIXTURE = `
@@ -259,7 +290,11 @@ test("extractCiGates: pulls npm-run gate steps from the ci.yml gate jobs only",
assert.ok(ids.includes("check:docs-all") && ids.includes("check:docs-symbols"), "multi-line run");
// …and NON-gate steps + jobs outside the gate set are ignored.
assert.ok(!ids.includes("build") && !ids.some((i) => i.startsWith("test:")), "no build/test-run");
assert.equal(gates.find((g) => g.job === "test-unit"), undefined, "test-unit job is not scanned");
assert.equal(
gates.find((g) => g.job === "test-unit"),
undefined,
"test-unit job is not scanned"
);
});
test("extractCiGates: preserves `-- <args>` so ratchet flags reach the script", () => {
@@ -272,7 +307,10 @@ test("extractCiGates: preserves `-- <args>` so ratchet flags reach the script",
test("extractCiGates: skips the non-local gates (pr-evidence, codeql-ratchet)", () => {
const ids = extract(CI_FIXTURE).map((g) => g.id);
assert.ok(!ids.includes("check:pr-evidence"), "pr-evidence needs a PR body — skipped");
assert.ok(!ids.includes("check:codeql-ratchet"), "codeql-ratchet is a remote-main check — skipped");
assert.ok(
!ids.includes("check:codeql-ratchet"),
"codeql-ratchet is a remote-main check — skipped"
);
assert.ok(FULL_CI_SKIP.has("check:pr-evidence") && FULL_CI_SKIP.has("check:codeql-ratchet"));
});
@@ -295,10 +333,7 @@ test("extractCiGates: attaches GITHUB_BASE_REF=main env to test-masking + de-dup
test("extractCiGates: the REAL ci.yml yields the base-reds that leaked in v3.8.46", async () => {
const fs = await import("node:fs");
const yaml = fs.readFileSync(
new URL("../../.github/workflows/ci.yml", import.meta.url),
"utf8"
);
const yaml = fs.readFileSync(new URL("../../.github/workflows/ci.yml", import.meta.url), "utf8");
const ids = new Set(extract(yaml).map((g) => g.id));
// The exact gates that leaked to the v3.8.46 release PR because the pre-flight
// never ran them — --full-ci now reproduces every one.