mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-05 03:21:35 +00:00
fix(state): preserve doctor import TTL
This commit is contained in:
committed by
Josh Avant
parent
d25e89d1ee
commit
505d7b9f52
@@ -1006,4 +1006,45 @@ describe("matrix doctor contract state migrations", () => {
|
||||
].toSorted(),
|
||||
);
|
||||
});
|
||||
|
||||
it("preserves a legacy inbound dedupe marker's remaining TTL", async () => {
|
||||
const stateDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-matrix-doctor-"));
|
||||
tempDirs.push(stateDir);
|
||||
const env = { OPENCLAW_STATE_DIR: stateDir };
|
||||
const io = { context: createContext(), env };
|
||||
const now = 2_000_000_000_000;
|
||||
const remainingTtlMs = 1_000;
|
||||
const roomId = "!room:example.org";
|
||||
const eventId = "$near-expiry";
|
||||
const key = `ops\0${roomId}\0${eventId}`;
|
||||
const markerTs = now - MATRIX_INBOUND_DEDUPE_TTL_MS + remainingTtlMs;
|
||||
const storedEntry = createPersistentDedupeImportEntry({ key, seenAt: markerTs });
|
||||
const nowSpy = vi.spyOn(Date, "now").mockReturnValue(now);
|
||||
|
||||
await expect(
|
||||
importNewestInboundDedupeMarkers({
|
||||
io,
|
||||
now,
|
||||
markers: [
|
||||
{
|
||||
accountId: "ops",
|
||||
roomId,
|
||||
eventId,
|
||||
ts: markerTs,
|
||||
},
|
||||
],
|
||||
}),
|
||||
).resolves.toEqual({ imported: 1, total: 1 });
|
||||
|
||||
const store = createPluginStateKeyedStoreForTests<PersistentDedupeEntry>("matrix", {
|
||||
namespace: resolveMatrixInboundDedupeStateNamespace(),
|
||||
maxEntries: 20_000,
|
||||
defaultTtlMs: MATRIX_INBOUND_DEDUPE_TTL_MS,
|
||||
env,
|
||||
});
|
||||
nowSpy.mockReturnValue(now + remainingTtlMs - 1);
|
||||
await expect(store.lookup(storedEntry.key)).resolves.toEqual(storedEntry.value);
|
||||
nowSpy.mockReturnValue(now + remainingTtlMs + 1);
|
||||
await expect(store.lookup(storedEntry.key)).resolves.toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -449,6 +449,7 @@ export async function importNewestInboundDedupeMarkers(params: {
|
||||
key: entry.key,
|
||||
value: entry.value,
|
||||
createdAt: marker.ts,
|
||||
...(entry.ttlMs != null ? { ttlMs: entry.ttlMs } : {}),
|
||||
})),
|
||||
);
|
||||
const importedKeys = new Set((await store.entries()).map((entry) => entry.key));
|
||||
|
||||
@@ -285,7 +285,7 @@ function createPluginDoctorStateMigrationContext(
|
||||
},
|
||||
importPluginStateEntries(
|
||||
options: OpenKeyedStoreOptions,
|
||||
entries: readonly { key: string; value: unknown; createdAt: number }[],
|
||||
entries: readonly { key: string; value: unknown; createdAt: number; ttlMs?: number }[],
|
||||
) {
|
||||
importPluginStateEntriesForDoctor(pluginId, { ...options, env: options.env ?? env }, entries);
|
||||
},
|
||||
|
||||
@@ -69,6 +69,7 @@ type PluginStateImportEntry = {
|
||||
key: string;
|
||||
value: unknown;
|
||||
createdAt: number;
|
||||
ttlMs?: number;
|
||||
};
|
||||
|
||||
const namespaceOptionSignatures = new Map<string, StoreOptionSignature>();
|
||||
@@ -555,7 +556,7 @@ export function registerPluginStateSyncSequencedJournalEntry(params: {
|
||||
});
|
||||
}
|
||||
|
||||
/** Doctor-only import that preserves source age for retention ordering. */
|
||||
/** Doctor-only import that preserves source age and remaining retention. */
|
||||
export function importPluginStateEntriesForDoctor(
|
||||
pluginId: string,
|
||||
options: OpenKeyedStoreOptions,
|
||||
@@ -575,7 +576,12 @@ export function importPluginStateEntriesForDoctor(
|
||||
if (!Number.isSafeInteger(entry.createdAt)) {
|
||||
throw invalidInput("plugin state import createdAt must be a safe integer", "register");
|
||||
}
|
||||
const prepared = prepareRegisterParams(entry.key, entry.value, defaultTtlMs);
|
||||
const prepared = prepareRegisterParams(
|
||||
entry.key,
|
||||
entry.value,
|
||||
defaultTtlMs,
|
||||
entry.ttlMs != null ? { ttlMs: entry.ttlMs } : undefined,
|
||||
);
|
||||
pluginStateRegister({
|
||||
pluginId,
|
||||
namespace,
|
||||
|
||||
@@ -59,10 +59,10 @@ export type PluginDoctorStateMigrationDetection = {
|
||||
|
||||
export type PluginDoctorStateMigrationContext = {
|
||||
openPluginStateKeyedStore: <T>(options: OpenKeyedStoreOptions) => PluginStateKeyedStore<T>;
|
||||
/** Doctor-only batch import preserving source age for retention ordering. */
|
||||
/** Doctor-only batch import preserving source age and remaining retention. */
|
||||
importPluginStateEntries?: (
|
||||
options: OpenKeyedStoreOptions,
|
||||
entries: readonly { key: string; value: unknown; createdAt: number }[],
|
||||
entries: readonly { key: string; value: unknown; createdAt: number; ttlMs?: number }[],
|
||||
) => void;
|
||||
/** Plugin-wide live-row capacity for import preflight. Older test hosts may omit it. */
|
||||
getPluginStateCapacity?: () => { liveEntries: number; maxEntries: number };
|
||||
|
||||
Reference in New Issue
Block a user