From f2dadd46391c596a0758863c99c8e9ca6db045c8 Mon Sep 17 00:00:00 2001 From: Jinwoo-H Date: Tue, 8 Sep 2026 23:57:59 -0400 Subject: [PATCH] test(ai-vault): pin the untrustworthy-key mechanism, not just its effect Both refusal tests asserted that a later healthy scan re-enriches, which a plain store would also satisfy once the resume cursor was preserved. Assert the cache entry directly: its mtime is the unmatchable sentinel and its resume point survives. The sentinel is exported so the tests name the contract instead of repeating -1. --- .../session-scanner-cursor-chat-meta.test.ts | 19 ++++++++++++++++--- .../ai-vault/session-scanner-parse-cache.ts | 2 +- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/main/ai-vault/session-scanner-cursor-chat-meta.test.ts b/src/main/ai-vault/session-scanner-cursor-chat-meta.test.ts index 3420705b19b..9408a0629b9 100644 --- a/src/main/ai-vault/session-scanner-cursor-chat-meta.test.ts +++ b/src/main/ai-vault/session-scanner-cursor-chat-meta.test.ts @@ -67,8 +67,10 @@ import { scanAiVaultSessions } from './session-scanner' import { createSessionParseStats, parseAgentSessionFileCached, - resetSessionParseCacheForTests + resetSessionParseCacheForTests, + UNMATCHABLE_MTIME_MS } from './session-scanner-parse-cache' +import { getSessionParseCacheEntry } from './session-parse-cache-store' import { appendFile, stat, truncate } from 'node:fs/promises' import { isolatedScanRoots } from './session-scanner-test-fixtures' import type { FileWithMtime, SessionFileDiscovery } from './session-scanner-types' @@ -395,6 +397,12 @@ describe('cursor chat meta scan failures', () => { expect(refusedCursor.map((session) => session.cwd)).toEqual([null, null]) expect(refused.issues).toHaveLength(1) expect(refused.issues[0].path).toBe(join(cursorHome, 'chats')) + // The un-enriched parse is kept only under an unmatchable key. + expect( + refused.sessions + .filter((session) => session.agent === 'cursor') + .map((session) => getSessionParseCacheEntry(session.filePath)?.mtimeMs) + ).toEqual([UNMATCHABLE_MTIME_MS, UNMATCHABLE_MTIME_MS]) failMetaJsonReads = false failMetaJsonStats = false @@ -493,8 +501,13 @@ describe('cursor chat meta cache keys', () => { const refused = await parseCursor(await cursorCandidate(cursorHome)) expect(refused.incremental).toBe(1) - // The poisoned key must force a re-read, but not a full one: the resume - // point survives, so the next healthy scan resumes at the stored offset. + // Pin the mechanism, not just its effect: the entry is kept under a key no + // stat can produce, and it still carries the fold to resume from. + const poisoned = getSessionParseCacheEntry(transcriptPath) + expect(poisoned?.mtimeMs).toBe(UNMATCHABLE_MTIME_MS) + expect(poisoned?.resume).not.toBeNull() + + // So the next healthy scan re-reads, but only the appended bytes. failMetaJsonReads = false const healed = await parseCursor(await cursorCandidate(cursorHome)) expect(healed.incremental).toBe(1) diff --git a/src/main/ai-vault/session-scanner-parse-cache.ts b/src/main/ai-vault/session-scanner-parse-cache.ts index f3c7b9aa8f1..35a32a5b4f4 100644 --- a/src/main/ai-vault/session-scanner-parse-cache.ts +++ b/src/main/ai-vault/session-scanner-parse-cache.ts @@ -80,7 +80,7 @@ function resumableStateFactoryFor( // No stat can report it, so `unchanged` is always false for such an entry // while its resume point stays usable. -const UNMATCHABLE_MTIME_MS = -1 +export const UNMATCHABLE_MTIME_MS = -1 export type SessionParseStats = TranscriptReadStats & { reused: number