mirror of
https://github.com/stablyai/orca.git
synced 2026-10-21 00:03:46 +00:00
* fix(ssh): handle owner displacement and graceful shutdown SSH connections can reconnect with valid session proof after network loss or device sleep. When the incumbent owner is still half-open, allow the reconnecting client to displace it outright rather than wait for socket closure — a window that may never close. Retain displaced deliveries for the new owner to rotate. During app shutdown, drain SSH sessions without terminating recovery operations, and retry pending owner grants in case a replacement commits mid-drain. * fix(ssh): handle owner displacement and graceful shutdown Make QuitTeardownStartGate a shared singleton so SSH connects use the same shutdown fence as the main quit path. Track test-connection probes to ensure they complete before final teardown. Guard owner displacement to prevent stale owners from clearing recovery state claimed by newer owners. * fix(ssh): fix flaky test sync and add error code safety check Test was using tick-based Promise.resolve() loops which don't guarantee the async operation has started. Replace with signal-based synchronization that waits for the actual lease flush. Also add nullish-coalescing to error code check to prevent crashes if error is null or undefined. * fix(ssh): fence reset transport opens during shutdown * fix(ssh): keep recovery leases stable across reconnects * fix(ssh): close transports owned by cancelled connect attempts When a connect is cancelled after its transport has opened, that cancelled attempt still owns the transport and must close it — otherwise it leaks. Add disconnectConnection() to close by identity (not by target ID) so a cancelled attempt closes only the transport it minted, without tearing down its replacement's live transport. Track priorConnection to detect whether this attempt opened a new transport or reused an existing one, and close only on abandonment if this attempt owns the session. * fix(ssh): fence old owner proofs and close superseded transports When an owner reconnects with a new proof while an old one is still live, the old proof is now fenced with SUPERSEDED_ERROR instead of retrying indefinitely. The relay also closes stale transports to signal that their recovery generation has been overtaken by a newer one. This ensures overlapping reconnect scenarios complete with the newest proof rather than getting blocked by stale recovery attempts. * fix(relay): re-pin stdin/stdout fds after closing to prevent recycling When the relay closes stdin/stdout to signal EOF to the SSH peer, the OS can recycle those fds (0 and 1) for new sockets or files. If Node still treats process.stdin/stdout as those numbers, subsequent operations corrupt socket clients and trigger shutdown errors. Re-pin the fds by opening /dev/null to keep them occupied and prevent recycling.
72 lines
2.0 KiB
TypeScript
72 lines
2.0 KiB
TypeScript
export const PTY_CONSUMER_SESSION_PROTOCOL_VERSION = 1
|
|
export const PTY_CONSUMER_OWNER_GRACE_MS = 30_000
|
|
export const PTY_CONSUMER_STALE_OWNER_RECOVERY_ERROR = -32041
|
|
// Why: recovery is blocked only while the incumbent owner's grant publication is still settling — a
|
|
// window bounded by one response write, so the client may retry within a short budget.
|
|
export const PTY_CONSUMER_OWNER_RECOVERY_PENDING_ERROR = -32042
|
|
export const PTY_CONSUMER_OWNER_RECOVERY_SUPERSEDED_ERROR = -32043
|
|
|
|
export type PtyConsumerRole = 'session-owner' | 'subscriber'
|
|
|
|
export type PtyConsumerSessionHello = {
|
|
clientInstanceId: string
|
|
requestedRole: PtyConsumerRole
|
|
resume?: {
|
|
ownerGeneration: number
|
|
ownerLease: string
|
|
}
|
|
capabilities?: {
|
|
outputFlowControl?: {
|
|
versions: number[]
|
|
requestedWindowSu: number
|
|
}
|
|
}
|
|
}
|
|
|
|
export type PtyConsumerSessionGrant = {
|
|
protocolVersion: typeof PTY_CONSUMER_SESSION_PROTOCOL_VERSION
|
|
serverBuildId: string
|
|
clientGeneration: number
|
|
role: PtyConsumerRole
|
|
ownerGeneration?: number
|
|
ownerLease?: string
|
|
capabilities?: {
|
|
outputFlowControl?: {
|
|
version: 1
|
|
windowSu: number
|
|
}
|
|
}
|
|
}
|
|
|
|
export type PtyConsumerAuthentication = {
|
|
connectionId: string
|
|
principal: string
|
|
authenticated: boolean
|
|
allowSessionOwner: boolean
|
|
}
|
|
|
|
export type PtyConsumerDisplacedOwner = {
|
|
connectionId: string
|
|
grant: Readonly<PtyConsumerSessionGrant>
|
|
}
|
|
|
|
export type PtyConsumerSessionAdmission = {
|
|
grant: Readonly<PtyConsumerSessionGrant>
|
|
// Why: set when this admission takes over a still-attached owner. The transport layer owns closing
|
|
// that connection and releasing its deliveries — do it only once the new grant has been published.
|
|
displacedOwner?: Readonly<PtyConsumerDisplacedOwner>
|
|
commitPublication: () => void
|
|
rollbackPublication: () => void
|
|
}
|
|
|
|
export type PtyConsumerSessionOptions = {
|
|
serverBuildId: string
|
|
outputFlowControl?: {
|
|
versions: readonly number[]
|
|
maxWindowSu: number
|
|
}
|
|
ownerGraceMs?: number
|
|
now?: () => number
|
|
createLease?: () => string
|
|
}
|