mirror of
https://github.com/stablyai/orca.git
synced 2026-10-23 08:03:39 +00:00
* docs: design SSH relay PTY backpressure * fix(ssh): bound relay frame decoding * fix(relay): bound PTY output publication * fix(ssh): bound PTY model admission * fix(ssh): settle closed model admissions * feat(ssh): negotiate bounded PTY consumer sessions * fix(ssh): fence exit on renderer settlement * feat(ssh): track PTY source credit end to end * fix(ssh): recover bounded PTY output across reconnect * feat(ssh): complete relay PTY output backpressure * fix(ssh): close final PTY source credit races * docs(ssh): record final backpressure validation * feat(ssh): complete relay PTY source-credit lifecycle * test(ssh): complete provider notification fixture * fix(ssh): preserve terminal source credit across rotation * fix(ssh): fail closed on recovery cancellation * fix(ssh): prioritize mux control writes after drain * fix(ssh): retire canceled relay restore deliveries * fix(ssh): order exit cancellation cleanup * fix(ssh): gate provisional source activation * test(ssh): register mux drain-priority coverage * fix(ssh): type stale owner recovery mismatches * fix(ssh): close projection replacement races * fix(relay): contain streaming edge failures * fix(ssh): secure relay endpoint credentials * docs(ssh): reconcile final backpressure lifecycle * fix(ssh): bound main IPC output lifecycle * fix(ssh): close recovery ownership gaps * docs(ssh): record exact artifact validation * fix(ssh): reject reclaimed snapshot replacements * fix(ssh): fence model admission across reconnect * fix(ssh): contain migration failure per PTY * docs(ssh): record final exact-head validation * test(ssh): align deploy fixtures with credential publication * feat(ssh): add per-target bounded output setting * fix(ssh): close source recovery review gaps * fix(ssh): latch source credit environment override * feat(ssh): make PTY source credit the default * docs(ssh): record always-on relay validation * docs(ssh): bind validation to current main * test(ssh): grant source credit in IPC fixture * test(ssh): grant source credit in fake relay --------- Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
195 lines
6.5 KiB
TypeScript
195 lines
6.5 KiB
TypeScript
import type {
|
|
RemoteTerminalSourceRangeReplacementPublication,
|
|
RemoteTerminalSourceRangeReplacementReservation,
|
|
RemoteTerminalSourceRangeStreamIdentity
|
|
} from '../runtime/remote-terminal-source-range-consumer'
|
|
import type { PtySourceSpan } from '../../shared/pty-source-credit-contract'
|
|
import type {
|
|
SshPtySourceConsumerId,
|
|
SshPtySourceObligationState
|
|
} from './ssh-pty-source-obligation-contract'
|
|
import type { SshPtySourceObligationCoordinator } from './ssh-pty-source-obligation-coordinator'
|
|
|
|
type ReplacementSpanRecord = Readonly<{
|
|
source: PtySourceSpan
|
|
transferState: SshPtySourceObligationState
|
|
}>
|
|
|
|
type ReplacementReservationRecord = {
|
|
reservation: RemoteTerminalSourceRangeReplacementReservation
|
|
spans: readonly ReplacementSpanRecord[]
|
|
consumer: SshPtySourceConsumerId
|
|
replacement: SshPtySourceConsumerId
|
|
reason: string
|
|
}
|
|
|
|
function remoteConsumerId(
|
|
identity: RemoteTerminalSourceRangeStreamIdentity
|
|
): SshPtySourceConsumerId {
|
|
return `remote:${identity.consumerId}`
|
|
}
|
|
|
|
export class SshPtyRemoteSourceRangeReplacements {
|
|
private readonly reservations = new Map<string, ReplacementReservationRecord>()
|
|
private nextReservationId = 1
|
|
|
|
constructor(private readonly coordinator: SshPtySourceObligationCoordinator) {}
|
|
|
|
reserve(
|
|
identity: RemoteTerminalSourceRangeStreamIdentity,
|
|
spanIds: readonly string[],
|
|
requiredSeq: number,
|
|
reason: string
|
|
): RemoteTerminalSourceRangeReplacementReservation | null {
|
|
if (spanIds.length === 0) {
|
|
return null
|
|
}
|
|
if (!Number.isSafeInteger(requiredSeq) || requiredSeq < 0) {
|
|
throw new Error('ssh_remote_source_range_replacement_sequence_invalid')
|
|
}
|
|
const consumer = remoteConsumerId(identity)
|
|
const replacement = `remote:snapshot:${identity.consumerId}` as const
|
|
const spans = spanIds.map((spanId) => this.coordinator.spanIdentity(spanId))
|
|
for (const { spanId } of spans) {
|
|
if (this.coordinator.obligation(spanId, consumer).state !== 'open') {
|
|
throw new Error('ssh_remote_source_range_transfer_invalid')
|
|
}
|
|
}
|
|
const transferred: ReplacementSpanRecord[] = []
|
|
try {
|
|
for (const source of spans) {
|
|
const { spanId } = source
|
|
const transition = { identity: source, spanId, consumer, reason }
|
|
if (!this.coordinator.beginTransfer(transition, replacement)) {
|
|
throw new Error('ssh_remote_source_range_transfer_invalid')
|
|
}
|
|
const transferState = this.coordinator.obligation(spanId, consumer)
|
|
if (transferState.state !== 'transferring' || transferState.to !== replacement) {
|
|
this.coordinator.rollbackTransfer(transition)
|
|
throw new Error('ssh_remote_source_range_transfer_invalid')
|
|
}
|
|
transferred.push(Object.freeze({ source, transferState }))
|
|
}
|
|
} catch (error) {
|
|
for (const span of transferred.toReversed()) {
|
|
this.rollbackExactSpan(span, consumer, reason)
|
|
}
|
|
throw error
|
|
}
|
|
const reservation = Object.freeze({
|
|
reservationId: `remote-source-replacement:${this.nextReservationId++}`,
|
|
identity: Object.freeze({ ...identity }),
|
|
requiredSeq
|
|
})
|
|
this.reservations.set(reservation.reservationId, {
|
|
reservation,
|
|
spans: Object.freeze(transferred),
|
|
consumer,
|
|
replacement,
|
|
reason
|
|
})
|
|
return reservation
|
|
}
|
|
|
|
commit(
|
|
reservation: RemoteTerminalSourceRangeReplacementReservation,
|
|
publication: RemoteTerminalSourceRangeReplacementPublication,
|
|
isCurrent: boolean,
|
|
onCommitted: (spanIds: readonly string[]) => void
|
|
): boolean {
|
|
const record = this.reservations.get(reservation.reservationId)
|
|
if (
|
|
!record ||
|
|
record.reservation !== reservation ||
|
|
!isCurrent ||
|
|
!Number.isSafeInteger(publication.seq) ||
|
|
publication.seq < reservation.requiredSeq ||
|
|
(publication.source !== 'headless' && publication.source !== 'renderer')
|
|
) {
|
|
return false
|
|
}
|
|
if (
|
|
record.spans.some(({ source, transferState }) => {
|
|
const { spanId } = source
|
|
if (!this.coordinator.hasRetainedSpan(spanId)) {
|
|
return true
|
|
}
|
|
return this.coordinator.obligation(spanId, record.consumer) !== transferState
|
|
})
|
|
) {
|
|
return false
|
|
}
|
|
for (const { source } of record.spans) {
|
|
const { spanId } = source
|
|
if (!this.coordinator.hasRetainedSpan(spanId)) {
|
|
continue
|
|
}
|
|
if (
|
|
!this.coordinator.commitTransfer({ identity: source, spanId, consumer: record.consumer })
|
|
) {
|
|
throw new Error('ssh_remote_source_range_replacement_commit_invalid')
|
|
}
|
|
}
|
|
this.reservations.delete(reservation.reservationId)
|
|
onCommitted(record.spans.map(({ source }) => source.spanId))
|
|
return true
|
|
}
|
|
|
|
rollback(reservation: RemoteTerminalSourceRangeReplacementReservation, reason: string): boolean {
|
|
const record = this.reservations.get(reservation.reservationId)
|
|
if (!record || record.reservation !== reservation) {
|
|
return false
|
|
}
|
|
this.reservations.delete(reservation.reservationId)
|
|
let rolledBack = true
|
|
for (const span of record.spans) {
|
|
const { spanId } = span.source
|
|
if (!this.coordinator.hasRetainedSpan(spanId)) {
|
|
continue
|
|
}
|
|
rolledBack = this.rollbackExactSpan(span, record.consumer, reason) && rolledBack
|
|
}
|
|
return rolledBack
|
|
}
|
|
|
|
rollbackIdentity(identity: RemoteTerminalSourceRangeStreamIdentity, reason: string): void {
|
|
for (const record of Array.from(this.reservations.values())) {
|
|
if (
|
|
record.reservation.identity.ptyId === identity.ptyId &&
|
|
record.reservation.identity.consumerId === identity.consumerId &&
|
|
record.reservation.identity.streamGeneration === identity.streamGeneration
|
|
) {
|
|
this.rollback(record.reservation, reason)
|
|
}
|
|
}
|
|
}
|
|
|
|
closeGeneration(providerGeneration: number, reason: string): void {
|
|
for (const record of Array.from(this.reservations.values())) {
|
|
if (record.spans.some(({ source }) => source.providerGeneration === providerGeneration)) {
|
|
this.rollback(record.reservation, `${reason}-replacement-aborted`)
|
|
}
|
|
}
|
|
}
|
|
|
|
private rollbackExactSpan(
|
|
span: ReplacementSpanRecord,
|
|
consumer: SshPtySourceConsumerId,
|
|
reason: string
|
|
): boolean {
|
|
const { source, transferState } = span
|
|
if (
|
|
!this.coordinator.hasRetainedSpan(source.spanId) ||
|
|
this.coordinator.obligation(source.spanId, consumer) !== transferState
|
|
) {
|
|
return false
|
|
}
|
|
return this.coordinator.rollbackTransfer({
|
|
identity: source,
|
|
spanId: source.spanId,
|
|
consumer,
|
|
reason
|
|
})
|
|
}
|
|
}
|