Files
orca/src/shared/runtime-listing-host-scope.test.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

82 lines
3.1 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import { hostScopeCensusIsComplete } from './runtime-listing-host-scope'
/**
* The gate and the disclosure list answer different questions off the same field. These pin the
* cases where they disagree — which is every case that mattered in #18595.
*/
describe('hostScopeCensusIsComplete', () => {
it('reads a peer runtime as disclosure, not as coverage this host owed', () => {
expect(
hostScopeCensusIsComplete({ hostIds: ['local'], omittedHostIds: ['runtime:env-7'] })
).toBe(true)
})
it('still reads an SSH host as a gap, because this runtime does query those', () => {
expect(hostScopeCensusIsComplete({ hostIds: ['local'], omittedHostIds: ['ssh:box-1'] })).toBe(
false
)
})
it('reads a mixed omission as incomplete on the strength of the SSH host alone', () => {
expect(
hostScopeCensusIsComplete({
hostIds: ['local'],
omittedHostIds: ['runtime:env-7', 'ssh:box-1']
})
).toBe(false)
})
it('calls a clean census complete', () => {
expect(hostScopeCensusIsComplete({ hostIds: ['local', 'ssh:box-1'], omittedHostIds: [] })).toBe(
true
)
})
// Defence in depth: `listKnownExecutionHostIds` always seeds `local`, so a real scope that
// covered nothing also omits `local` and is refused by the runtime-only rule anyway. This
// branch is what stops a scope that answered for no host from ever reading complete if that
// ever stops holding.
it('refuses a listing that covered no host at all', () => {
expect(hostScopeCensusIsComplete({ hostIds: [], omittedHostIds: ['runtime:env-7'] })).toBe(
false
)
expect(hostScopeCensusIsComplete({ hostIds: [], omittedHostIds: [] })).toBe(false)
})
// Why: `hostScope` shipped in v1.4.187. An older host cannot say what it covered, and absence
// of the claim is never the claim — this must stay the first branch.
it('refuses a host too old to publish a scope', () => {
expect(hostScopeCensusIsComplete(undefined)).toBe(false)
})
// Why: without this the runtime-only rule trusts a coverage claim it cannot read — the shape
// `{hostIds: ['runtime:'], omittedHostIds: ['runtime:env-7']}` was `unverifiable` before the
// gate changed and must not become `complete` on the strength of an unparseable id.
it('refuses a coverage claim with no legible host in it', () => {
expect(
hostScopeCensusIsComplete({
hostIds: ['runtime:' as never],
omittedHostIds: ['runtime:env-7']
})
).toBe(false)
})
// Why "at least one legible" and not "all legible": a host that later gains a kind this client
// cannot parse must not report an incomplete census forever — that is this bug in a new coat.
it('accepts a coverage claim carrying one legible host beside an unreadable one', () => {
expect(
hostScopeCensusIsComplete({ hostIds: ['local', 'newkind:x' as never], omittedHostIds: [] })
).toBe(true)
})
it('refuses an unparseable host id rather than discounting it', () => {
expect(
hostScopeCensusIsComplete({
hostIds: ['local'],
omittedHostIds: ['runtime:' as never]
})
).toBe(false)
})
})