From fdcba3e1b2d475d1755e43c447929f17cc6d0c7f Mon Sep 17 00:00:00 2001 From: Mohammad Hijjawi Date: Sat, 26 Sep 2026 16:18:18 +0700 Subject: [PATCH] fix(capabilities): stop caching the catalog source per module copy (#4351) --- open-sse/providers/capabilities.js | 8 ++++---- tests/unit/model-catalog-scope.test.js | 16 ++++++++++++++++ 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/open-sse/providers/capabilities.js b/open-sse/providers/capabilities.js index dd61b3e2..e3bb58ac 100644 --- a/open-sse/providers/capabilities.js +++ b/open-sse/providers/capabilities.js @@ -484,7 +484,8 @@ const MODALITY_KEYS = ["vision", "pdf", "audioInput", "videoInput"]; // The server bundles this module into every route chunk that needs it, and each // copy carries its own module state, so an install landing in the copy the // startup hook imported stays invisible to the copy resolving requests. The slot -// lives on globalThis instead; the local binding is the fast path. +// lives on globalThis instead, and every read goes through it: caching it locally +// would keep a reader alive in other copies after setCatalogSource(null). let catalogSource = null; /** @@ -498,9 +499,8 @@ export function setCatalogSource(source) { } function getCatalogSource() { - if (catalogSource) return catalogSource; - if (typeof globalThis === "undefined") return null; - return (catalogSource = globalThis.__9rCatalogSource || null); + if (typeof globalThis === "undefined") return catalogSource; + return globalThis.__9rCatalogSource || null; } // Apply the synced catalog + name heuristic on top of a table-resolved result. diff --git a/tests/unit/model-catalog-scope.test.js b/tests/unit/model-catalog-scope.test.js index c582d28f..5177901f 100644 --- a/tests/unit/model-catalog-scope.test.js +++ b/tests/unit/model-catalog-scope.test.js @@ -122,6 +122,22 @@ describe("model catalog", () => { } expect(globalThis.__9rCatalogSource).toBeNull(); }); + + it("detaches the source from a copy that already resolved through it", async () => { + capabilities.setCatalogSource({ + getModalities: (provider) => (provider === "gateway-a" ? { vision: true } : null), + getLimits: () => null, + }); + const other = await import("../../open-sse/providers/capabilities.js?copy=3"); + try { + expect(other.getCapabilitiesForModel("gateway-a", "laguna-9-preview").vision).toBe(true); + } finally { + capabilities.setCatalogSource(null); + } + // the sync resets the source before rebuilding; a copy that has read the + // slot once must not keep serving the uninstalled reader + expect(other.getCapabilitiesForModel("gateway-a", "laguna-9-preview").vision).toBe(false); + }); }); describe("catalog schema", () => {