From 7c119dd7edc3b0b7b2410643e941042621b4f119 Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza Date: Wed, 2 Sep 2026 10:01:47 -0300 Subject: [PATCH 1/5] fix(memory): resolve rerank provider node cache import (#12421) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The rerank-provider listing route dynamically imported @/lib/localDb — the barrel Hard Rule #2 forbids — and the stale path meant local rerank-capable provider nodes never appeared in GET /api/memory/rerank-providers. Now imports the specific @/lib/db/readCache module, with a route-level regression test through the public GET handler. Validated in a combined worktree with the batch's ready set boarded onto the current tip: parse sweep clean on every changed TypeScript file, typecheck:core clean, check:dashboard-typecheck OK (207 pre-existing errors, all within baseline), check:cycles OK, check-file-size OK, 203/205 focused node tests and 94/94 vitest — the two failures belong to #12427, which is held back. --- tests/unit/rerank-providers-route.test.ts | 42 +++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 tests/unit/rerank-providers-route.test.ts diff --git a/tests/unit/rerank-providers-route.test.ts b/tests/unit/rerank-providers-route.test.ts new file mode 100644 index 0000000000..326b0fa990 --- /dev/null +++ b/tests/unit/rerank-providers-route.test.ts @@ -0,0 +1,42 @@ +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import test from "node:test"; +import { NextRequest } from "next/server"; + +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-rerank-providers-route-")); +process.env.DATA_DIR = TEST_DATA_DIR; + +const core = await import("../../src/lib/db/core.ts"); +const { createProviderNode } = await import("../../src/lib/db/providers/nodes.ts"); +const rerankProvidersRoute = await import("../../src/app/api/memory/rerank-providers/route.ts"); + +test.after(() => { + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); +}); + +test("GET /api/memory/rerank-providers includes rerank-capable local provider nodes", async () => { + await createProviderNode({ + id: "rerank-route-test-node", + type: "openai-compatible", + name: "Local reranker", + prefix: "local-reranker", + apiType: "rerank", + baseUrl: "http://127.0.0.1:8099/v1", + }); + + const response = await rerankProvidersRoute.GET( + new NextRequest("http://localhost/api/memory/rerank-providers") + ); + const body = await response.json(); + + assert.equal(response.status, 200); + assert.deepEqual( + body.providers.find( + (provider: { provider?: string }) => provider.provider === "local-reranker" + ), + { provider: "local-reranker", hasKey: true, models: [] } + ); +}); From 089e70cbc566a14134531ff9173f1c4ac3d1ca2e Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza Date: Wed, 2 Sep 2026 10:01:51 -0300 Subject: [PATCH 2/5] fix(quality): validate typecheck baseline schema (#12419) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The API-route and Open-SSE typecheck gates treated every top-level baseline value as a diagnostic map, so policy metadata such as _relax_velocity_2026_08_30 was iterated character by character and reported as fabricated numeric TypeScript improvements. Both gates now share one fail-closed baseline boundary: underscore-prefixed top-level keys are reserved for metadata and skipped, and real file entries must be plain objects. Worth a follow-up: check-dashboard-typecheck still prints the same fabricated entries, so it looks like a third gate with the same defect that this PR's scope does not cover. Validated in a combined worktree with the batch's ready set boarded onto the current tip: parse sweep clean on every changed TypeScript file, typecheck:core clean, check:dashboard-typecheck OK (207 pre-existing errors, all within baseline), check:cycles OK, check-file-size OK, 203/205 focused node tests and 94/94 vitest — the two failures belong to #12427, which is held back. --- scripts/check/check-api-typecheck.mjs | 48 +------ scripts/check/check-open-sse-typecheck.mjs | 69 +--------- scripts/check/typecheckBaseline.mjs | 91 +++++++++++++ tests/unit/build/check-api-typecheck.test.ts | 128 +++++++++++++++++++ 4 files changed, 231 insertions(+), 105 deletions(-) create mode 100644 scripts/check/typecheckBaseline.mjs diff --git a/scripts/check/check-api-typecheck.mjs b/scripts/check/check-api-typecheck.mjs index 441e87834e..1a3e487009 100644 --- a/scripts/check/check-api-typecheck.mjs +++ b/scripts/check/check-api-typecheck.mjs @@ -19,53 +19,15 @@ import { execFileSync } from "node:child_process"; import fs from "node:fs"; import path from "node:path"; import { pathToFileURL } from "node:url"; +import { diffAgainstBaseline, parseTscOutput } from "./typecheckBaseline.mjs"; + +export { diffAgainstBaseline, parseTscOutput } from "./typecheckBaseline.mjs"; const ROOT = process.cwd(); const TSCONFIG = path.join(ROOT, "tsconfig.typecheck-api.json"); const BASELINE_PATH = path.join(ROOT, "config/quality/api-typecheck-baseline.json"); const UPDATE = process.argv.includes("--update"); -const TSC_ERROR_LINE = /^(.+?)\((\d+),(\d+)\): error (TS\d+):/; - -export function parseTscOutput(raw) { - const counts = {}; - for (const line of String(raw).split("\n")) { - const match = TSC_ERROR_LINE.exec(line); - if (!match) continue; - const [, file, , , code] = match; - if (!counts[file]) counts[file] = {}; - counts[file][code] = (counts[file][code] || 0) + 1; - } - return counts; -} - -export function diffAgainstBaseline(live, baseline) { - const regressions = []; - const improvements = []; - - for (const [file, codes] of Object.entries(live)) { - for (const [code, liveCount] of Object.entries(codes)) { - const baselineCount = (baseline[file] && baseline[file][code]) || 0; - if (liveCount > baselineCount) { - regressions.push({ file, code, liveCount, baselineCount }); - } else if (liveCount < baselineCount) { - improvements.push({ file, code, liveCount, baselineCount }); - } - } - } - - for (const [file, codes] of Object.entries(baseline)) { - for (const [code, baselineCount] of Object.entries(codes)) { - const liveCount = (live[file] && live[file][code]) || 0; - if (liveCount === 0 && baselineCount > 0) { - improvements.push({ file, code, liveCount: 0, baselineCount }); - } - } - } - - return { regressions, improvements }; -} - function runTsc() { try { return execFileSync( @@ -117,7 +79,9 @@ function main() { `[api-typecheck] ${improvements.length} baselined error(s) no longer present ` + `— run 'node scripts/check/check-api-typecheck.mjs --update' to ratchet the baseline down:\n` + improvements - .map((i) => ` - ${i.file} ${i.code} (baseline ${i.baselineCount} -> live ${i.liveCount})`) + .map( + (i) => ` - ${i.file} ${i.code} (baseline ${i.baselineCount} -> live ${i.liveCount})` + ) .join("\n") ); } diff --git a/scripts/check/check-open-sse-typecheck.mjs b/scripts/check/check-open-sse-typecheck.mjs index d18c588538..ad03e5a5b5 100644 --- a/scripts/check/check-open-sse-typecheck.mjs +++ b/scripts/check/check-open-sse-typecheck.mjs @@ -22,74 +22,15 @@ import { execFileSync } from "node:child_process"; import fs from "node:fs"; import path from "node:path"; import { pathToFileURL } from "node:url"; +import { diffAgainstBaseline, parseTscOutput } from "./typecheckBaseline.mjs"; + +export { diffAgainstBaseline, parseTscOutput } from "./typecheckBaseline.mjs"; const ROOT = process.cwd(); const TSCONFIG = path.join(ROOT, "open-sse", "tsconfig.json"); const BASELINE_PATH = path.join(ROOT, "config/quality/open-sse-typecheck-baseline.json"); const UPDATE = process.argv.includes("--update"); -// Matches tsc --pretty false output lines, e.g.: -// src/app/api/v1/chat/route.ts(12,7): error TS2304: Cannot find name 'bar'. -// open-sse/handlers/chatCore.ts(45,3): error TS7053: Element implicitly has an 'any'... -const TSC_ERROR_LINE = /^(.+?)\((\d+),(\d+)\): error (TS\d+):/; - -/** - * Parses raw `tsc --pretty false` stdout into a nested count map: - * { "": { "": } } - * - * Pure/exported for unit testing against synthetic tsc output — no child - * process involved here. - */ -export function parseTscOutput(raw) { - const counts = {}; - const lines = String(raw).split("\n"); - for (const line of lines) { - const match = TSC_ERROR_LINE.exec(line); - if (!match) continue; - const [, file, , , code] = match; - if (!counts[file]) counts[file] = {}; - counts[file][code] = (counts[file][code] || 0) + 1; - } - return counts; -} - -/** - * Compares live (file, TS code) error counts against a frozen baseline. - * Returns `{ regressions, improvements }`: - * - regressions: entries where live count > baselined count (or the pair is - * entirely new/unbaselined) — these fail the gate. - * - improvements: entries where live count < baselined count — informational, - * do not fail (use --update to ratchet the baseline down). - * - * Exported for unit testing. - */ -export function diffAgainstBaseline(live, baseline) { - const regressions = []; - const improvements = []; - - for (const [file, codes] of Object.entries(live)) { - for (const [code, liveCount] of Object.entries(codes)) { - const baselineCount = (baseline[file] && baseline[file][code]) || 0; - if (liveCount > baselineCount) { - regressions.push({ file, code, liveCount, baselineCount }); - } else if (liveCount < baselineCount) { - improvements.push({ file, code, liveCount, baselineCount }); - } - } - } - - for (const [file, codes] of Object.entries(baseline)) { - for (const [code, baselineCount] of Object.entries(codes)) { - const liveCount = (live[file] && live[file][code]) || 0; - if (liveCount === 0 && baselineCount > 0) { - improvements.push({ file, code, liveCount: 0, baselineCount }); - } - } - } - - return { regressions, improvements }; -} - function runTsc() { try { const stdout = execFileSync( @@ -143,7 +84,9 @@ function main() { `[open-sse-typecheck] ${improvements.length} baselined error(s) no longer present ` + `— run 'node scripts/check/check-open-sse-typecheck.mjs --update' to ratchet the baseline down:\n` + improvements - .map((i) => ` - ${i.file} ${i.code} (baseline ${i.baselineCount} -> live ${i.liveCount})`) + .map( + (i) => ` - ${i.file} ${i.code} (baseline ${i.baselineCount} -> live ${i.liveCount})` + ) .join("\n") ); } diff --git a/scripts/check/typecheckBaseline.mjs b/scripts/check/typecheckBaseline.mjs new file mode 100644 index 0000000000..c627d7984c --- /dev/null +++ b/scripts/check/typecheckBaseline.mjs @@ -0,0 +1,91 @@ +// Shared parsing and frozen-baseline comparison for the scoped TypeScript gates. + +const TSC_ERROR_LINE = /^(.+?)\((\d+),(\d+)\): error (TS\d+):/; +const TS_CODE = /^TS\d+$/; +const UNSAFE_PROPERTY_KEYS = new Set(["__proto__", "constructor", "prototype"]); + +function isPlainObject(value) { + if (value === null || typeof value !== "object" || Array.isArray(value)) return false; + const prototype = Object.getPrototypeOf(value); + return prototype === Object.prototype || prototype === null; +} + +function normalizeDiagnosticCounts(value, label) { + if (!isPlainObject(value)) { + throw new TypeError(`${label} must be a plain object`); + } + + const normalized = Object.create(null); + for (const [file, codes] of Object.entries(value)) { + if (UNSAFE_PROPERTY_KEYS.has(file)) { + throw new TypeError(`${label} contains unsupported property key "${file}"`); + } + if (file.startsWith("_")) continue; + if (!isPlainObject(codes)) { + throw new TypeError(`${label} entry "${file}" must be a plain object`); + } + + const normalizedCodes = Object.create(null); + for (const [code, count] of Object.entries(codes)) { + if (!TS_CODE.test(code)) { + throw new TypeError(`${label} entry "${file}" has invalid TypeScript code "${code}"`); + } + if (!Number.isFinite(count) || !Number.isInteger(count) || count < 0) { + throw new TypeError( + `${label} entry "${file}" code "${code}" must be a finite nonnegative integer` + ); + } + normalizedCodes[code] = count; + } + normalized[file] = normalizedCodes; + } + return normalized; +} + +/** Parse `tsc --pretty false` output into per-file/per-code diagnostic counts. */ +export function parseTscOutput(raw) { + const counts = {}; + for (const line of String(raw).split("\n")) { + const match = TSC_ERROR_LINE.exec(line); + if (!match) continue; + const [, file, , , code] = match; + if (!counts[file]) counts[file] = {}; + counts[file][code] = (counts[file][code] || 0) + 1; + } + return counts; +} + +/** + * Compare live diagnostic counts with a frozen baseline. + * + * Underscore-prefixed top-level keys are reserved for baseline metadata and + * never participate in the diagnostic comparison. + */ +export function diffAgainstBaseline(live, baseline) { + const liveCounts = normalizeDiagnosticCounts(live, "live diagnostics"); + const baselineCounts = normalizeDiagnosticCounts(baseline, "typecheck baseline"); + const regressions = []; + const improvements = []; + + for (const [file, codes] of Object.entries(liveCounts)) { + for (const [code, liveCount] of Object.entries(codes)) { + const baselineCount = baselineCounts[file]?.[code] ?? 0; + if (liveCount > baselineCount) { + regressions.push({ file, code, liveCount, baselineCount }); + } else if (liveCount < baselineCount) { + improvements.push({ file, code, liveCount, baselineCount }); + } + } + } + + for (const [file, codes] of Object.entries(baselineCounts)) { + for (const [code, baselineCount] of Object.entries(codes)) { + const liveCodes = liveCounts[file]; + if (!Object.hasOwn(liveCodes ?? {}, code) && baselineCount > 0) { + improvements.push({ file, code, liveCount: 0, baselineCount }); + } + } + } + + return { regressions, improvements }; +} diff --git a/tests/unit/build/check-api-typecheck.test.ts b/tests/unit/build/check-api-typecheck.test.ts index f7129c0cd4..5c584c24ae 100644 --- a/tests/unit/build/check-api-typecheck.test.ts +++ b/tests/unit/build/check-api-typecheck.test.ts @@ -7,6 +7,7 @@ import { parseTscOutput, diffAgainstBaseline, } from "../../../scripts/check/check-api-typecheck.mjs"; +import { diffAgainstBaseline as diffOpenSseAgainstBaseline } from "../../../scripts/check/check-open-sse-typecheck.mjs"; test("parseTscOutput: parses an API-route TS2554 regression", () => { const raw = @@ -85,3 +86,130 @@ test("diffAgainstBaseline: reports a disappeared diagnostic as an improvement", assert.equal(improvements[0].liveCount, 0); assert.equal(improvements[0].baselineCount, 2); }); + +test("diffAgainstBaseline: ignores underscore-prefixed baseline metadata", () => { + const baseline = { + _relax_velocity_2026_08_30: + "per-file TS diagnostic counts raised by 20% (289 -> 455); velocity phase", + "src/app/api/foo/route.ts": { TS2339: 1 }, + }; + const live = { "src/app/api/foo/route.ts": { TS2339: 1 } }; + + for (const compare of [diffAgainstBaseline, diffOpenSseAgainstBaseline]) { + assert.deepEqual(compare(live, baseline), { + regressions: [], + improvements: [], + }); + } +}); + +test("diffAgainstBaseline: rejects a string in place of a real file diagnostic map", () => { + const malformedBaseline = { + "src/app/api/foo/route.ts": "TS2339: 1", + }; + + assert.throws( + () => diffAgainstBaseline({}, malformedBaseline), + /src\/app\/api\/foo\/route\.ts.*plain object/ + ); + assert.throws( + () => diffOpenSseAgainstBaseline({}, malformedBaseline), + /src\/app\/api\/foo\/route\.ts.*plain object/ + ); +}); + +test("diffAgainstBaseline: rejects non-plain roots and file maps", () => { + const inheritedRoot = Object.create({ + "src/app/api/inherited/route.ts": { TS2339: 1 }, + }); + const inheritedFileMap = Object.create({ TS2339: 1 }); + + for (const malformedBaseline of [[], "not an object", null, inheritedRoot]) { + assert.throws( + () => diffAgainstBaseline({}, malformedBaseline), + /typecheck baseline must be a plain object/ + ); + } + for (const malformedFileMap of [[], null, inheritedFileMap]) { + assert.throws( + () => + diffAgainstBaseline( + {}, + { + "src/app/api/foo/route.ts": malformedFileMap, + } + ), + /src\/app\/api\/foo\/route\.ts.*plain object/ + ); + } +}); + +test("diffAgainstBaseline: rejects prototype property keys", () => { + const malformedBaseline = JSON.parse('{"__proto__":{"TS2339":1}}'); + + assert.throws( + () => diffAgainstBaseline({}, malformedBaseline), + /unsupported property key "__proto__"/ + ); +}); + +test("diffAgainstBaseline: rejects non-TypeScript diagnostic keys", () => { + for (const code of ["2339", "TSX2339", "TS23x", "constructor"]) { + assert.throws( + () => + diffAgainstBaseline( + {}, + { + "src/app/api/foo/route.ts": { [code]: 1 }, + } + ), + /invalid TypeScript code/ + ); + } +}); + +test("diffAgainstBaseline: rejects invalid diagnostic counts", () => { + for (const count of [Number.NaN, Number.POSITIVE_INFINITY, -1, 1.5, "1"]) { + assert.throws( + () => + diffAgainstBaseline( + {}, + { + "src/app/api/foo/route.ts": { TS2339: count }, + } + ), + /finite nonnegative integer/ + ); + } +}); + +test("diffAgainstBaseline: validates live diagnostics with the same schema", () => { + assert.throws( + () => + diffAgainstBaseline( + { "src/app/api/foo/route.ts": { TS2339: -1 } }, + { "src/app/api/foo/route.ts": { TS2339: 1 } } + ), + /live diagnostics.*finite nonnegative integer/ + ); +}); + +test("diffAgainstBaseline: accepts zero counts without fabricating a second improvement", () => { + assert.deepEqual( + diffAgainstBaseline( + { "src/app/api/foo/route.ts": { TS2339: 0 } }, + { "src/app/api/foo/route.ts": { TS2339: 1 } } + ), + { + regressions: [], + improvements: [ + { + file: "src/app/api/foo/route.ts", + code: "TS2339", + liveCount: 0, + baselineCount: 1, + }, + ], + } + ); +}); From a25ac4d979ef1cab6d5f9254327e879dfe26cb74 Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza Date: Wed, 2 Sep 2026 10:01:55 -0300 Subject: [PATCH 3/5] fix(types): make system prompt injection noImplicitAny-safe (#12416) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes the remaining noImplicitAny gap in global system-prompt injection without changing valid OpenAI or Claude request behaviour: injectSystemPrompt gets a caller-preserving generic type, unknown bodies/message entries/content are narrowed before access, malformed entries are skipped instead of throwing, and request/message/content immutability is preserved. Validated in a combined worktree with the batch's ready set boarded onto the current tip: parse sweep clean on every changed TypeScript file, typecheck:core clean, check:dashboard-typecheck OK (207 pre-existing errors, all within baseline), check:cycles OK, check-file-size OK, 203/205 focused node tests and 94/94 vitest — the two failures belong to #12427, which is held back. --- open-sse/services/systemPrompt.ts | 57 +++++++++++++++++++------------ tests/unit/system-prompt.test.ts | 46 +++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 22 deletions(-) diff --git a/open-sse/services/systemPrompt.ts b/open-sse/services/systemPrompt.ts index d728b885e2..8f4ef69a86 100644 --- a/open-sse/services/systemPrompt.ts +++ b/open-sse/services/systemPrompt.ts @@ -20,6 +20,14 @@ interface SystemPromptConfig { prompt: string; } +function isRecord(value: unknown): value is Record { + return value !== null && typeof value === "object" && !Array.isArray(value); +} + +function isSystemMessage(value: unknown): value is Record { + return isRecord(value) && (value.role === "system" || value.role === "developer"); +} + // Typed accessor for globalThis storage — avoids `as any` casts (#2470) const _store = globalThis as unknown as Record; @@ -74,45 +82,50 @@ export function getSystemPromptConfig() { * suffixPrompt is appended after existing system content. * This ensures: prefix → agent instructions → suffix (#2468). * - * @param {object} body - Request body - * @returns {object} Modified body + * @param body - Request body + * @returns Modified body */ -export function injectSystemPrompt(body) { +export function injectSystemPrompt(body: T): T { const cfg = getConfig(); if (!cfg.enabled) return body; const prefix = cfg.prefixPrompt || ""; const suffix = cfg.suffixPrompt || ""; if (!prefix && !suffix) return body; - if (!body || typeof body !== "object") return body; + if (!isRecord(body)) return body; if (body._skipSystemPrompt) return body; - const result = { ...body }; + const result: Record = { ...body }; // OpenAI/Claude format (messages[]) if (result.messages && Array.isArray(result.messages)) { - const sysIdx = result.messages.findIndex((m) => m.role === "system" || m.role === "developer"); - result.messages = [...result.messages]; + const messages: unknown[] = result.messages; + const sysIdx = messages.findIndex(isSystemMessage); + const nextMessages = [...messages]; if (sysIdx >= 0) { - const msg = { ...result.messages[sysIdx] }; - if (Array.isArray(msg.content)) { - const content = [...msg.content]; - if (prefix) content.unshift({ type: "text", text: prefix }); - if (suffix) content.push({ type: "text", text: suffix }); - msg.content = content; - } else { - let content = msg.content || ""; - if (prefix) content = prefix + "\n\n" + content; - if (suffix) content = content + "\n\n" + suffix; - msg.content = content; + const existingMessage = nextMessages[sysIdx]; + if (isRecord(existingMessage)) { + const msg = { ...existingMessage }; + if (Array.isArray(msg.content)) { + const content: unknown[] = [...msg.content]; + if (prefix) content.unshift({ type: "text", text: prefix }); + if (suffix) content.push({ type: "text", text: suffix }); + msg.content = content; + } else { + let content = String(msg.content || ""); + if (prefix) content = prefix + "\n\n" + content; + if (suffix) content = content + "\n\n" + suffix; + msg.content = content; + } + nextMessages[sysIdx] = msg; } - result.messages[sysIdx] = msg; } else { // No existing system message — combine both into one const combined = [prefix, suffix].filter(Boolean).join("\n\n"); if (combined) { - result.messages = [{ role: "system", content: combined }, ...result.messages]; + nextMessages.unshift({ role: "system", content: combined }); } } + result.messages = nextMessages; } // Claude format (system field) @@ -123,14 +136,14 @@ export function injectSystemPrompt(body) { if (suffix) sys = sys + "\n\n" + suffix; result.system = sys; } else if (Array.isArray(result.system)) { - let arr = [...result.system]; + let arr: unknown[] = [...result.system]; if (prefix) arr = [{ type: "text", text: prefix }, ...arr]; if (suffix) arr = [...arr, { type: "text", text: suffix }]; result.system = arr; } } - return result; + return Object.assign({}, body, result); } /** diff --git a/tests/unit/system-prompt.test.ts b/tests/unit/system-prompt.test.ts index ecad3bdc00..796745767e 100644 --- a/tests/unit/system-prompt.test.ts +++ b/tests/unit/system-prompt.test.ts @@ -120,6 +120,52 @@ test("injectSystemPrompt: null body returns as-is", () => { assert.equal(injectSystemPrompt(null), null); }); +test("injectSystemPrompt: non-object bodies return as-is", () => { + setSystemPromptConfig({ enabled: true, suffixPrompt: "test" }); + + for (const body of [undefined, "prompt", 42, true]) { + assert.equal(injectSystemPrompt(body), body); + } +}); + +test("injectSystemPrompt: skips malformed message entries safely", () => { + setSystemPromptConfig({ enabled: true, prefixPrompt: "PRE", suffixPrompt: "SUF" }); + const body = { + messages: [ + { role: "user", content: "hi" }, + null, + { role: "system", content: "Original prompt" }, + ], + }; + + const result = injectSystemPrompt(body); + + assert.equal(result.messages[2].content, "PRE\n\nOriginal prompt\n\nSUF"); + assert.equal(result.messages[1], null); +}); + +test("injectSystemPrompt: does not mutate the request or nested message content", () => { + setSystemPromptConfig({ enabled: true, prefixPrompt: "PRE", suffixPrompt: "SUF" }); + const systemContent = [{ type: "text", text: "Original prompt" }]; + const systemMessage = { role: "system", content: systemContent }; + const body = { + messages: [systemMessage, { role: "user", content: "hi" }], + }; + + const result = injectSystemPrompt(body); + + assert.notEqual(result, body); + assert.notEqual(result.messages, body.messages); + assert.notEqual(result.messages[0], systemMessage); + assert.notEqual(result.messages[0].content, systemContent); + assert.deepEqual(body, { + messages: [ + { role: "system", content: [{ type: "text", text: "Original prompt" }] }, + { role: "user", content: "hi" }, + ], + }); +}); + test("injectSystemPrompt: developer role treated as system", () => { setSystemPromptConfig({ enabled: true, prefixPrompt: "PRE", suffixPrompt: "SUF" }); const body = { From d6412532c4ac2089fe59dd6012d9b58b18208624 Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza Date: Wed, 2 Sep 2026 10:01:58 -0300 Subject: [PATCH 4/5] chore(quality): ratchet open-sse typecheck baseline to zero (#12418) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The current release dependencies already resolve the ChatGPT Web vendor diagnostics the allowance covered, so the stale open-sse typecheck allowance is removed and any future diagnostic becomes a blocking regression again. No vendor source, package manifest, checker script or runtime behaviour changes. Validated in a combined worktree with the batch's ready set boarded onto the current tip: parse sweep clean on every changed TypeScript file, typecheck:core clean, check:dashboard-typecheck OK (207 pre-existing errors, all within baseline), check:cycles OK, check-file-size OK, 203/205 focused node tests and 94/94 vitest — the two failures belong to #12427, which is held back. --- config/quality/open-sse-typecheck-baseline.json | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/config/quality/open-sse-typecheck-baseline.json b/config/quality/open-sse-typecheck-baseline.json index a324e7ca88..0967ef424b 100644 --- a/config/quality/open-sse-typecheck-baseline.json +++ b/config/quality/open-sse-typecheck-baseline.json @@ -1,9 +1 @@ -{ - "src/lib/guardrails/videoBridgeHelpers.ts": { - "TS2488": 2, - "TS2365": 3, - "TS2322": 2, - "TS2345": 2 - }, - "_relax_velocity_2026_08_30": "per-file TS diagnostic counts raised by 20% (5 → 9); velocity phase, see quality-baseline.json _policy." -} +{} From c420a51df65819926bc403c5abecdf25adff4e10 Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza Date: Wed, 2 Sep 2026 10:02:03 -0300 Subject: [PATCH 5/5] fix(providers): finalize Nimble and Opper asset provenance (#12415) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes the two remaining unresolved provider-asset records without weakening the provenance boundary: public/providers/nimble-search.svg is removed because no sufficient redistribution evidence is recorded, and Nimble Search renders with the internal generic icon before any CDN tier while staying fully functional; Opper is registered in the local SVG resolver with its existing bytes proven against opper-ai/provider-omniroute at immutable commit 9aacef7d6ae68d8d79f5aee042a25e9b646d2338, recording the MIT repository license from that same commit while keeping trademarkClearance null. Validated in a combined worktree with the batch's ready set boarded onto the current tip: parse sweep clean on every changed TypeScript file, typecheck:core clean, check:dashboard-typecheck OK (207 pre-existing errors, all within baseline), check:cycles OK, check-file-size OK, 203/205 focused node tests and 94/94 vitest — the two failures belong to #12427, which is held back. --- .../fixes/provider-assets-provenance-final.md | 1 + config/quality/provider-assets-provenance.jsonl | 5 ++--- public/providers/nimble-search.svg | 7 ------- src/shared/components/ProviderIcon.tsx | 3 ++- tests/unit/check-provider-asset-provenance.test.ts | 4 ++-- tests/unit/provider-assets-generic-fallback.test.mjs | 12 +++++++----- tests/unit/ui/ProviderIcon-icon-url.test.tsx | 6 ++++-- 7 files changed, 18 insertions(+), 20 deletions(-) create mode 100644 changelog.d/fixes/provider-assets-provenance-final.md delete mode 100644 public/providers/nimble-search.svg diff --git a/changelog.d/fixes/provider-assets-provenance-final.md b/changelog.d/fixes/provider-assets-provenance-final.md new file mode 100644 index 0000000000..b9d3a664a9 --- /dev/null +++ b/changelog.d/fixes/provider-assets-provenance-final.md @@ -0,0 +1 @@ +- Render Nimble Search with the generic provider icon and serve Opper's proven logo locally. diff --git a/config/quality/provider-assets-provenance.jsonl b/config/quality/provider-assets-provenance.jsonl index 757683de8a..a66d208a96 100644 --- a/config/quality/provider-assets-provenance.jsonl +++ b/config/quality/provider-assets-provenance.jsonl @@ -1,4 +1,4 @@ -{"recordType": "manifest", "schemaVersion": 1, "expectedAssetCount": 142, "auditedCommit": "7d57d9f4a15931aa33a9ab968e4e5d76a205e27c", "auditedAt": "2026-08-28", "scope": "Every regular file directly under public/providers at the audited commit.", "statusSemantics": {"proven": "Immutable source plus byte-exact or SVG path-data match.", "probable": "Repository evidence suggests provenance, but no immutable upstream match is proven.", "unresolved": "No sufficient immutable provenance evidence is recorded."}, "enforcement": "All physical files, hashes, magic MIME values, statuses, and duplicate aliases are blocking. Probable and unresolved statuses are recorded but non-blocking in schema version 1.", "legalScope": "Provenance records source matching only; it does not establish copyright or trademark clearance."} +{"recordType": "manifest", "schemaVersion": 1, "expectedAssetCount": 141, "auditedCommit": "ccb024cfa9a7612fa65b1f1795740572369d58f5", "auditedAt": "2026-09-02", "scope": "Every regular file directly under public/providers at the audited commit.", "statusSemantics": {"proven": "Immutable source plus byte-exact or SVG path-data match.", "probable": "Repository evidence suggests provenance, but no immutable upstream match is proven.", "unresolved": "No sufficient immutable provenance evidence is recorded."}, "enforcement": "All physical files, hashes, magic MIME values, statuses, and duplicate aliases are blocking. Probable and unresolved statuses are recorded but non-blocking in schema version 1.", "legalScope": "Provenance records source matching only; it does not establish copyright or trademark clearance."} {"recordType": "asset", "path": "public/providers/360ai.svg", "mediaType": "image/svg+xml", "sha256": "59366fe04a4336518b8277b430f4a464a91e7ec944c9cf6f40a945c74002386d", "provenanceStatus": "proven", "source": {"kind": "npm", "url": "https://registry.npmjs.org/@lobehub/icons/-/icons-5.10.0.tgz", "ref": "5.10.0", "path": "package/es/Ai360/components/Color.js", "integrity": "sha512-CIpjkISCLRK7haDtSugGFd0o3odaJts8ewJOkUiEFtns3xvsqbl8i24eowBnjw+yMDQVQyNONlhqTD58YC6Ljg==", "packageShasum": "add1baced073a60157d39c7820b8d5c1928a1054", "match": "svg-path-data", "matchDetail": "All 5/5 local SVG path d values match the pinned Color component."}, "upstreamLicenseClaim": {"value": "MIT", "assertedBy": "@lobehub/icons@5.10.0 package/LICENSE", "evidence": "https://registry.npmjs.org/@lobehub/icons/-/icons-5.10.0.tgz#package/LICENSE", "independentlyVerified": true, "scope": "Pinned package distribution only; no trademark clearance."}, "trademarkClearance": null, "evidenceNote": "All local SVG path data matches the pinned LobeHub Color component. This proves source provenance only, not trademark clearance."} {"recordType": "asset", "path": "public/providers/alibaba.svg", "mediaType": "image/svg+xml", "sha256": "1cd1e7be5108d1e847508dc9e40591fb6eb29aac20bbf7f4c56c6f082359b323", "provenanceStatus": "proven", "source": {"kind": "git", "url": "https://github.com/GLINCKER/thesvg", "ref": "7870bc1c5f657d9accbb7f96cc457b8dd3363ee8", "path": "public/icons/alibaba/default.svg", "integrity": "sha256:1cd1e7be5108d1e847508dc9e40591fb6eb29aac20bbf7f4c56c6f082359b323", "match": "byte-exact"}, "upstreamLicenseClaim": {"value": "MIT", "assertedBy": "theSVG pinned registry", "evidence": "https://github.com/GLINCKER/thesvg/blob/7870bc1c5f657d9accbb7f96cc457b8dd3363ee8/src/data/icons.json", "independentlyVerified": false, "scope": "Upstream SVG copyright claim only; no trademark clearance.", "upstreamUrl": "https://alibaba.com"}, "trademarkClearance": null, "evidenceNote": "Local bytes are byte-identical to the pinned theSVG source. This proves source provenance only, not copyright or trademark clearance."} {"recordType": "asset", "path": "public/providers/anthropic.svg", "mediaType": "image/svg+xml", "sha256": "7fea3100bfc2a9480e181fc615d4791cab014f54674b83953785abb86dc293f0", "provenanceStatus": "proven", "source": {"kind": "git", "url": "https://github.com/GLINCKER/thesvg", "ref": "7870bc1c5f657d9accbb7f96cc457b8dd3363ee8", "path": "public/icons/anthropic/default.svg", "integrity": "sha256:7fea3100bfc2a9480e181fc615d4791cab014f54674b83953785abb86dc293f0", "match": "byte-exact"}, "upstreamLicenseClaim": {"value": "CC0-1.0", "assertedBy": "theSVG pinned registry", "evidence": "https://github.com/GLINCKER/thesvg/blob/7870bc1c5f657d9accbb7f96cc457b8dd3363ee8/src/data/icons.json", "independentlyVerified": false, "scope": "Upstream SVG copyright claim only; no trademark clearance.", "upstreamUrl": "https://www.anthropic.com/"}, "trademarkClearance": null, "evidenceNote": "Local bytes are byte-identical to the pinned theSVG source. This proves source provenance only, not copyright or trademark clearance."} @@ -80,7 +80,6 @@ {"recordType": "asset", "path": "public/providers/moonshot.svg", "mediaType": "image/svg+xml", "sha256": "a6ac95d972fdb044cd4155b0f75d9c1c816348868c810f63c7e5c8840c9e3e12", "provenanceStatus": "proven", "source": {"kind": "git", "url": "https://github.com/GLINCKER/thesvg", "ref": "7870bc1c5f657d9accbb7f96cc457b8dd3363ee8", "path": "public/icons/moonshot/default.svg", "integrity": "sha256:a6ac95d972fdb044cd4155b0f75d9c1c816348868c810f63c7e5c8840c9e3e12", "match": "byte-exact"}, "upstreamLicenseClaim": {"value": "MIT", "assertedBy": "theSVG pinned registry", "evidence": "https://github.com/GLINCKER/thesvg/blob/7870bc1c5f657d9accbb7f96cc457b8dd3363ee8/src/data/icons.json", "independentlyVerified": false, "scope": "Upstream SVG copyright claim only; no trademark clearance.", "upstreamUrl": "https://moonshot.cn"}, "trademarkClearance": null, "evidenceNote": "Local bytes are byte-identical to the pinned theSVG source. This proves source provenance only, not copyright or trademark clearance."} {"recordType": "asset", "path": "public/providers/morph.svg", "mediaType": "image/svg+xml", "sha256": "0fdb479e13c5d5de15aa89d1f87c8d55f8f8a56d33fc5dee8c566d2e0dbd5b96", "provenanceStatus": "proven", "source": {"kind": "git", "url": "https://github.com/GLINCKER/thesvg", "ref": "7870bc1c5f657d9accbb7f96cc457b8dd3363ee8", "path": "public/icons/morph/default.svg", "integrity": "sha256:0fdb479e13c5d5de15aa89d1f87c8d55f8f8a56d33fc5dee8c566d2e0dbd5b96", "match": "byte-exact"}, "upstreamLicenseClaim": {"value": "MIT", "assertedBy": "theSVG pinned registry", "evidence": "https://github.com/GLINCKER/thesvg/blob/7870bc1c5f657d9accbb7f96cc457b8dd3363ee8/src/data/icons.json", "independentlyVerified": false, "scope": "Upstream SVG copyright claim only; no trademark clearance.", "upstreamUrl": "https://morphllm.com"}, "trademarkClearance": null, "evidenceNote": "Local bytes are byte-identical to the pinned theSVG source. This proves source provenance only, not copyright or trademark clearance."} {"recordType": "asset", "path": "public/providers/nebius.svg", "mediaType": "image/svg+xml", "sha256": "fb190b4efb1d143442ef6c5eb0258801fc27b7316e88216c59a0f4b58b8b0281", "provenanceStatus": "proven", "source": {"kind": "git", "url": "https://github.com/GLINCKER/thesvg", "ref": "7870bc1c5f657d9accbb7f96cc457b8dd3363ee8", "path": "public/icons/nebius/default.svg", "integrity": "sha256:fb190b4efb1d143442ef6c5eb0258801fc27b7316e88216c59a0f4b58b8b0281", "match": "byte-exact"}, "upstreamLicenseClaim": {"value": "MIT", "assertedBy": "theSVG pinned registry", "evidence": "https://github.com/GLINCKER/thesvg/blob/7870bc1c5f657d9accbb7f96cc457b8dd3363ee8/src/data/icons.json", "independentlyVerified": false, "scope": "Upstream SVG copyright claim only; no trademark clearance.", "upstreamUrl": "https://nebius.com"}, "trademarkClearance": null, "evidenceNote": "Local bytes are byte-identical to the pinned theSVG source. This proves source provenance only, not copyright or trademark clearance."} -{"recordType": "asset", "path": "public/providers/nimble-search.svg", "mediaType": "image/svg+xml", "sha256": "c22d214880d1cbf48aa08617fbc245b96f7372fe5602ef1382978eee522c6575", "provenanceStatus": "unresolved", "source": null, "upstreamLicenseClaim": null, "trademarkClearance": null, "evidenceNote": "Added by an unrelated already-merged provider PR (#11620/#11629); no provenance research recorded yet. Flagged unresolved pending review, per schema v1 (non-blocking)."} {"recordType": "asset", "path": "public/providers/nomic.svg", "mediaType": "image/svg+xml", "sha256": "73cc513c9d5f460ec8f00a097f3fabaa54c0e1f824944412e9a4461c0620fba6", "provenanceStatus": "probable", "source": null, "upstreamLicenseClaim": null, "trademarkClearance": null, "evidenceNote": "Repository history shows a shared generic initial-badge pattern, but no immutable authorship or license evidence is recorded."} {"recordType": "asset", "path": "public/providers/novita.svg", "mediaType": "image/svg+xml", "sha256": "ab99ef3113a12e64ef8b44eda132094017359ede5bdb33e830ed855b69520612", "provenanceStatus": "proven", "source": {"kind": "git", "url": "https://github.com/GLINCKER/thesvg", "ref": "7870bc1c5f657d9accbb7f96cc457b8dd3363ee8", "path": "public/icons/novita/default.svg", "integrity": "sha256:ab99ef3113a12e64ef8b44eda132094017359ede5bdb33e830ed855b69520612", "match": "byte-exact"}, "upstreamLicenseClaim": {"value": "MIT", "assertedBy": "theSVG pinned registry", "evidence": "https://github.com/GLINCKER/thesvg/blob/7870bc1c5f657d9accbb7f96cc457b8dd3363ee8/src/data/icons.json", "independentlyVerified": false, "scope": "Upstream SVG copyright claim only; no trademark clearance.", "upstreamUrl": "https://novita.ai/"}, "trademarkClearance": null, "evidenceNote": "Local bytes are byte-identical to the pinned theSVG source. This proves source provenance only, not copyright or trademark clearance."} {"recordType": "asset", "path": "public/providers/nube.svg", "mediaType": "image/svg+xml", "sha256": "e5eff793cbc8a917e499c18365979f001010b229dd53b0802d5f29d9dfb963e1", "provenanceStatus": "probable", "source": null, "upstreamLicenseClaim": null, "trademarkClearance": null, "evidenceNote": "Repository PR #6926 describes this family as letter-in-circle placeholders, but original authorship and license were not independently proven."} @@ -90,7 +89,7 @@ {"recordType": "asset", "path": "public/providers/openai.svg", "mediaType": "image/svg+xml", "sha256": "db81a8225166f02f773304ba4d8f0141343da5f43870d8b41f10bf6bc59840c8", "provenanceStatus": "proven", "source": {"kind": "git", "url": "https://github.com/GLINCKER/thesvg", "ref": "7870bc1c5f657d9accbb7f96cc457b8dd3363ee8", "path": "public/icons/openai/default.svg", "integrity": "sha256:db81a8225166f02f773304ba4d8f0141343da5f43870d8b41f10bf6bc59840c8", "match": "byte-exact"}, "upstreamLicenseClaim": {"value": "MIT", "assertedBy": "theSVG pinned registry", "evidence": "https://github.com/GLINCKER/thesvg/blob/7870bc1c5f657d9accbb7f96cc457b8dd3363ee8/src/data/icons.json", "independentlyVerified": false, "scope": "Upstream SVG copyright claim only; no trademark clearance.", "upstreamUrl": "https://openai.com/"}, "trademarkClearance": null, "evidenceNote": "Local bytes are byte-identical to the pinned theSVG source. This proves source provenance only, not copyright or trademark clearance."} {"recordType": "asset", "path": "public/providers/openclaw.svg", "mediaType": "image/svg+xml", "sha256": "4123c0c75dda5b28e3e0d38075514085bf546178a620776344813c08fa41277c", "provenanceStatus": "proven", "source": {"kind": "npm", "url": "https://registry.npmjs.org/@lobehub/icons/-/icons-5.10.0.tgz", "ref": "5.10.0", "path": "package/es/OpenClaw/components/Color.js", "integrity": "sha512-CIpjkISCLRK7haDtSugGFd0o3odaJts8ewJOkUiEFtns3xvsqbl8i24eowBnjw+yMDQVQyNONlhqTD58YC6Ljg==", "packageShasum": "add1baced073a60157d39c7820b8d5c1928a1054", "match": "svg-path-data", "matchDetail": "All 6/6 local SVG path d values match the pinned Color component."}, "upstreamLicenseClaim": {"value": "MIT", "assertedBy": "@lobehub/icons@5.10.0 package/LICENSE", "evidence": "https://registry.npmjs.org/@lobehub/icons/-/icons-5.10.0.tgz#package/LICENSE", "independentlyVerified": true, "scope": "Pinned package distribution only; no trademark clearance."}, "trademarkClearance": null, "evidenceNote": "All local SVG path data matches the pinned LobeHub Color component. This proves source provenance only, not trademark clearance."} {"recordType": "asset", "path": "public/providers/openrouter.svg", "mediaType": "image/svg+xml", "sha256": "d05021526e72fddf3426eabc066924aca83da0cd66a699a3de3bac58ed2fe0a2", "provenanceStatus": "proven", "source": {"kind": "git", "url": "https://github.com/GLINCKER/thesvg", "ref": "7870bc1c5f657d9accbb7f96cc457b8dd3363ee8", "path": "public/icons/openrouter/default.svg", "integrity": "sha256:d05021526e72fddf3426eabc066924aca83da0cd66a699a3de3bac58ed2fe0a2", "match": "byte-exact"}, "upstreamLicenseClaim": {"value": "CC0-1.0", "assertedBy": "theSVG pinned registry", "evidence": "https://github.com/GLINCKER/thesvg/blob/7870bc1c5f657d9accbb7f96cc457b8dd3363ee8/src/data/icons.json", "independentlyVerified": false, "scope": "Upstream SVG copyright claim only; no trademark clearance.", "upstreamUrl": "https://openrouter.ai/"}, "trademarkClearance": null, "evidenceNote": "Local bytes are byte-identical to the pinned theSVG source. This proves source provenance only, not copyright or trademark clearance."} -{"recordType": "asset", "path": "public/providers/opper.svg", "mediaType": "image/svg+xml", "sha256": "e45d0409e7746946f204903ad6e7da267d805b7d7534fa684eeb7b8ac5717791", "provenanceStatus": "unresolved", "source": null, "upstreamLicenseClaim": null, "trademarkClearance": null, "evidenceNote": "Added by an unrelated already-merged provider PR (#11620/#11629); no provenance research recorded yet. Flagged unresolved pending review, per schema v1 (non-blocking)."} +{"recordType": "asset", "path": "public/providers/opper.svg", "mediaType": "image/svg+xml", "sha256": "e45d0409e7746946f204903ad6e7da267d805b7d7534fa684eeb7b8ac5717791", "provenanceStatus": "proven", "source": {"kind": "git", "url": "https://github.com/opper-ai/provider-omniroute", "ref": "9aacef7d6ae68d8d79f5aee042a25e9b646d2338", "path": "public/providers/opper.svg", "integrity": "sha256:e45d0409e7746946f204903ad6e7da267d805b7d7534fa684eeb7b8ac5717791", "match": "byte-exact"}, "upstreamLicenseClaim": {"value": "MIT", "assertedBy": "opper-ai/provider-omniroute LICENSE at the pinned commit", "evidence": "https://github.com/opper-ai/provider-omniroute/blob/9aacef7d6ae68d8d79f5aee042a25e9b646d2338/LICENSE", "independentlyVerified": true, "scope": "Pinned repository distribution only; no trademark clearance."}, "trademarkClearance": null, "evidenceNote": "Local bytes are byte-identical to the pinned official-organization repository source. The same commit carries an MIT license. This proves source and repository-license provenance only, not copyright or trademark clearance."} {"recordType": "asset", "path": "public/providers/orcarouter.svg", "mediaType": "image/svg+xml", "sha256": "06b36d030492901cada4c1e757b613c3ada340727d74f601fe407cfad7b529cf", "provenanceStatus": "probable", "source": null, "upstreamLicenseClaim": null, "trademarkClearance": null, "evidenceNote": "Repository PR #6926 describes this family as letter-in-circle placeholders, but original authorship and license were not independently proven."} {"recordType": "asset", "path": "public/providers/ovhcloud.svg", "mediaType": "image/svg+xml", "sha256": "ab65efec83d5106fa649e1f3ec5db98beb20ec6708158362844c912c34e1d31a", "provenanceStatus": "proven", "source": {"kind": "git", "url": "https://github.com/GLINCKER/thesvg", "ref": "7870bc1c5f657d9accbb7f96cc457b8dd3363ee8", "path": "public/icons/ovhcloud/default.svg", "integrity": "sha256:ab65efec83d5106fa649e1f3ec5db98beb20ec6708158362844c912c34e1d31a", "match": "byte-exact"}, "upstreamLicenseClaim": {"value": "brand-use", "assertedBy": "theSVG pinned registry", "evidence": "https://github.com/GLINCKER/thesvg/blob/7870bc1c5f657d9accbb7f96cc457b8dd3363ee8/src/data/icons.json", "independentlyVerified": false, "scope": "Upstream SVG copyright claim only; no trademark clearance.", "upstreamUrl": "https://ovhcloud.com"}, "trademarkClearance": null, "evidenceNote": "Local bytes are byte-identical to the pinned theSVG source. This proves source provenance only, not copyright or trademark clearance."} {"recordType": "asset", "path": "public/providers/perplexity.svg", "mediaType": "image/svg+xml", "sha256": "c7a4c847b6b3c0e8a10868d35b0b4a89727c03f8db3394060dcdb33c4b21c83b", "provenanceStatus": "probable", "source": null, "upstreamLicenseClaim": null, "trademarkClearance": null, "evidenceNote": "Repository PR #6317 and the asset structure indicate a likely source family, but no immutable upstream source or hash was proven."} diff --git a/public/providers/nimble-search.svg b/public/providers/nimble-search.svg deleted file mode 100644 index aa53f2fe5e..0000000000 --- a/public/providers/nimble-search.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - diff --git a/src/shared/components/ProviderIcon.tsx b/src/shared/components/ProviderIcon.tsx index 4336ec096d..2f4df48d16 100644 --- a/src/shared/components/ProviderIcon.tsx +++ b/src/shared/components/ProviderIcon.tsx @@ -141,7 +141,6 @@ const KNOWN_SVGS = new Set([ "moonshot", "morph", "nebius", - "nimble-search", "nlpcloud", "nomic", "novita", @@ -152,6 +151,7 @@ const KNOWN_SVGS = new Set([ "openai", "openclaw", "openrouter", + "opper", "orcarouter", "ovhcloud", "perplexity", @@ -240,6 +240,7 @@ const GENERIC_PROVIDER_IDS = new Set([ "leonardo", "modal", "modelscope", + "nimble-search", "nlpcloud", "oauth", "oci", diff --git a/tests/unit/check-provider-asset-provenance.test.ts b/tests/unit/check-provider-asset-provenance.test.ts index b4e06c9da2..c91d4daa7f 100644 --- a/tests/unit/check-provider-asset-provenance.test.ts +++ b/tests/unit/check-provider-asset-provenance.test.ts @@ -541,7 +541,7 @@ test("provider asset provenance gate binds auditedCommit to the physical provide } }); -test("repository provider asset manifest covers the audited 142-file snapshot", (t) => { +test("repository provider asset manifest covers the audited 141-file snapshot", (t) => { const manifestPath = join(REPO_ROOT, "config/quality/provider-assets-provenance.jsonl"); const { auditedCommit } = JSON.parse(readFileSync(manifestPath, "utf8").split("\n")[0]); if (!gitHasCommit(auditedCommit) && isShallowRepository()) { @@ -556,7 +556,7 @@ test("repository provider asset manifest covers the audited 142-file snapshot", assert.equal(result.status, 0, `${result.stdout}\n${result.stderr}`); assert.match( result.stdout, - /142\/142 registered; proven=71 probable=69 unresolved=2; duplicate-groups=1/ + /141\/141 registered; proven=72 probable=69 unresolved=0; duplicate-groups=1/ ); }); diff --git a/tests/unit/provider-assets-generic-fallback.test.mjs b/tests/unit/provider-assets-generic-fallback.test.mjs index 2f59cd805a..6112f06e7f 100644 --- a/tests/unit/provider-assets-generic-fallback.test.mjs +++ b/tests/unit/provider-assets-generic-fallback.test.mjs @@ -35,6 +35,7 @@ const LOCAL_SVG_IDS_WITHOUT_PROVENANCE = [ "leonardo", "modal", "modelscope", + "nimble-search", "nlpcloud", "oauth", "oci", @@ -177,9 +178,9 @@ const AUDITED_REFERENCE_FILES = [ ...referenceRoots.flatMap((directory) => collectTextFiles(join(root, directory))), ]; -test("provider bundle retires exactly the 78 unresolved assets and keeps the generic icon", () => { - assert.equal(retiredAssetNames.length, 78); - assert.equal(new Set(retiredAssetNames).size, 78); +test("provider bundle retires exactly the 79 unresolved assets and keeps the generic icon", () => { + assert.equal(retiredAssetNames.length, 79); + assert.equal(new Set(retiredAssetNames).size, 79); for (const assetName of retiredAssetNames) { assert.equal( @@ -196,8 +197,9 @@ test("provider bundle retires exactly the 78 unresolved assets and keeps the gen // provenance PRs (#11735, #11736, #11711) landed first and independently retired // 6 further unproven files this PR never targeted (freebuff-dark.svg, // freebuff-light.svg, freebuff.png, openvecta.svg, picoclaw.jpg, zoocode.png), - // so the real remaining count is 142, not 148. - assert.equal(distributedAssets.length, 142, "all 142 non-target assets must remain"); + // so the real pre-fix count was 142, not 148. This fix retires the unresolved + // Nimble asset as well, leaving 141 distributed assets. + assert.equal(distributedAssets.length, 141, "all 141 non-target assets must remain"); assert.ok(distributedAssets.includes("cli-generic.svg")); }); diff --git a/tests/unit/ui/ProviderIcon-icon-url.test.tsx b/tests/unit/ui/ProviderIcon-icon-url.test.tsx index 95de173526..a3fd0c3adb 100644 --- a/tests/unit/ui/ProviderIcon-icon-url.test.tsx +++ b/tests/unit/ui/ProviderIcon-icon-url.test.tsx @@ -54,6 +54,7 @@ const PROVIDER_IDS_WITHOUT_LOCAL_ASSET_PROVENANCE = [ "leonardo", "modal", "modelscope", + "nimble-search", "nlpcloud", "oauth", "oci", @@ -229,6 +230,7 @@ describe("ProviderIcon — local SVG dimensions", () => { it.each([ ["cline", "/providers/cline.svg"], ["kimi-coding", "/providers/kimi-logomark-light.svg"], + ["opper", "/providers/opper.svg"], ])("gives %s a definite square layout size", (providerId, expectedSrc) => { const container = renderIcon({ providerId, size: 24 }); const img = container.querySelector(`img[src="${expectedSrc}"]`); @@ -244,8 +246,8 @@ describe("ProviderIcon — local SVG dimensions", () => { describe("ProviderIcon — unresolved local asset provenance", () => { it("covers the complete provider and alias inventory", () => { - expect(PROVIDER_IDS_WITHOUT_LOCAL_ASSET_PROVENANCE).toHaveLength(78); - expect(new Set(PROVIDER_IDS_WITHOUT_LOCAL_ASSET_PROVENANCE)).toHaveLength(78); + expect(PROVIDER_IDS_WITHOUT_LOCAL_ASSET_PROVENANCE).toHaveLength(79); + expect(new Set(PROVIDER_IDS_WITHOUT_LOCAL_ASSET_PROVENANCE)).toHaveLength(79); }); it.each(PROVIDER_IDS_WITHOUT_LOCAL_ASSET_PROVENANCE)(