From b16541afcb5a14d1370c571f93f63f101cf259b5 Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Fri, 28 Aug 2026 05:52:14 -0700 Subject: [PATCH] Pin the 30s shell-title corroboration bound from both sides Ablation on the refreshed composed tree showed the constant itself was unpinned: widening SHELL_TITLE_SCAN_CORROBORATION_MAX_AGE_MS to Infinity left the whole matrix green. A wedged `ps` is the trade-off this PR accepts deliberately, so the bound that scopes it is load-bearing. --- ...ss-foreground-observation-evidence.test.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/main/daemon/pty-subprocess-foreground-observation-evidence.test.ts b/src/main/daemon/pty-subprocess-foreground-observation-evidence.test.ts index 222b862c867..d7811f572e5 100644 --- a/src/main/daemon/pty-subprocess-foreground-observation-evidence.test.ts +++ b/src/main/daemon/pty-subprocess-foreground-observation-evidence.test.ts @@ -17,6 +17,9 @@ import type { SubprocessHandle } from './session-subprocess-handle' const SHELL_PID = 999_999_517 // Above the idle-shell refresh throttle (5s) so each read starts a fresh scan. const PAST_THE_SCAN_THROTTLE_MS = 6_000 +// Bracket the 30s shell-title corroboration window from both sides. +const INSIDE_CORROBORATION_WINDOW_MS = 25_000 +const PAST_CORROBORATION_WINDOW_MS = 35_000 describe('daemon foreground observation evidence', () => { let platformDescriptor: PropertyDescriptor | undefined @@ -109,6 +112,22 @@ describe('daemon foreground observation evidence', () => { expect(observation?.evidence.verdict).toBe('unverifiable') }) + it('stops observing a shell title once corroboration ages past the 30s bound', async () => { + resolveAgentForegroundProcessMock.mockResolvedValue({ available: true, processName: 'zsh' }) + expect((await readAfterSettledScan())?.evidence.verdict).toBe('observed') + + // A permanently wedged `ps`: the scan starts and never settles, so nothing + // refreshes corroboration and it simply ages out. Brackets the 30s bound on + // both sides so widening or removing it fails here. + resolveAgentForegroundProcessMock.mockReturnValue(new Promise(() => {})) + + await vi.advanceTimersByTimeAsync(INSIDE_CORROBORATION_WINDOW_MS) + expect(handle.observeForegroundProcess?.()?.evidence.verdict).toBe('observed') + + await vi.advanceTimersByTimeAsync(PAST_CORROBORATION_WINDOW_MS - INSIDE_CORROBORATION_WINDOW_MS) + expect(handle.observeForegroundProcess?.()?.evidence.verdict).toBe('unverifiable') + }) + it('keeps a live agent title an observation without needing a scan', () => { nodePty.process = 'codex'