mirror of
https://github.com/stablyai/orca.git
synced 2026-10-26 16:02:42 +00:00
test(pty): pin the unavailable arm's twin, and eight neighbouring refusal arms
A gate ablation found the `unavailable` arm of the completion monitor free while its structurally identical out-of-contract twin was pinned. After an `unavailable` sample the pending exit could stop being discarded and nothing would notice: a pending exit would survive the gap and be confirmed by the next positive sample, so the two "consecutive" samples that dispatch a completion would be separated by a read that observed nothing. `unavailable` is strictly less evidence than an out-of-contract verdict, so if either arm deserves the re-arm it is this one. Its backoff charge was free for the same reason and is pinned here too, or an unreachable host gets polled at the active tier forever. Also pinned, each red with that arm reverted alone: the exit-1-is-not-absence timed-out check on both pgrep and ps, the ps spawn-failure staying unverifiable, `processName: null` from a completed scan reading as positive absence, an unowned pty reading as positive absence, and a pty replaced mid-scan staying unverifiable. `!result.signal` in subprocessRanAndMatchedNothing is left unpinned on purpose: Node reports `code` and `signal` mutually exclusively, so `code === 1` with a signal is not an input production emits, and a test for it would assert an unreachable branch.
This commit is contained in:
@@ -84,6 +84,21 @@ describe('classifyLocalPtyChildProcesses', () => {
|
||||
).toEqual({ hasChildProcesses: false, evidence: { verdict: 'exited' } })
|
||||
})
|
||||
|
||||
it('reads a completed scan that observed nothing as positive absence', () => {
|
||||
// The canonical completion input: the agent exited, the scan ran to
|
||||
// completion and resolved no foreground process, and node-pty's title is
|
||||
// back to the shell. `processName: null` is the observation, not an
|
||||
// absent one, so this is the one shape that may say exited.
|
||||
expect(
|
||||
classifyLocalPtyChildProcesses({
|
||||
procPresent: true,
|
||||
titleRead: { ok: true, title: 'zsh' },
|
||||
shell: 'zsh',
|
||||
foreground: { verdict: 'observed', processName: null }
|
||||
})
|
||||
).toEqual({ hasChildProcesses: false, evidence: { verdict: 'exited' } })
|
||||
})
|
||||
|
||||
it('reads a shell title under a degraded scan as unverifiable', () => {
|
||||
// node-pty's POSIX title read silently falls back to the spawned shell
|
||||
// when the native read fails, so under scan distress "title == shell"
|
||||
|
||||
@@ -204,6 +204,51 @@ describe('LocalPtyProvider.inspectProcess evidence', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('reports a pty the provider no longer owns as positive absence', async () => {
|
||||
// The provider owns this table, so a missing entry is a reaped PTY rather
|
||||
// than a failed probe. It has to stay a positive observation: the monitor
|
||||
// refuses to complete on anything it cannot read, so publishing
|
||||
// `unverifiable` here would strand the pane's last completion forever.
|
||||
await expect(provider.inspectProcess('pty-the-provider-never-owned')).resolves.toEqual({
|
||||
foregroundProcess: null,
|
||||
hasChildProcesses: false,
|
||||
processEvidence: {
|
||||
foreground: { verdict: 'observed', processName: null },
|
||||
children: { verdict: 'exited' }
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
it('marks a pty replaced mid-scan unverifiable instead of an exit', async () => {
|
||||
const { id } = await provider.spawn({ cols: 80, rows: 24, sessionId: 'pane-1' })
|
||||
await recognizeCodex(id)
|
||||
|
||||
mockProc.process = 'zsh'
|
||||
resolveAgentForegroundProcessMock.mockImplementationOnce(async () => {
|
||||
// The scan outlives the pane: the PTY exits and the same caller session
|
||||
// id is reattached to a fresh node-pty while the scan is still running.
|
||||
exitCb?.({ exitCode: 0 })
|
||||
const replacement = createLocalPtyMockProcess({
|
||||
get: () => exitCb,
|
||||
set: (cb) => {
|
||||
exitCb = cb
|
||||
}
|
||||
})
|
||||
replacement.process = 'zsh'
|
||||
spawnMock.mockReturnValue(replacement)
|
||||
await provider.spawn({ cols: 80, rows: 24, sessionId: 'pane-1' })
|
||||
return { available: true, processName: 'zsh' }
|
||||
})
|
||||
|
||||
// The stale scan answered about a process that is no longer this pane's.
|
||||
// Its `null` name predates the evidence contract and reads as an exit.
|
||||
await expect(provider.inspectProcess(id)).resolves.toMatchObject({
|
||||
processEvidence: {
|
||||
foreground: { verdict: 'unverifiable', reason: 'pty replaced during inspection' }
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
it('treats a successful Windows job confirmation as an observation', async () => {
|
||||
Object.defineProperty(process, 'platform', { configurable: true, value: 'win32' })
|
||||
mockProc.process = 'powershell.exe'
|
||||
|
||||
Reference in New Issue
Block a user