mirror of
https://github.com/stablyai/orca.git
synced 2026-10-25 08:04:05 +00:00
fix(daemon): fail closed on unsafe legacy close
This commit is contained in:
@@ -712,7 +712,7 @@
|
||||
"ssh",
|
||||
"runtime"
|
||||
],
|
||||
"coverageNotes": "Live macOS Electron tests prove exact local PTY disappearance after parked-tab close and detached-pgid descendant death after agent close. Windows 2022 Actions red/green runs prove production store.removeWorktree(id, true) retires an agent PTY and its reparented native lock-holder before deleting the worktree, plain terminal close preserves an intentionally detached child, and natural agent-root exit publishes PTY exit only after its detached Job child is absent. Deterministic tests cover explicit and spawn-recognized native Windows agent Job selection, daemon session identity propagation, fail-closed Job teardown and bridge errors, safe failure when Job ownership is unavailable, adopted-v24 destructive-shutdown refusal, provider-scoped recovered routing, coalesced authoritative shutdown inventory and cached fallback/daemon duplicate refusal, duplicate-exit suppression, sleeping legacy ownership, daemon WSL exclusion, daemon-wide shutdown, daemon and SSH routing, local/daemon pending-snapshot ownership across natural exit, stale-root descendant-signal suppression, graceful-to-immediate kill upgrades, duplicate-kill completion sharing, locale-stable bounded/fresh/coalesced process-table reads, deadline-safe successor scans, cycle-safe linear descendant traversal, target-only escalation indexing, 32-wide bulk teardown, source-scan timestamp identity, same-second PID ambiguity, ordinary runtime close ownership, unified-only hydration, split ownership, pane detach transfer, restart alias hydration, and late-hook suppression; live Linux, WSL, SSH, and remote-runtime process evidence remains pending.",
|
||||
"coverageNotes": "Live macOS Electron tests prove exact local PTY disappearance after parked-tab close and detached-pgid descendant death after agent close. Windows 2022 Actions red/green runs prove production store.removeWorktree(id, true) retires an agent PTY and its reparented native lock-holder before deleting the worktree, plain terminal close preserves an intentionally detached child, and natural agent-root exit publishes PTY exit only after its detached Job child is absent. Deterministic tests cover explicit and spawn-recognized native Windows agent Job selection, daemon session identity propagation, fail-closed Job teardown and bridge errors, safe failure when Job ownership is unavailable, adopted-v24 unclassified-shutdown refusal with known-WSL compatibility, provider-scoped recovered routing, coalesced authoritative shutdown inventory and cached fallback/daemon duplicate refusal, duplicate-exit suppression, sleeping legacy ownership, daemon WSL exclusion, daemon-wide shutdown, daemon and SSH routing, local/daemon pending-snapshot ownership across natural exit, stale-root descendant-signal suppression, graceful-to-immediate kill upgrades, duplicate-kill completion sharing, locale-stable bounded/fresh/coalesced process-table reads, deadline-safe successor scans, cycle-safe linear descendant traversal, target-only escalation indexing, 32-wide bulk teardown, source-scan timestamp identity, same-second PID ambiguity, ordinary runtime close ownership, unified-only hydration, split ownership, pane detach transfer, restart alias hydration, and late-hook suppression; live Linux, WSL, SSH, and remote-runtime process evidence remains pending.",
|
||||
"motivatingLinks": [
|
||||
"https://github.com/stablyai/orca/pull/8628",
|
||||
"https://github.com/stablyai/orca/pull/8706",
|
||||
@@ -805,7 +805,7 @@
|
||||
{
|
||||
"file": "src/main/daemon/daemon-pty-adapter.test.ts",
|
||||
"assertions": [
|
||||
"an adopted Windows v24 daemon rejects deadline-bearing agent, plain, and WSL shutdown before connecting or sending kill while ordinary close remains compatible"
|
||||
"an adopted Windows v24 daemon rejects unclassified ordinary, sleeping, and deadline-bearing shutdown before root-only kill while preserving ordinary and deadline close for attached known WSL sessions"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -532,18 +532,33 @@ describe('DaemonPtyAdapter (IPtyProvider)', () => {
|
||||
})
|
||||
|
||||
describe('shutdown', () => {
|
||||
it('blocks every adopted Windows v24 session kind from deadline teardown but keeps ordinary close', async () => {
|
||||
it('blocks unclassified Windows v24 close while preserving known WSL close', async () => {
|
||||
const ensureConnectedSpy = vi
|
||||
.spyOn(DaemonClient.prototype, 'ensureConnected')
|
||||
.mockResolvedValue()
|
||||
const requestSpy = vi.spyOn(DaemonClient.prototype, 'request').mockResolvedValue(undefined)
|
||||
const ensureConnectedWithinSpy = vi
|
||||
.spyOn(DaemonClient.prototype, 'ensureConnectedWithin')
|
||||
.mockResolvedValue()
|
||||
const requestSpy = vi
|
||||
.spyOn(DaemonClient.prototype, 'request')
|
||||
.mockImplementation(async (method) =>
|
||||
method === 'createOrAttach'
|
||||
? {
|
||||
isNew: false,
|
||||
pid: 123,
|
||||
shellState: 'unsupported',
|
||||
snapshot: null,
|
||||
wslDistro: 'Ubuntu'
|
||||
}
|
||||
: undefined
|
||||
)
|
||||
const platform = Object.getOwnPropertyDescriptor(process, 'platform')
|
||||
Object.defineProperty(process, 'platform', { configurable: true, value: 'win32' })
|
||||
const legacy = new DaemonPtyAdapter({ socketPath, tokenPath, protocolVersion: 24 })
|
||||
|
||||
try {
|
||||
// Why: v24 did not record whether a session was agent, plain, or WSL,
|
||||
// so migration safety must reject deadline teardown for all three.
|
||||
// Why: inventory-only sessions lack attached WSL metadata, while v24
|
||||
// could miss native agent identity; unclassified close must fail safe.
|
||||
for (const id of ['legacy-agent', 'legacy-plain', 'legacy-wsl']) {
|
||||
await expect(
|
||||
legacy.shutdown(id, {
|
||||
@@ -551,22 +566,55 @@ describe('DaemonPtyAdapter (IPtyProvider)', () => {
|
||||
deadlineMs: Date.now() + 5_000
|
||||
})
|
||||
).rejects.toThrow('requires daemon protocol 25 or newer')
|
||||
await expect(legacy.shutdown(id, { immediate: true })).rejects.toThrow(
|
||||
'restart Orca before closing adopted terminals'
|
||||
)
|
||||
await expect(legacy.shutdown(id, { immediate: true, keepHistory: true })).rejects.toThrow(
|
||||
'restart Orca before closing adopted terminals'
|
||||
)
|
||||
}
|
||||
expect(ensureConnectedSpy).not.toHaveBeenCalled()
|
||||
expect(ensureConnectedWithinSpy).not.toHaveBeenCalled()
|
||||
expect(requestSpy).not.toHaveBeenCalled()
|
||||
|
||||
await expect(legacy.shutdown('legacy-plain', { immediate: true })).resolves.toBeUndefined()
|
||||
expect(requestSpy).toHaveBeenCalledWith(
|
||||
await legacy.spawn({
|
||||
sessionId: 'known-legacy-wsl',
|
||||
cols: 80,
|
||||
rows: 24,
|
||||
terminalWindowsWslDistro: 'Ubuntu'
|
||||
})
|
||||
await expect(
|
||||
legacy.shutdown('known-legacy-wsl', { immediate: true })
|
||||
).resolves.toBeUndefined()
|
||||
expect(requestSpy).toHaveBeenLastCalledWith(
|
||||
'kill',
|
||||
{ sessionId: 'legacy-plain', immediate: true },
|
||||
{ sessionId: 'known-legacy-wsl', immediate: true },
|
||||
undefined
|
||||
)
|
||||
await legacy.spawn({
|
||||
sessionId: 'known-legacy-wsl-delete',
|
||||
cols: 80,
|
||||
rows: 24,
|
||||
terminalWindowsWslDistro: 'Ubuntu'
|
||||
})
|
||||
await expect(
|
||||
legacy.shutdown('known-legacy-wsl-delete', {
|
||||
immediate: true,
|
||||
deadlineMs: Date.now() + 5_000
|
||||
})
|
||||
).resolves.toBeUndefined()
|
||||
expect(requestSpy).toHaveBeenLastCalledWith(
|
||||
'kill',
|
||||
{ sessionId: 'known-legacy-wsl-delete', immediate: true },
|
||||
expect.any(Number)
|
||||
)
|
||||
} finally {
|
||||
legacy.dispose()
|
||||
if (platform) {
|
||||
Object.defineProperty(process, 'platform', platform)
|
||||
}
|
||||
requestSpy.mockRestore()
|
||||
ensureConnectedWithinSpy.mockRestore()
|
||||
ensureConnectedSpy.mockRestore()
|
||||
}
|
||||
})
|
||||
|
||||
@@ -588,13 +588,13 @@ export class DaemonPtyAdapter implements IPtyProvider {
|
||||
): Promise<void> {
|
||||
if (
|
||||
process.platform === 'win32' &&
|
||||
opts.deadlineMs !== undefined &&
|
||||
this.protocolVersion < WINDOWS_AGENT_JOB_PROTOCOL_VERSION
|
||||
this.protocolVersion < WINDOWS_AGENT_JOB_PROTOCOL_VERSION &&
|
||||
!this.wslDistrosBySessionId.has(id)
|
||||
) {
|
||||
// Why: an adopted v24 daemon could not own agent descendants at spawn;
|
||||
// destructive deletion must not mistake its root-only exit for a safe drain.
|
||||
// Why: v24 could miss native agent identity and never Job-owned descendants;
|
||||
// only an attached, known WSL session is safe for ordinary root close.
|
||||
throw new DaemonProtocolError(
|
||||
`Windows destructive PTY shutdown requires daemon protocol ${WINDOWS_AGENT_JOB_PROTOCOL_VERSION} or newer`
|
||||
`Windows PTY shutdown requires daemon protocol ${WINDOWS_AGENT_JOB_PROTOCOL_VERSION} or newer; restart Orca before closing adopted terminals`
|
||||
)
|
||||
}
|
||||
// Why: shutdown can be the first lazy-client operation after restart; connect
|
||||
|
||||
Reference in New Issue
Block a user