Files
orca/src/main/ssh/ssh-provider-authority.ts
T
JinjingandOrcaWin a40183389b feat: bound direct SSH reconnect fan-out and recovery (#11003)
* docs: design for direct SSH reconnect fan-out

Capture the implementation-ready plan for host-qualified, epoch-fenced
SSH reconnect recovery after two rounds of multi-model LLM counsel review.

* docs: reconcile SSH reconnect fan-out design

* docs: close reconnect design consistency gaps

* feat: implement bounded direct SSH reconnect recovery

* fix: bound direct SSH retry settlement

* fix: harden direct SSH reconnect authority

* fix: preserve split SSH retry ownership

* fix: preserve SSH split continuation authority

* docs: record final SSH reconnect validation

* fix: preserve SSH authority through retained and detached state

* fix: retain SSH authority across delayed split mounts

* fix: close SSH authority recovery gaps

* fix: fence stale SSH transport replacement

* fix: serialize SSH target teardown

* fix: settle SSH teardown failures before reconnect

* fix: retire failed SSH reset sessions

* test: reconcile current main E2E contracts

* fix: close direct SSH reconnect review gaps

* fix: fence stale SSH reconnect side effects

* fix: close final SSH reconnect lifecycle gaps

* test: stabilize current-main reliability gates

* test: prove plugin navigation containment

* test: make plugin navigation oracle authoritative

* test: make plugin navigation oracle deterministic

* ci: allow sharded e2e suite to finish

* test: wait for runtime pane publication

* test: classify pane readiness by error code

* test: select close persistence terminal by tab identity

* docs: mark reconnect implementation validated

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-07-28 12:33:17 -07:00

149 lines
5.0 KiB
TypeScript

import { randomBytes } from 'node:crypto'
import type { DirectSshAuthority, SshProviderEpoch } from '../../shared/ssh-types'
import {
advanceSshConnectionGeneration,
getSshConnectionGeneration
} from './ssh-connection-generation'
const authorityByTarget = new Map<string, DirectSshAuthority>()
type ProviderRequestAbortRegistration = {
authority: DirectSshAuthority
controller: AbortController
onAbort: () => void
}
const providerRequestAbortsByTarget = new Map<string, Set<ProviderRequestAbortRegistration>>()
let providerEpochSequence = 0
function issueSshProviderEpoch(): SshProviderEpoch {
if (providerEpochSequence >= Number.MAX_SAFE_INTEGER) {
throw new Error('SSH provider epoch sequence exhausted')
}
providerEpochSequence += 1
return `${randomBytes(12).toString('hex')}-${providerEpochSequence.toString(36)}` as SshProviderEpoch
}
function createAuthority(targetId: string, connectionGeneration: number): DirectSshAuthority {
return {
targetId,
providerEpoch: issueSshProviderEpoch(),
connectionGeneration
}
}
function authoritiesEqual(left: DirectSshAuthority, right: DirectSshAuthority): boolean {
return (
left.targetId === right.targetId &&
left.providerEpoch === right.providerEpoch &&
left.connectionGeneration === right.connectionGeneration
)
}
function removeProviderRequestAbort(registration: ProviderRequestAbortRegistration): void {
registration.controller.signal.removeEventListener('abort', registration.onAbort)
const registrations = providerRequestAbortsByTarget.get(registration.authority.targetId)
if (!registrations?.delete(registration)) {
return
}
if (registrations.size === 0) {
providerRequestAbortsByTarget.delete(registration.authority.targetId)
}
}
function abortProviderRequestsForAuthority(authority: DirectSshAuthority): void {
const registrations = providerRequestAbortsByTarget.get(authority.targetId)
if (!registrations) {
return
}
const matching = [...registrations].filter((registration) =>
authoritiesEqual(registration.authority, authority)
)
for (const registration of matching) {
removeProviderRequestAbort(registration)
}
for (const registration of matching) {
if (!registration.controller.signal.aborted) {
registration.controller.abort()
}
}
}
function abortAllProviderRequests(): void {
const registrations = [...providerRequestAbortsByTarget.values()].flatMap((entries) => [
...entries
])
providerRequestAbortsByTarget.clear()
for (const registration of registrations) {
registration.controller.signal.removeEventListener('abort', registration.onAbort)
}
for (const registration of registrations) {
if (!registration.controller.signal.aborted) {
registration.controller.abort()
}
}
}
export function registerSshProviderRequestAbort(
authority: DirectSshAuthority,
controller: AbortController
): () => void {
if (controller.signal.aborted || !isCurrentSshProviderAuthority(authority)) {
return () => {}
}
const capturedAuthority = { ...authority }
const registration: ProviderRequestAbortRegistration = {
authority: capturedAuthority,
controller,
onAbort: () => removeProviderRequestAbort(registration)
}
const registrations =
providerRequestAbortsByTarget.get(authority.targetId) ??
new Set<ProviderRequestAbortRegistration>()
registrations.add(registration)
providerRequestAbortsByTarget.set(authority.targetId, registrations)
controller.signal.addEventListener('abort', registration.onAbort, { once: true })
return () => removeProviderRequestAbort(registration)
}
export function getSshProviderAuthority(targetId: string): DirectSshAuthority {
const generation = getSshConnectionGeneration(targetId)
const current = authorityByTarget.get(targetId)
if (current?.connectionGeneration === generation) {
return current
}
const authority = createAuthority(targetId, generation)
authorityByTarget.set(targetId, authority)
return authority
}
export function rotateSshProviderAuthority(targetId: string): DirectSshAuthority {
const previous = authorityByTarget.get(targetId)
const previousGeneration = getSshConnectionGeneration(targetId)
const nextGeneration = advanceSshConnectionGeneration(targetId)
const authority = createAuthority(targetId, nextGeneration)
if (nextGeneration !== previousGeneration + 1) {
authorityByTarget.clear()
authorityByTarget.set(targetId, authority)
abortAllProviderRequests()
return authority
}
authorityByTarget.set(targetId, authority)
if (previous) {
abortProviderRequestsForAuthority(previous)
}
return authority
}
export function isCurrentSshProviderAuthority(authority: DirectSshAuthority): boolean {
const current = authorityByTarget.get(authority.targetId)
return (
current !== undefined &&
current.connectionGeneration === getSshConnectionGeneration(authority.targetId) &&
authoritiesEqual(current, authority)
)
}
export function resetSshProviderAuthorities(): void {
abortAllProviderRequests()
authorityByTarget.clear()
}