test(session): pin the tombstone/gap boundary the two readings meet at

An explicit empty tabsByWorktree row means the user closed the last
terminal; adoption reads an empty base row as a gap to fill. Same value,
opposite readings, so the boundary is asserted rather than argued: the
tombstone lands in the owning partition, restores as a present empty row
rather than a deleted key, is declined by the real seeding predicate, is
published as an empty list, and the legacy-transition resurrection
happens once and cannot recur.
This commit is contained in:
Neil
2026-09-11 01:42:36 -07:00
parent edd9867c1f
commit 3a378f63bf
@@ -20,6 +20,7 @@ import {
} from '../../../shared/remote-workspace-session-projection'
import type { TerminalTab } from '../../../shared/terminal-tab-types'
import type { WorkspaceSessionState } from '../../../shared/workspace-session-state-types'
import { shouldAutoCreateInitialTerminal } from '@/components/terminal/initial-terminal'
import { mergeDirectSshRemoteWorkspaceSession } from '../hooks/remote-workspace-session-merge'
import { fetchWorkspaceSessionWithRuntimeHostOwners } from './workspace-session-host-hydration'
@@ -530,6 +531,127 @@ describe('ssh host partition write/read round trip', () => {
})
})
describe('ssh host partition and the closed-last-terminal tombstone', () => {
/** Two readings of one value meet here. The terminal layer writes an explicit empty
* `tabsByWorktree` row to mean "the user closed the last terminal" and reserves an ABSENT row for
* "never initialized" — a real tombstone, honoured by `shouldAutoCreateInitialTerminal`. This
* adoption reads an empty row on the BASE side as a gap to fill. Opposite readings, same value,
* so the boundary between them is asserted rather than reasoned about: a mature product ships
* exactly this defect, with a correct write side and one reader that decides seeding on a count
* and never consults the record. */
async function roundTripSession(payload: WorkspaceSessionState): Promise<{
restored: WorkspaceSessionState
partitions: Record<string, WorkspaceSessionState>
}> {
const { buildWorkspaceSessionHostSnapshots } =
await import('./workspace-session-host-persistence')
const snapshots = buildWorkspaceSessionHostSnapshots(payload, {
repos: [{ id: REPO_ID, connectionId: TARGET_ID, executionHostId: null }],
worktreesByRepo: {}
})
const partitions: Record<string, WorkspaceSessionState> = {}
for (const snapshot of snapshots) {
partitions[snapshot.hostId ?? 'local'] = snapshot.state
}
const read = await fetchWorkspaceSessionWithRuntimeHostOwners(
partitionedApi(partitions as never),
repos
)
return { restored: read.session, partitions }
}
it('writes an SSH workspace emptied by this build into the partition that owns it', async () => {
// The precondition the whole non-recurrence claim rests on: the tombstone lands in
// `ssh:<targetId>` and `local` keeps no row, so the legacy shape cannot be regenerated.
const { partitions } = await roundTripSession(
session({ tabsByWorktree: { [WORKTREE_ID]: [] } })
)
expect(partitions[SSH_HOST_ID]?.tabsByWorktree?.[WORKTREE_ID]).toEqual([])
expect(Object.hasOwn(partitions.local?.tabsByWorktree ?? {}, WORKTREE_ID)).toBe(false)
})
it('restores that tombstone as an explicit empty row, not a deleted key', async () => {
// A deleted key reads back as "never initialized" and the workspace re-seeds on every launch,
// which is the defect the tombstone exists to prevent. Presence is the whole signal.
const { restored } = await roundTripSession(session({ tabsByWorktree: { [WORKTREE_ID]: [] } }))
expect(Object.hasOwn(restored.tabsByWorktree, WORKTREE_ID)).toBe(true)
expect(restored.tabsByWorktree[WORKTREE_ID]).toEqual([])
})
it('leaves the restored workspace un-seeded by the shared seeding predicate', async () => {
// Asserted through the real predicate rather than by inspecting the row, because the row being
// right is worth nothing if the reader that acts on it disagrees.
const { restored } = await roundTripSession(session({ tabsByWorktree: { [WORKTREE_ID]: [] } }))
expect(
shouldAutoCreateInitialTerminal(
restored.tabsByWorktree[WORKTREE_ID]?.length ?? 0,
Object.hasOwn(restored.tabsByWorktree, WORKTREE_ID)
)
).toBe(false)
})
it('does not adopt a stale populated ssh row over a tombstone in the owning partition', async () => {
// The collision stated directly. The tombstone is in `ssh:<targetId>` — where this build writes
// it — and adoption must neither hand stale tabs back nor read the row as a gap.
const partitions = {
local: session({ tabsByWorktree: {} }),
[SSH_HOST_ID]: session({ tabsByWorktree: { [WORKTREE_ID]: [] } })
}
const read = await fetchWorkspaceSessionWithRuntimeHostOwners(
partitionedApi(partitions as never),
repos
)
expect(read.session.tabsByWorktree[WORKTREE_ID]).toEqual([])
expect(Object.hasOwn(read.session.tabsByWorktree, WORKTREE_ID)).toBe(true)
})
it('resurrects the legacy-transition shape exactly once and not again', async () => {
// The documented knownGap, and the claim that makes it acceptable. Boot 1 adopts the stranded
// tabs back over `local`'s empty row — non-destructive, and the repair working. The user then
// empties the workspace on THIS build, and boot 2 must hold the tombstone: the row now lives in
// the owning partition and `local` no longer names the workspace, so there is nothing left to
// resurrect from. A gap that recurred would be a permanent re-seed, not a one-shot.
const firstBoot = await fetchWorkspaceSessionWithRuntimeHostOwners(
partitionedApi({
local: session({ tabsByWorktree: { [WORKTREE_ID]: [] } }),
[SSH_HOST_ID]: session({ tabsByWorktree: { [WORKTREE_ID]: [tab('tab-stale')] } })
} as never),
repos
)
expect(firstBoot.session.tabsByWorktree[WORKTREE_ID]?.map((entry) => entry.id)).toEqual([
'tab-stale'
])
const { restored: secondBoot } = await roundTripSession(
session({ ...firstBoot.session, tabsByWorktree: { [WORKTREE_ID]: [] } })
)
expect(secondBoot.tabsByWorktree[WORKTREE_ID]).toEqual([])
expect(
shouldAutoCreateInitialTerminal(
secondBoot.tabsByWorktree[WORKTREE_ID]?.length ?? 0,
Object.hasOwn(secondBoot.tabsByWorktree, WORKTREE_ID)
)
).toBe(false)
})
it('publishes the tombstone rather than a row the host can read as unknown', async () => {
// Rule 3, client-publishes -> other-client-reads. An emptied workspace must publish its empty
// list so a paired client sees the same state; the merge's `hostUnknown` defence covers tabs
// this client holds, and an empty row is exactly what it holds here.
const { restored } = await roundTripSession(session({ tabsByWorktree: { [WORKTREE_ID]: [] } }))
const published = exportRemoteWorkspaceSession(restored, {
isTargetWorktree: (worktreeId) => worktreeId === WORKTREE_ID
})
expect(published.tabsByWorktreePath[WORKTREE_PATH]).toEqual([])
})
})
describe('ssh host partition remote-workspace round trip', () => {
it('does not delete the worktree tabs across a publish and the next pull', async () => {
const partitions = strandedPartitions([tab('tab-runtime')])