From 705dea70f09692b3a31b74d0c92aa369839b9cc4 Mon Sep 17 00:00:00 2001 From: 777genius Date: Sun, 31 May 2026 19:19:53 +0300 Subject: [PATCH] fix(team): remove mismatched task projection caches --- src/main/workers/team-fs-worker.ts | 1 + .../team/TeamFsWorker.integration.test.ts | 60 +++++++++++++++++++ 2 files changed, 61 insertions(+) diff --git a/src/main/workers/team-fs-worker.ts b/src/main/workers/team-fs-worker.ts index 785d3ae5..23a1ec97 100644 --- a/src/main/workers/team-fs-worker.ts +++ b/src/main/workers/team-fs-worker.ts @@ -848,6 +848,7 @@ async function readPersistentTaskProjectionCache( parsed.optionKey !== optionKey ) { taskDiag.persistentCacheMisses++; + await fs.promises.rm(cachePath, { force: true }).catch(() => undefined); return null; } if (!isRecord(parsed.entries)) { diff --git a/test/main/services/team/TeamFsWorker.integration.test.ts b/test/main/services/team/TeamFsWorker.integration.test.ts index 8035b534..c7d3226e 100644 --- a/test/main/services/team/TeamFsWorker.integration.test.ts +++ b/test/main/services/team/TeamFsWorker.integration.test.ts @@ -864,6 +864,66 @@ describe('team-fs-worker integration', () => { } }); + it('removes mismatched persisted task projection cache metadata', async () => { + const workerPath = await getWorkerPath(); + tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'team-fs-worker-')); + const tasksBase = path.join(tempDir, 'tasks'); + const projectionCacheBase = path.join(tempDir, 'projection-cache'); + const teamName = 'mismatched-persistent-cache-team'; + const tasksDir = path.join(tasksBase, teamName); + const taskPath = path.join(tasksDir, '1.json'); + await fs.mkdir(tasksDir, { recursive: true }); + await fs.writeFile( + taskPath, + JSON.stringify({ + id: '1', + subject: 'Temporary subject', + status: 'pending', + createdAt: '2026-05-02T12:00:00.000Z', + }), + 'utf8' + ); + + const writerWorker = createWorker(workerPath); + let cachePath = ''; + try { + const { result, diag } = await callWorker(writerWorker, 'getAllTasks', { + tasksBase, + projectionCacheBase, + maxTaskBytes: 128 * 1024, + maxTaskReadMs: 5_000, + concurrency: 2, + }); + expect(Array.isArray(result) ? result[0] : null).toMatchObject({ teamName }); + expect((diag as Record)?.persistentCacheWrites).toBe(1); + const cacheFiles = await fs.readdir(path.join(projectionCacheBase, 'v1')); + expect(cacheFiles).toHaveLength(1); + cachePath = path.join(projectionCacheBase, 'v1', cacheFiles[0]); + } finally { + await writerWorker.terminate(); + } + await fs.rm(taskPath); + + const mismatchWorker = createWorker(workerPath); + try { + const mismatch = await callGetAllTasks(mismatchWorker, tasksBase, projectionCacheBase); + expect(mismatch.tasks).toHaveLength(0); + expect(mismatch.diag?.persistentCacheMisses).toBe(1); + await expect(fs.stat(cachePath)).rejects.toMatchObject({ code: 'ENOENT' }); + } finally { + await mismatchWorker.terminate(); + } + + const cleanWorker = createWorker(workerPath); + try { + const clean = await callGetAllTasks(cleanWorker, tasksBase, projectionCacheBase); + expect(clean.tasks).toHaveLength(0); + expect(clean.diag?.persistentCacheMisses).toBe(0); + } finally { + await cleanWorker.terminate(); + } + }); + it('rejects persisted task projections that contain deleted tasks as task records', async () => { const workerPath = await getWorkerPath(); tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'team-fs-worker-'));