refactor: consolidate duplicated plugin state and doctor migration plumbing onto SDK seams (#99850)

* refactor(plugin-sdk): add createPersistentDedupeCache and migrate channel presence caches

* refactor(matrix): adopt SDK approval reaction target store

* refactor(plugin-sdk): share doctor legacy-state migration fs helpers

* refactor(memory-core): dedupe qmd cache entry envelope validation

* chore(plugin-sdk): pin surface budgets for shared dedupe and doctor helpers

* test(matrix): use future approval expiry fixtures for reaction targets

* test(matrix): use future approval expiry fixtures for reaction targets
This commit is contained in:
Peter Steinberger
2026-07-04 01:51:03 -07:00
committed by GitHub
parent 3d404478b8
commit eafe2a8d0b
27 changed files with 565 additions and 946 deletions

View File

@@ -0,0 +1,116 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import { createPersistentDedupeCache } from "./dedupe-runtime.js";
type Record = { at: number };
function createMemoryStore() {
const entries = new Map<string, Record>();
return {
entries,
store: {
register: vi.fn(async (key: string, value: Record) => {
entries.set(key, value);
}),
lookup: vi.fn(async (key: string) => entries.get(key)),
},
};
}
function createCache(params?: {
openStore?: () => ReturnType<typeof createMemoryStore>["store"] | undefined;
logError?: (error: unknown) => void;
readTimestamp?: (record: Record) => number | undefined;
}) {
const backing = createMemoryStore();
const cache = createPersistentDedupeCache<Record>({
// Plain Symbol() is unique per cache, so parallel tests never share memory layers.
globalKey: Symbol("test.persistent-dedupe"),
ttlMs: 60_000,
maxSize: 100,
persistent: {
namespace: "test.persistent-dedupe",
maxEntries: 100,
openStore: params?.openStore ?? (() => backing.store),
logError: params?.logError,
readTimestamp: params?.readTimestamp,
},
});
return { cache, backing };
}
describe("createPersistentDedupeCache", () => {
afterEach(() => {
vi.useRealTimers();
});
it("records presence in both layers and answers from memory first", async () => {
const { cache, backing } = createCache();
await cache.register("k1", { at: 1 });
expect(cache.peek("k1")).toBe(true);
expect(await cache.lookup("k1")).toBe(true);
expect(backing.store.register).toHaveBeenCalledWith("k1", { at: 1 });
expect(backing.store.lookup).not.toHaveBeenCalled();
});
it("falls back to persistence and re-primes memory on a hit", async () => {
const { cache, backing } = createCache();
backing.entries.set("k2", { at: 42 });
expect(cache.peek("k2")).toBe(false);
expect(await cache.lookup("k2")).toBe(true);
expect(cache.peek("k2")).toBe(true);
});
it("re-primes memory with the persisted timestamp when provided", async () => {
vi.useFakeTimers();
vi.setSystemTime(1_000_000);
const { cache, backing } = createCache({ readTimestamp: (record) => record.at });
backing.entries.set("k3", { at: 1_000_000 - 59_000 });
expect(await cache.lookup("k3")).toBe(true);
// Re-primed at the original timestamp: expires 59s later instead of a fresh 60s TTL.
vi.setSystemTime(1_000_000 + 2_000);
expect(cache.peek("k3")).toBe(false);
});
it("disables persistence after an open failure and never rejects", async () => {
const logError = vi.fn();
const openStore = vi.fn(() => {
throw new Error("sqlite unavailable");
});
const { cache } = createCache({ openStore, logError });
await expect(cache.register("k4", { at: 1 })).resolves.toBeUndefined();
expect(cache.peek("k4")).toBe(true);
expect(await cache.lookup("k5")).toBe(false);
expect(openStore).toHaveBeenCalledTimes(1);
expect(logError).toHaveBeenCalledTimes(1);
});
it("disables persistence after a lookup failure", async () => {
const logError = vi.fn();
const store = {
register: vi.fn(async () => {}),
lookup: vi.fn(async () => {
throw new Error("read failed");
}),
};
const { cache } = createCache({ openStore: () => store, logError });
expect(await cache.lookup("k6")).toBe(false);
expect(logError).toHaveBeenCalledTimes(1);
await cache.register("k7", { at: 1 });
expect(store.register).not.toHaveBeenCalled();
});
it("clearForTest resets memory and re-enables persistence", async () => {
const openStore = vi
.fn()
.mockImplementationOnce(() => {
throw new Error("boom");
})
.mockImplementation(() => createMemoryStore().store);
const { cache } = createCache({ openStore });
await cache.register("k8", { at: 1 });
cache.clearForTest();
expect(cache.peek("k8")).toBe(false);
await cache.register("k9", { at: 1 });
expect(openStore).toHaveBeenCalledTimes(2);
});
});

View File

@@ -1,3 +1,120 @@
// In-memory dedupe helpers for plugin runtime hot paths.
import { resolveGlobalDedupeCache } from "../infra/dedupe.js";
import type { OpenKeyedStoreOptions } from "./plugin-state-runtime.js";
export { createDedupeCache, resolveGlobalDedupeCache } from "../infra/dedupe.js";
type PersistentDedupeStore<TRecord> = {
register(key: string, value: TRecord, opts?: { ttlMs?: number }): Promise<void>;
lookup(key: string): Promise<TRecord | undefined>;
};
/** Dual-layer presence cache: process-memory dedupe plus best-effort persistent state. */
export type PersistentDedupeCache<TRecord> = {
/** Memory-only presence check without refreshing recency. */
peek(key: string): boolean;
/** Memory-first presence check; falls back to persistence and re-primes memory on a hit. */
lookup(key: string): Promise<boolean>;
/** Records presence in memory and best-effort persistence. Never rejects. */
register(key: string, record: TRecord, opts?: { at?: number }): Promise<void>;
/** Clears memory and re-enables a persistent layer disabled by an earlier failure. */
clearForTest(): void;
};
/**
* Creates a channel-family presence cache backed by a global in-memory dedupe layer
* plus a lazily opened plugin keyed store. Persistence is best effort: the first
* open/read/write failure disables the persistent layer for the process so message
* handling never breaks on state errors, matching the shipped channel-cache contract.
*/
export function createPersistentDedupeCache<TRecord>(params: {
/** Global symbol key so the memory layer stays shared across bundled chunks. */
globalKey: symbol;
ttlMs: number;
maxSize: number;
persistent: {
namespace: string;
maxEntries: number;
/** Usually `() => runtime?.state.openKeyedStore(options)`; undefined skips persistence. */
openStore: (options: OpenKeyedStoreOptions) => PersistentDedupeStore<TRecord> | undefined;
logError?: (error: unknown) => void;
/** Memory re-prime timestamp after a persistent hit; defaults to now. */
readTimestamp?: (record: TRecord) => number | undefined;
};
}): PersistentDedupeCache<TRecord> {
const memory = resolveGlobalDedupeCache(params.globalKey, {
ttlMs: params.ttlMs,
maxSize: params.maxSize,
});
let persistentStore: PersistentDedupeStore<TRecord> | undefined;
let persistentStoreDisabled = false;
const disablePersistentStore = (error: unknown) => {
persistentStoreDisabled = true;
persistentStore = undefined;
params.persistent.logError?.(error);
};
const getPersistentStore = (): PersistentDedupeStore<TRecord> | undefined => {
if (persistentStoreDisabled) {
return undefined;
}
if (persistentStore) {
return persistentStore;
}
try {
persistentStore = params.persistent.openStore({
namespace: params.persistent.namespace,
maxEntries: params.persistent.maxEntries,
defaultTtlMs: params.ttlMs,
});
return persistentStore;
} catch (error) {
disablePersistentStore(error);
return undefined;
}
};
return {
peek: (key) => memory.peek(key),
lookup: async (key) => {
if (memory.peek(key)) {
return true;
}
const store = getPersistentStore();
if (!store) {
return false;
}
let record: TRecord | undefined;
try {
record = await store.lookup(key);
} catch (error) {
disablePersistentStore(error);
return false;
}
if (record === undefined) {
return false;
}
memory.check(key, params.persistent.readTimestamp?.(record));
return true;
},
register: async (key, record, opts) => {
memory.check(key, opts?.at);
const store = getPersistentStore();
if (!store) {
return;
}
try {
await store.register(key, record);
} catch (error) {
disablePersistentStore(error);
}
},
clearForTest: () => {
memory.clear();
persistentStore = undefined;
persistentStoreDisabled = false;
},
};
}

View File

@@ -29,4 +29,8 @@ export type {
PluginDoctorStateMigration,
PluginDoctorStateMigrationContext,
} from "../plugins/doctor-contract-registry.js";
export {
archiveLegacyStateSource,
legacyStateFileExists,
} from "../plugins/doctor-state-migration-fs.js";
export type { DoctorSessionRouteStateOwner } from "../plugins/doctor-session-route-state-owner-types.js";