/** * F3: Temporal Validity Windows — tests for eventTime, validUntil, * retrieval filtering, and auto-GC decay acceleration. */ import { describe, expect, test } from "bun:test"; import { defaultEvolution, parseEvolution, patchEvolution, type EvolutionMetadata, } from "../memory-evolution.js"; // --------------------------------------------------------------------------- // Schema: eventTime field // --------------------------------------------------------------------------- describe("F3: eventTime in EvolutionMetadata", () => { test("defaultEvolution includes eventTime: null", () => { const evo = defaultEvolution(1000); expect(evo.eventTime).toBeNull(); expect(evo.validFrom).toBe(1000); expect(evo.validUntil).toBeNull(); }); test("parseEvolution extracts eventTime from metadata", () => { const metadata = JSON.stringify({ evolution: { status: "active", version: 1, accessCount: 0, lastAccessedAt: null, supersededBy: null, supersedes: null, evolutionNote: null, consolidatedInto: null, contributedToPattern: null, sourceMemories: [], validFrom: 1000, validUntil: null, eventTime: 5000, }, }); const evo = parseEvolution(metadata); expect(evo.eventTime).toBe(5000); }); test("parseEvolution defaults eventTime to null when missing", () => { const metadata = JSON.stringify({ evolution: { status: "active", version: 1, validFrom: 1000, }, }); const evo = parseEvolution(metadata); expect(evo.eventTime).toBeNull(); }); test("patchEvolution can set eventTime", () => { const metadata = JSON.stringify({ evolution: defaultEvolution(1000) }); const patched = patchEvolution(metadata, { eventTime: 9999 }); const evo = parseEvolution(patched); expect(evo.eventTime).toBe(9999); }); test("patchEvolution can set validUntil", () => { const metadata = JSON.stringify({ evolution: defaultEvolution(1000) }); const patched = patchEvolution(metadata, { validUntil: 2000 }); const evo = parseEvolution(patched); expect(evo.validUntil).toBe(2000); // Other fields unchanged expect(evo.validFrom).toBe(1000); expect(evo.status).toBe("active"); }); }); // --------------------------------------------------------------------------- // Retrieval: filterByValidity (unit-level via import) // --------------------------------------------------------------------------- // We test the filterByValidity logic indirectly through the exported // parseEvolution + the contract: expired memories get score * 0.2. // Direct integration tested via full retriever in separate test files. describe("F3: validity window logic", () => { function makeEvo(overrides: Partial = {}): EvolutionMetadata { return { ...defaultEvolution(1000), ...overrides }; } test("memory with validUntil in the future is not expired", () => { const now = 5000; const evo = makeEvo({ validUntil: 10000 }); // validUntil > now → not expired expect(evo.validUntil).toBeGreaterThan(now); }); test("memory with validUntil in the past is expired", () => { const now = 5000; const evo = makeEvo({ validUntil: 3000 }); // validUntil < now → expired expect(evo.validUntil).toBeLessThan(now); }); test("memory without validUntil never expires", () => { const evo = makeEvo({ validUntil: null }); expect(evo.validUntil).toBeNull(); }); test("point-in-time query: memory valid in range", () => { const evo = makeEvo({ validFrom: 1000, validUntil: 5000 }); const queryTime = 3000; expect(evo.validFrom).toBeLessThanOrEqual(queryTime); expect(evo.validUntil).toBeGreaterThanOrEqual(queryTime); }); test("point-in-time query: memory not yet valid", () => { const evo = makeEvo({ validFrom: 5000, validUntil: 10000 }); const queryTime = 3000; expect(evo.validFrom).toBeGreaterThan(queryTime); }); test("point-in-time query: memory already expired at query time", () => { const evo = makeEvo({ validFrom: 1000, validUntil: 3000 }); const queryTime = 5000; expect(evo.validUntil).toBeLessThan(queryTime); }); }); // --------------------------------------------------------------------------- // Auto-GC: decay acceleration for expired memories // --------------------------------------------------------------------------- describe("F3: GC decay acceleration", () => { test("expired memory decay score is halved", () => { // Simulate the GC logic: if validUntil < now, decayScore *= 0.5 const baseDecayScore = 0.30; const now = 10000; const validUntil = 5000; // expired const adjusted = validUntil < now ? baseDecayScore * 0.5 : baseDecayScore; expect(adjusted).toBe(0.15); }); test("non-expired memory decay score unchanged", () => { const baseDecayScore = 0.30; const now = 10000; const validUntil = 20000; // still valid const adjusted = validUntil < now ? baseDecayScore * 0.5 : baseDecayScore; expect(adjusted).toBe(0.30); }); test("memory without validUntil — decay score unchanged", () => { const baseDecayScore = 0.30; const validUntil = null; const adjusted = (validUntil != null && validUntil < Date.now()) ? baseDecayScore * 0.5 : baseDecayScore; expect(adjusted).toBe(0.30); }); });