mirror of
https://github.com/stablyai/orca.git
synced 2026-09-30 16:02:56 +00:00
test: retire long-tail cases whose assertion is decided by the test itself (#24132)
Resumes the backlog sweep at a chunk size that actually gets read. Six auditors, 84 files
each, and all six read their full scope case-by-case against production — the first wave
where every chunk closed with no gap. 33 case declarations removed across 22 files, 1 test
file deleted, 826 lines gone. No production code touched.
This wave exists because a conclusion of mine was wrong. I had recorded that yield collapsed
~36x and that deletion was no longer the high-value work. I was dividing cases removed by
files IN SCOPE while the fraction auditors actually READ fell from 100% to about 4%, because
I kept handing them 300-800 files. Recomputed against files read, yield has been flat at 4-7
per 100 with no downward trend. This wave came in at 8.2.
The most instructive removal looked like the most valuable test in scope.
`orchestration-worker-release-reap-fixed.func.test.ts` cites a production bug by two
identifiers, describes orphaned PTYs accumulating until `TasksMax=4096` aborts processes on
EAGAIN, and advertises itself as the functional tier wiring the real orchestration RPC
surface, the real `OrchestrationDb` and the real release modules. Deleting it leaves no
reference to that bug anywhere in `src`.
It still had to go: its fake runtime performed the fence it asserted —
if (pty.incarnationId !== inc) { return null }
handleTable.set('term_reminted', { ptyId, epoch: rendererGraphEpoch })
— so the case checking that a reused ptyId with a mismatched incarnation does not resolve was
checking a decision its own spy made twenty lines earlier. The real fence is owned by
`orca-runtime-terminal-handle-incarnation.test.ts:257`, and the other two cases replay
`orchestration-worker-release-incarnation-fallback.test.ts` (which uses a plain
`mockReturnValue` rather than reimplementing the remint) and `worker/worker-release.test.ts:23`.
"Integration test" and "wires real modules" describe the scaffolding, not the asserted step.
Other removals: a self-comparison disguised by an alias, where
`export const getIssueOwnerRepo = getOwnerRepo` makes a case asserting the two "agree" into
`f(x) === f(x)`; four cases whose `vi.mock` of `resolveIssueSource` made both the preference
value and the topology inert; five verdict-precedence cases owned by a verdict-agnostic block;
three call-shape probes on one-line store pass-throughs whose real contracts are driven by
behavioural neighbours; and a `export type _Ref = [...]` declaration whose own comment admits
it exists only to preserve test-only module-surface references.
Kept after checking production rather than shape. An auditor found two near-identical
ten-reconnect loops and kept both: one uses a test-local live-lease filter, the other the
shipped `sshRemotePtyLeaseAllowsReattach` predicate, and the file's own comment explains the
duality is deliberate "so the two cannot drift". Another kept a paths-alignment case that
looks like a validator tested against its own list, because adding a generated file without
registering its path does fail it — and `shellReadyWrappersExist` uses that registered list to
decide whether a partial tree needs regeneration.
Production duplication is now confirmed four times over, and it is why mirrored tests exist:
`createUpdateWorktreeLineage`/`createAssignWorktreeParent` differ by one `console.error`
string; `terminal-path-tap.ts` and `document/path-tap.ts` carry hand-maintained copies of
`matchFilePathAtColumn` under a docblock reading "keep the two in sync". In those cases both
test sides are load-bearing and the duplication belongs on a refactor list.
`mobile/tests-typecheck-baseline.txt` loses one entry. Trimming
`relay-host-signed-out-verdict.test.ts` made it typecheck clean, so the ratchet required
pruning its grandfathered entry — the file graduates from exempt to enforced. Baseline is now
124 entries, down from 125.
Verified: 690 test files / 7,560 cases pass across the touched desktop areas; the modified
mobile files pass (162 cases); `check-tests-typecheck-ratchet.mjs` OK (898 files in program,
124 grandfathered); `check-reliability-gates.mjs` 140 gates; the deleted file is absent from
the gate manifest, `cloud/package.json` and the mobile baseline; nothing under
`mobile/src/test-support/rpc-recording/` or `mobile/rpc-foundation/goldens/` touched.
This commit is contained in:
@@ -98,23 +98,6 @@ describe('diagnoseConnection', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('marks authenticated Relay liveness failures as safe to send', () => {
|
||||
expect(
|
||||
diagnoseConnection({
|
||||
endpoint: 'ws://192.168.1.2:6768',
|
||||
state: 'reconnecting',
|
||||
activePath: 'relay',
|
||||
entries: [
|
||||
{
|
||||
...event('Relay health check failed'),
|
||||
code: 'liveness-timeout',
|
||||
path: 'relay'
|
||||
}
|
||||
]
|
||||
}).reportability
|
||||
).toBe('orca-relay')
|
||||
})
|
||||
|
||||
it.each([
|
||||
['direct timeout', 'connect-timeout', 'tailscale'],
|
||||
['handshake timeout', 'handshake-timeout', 'relay'],
|
||||
|
||||
@@ -150,26 +150,6 @@ describe('startMobileDictationDesktopSession', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('does not surface a desktop-start failure after the start became stale', async () => {
|
||||
let setNewerStart = () => undefined
|
||||
const harness = createStartHarness({
|
||||
sendRequest: async (method) => {
|
||||
if (method === 'speech.dictation.start') {
|
||||
setNewerStart()
|
||||
throw new Error('Desktop start failed')
|
||||
}
|
||||
return OK_RESPONSE
|
||||
}
|
||||
})
|
||||
setNewerStart = harness.setNewerStart
|
||||
|
||||
await expect(startMobileDictationDesktopSession(harness.options)).resolves.toBe(false)
|
||||
|
||||
expect(harness.setIdle).not.toHaveBeenCalled()
|
||||
expect(harness.getActiveId()).toBe('dictation-b')
|
||||
expect(harness.commitRecordingStart).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('commits recording before returning a current start to the hook', async () => {
|
||||
const harness = createStartHarness()
|
||||
|
||||
|
||||
@@ -188,29 +188,4 @@ describe('which commit the page reports its frame from', () => {
|
||||
})
|
||||
expect(reports).toBe(1)
|
||||
})
|
||||
|
||||
it('reports the screen that arrived and not the one that replaced it', async () => {
|
||||
const route = deferredRouteChunk()
|
||||
const Screen = lazy(() => route.chunk.then(withRouteScreenPaintReport))
|
||||
let reports = 0
|
||||
await act(async () => {
|
||||
create(
|
||||
<RouteScreenPaintProvider
|
||||
report={() => {
|
||||
reports += 1
|
||||
return () => undefined
|
||||
}}
|
||||
>
|
||||
<Suspense fallback={null}>
|
||||
<Screen />
|
||||
</Suspense>
|
||||
</RouteScreenPaintProvider>
|
||||
)
|
||||
})
|
||||
await act(async () => {
|
||||
route.arrive()
|
||||
})
|
||||
// Once per screen that commits: the shell latches the first, and a re-render is not a new one.
|
||||
expect(reports).toBe(1)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -197,11 +197,6 @@ describe('a verb the shell refuses', () => {
|
||||
expect(reasons).toEqual(['native_verb_failed', 'reply-too-large', 'native_verb_result'])
|
||||
})
|
||||
|
||||
it('names the frame refusal when the reply could never have reached the page', async () => {
|
||||
const error = await rejectionFrom(() => Promise.resolve({ value: 'a'.repeat(9 * 1024 * 1024) }))
|
||||
expect(error.reason).toBe('reply-too-large')
|
||||
})
|
||||
|
||||
it('names the grant when this side refused before sending', async () => {
|
||||
const page = createPageClient()
|
||||
page.deliver({ ...INIT, grants: { ...GRANTS, native: ['navigate'] } })
|
||||
|
||||
@@ -176,14 +176,6 @@ describe('the catch-all switch', () => {
|
||||
expect(dependencies.routes[0]?.params).toEqual({ tab: 'diff' })
|
||||
})
|
||||
|
||||
it('never carries its own segments back as query params', async () => {
|
||||
// `useLocalSearchParams` merges the route params into the search params, and both of these are
|
||||
// already in the pathname above.
|
||||
dependencies.params = { hostId: 'host-1', page: ['settings'] }
|
||||
await render()
|
||||
expect(dependencies.routes[0]).not.toHaveProperty('params')
|
||||
})
|
||||
|
||||
it('refuses a deep link carrying more params than the bridge will take', async () => {
|
||||
// The schema bounds them, so `shellScreenRoute` answers null and the switch refuses. Dropping
|
||||
// the overflow instead would open the page on a screen missing the state it was asked for.
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import type { GitHubWorkItem } from '../../../src/shared/github/work-item-types'
|
||||
import type { GitLabWorkItem } from '../../../src/shared/gitlab-types'
|
||||
import type { LinearIssue } from '../../../src/shared/linear/issue-types'
|
||||
import {
|
||||
buildGitHubLinkedWorkItem,
|
||||
buildGitLabLinkedWorkItem,
|
||||
@@ -240,6 +237,3 @@ describe('resolveComposerBranchPick', () => {
|
||||
expect(pick.base.branchNameOverride).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
// Keep the exported type aliases referenced so the module surface stays covered.
|
||||
export type _Ref = [GitHubWorkItem, GitLabWorkItem, LinearIssue]
|
||||
|
||||
@@ -256,15 +256,6 @@ describe('the orca.yaml hooks require nothing', () => {
|
||||
})
|
||||
|
||||
describe('sparse presets', () => {
|
||||
it('reads the recorded preset, which carries no repoId or timestamps', () => {
|
||||
const parsed = repoSparsePresetListSchema.safeParse({
|
||||
presets: [{ id: 'p1', name: 'docs', directories: ['docs'] }]
|
||||
})
|
||||
expect(parsed.success && parsed.data).toEqual([
|
||||
{ id: 'p1', name: 'docs', directories: ['docs'] }
|
||||
])
|
||||
})
|
||||
|
||||
it('drops a preset with no id, which the picker could not select', () => {
|
||||
const parsed = repoSparsePresetListSchema.safeParse({ presets: [{ name: 'docs' }] })
|
||||
expect(parsed.success && parsed.data).toEqual([])
|
||||
|
||||
@@ -136,20 +136,6 @@ describe('mobile endpoint supervisor nudges', () => {
|
||||
supervisor.stop()
|
||||
})
|
||||
|
||||
it('does not suspend a relay from one focus RPC failure', async () => {
|
||||
const logical = new FakeLogicalClient('connected', 'relay')
|
||||
const deps = dependencies()
|
||||
const supervisor = new MobileEndpointSupervisor(logical, host.id, relay, deps)
|
||||
await supervisor.start()
|
||||
|
||||
supervisor.nudge('focus')
|
||||
await vi.advanceTimersByTimeAsync(0)
|
||||
expect(logical.suspendActiveSession).not.toHaveBeenCalled()
|
||||
expect(deps.openRelay).not.toHaveBeenCalled()
|
||||
expect(logical.getState()).toBe('connected')
|
||||
supervisor.stop()
|
||||
})
|
||||
|
||||
it('queues a network replacement that lands while another dial owns the mutex', async () => {
|
||||
const logical = new FakeLogicalClient('disconnected', 'lan')
|
||||
let resolveWrite: (() => void) | null = null
|
||||
|
||||
@@ -55,12 +55,6 @@ describe('evaluateMobileWebBundleCompat', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('separates permission to fetch a manifest from permission to open one', () => {
|
||||
// Why: both are `ok`, and a caller that mounted on the first would mount an unchecked bundle.
|
||||
expect(evaluate({ manifest: null })).toEqual({ kind: 'ok', manifestChecked: false })
|
||||
expect(evaluate({})).toEqual({ kind: 'ok', manifestChecked: true })
|
||||
})
|
||||
|
||||
it('blocks a host that ships no bundle', () => {
|
||||
expect(evaluate({ hostCapabilities: ['browser.screencast.v1'] })).toEqual({
|
||||
kind: 'blocked',
|
||||
|
||||
@@ -234,40 +234,4 @@ describe('classifyConnection with a signed-out desktop', () => {
|
||||
classifyConnection({ ...base, state: 'reconnecting', reconnectAttempts: 20 }).label
|
||||
).toBe(SIGNED_OUT_LABEL)
|
||||
})
|
||||
|
||||
it('reads as stale once this session had been connected', () => {
|
||||
expect(
|
||||
classifyConnection({ ...base, state: 'reconnecting', lastConnectedAt: 1, nowMs: 2 }).reason
|
||||
).toBe('stale')
|
||||
})
|
||||
|
||||
// A Tailscale endpoint cannot make "sign in on your desktop" better advice.
|
||||
it('never appends the Tailscale hint', () => {
|
||||
expect(
|
||||
classifyConnection({ ...base, state: 'reconnecting', endpoint: '100.64.0.1' })
|
||||
).not.toHaveProperty('hint')
|
||||
})
|
||||
|
||||
it('never outranks a connected session', () => {
|
||||
expect(classifyConnection({ ...base, state: 'connected' }).label).toBe('Connected')
|
||||
})
|
||||
|
||||
// Re-pairing, not signing in, is the remedy when the pairing itself is dead.
|
||||
it('never outranks a revoked pairing', () => {
|
||||
expect(classifyConnection({ ...base, state: 'reconnecting', pairingRejected: true }).kind).toBe(
|
||||
'auth-failed'
|
||||
)
|
||||
})
|
||||
|
||||
it('leaves every other verdict alone when the desktop is not signed out', () => {
|
||||
expect(
|
||||
classifyConnection({
|
||||
state: 'connecting',
|
||||
reconnectAttempts: 0,
|
||||
lastConnectedAt: null,
|
||||
pendingPath: 'relay',
|
||||
relayHostReachability: 'connecting'
|
||||
}).label
|
||||
).toBe('Connecting via Relay…')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -275,26 +275,6 @@ describe('RpcClientStreamRegistry', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('keeps a disposed browser tombstone until ready can be unsubscribed', () => {
|
||||
const { registry, sent } = createRegistry()
|
||||
const dispose = registry.subscribe('browser.screencast', { page: 'page-1' }, () => {})
|
||||
const request = sent[0]!
|
||||
|
||||
dispose()
|
||||
expect(sent).toHaveLength(1)
|
||||
|
||||
registry.handleResponse(
|
||||
streamingResponse(request.id, {
|
||||
type: 'ready',
|
||||
subscriptionId: 'browser-screencast:page-1:test'
|
||||
})
|
||||
)
|
||||
expect(sent[1]).toMatchObject({
|
||||
method: 'browser.screencast.unsubscribe',
|
||||
params: { subscriptionId: 'browser-screencast:page-1:test' }
|
||||
})
|
||||
})
|
||||
|
||||
it('releases a replayed browser stream replaced by a new one before its ready', () => {
|
||||
const { registry, sent } = createRegistry()
|
||||
registry.subscribe('browser.screencast', { page: 'page-1' }, () => {})
|
||||
|
||||
@@ -115,7 +115,6 @@ src/transport/mobile-relay-rpc-session.test.ts
|
||||
src/transport/mobile-relay-runtime-failover.test.ts
|
||||
src/transport/pairing-relay-candidate.test.ts
|
||||
src/transport/pre-profile-pairing-coordinator.test.ts
|
||||
src/transport/relay-host-signed-out-verdict.test.ts
|
||||
src/transport/rpc-client-synthesized-close-diagnostics.test.ts
|
||||
src/transport/rpc-operation.test.ts
|
||||
src/transport/rpc-session-liveness-watchdog.test.ts
|
||||
|
||||
@@ -730,23 +730,6 @@ describe('GitHub issue source split', () => {
|
||||
expect(result.issueSourceFellBack).toBeUndefined()
|
||||
})
|
||||
|
||||
it("preference='auto' + no upstream → queries origin", async () => {
|
||||
resolveIssueSourceMock.mockResolvedValueOnce({
|
||||
source: { owner: 'solo', repo: 'orca' },
|
||||
fellBack: false
|
||||
})
|
||||
getOwnerRepoMock.mockResolvedValueOnce({ owner: 'solo', repo: 'orca' })
|
||||
ghExecFileAsyncMock.mockResolvedValueOnce({ stdout: '[]' }).mockResolvedValueOnce({
|
||||
stdout: '[]'
|
||||
})
|
||||
|
||||
await listWorkItems('/repo-root', 10, undefined, undefined, 'auto')
|
||||
|
||||
expect(ghExecFileAsyncMock).toHaveBeenNthCalledWith(1, issueSearchArgs('solo/orca'), {
|
||||
cwd: '/repo-root'
|
||||
})
|
||||
})
|
||||
|
||||
it("preference='auto' + upstream exists → PRs query upstream too", async () => {
|
||||
// Why: fork-contribution PRs live on the upstream repo — the fork's own
|
||||
// PR list is almost always empty. 'auto' must resolve PRs upstream-first
|
||||
@@ -799,22 +782,6 @@ describe('GitHub issue source split', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it("preference='upstream' + upstream exists → queries upstream", async () => {
|
||||
resolveIssueSourceMock.mockResolvedValueOnce({
|
||||
source: { owner: 'stablyai', repo: 'orca' },
|
||||
fellBack: false
|
||||
})
|
||||
getOwnerRepoMock.mockResolvedValueOnce({ owner: 'fork', repo: 'orca' })
|
||||
ghExecFileAsyncMock.mockResolvedValueOnce({ stdout: '[]' }).mockResolvedValueOnce({
|
||||
stdout: '[]'
|
||||
})
|
||||
|
||||
const result = await listWorkItems('/repo-root', 10, undefined, undefined, 'upstream')
|
||||
|
||||
expect(decodedIssueSearchPath(0)).toContain('q=repo:stablyai/orca is:issue is:open')
|
||||
expect(result.issueSourceFellBack).toBeUndefined()
|
||||
})
|
||||
|
||||
it("preference='upstream' + no upstream → falls back to origin with fellBack=true", async () => {
|
||||
resolveIssueSourceMock.mockResolvedValueOnce({
|
||||
source: { owner: 'solo', repo: 'orca' },
|
||||
@@ -831,36 +798,6 @@ describe('GitHub issue source split', () => {
|
||||
expect(result.issueSourceFellBack).toBe(true)
|
||||
})
|
||||
|
||||
it("preference='origin' + upstream exists → queries origin (not upstream)", async () => {
|
||||
resolveIssueSourceMock.mockResolvedValueOnce({
|
||||
source: { owner: 'fork', repo: 'orca' },
|
||||
fellBack: false
|
||||
})
|
||||
getOwnerRepoMock.mockResolvedValueOnce({ owner: 'fork', repo: 'orca' })
|
||||
ghExecFileAsyncMock.mockResolvedValueOnce({ stdout: '[]' }).mockResolvedValueOnce({
|
||||
stdout: '[]'
|
||||
})
|
||||
|
||||
await listWorkItems('/repo-root', 10, undefined, undefined, 'origin')
|
||||
|
||||
expect(decodedIssueSearchPath(0)).toContain('q=repo:fork/orca is:issue is:open')
|
||||
})
|
||||
|
||||
it("preference='origin' + no upstream → queries origin", async () => {
|
||||
resolveIssueSourceMock.mockResolvedValueOnce({
|
||||
source: { owner: 'solo', repo: 'orca' },
|
||||
fellBack: false
|
||||
})
|
||||
getOwnerRepoMock.mockResolvedValueOnce({ owner: 'solo', repo: 'orca' })
|
||||
ghExecFileAsyncMock.mockResolvedValueOnce({ stdout: '[]' }).mockResolvedValueOnce({
|
||||
stdout: '[]'
|
||||
})
|
||||
|
||||
await listWorkItems('/repo-root', 10, undefined, undefined, 'origin')
|
||||
|
||||
expect(decodedIssueSearchPath(0)).toContain('q=repo:solo/orca is:issue is:open')
|
||||
})
|
||||
|
||||
it('surfaces upstreamCandidate in sources regardless of effective preference', async () => {
|
||||
// Why: the renderer selector needs to keep rendering after the user picks
|
||||
// 'origin'. That requires the envelope to carry the raw upstream even
|
||||
|
||||
@@ -144,32 +144,6 @@ describe('getPRForBranch', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('omits maintainerCanModify when the API does not report the flag', async () => {
|
||||
getOwnerRepoMock.mockResolvedValueOnce({ owner: 'stablyai', repo: 'orca' })
|
||||
getOwnerRepoForRemoteMock.mockResolvedValueOnce({ owner: 'stablyai', repo: 'orca' })
|
||||
ghExecFileAsyncMock.mockResolvedValueOnce({
|
||||
stdout: JSON.stringify({
|
||||
head: {
|
||||
ref: 'fix-sidebar',
|
||||
repo: {
|
||||
full_name: 'stablyai/orca',
|
||||
name: 'orca',
|
||||
clone_url: 'https://github.com/stablyai/orca.git',
|
||||
ssh_url: 'git@github.com:stablyai/orca.git',
|
||||
owner: { login: 'stablyai' }
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
await expect(getPullRequestPushTarget('/repo-root', 1738)).resolves.toEqual({
|
||||
pushTarget: {
|
||||
remoteName: 'origin',
|
||||
branchName: 'fix-sidebar'
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
it('uses origin for same-repository PR push targets', async () => {
|
||||
getOwnerRepoMock.mockResolvedValueOnce({ owner: 'stablyai', repo: 'orca' })
|
||||
getOwnerRepoForRemoteMock.mockResolvedValueOnce({ owner: 'stablyai', repo: 'orca' })
|
||||
|
||||
@@ -149,16 +149,6 @@ describe('github owner/repo resolution', () => {
|
||||
expect(gitRemoteGetUrlCalls('origin')).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('prefers upstream for issue owner/repo resolution', async () => {
|
||||
mockGitRemoteCommands({
|
||||
origin: 'git@github.com:fork/orca.git\n',
|
||||
upstream: 'git@github.com:stablyai/orca.git\n'
|
||||
})
|
||||
|
||||
await expect(getIssueOwnerRepo('/repo')).resolves.toEqual({ owner: 'stablyai', repo: 'orca' })
|
||||
expect(gitRemoteGetUrlCalls('upstream')).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('falls back to origin when upstream is present but non-GitHub', async () => {
|
||||
mockGitRemoteCommands({
|
||||
origin: 'git@github.com:fork/orca.git\n',
|
||||
|
||||
@@ -30,7 +30,7 @@ vi.mock('./local-git-config-signature', () => ({
|
||||
|
||||
import { _resetRemoteNameListingCache } from '../git/remote-name-listing'
|
||||
import { getOwnerRepoForRemote, _resetOwnerRepoCache } from './github-repository-identity'
|
||||
import { getOwnerRepo, getIssueOwnerRepo } from './github-owner-repo-selection'
|
||||
import { getOwnerRepo } from './github-owner-repo-selection'
|
||||
import { getRepoUpstream } from './client'
|
||||
|
||||
const FORK_PATH = '/tmp/fork-checkout'
|
||||
@@ -84,13 +84,6 @@ describe('issue #7331: fork PR owner/repo resolution', () => {
|
||||
expect(prRepo).toEqual({ owner: 'stablyai', repo: 'orca' })
|
||||
})
|
||||
|
||||
it('getOwnerRepo and getIssueOwnerRepo agree on a fork checkout', async () => {
|
||||
const prRepo = await getOwnerRepo(FORK_PATH)
|
||||
const issueRepo = await getIssueOwnerRepo(FORK_PATH)
|
||||
|
||||
expect(prRepo).toEqual(issueRepo)
|
||||
})
|
||||
|
||||
it('getOwnerRepo falls back to origin when there is no upstream remote', async () => {
|
||||
const prRepo = await getOwnerRepo(NON_FORK_PATH)
|
||||
|
||||
|
||||
@@ -708,76 +708,6 @@ describe('getWorkItemDetails', () => {
|
||||
expect(getWorkItemMock).toHaveBeenCalledWith('/repo-root', 42, 'pr', null, {}, 'origin')
|
||||
})
|
||||
|
||||
// Why: a rate-limited/auth-failed file fetch must not render as an empty PR;
|
||||
// the Files tab keys its retry state off details.filesUnavailable.
|
||||
it('flags filesUnavailable when the PR file fetch fails but leaves the PR empty otherwise intact', async () => {
|
||||
getWorkItemMock.mockResolvedValueOnce({
|
||||
id: 'pr:8305',
|
||||
type: 'pr',
|
||||
number: 8305,
|
||||
title: 'Files fetch fails',
|
||||
state: 'open',
|
||||
url: 'https://github.com/acme/widgets/pull/8305',
|
||||
labels: [],
|
||||
updatedAt: '2026-07-11T00:00:00Z',
|
||||
author: 'pr-author'
|
||||
})
|
||||
getOwnerRepoForRemoteMock.mockResolvedValue({ owner: 'acme', repo: 'widgets' })
|
||||
getPRCommentsMock.mockResolvedValue([])
|
||||
getPRChecksMock.mockResolvedValue([])
|
||||
ghExecFileAsyncMock.mockImplementation(async (args: string[]) => {
|
||||
const target = args.at(-1)
|
||||
if (target === 'repos/acme/widgets/pulls/8305') {
|
||||
return {
|
||||
stdout: JSON.stringify({ head: { sha: 'head-sha' }, base: { sha: 'base-sha' } })
|
||||
}
|
||||
}
|
||||
if (target === 'repos/acme/widgets/pulls/8305/files?per_page=100') {
|
||||
throw new Error('gh: API rate limit exceeded (403)')
|
||||
}
|
||||
return { stdout: JSON.stringify({ data: {} }) }
|
||||
})
|
||||
|
||||
const details = await getWorkItemDetails('/repo-root', 8305, 'pr')
|
||||
|
||||
expect(details?.filesUnavailable).toBe(true)
|
||||
expect(details?.files).toBeUndefined()
|
||||
})
|
||||
|
||||
it('treats an empty file list as a genuinely empty PR, not an unavailable one', async () => {
|
||||
getWorkItemMock.mockResolvedValueOnce({
|
||||
id: 'pr:8306',
|
||||
type: 'pr',
|
||||
number: 8306,
|
||||
title: 'Empty PR',
|
||||
state: 'open',
|
||||
url: 'https://github.com/acme/widgets/pull/8306',
|
||||
labels: [],
|
||||
updatedAt: '2026-07-11T00:00:00Z',
|
||||
author: 'pr-author'
|
||||
})
|
||||
getOwnerRepoForRemoteMock.mockResolvedValue({ owner: 'acme', repo: 'widgets' })
|
||||
getPRCommentsMock.mockResolvedValue([])
|
||||
getPRChecksMock.mockResolvedValue([])
|
||||
ghExecFileAsyncMock.mockImplementation(async (args: string[]) => {
|
||||
const target = args.at(-1)
|
||||
if (target === 'repos/acme/widgets/pulls/8306') {
|
||||
return {
|
||||
stdout: JSON.stringify({ head: { sha: 'head-sha' }, base: { sha: 'base-sha' } })
|
||||
}
|
||||
}
|
||||
if (target === 'repos/acme/widgets/pulls/8306/files?per_page=100') {
|
||||
return { stdout: '[]' }
|
||||
}
|
||||
return { stdout: JSON.stringify({ data: {} }) }
|
||||
})
|
||||
|
||||
const details = await getWorkItemDetails('/repo-root', 8306, 'pr')
|
||||
|
||||
expect(details?.filesUnavailable).toBe(false)
|
||||
expect(details?.files).toEqual([])
|
||||
})
|
||||
|
||||
// Why: `gh pr view` omits avatar_url, so the login-based github.com URL 404s on
|
||||
// GHE. getWorkItemDetails must resolve author/reviewer/assignee avatars via the
|
||||
// GraphQL user(login:) batch and stamp them onto the returned item. See #8784.
|
||||
|
||||
@@ -393,61 +393,4 @@ describe('Store', () => {
|
||||
[TEST_LEAF_2]: 'runtime-pty-2'
|
||||
})
|
||||
})
|
||||
|
||||
it('does not restore a binding with an explicit SSH termination tombstone', async () => {
|
||||
const store = await createStore()
|
||||
const hostId = 'ssh:ssh-1'
|
||||
const session: WorkspaceSessionState = {
|
||||
activeRepoId: 'r1',
|
||||
activeWorktreeId: 'wt1',
|
||||
activeTabId: 'tab1',
|
||||
tabsByWorktree: {
|
||||
wt1: [
|
||||
{
|
||||
id: 'tab1',
|
||||
worktreeId: 'wt1',
|
||||
title: 'Terminal',
|
||||
customTitle: null,
|
||||
color: null,
|
||||
sortOrder: 0,
|
||||
createdAt: 1,
|
||||
ptyId: 'ssh:ssh-1@@closed'
|
||||
}
|
||||
]
|
||||
},
|
||||
terminalLayoutsByTabId: {
|
||||
tab1: {
|
||||
root: { type: 'leaf', leafId: TEST_LEAF_1 },
|
||||
activeLeafId: TEST_LEAF_1,
|
||||
expandedLeafId: null,
|
||||
ptyIdsByLeafId: { [TEST_LEAF_1]: 'ssh:ssh-1@@closed' }
|
||||
}
|
||||
}
|
||||
}
|
||||
store.setWorkspaceSession(session, hostId)
|
||||
store.upsertSshRemotePtyLease({
|
||||
targetId: 'ssh-1',
|
||||
ptyId: 'closed',
|
||||
worktreeId: 'wt1',
|
||||
tabId: 'tab1',
|
||||
leafId: TEST_LEAF_1,
|
||||
state: 'terminated'
|
||||
})
|
||||
store.setWorkspaceSession(
|
||||
{
|
||||
...session,
|
||||
tabsByWorktree: {
|
||||
wt1: [{ ...session.tabsByWorktree.wt1[0]!, ptyId: null }]
|
||||
},
|
||||
terminalLayoutsByTabId: {
|
||||
tab1: { ...session.terminalLayoutsByTabId.tab1!, ptyIdsByLeafId: {} }
|
||||
}
|
||||
},
|
||||
hostId
|
||||
)
|
||||
|
||||
const persisted = store.getWorkspaceSession(hostId)
|
||||
expect(persisted.tabsByWorktree.wt1[0]!.ptyId).toBeNull()
|
||||
expect(persisted.terminalLayoutsByTabId.tab1.ptyIdsByLeafId).toEqual({})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -614,22 +614,4 @@ describe('retiring probes for removed repos', () => {
|
||||
expect(listHandlersChanged).toHaveBeenCalledTimes(1)
|
||||
expect(runtimeChanged).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('still notifies a caller whose sweep was coalesced into one already running', async () => {
|
||||
const first = deferred<GitRemoteIdentityProbe>()
|
||||
vi.mocked(probeGitRemoteIdentity).mockReturnValue(first.promise)
|
||||
const store = makeMutableStore([makeRepo()])
|
||||
const listHandlerChanged = vi.fn()
|
||||
// The runtime RPC caller also drops a resolved-worktree cache, so it must not be dropped.
|
||||
const runtimeChanged = vi.fn()
|
||||
|
||||
enrichMissingRepoGitRemoteIdentities(store, { onChanged: listHandlerChanged })
|
||||
enrichMissingRepoGitRemoteIdentities(store, { onChanged: runtimeChanged })
|
||||
|
||||
first.resolve(resolvedProbe)
|
||||
await drainEnrichmentSweep()
|
||||
|
||||
expect(listHandlerChanged).toHaveBeenCalledTimes(1)
|
||||
expect(runtimeChanged).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -388,19 +388,6 @@ describe('E2EEChannel', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('cross-compatibility', () => {
|
||||
it('desktop encrypt is decryptable by desktop decrypt (sanity)', () => {
|
||||
const a = generateKeyPair()
|
||||
const b = generateKeyPair()
|
||||
const sharedA = deriveSharedKey(a.secretKey, b.publicKey)
|
||||
const sharedB = deriveSharedKey(b.secretKey, a.publicKey)
|
||||
|
||||
const msg = '{"method":"terminal.subscribe","params":{"terminal":"t1"}}'
|
||||
const enc = encrypt(msg, sharedA)
|
||||
expect(decrypt(enc, sharedB)).toBe(msg)
|
||||
})
|
||||
})
|
||||
|
||||
describe('destroy', () => {
|
||||
it('clears state and stops forwarding', () => {
|
||||
const ctx = setup()
|
||||
|
||||
@@ -1,358 +0,0 @@
|
||||
// PRB-0219 / upstream #18737 — FUNCTIONAL (integration) reproduction of the steady-state
|
||||
// worker-release reap LEAK.
|
||||
//
|
||||
// This is the "functional/integration" tier of the PRB-0219 test strategy. It wires the REAL
|
||||
// orchestration RPC surface (orchestration.workerStart / workerRelease / workerList), the REAL
|
||||
// OrchestrationDb, and the REAL release completion + observation modules against a fake runtime
|
||||
// that faithfully models the two identity planes involved in the bug:
|
||||
//
|
||||
// * VOLATILE, epoch-fenced handle table — `handleTable` keyed by terminal handle, each entry
|
||||
// stamped with the `rendererGraphEpoch` at which it was issued. `showTerminal` resolves a
|
||||
// handle ONLY while its stamped epoch matches the current epoch (mirrors getLiveLeafForHandle
|
||||
// throwing 'terminal_handle_stale' on a rendererGraphEpoch bump). A relay reconnect / renderer
|
||||
// remount on headless serve bumps the epoch.
|
||||
// * DURABLE, incarnation-addressed process table — `ptysById` keyed by the ptyId embedded in the
|
||||
// worker's persisted process_incarnation (`${ptyId}:${incarnationId}`). The pty stays LIVE
|
||||
// across an epoch bump; nothing about the graph epoch kills the process.
|
||||
//
|
||||
// The bug (mechanism): when the epoch bumps, the durable db handle stops resolving through
|
||||
// showTerminal WHILE THE PTY IS STILL ALIVE. inspectWorkerTerminal swallows the throw and reports
|
||||
// `missing`; completeWorkerTerminalRelease then commits `release_unknown` and returns WITHOUT ever
|
||||
// calling runtime.closeTerminal — so the process/PTY leaks. On mtl-02 those orphans accumulate in
|
||||
// the orca-serve@factory cgroup until TasksMax=4096 is hit and Bun/omp abort() on EAGAIN.
|
||||
//
|
||||
// The observable leak signal asserted here: state 'release_unknown' + processAction 'none' +
|
||||
// closeTerminal NEVER called + the pty STILL alive in ptysById + worker-list terminalState stuck
|
||||
// on 'release_unknown' (never 'released').
|
||||
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import { ORCHESTRATION_METHODS } from './orchestration'
|
||||
import { eraseRpcMethods, type RpcContext } from '../core'
|
||||
import { OrchestrationDb } from '../../orchestration/db'
|
||||
import { OrcaRuntimeService } from '../../orca-runtime'
|
||||
|
||||
describe('PRB-0219 worker-release reap FIX (functional verification)', () => {
|
||||
let db: OrchestrationDb
|
||||
let dbOpen = false
|
||||
let runtime: OrcaRuntimeService
|
||||
let ctx: RpcContext
|
||||
let activeRunId: string
|
||||
|
||||
const coordinatorPaneKey = 'tab_coord:aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa'
|
||||
const workerPaneKey = 'tab_worker:bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb'
|
||||
|
||||
// Dispatch/automation ptyId shape: `${repoId}::${worktreePath}@@${suffix}` (executionHostId:null).
|
||||
const PTY_ID = 'repo-7f3a::/data/wt/factory-task-1@@a1b2c3d4'
|
||||
const INCARNATION_ID = 1
|
||||
const PROCESS_INCARNATION = `${PTY_ID}:${INCARNATION_ID}`
|
||||
|
||||
// ---- fake runtime process/handle planes (module-level so spies can mutate them) ----
|
||||
let ptysById: Map<string, { incarnationId: number; alive: boolean }>
|
||||
let handleTable: Map<string, { ptyId: string; epoch: number }>
|
||||
let rendererGraphEpoch: number
|
||||
let closedPtyIds: string[]
|
||||
|
||||
/** Resolve a handle to its live pty only while its stamped graph epoch is current. */
|
||||
function resolveHandleToLivePty(
|
||||
handle: string
|
||||
): { ptyId: string; pty: { incarnationId: number; alive: boolean } } | null {
|
||||
const entry = handleTable.get(handle)
|
||||
if (!entry) {
|
||||
return null
|
||||
}
|
||||
if (entry.epoch !== rendererGraphEpoch) {
|
||||
// rendererGraphEpoch fence: the durable handle no longer resolves to a live leaf.
|
||||
return null
|
||||
}
|
||||
const pty = ptysById.get(entry.ptyId)
|
||||
if (!pty || !pty.alive) {
|
||||
return null
|
||||
}
|
||||
return { ptyId: entry.ptyId, pty }
|
||||
}
|
||||
|
||||
/** Wire the real RPC surface and db against the two-plane fake runtime. */
|
||||
function setup(): void {
|
||||
ptysById = new Map([
|
||||
['coord-pty', { incarnationId: 1, alive: true }],
|
||||
[PTY_ID, { incarnationId: INCARNATION_ID, alive: true }]
|
||||
])
|
||||
handleTable = new Map([
|
||||
['term_coord', { ptyId: 'coord-pty', epoch: 0 }],
|
||||
['term_worker', { ptyId: PTY_ID, epoch: 0 }]
|
||||
])
|
||||
rendererGraphEpoch = 0
|
||||
closedPtyIds = []
|
||||
|
||||
db = new OrchestrationDb(':memory:')
|
||||
dbOpen = true
|
||||
runtime = new OrcaRuntimeService()
|
||||
runtime.setOrchestrationDb(db)
|
||||
|
||||
// Incarnation-addressed liveness probe: reads the DURABLE plane, so it stays 'live' across the
|
||||
// epoch bump (the process really is still running). Matches the real asymmetry.
|
||||
vi.spyOn(runtime, 'inspectTerminalProcessIncarnationLiveness').mockImplementation(
|
||||
async (incarnation: string) => {
|
||||
const idx = incarnation.lastIndexOf(':')
|
||||
const ptyId = incarnation.slice(0, idx)
|
||||
const pty = ptysById.get(ptyId)
|
||||
return pty?.alive ? 'live' : 'exited'
|
||||
}
|
||||
)
|
||||
|
||||
// The incarnation-addressed re-resolution primitive the fix adds. Present here as a spy so the
|
||||
// same harness proves BOTH tiers: pre-fix completion never calls it (leak); post-fix completion
|
||||
// calls it to remint a live handle (reap). Fence: EXACT incarnationId match only.
|
||||
vi.spyOn(runtime, 'resolveTerminalHandleByProcessIncarnation').mockImplementation(
|
||||
(incarnation: string): string | null => {
|
||||
const idx = incarnation.lastIndexOf(':')
|
||||
const ptyId = incarnation.slice(0, idx)
|
||||
const inc = Number(incarnation.slice(idx + 1))
|
||||
const pty = ptysById.get(ptyId)
|
||||
if (!pty || !pty.alive) {
|
||||
return null
|
||||
}
|
||||
if (pty.incarnationId !== inc) {
|
||||
// Fence: a reused ptyId with a different incarnation must NOT resolve.
|
||||
return null
|
||||
}
|
||||
// Remint a live handle at the current graph epoch.
|
||||
handleTable.set('term_reminted', { ptyId, epoch: rendererGraphEpoch })
|
||||
return 'term_reminted'
|
||||
}
|
||||
)
|
||||
|
||||
// Identity plane (durable) — answers for the original AND any reminted handle. Independent of
|
||||
// the graph epoch, exactly like the real getTerminal* accessors that read dispatch authority.
|
||||
const knownWorkerHandle = (handle: string): boolean =>
|
||||
handle === 'term_worker' || handle === 'term_reminted'
|
||||
vi.spyOn(runtime, 'getTerminalPaneKey').mockImplementation((handle) =>
|
||||
handle === 'term_coord'
|
||||
? coordinatorPaneKey
|
||||
: knownWorkerHandle(handle)
|
||||
? workerPaneKey
|
||||
: null
|
||||
)
|
||||
vi.spyOn(runtime, 'getTerminalProcessIncarnation').mockImplementation((handle) =>
|
||||
knownWorkerHandle(handle) ? PROCESS_INCARNATION : null
|
||||
)
|
||||
vi.spyOn(runtime, 'getOrchestrationDispatchAuthority').mockImplementation((handle) =>
|
||||
knownWorkerHandle(handle)
|
||||
? // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: This test fixture is deliberately shaped to exercise the private/runtime boundary.
|
||||
({
|
||||
terminalHandle: handle,
|
||||
paneKey: workerPaneKey,
|
||||
processIncarnation: PROCESS_INCARNATION,
|
||||
hostScope: { kind: 'local', hostId: 'local' }
|
||||
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
|
||||
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
|
||||
} as never)
|
||||
: null
|
||||
)
|
||||
vi.spyOn(runtime, 'validateOrchestrationAgentLauncher').mockImplementation(() => {})
|
||||
|
||||
// Volatile handle resolution — epoch-fenced. Throws 'terminal_handle_stale' once the epoch
|
||||
// moves past the epoch at which the handle was issued.
|
||||
vi.spyOn(runtime, 'showTerminal').mockImplementation(async (handle) => {
|
||||
if (!resolveHandleToLivePty(handle)) {
|
||||
throw new Error('terminal_handle_stale')
|
||||
}
|
||||
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
|
||||
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
|
||||
return { handle, worktreeId: 'repo::worktree', status: 'running' } as never
|
||||
})
|
||||
|
||||
// The reap: closing a handle kills exactly the pty it resolves to.
|
||||
vi.spyOn(runtime, 'closeTerminal').mockImplementation(async (handle) => {
|
||||
const live = resolveHandleToLivePty(handle)
|
||||
if (!live) {
|
||||
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
|
||||
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
|
||||
return { handle, tabId: null, ptyKilled: false } as never
|
||||
}
|
||||
live.pty.alive = false
|
||||
ptysById.delete(live.ptyId)
|
||||
closedPtyIds.push(live.ptyId)
|
||||
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
|
||||
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
|
||||
return { handle, tabId: `tab:${live.ptyId}`, ptyKilled: true } as never
|
||||
})
|
||||
|
||||
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
|
||||
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
|
||||
// Remaining runtime surface required to start + settle a worker (mirrors the unit harness).
|
||||
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
|
||||
vi.spyOn(runtime, 'showManagedTerminalWorkspace').mockResolvedValue({
|
||||
id: 'repo::worktree'
|
||||
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
|
||||
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
|
||||
} as never)
|
||||
vi.spyOn(runtime, 'createTerminal').mockResolvedValue({
|
||||
handle: 'term_worker',
|
||||
worktreeId: 'repo::worktree',
|
||||
title: 'worker'
|
||||
})
|
||||
vi.spyOn(runtime, 'waitForTerminal').mockResolvedValue({
|
||||
handle: 'term_worker',
|
||||
condition: 'tui-idle',
|
||||
satisfied: true,
|
||||
status: 'running',
|
||||
exitCode: null
|
||||
})
|
||||
vi.spyOn(runtime, 'getTerminalOrchestrationCliCommand').mockReturnValue('orca')
|
||||
vi.spyOn(runtime, 'sendTerminalAgentPrompt').mockResolvedValue({
|
||||
handle: 'term_worker',
|
||||
accepted: true,
|
||||
bytesWritten: 1
|
||||
})
|
||||
vi.spyOn(runtime, 'isTerminalRunningAgent').mockResolvedValue(true)
|
||||
vi.spyOn(runtime, 'getExactWorkerProviderSession').mockReturnValue(null)
|
||||
vi.spyOn(runtime, 'readTerminal').mockResolvedValue({
|
||||
handle: 'term_worker',
|
||||
status: 'running',
|
||||
tail: ['worker output line 1', 'worker output line 2'],
|
||||
truncated: false,
|
||||
nextCursor: '2'
|
||||
})
|
||||
vi.spyOn(runtime, 'notifyMessageArrived').mockImplementation(() => {})
|
||||
|
||||
activeRunId = db.createRun({
|
||||
objective: 'PRB-0219 reap leak fixture',
|
||||
coordinatorHandle: 'term_coord',
|
||||
coordinatorPaneKey
|
||||
}).id
|
||||
ctx = { runtime }
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
if (dbOpen) {
|
||||
dbOpen = false
|
||||
db.close()
|
||||
}
|
||||
vi.restoreAllMocks()
|
||||
})
|
||||
|
||||
/** Look up a registered orchestration RPC method by name. */
|
||||
function findMethod(name: string) {
|
||||
const method = eraseRpcMethods(ORCHESTRATION_METHODS).find((m) => m.name === name)
|
||||
if (!method) {
|
||||
throw new Error(`Method not found: ${name}`)
|
||||
}
|
||||
return method
|
||||
}
|
||||
|
||||
/** Parse a method's params and invoke its handler against the shared ctx. */
|
||||
async function call(name: string, params: Record<string, unknown>) {
|
||||
const method = findMethod(name)
|
||||
const parsed = method.params ? method.params.parse(params) : undefined
|
||||
return method.handler(parsed, ctx)
|
||||
}
|
||||
|
||||
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
|
||||
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
|
||||
/** Start a worker and settle its report, the state a release acts on. */
|
||||
async function startSettledWorker(): Promise<{ taskId: string; dispatchId: string }> {
|
||||
const task = db.createTask({ spec: 'reap-leak fixture task', runId: activeRunId })
|
||||
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
|
||||
const result = (await call('orchestration.workerStart', {
|
||||
task: task.id,
|
||||
from: 'term_coord',
|
||||
agent: 'codex'
|
||||
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
|
||||
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
|
||||
})) as { dispatchId: string; state: string }
|
||||
expect(result.state).toBe('ready')
|
||||
const settlement = db.settleWorkerReport({
|
||||
taskId: task.id,
|
||||
dispatchId: result.dispatchId,
|
||||
outcome: 'succeeded',
|
||||
result: 'worker succeeded'
|
||||
})
|
||||
expect(settlement.action).toBe('settled')
|
||||
return { taskId: task.id, dispatchId: result.dispatchId }
|
||||
}
|
||||
|
||||
/** The terminalState workerList projects for a dispatch, or null. */
|
||||
async function workerTerminalState(dispatchId: string): Promise<string | null> {
|
||||
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
|
||||
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
|
||||
const listed = (await call('orchestration.workerList', { run: activeRunId })) as {
|
||||
workers: { dispatchId: string; terminalState: string | null }[]
|
||||
}
|
||||
return listed.workers.find((w) => w.dispatchId === dispatchId)?.terminalState ?? null
|
||||
}
|
||||
|
||||
it('REAP (fixed): a rendererGraphEpoch bump strands the durable handle, but the incarnation fallback remints a live handle and reaps the process', async () => {
|
||||
setup()
|
||||
const { dispatchId } = await startSettledWorker()
|
||||
const resource = db.getWorkerTerminalResourceByOwner(dispatchId)
|
||||
expect(resource?.process_incarnation).toBe(PROCESS_INCARNATION)
|
||||
|
||||
// Relay reconnect / renderer remount bumps the graph epoch: the durable handle goes stale
|
||||
// while the PTY stays alive.
|
||||
rendererGraphEpoch = 1
|
||||
await expect(runtime.showTerminal('term_worker')).rejects.toThrow('terminal_handle_stale')
|
||||
expect(ptysById.get(PTY_ID)?.alive).toBe(true)
|
||||
|
||||
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
|
||||
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
|
||||
const receipt = (await call('orchestration.workerRelease', { dispatch: dispatchId })) as {
|
||||
state: string
|
||||
processAction: string
|
||||
}
|
||||
|
||||
// The fix: inspectWorkerTerminal re-resolved the live PTY by process incarnation, reminted a
|
||||
// handle, and completion closed THAT handle — the process is actually reaped.
|
||||
expect(receipt.state).toBe('released')
|
||||
expect(receipt.processAction).toBe('closed_agent_terminal')
|
||||
expect(runtime.closeTerminal).toHaveBeenCalledWith('term_reminted')
|
||||
expect(closedPtyIds).toEqual([PTY_ID])
|
||||
expect(ptysById.has(PTY_ID)).toBe(false)
|
||||
expect(await workerTerminalState(dispatchId)).toBe('released')
|
||||
})
|
||||
|
||||
it('CONTROL: with the graph epoch intact the same release reaps exactly that PTY', async () => {
|
||||
setup()
|
||||
const { dispatchId } = await startSettledWorker()
|
||||
|
||||
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
|
||||
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
|
||||
const receipt = (await call('orchestration.workerRelease', { dispatch: dispatchId })) as {
|
||||
state: string
|
||||
processAction: string
|
||||
}
|
||||
|
||||
expect(receipt.state).toBe('released')
|
||||
expect(receipt.processAction).toBe('closed_agent_terminal')
|
||||
expect(closedPtyIds).toEqual([PTY_ID])
|
||||
expect(ptysById.has(PTY_ID)).toBe(false)
|
||||
expect(await workerTerminalState(dispatchId)).toBe('released')
|
||||
})
|
||||
|
||||
it('FENCE (fixed): a reused ptyId carrying a different incarnation must NOT remint or close — stays release_unknown', async () => {
|
||||
setup()
|
||||
const { dispatchId } = await startSettledWorker()
|
||||
|
||||
// The graph epoch bumps AND the ptyId has been reused by a different process (incarnation 2),
|
||||
// while the worker's recorded incarnation is still 1. The exact-incarnation fence must refuse.
|
||||
rendererGraphEpoch = 1
|
||||
const reused = ptysById.get(PTY_ID)
|
||||
if (reused) {
|
||||
reused.incarnationId = 2
|
||||
}
|
||||
|
||||
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
|
||||
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
|
||||
const receipt = (await call('orchestration.workerRelease', { dispatch: dispatchId })) as {
|
||||
state: string
|
||||
processAction: string
|
||||
}
|
||||
|
||||
expect(receipt.state).toBe('release_unknown')
|
||||
expect(receipt.processAction).toBe('none')
|
||||
expect(runtime.closeTerminal).not.toHaveBeenCalled()
|
||||
// The other lane's live process is left untouched (never reaped by an over-broad match).
|
||||
expect(ptysById.get(PTY_ID)?.alive).toBe(true)
|
||||
expect(await workerTerminalState(dispatchId)).toBe('release_unknown')
|
||||
})
|
||||
})
|
||||
@@ -89,11 +89,6 @@ describe('SshConnectionStore', () => {
|
||||
sshConfigHostsToTargetsMock.mockReset()
|
||||
})
|
||||
|
||||
it('listTargets delegates to store', () => {
|
||||
sshStore.listTargets()
|
||||
expect(mockStore.getSshTargets).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('lists picker suppression aliases without consulting re-adoption tombstones', () => {
|
||||
mockStore.addDeletedSshConfigAlias('config-removed')
|
||||
mockStore.addRemovedSshTargetTombstone({
|
||||
@@ -166,26 +161,6 @@ describe('SshConnectionStore', () => {
|
||||
expect(sshStore.listTargets()).toEqual([userTarget])
|
||||
})
|
||||
|
||||
it('updateTarget delegates to store', () => {
|
||||
const original: SshTarget = {
|
||||
id: 'ssh-1',
|
||||
label: 'Old Name',
|
||||
host: 'example.com',
|
||||
port: 22,
|
||||
username: 'user'
|
||||
}
|
||||
mockStore.addSshTarget(original)
|
||||
|
||||
const result = sshStore.updateTarget('ssh-1', { label: 'New Name' })
|
||||
expect(result).toBeTruthy()
|
||||
expect(mockStore.updateSshTarget).toHaveBeenCalledWith('ssh-1', { label: 'New Name' })
|
||||
})
|
||||
|
||||
it('removeTarget delegates to store', () => {
|
||||
sshStore.removeTarget('ssh-1')
|
||||
expect(mockStore.removeSshTarget).toHaveBeenCalledWith('ssh-1')
|
||||
})
|
||||
|
||||
describe('importFromSshConfig', () => {
|
||||
function candidate(overrides: Partial<SshTarget> & { configHost: string }): SshTarget {
|
||||
return {
|
||||
|
||||
@@ -84,19 +84,6 @@ describe('remote ripgrep cache GC', () => {
|
||||
expect(removedTrees()).toEqual([])
|
||||
})
|
||||
|
||||
// Why this case exists: the relay directory is named from a hash of the relay bytes, and
|
||||
// ripgrep is not among them, so a release that bumps only the ripgrep package -- the monthly
|
||||
// Dependabot PR -- shares a relay directory with its predecessor while minting a new entry.
|
||||
// With a single marker slot the second client overwrote the first's reference and this pass
|
||||
// then collected the binary the first client's relay was still running against.
|
||||
it('keeps both builds when two clients share one relay directory', async () => {
|
||||
reply([CURRENT, SUPERSEDED], [CURRENT, SUPERSEDED])
|
||||
|
||||
await gcRemoteRipgrepCache(conn, LINUX, '/home/me', { pinnedEntry: CURRENT })
|
||||
|
||||
expect(removedTrees()).toEqual([])
|
||||
})
|
||||
|
||||
// Why assert the shell and not just the parse: the marker is per entry, so a scan that read a
|
||||
// single fixed filename would silently see only one of the two references above.
|
||||
it('scans every marker in a relay directory, not one fixed name', async () => {
|
||||
|
||||
Reference in New Issue
Block a user