Files
orca/src/shared/runtime-listing-host-scope.ts
T
Neil 0d2375a7ff fix(remote): stop a disclosure list latching the mirror completeness gate (#18619)
* fix(remote): stop a disclosure list latching the mirror completeness gate

`hostScope.omittedHostIds` was doing two jobs with opposite requirements. As
disclosure it must over-name: `omitted-host-scope-selectors.ts` deliberately
keeps ids for servers that are no longer paired so a caller can still see the
gap, and `docs/reference/ssh-execution-boundary.md` requires a listing to name
what it did not cover. As a completeness gate it must name only coverage that
was owed and not delivered, or it latches.

It latched. `workspaceSessionsByHostId` keeps a partition for every runtime a
machine has ever paired with and nothing prunes it, and a mirrored `remote:` row
names its peer too — so any client that has ever paired outward publishes a
permanently non-empty `omittedHostIds`. `probeHostLiveTerminals` read that as
`unverifiable`, `markHostSessionMirrorHydrated` never fired, and panes parked on
`parkUntilHostSessionMirrorHydrates` never drained.

`hostScopeCensusIsComplete` gives the gate its own answer and leaves the
disclosure list alone. A `runtime:` host is never owed coverage by the runtime
answering: a paired runtime is a peer with its own control plane reached with
`--environment`, and there is no paired-runtime PTY provider for this runtime to
have queried. Two other branches stay load-bearing — an absent scope is a host
too old to claim one, and a listing that covered no host proves nothing.

No wire change: the host publishes byte-identical content and only the client's
reading moves, so this reaches the reporter by updating their client alone
rather than waiting for their remote. That also avoids a new field's fallback
rule, where "absent means complete" would recreate the bug with the polarity
flipped.

`queried-host-kinds.test.ts` pins the invariant the predicate rests on at its
source, because the consolidation moving the SSH path onto orcad is the change
most likely to introduce a runtime-backed PTY provider and quietly invalidate it.

Fixes #18595

* test(remote): pin the orphan-recovery host-scope gate and narrow the invariant claim

The readiness review found the second gate unpinned: reverting
`web-session-terminal-orphan-recovery-inventory.ts` alone to the pre-PR
expression left the whole renderer suite green, because every existing fixture
passes `omittedHostIds: []`. The commit claimed two gates and proved one.

Four cases now drive `resolveTerminalOrphanInventory` through a non-empty scope.
Reverting that gate alone fails the peer-runtime case. Note the absent-scope case
deletes the key rather than passing `undefined`, because `listResult` substitutes
its default for `undefined` — routing through the fixture there silently tests
the default instead.

`queried-host-kinds.test.ts` also claimed more than it caught: a runtime-backed
transport registered under an SSH connection id reports as `ssh:` and passes,
which is the shape the orcad consolidation is expected to take. It pins the
spelling this function emits, which is what the gate keys on, and now says so.

* fix(remote): require a legible covered host before believing a census

CodeRabbit found a real asymmetry: the predicate refused an omitted host id it
could not parse, but accepted an unparseable *covered* id as proof of coverage.
`isTerminalListResult` validates only that `hostIds` is an array, so
`{hostIds: ['runtime:'], omittedHostIds: ['runtime:env-7']}` was `unverifiable`
before this PR and would have become `complete` after it.

Taken as "at least one legible covered host" rather than the suggested "every id
parses". A host that later gains a kind this client cannot parse would otherwise
report an incomplete census forever — which is this bug in a new coat, and the
failure mode the predicate exists to prevent.

The check exposed four tests publishing `hostIds: ['remote-runtime']`, a bare
environment id that `parseExecutionHostId` rejects. No host emits that: a runtime
answering `terminal.list` names the execution hosts it covered, which is `local`
— verified against a live paired runtime. Those fixtures are corrected to the
shape the wire actually carries, which is why the assertions move.
2026-09-04 06:09:07 -07:00

42 lines
2.3 KiB
TypeScript

import { parseExecutionHostId, type ExecutionHostId } from './execution-host'
/**
* What a bounded listing did and did not cover, by execution host. An absent scope means the
* host is too old to report one — not that it covered everything. See
* `docs/reference/ssh-execution-boundary.md`: a listing is only evidence about the hosts it
* actually covered, so an empty answer for a host that is missing here proves nothing.
*/
export type RuntimeListingHostScope = {
hostIds: ExecutionHostId[]
omittedHostIds: ExecutionHostId[]
}
/**
* Whether the answering runtime enumerated every host its listing owed coverage for.
*
* `omittedHostIds` is a disclosure list and deliberately over-names — `omitted-host-scope-selectors.ts`
* keeps ids for servers that are no longer paired so a caller can still see the gap. That makes it the
* wrong input for a completeness gate, which needs "coverage owed and not delivered". The two jobs pull
* in opposite directions, and reading the disclosure list as the gate latched every remote pane on any
* client that had ever paired outward (#18595).
*
* A `runtime:` host is never owed coverage by the runtime answering: a paired runtime is a peer with its
* own control plane, reached with `--environment`, and this runtime has no paired-runtime PTY provider to
* have queried. Its terminals are its own answer to give, so its presence here is disclosure, not a gap.
*/
export function hostScopeCensusIsComplete(scope: RuntimeListingHostScope | undefined): boolean {
// A host too old to publish a scope cannot claim one; absence is never completeness.
if (scope === undefined) {
return false
}
// A listing that covered no host proves nothing, and an unreadable coverage claim is not a
// claim: `isTerminalListResult` checks only that `hostIds` is an array, so at least one covered
// id has to be legible before the claim can be believed. Deliberately "at least one" rather than
// "all": a host that later gains a kind this client cannot parse would otherwise report an
// incomplete census forever, which is the bug this predicate exists to stop.
if (!scope.hostIds.some((hostId) => parseExecutionHostId(hostId))) {
return false
}
return scope.omittedHostIds.every((hostId) => parseExecutionHostId(hostId)?.kind === 'runtime')
}