Files
orca/src/shared/runtime-host-contact.ts
T
Neil a61119ceb0 refactor(runtime): name the four answers a host probe can give (#21207)
The renderer expressed every non-answer as one nullable `status`, so a probe in
flight, a probe that failed, a host that refused us and a retired pairing all
reached readers as the same `null` -- and readers spent that `null` on decisions
of very different weight, including destructive ones.

`RuntimeHostContact` names the four. Nothing changes yet: the connection-state
derivation is rewritten on top of it and a 384-case parity table asserts the
result is identical to a frozen copy of the old one on every combination of
verification, transport, retired, answered and remote-control state.
2026-09-17 21:22:39 -07:00

119 lines
4.8 KiB
TypeScript

import type { RuntimeHostStatusSnapshot } from './runtime-host-status'
import type { RuntimeStatus } from './runtime-types'
/**
* What a host's last probe is worth, kept apart from what the host actually said.
*
* The store's `status` field answers both questions with one nullable value, so a probe still in
* flight, a probe that failed, a host that refused us and a pairing that was retired all arrive at
* a reader as the same `null`. Readers then spend that `null` on decisions of very different
* weight. This names the four answers so the decision happens where the evidence is understood.
*
* `unverifiable` is never `exited` (docs/reference/ssh-execution-boundary.md). `refused` and
* `retired` are the only arms carrying positive evidence, and they are separate because they
* differ in kind: one is the host turning us away, the other is the pairing being ended.
*/
export type RuntimeHostContact =
| { verdict: 'live'; status: RuntimeStatus }
| {
verdict: 'unverifiable'
reason: RuntimeHostContactUnverifiableReason
lastAnswer: RuntimeStatus | null
}
| { verdict: 'refused'; lastAnswer: RuntimeStatus | null }
| { verdict: 'retired'; lastAnswer: RuntimeStatus | null }
/**
* Why the host's current state is unknown. `never-asked` is the absence of any transport attempt,
* which is where an unreachable paired host permanently sits — distinct from a handshake in
* flight, and the reason it must stay actionable rather than spin.
*/
export type RuntimeHostContactUnverifiableReason =
| 'never-asked'
| 'checking'
| 'probe-failed'
| 'transport-connecting'
| 'transport-down'
/**
* The host's last answer whatever the verdict, for facts that do not expire — its build's
* capabilities, its platform, its runtime id. Returns null only when the host never answered.
*/
export function lastRuntimeHostAnswer(contact: RuntimeHostContact): RuntimeStatus | null {
return contact.verdict === 'live' ? contact.status : contact.lastAnswer
}
/** The answer only while it is current, for decisions that must not act on a stale fact. */
export function liveRuntimeHostStatus(contact: RuntimeHostContact): RuntimeStatus | null {
return contact.verdict === 'live' ? contact.status : null
}
/** True only for the host's own terminal verdicts — the one state that may withdraw a fact. */
export function isRuntimeHostContactRevokedVerdict(contact: RuntimeHostContact): boolean {
return contact.verdict === 'refused' || contact.verdict === 'retired'
}
/**
* Why the order matters: it is the order the connection-state derivation already used, and the
* parity suite pins every combination against it. Transport loss outranks a probe in flight
* because a dead socket explains the silence; a ready transport with a failed probe is the host
* being unreachable at the runtime layer, not at the network layer.
*/
export function runtimeHostContactFromSnapshot(
snapshot: RuntimeHostStatusSnapshot,
entryStatus: RuntimeStatus | null = snapshot.status
): RuntimeHostContact {
const lastAnswer = snapshot.status
if (snapshot.retired) {
return { verdict: 'retired', lastAnswer }
}
if (snapshot.verification === 'blocked') {
return { verdict: 'refused', lastAnswer }
}
if (snapshot.transport === 'disconnected') {
return { verdict: 'unverifiable', reason: 'transport-down', lastAnswer }
}
if (snapshot.verification === 'checking' && !entryStatus) {
return { verdict: 'unverifiable', reason: 'checking', lastAnswer }
}
if (snapshot.transport === 'ready' && snapshot.verification !== 'verified') {
return { verdict: 'unverifiable', reason: 'probe-failed', lastAnswer }
}
if (snapshot.verification === 'verified' && entryStatus) {
return { verdict: 'live', status: entryStatus }
}
if (snapshot.transport === 'connecting') {
return { verdict: 'unverifiable', reason: 'transport-connecting', lastAnswer }
}
return { verdict: 'unverifiable', reason: 'never-asked', lastAnswer }
}
/**
* The contact for a recorded entry. A stored `contact` wins so a writer can state one the
* snapshot cannot express — a probe that threw before any snapshot existed, say — and the
* snapshot derivation is the fallback while writers are still being converted.
*/
export function runtimeHostContactForEntry(
entry:
| {
status: RuntimeStatus | null
contact?: RuntimeHostContact
snapshot?: RuntimeHostStatusSnapshot
}
| null
| undefined
): RuntimeHostContact {
if (!entry) {
return { verdict: 'unverifiable', reason: 'never-asked', lastAnswer: null }
}
if (entry.contact) {
return entry.contact
}
if (entry.snapshot) {
return runtimeHostContactFromSnapshot(entry.snapshot, entry.status)
}
return entry.status
? { verdict: 'live', status: entry.status }
: { verdict: 'unverifiable', reason: 'probe-failed', lastAnswer: null }
}