From 78d2f01ec80e62c7627babeec3874e7ae8e0169b Mon Sep 17 00:00:00 2001 From: "haozhe.yang" Date: Wed, 5 Aug 2026 13:37:49 +0800 Subject: [PATCH] test(agent-core-v2): stop the reconcile loop during the sessionIndex read baseline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The baseline gates warm reads on behavioral work counts, including zero directory listings inside each counting window. The service's background reconcile loop (60s interval) runs an authoritative scan on every tick — two storage.list calls — and once a slow CI runner stretches the test past that interval, a tick lands inside a counting window on every attempt and is attributed to the read under test, failing fsLists === 0 even with the retry (observed on run 30975794688: 168s across two attempts, fsLists = 2 both times). Freeze the loop right after prepare() via a new stopReconcileLoop() test hook (sibling of reconcileNow/reprojectNow). The only remaining directory listing sources inside a window are the fallback read paths themselves, so a non-zero count is deterministically a real regression again. --- .../src/app/sessionIndex/sessionIndexService.ts | 6 ++++++ .../test/app/sessionIndex/sessionIndex.test.ts | 10 ++++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/packages/agent-core-v2/src/app/sessionIndex/sessionIndexService.ts b/packages/agent-core-v2/src/app/sessionIndex/sessionIndexService.ts index 71dc531096..40582755b6 100644 --- a/packages/agent-core-v2/src/app/sessionIndex/sessionIndexService.ts +++ b/packages/agent-core-v2/src/app/sessionIndex/sessionIndexService.ts @@ -246,6 +246,12 @@ export class FileSessionIndex extends Disposable implements ISessionIndex { await this.ensureProjection(); } + /** Test hook: stop the background reconcile loop, so measurement windows + * contain only the operations under test. */ + stopReconcileLoop(): void { + this.reconcileTimer.cancel(); + } + private async tick(): Promise { if (!this.readModelEnabled()) return; if (this.state === 'degraded') { diff --git a/packages/agent-core-v2/test/app/sessionIndex/sessionIndex.test.ts b/packages/agent-core-v2/test/app/sessionIndex/sessionIndex.test.ts index eb74744050..5bd3931026 100644 --- a/packages/agent-core-v2/test/app/sessionIndex/sessionIndex.test.ts +++ b/packages/agent-core-v2/test/app/sessionIndex/sessionIndex.test.ts @@ -888,8 +888,11 @@ describe('FileSessionIndex (read model)', () => { // every warm read must touch a bounded number of store rows and zero // session directories, and that work must be identical at 1k, 10k, and 50k // sessions — a linear regression changes the counts deterministically, on - // any runner. The single retry absorbs a background reconcile tick (60s - // interval) landing inside a counting window. + // any runner. The background reconcile loop is stopped right after + // prepare(): a tick's authoritative scan enumerates the session + // directories (60s interval), and on a runner slow enough for the test to + // cross that interval it would land inside a counting window and be + // attributed to the read under test. The retry absorbs runner hiccups. const baseline = { retry: 1, timeout: 120_000 }; it('baseline: warm listRecent(limit=20) at 1k vs 10k vs 50k sessions', baseline, async () => { @@ -907,6 +910,9 @@ describe('FileSessionIndex (read model)', () => { // directly into the generation (the mirror path is covered elsewhere). await seedSession('seed', { createdAt: 0, updatedAt: 0 }); await store.prepare(); + // Freeze the background reconcile loop so the counting windows below + // contain only the read under test. + store.stopReconcileLoop(); const collection = sessionCollection(1); const seedRows = async (from: number, to: number): Promise => {