Skip to content

Commit 7019b4f

Browse files
author
hailory
committed
[AI] test(memory): add upgrade safety proof for provider.model backfill
Prove that existing indexes with empty adapter model metadata transition safely after the backfill: stored meta (written by the identity path) already carries the correct model; after upgrade, provider.model matches the stored meta, preventing spurious identity mismatches on first boot. Ref: #90042, #91691
1 parent 1989f81 commit 7019b4f

1 file changed

Lines changed: 54 additions & 1 deletion

File tree

extensions/memory-core/src/memory/embeddings.test.ts

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@ import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
33
import type { EmbeddingProviderAdapter } from "openclaw/plugin-sdk/embedding-providers";
44
import type { MemoryEmbeddingProviderAdapter } from "openclaw/plugin-sdk/memory-core-host-engine-embeddings";
55
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
6-
import { createEmbeddingProvider, resolveEmbeddingProviderFallbackModel } from "./embeddings.js";
6+
import {
7+
createEmbeddingProvider,
8+
resolveEmbeddingProviderFallbackModel,
9+
resolveEmbeddingProviderIndexIdentity,
10+
} from "./embeddings.js";
711

812
const mockEmbeddingRegistry = vi.hoisted(() => ({
913
genericAdapters: [] as EmbeddingProviderAdapter[],
@@ -297,6 +301,55 @@ describe("createEmbeddingProvider", () => {
297301
expect(result.provider?.model).toBe("text-embedding-3-large");
298302
});
299303

304+
it("upgrade safety: backfill aligns provider.model with identity resolution for existing indexes", async () => {
305+
// #90042 upgrade scenario: pre-fix, an adapter with defaultModel whose
306+
// create() returned provider.model="" would propagate the empty model
307+
// to every downstream consumer (identity checks, meta writes, chunk
308+
// model tags, vector-search filters). Post-fix, createWithAdapter
309+
// backfills the resolved model so provider.model matches what
310+
// resolveEmbeddingProviderIndexIdentity independently computes.
311+
//
312+
// Impact on existing indexes: stored meta was written by the identity
313+
// path (which already used resolveProviderModel), so it carries the
314+
// correct model. After upgrade, provider.model now matches the stored
315+
// meta → no spurious mismatch → no unnecessary reindex on first boot.
316+
registerMemoryEmbeddingProvider({
317+
id: "openai",
318+
transport: "remote",
319+
defaultModel: "text-embedding-3-small",
320+
resolveIndexIdentity: (options) => ({
321+
model: options.model,
322+
cacheKeyData: {},
323+
}),
324+
create: async () => ({
325+
provider: {
326+
id: "openai",
327+
model: "",
328+
embedQuery: async () => [1],
329+
embedBatch: async (texts) => texts.map(() => [1]),
330+
},
331+
}),
332+
});
333+
334+
const result = await createEmbeddingProvider({
335+
...createOptions("openai"),
336+
model: "",
337+
});
338+
339+
expect(result.provider?.model).toBe("text-embedding-3-small");
340+
341+
const identity = resolveEmbeddingProviderIndexIdentity({
342+
...createOptions("openai"),
343+
model: "",
344+
});
345+
expect(identity?.provider.model).toBe("text-embedding-3-small");
346+
347+
// Post-upgrade invariant: provider.model and identity model are
348+
// consistent, preventing the "index was built for model X, expected "
349+
// mismatch that #90042 and #91691 describe.
350+
expect(result.provider?.model).toBe(identity?.provider.model);
351+
});
352+
300353
it("uses config-scoped lookup for generic fallback model resolution", () => {
301354
registerGenericEmbeddingProvider({
302355
id: "openai-compatible",

0 commit comments

Comments
 (0)