From 76ac3af52926cb90c7b668456b3dc7405f72ea77 Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Fri, 12 Jun 2026 06:26:06 -0700 Subject: [PATCH] test: coverage for ambient reflex-channel logging + watch window/cap flags MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ship coverage audit (85%, gate PASS) named five gaps; the two substantive cheap ones close here: the codex-D11 logChannel='reflex' path now has a behavioral pin (events land on channel 'reflex' through the drained sink; no logChannel → no events), and gbrain watch's --window-turns / --max-pages flags are exercised (turn-1 attribution under window=1; cap to one page). Remaining flagged-not-blocking: the wallclock-timeout branch (untestable without >10s real-clock flake — same rationale as the arming pin), formatResult's volunteer case (module-private), and the cycle purge wiring. Co-Authored-By: Claude Fable 5 --- test/retrieval-reflex.test.ts | 44 +++++++++++++++++++++++++++++++++++ test/watch-command.test.ts | 28 ++++++++++++++++++++++ 2 files changed, 72 insertions(+) diff --git a/test/retrieval-reflex.test.ts b/test/retrieval-reflex.test.ts index bd6d845ac..11657b7a6 100644 --- a/test/retrieval-reflex.test.ts +++ b/test/retrieval-reflex.test.ts @@ -289,3 +289,47 @@ describe('v0.43 (#2095) — rolling window extraction through assemble()', () => }); }); }); + +describe('ambient-channel event logging (codex D11 — logChannel: reflex)', () => { + test('resolver with logChannel logs channel=reflex events through the drained sink', async () => { + const { awaitPendingVolunteerEventWrites, _resetPendingVolunteerEventWritesForTests } = + await import('../src/core/context/volunteer-events.ts'); + _resetPendingVolunteerEventWritesForTests(); + await engine.executeRaw('DELETE FROM context_volunteer_events').catch(() => {}); + await seed('people/alice-example', 'Alice Example', 'A founder.'); + + const block = await resolveEntitiesToPointers( + engine, + 'default', + extractCandidates('what do you think about Alice Example?'), + { logChannel: 'reflex' }, + ); + expect(block).not.toBeNull(); + const { unfinished } = await awaitPendingVolunteerEventWrites(5_000); + expect(unfinished).toBe(0); + const rows = await engine.executeRaw<{ channel: string; slug: string; match_arm: string }>( + 'SELECT channel, slug, match_arm FROM context_volunteer_events', + [], + ); + expect(rows.length).toBe(1); + expect(rows[0].channel).toBe('reflex'); + expect(rows[0].slug).toBe('people/alice-example'); + expect(rows[0].match_arm).toBe('title'); + }); + + test('no logChannel → no events (the volunteer layer logs its own)', async () => { + await engine.executeRaw('DELETE FROM context_volunteer_events').catch(() => {}); + await seed('people/alice-example', 'Alice Example', 'A founder.'); + const block = await resolveEntitiesToPointers( + engine, + 'default', + extractCandidates('about Alice Example'), + {}, + ); + expect(block).not.toBeNull(); + const { awaitPendingVolunteerEventWrites } = await import('../src/core/context/volunteer-events.ts'); + await awaitPendingVolunteerEventWrites(5_000); + const rows = await engine.executeRaw<{ channel: string }>('SELECT channel FROM context_volunteer_events', []); + expect(rows.length).toBe(0); + }); +}); diff --git a/test/watch-command.test.ts b/test/watch-command.test.ts index 602479732..cc33db249 100644 --- a/test/watch-command.test.ts +++ b/test/watch-command.test.ts @@ -125,3 +125,31 @@ describe('gbrain watch (#2095)', () => { expect(out.join('')).toContain('people/alice-example'); }); }); + +describe('gbrain watch — window + cap flags (ship coverage G4)', () => { + test('--window-turns 1: fires on the mention turn itself; the pronoun follow-up adds nothing', async () => { + await seed('people/alice-example', 'Alice Example', 'Alice is a founder.'); + const out = await watchRun( + [ + 'assistant: Alice Example led one last year.', + 'user: what did she invest in?', + ], + ['--window-turns', '1', '--json'], + ); + // Watch volunteers per turn: the entity fires at turn 1 (its mention + // turn). With window=1 the follow-up turn extracts nothing, so exactly + // one row exists and it carries turn 1 attribution. + expect(out.length).toBe(1); + expect(JSON.parse(out[0]).turn).toBe(1); + }); + + test('--max-pages 1 caps a multi-entity turn to one volunteered page', async () => { + await seed('people/alice-example', 'Alice Example', 'Founder.'); + await seed('people/bob-sample', 'Bob Sample', 'Engineer.'); + const out = await watchRun( + ['user: intro Alice Example to Bob Sample'], + ['--max-pages', '1', '--json'], + ); + expect(out.length).toBe(1); + }); +});