From 810080fe868bfc54a843ead2467a0dfd2e08f494 Mon Sep 17 00:00:00 2001 From: OrcaWin Date: Sat, 7 Nov 2026 16:13:17 -0800 Subject: [PATCH] test: remove 468 low-value tests (#39614) Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> --- config/flaky-test-pattern-baseline.txt | 12 +- config/reliability-gates.jsonc | 423 ++++---------- .../scripts/ci/sharding/ci-shard-timings.json | 30 - .../bridge-host-verdict-hold.test.ts | 62 +- .../bridge/bridge-execution-host.test.ts | 75 --- .../bridge/page-device-store.test.ts | 78 --- .../rpc-client-stream-registry.test.ts | 280 --------- .../stable-logical-rpc-client.test.ts | 328 +---------- .../claude-agent-teams-pty-exit-leak.spec.ts | 80 --- ...on-tabs-empty-worktree-publication.spec.ts | 74 --- .../runtime/terminal-agent-phone-push.test.ts | 173 +----- .../terminal-agent-prompt-answer.test.ts | 57 -- .../terminal-chat-owner-persistence.test.ts | 52 -- .../terminal-intentional-stop-exit.test.ts | 76 +-- ...rminal-interactive-wait-visibility.test.ts | 427 +------------- ...rminal-pane-recovery-liveness-gate.test.ts | 86 --- .../runtime/terminal-query-responder.test.ts | 351 ----------- .../terminal-restore-record-seed.test.ts | 465 +-------------- ...nal-retirement-proof-emitted-frame.test.ts | 92 --- .../runtime/terminal-run-facts-input.test.ts | 53 -- .../terminal-send-stale-leaf-liveness.test.ts | 181 ------ ...al-subscriber-driven-daemon-attach.test.ts | 134 ----- .../terminal-update-admission-census.test.ts | 32 - ...inal-orphan-adoption-duplicate-pty.test.ts | 105 ---- ...e-terminal-orphan-session-adoption.test.ts | 73 --- ...erminal-orphan-topology-validation.test.ts | 136 ----- .../adoption/terminal-orphan-topology.test.ts | 107 ---- .../identity/main-pane-record-arrival.test.ts | 45 -- .../workspace-session-worktree-id.test.ts | 33 -- .../launch/mobile-terminal-chat-fork.test.ts | 117 +--- ...e-created-worktree-terminal-target.test.ts | 183 ------ .../launch/terminal-image-viewer-env.test.ts | 67 --- .../launch/terminal-pty-exit-waiter.spec.ts | 68 --- .../terminal-split-layout-owner.test.ts | 162 ------ .../window-tab-start-switch-off.test.ts | 19 - .../layout/agent-generated-tab-titles.test.ts | 104 ---- .../headless-terminal-split-layout.test.ts | 76 --- .../lifecycle/main-pane-liveness.test.ts | 102 ---- .../tail/terminal-tail-redraw-scaling.test.ts | 34 -- ...ntime-terminal-inventory-contracts.test.ts | 2 - .../terminal-identity-read-context.test.ts | 173 +----- ...terminal-list-execution-host-scope.spec.ts | 92 +-- .../runtime-terminal-exit-contracts.test.ts | 2 - ...-session-terminal-retirement-proof.test.ts | 45 -- ...mobile-session-terminal-retirement.test.ts | 196 ------- .../pty-pane-copies-untouched-main.test.ts | 75 --- .../surface/terminal-surface-close.test.ts | 64 -- .../terminal-surface-replies-main.test.ts | 92 +-- ...untime-terminal-saved-name-respawn.test.ts | 122 ---- .../active-worktree-context-parity.test.ts | 21 - .../wait/launched-agent-write-guard.test.ts | 30 - .../wait/tui-idle-claude-task-wakeup.test.ts | 116 ---- .../wait/tui-idle-codex-plan-menu.test.ts | 20 - ...le-secret-store-preservation.win32.test.ts | 26 - .../runtime/wait-blocked-check-state.test.ts | 71 --- .../worker-terminal-archive-release.test.ts | 80 --- .../worker-terminal-archive-screen.test.ts | 93 +-- .../worker-terminal-wrap-provenance.test.ts | 61 -- .../workspace-layout-operation-frames.test.ts | 117 +--- .../runtime/workspace-layout-stream.test.ts | 236 +------- .../workspace-notification-mute.test.ts | 242 -------- ...aired-client-discovery.integration.test.ts | 55 -- ...ntime-worktree-change-invalidation.test.ts | 133 ----- .../repo-worktree-row-resolution.test.ts | 279 +-------- .../runtime-managed-worktree-queries.test.ts | 357 +----------- .../runtime-resolved-worktree-cache.test.ts | 69 --- .../catalog/runtime-visible-catalog.test.ts | 78 --- .../runtime-worktree-selection.test.ts | 59 -- .../catalog/workspace-query-prototype.test.ts | 548 ------------------ .../catalog/worktree-list-host-scope.test.ts | 20 - ...rktree-scan-admin-fingerprint-gate.test.ts | 389 +------------ .../worktree-scan-repeated-path.test.ts | 35 -- .../managed-worktree-create-registry.test.ts | 14 - .../managed-worktree-create-telemetry.test.ts | 56 +- .../orca-runtime-create-base-prefetch.test.ts | 98 ---- ...worktree-provisioning-availability.test.ts | 105 +--- ...-local-worktree-create-pr-conflict.test.ts | 162 +----- ...local-worktree-create-pr-conflicts.test.ts | 427 -------------- .../runtime-local-worktree-create.test.ts | 82 --- ...runtime-local-worktree-setup-trust.test.ts | 17 - .../runtime-provisioned-root-adoption.test.ts | 7 - .../runtime-worktree-create-git.test.ts | 26 - .../runtime-worktree-create-repo-host.test.ts | 27 +- .../workspace-lifecycle-queue.test.ts | 111 ---- ...untime-worktree-lineage-controller.test.ts | 321 +--------- ...untime-worktree-lineage-resolution.test.ts | 34 -- ...managed-worktree-metadata-wsl-move.test.ts | 7 - .../sweep-terminal-placement.test.ts | 12 - .../runtime-worktree-ps-summaries.test.ts | 269 --------- ...worktree-ps-unverifiable-terminals.test.ts | 24 - ...ree-structured-agent-rows-liveness.test.ts | 27 +- .../worktree-ps-degraded-repo-scan.test.ts | 132 ----- .../worktree-ps-host-scope.test.ts | 9 - ...g-worktree-terminal-reconciliation.test.ts | 93 --- ...time-failed-local-worktree-removal.test.ts | 38 -- ...-folder-workspace-delete-lifecycle.test.ts | 28 - ...interrupted-local-worktree-removal.test.ts | 26 - ...ructured-session-worktree-teardown.test.ts | 311 +--------- 98 files changed, 166 insertions(+), 11347 deletions(-) delete mode 100644 mobile/src/mobile-web-shell/bridge/bridge-execution-host.test.ts delete mode 100644 mobile/src/mobile-web-shell/bridge/page-device-store.test.ts delete mode 100644 src/main/runtime/orca-runtime-tests/claude-agent-teams-pty-exit-leak.spec.ts delete mode 100644 src/main/runtime/orca-runtime-tests/session-tabs-empty-worktree-publication.spec.ts delete mode 100644 src/main/runtime/terminal-chat-owner-persistence.test.ts delete mode 100644 src/main/runtime/terminal-retirement-proof-emitted-frame.test.ts delete mode 100644 src/main/runtime/terminal/adoption/runtime-terminal-orphan-adoption-duplicate-pty.test.ts delete mode 100644 src/main/runtime/terminal/adoption/runtime-terminal-orphan-session-adoption.test.ts delete mode 100644 src/main/runtime/terminal/adoption/runtime-terminal-orphan-topology-validation.test.ts delete mode 100644 src/main/runtime/terminal/adoption/terminal-orphan-topology.test.ts delete mode 100644 src/main/runtime/terminal/identity/workspace-session-worktree-id.test.ts delete mode 100644 src/main/runtime/terminal/launch/runtime-created-worktree-terminal-target.test.ts delete mode 100644 src/main/runtime/terminal/launch/terminal-image-viewer-env.test.ts delete mode 100644 src/main/runtime/terminal/launch/window-tab-start-switch-off.test.ts delete mode 100644 src/main/runtime/terminal/layout/headless-terminal-split-layout.test.ts delete mode 100644 src/main/runtime/terminal/output/tail/terminal-tail-redraw-scaling.test.ts delete mode 100644 src/main/runtime/terminal/surface/mobile-session-terminal-retirement-proof.test.ts delete mode 100644 src/main/runtime/terminal/surface/mobile-session-terminal-retirement.test.ts delete mode 100644 src/main/runtime/terminal/surface/pty-pane-copies-untouched-main.test.ts delete mode 100644 src/main/runtime/terminal/surface/terminal-surface-close.test.ts delete mode 100644 src/main/runtime/terminal/title/orca-runtime-terminal-saved-name-respawn.test.ts delete mode 100644 src/main/runtime/terminal/wait/tui-idle-claude-task-wakeup.test.ts delete mode 100644 src/main/runtime/wait-blocked-check-state.test.ts delete mode 100644 src/main/runtime/workspace-notification-mute.test.ts delete mode 100644 src/main/runtime/workspaces/catalog/orca-runtime-worktree-change-invalidation.test.ts delete mode 100644 src/main/runtime/workspaces/catalog/runtime-resolved-worktree-cache.test.ts delete mode 100644 src/main/runtime/workspaces/catalog/runtime-visible-catalog.test.ts delete mode 100644 src/main/runtime/workspaces/catalog/runtime-worktree-selection.test.ts delete mode 100644 src/main/runtime/workspaces/catalog/workspace-query-prototype.test.ts delete mode 100644 src/main/runtime/workspaces/creation/orca-runtime-create-base-prefetch.test.ts delete mode 100644 src/main/runtime/workspaces/creation/runtime-local-worktree-create-pr-conflicts.test.ts delete mode 100644 src/main/runtime/workspaces/lineage/runtime-worktree-lineage-resolution.test.ts delete mode 100644 src/main/runtime/workspaces/process-listing/runtime-worktree-ps-summaries.test.ts delete mode 100644 src/main/runtime/workspaces/process-listing/runtime-worktree-ps-unverifiable-terminals.test.ts delete mode 100644 src/main/runtime/workspaces/removal/runtime-folder-workspace-delete-lifecycle.test.ts diff --git a/config/flaky-test-pattern-baseline.txt b/config/flaky-test-pattern-baseline.txt index 988dd0a9c53..0ae6279291b 100644 --- a/config/flaky-test-pattern-baseline.txt +++ b/config/flaky-test-pattern-baseline.txt @@ -268,7 +268,6 @@ 1 test-no-real-time-wait:src/main/runtime/terminal/wait/launched-agent-crash-guard.live-shell.test.ts 1 test-no-real-time-wait:src/main/runtime/terminal/wait/launched-agent-shell-handoff-readiness.test.ts 2 test-no-real-time-wait:src/main/runtime/terminal/wait/tui-idle-name-only-real-pty.integration.test.ts -1 test-no-real-time-wait:src/main/runtime/workspaces/removal/structured-session-worktree-teardown.test.ts 4 test-no-real-time-wait:src/main/runtime/workspaces/removal/worktree-teardown-unstopped-pty.test.ts 3 test-no-real-time-wait:src/main/skills/skill-bundle-process-termination.integration.test.ts 1 test-no-real-time-wait:src/main/skills/skill-cloud-direct-upload.test.ts @@ -654,16 +653,15 @@ 1 test-no-wall-clock:src/main/runtime/structured-child-records-switch.test.ts 1 test-no-wall-clock:src/main/runtime/structured-codex-child-work-runtime.test.ts 1 test-no-wall-clock:src/main/runtime/terminal-pane-recovery-liveness-gate.test.ts -46 test-no-wall-clock:src/main/runtime/terminal-query-responder.test.ts +24 test-no-wall-clock:src/main/runtime/terminal-query-responder.test.ts 1 test-no-wall-clock:src/main/runtime/terminal-subscriber-driven-daemon-attach.test.ts -2 test-no-wall-clock:src/main/runtime/terminal/wait/tui-idle-claude-task-wakeup.test.ts 16 test-no-wall-clock:src/main/runtime/terminal/wait/tui-idle-hook-lane-runtime.test.ts 4 test-no-wall-clock:src/main/runtime/terminal/wait/tui-idle-name-only-real-pty.integration.test.ts -4 test-no-wall-clock:src/main/runtime/worker-terminal-archive-release.test.ts -9 test-no-wall-clock:src/main/runtime/worker-terminal-archive-screen.test.ts -7 test-no-wall-clock:src/main/runtime/worker-terminal-wrap-provenance.test.ts +3 test-no-wall-clock:src/main/runtime/worker-terminal-archive-release.test.ts +4 test-no-wall-clock:src/main/runtime/worker-terminal-archive-screen.test.ts +5 test-no-wall-clock:src/main/runtime/worker-terminal-wrap-provenance.test.ts 2 test-no-wall-clock:src/main/runtime/workspace-layout-live-titles.test.ts -3 test-no-wall-clock:src/main/runtime/workspaces/process-listing/runtime-worktree-structured-agent-rows-liveness.test.ts +1 test-no-wall-clock:src/main/runtime/workspaces/process-listing/runtime-worktree-structured-agent-rows-liveness.test.ts 6 test-no-wall-clock:src/main/skills/skill-bundle-process-termination.integration.test.ts 7 test-no-wall-clock:src/main/skills/skill-install-lock.test.ts 1 test-no-wall-clock:src/main/skills/skill-install-recovery.test.ts diff --git a/config/reliability-gates.jsonc b/config/reliability-gates.jsonc index 659d1cf1b05..808caab8b1f 100644 --- a/config/reliability-gates.jsonc +++ b/config/reliability-gates.jsonc @@ -454,10 +454,10 @@ "providers": ["local", "daemon", "ssh", "wsl", "remote-runtime", "mobile-relay"], "coveredPlatforms": ["macos"], "coveredProviders": [], - "coverageNotes": "Audit1991: Remove partial-runtime terminal-send guard/status/floor/query projection and argument-inventory samples. Retained RPC viewport admission, authenticated-client spoof refusal, text admission and stale-binding fences remain bounded mock-boundary behavior. The unchanged wire-codec receipt remains valid only for its original command; it does not validate this reduced RPC selector. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, physical provider/PTY, rendered, live remote/mobile or cross-platform result is claimed. Historical prior account (preserved verbatim): Audit1908 removes the complete prompt-presentation hook and literal interactive-protocol mapping suites and narrows the interactive card and prompt-card input suites. Lost alarms include replacement approval A settling after answered B; collapse/expand composer ownership and partial-answer presentation; identical new waits after an unmounted view; collapsed transcript re-reads; answered/dismissed status-backed persistence and retirement; and literal answer-to-PTY byte arrays. The retained stale-PTY acknowledgment, cancellation/answer retry, and answered-approval remount/rebind assertions remain meaningful but are not equivalent replacement coverage. No fresh reduced-selector result, physical provider, rendered/native-focus, mixed-version deployment, soak, or measured-budget validation is claimed. The unchanged wire-codec receipt remains valid only for its exact command. Historical prior account (preserved verbatim): Audit1640 removes low-value IPC, SSH, workspace and transcript-watcher tests without equivalent replacement coverage. Per-source reviews record retained public refusal, cancellation, execution-host and real-filesystem outcomes with injected/native/remote limits. Affected historical receipts are retired; gates without active current evidence are downgraded. Historical scope before this audit: Audit1471 removes the entire interactive-send hook suite. Its mock-writer routing, cancellation-handle, option-digit, interrupt, supplied settlement and inference-argument alarms are retired. No actual PTY bytes, replacement question lifetime or agent delivery were exercised. No equivalent replacement or fresh execution of the reduced reliability selector is claimed; historical receipts retain their original scope. Audit1468 removes the entire runtime-send suite. Its mock PTY clear/body/Enter byte-order, nominal timing, queue cancellation/serialization, attachment escaping, key-group pacing and deferred acceptance callback alarms are intentionally retired. No actual receiving agent or physical terminal delivery was exercised. No equivalent replacement or fresh execution of the reduced reliability selector is claimed; historical receipts retain their original scope. Retained exact unchanged-source receipts remain the only current execution evidence. Reduced selectors have no fresh execution result. Audit1417 intentionally reduces src/renderer/src/lib/agent-paste-draft-submit-retry.test.ts: Loses configured retry gap and single-Enter agent selection alarms alongside forbidden timer-count checks. No equivalent replacement coverage or fresh result is claimed. Historical coverage account: Audit1383 changes referenced sources; no new execution result validates reduced selectors. Retained unchanged receipts apply only to unchanged commands. Historical coverage: Audits1340 and1348 changes referenced sources. Retained unchanged-source receipts apply only to their unchanged commands; the reduced selectors have no new execution result. Historical coverage account (not a current claim for changed sources): Audit1316 changes referenced sources. Retained unchanged-source receipts apply only to their unchanged commands; the reduced selectors have no new execution result. Historical coverage account (not a current claim for changed sources): Production mobile controller, parser, presentation, overlay, view, question, permission and composer run with native surfaces mocked on macOS. Desktop selector groups traverse the real observed-write queue, verified helper, IPC chunker, SSH provider and transport callbacks; paired sends traverse the real dispatcher and runtime writer into that provider. Local/daemon contracts and released-version terminal codecs are covered. Provider labels describe contracts, not physical deployments; physical WSL/Linux/Windows remain gaps. Audit885 removes the isolated observed-send mock-call suite; no replacement-equivalence claim is made. Audit1107 removes the small healthy ordinary SSH-send byte/callback sample. Its exact clear/body/Enter sample alarm is lost; retained slow-answer, cancellation and simulated older-host sequence checks are not claimed as equivalent replacement coverage. The SSH delivery fixture uses controlled fake provider settlement, and its paired path uses the real runtime/dispatcher without a physical SSH or paired wire transport. Audit1278 removes the mobile Stop display/disabled/placeholder suite; its local Stop presentation, repeat-stop and dispatch-visibility alarms are intentionally lost. No equivalent replacement or runtime stopping proof is claimed. Audit1292: The blind OpenCode readiness warning and late launch success/failure content-plus-submit write samples are removed. Their mocked readiness/inspection/delivery alarms are lost; no real PTY delivery or equivalent replacement coverage is claimed. Audit1294 removes mobile/src/session/native-chat/input/mobile-native-chat-permission-send.test.ts. Its mock-only request/capability/private-state/copy alarms are intentionally lost. Remaining tests are not equivalent replacement coverage. Narrowed commands require fresh parent-owned validation; no new runtime, platform or physical mobile evidence is claimed. Audit1302: src/main/ipc/pty/ipc/write-input-acceptance.test.ts: Provider registry and write acceptance/refusal/unverifiable receipts are supplied by fixture; local/daemon/WSL/SSH labels use the same fake provider. Chunking/settlement/remount cases inspect delegated calls or canned receipts without actual provider acceptance/acknowledgement. Lose chunk/refusal/pending/remount and raw-pane routing alarms; no equivalent replacement. Reduced selectors require fresh validation; prior full-suite receipts do not validate removed or changed coverage. No equivalent replacement or physical-provider claim. Audit1303 intentionally removes supplied-state/mock-callback component and hook examples in mobile/src/session/native-chat/input/use-mobile-native-chat-ask-dismiss.test.ts, mobile/src/session/native-chat/input/MobileNativeChatPromptCard.test.tsx. Their exact retired alarms are not equivalent to retained coverage; no new runtime, physical phone or deployed mixed-version evidence is claimed. Audit1310 removes the supplied-geometry/direct-callback chat history-paging suite. Its prepend/scroll-position examples are intentionally lost; no real rendered anchoring, equivalent replacement or physical phone proof is claimed. Narrowed commands need fresh validation. Audit1315 removes/narrows mobile/src/session/native-chat/MobileNativeChatView.composer-lock.test.ts, mobile/src/session/native-chat/MobileNativeChatView.prompt-presentation.test.ts. Exact retired scenarios are intentionally lost; surviving assertions are limited to unchanged retained authored cases. No equivalent replacement, physical phone or deployed mixed-host claim. Fresh validation is required for narrowed commands. Audit1316: src/renderer/src/lib/agent-followup-delivery.test.ts removed. Interpreter wrapper/live child/shell readiness and final readiness recheck delivery mock samples lose alarms. No equivalent replacement coverage is claimed. Audit1322 removes/narrows mobile/src/session/native-chat/MobileNativeChatView.tail-follow.test.ts. Exact retired scenarios are intentionally lost; surviving assertions are limited to unchanged retained authored cases. No equivalent replacement, physical phone or deployed mixed-host claim. Fresh validation is required for narrowed commands. Audit1341 scope1334 removes/narrows mobile/src/session/native-chat/mobile-native-chat-send.test.ts, mobile/src/session/native-chat/transcript/use-mobile-native-chat-message-send.test.ts. Exact retired scenarios are intentionally lost; surviving assertions are limited to unchanged retained authored cases. No equivalent replacement, physical phone or deployed mixed-host claim. Fresh validation is required for narrowed commands. Audit1341 scope1335 removes/narrows mobile/src/session/native-chat/use-mobile-native-chat-controller.test.ts, mobile/src/session/mobile-prompt-controller.test.ts, mobile/src/session/native-chat/input/use-mobile-native-chat-answer-send.test.ts. Exact retired scenarios are intentionally lost; surviving assertions are limited to unchanged retained authored cases. No equivalent replacement, physical phone or deployed mixed-host claim. Fresh validation is required for narrowed commands. Audit1340: src/main/daemon/daemon-pty-write-settlement-recovery.test.ts removed. Ambiguous handoff settlement remains unverifiable when listener throws. No equivalent replacement coverage is claimed. Audit1348: src/main/ipc/pty/ipc/write-input-chunk-yield.test.ts removed. Check-phase yielding and no-yield single-chunk scheduling work. No equivalent replacement coverage is claimed. Audit1352: focused Linux unit selections validated on source head b638e07d395b76af3ba6b03761a1a0fe7e5e026e. Historical statements above retain their original scope. Intentional test losses are recorded in knownGaps; these runs claim no physical-provider, p95, rendered E2E or cross-platform validation. Audit1360: focused Linux unit selections validated on source head 415c9cffc2d10024131ec4b6721767a22195de6d. Historical statements above retain their original scope. Intentional test losses are recorded in knownGaps; these runs claim no physical-provider, p95, rendered E2E or cross-platform validation. Audit1383: src/main/ipc/pty/ipc/write-input-user-input.test.ts reduced. Lose admission provenance spy, direct local Escape write outcome matrix and separate client-side SSH no-evidence control. Provider writes mocked; retained tests protect observation order/facts only. No equivalent replacement coverage is claimed. Audit1401: src/main/ipc/pty/ipc/write-input-user-input.test.ts reduced. Loses local-only post-accept observation and refused/unverifiable settlement delivery alarms. No equivalent replacement coverage is claimed. Existing exact unaffected receipts remain unchanged and apply only to their unchanged selectors. Changed optional selectors have no new execution result. Audit1402: src/renderer/src/components/native-chat/native-chat-runtime-send-launch-draft.test.ts removed. Intentionally loses clear-confirmation/body/submit and maximal-clear ordering, queued text/image send ordering, ordinary cursor-spanning draft replacement and oversized refusal/queue settlement alarms. No equivalent replacement or real terminal delivery claim. No equivalent replacement coverage is claimed. Existing exact unaffected receipts remain unchanged and apply only to their unchanged selectors. Changed optional selectors have no new execution result. Audit1426 removes NativeChatComposer.test.tsx. Local pane-remount, draft/cancel/confirmation sequencing and manual IME callback alarms are lost. No equivalent replacement coverage or provider delivery is claimed; historical receipts retain their original scope and the reduced selector has no new execution result.", + "coverageNotes": "Audit2003: Remove agent-prompt absence routing, throwing/live/missing-probe and cached-probe-count examples from the stale-leaf source. Controller-proven absence, unknown-liveness admission, obsolete-absence clearing and same-ID respawn fences remain at a supplied provider/row seam. Preserve the independent wire-codec receipt only in its original scope. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, physical provider/PTY, rendered, live remote/mobile or cross-platform result is claimed. Historical prior account (preserved verbatim): Audit1991: Remove partial-runtime terminal-send guard/status/floor/query projection and argument-inventory samples. Retained RPC viewport admission, authenticated-client spoof refusal, text admission and stale-binding fences remain bounded mock-boundary behavior. The unchanged wire-codec receipt remains valid only for its original command; it does not validate this reduced RPC selector. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, physical provider/PTY, rendered, live remote/mobile or cross-platform result is claimed. Historical prior account (preserved verbatim): Audit1908 removes the complete prompt-presentation hook and literal interactive-protocol mapping suites and narrows the interactive card and prompt-card input suites. Lost alarms include replacement approval A settling after answered B; collapse/expand composer ownership and partial-answer presentation; identical new waits after an unmounted view; collapsed transcript re-reads; answered/dismissed status-backed persistence and retirement; and literal answer-to-PTY byte arrays. The retained stale-PTY acknowledgment, cancellation/answer retry, and answered-approval remount/rebind assertions remain meaningful but are not equivalent replacement coverage. No fresh reduced-selector result, physical provider, rendered/native-focus, mixed-version deployment, soak, or measured-budget validation is claimed. The unchanged wire-codec receipt remains valid only for its exact command. Historical prior account (preserved verbatim): Audit1640 removes low-value IPC, SSH, workspace and transcript-watcher tests without equivalent replacement coverage. Per-source reviews record retained public refusal, cancellation, execution-host and real-filesystem outcomes with injected/native/remote limits. Affected historical receipts are retired; gates without active current evidence are downgraded. Historical scope before this audit: Audit1471 removes the entire interactive-send hook suite. Its mock-writer routing, cancellation-handle, option-digit, interrupt, supplied settlement and inference-argument alarms are retired. No actual PTY bytes, replacement question lifetime or agent delivery were exercised. No equivalent replacement or fresh execution of the reduced reliability selector is claimed; historical receipts retain their original scope. Audit1468 removes the entire runtime-send suite. Its mock PTY clear/body/Enter byte-order, nominal timing, queue cancellation/serialization, attachment escaping, key-group pacing and deferred acceptance callback alarms are intentionally retired. No actual receiving agent or physical terminal delivery was exercised. No equivalent replacement or fresh execution of the reduced reliability selector is claimed; historical receipts retain their original scope. Retained exact unchanged-source receipts remain the only current execution evidence. Reduced selectors have no fresh execution result. Audit1417 intentionally reduces src/renderer/src/lib/agent-paste-draft-submit-retry.test.ts: Loses configured retry gap and single-Enter agent selection alarms alongside forbidden timer-count checks. No equivalent replacement coverage or fresh result is claimed. Historical coverage account: Audit1383 changes referenced sources; no new execution result validates reduced selectors. Retained unchanged receipts apply only to unchanged commands. Historical coverage: Audits1340 and1348 changes referenced sources. Retained unchanged-source receipts apply only to their unchanged commands; the reduced selectors have no new execution result. Historical coverage account (not a current claim for changed sources): Audit1316 changes referenced sources. Retained unchanged-source receipts apply only to their unchanged commands; the reduced selectors have no new execution result. Historical coverage account (not a current claim for changed sources): Production mobile controller, parser, presentation, overlay, view, question, permission and composer run with native surfaces mocked on macOS. Desktop selector groups traverse the real observed-write queue, verified helper, IPC chunker, SSH provider and transport callbacks; paired sends traverse the real dispatcher and runtime writer into that provider. Local/daemon contracts and released-version terminal codecs are covered. Provider labels describe contracts, not physical deployments; physical WSL/Linux/Windows remain gaps. Audit885 removes the isolated observed-send mock-call suite; no replacement-equivalence claim is made. Audit1107 removes the small healthy ordinary SSH-send byte/callback sample. Its exact clear/body/Enter sample alarm is lost; retained slow-answer, cancellation and simulated older-host sequence checks are not claimed as equivalent replacement coverage. The SSH delivery fixture uses controlled fake provider settlement, and its paired path uses the real runtime/dispatcher without a physical SSH or paired wire transport. Audit1278 removes the mobile Stop display/disabled/placeholder suite; its local Stop presentation, repeat-stop and dispatch-visibility alarms are intentionally lost. No equivalent replacement or runtime stopping proof is claimed. Audit1292: The blind OpenCode readiness warning and late launch success/failure content-plus-submit write samples are removed. Their mocked readiness/inspection/delivery alarms are lost; no real PTY delivery or equivalent replacement coverage is claimed. Audit1294 removes mobile/src/session/native-chat/input/mobile-native-chat-permission-send.test.ts. Its mock-only request/capability/private-state/copy alarms are intentionally lost. Remaining tests are not equivalent replacement coverage. Narrowed commands require fresh parent-owned validation; no new runtime, platform or physical mobile evidence is claimed. Audit1302: src/main/ipc/pty/ipc/write-input-acceptance.test.ts: Provider registry and write acceptance/refusal/unverifiable receipts are supplied by fixture; local/daemon/WSL/SSH labels use the same fake provider. Chunking/settlement/remount cases inspect delegated calls or canned receipts without actual provider acceptance/acknowledgement. Lose chunk/refusal/pending/remount and raw-pane routing alarms; no equivalent replacement. Reduced selectors require fresh validation; prior full-suite receipts do not validate removed or changed coverage. No equivalent replacement or physical-provider claim. Audit1303 intentionally removes supplied-state/mock-callback component and hook examples in mobile/src/session/native-chat/input/use-mobile-native-chat-ask-dismiss.test.ts, mobile/src/session/native-chat/input/MobileNativeChatPromptCard.test.tsx. Their exact retired alarms are not equivalent to retained coverage; no new runtime, physical phone or deployed mixed-version evidence is claimed. Audit1310 removes the supplied-geometry/direct-callback chat history-paging suite. Its prepend/scroll-position examples are intentionally lost; no real rendered anchoring, equivalent replacement or physical phone proof is claimed. Narrowed commands need fresh validation. Audit1315 removes/narrows mobile/src/session/native-chat/MobileNativeChatView.composer-lock.test.ts, mobile/src/session/native-chat/MobileNativeChatView.prompt-presentation.test.ts. Exact retired scenarios are intentionally lost; surviving assertions are limited to unchanged retained authored cases. No equivalent replacement, physical phone or deployed mixed-host claim. Fresh validation is required for narrowed commands. Audit1316: src/renderer/src/lib/agent-followup-delivery.test.ts removed. Interpreter wrapper/live child/shell readiness and final readiness recheck delivery mock samples lose alarms. No equivalent replacement coverage is claimed. Audit1322 removes/narrows mobile/src/session/native-chat/MobileNativeChatView.tail-follow.test.ts. Exact retired scenarios are intentionally lost; surviving assertions are limited to unchanged retained authored cases. No equivalent replacement, physical phone or deployed mixed-host claim. Fresh validation is required for narrowed commands. Audit1341 scope1334 removes/narrows mobile/src/session/native-chat/mobile-native-chat-send.test.ts, mobile/src/session/native-chat/transcript/use-mobile-native-chat-message-send.test.ts. Exact retired scenarios are intentionally lost; surviving assertions are limited to unchanged retained authored cases. No equivalent replacement, physical phone or deployed mixed-host claim. Fresh validation is required for narrowed commands. Audit1341 scope1335 removes/narrows mobile/src/session/native-chat/use-mobile-native-chat-controller.test.ts, mobile/src/session/mobile-prompt-controller.test.ts, mobile/src/session/native-chat/input/use-mobile-native-chat-answer-send.test.ts. Exact retired scenarios are intentionally lost; surviving assertions are limited to unchanged retained authored cases. No equivalent replacement, physical phone or deployed mixed-host claim. Fresh validation is required for narrowed commands. Audit1340: src/main/daemon/daemon-pty-write-settlement-recovery.test.ts removed. Ambiguous handoff settlement remains unverifiable when listener throws. No equivalent replacement coverage is claimed. Audit1348: src/main/ipc/pty/ipc/write-input-chunk-yield.test.ts removed. Check-phase yielding and no-yield single-chunk scheduling work. No equivalent replacement coverage is claimed. Audit1352: focused Linux unit selections validated on source head b638e07d395b76af3ba6b03761a1a0fe7e5e026e. Historical statements above retain their original scope. Intentional test losses are recorded in knownGaps; these runs claim no physical-provider, p95, rendered E2E or cross-platform validation. Audit1360: focused Linux unit selections validated on source head 415c9cffc2d10024131ec4b6721767a22195de6d. Historical statements above retain their original scope. Intentional test losses are recorded in knownGaps; these runs claim no physical-provider, p95, rendered E2E or cross-platform validation. Audit1383: src/main/ipc/pty/ipc/write-input-user-input.test.ts reduced. Lose admission provenance spy, direct local Escape write outcome matrix and separate client-side SSH no-evidence control. Provider writes mocked; retained tests protect observation order/facts only. No equivalent replacement coverage is claimed. Audit1401: src/main/ipc/pty/ipc/write-input-user-input.test.ts reduced. Loses local-only post-accept observation and refused/unverifiable settlement delivery alarms. No equivalent replacement coverage is claimed. Existing exact unaffected receipts remain unchanged and apply only to their unchanged selectors. Changed optional selectors have no new execution result. Audit1402: src/renderer/src/components/native-chat/native-chat-runtime-send-launch-draft.test.ts removed. Intentionally loses clear-confirmation/body/submit and maximal-clear ordering, queued text/image send ordering, ordinary cursor-spanning draft replacement and oversized refusal/queue settlement alarms. No equivalent replacement or real terminal delivery claim. No equivalent replacement coverage is claimed. Existing exact unaffected receipts remain unchanged and apply only to their unchanged selectors. Changed optional selectors have no new execution result. Audit1426 removes NativeChatComposer.test.tsx. Local pane-remount, draft/cancel/confirmation sequencing and manual IME callback alarms are lost. No equivalent replacement coverage or provider delivery is claimed; historical receipts retain their original scope and the reduced selector has no new execution result.", "motivatingLinks": ["https://github.com/stablyai/orca/pull/25761"], "invariant": "A prompt occurrence leaves the input region only when an accepted reply answers it (a current host's provider acknowledgment, or an older host's whole-write acceptance as before) or the user collapses it to a strip above the composer, which writes nothing and keeps the card mounted with its partial answer; status-backed occurrences are told apart by the host wait's start, so view toggles and remounts preserve them while real clear/replacement supersedes them, and a transcript-only one is dropped when the view stops observing it; obsolete results cannot change replacements; selector groups wait for complete writes before pacing; refusal or unknown handoff never causes replay; a card retires an unsubmitted composer send without writing under it or losing its text, and the hidden composer cannot send or interrupt; Collapse or Escape folds a card without writing, never during an in-flight answer, and a new occurrence shows expanded again; the phone blocks only Send while a card is expanded and shows one card per occurrence; cancellation stops unsent groups and active dictation can stop.", - "oracle": "Audit1991: Remove partial-runtime terminal-send guard/status/floor/query projection and argument-inventory samples. Retained RPC viewport admission, authenticated-client spoof refusal, text admission and stale-binding fences remain bounded mock-boundary behavior. The unchanged wire-codec receipt remains valid only for its original command; it does not validate this reduced RPC selector. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, physical provider/PTY, rendered, live remote/mobile or cross-platform result is claimed. Historical prior account (preserved verbatim): Audit1908 removes the complete prompt-presentation hook and literal interactive-protocol mapping suites and narrows the interactive card and prompt-card input suites. Lost alarms include replacement approval A settling after answered B; collapse/expand composer ownership and partial-answer presentation; identical new waits after an unmounted view; collapsed transcript re-reads; answered/dismissed status-backed persistence and retirement; and literal answer-to-PTY byte arrays. The retained stale-PTY acknowledgment, cancellation/answer retry, and answered-approval remount/rebind assertions remain meaningful but are not equivalent replacement coverage. No fresh reduced-selector result, physical provider, rendered/native-focus, mixed-version deployment, soak, or measured-budget validation is claimed. The unchanged wire-codec receipt remains valid only for its exact command. Historical prior account (preserved verbatim): Retained exact unchanged-source receipts remain the only current execution evidence. Reduced selectors have no fresh execution result. Historical pre-audit oracle account for changed sources: Existing exact unchanged-selector receipts remain the only current execution evidence. Changed source selectors have no fresh execution result. Historical prior-source oracle account: Existing exact unchanged-selector receipts remain the only current execution evidence. Changed source selectors have no fresh execution result. Historical prior-source oracle account: Retained unchanged-selector receipts are the only current execution evidence. Reduced-source assertions await validation. Historical prior oracle: The retained unchanged-source receipts are the only current execution evidence; reduced-source assertions await validation. Historical oracle account (not a current claim for changed sources): The retained unchanged-source receipts are the only current execution evidence; reduced-source assertions await validation. Historical oracle account (not a current claim for changed sources): After acknowledged phone replies with stale host status, the exact card disappears and Send is enabled; unknown, refused or unverifiable-settlement replies retain enabled choices, while an older host's accepted reply dismisses like an acknowledged one; a same-occurrence view toggle retains pending acknowledgment and real replacements discard it. A slow 32 KiB SSH answer completes all chunks before Enter; cancellation preserves issued bytes but omits remaining groups. Paired settlement invokes the actual provider and lost callbacks remain unknown; older hosts deliver ordinary messages, launch and selector groups once and their accepted answers dismiss as before. An answered card stays hidden across a view remount; a collapsed card is a strip that frees the composer and expands back; an identical question in a new wait shows again; a composer send caught by a new card keeps its bubble without submitting or clearing the line under it.", + "oracle": "Audit2003: Remove agent-prompt absence routing, throwing/live/missing-probe and cached-probe-count examples from the stale-leaf source. Controller-proven absence, unknown-liveness admission, obsolete-absence clearing and same-ID respawn fences remain at a supplied provider/row seam. Preserve the independent wire-codec receipt only in its original scope. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, physical provider/PTY, rendered, live remote/mobile or cross-platform result is claimed. Historical prior account (preserved verbatim): Audit1991: Remove partial-runtime terminal-send guard/status/floor/query projection and argument-inventory samples. Retained RPC viewport admission, authenticated-client spoof refusal, text admission and stale-binding fences remain bounded mock-boundary behavior. The unchanged wire-codec receipt remains valid only for its original command; it does not validate this reduced RPC selector. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, physical provider/PTY, rendered, live remote/mobile or cross-platform result is claimed. Historical prior account (preserved verbatim): Audit1908 removes the complete prompt-presentation hook and literal interactive-protocol mapping suites and narrows the interactive card and prompt-card input suites. Lost alarms include replacement approval A settling after answered B; collapse/expand composer ownership and partial-answer presentation; identical new waits after an unmounted view; collapsed transcript re-reads; answered/dismissed status-backed persistence and retirement; and literal answer-to-PTY byte arrays. The retained stale-PTY acknowledgment, cancellation/answer retry, and answered-approval remount/rebind assertions remain meaningful but are not equivalent replacement coverage. No fresh reduced-selector result, physical provider, rendered/native-focus, mixed-version deployment, soak, or measured-budget validation is claimed. The unchanged wire-codec receipt remains valid only for its exact command. Historical prior account (preserved verbatim): Retained exact unchanged-source receipts remain the only current execution evidence. Reduced selectors have no fresh execution result. Historical pre-audit oracle account for changed sources: Existing exact unchanged-selector receipts remain the only current execution evidence. Changed source selectors have no fresh execution result. Historical prior-source oracle account: Existing exact unchanged-selector receipts remain the only current execution evidence. Changed source selectors have no fresh execution result. Historical prior-source oracle account: Retained unchanged-selector receipts are the only current execution evidence. Reduced-source assertions await validation. Historical prior oracle: The retained unchanged-source receipts are the only current execution evidence; reduced-source assertions await validation. Historical oracle account (not a current claim for changed sources): The retained unchanged-source receipts are the only current execution evidence; reduced-source assertions await validation. Historical oracle account (not a current claim for changed sources): After acknowledged phone replies with stale host status, the exact card disappears and Send is enabled; unknown, refused or unverifiable-settlement replies retain enabled choices, while an older host's accepted reply dismisses like an acknowledged one; a same-occurrence view toggle retains pending acknowledgment and real replacements discard it. A slow 32 KiB SSH answer completes all chunks before Enter; cancellation preserves issued bytes but omits remaining groups. Paired settlement invokes the actual provider and lost callbacks remain unknown; older hosts deliver ordinary messages, launch and selector groups once and their accepted answers dismiss as before. An answered card stays hidden across a view remount; a collapsed card is a strip that frees the composer and expands back; an identical question in a new wait shows again; a composer send caught by a new card keeps its bubble without submitting or clearing the line under it.", "commands": [ "ORCA_BACKGROUND_LAUNCH=1 pnpm test src/renderer/src/components/native-chat/prompts/NativeChatInteractiveCard.test.tsx src/renderer/src/components/native-chat/prompts/NativeChatResolvedView.prompt-card-input.test.tsx src/renderer/src/components/native-chat/surfaces/integration/NativeChatResolvedView.prompt-card-composer.test.tsx src/renderer/src/components/native-chat/conversation/turns/NativeChatResolvedView.turn-status.test.tsx src/renderer/src/runtime/hosted-terminals/inspection/runtime-terminal-inspection.test.ts src/renderer/src/lib/agent-paste-draft.test.ts src/renderer/src/lib/agent-paste-draft-submit-retry.test.ts src/main/ipc/pty/ipc/write-input-user-input.test.ts src/main/ipc/pty/pty-write-ipc-validation.test.ts src/main/runtime/terminal/input/runtime-terminal-writer.test.ts src/main/runtime/rpc/terminal-send.test.ts src/main/runtime/terminal-send-stale-leaf-liveness.test.ts tests/e2e/native-chat-ssh-delivery.unit.test.ts --maxWorkers=2", "ORCA_BACKGROUND_LAUNCH=1 mobile/node_modules/.bin/vitest run --config mobile/vitest.config.ts mobile/src/session/native-chat/MobileNativeChatView.prompt-presentation.test.ts mobile/src/session/native-chat/use-mobile-native-chat-controller.test.ts mobile/src/session/native-chat/MobileNativeChatView.failure-banner.test.ts mobile/src/session/mobile-prompt-controller.test.ts mobile/src/session/native-chat/input/use-mobile-native-chat-cancel-ask.test.ts mobile/src/session/native-chat/input/use-mobile-native-chat-answer-send.test.ts mobile/src/session/native-chat/transcript/use-mobile-native-chat-message-send.test.ts --maxWorkers=2", @@ -629,7 +629,8 @@ "Audit1426 removes NativeChatComposer.test.tsx. Local pane-remount, draft/cancel/confirmation sequencing and manual IME callback alarms are lost. No equivalent replacement coverage or provider delivery is claimed; historical receipts retain their original scope and the reduced selector has no new execution result.", "Audit1908 removes the complete prompt-presentation hook and literal interactive-protocol mapping suites and narrows the interactive card and prompt-card input suites. Lost alarms include replacement approval A settling after answered B; collapse/expand composer ownership and partial-answer presentation; identical new waits after an unmounted view; collapsed transcript re-reads; answered/dismissed status-backed persistence and retirement; and literal answer-to-PTY byte arrays. The retained stale-PTY acknowledgment, cancellation/answer retry, and answered-approval remount/rebind assertions remain meaningful but are not equivalent replacement coverage. No fresh reduced-selector result, physical provider, rendered/native-focus, mixed-version deployment, soak, or measured-budget validation is claimed. The unchanged wire-codec receipt remains valid only for its exact command. Exact retired direct alarms: 'ignores approval A settling after answered replacement B'; 'replaces the composer and hides only once the choice was delivered'; 'keeps the card answerable when the choice was refused or its delivery is unknown'; 'collapses the approval on %s without writing, until a new wait shows it again'; 'shows a second approval with the same text once it is a new wait'; 'routes question Cancel to rejection and releases the composer slot without Stop'; 'cannot collapse a question while its answer is still being written'; 'keeps the question when its Cancel was not delivered'; 'keeps a verified send visible until delivery succeeds'; 'restores a verified send for retry when delivery is rejected'; 'stays dismissed after answering while the transcript call is still pending'; 'clears once the FIFO tool result lands'; 'retains terminal choices across an agent notice until legacy human input'; 'filters Q1 before grouping Q2 and never writes Cancel, including window movement'; 'resets an edited pending form without unmounting its answer owner'; 'underlines an existing file in the plan and opens it through the chat'; 'shows an identical question again in a new wait that arrived while the view was unmounted'; 'keeps a collapsed transcript question collapsed while the transcript re-reads'; 'keeps a partly answered question across collapse and expand, and Escape in its text field'; 'keeps one line per question with a blank middle answer (3 questions)'; 'single-select: sends the picked option NUMBER (not the label), no trailing Enter'; 'single-select free text: opens \"Type something\" then types the answer + Enter'; 'multi-select: toggles each option NUMBER, then steps to Submit and confirms'; 'multi-question: steps past an unanswered question then submits'; 'preview single-select: focuses the option number, notes free text, then commits with Enter'; \"submits the final multi-question option without Claude's extra Enter\"; 'adds free text as notes before committing the selected row'; \"targets Codex's synthetic None-of-the-above row for a custom answer\"; 'clears skipped rows and confirms the partial answer once'; 'keeps a status-backed answer hidden for a remounted view'; 'shows an identical question again once the prompt cleared'; 'forgets the dismissal when its tab retires'.", "Audit1908 historical original assertion objects; only the five identified retired assertion titles are removed from active refs. Remaining titles stay active. Complete original raw assertion objects follow verbatim:\n{\n \"file\": \"src/renderer/src/components/native-chat/prompts/NativeChatInteractiveCard.test.tsx\",\n \"assertions\": [\n \"ignores approval A settling after answered replacement B\",\n \"ignores a raw acknowledgment from a rebound PTY\",\n \"sends only one cancellation, can cancel an answer, and permits retry after unknown\"\n ]\n }\n{\n \"file\": \"src/renderer/src/components/native-chat/prompts/NativeChatResolvedView.prompt-card-input.test.tsx\",\n \"assertions\": [\n \"keeps an answered approval hidden across a chat view remount and a PTY rebind\",\n \"collapses to a strip that frees the composer, and expanding gives the card the input back\",\n \"shows an identical question again in a new wait that arrived while the view was unmounted\",\n \"keeps a collapsed transcript question collapsed while the transcript re-reads\",\n \"keeps a partly answered question across collapse and expand, and Escape in its text field\"\n ]\n }", - "Audit1991: Remove partial-runtime terminal-send guard/status/floor/query projection and argument-inventory samples. Retained RPC viewport admission, authenticated-client spoof refusal, text admission and stale-binding fences remain bounded mock-boundary behavior. The unchanged wire-codec receipt remains valid only for its original command; it does not validate this reduced RPC selector. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, physical provider/PTY, rendered, live remote/mobile or cross-platform result is claimed." + "Audit1991: Remove partial-runtime terminal-send guard/status/floor/query projection and argument-inventory samples. Retained RPC viewport admission, authenticated-client spoof refusal, text admission and stale-binding fences remain bounded mock-boundary behavior. The unchanged wire-codec receipt remains valid only for its original command; it does not validate this reduced RPC selector. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, physical provider/PTY, rendered, live remote/mobile or cross-platform result is claimed.", + "Audit2003: Remove agent-prompt absence routing, throwing/live/missing-probe and cached-probe-count examples from the stale-leaf source. Controller-proven absence, unknown-liveness admission, obsolete-absence clearing and same-ID respawn fences remain at a supplied provider/row seam. Preserve the independent wire-codec receipt only in its original scope. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, physical provider/PTY, rendered, live remote/mobile or cross-platform result is claimed." ], "promotionCriteria": [ "Record change-reverted red/green evidence and repeatable remote rendered approval/question/dictation journeys.", @@ -3344,7 +3345,7 @@ "id": "mobile-relay.endpoint-recovery", "title": "Mobile relay recovery retries offline hosts and races direct endpoints", "maturity": "experimental", - "protection": "partial", + "protection": "none", "owner": "mobile-runtime", "layer": "shared-mobile-transport-contract", "surfaces": [ @@ -3355,150 +3356,35 @@ ], "platforms": ["ios", "android", "macos", "linux", "windows"], "providers": ["lan", "tailscale", "cloud-relay"], - "coveredPlatforms": ["linux"], + "coveredPlatforms": [], "coveredProviders": [], - "coverageNotes": "Deterministic TypeScript tests cover foreground retry timers, continuous-outage escalation, persistent pairing-rejection escalation, a bounded background Relay grace, direct-winner and background cancellation, credential-gated Relay presentation, and concurrent LAN/Tailscale authentication. Physical iOS/Android radios, GFE, and production relay recovery remain live-test gaps. Audit1303 intentionally removes supplied-state/mock-callback component and hook examples in mobile/src/transport/connection-health.test.ts. Their exact retired alarms are not equivalent to retained coverage; no new runtime, physical phone or deployed mixed-version evidence is claimed. Audit1310 removes the fake-logical-session background lifecycle suite and two supervisor-nudge examples whose fake migration or negative spy supplied the oracle. Its quick-background/grace-expiry/recovery/lease-rotation examples are intentionally lost. The retained nudge callbacks still check healthy-session retention and losing physical-candidate closure; they do not replace the retired lifecycle alarms. Narrowed commands need fresh validation; no physical radio, production Relay or deployed mixed-version claim. Audit1341 scope1334 removes/narrows mobile/src/transport/mobile-relay-reconnect-controller.test.ts. Exact retired scenarios are intentionally lost; surviving assertions are limited to unchanged retained authored cases. No equivalent replacement, physical phone or deployed mixed-host claim. Fresh validation is required for narrowed commands. Audit1341 scope1335 removes/narrows mobile/src/transport/stable-logical-rpc-client.test.ts. Exact retired scenarios are intentionally lost; surviving assertions are limited to unchanged retained authored cases. No equivalent replacement, physical phone or deployed mixed-host claim. Fresh validation is required for narrowed commands. Audit1735 (refreshed audit1727 at 6da544b7) removes 19 of 38 supervisor definitions. The four retired supervisor assertionRefs covered HOST_OFFLINE self-retry, absent/expired-credential Relay presentation, stale presentation after backgrounded credential selection, and pending presentation through failed-dial cooldown. Those exact alarms are intentionally lost; retained orchestration tests are not equivalent replacement coverage. FakeLogicalClient supplies migration/authentication acceptance and old-session closure, so retained supervisor cases prove timer, async-arbitration and credential-admission coordination only. Narrowed commands require fresh validation; no physical phone, deployed Relay or mixed-version evidence is added. Audit1753 removes the isolated close-code membership and recovery-recipe inventory. Those helper alarms are intentionally lost; retained transport scenarios are not equivalent replacements. No new runtime, physical-device or deployed mixed-version evidence is claimed. Audit1753: the retained nine-file mobile transport command passed90 Node unit cases on Linux. This is one local execution with fake clocks/transports; no live-provider, native-phone, mixed-version, p95 or soak evidence. The original macOS helper receipt is archived in knownGaps; current coveredPlatforms/providers reflect only the new unit receipt.", + "coverageNotes": "Audit2003: Remove held-subscription and viewport replay, background suspension, delivery-ambiguity/error propagation, dial-phase/recovery-path projections, relay timing and authentication-failure examples from the logical client. Retain only make-before-break cutover with subscription replay, committed-response/close arbitration and late-event cancellation fencing against fake physical sessions. Retire all dated combined receipts whole; their case counts and sibling results do not validate these changed bytes. Archive all prospective selector/file/assertion and covered-scope fields with the downgraded gate; the three retained logical callbacks are described here only, without active gate authority. Downgrade active protection to none because no independent unchanged receipt survives. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, physical provider/PTY, rendered, live remote/mobile or cross-platform result is claimed. Historical prior account (preserved verbatim): Deterministic TypeScript tests cover foreground retry timers, continuous-outage escalation, persistent pairing-rejection escalation, a bounded background Relay grace, direct-winner and background cancellation, credential-gated Relay presentation, and concurrent LAN/Tailscale authentication. Physical iOS/Android radios, GFE, and production relay recovery remain live-test gaps. Audit1303 intentionally removes supplied-state/mock-callback component and hook examples in mobile/src/transport/connection-health.test.ts. Their exact retired alarms are not equivalent to retained coverage; no new runtime, physical phone or deployed mixed-version evidence is claimed. Audit1310 removes the fake-logical-session background lifecycle suite and two supervisor-nudge examples whose fake migration or negative spy supplied the oracle. Its quick-background/grace-expiry/recovery/lease-rotation examples are intentionally lost. The retained nudge callbacks still check healthy-session retention and losing physical-candidate closure; they do not replace the retired lifecycle alarms. Narrowed commands need fresh validation; no physical radio, production Relay or deployed mixed-version claim. Audit1341 scope1334 removes/narrows mobile/src/transport/mobile-relay-reconnect-controller.test.ts. Exact retired scenarios are intentionally lost; surviving assertions are limited to unchanged retained authored cases. No equivalent replacement, physical phone or deployed mixed-host claim. Fresh validation is required for narrowed commands. Audit1341 scope1335 removes/narrows mobile/src/transport/stable-logical-rpc-client.test.ts. Exact retired scenarios are intentionally lost; surviving assertions are limited to unchanged retained authored cases. No equivalent replacement, physical phone or deployed mixed-host claim. Fresh validation is required for narrowed commands. Audit1735 (refreshed audit1727 at 6da544b7) removes 19 of 38 supervisor definitions. The four retired supervisor assertionRefs covered HOST_OFFLINE self-retry, absent/expired-credential Relay presentation, stale presentation after backgrounded credential selection, and pending presentation through failed-dial cooldown. Those exact alarms are intentionally lost; retained orchestration tests are not equivalent replacement coverage. FakeLogicalClient supplies migration/authentication acceptance and old-session closure, so retained supervisor cases prove timer, async-arbitration and credential-admission coordination only. Narrowed commands require fresh validation; no physical phone, deployed Relay or mixed-version evidence is added. Audit1753 removes the isolated close-code membership and recovery-recipe inventory. Those helper alarms are intentionally lost; retained transport scenarios are not equivalent replacements. No new runtime, physical-device or deployed mixed-version evidence is claimed. Audit1753: the retained nine-file mobile transport command passed90 Node unit cases on Linux. This is one local execution with fake clocks/transports; no live-provider, native-phone, mixed-version, p95 or soak evidence. The original macOS helper receipt is archived in knownGaps; current coveredPlatforms/providers reflect only the new unit receipt.", "motivatingLinks": [ "https://github.com/stablyai/orca-cloud/pull/96", "https://linear.app/stably/issue/STA-4587", "https://linear.app/stably/issue/STA-4681" ], "invariant": "A foregrounded paired phone must recover from relay HOST_OFFLINE without a foreground or network-change signal, while direct recovery must select the first authenticated configured LAN or Tailscale endpoint without serial timeout delays. Relay recovery presentation starts only for a failed active Relay or a dial backed by an eligible credential, remains pending through a genuine failed-dial cooldown, and exposes the supervisor's consecutive failed-dial count so a continuous outage escalates to unreachable. A relay credential the desktop keeps refusing escalates to re-pair once its transient-rejection budget is spent, and only an authenticated session clears that verdict. A healthy Relay survives backgrounding for at most 30 seconds and is reused on a quick foreground without redial; expiry, transport failure, direct success, or stop cancels the grace safely, late confirmation bookkeeping preserves lease rotation for a retained live Relay, and losing probes or replacements close without affecting the winner.", - "oracle": "Historical oracle specification; the retired supervisor examples named in audit1735 knownGaps are no longer current protection. Inject deterministic relay close codes, missing and expired credentials, delayed credential reads, random bytes, fake timers, suspended timers, stale callbacks, and independently controlled direct clients. Require HOST_OFFLINE to replace any faster transport timer with one 5-15 second retry, require no retry before the selected delay, keep Relay presentation absent when no socket can open, clear it across lifecycle races, retain it through real dial cooldowns, retain one healthy Relay through 29,999 milliseconds of background time without a new dial, close it exactly once at 30 seconds or on a later foreground when the timer was suspended, reject a stale expiry after foreground, recover a retained Relay transport failure only after foreground, resume a lease rotation that came due during the grace, arm a lease whose confirmation persistence finishes during the grace, and leave healthy direct sessions unchanged. Establish Relay through the supervisor and require its active close plus 11 failed replacements to publish reconnect attempt 12 and a Relay-unreachable verdict, race all unique non-relay endpoints, select the first authenticated path, close every loser exactly once, and retain no retry after direct connectivity wins. Drive a supervisor-established Relay through four simulated hours of E2EE rejections and require the re-pair verdict, require it to survive a background/foreground cycle, require two rejections followed by acceptance to never latch it, and require a plain transport outage to still read Relay-unreachable.", - "commands": [ - "pnpm --dir mobile exec vitest run --root .. mobile/src/transport/mobile-direct-endpoint-probe.test.ts mobile/src/transport/mobile-relay-background-grace.test.ts mobile/src/transport/mobile-relay-reconnect-controller.test.ts mobile/src/transport/mobile-endpoint-supervisor.test.ts mobile/src/transport/mobile-endpoint-supervisor-nudge.test.ts mobile/src/transport/mobile-relay-outage-escalation.test.ts mobile/src/transport/mobile-relay-pairing-rejection-escalation.test.ts mobile/src/transport/stable-logical-rpc-client.test.ts mobile/src/transport/mobile-relay-runtime-failover.test.ts --maxWorkers=2", - "pnpm --dir mobile exec vitest run --root .. mobile/src/transport/mobile-direct-endpoint-probe.test.ts mobile/src/transport/mobile-relay-reconnect-controller.test.ts mobile/src/transport/mobile-endpoint-supervisor.test.ts mobile/src/transport/mobile-relay-outage-escalation.test.ts mobile/src/transport/mobile-relay-pairing-rejection-escalation.test.ts mobile/src/transport/stable-logical-rpc-client.test.ts mobile/src/transport/mobile-relay-runtime-failover.test.ts" - ], - "testFiles": [ - "mobile/src/transport/mobile-direct-endpoint-probe.test.ts", - "mobile/src/transport/mobile-relay-background-grace.test.ts", - "mobile/src/transport/mobile-relay-reconnect-controller.test.ts", - "mobile/src/transport/mobile-endpoint-supervisor.test.ts", - "mobile/src/transport/mobile-endpoint-supervisor-nudge.test.ts", - "mobile/src/transport/mobile-relay-outage-escalation.test.ts", - "mobile/src/transport/mobile-relay-pairing-rejection-escalation.test.ts", - "mobile/src/transport/stable-logical-rpc-client.test.ts", - "mobile/src/transport/mobile-relay-runtime-failover.test.ts" - ], - "assertionRefs": [ - { - "file": "mobile/src/transport/mobile-direct-endpoint-probe.test.ts", - "assertions": [ - "a reachable Tailscale endpoint authenticates without waiting for a stale primary LAN timeout", - "the stale direct candidate closes while the authenticated winner stays open" - ] - }, - { - "file": "mobile/src/transport/mobile-relay-background-grace.test.ts", - "assertions": [ - "the grace expires exactly once at 30 seconds", - "foreground deadline checks enforce expiry when the OS suspended the timer", - "a stale expiry callback cannot close a foreground connection" - ] - }, - { - "file": "mobile/src/transport/mobile-relay-reconnect-controller.test.ts", - "assertions": [ - "keeps one retry timer when recovery changes from capacity to host offline", - "drops a pending relay retry after direct connectivity wins", - "latches pairing-rejected only after the transient-rejection budget is spent", - "keeps the pairing-rejected latch across a gate lift and app resume", - "never latches pairing-rejected while an authenticated relay is still live", - "clears the pairing-rejected latch once the desktop authenticates the device", - "clears the pairing-rejected latch when direct connectivity proves the pairing", - "reprobes slowly after rejected E2EE authentication instead of parking forever", - "keeps the fresh-credential gate reprobing after each failed gated attempt", - "resets the gated cadence when the app returns to the foreground", - "mints the gate pass when arming a no-bundle reprobe under a held gate", - "does not arm a gate reprobe timer when the supervisor declined retries", - "does not let an orphaned reprobe timer swallow the next fast backoff", - "lifts the fresh-credential gate when a durable bundle carries a new version", - "does not reset backoff merely because a failed attempt took one ceiling", - "resets backoff after an authenticated relay remains stable", - "paces the next dial by the director Retry-After instead of the local backoff", - "nudges a retained relay tolerantly when availability returns", - "redials an attach timeout once quickly per failure streak" - ] - }, - { - "file": "mobile/src/transport/mobile-endpoint-supervisor-nudge.test.ts", - "assertions": [ - "a replacement Relay that authenticates after backgrounding withdraws without replacing the retained session" - ] - }, - { - "file": "mobile/src/transport/mobile-relay-outage-escalation.test.ts", - "assertions": [ - "a supervisor-established Relay close plus 11 failed replacements publish reconnect attempt 12", - "the existing connection classifier escalates the continuous outage to Can't connect via Relay" - ] - }, - { - "file": "mobile/src/transport/mobile-relay-pairing-rejection-escalation.test.ts", - "assertions": [ - "a persistent E2EE rejection escalates to Pairing invalid — re-pair with your desktop", - "the re-pair verdict survives a background/foreground cycle", - "rejections inside the transient budget followed by acceptance never latch it", - "a plain transport outage still reads Can't connect via Relay" - ] - }, - { - "file": "mobile/src/transport/stable-logical-rpc-client.test.ts", - "assertions": [ - "makes before break, rejects in-flight work, and replays subscriptions", - "keeps replies that commit before cutover and carries viewport state into replay", - "suspends one physical session and replays subscriptions on foreground replacement", - "reports requests the suspended session never sent as suspended, and written ones as ambiguous", - "preserves delivery ambiguity without replaying a mutation after relay replacement", - "lets the physical close settle in-flight requests on close, keeping pre-write failures definite", - "preserves a committed response when logical close wins the callback race", - "publishes the replacement dial phases while the client is suspended", - "forwards a reconnecting phase published by the dialing session itself", - "names a relay dial started from reconnecting without disturbing the state", - "publishes recovery-path changes and keeps Relay pending between failed dials", - "does not revive a stale recovery path after a connection later drops", - "drops the pending path when the previous session recovers mid-dial", - "never downgrades a live session while a make-before-break replacement dials", - "restores disconnected when a dial it was narrating fails", - "outlives the flat bound when the relay cell holds the dial", - "closes a replacement that fails authentication and preserves the active session", - "drops a stream event that lands after the caller unsubscribed" - ] - }, - { - "file": "mobile/src/transport/mobile-relay-runtime-failover.test.ts", - "assertions": [ - "a fresh credential starts Relay presentation with the controller's current count", - "missing or expired credentials never open a Relay socket" - ] - } - ], - "evidenceRuns": [ - { - "date": "2026-11-07", - "runner": "local", - "platform": "linux", - "command": "pnpm --dir mobile exec vitest run --root .. mobile/src/transport/mobile-direct-endpoint-probe.test.ts mobile/src/transport/mobile-relay-background-grace.test.ts mobile/src/transport/mobile-relay-reconnect-controller.test.ts mobile/src/transport/mobile-endpoint-supervisor.test.ts mobile/src/transport/mobile-endpoint-supervisor-nudge.test.ts mobile/src/transport/mobile-relay-outage-escalation.test.ts mobile/src/transport/mobile-relay-pairing-rejection-escalation.test.ts mobile/src/transport/stable-logical-rpc-client.test.ts mobile/src/transport/mobile-relay-runtime-failover.test.ts --maxWorkers=2", - "result": "passed", - "durationSeconds": 1.819, - "summary": "9 test files and 90 cases passed in the retained mobile transport command on candidate base35975c05 with audit1754 deletions. Node unit scenarios use fake clocks/transport/native stand-ins; no physical phone, deployed Relay, real LAN/Tailscale, mixed-version peer, p95 or soak evidence. The removed helper inventories have no equivalent replacement claim." - } - ], + "oracle": "Audit2003: Remove held-subscription and viewport replay, background suspension, delivery-ambiguity/error propagation, dial-phase/recovery-path projections, relay timing and authentication-failure examples from the logical client. Retain only make-before-break cutover with subscription replay, committed-response/close arbitration and late-event cancellation fencing against fake physical sessions. Retire all dated combined receipts whole; their case counts and sibling results do not validate these changed bytes. Archive all prospective selector/file/assertion and covered-scope fields with the downgraded gate; the three retained logical callbacks are described here only, without active gate authority. Downgrade active protection to none because no independent unchanged receipt survives. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, physical provider/PTY, rendered, live remote/mobile or cross-platform result is claimed. Historical prior account (preserved verbatim): Historical oracle specification; the retired supervisor examples named in audit1735 knownGaps are no longer current protection. Inject deterministic relay close codes, missing and expired credentials, delayed credential reads, random bytes, fake timers, suspended timers, stale callbacks, and independently controlled direct clients. Require HOST_OFFLINE to replace any faster transport timer with one 5-15 second retry, require no retry before the selected delay, keep Relay presentation absent when no socket can open, clear it across lifecycle races, retain it through real dial cooldowns, retain one healthy Relay through 29,999 milliseconds of background time without a new dial, close it exactly once at 30 seconds or on a later foreground when the timer was suspended, reject a stale expiry after foreground, recover a retained Relay transport failure only after foreground, resume a lease rotation that came due during the grace, arm a lease whose confirmation persistence finishes during the grace, and leave healthy direct sessions unchanged. Establish Relay through the supervisor and require its active close plus 11 failed replacements to publish reconnect attempt 12 and a Relay-unreachable verdict, race all unique non-relay endpoints, select the first authenticated path, close every loser exactly once, and retain no retry after direct connectivity wins. Drive a supervisor-established Relay through four simulated hours of E2EE rejections and require the re-pair verdict, require it to survive a background/foreground cycle, require two rejections followed by acceptance to never latch it, and require a plain transport outage to still read Relay-unreachable.", + "commands": [], + "testFiles": [], + "assertionRefs": [], + "evidenceRuns": [], "runtimeBudget": { "p95Seconds": 5, "scope": "focused shared and mobile transport unit tests" }, "flakeHistory": { "status": "unknown", - "evidence": "Audit1753: one retained mobile transport command passed locally on Linux (9files/90cases); no current soak history. The prior helper receipt and its historical flake account are archived in knownGaps." + "evidence": "Audit2003: Remove held-subscription and viewport replay, background suspension, delivery-ambiguity/error propagation, dial-phase/recovery-path projections, relay timing and authentication-failure examples from the logical client. Retain only make-before-break cutover with subscription replay, committed-response/close arbitration and late-event cancellation fencing against fake physical sessions. Retire all dated combined receipts whole; their case counts and sibling results do not validate these changed bytes. Archive all prospective selector/file/assertion and covered-scope fields with the downgraded gate; the three retained logical callbacks are described here only, without active gate authority. Downgrade active protection to none because no independent unchanged receipt survives. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, physical provider/PTY, rendered, live remote/mobile or cross-platform result is claimed. Historical prior account (preserved verbatim): Audit1753: one retained mobile transport command passed locally on Linux (9files/90cases); no current soak history. The prior helper receipt and its historical flake account are archived in knownGaps." }, "redGreenEvidence": { "status": "complete", - "evidence": "On 2026-08-26, the unchanged immediate-teardown implementation failed the new supervisor oracle at the first 29,999-millisecond assertion because suspendActiveSession had already run once; the candidate retained Relay until the exact deadline and rejected stale or OS-suspended timer paths. The first grace candidate also failed the persistence-straddles-background oracle because only one Relay open occurred after the lease rotation deadline; scheduling from retained Relay liveness restores the required second open. Banking the pairing rejection after registerFailure's gated early return — the pre-fix placement — leaves the four-hour E2EE oracle on 'Connecting via Relay…' and reds 7 assertions while the 17 others stay green; moving it ahead of the gate turns them green. The pre-fix production-lifecycle oracle establishes Relay through the supervisor, then completes one active close plus 11 replacement failures while the public retry count remains 0; the candidate publishes 12 and reaches the existing Relay-unreachable verdict. The prior external-signal HOST_OFFLINE policy and serial direct probe also fail their respective retry and first-authenticated-endpoint timing oracles." + "evidence": "Audit2003: Remove held-subscription and viewport replay, background suspension, delivery-ambiguity/error propagation, dial-phase/recovery-path projections, relay timing and authentication-failure examples from the logical client. Retain only make-before-break cutover with subscription replay, committed-response/close arbitration and late-event cancellation fencing against fake physical sessions. Retire all dated combined receipts whole; their case counts and sibling results do not validate these changed bytes. Archive all prospective selector/file/assertion and covered-scope fields with the downgraded gate; the three retained logical callbacks are described here only, without active gate authority. Downgrade active protection to none because no independent unchanged receipt survives. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, physical provider/PTY, rendered, live remote/mobile or cross-platform result is claimed. Historical prior account (preserved verbatim): On 2026-08-26, the unchanged immediate-teardown implementation failed the new supervisor oracle at the first 29,999-millisecond assertion because suspendActiveSession had already run once; the candidate retained Relay until the exact deadline and rejected stale or OS-suspended timer paths. The first grace candidate also failed the persistence-straddles-background oracle because only one Relay open occurred after the lease rotation deadline; scheduling from retained Relay liveness restores the required second open. Banking the pairing rejection after registerFailure's gated early return — the pre-fix placement — leaves the four-hour E2EE oracle on 'Connecting via Relay…' and reds 7 assertions while the 17 others stay green; moving it ahead of the gate turns them green. The pre-fix production-lifecycle oracle establishes Relay through the supervisor, then completes one active close plus 11 replacement failures while the public retry count remains 0; the candidate publishes 12 and reaches the existing Relay-unreachable verdict. The prior external-signal HOST_OFFLINE policy and serial direct probe also fail their respective retry and first-authenticated-endpoint timing oracles." }, "performanceBudget": { "required": true, - "evidence": "The background grace adds at most one one-shot timer and retains at most the already-active Relay splice for 30 seconds; it adds no polling, new dial, probe, listener, or wire API. A quick foreground performs zero Relay opens, while expiry closes once and opens no replacement until foreground. The deterministic outage asserts exactly one successful establishment plus 11 failed replacements at the escalation boundary. All configured direct candidates start in one turn, the first authenticated candidate wins after 100 ms, and every losing client is closed." + "evidence": "Audit2003: Remove held-subscription and viewport replay, background suspension, delivery-ambiguity/error propagation, dial-phase/recovery-path projections, relay timing and authentication-failure examples from the logical client. Retain only make-before-break cutover with subscription replay, committed-response/close arbitration and late-event cancellation fencing against fake physical sessions. Retire all dated combined receipts whole; their case counts and sibling results do not validate these changed bytes. Archive all prospective selector/file/assertion and covered-scope fields with the downgraded gate; the three retained logical callbacks are described here only, without active gate authority. Downgrade active protection to none because no independent unchanged receipt survives. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, physical provider/PTY, rendered, live remote/mobile or cross-platform result is claimed. Historical prior account (preserved verbatim): The background grace adds at most one one-shot timer and retains at most the already-active Relay splice for 30 seconds; it adds no polling, new dial, probe, listener, or wire API. A quick foreground performs zero Relay opens, while expiry closes once and opens no replacement until foreground. The deterministic outage asserts exactly one successful establishment plus 11 failed replacements at the escalation boundary. All configured direct candidates start in one turn, the first authenticated candidate wins after 100 ms, and every losing client is closed." }, "promotionCriteria": [ "Collect 100 consecutive CI passes or 14 days of soak history.", @@ -3520,7 +3406,30 @@ "Audit1735 (refreshed audit1727 at 6da544b7) removes 19 of 38 supervisor definitions. The four retired supervisor assertionRefs covered HOST_OFFLINE self-retry, absent/expired-credential Relay presentation, stale presentation after backgrounded credential selection, and pending presentation through failed-dial cooldown. Those exact alarms are intentionally lost; retained orchestration tests are not equivalent replacement coverage. FakeLogicalClient supplies migration/authentication acceptance and old-session closure, so retained supervisor cases prove timer, async-arbitration and credential-admission coordination only. Narrowed commands require fresh validation; no physical phone, deployed Relay or mixed-version evidence is added.", "Audit1753 retires the standalone close-code/recovery-recipe assertions, including the HOST_OFFLINE fullJitter flag alarm; the original helper receipt is archived below, and no equivalent replacement is claimed.", "Audit1753 retired original helper receipt; complete record preserved: {\n \"date\": \"2026-08-16\",\n \"runner\": \"local\",\n \"platform\": \"macos\",\n \"command\": \"pnpm exec vitest run --config config/vitest.config.ts src/shared/mobile-relay-close-codes.test.ts --reporter=dot\",\n \"currentCommand\": \"pnpm exec vitest run --config config/vitest.config.ts src/shared/remote-runtime/relay/mobile-relay-close-codes.test.ts --reporter=dot\",\n \"result\": \"passed\",\n \"durationSeconds\": 0.17,\n \"summary\": \"The shared close-code contract passed with five assertions.\"\n}", - "Audit1753 historical scope before retiring the helper receipt: {\"coveredPlatforms\":[\"macos\"],\"coveredProviders\":[\"lan\",\"tailscale\",\"cloud-relay\"],\"flakeHistory\":{\"status\":\"unknown\",\"evidence\":\"Two deterministic local runs exist; CI and soak history are not yet available.\"}}" + "Audit1753 historical scope before retiring the helper receipt: {\"coveredPlatforms\":[\"macos\"],\"coveredProviders\":[\"lan\",\"tailscale\",\"cloud-relay\"],\"flakeHistory\":{\"status\":\"unknown\",\"evidence\":\"Two deterministic local runs exist; CI and soak history are not yet available.\"}}", + "Audit2003: Remove held-subscription and viewport replay, background suspension, delivery-ambiguity/error propagation, dial-phase/recovery-path projections, relay timing and authentication-failure examples from the logical client. Retain only make-before-break cutover with subscription replay, committed-response/close arbitration and late-event cancellation fencing against fake physical sessions. Retire all dated combined receipts whole; their case counts and sibling results do not validate these changed bytes. Archive all prospective selector/file/assertion and covered-scope fields with the downgraded gate; the three retained logical callbacks are described here only, without active gate authority. Downgrade active protection to none because no independent unchanged receipt survives. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, physical provider/PTY, rendered, live remote/mobile or cross-platform result is claimed.", + "Audit2003 historical original commands object, preserved verbatim; retired objects are not active execution evidence or replacement coverage:\n\"pnpm --dir mobile exec vitest run --root .. mobile/src/transport/mobile-direct-endpoint-probe.test.ts mobile/src/transport/mobile-relay-background-grace.test.ts mobile/src/transport/mobile-relay-reconnect-controller.test.ts mobile/src/transport/mobile-endpoint-supervisor.test.ts mobile/src/transport/mobile-endpoint-supervisor-nudge.test.ts mobile/src/transport/mobile-relay-outage-escalation.test.ts mobile/src/transport/mobile-relay-pairing-rejection-escalation.test.ts mobile/src/transport/stable-logical-rpc-client.test.ts mobile/src/transport/mobile-relay-runtime-failover.test.ts --maxWorkers=2\"", + "Audit2003 historical original commands object, preserved verbatim; retired objects are not active execution evidence or replacement coverage:\n\"pnpm --dir mobile exec vitest run --root .. mobile/src/transport/mobile-direct-endpoint-probe.test.ts mobile/src/transport/mobile-relay-reconnect-controller.test.ts mobile/src/transport/mobile-endpoint-supervisor.test.ts mobile/src/transport/mobile-relay-outage-escalation.test.ts mobile/src/transport/mobile-relay-pairing-rejection-escalation.test.ts mobile/src/transport/stable-logical-rpc-client.test.ts mobile/src/transport/mobile-relay-runtime-failover.test.ts\"", + "Audit2003 historical original testFiles object, preserved verbatim; retired objects are not active execution evidence or replacement coverage:\n\"mobile/src/transport/mobile-direct-endpoint-probe.test.ts\"", + "Audit2003 historical original testFiles object, preserved verbatim; retired objects are not active execution evidence or replacement coverage:\n\"mobile/src/transport/mobile-relay-background-grace.test.ts\"", + "Audit2003 historical original testFiles object, preserved verbatim; retired objects are not active execution evidence or replacement coverage:\n\"mobile/src/transport/mobile-relay-reconnect-controller.test.ts\"", + "Audit2003 historical original testFiles object, preserved verbatim; retired objects are not active execution evidence or replacement coverage:\n\"mobile/src/transport/mobile-endpoint-supervisor.test.ts\"", + "Audit2003 historical original testFiles object, preserved verbatim; retired objects are not active execution evidence or replacement coverage:\n\"mobile/src/transport/mobile-endpoint-supervisor-nudge.test.ts\"", + "Audit2003 historical original testFiles object, preserved verbatim; retired objects are not active execution evidence or replacement coverage:\n\"mobile/src/transport/mobile-relay-outage-escalation.test.ts\"", + "Audit2003 historical original testFiles object, preserved verbatim; retired objects are not active execution evidence or replacement coverage:\n\"mobile/src/transport/mobile-relay-pairing-rejection-escalation.test.ts\"", + "Audit2003 historical original testFiles object, preserved verbatim; retired objects are not active execution evidence or replacement coverage:\n\"mobile/src/transport/stable-logical-rpc-client.test.ts\"", + "Audit2003 historical original testFiles object, preserved verbatim; retired objects are not active execution evidence or replacement coverage:\n\"mobile/src/transport/mobile-relay-runtime-failover.test.ts\"", + "Audit2003 historical original assertionRefs object, preserved verbatim; retired objects are not active execution evidence or replacement coverage:\n{\n \"file\": \"mobile/src/transport/mobile-direct-endpoint-probe.test.ts\",\n \"assertions\": [\n \"a reachable Tailscale endpoint authenticates without waiting for a stale primary LAN timeout\",\n \"the stale direct candidate closes while the authenticated winner stays open\"\n ]\n }", + "Audit2003 historical original assertionRefs object, preserved verbatim; retired objects are not active execution evidence or replacement coverage:\n{\n \"file\": \"mobile/src/transport/mobile-relay-background-grace.test.ts\",\n \"assertions\": [\n \"the grace expires exactly once at 30 seconds\",\n \"foreground deadline checks enforce expiry when the OS suspended the timer\",\n \"a stale expiry callback cannot close a foreground connection\"\n ]\n }", + "Audit2003 historical original assertionRefs object, preserved verbatim; retired objects are not active execution evidence or replacement coverage:\n{\n \"file\": \"mobile/src/transport/mobile-relay-reconnect-controller.test.ts\",\n \"assertions\": [\n \"keeps one retry timer when recovery changes from capacity to host offline\",\n \"drops a pending relay retry after direct connectivity wins\",\n \"latches pairing-rejected only after the transient-rejection budget is spent\",\n \"keeps the pairing-rejected latch across a gate lift and app resume\",\n \"never latches pairing-rejected while an authenticated relay is still live\",\n \"clears the pairing-rejected latch once the desktop authenticates the device\",\n \"clears the pairing-rejected latch when direct connectivity proves the pairing\",\n \"reprobes slowly after rejected E2EE authentication instead of parking forever\",\n \"keeps the fresh-credential gate reprobing after each failed gated attempt\",\n \"resets the gated cadence when the app returns to the foreground\",\n \"mints the gate pass when arming a no-bundle reprobe under a held gate\",\n \"does not arm a gate reprobe timer when the supervisor declined retries\",\n \"does not let an orphaned reprobe timer swallow the next fast backoff\",\n \"lifts the fresh-credential gate when a durable bundle carries a new version\",\n \"does not reset backoff merely because a failed attempt took one ceiling\",\n \"resets backoff after an authenticated relay remains stable\",\n \"paces the next dial by the director Retry-After instead of the local backoff\",\n \"nudges a retained relay tolerantly when availability returns\",\n \"redials an attach timeout once quickly per failure streak\"\n ]\n }", + "Audit2003 historical original assertionRefs object, preserved verbatim; retired objects are not active execution evidence or replacement coverage:\n{\n \"file\": \"mobile/src/transport/mobile-endpoint-supervisor-nudge.test.ts\",\n \"assertions\": [\n \"a replacement Relay that authenticates after backgrounding withdraws without replacing the retained session\"\n ]\n }", + "Audit2003 historical original assertionRefs object, preserved verbatim; retired objects are not active execution evidence or replacement coverage:\n{\n \"file\": \"mobile/src/transport/mobile-relay-outage-escalation.test.ts\",\n \"assertions\": [\n \"a supervisor-established Relay close plus 11 failed replacements publish reconnect attempt 12\",\n \"the existing connection classifier escalates the continuous outage to Can't connect via Relay\"\n ]\n }", + "Audit2003 historical original assertionRefs object, preserved verbatim; retired objects are not active execution evidence or replacement coverage:\n{\n \"file\": \"mobile/src/transport/mobile-relay-pairing-rejection-escalation.test.ts\",\n \"assertions\": [\n \"a persistent E2EE rejection escalates to Pairing invalid — re-pair with your desktop\",\n \"the re-pair verdict survives a background/foreground cycle\",\n \"rejections inside the transient budget followed by acceptance never latch it\",\n \"a plain transport outage still reads Can't connect via Relay\"\n ]\n }", + "Audit2003 historical original assertionRefs object, preserved verbatim; retired objects are not active execution evidence or replacement coverage:\n{\n \"file\": \"mobile/src/transport/stable-logical-rpc-client.test.ts\",\n \"assertions\": [\n \"makes before break, rejects in-flight work, and replays subscriptions\",\n \"keeps replies that commit before cutover and carries viewport state into replay\",\n \"suspends one physical session and replays subscriptions on foreground replacement\",\n \"reports requests the suspended session never sent as suspended, and written ones as ambiguous\",\n \"preserves delivery ambiguity without replaying a mutation after relay replacement\",\n \"lets the physical close settle in-flight requests on close, keeping pre-write failures definite\",\n \"preserves a committed response when logical close wins the callback race\",\n \"publishes the replacement dial phases while the client is suspended\",\n \"forwards a reconnecting phase published by the dialing session itself\",\n \"names a relay dial started from reconnecting without disturbing the state\",\n \"publishes recovery-path changes and keeps Relay pending between failed dials\",\n \"does not revive a stale recovery path after a connection later drops\",\n \"drops the pending path when the previous session recovers mid-dial\",\n \"never downgrades a live session while a make-before-break replacement dials\",\n \"restores disconnected when a dial it was narrating fails\",\n \"outlives the flat bound when the relay cell holds the dial\",\n \"closes a replacement that fails authentication and preserves the active session\",\n \"drops a stream event that lands after the caller unsubscribed\"\n ]\n }", + "Audit2003 historical original assertionRefs object, preserved verbatim; retired objects are not active execution evidence or replacement coverage:\n{\n \"file\": \"mobile/src/transport/mobile-relay-runtime-failover.test.ts\",\n \"assertions\": [\n \"a fresh credential starts Relay presentation with the controller's current count\",\n \"missing or expired credentials never open a Relay socket\"\n ]\n }", + "Audit2003 historical original coveredPlatforms object, preserved verbatim; retired objects are not active execution evidence or replacement coverage:\n[\"linux\"]", + "Audit2003 historical original coveredProviders object, preserved verbatim; retired objects are not active execution evidence or replacement coverage:\n[]", + "Audit2003 historical original evidenceRuns object, preserved verbatim; retired objects are not active execution evidence or replacement coverage:\n{\n \"date\": \"2026-11-07\",\n \"runner\": \"local\",\n \"platform\": \"linux\",\n \"command\": \"pnpm --dir mobile exec vitest run --root .. mobile/src/transport/mobile-direct-endpoint-probe.test.ts mobile/src/transport/mobile-relay-background-grace.test.ts mobile/src/transport/mobile-relay-reconnect-controller.test.ts mobile/src/transport/mobile-endpoint-supervisor.test.ts mobile/src/transport/mobile-endpoint-supervisor-nudge.test.ts mobile/src/transport/mobile-relay-outage-escalation.test.ts mobile/src/transport/mobile-relay-pairing-rejection-escalation.test.ts mobile/src/transport/stable-logical-rpc-client.test.ts mobile/src/transport/mobile-relay-runtime-failover.test.ts --maxWorkers=2\",\n \"result\": \"passed\",\n \"durationSeconds\": 1.819,\n \"summary\": \"9 test files and 90 cases passed in the retained mobile transport command on candidate base35975c05 with audit1754 deletions. Node unit scenarios use fake clocks/transport/native stand-ins; no physical phone, deployed Relay, real LAN/Tailscale, mixed-version peer, p95 or soak evidence. The removed helper inventories have no equivalent replacement claim.\"\n }" ], "demotionRule": "Keep experimental or demote if focused tests flake, HOST_OFFLINE can park indefinitely, direct probes serialize configured endpoints, loser cleanup leaks clients, or physical-device radio cost exceeds the measured budget." }, @@ -3528,7 +3437,7 @@ "id": "mobile-transport.lifecycle-liveness", "title": "Wanted mobile hosts self-heal without false physical-session death", "maturity": "experimental", - "protection": "partial", + "protection": "none", "owner": "mobile-runtime", "layer": "mobile-provider-and-physical-session-contract", "surfaces": [ @@ -3540,153 +3449,33 @@ ], "platforms": ["ios", "android", "macos", "linux", "windows"], "providers": ["lan", "tailscale", "cloud-relay"], - "coveredPlatforms": ["macos", "linux"], - "coveredProviders": ["lan", "tailscale", "cloud-relay"], - "coverageNotes": "Deterministic React/provider tests cover catalog, missing-host, construction, cancellation, release, forget, refresh, backoff, stale-generation outcomes, and path-aware recovery presentation. Direct sessions cover idle probing and three fair misses. Relay sessions cover a 15s idle probe, rate-limited foreground probes, two fair misses, authenticated activity before semantic decoding, delivery ambiguity, scheduler stalls, and write failure. Physical iOS/Android sleep, carrier/Tailscale loss, and production relay paths remain live-test gaps. Audit1303 intentionally removes supplied-state/mock-callback component and hook examples in mobile/src/transport/connection-health.test.ts. Their exact retired alarms are not equivalent to retained coverage; no new runtime, physical phone or deployed mixed-version evidence is claimed. Audit1315 removes/narrows mobile/src/transport/rpc-session-liveness-watchdog.test.ts. Exact retired scenarios are intentionally lost; surviving assertions are limited to unchanged retained authored cases. No equivalent replacement, physical phone or deployed mixed-host claim. Fresh validation is required for narrowed commands. Audit1322 removes/narrows mobile/src/transport/rpc-session-liveness-integration.test.ts, mobile/src/transport/mobile-relay-rpc-session-liveness.test.ts. Exact retired scenarios are intentionally lost; surviving assertions are limited to unchanged retained authored cases. No equivalent replacement, physical phone or deployed mixed-host claim. Fresh validation is required for narrowed commands. Audit1341 scope1335 removes/narrows mobile/src/transport/mobile-relay-rpc-session.test.ts, mobile/src/transport/stable-logical-rpc-client.test.ts. Exact retired scenarios are intentionally lost; surviving assertions are limited to unchanged retained authored cases. No equivalent replacement, physical phone or deployed mixed-host claim. Fresh validation is required for narrowed commands. Audit1345: focused Linux unit execution now validates this primary selector on source head de234fc7b4a73eda58dd0084bc9e064b5d1191e0. Historical platform statements above describe earlier receipts. Removed fixture/matrix/call-count alarms are intentional losses with no equivalent replacement; no live SSH, physical-provider, p95 or cross-platform claim. Audit1351 refreshes Audit1339 removals/narrowing of mobile/src/transport/mobile-relay-e2ee-link.test.ts. Exact retired scenarios are intentionally lost; surviving assertions are limited to unchanged retained authored cases. No equivalent replacement, physical phone or deployed mixed-host claim. Fresh validation is required for narrowed commands. Audit1352: focused Linux unit selections validated on source head b638e07d395b76af3ba6b03761a1a0fe7e5e026e. Historical statements above retain their original scope. Intentional test losses are recorded in knownGaps; these runs claim no physical-provider, p95, rendered E2E or cross-platform validation. Audit1360: focused Linux unit selections validated on source head 415c9cffc2d10024131ec4b6721767a22195de6d. Historical statements above retain their original scope. Intentional test losses are recorded in knownGaps; these runs claim no physical-provider, p95, rendered E2E or cross-platform validation.", + "coveredPlatforms": [], + "coveredProviders": [], + "coverageNotes": "Audit2003: Remove held-subscription and viewport replay, background suspension, delivery-ambiguity/error propagation, dial-phase/recovery-path projections, relay timing and authentication-failure examples from the logical client. Retain only make-before-break cutover with subscription replay, committed-response/close arbitration and late-event cancellation fencing against fake physical sessions. Retire all dated combined receipts whole; their case counts and sibling results do not validate these changed bytes. Archive all prospective selector/file/assertion and covered-scope fields with the downgraded gate; the three retained logical callbacks are described here only, without active gate authority. Downgrade active protection to none because no independent unchanged receipt survives. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, physical provider/PTY, rendered, live remote/mobile or cross-platform result is claimed. Historical prior account (preserved verbatim): Deterministic React/provider tests cover catalog, missing-host, construction, cancellation, release, forget, refresh, backoff, stale-generation outcomes, and path-aware recovery presentation. Direct sessions cover idle probing and three fair misses. Relay sessions cover a 15s idle probe, rate-limited foreground probes, two fair misses, authenticated activity before semantic decoding, delivery ambiguity, scheduler stalls, and write failure. Physical iOS/Android sleep, carrier/Tailscale loss, and production relay paths remain live-test gaps. Audit1303 intentionally removes supplied-state/mock-callback component and hook examples in mobile/src/transport/connection-health.test.ts. Their exact retired alarms are not equivalent to retained coverage; no new runtime, physical phone or deployed mixed-version evidence is claimed. Audit1315 removes/narrows mobile/src/transport/rpc-session-liveness-watchdog.test.ts. Exact retired scenarios are intentionally lost; surviving assertions are limited to unchanged retained authored cases. No equivalent replacement, physical phone or deployed mixed-host claim. Fresh validation is required for narrowed commands. Audit1322 removes/narrows mobile/src/transport/rpc-session-liveness-integration.test.ts, mobile/src/transport/mobile-relay-rpc-session-liveness.test.ts. Exact retired scenarios are intentionally lost; surviving assertions are limited to unchanged retained authored cases. No equivalent replacement, physical phone or deployed mixed-host claim. Fresh validation is required for narrowed commands. Audit1341 scope1335 removes/narrows mobile/src/transport/mobile-relay-rpc-session.test.ts, mobile/src/transport/stable-logical-rpc-client.test.ts. Exact retired scenarios are intentionally lost; surviving assertions are limited to unchanged retained authored cases. No equivalent replacement, physical phone or deployed mixed-host claim. Fresh validation is required for narrowed commands. Audit1345: focused Linux unit execution now validates this primary selector on source head de234fc7b4a73eda58dd0084bc9e064b5d1191e0. Historical platform statements above describe earlier receipts. Removed fixture/matrix/call-count alarms are intentional losses with no equivalent replacement; no live SSH, physical-provider, p95 or cross-platform claim. Audit1351 refreshes Audit1339 removals/narrowing of mobile/src/transport/mobile-relay-e2ee-link.test.ts. Exact retired scenarios are intentionally lost; surviving assertions are limited to unchanged retained authored cases. No equivalent replacement, physical phone or deployed mixed-host claim. Fresh validation is required for narrowed commands. Audit1352: focused Linux unit selections validated on source head b638e07d395b76af3ba6b03761a1a0fe7e5e026e. Historical statements above retain their original scope. Intentional test losses are recorded in knownGaps; these runs claim no physical-provider, p95, rendered E2E or cross-platform validation. Audit1360: focused Linux unit selections validated on source head 415c9cffc2d10024131ec4b6721767a22195de6d. Historical statements above retain their original scope. Intentional test losses are recorded in knownGaps; these runs claim no physical-provider, p95, rendered E2E or cross-platform validation.", "motivatingLinks": [ "https://linear.app/stably/issue/STA-4153/mobile-app-disconnects-frequently" ], "invariant": "A provider-wanted host without a logical client retries every current-generation pre-client failure until success or explicit cancellation, while release, disconnect, forget, refresh, and stale async continuations preserve their distinct resurrection policies. Direct sessions probe after authenticated silence and terminate after three fair misses. Relay sessions probe after 15s of authenticated silence, retry at once after a fair miss, probe on demand only decisively while a caller's reply is overdue after 4s of authenticated silence, use rate-limited foreground probes, and terminate after two fair misses; a tolerant miss does not count while authenticated frames arrived during the probe window, or while a sent request still awaits its reply and the link has been silent for less than the request timeout. Authenticated traffic, scheduler stalls, and replacement identities cannot manufacture death. Relay recovery must notify presentation independently of transport-state changes, must not inherit a failed direct path's Tailscale hint, and must retain long-outage escalation.", - "oracle": "Inject rejected and never-settling catalog reads, missing profiles, synchronous construction failure, release, refresh, forget, revival, and stale completion under fake time. Require bounded 1s through 60s retry, exact current-generation publication, and no zero-owner or forgotten-client resurrection. Inject direct and relay authenticated text/binary traffic, silent probe windows, stalled clocks, replacement identities, and failed writes. Require direct three-miss tolerance; Relay idle probes only after 15s without authenticated frames, one foreground probe sequence, two fair 4s misses, a 10s voluntary rate limit, no old-session network-change probe, and delivery-unknown mutations without replay. Start and clear Relay recovery without changing transport state and require observers to rerender. Project five failed Tailscale attempts into a neutral Relay-specific verdict, then require twelve attempts to produce a Relay-specific unreachable verdict with no Tailscale hint.", - "commands": [ - "pnpm --dir mobile exec vitest run --root .. mobile/src/transport/client-context.test.ts mobile/src/transport/host-open-recovery.test.tsx mobile/src/transport/host-open-retry-scheduler.test.ts mobile/src/transport/rpc-session-liveness-watchdog.test.ts mobile/src/transport/rpc-session-liveness-integration.test.ts mobile/src/transport/mobile-relay-rpc-session.test.ts mobile/src/transport/mobile-relay-rpc-session-liveness.test.ts mobile/src/transport/mobile-endpoint-nudge-router.test.ts mobile/src/transport/mobile-relay-e2ee-link.test.ts mobile/src/transport/stable-logical-rpc-client.test.ts" - ], - "testFiles": [ - "mobile/src/transport/client-context.test.ts", - "mobile/src/transport/host-open-recovery.test.tsx", - "mobile/src/transport/host-open-retry-scheduler.test.ts", - "mobile/src/transport/rpc-session-liveness-watchdog.test.ts", - "mobile/src/transport/rpc-session-liveness-integration.test.ts", - "mobile/src/transport/mobile-relay-rpc-session.test.ts", - "mobile/src/transport/mobile-relay-rpc-session-liveness.test.ts", - "mobile/src/transport/mobile-endpoint-nudge-router.test.ts", - "mobile/src/transport/mobile-relay-e2ee-link.test.ts", - "mobile/src/transport/stable-logical-rpc-client.test.ts" - ], - "assertionRefs": [ - { - "file": "mobile/src/transport/client-context.test.ts", - "assertions": [ - "a retired close-on-release owner closes an ownerless manual reconnect", - "a Relay recovery path change rerenders observers without a transport-state change" - ] - }, - { - "file": "mobile/src/transport/host-open-recovery.test.tsx", - "assertions": [ - "catalog, missing-host, and construction failures retry without remounting", - "final release and forget prevent later client publication", - "refresh bypasses a never-settling stale open and stale failure cannot clobber its replacement", - "revival expedites recovery without resetting the failure tier", - "a retired owner's release cannot cancel a newer owner's open", - "explicit reconnect demand survives a pre-client failure" - ] - }, - { - "file": "mobile/src/transport/rpc-session-liveness-watchdog.test.ts", - "assertions": [ - "does not reuse a probe id after the same identity starts a new generation" - ] - }, - { - "file": "mobile/src/transport/rpc-session-liveness-integration.test.ts", - "assertions": [ - "upgrades a focus probe on resume without letting repeated nudges extend the deadline", - "reconnects when control probes stall while terminal and browser traffic continues", - "ignores superseded probes and unrelated RPC replies during a control check", - "does not let probe replies consume the authentication retry budget" - ] - }, - { - "file": "mobile/src/transport/mobile-relay-rpc-session.test.ts", - "assertions": [ - "releases stream listeners on failure even when close follows it", - "connects when an older runtime rejects capability negotiation", - "connects when the relay never answers capability negotiation", - "rejects a mismatched outer credential version and closes the physical link", - "routes terminal and browser binary streams after confirmation", - "keeps the configured connect default for omitted options and honors explicit timeouts", - "rejects pending RPC work when the physical link fails", - "releases requests waiting on a failed session once it is retired, unsent", - "keeps a synchronous pre-write relay failure definite", - "marks in-flight requests delivery-unknown when the session closes", - "marks a relay RPC timeout delivery-unknown", - "sends a server workspace call as the same top-level executionHost frame as before the envelope", - "merges an envelope under the transport fields on requests, subscribes and unsubscribes", - "carries captured GitHub authority through the production relay physical await without replay" - ] - }, - { - "file": "mobile/src/transport/mobile-relay-rpc-session-liveness.test.ts", - "assertions": [ - "keeps availability returns tolerant while a proven resume bypasses recent probes", - "probes an idle relay and closes it when the probes go unanswered", - "keeps an idle relay open while a slow reply is still downloading", - "closes a silent relay even while requests keep going unanswered", - "keeps an idle relay open while a stream backlog delays the probe reply" - ] - }, - { - "file": "mobile/src/transport/stable-logical-rpc-client.test.ts", - "assertions": [ - "makes before break, rejects in-flight work, and replays subscriptions", - "keeps replies that commit before cutover and carries viewport state into replay", - "suspends one physical session and replays subscriptions on foreground replacement", - "reports requests the suspended session never sent as suspended, and written ones as ambiguous", - "preserves delivery ambiguity without replaying a mutation after relay replacement", - "lets the physical close settle in-flight requests on close, keeping pre-write failures definite", - "preserves a committed response when logical close wins the callback race", - "publishes the replacement dial phases while the client is suspended", - "forwards a reconnecting phase published by the dialing session itself", - "names a relay dial started from reconnecting without disturbing the state", - "publishes recovery-path changes and keeps Relay pending between failed dials", - "does not revive a stale recovery path after a connection later drops", - "drops the pending path when the previous session recovers mid-dial", - "never downgrades a live session while a make-before-break replacement dials", - "restores disconnected when a dial it was narrating fails", - "outlives the flat bound when the relay cell holds the dial", - "closes a replacement that fails authentication and preserves the active session", - "drops a stream event that lands after the caller unsubscribed" - ] - } - ], - "evidenceRuns": [ - { - "date": "2026-11-04", - "runner": "local", - "platform": "linux", - "result": "passed", - "command": "cd mobile && ORCA_BACKGROUND_LAUNCH=1 node /home/neil/orca/workspaces/orca/comprehensive-test-audit-10/build/audits/audit1352-combined-low-value-tests/validation/mobile/node_modules/vitest/vitest.mjs run --config vitest.config.ts src/transport/client-context.test.ts src/transport/host-open-recovery.test.tsx src/transport/host-open-retry-scheduler.test.ts src/transport/mobile-endpoint-nudge-router.test.ts src/transport/mobile-relay-e2ee-link.test.ts src/transport/mobile-relay-rpc-session-liveness.test.ts src/transport/mobile-relay-rpc-session.test.ts src/transport/rpc-session-liveness-integration.test.ts src/transport/rpc-session-liveness-watchdog.test.ts src/transport/stable-logical-rpc-client.test.ts --maxWorkers=2", - "currentCommand": "pnpm --dir mobile exec vitest run --root .. mobile/src/transport/client-context.test.ts mobile/src/transport/host-open-recovery.test.tsx mobile/src/transport/host-open-retry-scheduler.test.ts mobile/src/transport/rpc-session-liveness-watchdog.test.ts mobile/src/transport/rpc-session-liveness-integration.test.ts mobile/src/transport/mobile-relay-rpc-session.test.ts mobile/src/transport/mobile-relay-rpc-session-liveness.test.ts mobile/src/transport/mobile-endpoint-nudge-router.test.ts mobile/src/transport/mobile-relay-e2ee-link.test.ts mobile/src/transport/stable-logical-rpc-client.test.ts", - "durationSeconds": 2.65, - "summary": "Test Files 10 passed (10); Tests 80 passed (80). Focused Linux unit execution; no physical-provider, p95 or cross-platform claim. Source head b638e07d395b76af3ba6b03761a1a0fe7e5e026e." - }, - { - "date": "2026-11-04", - "runner": "local", - "platform": "linux", - "result": "passed", - "command": "cd mobile && ORCA_BACKGROUND_LAUNCH=1 node /home/neil/orca/workspaces/orca/comprehensive-test-audit-10/build/audits/audit1360-combined-low-value-tests/validation/mobile/node_modules/vitest/vitest.mjs run --config vitest.config.ts src/transport/client-context.test.ts src/transport/host-open-recovery.test.tsx src/transport/host-open-retry-scheduler.test.ts src/transport/mobile-endpoint-nudge-router.test.ts src/transport/mobile-relay-e2ee-link.test.ts src/transport/mobile-relay-rpc-session-liveness.test.ts src/transport/mobile-relay-rpc-session.test.ts src/transport/rpc-session-liveness-integration.test.ts src/transport/rpc-session-liveness-watchdog.test.ts src/transport/stable-logical-rpc-client.test.ts --maxWorkers=2", - "currentCommand": "pnpm --dir mobile exec vitest run --root .. mobile/src/transport/client-context.test.ts mobile/src/transport/host-open-recovery.test.tsx mobile/src/transport/host-open-retry-scheduler.test.ts mobile/src/transport/rpc-session-liveness-watchdog.test.ts mobile/src/transport/rpc-session-liveness-integration.test.ts mobile/src/transport/mobile-relay-rpc-session.test.ts mobile/src/transport/mobile-relay-rpc-session-liveness.test.ts mobile/src/transport/mobile-endpoint-nudge-router.test.ts mobile/src/transport/mobile-relay-e2ee-link.test.ts mobile/src/transport/stable-logical-rpc-client.test.ts", - "durationSeconds": 2.67, - "summary": "Test Files 10 passed (10); Tests 80 passed (80). Focused Linux unit execution; no physical-provider, p95 or cross-platform claim. Source head 415c9cffc2d10024131ec4b6721767a22195de6d." - } - ], + "oracle": "Audit2003: Remove held-subscription and viewport replay, background suspension, delivery-ambiguity/error propagation, dial-phase/recovery-path projections, relay timing and authentication-failure examples from the logical client. Retain only make-before-break cutover with subscription replay, committed-response/close arbitration and late-event cancellation fencing against fake physical sessions. Retire all dated combined receipts whole; their case counts and sibling results do not validate these changed bytes. Archive all prospective selector/file/assertion and covered-scope fields with the downgraded gate; the three retained logical callbacks are described here only, without active gate authority. Downgrade active protection to none because no independent unchanged receipt survives. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, physical provider/PTY, rendered, live remote/mobile or cross-platform result is claimed. Historical prior account (preserved verbatim): Inject rejected and never-settling catalog reads, missing profiles, synchronous construction failure, release, refresh, forget, revival, and stale completion under fake time. Require bounded 1s through 60s retry, exact current-generation publication, and no zero-owner or forgotten-client resurrection. Inject direct and relay authenticated text/binary traffic, silent probe windows, stalled clocks, replacement identities, and failed writes. Require direct three-miss tolerance; Relay idle probes only after 15s without authenticated frames, no demand-driven liveness traffic, one foreground probe sequence, two fair 4s misses, a 10s voluntary rate limit, no old-session network-change probe, and delivery-unknown mutations without replay. Start and clear Relay recovery without changing transport state and require observers to rerender. Project five failed Tailscale attempts into a neutral Relay-specific verdict, then require twelve attempts to produce a Relay-specific unreachable verdict with no Tailscale hint. Upstream oracle specification after Relay correction (not execution evidence): Inject rejected and never-settling catalog reads, missing profiles, synchronous construction failure, release, refresh, forget, revival, and stale completion under fake time. Require bounded 1s through 60s retry, exact current-generation publication, and no zero-owner or forgotten-client resurrection. Inject direct and relay authenticated text/binary traffic, silent probe windows, stalled clocks, replacement identities, and failed writes. Require direct three-miss tolerance; Relay idle probes only after 15s without authenticated frames, one foreground probe sequence, two fair 4s misses, a 10s voluntary rate limit, no old-session network-change probe, and delivery-unknown mutations without replay. Start and clear Relay recovery without changing transport state and require observers to rerender. Project five failed Tailscale attempts into a neutral Relay-specific verdict, then require twelve attempts to produce a Relay-specific unreachable verdict with no Tailscale hint.", + "commands": [], + "testFiles": [], + "assertionRefs": [], + "evidenceRuns": [], "runtimeBudget": { "p95Seconds": 5, "scope": "focused mobile provider and transport unit tests New focused Linux selector completed once; historical p95 target is unchanged and not measured by this execution. New focused Linux selector completed once; historical p95 target is unchanged and not measured by this execution." }, "flakeHistory": { "status": "unknown", - "evidence": "One deterministic local focused run exists; CI and soak history are not yet available." + "evidence": "Audit2003: Remove held-subscription and viewport replay, background suspension, delivery-ambiguity/error propagation, dial-phase/recovery-path projections, relay timing and authentication-failure examples from the logical client. Retain only make-before-break cutover with subscription replay, committed-response/close arbitration and late-event cancellation fencing against fake physical sessions. Retire all dated combined receipts whole; their case counts and sibling results do not validate these changed bytes. Archive all prospective selector/file/assertion and covered-scope fields with the downgraded gate; the three retained logical callbacks are described here only, without active gate authority. Downgrade active protection to none because no independent unchanged receipt survives. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, physical provider/PTY, rendered, live remote/mobile or cross-platform result is claimed. Historical prior account (preserved verbatim): One deterministic local focused run exists; CI and soak history are not yet available." }, "redGreenEvidence": { "status": "complete", - "evidence": "Current main failed six deterministic assertions: terminal catalog failure, zero-owner publication, stale failure clobber, initial direct handshake write throw, one-window direct death, and foreground probe write throw. The candidate passes the same assertions and adds relay parity, backoff, cancellation, and scheduler-fairness coverage." + "evidence": "Audit2003: Remove held-subscription and viewport replay, background suspension, delivery-ambiguity/error propagation, dial-phase/recovery-path projections, relay timing and authentication-failure examples from the logical client. Retain only make-before-break cutover with subscription replay, committed-response/close arbitration and late-event cancellation fencing against fake physical sessions. Retire all dated combined receipts whole; their case counts and sibling results do not validate these changed bytes. Archive all prospective selector/file/assertion and covered-scope fields with the downgraded gate; the three retained logical callbacks are described here only, without active gate authority. Downgrade active protection to none because no independent unchanged receipt survives. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, physical provider/PTY, rendered, live remote/mobile or cross-platform result is claimed. Historical prior account (preserved verbatim): Current main failed six deterministic assertions: terminal catalog failure, zero-owner publication, stale failure clobber, initial direct handshake write throw, one-window direct death, and foreground probe write throw. The candidate passes the same assertions and adds relay parity, backoff, cancellation, and scheduler-fairness coverage." }, "performanceBudget": { "required": true, - "evidence": "Each wanted clientless host owns at most one open and one retry timer with a 60-second capped tail. Direct sessions own one idle or probe timer. Relay sessions own one idle or probe timer and start an idle probe 15 seconds after the last authenticated frame, about four small round trips per healthy idle foreground minute. A fair miss on a link that isn't busy sends one retry as soon as its 4-second timeout fires, and a second fair miss closes the session. Demand-driven probes are decisive and run only while a caller's reply is overdue after 4 seconds of authenticated silence, at most one per 4 seconds; foreground sequences start at most once per 10 seconds. Each Relay probe costs one relay forward and one desktop status.get probe dispatch; no subprocess or wire opcode is introduced." + "evidence": "Audit2003: Remove held-subscription and viewport replay, background suspension, delivery-ambiguity/error propagation, dial-phase/recovery-path projections, relay timing and authentication-failure examples from the logical client. Retain only make-before-break cutover with subscription replay, committed-response/close arbitration and late-event cancellation fencing against fake physical sessions. Retire all dated combined receipts whole; their case counts and sibling results do not validate these changed bytes. Archive all prospective selector/file/assertion and covered-scope fields with the downgraded gate; the three retained logical callbacks are described here only, without active gate authority. Downgrade active protection to none because no independent unchanged receipt survives. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, physical provider/PTY, rendered, live remote/mobile or cross-platform result is claimed. Historical prior account (preserved verbatim): Each wanted clientless host owns at most one open and one retry timer with a 60-second capped tail. Direct sessions own one idle or probe timer. Relay sessions own one idle or probe timer and send at most one idle probe per 15 seconds without authenticated frames, about four small round trips per idle foreground minute, and no demand-driven liveness traffic; foreground sequences start at most once per 10 seconds. Each Relay idle probe costs one relay forward and one desktop status.get probe dispatch; no subprocess or wire opcode is introduced. Upstream resource specification after Relay correction (not a measured budget): Each wanted clientless host owns at most one open and one retry timer with a 60-second capped tail. Direct sessions own one idle or probe timer. Relay sessions own one idle or probe timer and start an idle probe 15 seconds after the last authenticated frame, about four small round trips per healthy idle foreground minute. A fair miss on a link that isn't busy sends one retry as soon as its 4-second timeout fires, and a second fair miss closes the session. Demand-driven probes are decisive and run only while a caller's reply is overdue after 4 seconds of authenticated silence, at most one per 4 seconds; foreground sequences start at most once per 10 seconds. Each Relay probe costs one relay forward and one desktop status.get probe dispatch; no subprocess or wire opcode is introduced." }, "promotionCriteria": [ "Collect 100 consecutive CI passes or 14 days of soak history.", @@ -3696,7 +3485,6 @@ ], "knownGaps": [ "No physical iOS or Android device was exercised.", - "No test in the commands covers the decisive on-demand Relay probe that runs while a dictation reply is overdue (rpc-liveness-probe-while-waiting.ts).", "No affected-device log yet proves the old one-window watchdog caused the reported live-session drop.", "Carrier NAT, production Tailscale, production relay cells, and real scheduler suspension remain unmeasured.", "Persistent pre-client failure metadata is not yet exposed in Home UI.", @@ -3712,7 +3500,31 @@ "Audit1341 scope1335 removes/narrows mobile/src/transport/mobile-relay-rpc-session.test.ts, mobile/src/transport/stable-logical-rpc-client.test.ts. Exact retired scenarios are intentionally lost; surviving assertions are limited to unchanged retained authored cases. No equivalent replacement, physical phone or deployed mixed-host claim. Fresh validation is required for narrowed commands.", "Archived audit1341 scope1335 historical receipt (original command/currentCommand/result/count/duration unchanged; not evidence for the shortened suite): {\"date\":\"2026-11-04\",\"runner\":\"local\",\"platform\":\"linux\",\"result\":\"passed\",\"command\":\"cd mobile && ORCA_BACKGROUND_LAUNCH=1 node /home/neil/orca/workspaces/orca/comprehensive-test-audit-10/build/audits/audit1324-combined-low-value-tests/validation/mobile/node_modules/vitest/vitest.mjs run --config vitest.config.ts src/transport/client-context.test.ts src/transport/host-open-recovery.test.tsx src/transport/host-open-retry-scheduler.test.ts src/transport/rpc-session-liveness-watchdog.test.ts src/transport/rpc-session-liveness-integration.test.ts src/transport/mobile-relay-rpc-session.test.ts src/transport/mobile-relay-rpc-session-liveness.test.ts src/transport/mobile-endpoint-nudge-router.test.ts src/transport/mobile-relay-e2ee-link.test.ts src/transport/stable-logical-rpc-client.test.ts --maxWorkers=2\",\"currentCommand\":\"pnpm --dir mobile exec vitest run --root .. mobile/src/transport/client-context.test.ts mobile/src/transport/host-open-recovery.test.tsx mobile/src/transport/host-open-retry-scheduler.test.ts mobile/src/transport/rpc-session-liveness-watchdog.test.ts mobile/src/transport/rpc-session-liveness-integration.test.ts mobile/src/transport/mobile-relay-rpc-session.test.ts mobile/src/transport/mobile-relay-rpc-session-liveness.test.ts mobile/src/transport/mobile-endpoint-nudge-router.test.ts mobile/src/transport/mobile-relay-e2ee-link.test.ts mobile/src/transport/stable-logical-rpc-client.test.ts\",\"durationSeconds\":2.91,\"summary\":\"Test Files 10 passed (10); Tests 90 passed (90). Focused Linux unit execution; no p95, physical-provider or cross-platform claim. Source head aff14a8338e6a649ef28b44458e3545c429613a1.\"}", "Audit1351 refreshes Audit1339 removals/narrowing of mobile/src/transport/mobile-relay-e2ee-link.test.ts. Exact retired scenarios are intentionally lost; surviving assertions are limited to unchanged retained authored cases. No equivalent replacement, physical phone or deployed mixed-host claim. Fresh validation is required for narrowed commands.", - "Retired during audit1352 selector cleanup; original complete execution receipt (unchanged): {\"date\":\"2026-11-04\",\"runner\":\"local\",\"platform\":\"linux\",\"result\":\"passed\",\"command\":\"cd mobile && ORCA_BACKGROUND_LAUNCH=1 node /home/neil/orca/workspaces/orca/comprehensive-test-audit-10/build/audits/audit1345-combined-low-value-tests/validation/mobile/node_modules/vitest/vitest.mjs run --config vitest.config.ts src/transport/client-context.test.ts src/transport/host-open-recovery.test.tsx src/transport/host-open-retry-scheduler.test.ts src/transport/mobile-endpoint-nudge-router.test.ts src/transport/mobile-relay-e2ee-link.test.ts src/transport/mobile-relay-rpc-session-liveness.test.ts src/transport/mobile-relay-rpc-session.test.ts src/transport/rpc-session-liveness-integration.test.ts src/transport/rpc-session-liveness-watchdog.test.ts src/transport/stable-logical-rpc-client.test.ts --maxWorkers=2\",\"currentCommand\":\"pnpm --dir mobile exec vitest run --root .. mobile/src/transport/client-context.test.ts mobile/src/transport/host-open-recovery.test.tsx mobile/src/transport/host-open-retry-scheduler.test.ts mobile/src/transport/rpc-session-liveness-watchdog.test.ts mobile/src/transport/rpc-session-liveness-integration.test.ts mobile/src/transport/mobile-relay-rpc-session.test.ts mobile/src/transport/mobile-relay-rpc-session-liveness.test.ts mobile/src/transport/mobile-endpoint-nudge-router.test.ts mobile/src/transport/mobile-relay-e2ee-link.test.ts mobile/src/transport/stable-logical-rpc-client.test.ts\",\"durationSeconds\":3.07,\"summary\":\"Test Files 10 passed (10); Tests 83 passed (83). Focused Linux unit execution; no p95, physical-provider or cross-platform claim. Source head de234fc7b4a73eda58dd0084bc9e064b5d1191e0.\"}" + "Retired during audit1352 selector cleanup; original complete execution receipt (unchanged): {\"date\":\"2026-11-04\",\"runner\":\"local\",\"platform\":\"linux\",\"result\":\"passed\",\"command\":\"cd mobile && ORCA_BACKGROUND_LAUNCH=1 node /home/neil/orca/workspaces/orca/comprehensive-test-audit-10/build/audits/audit1345-combined-low-value-tests/validation/mobile/node_modules/vitest/vitest.mjs run --config vitest.config.ts src/transport/client-context.test.ts src/transport/host-open-recovery.test.tsx src/transport/host-open-retry-scheduler.test.ts src/transport/mobile-endpoint-nudge-router.test.ts src/transport/mobile-relay-e2ee-link.test.ts src/transport/mobile-relay-rpc-session-liveness.test.ts src/transport/mobile-relay-rpc-session.test.ts src/transport/rpc-session-liveness-integration.test.ts src/transport/rpc-session-liveness-watchdog.test.ts src/transport/stable-logical-rpc-client.test.ts --maxWorkers=2\",\"currentCommand\":\"pnpm --dir mobile exec vitest run --root .. mobile/src/transport/client-context.test.ts mobile/src/transport/host-open-recovery.test.tsx mobile/src/transport/host-open-retry-scheduler.test.ts mobile/src/transport/rpc-session-liveness-watchdog.test.ts mobile/src/transport/rpc-session-liveness-integration.test.ts mobile/src/transport/mobile-relay-rpc-session.test.ts mobile/src/transport/mobile-relay-rpc-session-liveness.test.ts mobile/src/transport/mobile-endpoint-nudge-router.test.ts mobile/src/transport/mobile-relay-e2ee-link.test.ts mobile/src/transport/stable-logical-rpc-client.test.ts\",\"durationSeconds\":3.07,\"summary\":\"Test Files 10 passed (10); Tests 83 passed (83). Focused Linux unit execution; no p95, physical-provider or cross-platform claim. Source head de234fc7b4a73eda58dd0084bc9e064b5d1191e0.\"}", + "Audit2003: Remove held-subscription and viewport replay, background suspension, delivery-ambiguity/error propagation, dial-phase/recovery-path projections, relay timing and authentication-failure examples from the logical client. Retain only make-before-break cutover with subscription replay, committed-response/close arbitration and late-event cancellation fencing against fake physical sessions. Retire all dated combined receipts whole; their case counts and sibling results do not validate these changed bytes. Archive all prospective selector/file/assertion and covered-scope fields with the downgraded gate; the three retained logical callbacks are described here only, without active gate authority. Downgrade active protection to none because no independent unchanged receipt survives. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, physical provider/PTY, rendered, live remote/mobile or cross-platform result is claimed.", + "Audit2003 historical original commands object, preserved verbatim; retired objects are not active execution evidence or replacement coverage:\n\"pnpm --dir mobile exec vitest run --root .. mobile/src/transport/client-context.test.ts mobile/src/transport/host-open-recovery.test.tsx mobile/src/transport/host-open-retry-scheduler.test.ts mobile/src/transport/rpc-session-liveness-watchdog.test.ts mobile/src/transport/rpc-session-liveness-integration.test.ts mobile/src/transport/mobile-relay-rpc-session.test.ts mobile/src/transport/mobile-relay-rpc-session-liveness.test.ts mobile/src/transport/mobile-endpoint-nudge-router.test.ts mobile/src/transport/mobile-relay-e2ee-link.test.ts mobile/src/transport/stable-logical-rpc-client.test.ts\"", + "Audit2003 historical original testFiles object, preserved verbatim; retired objects are not active execution evidence or replacement coverage:\n\"mobile/src/transport/client-context.test.ts\"", + "Audit2003 historical original testFiles object, preserved verbatim; retired objects are not active execution evidence or replacement coverage:\n\"mobile/src/transport/host-open-recovery.test.tsx\"", + "Audit2003 historical original testFiles object, preserved verbatim; retired objects are not active execution evidence or replacement coverage:\n\"mobile/src/transport/host-open-retry-scheduler.test.ts\"", + "Audit2003 historical original testFiles object, preserved verbatim; retired objects are not active execution evidence or replacement coverage:\n\"mobile/src/transport/rpc-session-liveness-watchdog.test.ts\"", + "Audit2003 historical original testFiles object, preserved verbatim; retired objects are not active execution evidence or replacement coverage:\n\"mobile/src/transport/rpc-session-liveness-integration.test.ts\"", + "Audit2003 historical original testFiles object, preserved verbatim; retired objects are not active execution evidence or replacement coverage:\n\"mobile/src/transport/mobile-relay-rpc-session.test.ts\"", + "Audit2003 historical original testFiles object, preserved verbatim; retired objects are not active execution evidence or replacement coverage:\n\"mobile/src/transport/mobile-relay-rpc-session-liveness.test.ts\"", + "Audit2003 historical original testFiles object, preserved verbatim; retired objects are not active execution evidence or replacement coverage:\n\"mobile/src/transport/mobile-endpoint-nudge-router.test.ts\"", + "Audit2003 historical original testFiles object, preserved verbatim; retired objects are not active execution evidence or replacement coverage:\n\"mobile/src/transport/mobile-relay-e2ee-link.test.ts\"", + "Audit2003 historical original testFiles object, preserved verbatim; retired objects are not active execution evidence or replacement coverage:\n\"mobile/src/transport/stable-logical-rpc-client.test.ts\"", + "Audit2003 historical original assertionRefs object, preserved verbatim; retired objects are not active execution evidence or replacement coverage:\n{\n \"file\": \"mobile/src/transport/client-context.test.ts\",\n \"assertions\": [\n \"a retired close-on-release owner closes an ownerless manual reconnect\",\n \"a Relay recovery path change rerenders observers without a transport-state change\"\n ]\n }", + "Audit2003 historical original assertionRefs object, preserved verbatim; retired objects are not active execution evidence or replacement coverage:\n{\n \"file\": \"mobile/src/transport/host-open-recovery.test.tsx\",\n \"assertions\": [\n \"catalog, missing-host, and construction failures retry without remounting\",\n \"final release and forget prevent later client publication\",\n \"refresh bypasses a never-settling stale open and stale failure cannot clobber its replacement\",\n \"revival expedites recovery without resetting the failure tier\",\n \"a retired owner's release cannot cancel a newer owner's open\",\n \"explicit reconnect demand survives a pre-client failure\"\n ]\n }", + "Audit2003 historical original assertionRefs object, preserved verbatim; retired objects are not active execution evidence or replacement coverage:\n{\n \"file\": \"mobile/src/transport/rpc-session-liveness-watchdog.test.ts\",\n \"assertions\": [\n \"does not reuse a probe id after the same identity starts a new generation\"\n ]\n }", + "Audit2003 historical original assertionRefs object, preserved verbatim; retired objects are not active execution evidence or replacement coverage:\n{\n \"file\": \"mobile/src/transport/rpc-session-liveness-integration.test.ts\",\n \"assertions\": [\n \"upgrades a focus probe on resume without letting repeated nudges extend the deadline\",\n \"reconnects when control probes stall while terminal and browser traffic continues\",\n \"ignores superseded probes and unrelated RPC replies during a control check\",\n \"does not let probe replies consume the authentication retry budget\"\n ]\n }", + "Audit2003 historical original assertionRefs object, preserved verbatim; retired objects are not active execution evidence or replacement coverage:\n{\n \"file\": \"mobile/src/transport/mobile-relay-rpc-session.test.ts\",\n \"assertions\": [\n \"releases stream listeners on failure even when close follows it\",\n \"connects when an older runtime rejects capability negotiation\",\n \"connects when the relay never answers capability negotiation\",\n \"rejects a mismatched outer credential version and closes the physical link\",\n \"routes terminal and browser binary streams after confirmation\",\n \"keeps the configured connect default for omitted options and honors explicit timeouts\",\n \"rejects pending RPC work when the physical link fails\",\n \"releases requests waiting on a failed session once it is retired, unsent\",\n \"keeps a synchronous pre-write relay failure definite\",\n \"marks in-flight requests delivery-unknown when the session closes\",\n \"marks a relay RPC timeout delivery-unknown\",\n \"sends a server workspace call as the same top-level executionHost frame as before the envelope\",\n \"merges an envelope under the transport fields on requests, subscribes and unsubscribes\",\n \"carries captured GitHub authority through the production relay physical await without replay\"\n ]\n }", + "Audit2003 historical original assertionRefs object, preserved verbatim; retired objects are not active execution evidence or replacement coverage:\n{\n \"file\": \"mobile/src/transport/mobile-relay-rpc-session-liveness.test.ts\",\n \"assertions\": [\n \"keeps availability returns tolerant while a proven resume bypasses recent probes\",\n \"probes an idle relay and closes it when the probes go unanswered\",\n \"keeps an idle relay open while a slow reply is still downloading\",\n \"closes a silent relay even while requests keep going unanswered\",\n \"keeps an idle relay open while a stream backlog delays the probe reply\"\n ]\n }", + "Audit2003 historical original assertionRefs object, preserved verbatim; retired objects are not active execution evidence or replacement coverage:\n{\n \"file\": \"mobile/src/transport/stable-logical-rpc-client.test.ts\",\n \"assertions\": [\n \"makes before break, rejects in-flight work, and replays subscriptions\",\n \"keeps replies that commit before cutover and carries viewport state into replay\",\n \"suspends one physical session and replays subscriptions on foreground replacement\",\n \"reports requests the suspended session never sent as suspended, and written ones as ambiguous\",\n \"preserves delivery ambiguity without replaying a mutation after relay replacement\",\n \"lets the physical close settle in-flight requests on close, keeping pre-write failures definite\",\n \"preserves a committed response when logical close wins the callback race\",\n \"publishes the replacement dial phases while the client is suspended\",\n \"forwards a reconnecting phase published by the dialing session itself\",\n \"names a relay dial started from reconnecting without disturbing the state\",\n \"publishes recovery-path changes and keeps Relay pending between failed dials\",\n \"does not revive a stale recovery path after a connection later drops\",\n \"drops the pending path when the previous session recovers mid-dial\",\n \"never downgrades a live session while a make-before-break replacement dials\",\n \"restores disconnected when a dial it was narrating fails\",\n \"outlives the flat bound when the relay cell holds the dial\",\n \"closes a replacement that fails authentication and preserves the active session\",\n \"drops a stream event that lands after the caller unsubscribed\"\n ]\n }", + "Audit2003 historical original coveredPlatforms object, preserved verbatim; retired objects are not active execution evidence or replacement coverage:\n[\"macos\", \"linux\"]", + "Audit2003 historical original coveredProviders object, preserved verbatim; retired objects are not active execution evidence or replacement coverage:\n[\"lan\", \"tailscale\", \"cloud-relay\"]", + "Audit2003 historical original evidenceRuns object, preserved verbatim; retired objects are not active execution evidence or replacement coverage:\n{\n \"date\": \"2026-11-04\",\n \"runner\": \"local\",\n \"platform\": \"linux\",\n \"result\": \"passed\",\n \"command\": \"cd mobile && ORCA_BACKGROUND_LAUNCH=1 node /home/neil/orca/workspaces/orca/comprehensive-test-audit-10/build/audits/audit1352-combined-low-value-tests/validation/mobile/node_modules/vitest/vitest.mjs run --config vitest.config.ts src/transport/client-context.test.ts src/transport/host-open-recovery.test.tsx src/transport/host-open-retry-scheduler.test.ts src/transport/mobile-endpoint-nudge-router.test.ts src/transport/mobile-relay-e2ee-link.test.ts src/transport/mobile-relay-rpc-session-liveness.test.ts src/transport/mobile-relay-rpc-session.test.ts src/transport/rpc-session-liveness-integration.test.ts src/transport/rpc-session-liveness-watchdog.test.ts src/transport/stable-logical-rpc-client.test.ts --maxWorkers=2\",\n \"currentCommand\": \"pnpm --dir mobile exec vitest run --root .. mobile/src/transport/client-context.test.ts mobile/src/transport/host-open-recovery.test.tsx mobile/src/transport/host-open-retry-scheduler.test.ts mobile/src/transport/rpc-session-liveness-watchdog.test.ts mobile/src/transport/rpc-session-liveness-integration.test.ts mobile/src/transport/mobile-relay-rpc-session.test.ts mobile/src/transport/mobile-relay-rpc-session-liveness.test.ts mobile/src/transport/mobile-endpoint-nudge-router.test.ts mobile/src/transport/mobile-relay-e2ee-link.test.ts mobile/src/transport/stable-logical-rpc-client.test.ts\",\n \"durationSeconds\": 2.65,\n \"summary\": \"Test Files 10 passed (10); Tests 80 passed (80). Focused Linux unit execution; no physical-provider, p95 or cross-platform claim. Source head b638e07d395b76af3ba6b03761a1a0fe7e5e026e.\"\n }", + "Audit2003 historical original evidenceRuns object, preserved verbatim; retired objects are not active execution evidence or replacement coverage:\n{\n \"date\": \"2026-11-04\",\n \"runner\": \"local\",\n \"platform\": \"linux\",\n \"result\": \"passed\",\n \"command\": \"cd mobile && ORCA_BACKGROUND_LAUNCH=1 node /home/neil/orca/workspaces/orca/comprehensive-test-audit-10/build/audits/audit1360-combined-low-value-tests/validation/mobile/node_modules/vitest/vitest.mjs run --config vitest.config.ts src/transport/client-context.test.ts src/transport/host-open-recovery.test.tsx src/transport/host-open-retry-scheduler.test.ts src/transport/mobile-endpoint-nudge-router.test.ts src/transport/mobile-relay-e2ee-link.test.ts src/transport/mobile-relay-rpc-session-liveness.test.ts src/transport/mobile-relay-rpc-session.test.ts src/transport/rpc-session-liveness-integration.test.ts src/transport/rpc-session-liveness-watchdog.test.ts src/transport/stable-logical-rpc-client.test.ts --maxWorkers=2\",\n \"currentCommand\": \"pnpm --dir mobile exec vitest run --root .. mobile/src/transport/client-context.test.ts mobile/src/transport/host-open-recovery.test.tsx mobile/src/transport/host-open-retry-scheduler.test.ts mobile/src/transport/rpc-session-liveness-watchdog.test.ts mobile/src/transport/rpc-session-liveness-integration.test.ts mobile/src/transport/mobile-relay-rpc-session.test.ts mobile/src/transport/mobile-relay-rpc-session-liveness.test.ts mobile/src/transport/mobile-endpoint-nudge-router.test.ts mobile/src/transport/mobile-relay-e2ee-link.test.ts mobile/src/transport/stable-logical-rpc-client.test.ts\",\n \"durationSeconds\": 2.67,\n \"summary\": \"Test Files 10 passed (10); Tests 80 passed (80). Focused Linux unit execution; no physical-provider, p95 or cross-platform claim. Source head 415c9cffc2d10024131ec4b6721767a22195de6d.\"\n }", + "No test in the commands covers the decisive on-demand Relay probe that runs while a dictation reply is overdue (rpc-liveness-probe-while-waiting.ts)." ], "demotionRule": "Keep experimental or demote if a wanted host can park without retry, stale work can publish or notify, disconnect or forget resurrects a host, direct closes before three fair misses, Relay idle-probes within 15s of authenticated traffic, probes on demand while replies arrive on time, survives two fair misses, authenticated traffic fails to restore health, timers leak across identity replacement, mutations replay after ambiguous delivery, or physical-device radio cost exceeds the measured budget." }, @@ -3890,13 +3702,13 @@ "providers": ["local", "ssh"], "coveredPlatforms": ["macos"], "coveredProviders": [], - "coverageNotes": "Audit1645 removes low-value test projections, mock wiring, private-state assertions, inventories and duplicate examples without equivalent replacement coverage. Per-source reviews record retained public behavior and refusal boundaries with injected/native/remote limits. Affected historical receipts are retired; gates without active current evidence are downgraded. Historical scope before this audit: Audit1412 changes referenced sources. Retained unchanged-source receipts apply only to their unchanged commands; the reduced selectors have no new execution result. Historical coverage account (not a current claim for changed sources): Local deterministic evidence covers git-common classification, desktop watcher debounce counts, non-overlapping poller semantics, macOS native-watch fallback, paired-runtime publication to two independent clients with reconnect and stale-cache checks, and process generation-scope rollover across sibling targets. A headed macOS Electron host and separate paired Electron client both rendered a worktree created through the real Git binary and watcher entry path without reconnect. Linux/Windows desktop clients, multi-target live fanout, and WSL runs remain gaps. Audit1309: src/main/ipc/worktrees/creation/worktree-head-identity-reader.test.ts: Keep real filesystem HEAD/loose/packed/relative metadata reads and traversal refusal. Remove invalid-oid/nonstandard-layout characterization. Lose malformed object-id and bare-dir omission alarms; no Git-produced layout claim. Reduced selectors require fresh validation; prior full-suite receipts do not validate removed or changed coverage. No equivalent replacement or physical-provider claim. Audit1381 removes the two mock-forwarding registrations in src/renderer/src/components/right-sidebar/source-control/sync/git-status-push-signal-refresh.test.ts. Sibling attribution suppression and missing/unknown attribution refresh callback alarms are intentionally lost; no equivalent replacement or real hook lifecycle/status-update coverage is claimed. Remaining exact receipts, platform/provider scope, and budgets remain limited to their unchanged selectors; no new execution result is claimed. Audit1412: src/main/ssh/ssh-provider-authority.test.ts removed. Loses real AbortController revocation/target isolation/reentrancy outcomes, recreated-target generation-floor/session rollover behavior, stale mutation refusal and settled-registration cleanup alarms. These useful alarms are deliberately lost; no equivalent cancellation coverage is claimed. No equivalent replacement coverage is claimed.", + "coverageNotes": "Audit2003: Remove the standalone one-client shared-runtime/headless-publication example. The retained two-client callback (real WebSocket/runtime coordination with supplied Git listings and watcher notifications) and invalidated scan/reconnect races keep their exact owning scope, without replacement-equivalence or rendered-headless proof. Retire the entire dated38-case receipt through its effective currentCommand. Preserve the unrelated SSH bridge receipt and paired Electron receipt within their original commands. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, physical provider/PTY, rendered, live remote/mobile or cross-platform result is claimed. Historical prior account (preserved verbatim): Audit1645 removes low-value test projections, mock wiring, private-state assertions, inventories and duplicate examples without equivalent replacement coverage. Per-source reviews record retained public behavior and refusal boundaries with injected/native/remote limits. Affected historical receipts are retired; gates without active current evidence are downgraded. Historical scope before this audit: Audit1412 changes referenced sources. Retained unchanged-source receipts apply only to their unchanged commands; the reduced selectors have no new execution result. Historical coverage account (not a current claim for changed sources): Local deterministic evidence covers git-common classification, desktop watcher debounce counts, non-overlapping poller semantics, macOS native-watch fallback, paired-runtime publication to two independent clients with reconnect and stale-cache checks, and process generation-scope rollover across sibling targets. A headed macOS Electron host and separate paired Electron client both rendered a worktree created through the real Git binary and watcher entry path without reconnect. Linux/Windows desktop clients, multi-target live fanout, and WSL runs remain gaps. Audit1309: src/main/ipc/worktrees/creation/worktree-head-identity-reader.test.ts: Keep real filesystem HEAD/loose/packed/relative metadata reads and traversal refusal. Remove invalid-oid/nonstandard-layout characterization. Lose malformed object-id and bare-dir omission alarms; no Git-produced layout claim. Reduced selectors require fresh validation; prior full-suite receipts do not validate removed or changed coverage. No equivalent replacement or physical-provider claim. Audit1381 removes the two mock-forwarding registrations in src/renderer/src/components/right-sidebar/source-control/sync/git-status-push-signal-refresh.test.ts. Sibling attribution suppression and missing/unknown attribution refresh callback alarms are intentionally lost; no equivalent replacement or real hook lifecycle/status-update coverage is claimed. Remaining exact receipts, platform/provider scope, and budgets remain limited to their unchanged selectors; no new execution result is claimed. Audit1412: src/main/ssh/ssh-provider-authority.test.ts removed. Loses real AbortController revocation/target isolation/reentrancy outcomes, recreated-target generation-floor/session rollover behavior, stale mutation refusal and settled-registration cleanup alarms. These useful alarms are deliberately lost; no equivalent cancellation coverage is claimed. No equivalent replacement coverage is claimed.", "motivatingLinks": [ "https://github.com/stablyai/orca/pull/7086", "https://github.com/stablyai/orca/issues/16477" ], "invariant": "Index-only Git activity below the common Git directory must not emit worktrees:changed, invalidate worktree caches, or trigger fetchWorktrees fanout; structural add/remove/HEAD/gitdir/locked/config.worktree changes must still refresh worktrees and nudge Source Control. When the authoritative paired host accepts an external structural change for a uniquely identified server-local repo, each live client for that host must receive one existing host-scoped worktreesChanged invalidation and converge to the same repo/worktree identity without reconnect; duplicate watcher events must coalesce, stale cached or in-flight catalogs must not survive invalidation, and event-ignoring clients must remain compatible. Colliding local/SSH repo IDs must not publish the host-blind event until existing semantics can carry owner identity. External head moves (commit, amend, reset) must reach background worktree rows through spawn-free metadata reads, never through structural fanout. A process generation-scope rollover revokes every direct SSH target and old-scope provider request, not only the target whose counter exhausted.", - "oracle": "The retained unchanged-source receipts are the only current execution evidence; reduced-source assertions await validation. Historical oracle account (not a current claim for changed sources): Classify exact git-common paths as structural, status-only, or ignored; count notifications from debounced watcher events; force the Linux/Windows poll path to emit allowlisted leaf events, detect linked HEAD rewrites independent of entry-directory mtime, and surface in-place index rewrites via the backstop re-stat; diff head identities from metadata-file reads and notify only real head moves. Join the real runtime RPC server to two independently authenticated E2EE clients plus an event-ignoring client, advance the host Git inventory, trigger duplicate structural watcher callbacks, and require one host renderer notification plus one runtime event and a refreshed catalog per subscribed client; require the event-ignoring client to refresh safely on explicit read. Hold a scan after it captures old inventory, join it from a second paired client, publish the watcher invalidation, advance Git independently, and require both overlapping RPCs to retry or join one fresh scan before returning. Disconnect one client, advance inventory again, and require reconnect to read current state without stale event replay. Give a watcher a local repo ID colliding with an SSH owner and require the host renderer refresh while the unqualified runtime event stays absent. Force the real path in isolated Electron apps with git worktree add, a host Git inventory assertion, store-subscription barriers, matching host/client worktree identity, and visible rows in both renderers. For direct SSH, roll one exhausted target into a fresh process generation scope while invalidating sibling target tokens, and fill a partial pushed authority before clearing that target's PTY bindings once. Audit1381 removes the two mock-forwarding registrations in src/renderer/src/components/right-sidebar/source-control/sync/git-status-push-signal-refresh.test.ts. Sibling attribution suppression and missing/unknown attribution refresh callback alarms are intentionally lost; no equivalent replacement or real hook lifecycle/status-update coverage is claimed. Remaining exact receipts, platform/provider scope, and budgets remain limited to their unchanged selectors; no new execution result is claimed.", + "oracle": "Audit2003: Remove the standalone one-client shared-runtime/headless-publication example. The retained two-client callback (real WebSocket/runtime coordination with supplied Git listings and watcher notifications) and invalidated scan/reconnect races keep their exact owning scope, without replacement-equivalence or rendered-headless proof. Retire the entire dated38-case receipt through its effective currentCommand. Preserve the unrelated SSH bridge receipt and paired Electron receipt within their original commands. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, physical provider/PTY, rendered, live remote/mobile or cross-platform result is claimed. Historical prior account (preserved verbatim): The retained unchanged-source receipts are the only current execution evidence; reduced-source assertions await validation. Historical oracle account (not a current claim for changed sources): Classify exact git-common paths as structural, status-only, or ignored; count notifications from debounced watcher events; force the Linux/Windows poll path to emit allowlisted leaf events, detect linked HEAD rewrites independent of entry-directory mtime, and surface in-place index rewrites via the backstop re-stat; diff head identities from metadata-file reads and notify only real head moves. Join the real runtime RPC server to two independently authenticated E2EE clients plus an event-ignoring client, advance the host Git inventory, trigger duplicate structural watcher callbacks, and require one host renderer notification plus one runtime event and a refreshed catalog per subscribed client; require the event-ignoring client to refresh safely on explicit read. Hold a scan after it captures old inventory, join it from a second paired client, publish the watcher invalidation, advance Git independently, and require both overlapping RPCs to retry or join one fresh scan before returning. Disconnect one client, advance inventory again, and require reconnect to read current state without stale event replay. Give a watcher a local repo ID colliding with an SSH owner and require the host renderer refresh while the unqualified runtime event stays absent. Force the real path in isolated Electron apps with git worktree add, a host Git inventory assertion, store-subscription barriers, matching host/client worktree identity, and visible rows in both renderers. For direct SSH, roll one exhausted target into a fresh process generation scope while invalidating sibling target tokens, and fill a partial pushed authority before clearing that target's PTY bindings once. Audit1381 removes the two mock-forwarding registrations in src/renderer/src/components/right-sidebar/source-control/sync/git-status-push-signal-refresh.test.ts. Sibling attribution suppression and missing/unknown attribution refresh callback alarms are intentionally lost; no equivalent replacement or real hook lifecycle/status-update coverage is claimed. Remaining exact receipts, platform/provider scope, and budgets remain limited to their unchanged selectors; no new execution result is claimed.", "commands": [ "pnpm exec vitest run --config config/vitest.config.ts src/main/ipc/worktrees/creation/worktree-base-directory-event-filter.test.ts src/main/ipc/worktrees/creation/worktree-head-identity-reader.test.ts", "pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/workspaces/catalog/external-worktree-paired-client-discovery.integration.test.ts src/main/window/attach-main-window-services.test.ts", @@ -3934,8 +3746,7 @@ "host inventory and both paired catalogs advance to the same worktree identity after runtime cache invalidation", "an invalidated in-flight host scan and a second client's joined read converge through one fresh retry before either can return stale data", "a disconnected client reconnects to current catalog state without replaying a stale invalidation", - "an event-ignoring client remains compatible, and colliding local/SSH owners do not publish an owner-blind event", - "the shared runtime publication remains valid without a headed renderer notifier" + "an event-ignoring client remains compatible, and colliding local/SSH owners do not publish an owner-blind event" ] }, { @@ -3970,16 +3781,6 @@ "durationSeconds": 1.21, "summary": "The direct SSH state-bridge wiring file passed with 6 tests after the reconnect coordinator, scheduler and telemetry were deleted." }, - { - "date": "2026-08-26", - "runner": "local", - "platform": "macos", - "command": "pnpm exec vitest run --config config/vitest.config.ts src/main/ipc/watched-worktree-catalog-notification.test.ts src/main/runtime/external-worktree-paired-client-discovery.integration.test.ts src/main/window/attach-main-window-services.test.ts", - "currentCommand": "pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/workspaces/catalog/external-worktree-paired-client-discovery.integration.test.ts src/main/window/attach-main-window-services.test.ts", - "result": "passed", - "durationSeconds": 6.19, - "summary": "Three watcher/runtime/wiring files passed with 38 tests, including two-client publication, reconnect, stale scan fencing, headless parity, owner-collision suppression, failure isolation, and main-window notifier wiring. Historical run: the watched-worktree-catalog-notification suite was later removed as mock forwarding coverage; it is not an active gate or replacement-equivalence claim." - }, { "date": "2026-08-26", "runner": "local", @@ -4027,7 +3828,10 @@ "Audit1412: src/main/ssh/ssh-provider-authority.test.ts removed. Loses real AbortController revocation/target isolation/reentrancy outcomes, recreated-target generation-floor/session rollover behavior, stale mutation refusal and settled-registration cleanup alarms. These useful alarms are deliberately lost; no equivalent cancellation coverage is claimed. No equivalent replacement coverage is claimed.", "Audit1412 retires this complete dated receipt because a source in its selector was deleted or reduced. It does not validate the reduced selector. Original receipt verbatim:\n{\n \"date\": \"2026-07-27\",\n \"runner\": \"local\",\n \"platform\": \"macos\",\n \"command\": \"pnpm exec vitest run --config config/vitest.config.ts src/main/ssh/connection/ssh-connection-generation.test.ts src/main/ssh/ssh-provider-authority.test.ts --reporter=dot\",\n \"result\": \"passed\",\n \"durationSeconds\": 1.61,\n \"summary\": \"Three main-process catalog and authority files passed with 118 tests, including contradictory catalog provenance rejection and all-target generation-scope revocation. The third file, src/main/ipc/repos-remote.test.ts, was later deleted with the SSH repo IPC handlers.\"\n }", "Audit1412 historical platform/provider scope before source changes: {\"coveredPlatforms\":[\"macos\"],\"coveredProviders\":[\"local\",\"ssh\"]}. Remaining provider scope applies only to unchanged-source receipts.", - "Audit1412 historical assertion inventory for deleted/reduced sources; not current validated assertions:\n[\n {\n \"file\": \"src/main/ssh/ssh-provider-authority.test.ts\",\n \"assertions\": [\n \"generation-scope rollover invalidates every target authority before abort callbacks run\",\n \"every registered old-scope provider request aborts exactly once\"\n ]\n }\n]" + "Audit1412 historical assertion inventory for deleted/reduced sources; not current validated assertions:\n[\n {\n \"file\": \"src/main/ssh/ssh-provider-authority.test.ts\",\n \"assertions\": [\n \"generation-scope rollover invalidates every target authority before abort callbacks run\",\n \"every registered old-scope provider request aborts exactly once\"\n ]\n }\n]", + "Audit2003: Remove the standalone one-client shared-runtime/headless-publication example. The retained two-client callback (real WebSocket/runtime coordination with supplied Git listings and watcher notifications) and invalidated scan/reconnect races keep their exact owning scope, without replacement-equivalence or rendered-headless proof. Retire the entire dated38-case receipt through its effective currentCommand. Preserve the unrelated SSH bridge receipt and paired Electron receipt within their original commands. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, physical provider/PTY, rendered, live remote/mobile or cross-platform result is claimed.", + "Audit2003 historical original assertionRefs object, preserved verbatim; retired objects are not active execution evidence or replacement coverage:\n{\n \"file\": \"src/main/runtime/workspaces/catalog/external-worktree-paired-client-discovery.integration.test.ts\",\n \"assertions\": [\n \"duplicate host watcher callbacks coalesce to one local notification and one worktreesChanged event for each of two live paired clients\",\n \"host inventory and both paired catalogs advance to the same worktree identity after runtime cache invalidation\",\n \"an invalidated in-flight host scan and a second client's joined read converge through one fresh retry before either can return stale data\",\n \"a disconnected client reconnects to current catalog state without replaying a stale invalidation\",\n \"an event-ignoring client remains compatible, and colliding local/SSH owners do not publish an owner-blind event\",\n \"the shared runtime publication remains valid without a headed renderer notifier\"\n ]\n }", + "Audit2003 historical original evidenceRuns object, preserved verbatim; retired objects are not active execution evidence or replacement coverage:\n{\n \"date\": \"2026-08-26\",\n \"runner\": \"local\",\n \"platform\": \"macos\",\n \"command\": \"pnpm exec vitest run --config config/vitest.config.ts src/main/ipc/watched-worktree-catalog-notification.test.ts src/main/runtime/external-worktree-paired-client-discovery.integration.test.ts src/main/window/attach-main-window-services.test.ts\",\n \"currentCommand\": \"pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/workspaces/catalog/external-worktree-paired-client-discovery.integration.test.ts src/main/window/attach-main-window-services.test.ts\",\n \"result\": \"passed\",\n \"durationSeconds\": 6.19,\n \"summary\": \"Three watcher/runtime/wiring files passed with 38 tests, including two-client publication, reconnect, stale scan fencing, headless parity, owner-collision suppression, failure isolation, and main-window notifier wiring. Historical run: the watched-worktree-catalog-notification suite was later removed as mock forwarding coverage; it is not an active gate or replacement-equivalence claim.\"\n }" ], "demotionRule": "Keep experimental or demote if the focused gate flakes without a product or harness bug, if index-only churn can emit worktrees:changed, or if structural add/remove/HEAD/lock changes fail to converge." }, @@ -7853,7 +7657,7 @@ "providers": ["local", "daemon", "ssh", "wsl", "remote-runtime"], "coveredPlatforms": ["macos"], "coveredProviders": ["local", "daemon", "ssh", "wsl", "remote-runtime"], - "coverageNotes": "Audit1983: Delete the mocked agent-session suite and its prospective selector and aliases. Retire all3 whole dated changed-selector receipts, preserving their original raw objects in historical gaps without transferring counts or durations. Preserve5 unrelated receipts. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, provider/PTY, rendered, live remote/mobile or cross-platform result is claimed. Historical prior account (preserved verbatim): Audit1931 (sources1928/1929): Partial agent-operation service suite retains only concurrent retry joining with changed-fingerprint conflict. Retire its complete three-title assertion object: old-owner legacy fallback before side effects, nested SSH Pi transcript refusal and completed/ambiguous physical-commit replay fences are no longer backed. Other probe/launch option/ledger/failure/remote-inventory examples disappear as detailed below. No actual provider spawn, live SSH or physical commit proof is retained by this source. Preserve meaningful remaining selectors/assertions and unchanged dated receipts within their original scope. Reduced selectors have no fresh gate execution evidence in this metadata packet. No equivalent replacement, physical watcher/PTY/provider, live SSH/remote/mobile, rendered, cross-platform, soak or measured-budget result is claimed. Historical prior account (preserved verbatim): Audit1911: The complete provider PTY-inspection mock suite is removed: missing-provider rejection, stale-PTY renderer unverifiable mapping, preserved supplied renderer failures, and supplied transport-loss mapping alarms are intentionally lost. No actual terminal teardown, daemon error or remote transport was exercised. Only the deleted explicit source tokens, testFile and whole assertionRef are removed. Meaningful remaining source selectors/assertions and unaffected dated receipts retain their original scope. No equivalent replacement, fresh reduced-gate, native/platform/provider, soak or measured-budget result is claimed. Historical prior account (preserved verbatim): Audit1627 removes low-value mocked daemon projections, scalar tables and duplicate terminal examples without equivalent replacement coverage. Per-source reviews record retained terminal content preservation, process ownership refusal and recovery boundaries together with injected/native/remote limits. Affected historical receipts are retired; gates without active current evidence are downgraded. Historical scope before this audit: Audit1623 removes low-value mocked daemon projections, scalar tables and duplicate terminal examples without equivalent replacement coverage. Per-source reviews record retained terminal content preservation, process ownership refusal and recovery boundaries together with injected/native/remote limits. Affected historical receipts are retired; gates without active current evidence are downgraded. Historical scope before this audit: Audit1495 intentionally deletes the orphan-owner suite: explicit owner/host predicate examples are retired; no equivalent replacement coverage or fresh result is claimed. Retained exact unchanged-source receipts remain the only current execution evidence. Reduced selectors have no fresh execution result. Audit1417 intentionally deletes src/renderer/src/components/terminal-pane/agent-completion-coordinator-pending-title-inspection.test.ts: Loses obsolete pending title process races, inspection failure suppression, explicit completion precedence and delayed remote inspection alarms. No equivalent replacement coverage or fresh result is claimed. Historical coverage account: Audit1342 changes referenced sources. Retained unchanged-source receipts apply only to their unchanged commands; the reduced selectors have no new execution result. Historical coverage account (not a current claim for changed sources): Audits1301 and1308 changes referenced sources. Retained unchanged-source receipts apply only to their unchanged commands; the reduced selectors have no new execution result. Historical coverage account (not a current claim for changed sources): Deterministic macOS tests cover controller claims, daemon and SSH/relay operation replay, mixed-version selection, runtime ownership, exact provisional handoff, durable terminal retirement, two independent viewer mirrors, guarded adoption of legacy live PTYs, and completion classification when either the outer remote transport or authoritative host/provider process inspection becomes unreachable. The adoption harness models v1.4.150 agent/setup/shell tabs, current-generation restart and reconnect, exact handle/incarnation/worktree/host checks, topology CAS, competing clients, split-pane/group restoration, WSL ownership, and SSH owner rejection. The secondary parity repro runs independent clients against one headless remote Orca runtime over encrypted pairing and a real daemon-backed PTY, with tokened fixture-process identity separated from unrelated Codex app-server startup probes. The automated primary topology runs an isolated headed macOS Orca desktop server plus a separate paired web client and proves viewer-local fresh/resume focus, exact legacy placement, writable PTYs, unrelated-terminal survival, and host/client cleanup. SSH coverage is provider/relay contract and fault-injection coverage only; it does not substitute for paired-server coverage. Live Windows, Linux, WSL, SSH, and physical paired-Linux hosts remain gaps. The isolated intent-owner ledger suite was removed. web-runtime-session.test.ts retains exact post-create handoff confirmation only; its previous fresh/resume presentation and publication-replay assertions are no longer current references. Those direct owner-partition, cap and RPC/focus alarms are lost; other agent-session evidence does not establish equivalence. Structured handoff and adopted-leaf focus references now point to their dedicated surviving fixtures. Mixed-version recovery retains actual old-host recovery followed by a new CLI tab; its unsupported/missing-evidence tables are removed. Orphan recovery retains in-flight adoption-conflict serialization only; previous topology/pruning and absence/adoption examples are removed. These removed projection, capability and adoption-shape alarms are lost; retained cases do not replace them. Audit1301: src/renderer/src/runtime/remote-runtime-session-tabs-inflight.test.ts reduced. Direct dedupe eviction/count and equal receipt-position sample loses authored alarm. No equivalent replacement coverage is claimed. Audit1308: src/renderer/src/runtime/web-session-tabs-sync-agent-handoff.test.ts removed. Exact provisional replacement and post-create exit confirmation handoff pipeline alarms disappear. No equivalent replacement coverage is claimed. Audit1313: src/main/runtime/terminal-orphan-topology.test.ts: Retain actual topology merge algorithms preserving disjoint subtrees and grafted/ambiguous shared anchors with independent expected trees. Remove duplicate leaf validator and empty/no-change passthrough examples. Lose basic validation and no-group/noop characterization; no renderer layout proof. Reduced selectors require fresh validation; prior full-suite receipts do not validate removed or changed coverage. No equivalent replacement or physical-provider claim. Audit1342: src/renderer/src/components/terminal-pane/pty-connection-completion-and-notification.test.ts reduced. Driver registration/import of a whole-deleted selected spec is removed; directly authored cases remain unchanged. No extra authored removal credit. No equivalent replacement coverage is claimed. Audit1383: src/main/runtime/orca-runtime-tests/worktree-native-chat-setup.spec.ts reduced. Lose command-list/receipt/default/background activation matrices whose provider spawn returns canned IDs. Retained test proves actual tab allocation separation, not terminal startup. No equivalent replacement coverage is claimed. Existing exact unaffected receipts and their platform/provider/budget claims remain scoped to unchanged selectors. Changed optional selectors have no new execution result. Audit1401: src/main/runtime/orca-runtime-tests/mobile-session-tabs-duplicate-pty-repair.spec.ts removed. Loses listing-on-already-repaired-data alarm; it never established runtime repair invocation. src/main/runtime/orca-runtime-tests/mobile-session-tabs-split-agent-tab.spec.ts removed. Loses actual mobile split sibling shell versus recorded-agent resume command alarms; no equivalent coverage asserted. src/main/runtime/orca-runtime-tests/worktree-native-chat-setup.spec.ts removed. Loses separate setup/startup tab result in that manually assembled path. src/main/runtime/orca-runtime.test.ts reduced. Driver registration/import of a whole-deleted selected spec is removed; directly authored cases remain unchanged. No extra authored removal credit. No equivalent replacement coverage is claimed. Existing exact unaffected receipts remain unchanged and apply only to their unchanged selectors. Changed optional selectors have no new execution result.", + "coverageNotes": "Audit2003: Delete the complete pure orphan-topology suite and its file membership; preserve the other selectors from its combined command after archiving the original command string. Lost alarms are synthetic current-layout preservation, one-anchor grafting and ambiguous-anchor appending examples. Do not transfer their ownership/topology scope to sibling suites. Preserve all5 independent historical receipts exactly. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, physical provider/PTY, rendered, live remote/mobile or cross-platform result is claimed. Historical prior account (preserved verbatim): Audit1983: Delete the mocked agent-session suite and its prospective selector and aliases. Retire all3 whole dated changed-selector receipts, preserving their original raw objects in historical gaps without transferring counts or durations. Preserve5 unrelated receipts. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, provider/PTY, rendered, live remote/mobile or cross-platform result is claimed. Historical prior account (preserved verbatim): Audit1931 (sources1928/1929): Partial agent-operation service suite retains only concurrent retry joining with changed-fingerprint conflict. Retire its complete three-title assertion object: old-owner legacy fallback before side effects, nested SSH Pi transcript refusal and completed/ambiguous physical-commit replay fences are no longer backed. Other probe/launch option/ledger/failure/remote-inventory examples disappear as detailed below. No actual provider spawn, live SSH or physical commit proof is retained by this source. Preserve meaningful remaining selectors/assertions and unchanged dated receipts within their original scope. Reduced selectors have no fresh gate execution evidence in this metadata packet. No equivalent replacement, physical watcher/PTY/provider, live SSH/remote/mobile, rendered, cross-platform, soak or measured-budget result is claimed. Historical prior account (preserved verbatim): Audit1911: The complete provider PTY-inspection mock suite is removed: missing-provider rejection, stale-PTY renderer unverifiable mapping, preserved supplied renderer failures, and supplied transport-loss mapping alarms are intentionally lost. No actual terminal teardown, daemon error or remote transport was exercised. Only the deleted explicit source tokens, testFile and whole assertionRef are removed. Meaningful remaining source selectors/assertions and unaffected dated receipts retain their original scope. No equivalent replacement, fresh reduced-gate, native/platform/provider, soak or measured-budget result is claimed. Historical prior account (preserved verbatim): Audit1627 removes low-value mocked daemon projections, scalar tables and duplicate terminal examples without equivalent replacement coverage. Per-source reviews record retained terminal content preservation, process ownership refusal and recovery boundaries together with injected/native/remote limits. Affected historical receipts are retired; gates without active current evidence are downgraded. Historical scope before this audit: Audit1623 removes low-value mocked daemon projections, scalar tables and duplicate terminal examples without equivalent replacement coverage. Per-source reviews record retained terminal content preservation, process ownership refusal and recovery boundaries together with injected/native/remote limits. Affected historical receipts are retired; gates without active current evidence are downgraded. Historical scope before this audit: Audit1495 intentionally deletes the orphan-owner suite: explicit owner/host predicate examples are retired; no equivalent replacement coverage or fresh result is claimed. Retained exact unchanged-source receipts remain the only current execution evidence. Reduced selectors have no fresh execution result. Audit1417 intentionally deletes src/renderer/src/components/terminal-pane/agent-completion-coordinator-pending-title-inspection.test.ts: Loses obsolete pending title process races, inspection failure suppression, explicit completion precedence and delayed remote inspection alarms. No equivalent replacement coverage or fresh result is claimed. Historical coverage account: Audit1342 changes referenced sources. Retained unchanged-source receipts apply only to their unchanged commands; the reduced selectors have no new execution result. Historical coverage account (not a current claim for changed sources): Audits1301 and1308 changes referenced sources. Retained unchanged-source receipts apply only to their unchanged commands; the reduced selectors have no new execution result. Historical coverage account (not a current claim for changed sources): Deterministic macOS tests cover controller claims, daemon and SSH/relay operation replay, mixed-version selection, runtime ownership, exact provisional handoff, durable terminal retirement, two independent viewer mirrors, guarded adoption of legacy live PTYs, and completion classification when either the outer remote transport or authoritative host/provider process inspection becomes unreachable. The adoption harness models v1.4.150 agent/setup/shell tabs, current-generation restart and reconnect, exact handle/incarnation/worktree/host checks, topology CAS, competing clients, split-pane/group restoration, WSL ownership, and SSH owner rejection. The secondary parity repro runs independent clients against one headless remote Orca runtime over encrypted pairing and a real daemon-backed PTY, with tokened fixture-process identity separated from unrelated Codex app-server startup probes. The automated primary topology runs an isolated headed macOS Orca desktop server plus a separate paired web client and proves viewer-local fresh/resume focus, exact legacy placement, writable PTYs, unrelated-terminal survival, and host/client cleanup. SSH coverage is provider/relay contract and fault-injection coverage only; it does not substitute for paired-server coverage. Live Windows, Linux, WSL, SSH, and physical paired-Linux hosts remain gaps. The isolated intent-owner ledger suite was removed. web-runtime-session.test.ts retains exact post-create handoff confirmation only; its previous fresh/resume presentation and publication-replay assertions are no longer current references. Those direct owner-partition, cap and RPC/focus alarms are lost; other agent-session evidence does not establish equivalence. Structured handoff and adopted-leaf focus references now point to their dedicated surviving fixtures. Mixed-version recovery retains actual old-host recovery followed by a new CLI tab; its unsupported/missing-evidence tables are removed. Orphan recovery retains in-flight adoption-conflict serialization only; previous topology/pruning and absence/adoption examples are removed. These removed projection, capability and adoption-shape alarms are lost; retained cases do not replace them. Audit1301: src/renderer/src/runtime/remote-runtime-session-tabs-inflight.test.ts reduced. Direct dedupe eviction/count and equal receipt-position sample loses authored alarm. No equivalent replacement coverage is claimed. Audit1308: src/renderer/src/runtime/web-session-tabs-sync-agent-handoff.test.ts removed. Exact provisional replacement and post-create exit confirmation handoff pipeline alarms disappear. No equivalent replacement coverage is claimed. Audit1313: src/main/runtime/terminal-orphan-topology.test.ts: Retain actual topology merge algorithms preserving disjoint subtrees and grafted/ambiguous shared anchors with independent expected trees. Remove duplicate leaf validator and empty/no-change passthrough examples. Lose basic validation and no-group/noop characterization; no renderer layout proof. Reduced selectors require fresh validation; prior full-suite receipts do not validate removed or changed coverage. No equivalent replacement or physical-provider claim. Audit1342: src/renderer/src/components/terminal-pane/pty-connection-completion-and-notification.test.ts reduced. Driver registration/import of a whole-deleted selected spec is removed; directly authored cases remain unchanged. No extra authored removal credit. No equivalent replacement coverage is claimed. Audit1383: src/main/runtime/orca-runtime-tests/worktree-native-chat-setup.spec.ts reduced. Lose command-list/receipt/default/background activation matrices whose provider spawn returns canned IDs. Retained test proves actual tab allocation separation, not terminal startup. No equivalent replacement coverage is claimed. Existing exact unaffected receipts and their platform/provider/budget claims remain scoped to unchanged selectors. Changed optional selectors have no new execution result. Audit1401: src/main/runtime/orca-runtime-tests/mobile-session-tabs-duplicate-pty-repair.spec.ts removed. Loses listing-on-already-repaired-data alarm; it never established runtime repair invocation. src/main/runtime/orca-runtime-tests/mobile-session-tabs-split-agent-tab.spec.ts removed. Loses actual mobile split sibling shell versus recorded-agent resume command alarms; no equivalent coverage asserted. src/main/runtime/orca-runtime-tests/worktree-native-chat-setup.spec.ts removed. Loses separate setup/startup tab result in that manually assembled path. src/main/runtime/orca-runtime.test.ts reduced. Driver registration/import of a whole-deleted selected spec is removed; directly authored cases remain unchanged. No extra authored removal credit. No equivalent replacement coverage is claimed. Existing exact unaffected receipts remain unchanged and apply only to their unchanged selectors. Changed optional selectors have no new execution result.", "motivatingLinks": [ "https://github.com/stablyai/orca/issues/8878", "https://github.com/stablyai/orca/issues/9151", @@ -7863,7 +7667,7 @@ "https://github.com/stablyai/orca/pull/10193" ], "invariant": "For every claim-capable execution route, one provider-session identity has at most one live PTY owner and one canonical host surface across concurrent clients, retries, reconnects, and stale publications. For paired structured fresh and resume requests, the authenticated owning runtime creates in background without a renderer window; activate=true focuses the exact requested leaf only on the requesting viewer, while activate=false changes no viewer focus. A live orphan may be adopted only when the controller proves its exact handle and incarnation, its worktree and host owner match, no competing visual owner exists, and a host topology CAS wins. A viewer may classify completion only from successful host/provider inspection or explicit lifecycle evidence; transport, handle, or provider unavailability remains unknown and breaks any consecutive-idle proof. A physical exit retires that exact incarnation durably so stale client state and host restart cannot recreate it. Mixed-version routes select the unchanged legacy request before any authority side effect or execution-owner-local filesystem access.", - "oracle": "Audit1983: Delete the mocked agent-session suite and its prospective selector and aliases. Retire all3 whole dated changed-selector receipts, preserving their original raw objects in historical gaps without transferring counts or durations. Preserve5 unrelated receipts. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, provider/PTY, rendered, live remote/mobile or cross-platform result is claimed. Historical prior account (preserved verbatim): Audit1931 (sources1928/1929): Partial agent-operation service suite retains only concurrent retry joining with changed-fingerprint conflict. Retire its complete three-title assertion object: old-owner legacy fallback before side effects, nested SSH Pi transcript refusal and completed/ambiguous physical-commit replay fences are no longer backed. Other probe/launch option/ledger/failure/remote-inventory examples disappear as detailed below. No actual provider spawn, live SSH or physical commit proof is retained by this source. Preserve meaningful remaining selectors/assertions and unchanged dated receipts within their original scope. Reduced selectors have no fresh gate execution evidence in this metadata packet. No equivalent replacement, physical watcher/PTY/provider, live SSH/remote/mobile, rendered, cross-platform, soak or measured-budget result is claimed. Historical prior account (preserved verbatim): Audit1911: The complete provider PTY-inspection mock suite is removed: missing-provider rejection, stale-PTY renderer unverifiable mapping, preserved supplied renderer failures, and supplied transport-loss mapping alarms are intentionally lost. No actual terminal teardown, daemon error or remote transport was exercised. Only the deleted explicit source tokens, testFile and whole assertionRef are removed. Meaningful remaining source selectors/assertions and unaffected dated receipts retain their original scope. No equivalent replacement, fresh reduced-gate, native/platform/provider, soak or measured-budget result is claimed. Historical prior account (preserved verbatim): Retained exact unchanged-source receipts remain the only current execution evidence. Reduced selectors have no fresh execution result. Historical pre-audit oracle account for changed sources: Existing exact unchanged-selector receipts remain the only current execution evidence. Changed source selectors have no fresh execution result. Historical prior-source oracle account: The retained unchanged-source receipts are the only current execution evidence; reduced-source assertions await validation. Historical oracle account (not a current claim for changed sources): The retained unchanged-source receipts are the only current execution evidence; reduced-source assertions await validation. Historical oracle account (not a current claim for changed sources): Race independent clients and repeated operation IDs, then assert one physical spawn and one canonical PTY/surface; inject exit-before-reply, provider disconnect, conflicting claim scope, old daemon/relay capabilities, reused handles, stale incarnations, owner mismatch, and topology revision conflict; assert safe adoption or explicit failure without a second spawn or wrong-process attachment. Run fresh/resume with activate true/false against an isolated headed desktop host and a separate paired client, then against isolated headless serve: assert host presentation stays background, only the requesting viewer focuses the exact leaf, inactive calls preserve client/DOM focus, a same-version publication replay cannot lose focus intent, and sibling-first split publication cannot consume exact-leaf intent. Restore legacy split panes and groups beside a newer host-owned tab, preserving exact predecessor/new/successor order, output, input, resize, titles, tab/leaf identity, active group, and multi-client convergence. For completion, drive a known running agent through outer transport loss, authoritative provider rejection, reconnect, explicit stop, real exit status, and successful hook completion; assert unavailable evidence never dispatches completion and two fresh authoritative idle samples are required after the gap. After exact exit, assert terminal and tab listings omit the surface, a stale publication cannot restore it, restart cannot resurrect it, exact tokened fixture PIDs are dead, and unrelated tabs/processes survive until scoped cleanup.", + "oracle": "Audit2003: Delete the complete pure orphan-topology suite and its file membership; preserve the other selectors from its combined command after archiving the original command string. Lost alarms are synthetic current-layout preservation, one-anchor grafting and ambiguous-anchor appending examples. Do not transfer their ownership/topology scope to sibling suites. Preserve all5 independent historical receipts exactly. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, physical provider/PTY, rendered, live remote/mobile or cross-platform result is claimed. Historical prior account (preserved verbatim): Audit1983: Delete the mocked agent-session suite and its prospective selector and aliases. Retire all3 whole dated changed-selector receipts, preserving their original raw objects in historical gaps without transferring counts or durations. Preserve5 unrelated receipts. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, provider/PTY, rendered, live remote/mobile or cross-platform result is claimed. Historical prior account (preserved verbatim): Audit1931 (sources1928/1929): Partial agent-operation service suite retains only concurrent retry joining with changed-fingerprint conflict. Retire its complete three-title assertion object: old-owner legacy fallback before side effects, nested SSH Pi transcript refusal and completed/ambiguous physical-commit replay fences are no longer backed. Other probe/launch option/ledger/failure/remote-inventory examples disappear as detailed below. No actual provider spawn, live SSH or physical commit proof is retained by this source. Preserve meaningful remaining selectors/assertions and unchanged dated receipts within their original scope. Reduced selectors have no fresh gate execution evidence in this metadata packet. No equivalent replacement, physical watcher/PTY/provider, live SSH/remote/mobile, rendered, cross-platform, soak or measured-budget result is claimed. Historical prior account (preserved verbatim): Audit1911: The complete provider PTY-inspection mock suite is removed: missing-provider rejection, stale-PTY renderer unverifiable mapping, preserved supplied renderer failures, and supplied transport-loss mapping alarms are intentionally lost. No actual terminal teardown, daemon error or remote transport was exercised. Only the deleted explicit source tokens, testFile and whole assertionRef are removed. Meaningful remaining source selectors/assertions and unaffected dated receipts retain their original scope. No equivalent replacement, fresh reduced-gate, native/platform/provider, soak or measured-budget result is claimed. Historical prior account (preserved verbatim): Retained exact unchanged-source receipts remain the only current execution evidence. Reduced selectors have no fresh execution result. Historical pre-audit oracle account for changed sources: Existing exact unchanged-selector receipts remain the only current execution evidence. Changed source selectors have no fresh execution result. Historical prior-source oracle account: The retained unchanged-source receipts are the only current execution evidence; reduced-source assertions await validation. Historical oracle account (not a current claim for changed sources): The retained unchanged-source receipts are the only current execution evidence; reduced-source assertions await validation. Historical oracle account (not a current claim for changed sources): Race independent clients and repeated operation IDs, then assert one physical spawn and one canonical PTY/surface; inject exit-before-reply, provider disconnect, conflicting claim scope, old daemon/relay capabilities, reused handles, stale incarnations, owner mismatch, and topology revision conflict; assert safe adoption or explicit failure without a second spawn or wrong-process attachment. Run fresh/resume with activate true/false against an isolated headed desktop host and a separate paired client, then against isolated headless serve: assert host presentation stays background, only the requesting viewer focuses the exact leaf, inactive calls preserve client/DOM focus, a same-version publication replay cannot lose focus intent, and sibling-first split publication cannot consume exact-leaf intent. Restore legacy split panes and groups beside a newer host-owned tab, preserving exact predecessor/new/successor order, output, input, resize, titles, tab/leaf identity, active group, and multi-client convergence. For completion, drive a known running agent through outer transport loss, authoritative provider rejection, reconnect, explicit stop, real exit status, and successful hook completion; assert unavailable evidence never dispatches completion and two fresh authoritative idle samples are required after the gap. After exact exit, assert terminal and tab listings omit the surface, a stale publication cannot restore it, restart cannot resurrect it, exact tokened fixture PIDs are dead, and unrelated tabs/processes survive until scoped cleanup.", "commands": [ "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/runtime/web-runtime-session.test.ts src/renderer/src/runtime/web-session-tabs-sync-focus-intent.test.ts", "pnpm exec vitest run --config config/vitest.config.ts src/shared/claimed-agent-pty-owner.test.ts src/main/daemon/providers/adapter/testing/daemon-pty-adapter.test.ts src/main/runtime/orca-runtime-agent-session-operation.test.ts src/main/runtime/remote-agent-session-host-authority.integration.test.ts src/main/runtime/terminal/surface/orca-runtime-terminal-retirement.test.ts src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-host-session-launch.test.ts src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-create-handoff.test.ts src/renderer/src/runtime/remote-runtime-session-tabs-inflight.test.ts src/renderer/src/runtime/web-runtime-session.test.ts", @@ -7874,7 +7678,7 @@ "VITE_EXPOSE_STORE=true pnpm run build:web", "ORCA_E2E_WEB_CLIENT=1 SKIP_BUILD=1 pnpm exec playwright test tests/e2e/remote-agent-session-focus-authority.spec.ts --config tests/playwright.config.ts --project electron-headful --workers=1", "Manual headed paired-server journey: isolated Orca desktop host + separate paired web client + real Codex process + 20-second WebSocket fault + reconnect + explicit stop", - "pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/terminal/adoption/terminal-orphan-topology.test.ts src/renderer/src/runtime/hosted-terminals/recovery/web-session-terminal-orphan-recovery.test.ts src/renderer/src/runtime/hosted-terminals/recovery/web-session-terminal-orphan-mixed-version.test.ts --maxWorkers=1", + "pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/orca-runtime.test.ts src/renderer/src/runtime/hosted-terminals/recovery/web-session-terminal-orphan-recovery.test.ts src/renderer/src/runtime/hosted-terminals/recovery/web-session-terminal-orphan-mixed-version.test.ts --maxWorkers=1", "pnpm exec vitest run --config config/vitest.config.ts src/main/daemon/terminal-host/testing/terminal-host.test.ts src/main/daemon/providers/routing/testing/daemon-pty-router.test.ts src/main/daemon/providers/routing/testing/degraded-daemon-pty-provider.test.ts src/main/daemon/providers/adapter/testing/daemon-pty-adapter.test.ts src/main/runtime/orca-runtime.test.ts tests/e2e/remote-agent-completion-authority.unit.test.ts src/renderer/src/runtime/hosted-terminals/inspection/runtime-terminal-inspection.test.ts src/renderer/src/components/terminal-pane/agent-completion/agent-completion-coordinator-process-cadence.test.ts", "pnpm exec vitest run --config config/vitest.config.ts tests/e2e/remote-agent-completion-authority.unit.test.ts src/main/daemon/terminal-host/testing/terminal-host.test.ts src/main/daemon/providers/routing/testing/daemon-pty-router.test.ts src/main/daemon/providers/routing/testing/degraded-daemon-pty-provider.test.ts src/main/daemon/providers/adapter/testing/daemon-pty-adapter.test.ts src/renderer/src/runtime/hosted-terminals/inspection/runtime-terminal-inspection.test.ts src/renderer/src/components/terminal-pane/agent-completion/agent-completion-coordinator-process-cadence.test.ts src/renderer/src/components/terminal-pane/pty-connection/tests/pty-connection-completion-and-notification.test.ts src/renderer/src/lib/agent-session/accounts/codex/codex-session-restart.test.ts" ], @@ -7901,7 +7705,6 @@ "config/scripts/diagnostics/remote-agent-authority/remote-agent-session-authority-repro.mjs", "config/scripts/development/remote-session-cleanup/remote-agent-session-process-cleanup.mjs", "tests/e2e/remote-terminal-tab-retirement.unit.test.ts", - "src/main/runtime/terminal/adoption/terminal-orphan-topology.test.ts", "src/renderer/src/runtime/hosted-terminals/recovery/web-session-terminal-orphan-recovery.test.ts", "src/renderer/src/runtime/hosted-terminals/recovery/web-session-terminal-orphan-mixed-version.test.ts", "src/renderer/src/runtime/web-session-tabs-sync-focus-intent.test.ts" @@ -8110,7 +7913,10 @@ "Audit1983 historical original assertionRefs object, preserved verbatim; retired objects are not active execution evidence or replacement coverage:\n{\n \"file\": \"src/main/runtime/rpc/methods/terminal/agent-session.test.ts\",\n \"assertions\": [\n \"authenticated runtime and mobile structured requests normalize focused presentation to background before reaching the owning runtime\",\n \"trusted in-process structured callers retain focused presentation\"\n ]\n }", "Audit1983 historical original evidenceRuns object, preserved verbatim; retired objects are not active execution evidence or replacement coverage:\n{\n \"date\": \"2026-07-23\",\n \"runner\": \"local\",\n \"platform\": \"macos\",\n \"command\": \"pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/rpc/methods/agent-session.test.ts src/renderer/src/runtime/web-runtime-session.test.ts src/renderer/src/runtime/web-session-tabs-sync.test.ts src/renderer/src/runtime/web-session-intent-owner.test.ts src/renderer/src/runtime/remote-server-parity.test.ts\",\n \"result\": \"passed\",\n \"durationSeconds\": 2.28,\n \"summary\": \"Five focused files and 140 tests passed on the structural candidate, covering authenticated host presentation normalization, trusted local preservation, fresh/resume viewer intent, same-version response/publication replay, exact split-leaf focus, sibling-first publication, and paired-runtime parity.\",\n \"currentCommand\": \"pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/rpc/methods/terminal/agent-session.test.ts src/renderer/src/runtime/web-runtime-session.test.ts src/renderer/src/runtime/web-session-tabs-sync-focus-intent.test.ts\"\n }", "Audit1983 historical original evidenceRuns object, preserved verbatim; retired objects are not active execution evidence or replacement coverage:\n{\n \"date\": \"2026-07-23\",\n \"runner\": \"local\",\n \"platform\": \"macos\",\n \"command\": \"pnpm test:repro:remote-agent-session\",\n \"result\": \"passed\",\n \"durationSeconds\": 53.6,\n \"summary\": \"The build-backed isolated headless serve harness passed over encrypted pairing. Tokened fresh/resume fixture processes were distinguished from unrelated Codex app-server startup probes; response-loss replay, exact spawn identity/count, writable PTYs, unrelated survival, stale rejection, exact PID death, empty restart inventory, and no session resurrection all passed.\",\n \"currentCommand\": \"pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/rpc/methods/terminal/agent-session.test.ts src/renderer/src/runtime/web-runtime-session.test.ts src/renderer/src/runtime/web-session-tabs-sync-focus-intent.test.ts\"\n }", - "Audit1983 historical original evidenceRuns object, preserved verbatim; retired objects are not active execution evidence or replacement coverage:\n{\n \"date\": \"2026-07-22\",\n \"runner\": \"local\",\n \"platform\": \"macos\",\n \"command\": \"pnpm test:repro:remote-agent-session\",\n \"result\": \"passed\",\n \"durationSeconds\": 48.63,\n \"summary\": \"The secondary build-backed headless parity harness passed post-rebase on main@72a2d7bc7 over encrypted WebSocket pairing with independent clients, proving one spawn, retry adoption, durable exit retirement, stale-publication rejection, and no restart resurrection.\",\n \"currentCommand\": \"pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/rpc/methods/terminal/agent-session.test.ts src/renderer/src/runtime/web-runtime-session.test.ts src/renderer/src/runtime/web-session-tabs-sync-focus-intent.test.ts\"\n }" + "Audit1983 historical original evidenceRuns object, preserved verbatim; retired objects are not active execution evidence or replacement coverage:\n{\n \"date\": \"2026-07-22\",\n \"runner\": \"local\",\n \"platform\": \"macos\",\n \"command\": \"pnpm test:repro:remote-agent-session\",\n \"result\": \"passed\",\n \"durationSeconds\": 48.63,\n \"summary\": \"The secondary build-backed headless parity harness passed post-rebase on main@72a2d7bc7 over encrypted WebSocket pairing with independent clients, proving one spawn, retry adoption, durable exit retirement, stale-publication rejection, and no restart resurrection.\",\n \"currentCommand\": \"pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/rpc/methods/terminal/agent-session.test.ts src/renderer/src/runtime/web-runtime-session.test.ts src/renderer/src/runtime/web-session-tabs-sync-focus-intent.test.ts\"\n }", + "Audit2003: Delete the complete pure orphan-topology suite and its file membership; preserve the other selectors from its combined command after archiving the original command string. Lost alarms are synthetic current-layout preservation, one-anchor grafting and ambiguous-anchor appending examples. Do not transfer their ownership/topology scope to sibling suites. Preserve all5 independent historical receipts exactly. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, physical provider/PTY, rendered, live remote/mobile or cross-platform result is claimed.", + "Audit2003 historical original commands object, preserved verbatim; retired objects are not active execution evidence or replacement coverage:\n\"pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/terminal/adoption/terminal-orphan-topology.test.ts src/renderer/src/runtime/hosted-terminals/recovery/web-session-terminal-orphan-recovery.test.ts src/renderer/src/runtime/hosted-terminals/recovery/web-session-terminal-orphan-mixed-version.test.ts --maxWorkers=1\"", + "Audit2003 historical original testFiles object, preserved verbatim; retired objects are not active execution evidence or replacement coverage:\n\"src/main/runtime/terminal/adoption/terminal-orphan-topology.test.ts\"" ], "demotionRule": "Keep experimental or demote if the focused gate flakes without a product or harness bug, if a retry can physically spawn twice, if a stale exit/publication can replace or resurrect a terminal, or if mixed-version fallback occurs after an authority side effect." }, @@ -9075,13 +8881,13 @@ "providers": ["paired-runtime", "ssh"], "coveredPlatforms": ["macos", "linux"], "coveredProviders": [], - "coverageNotes": "Audit1991: Remove initial-snapshot sequence-domain, limited-tail/truncation and unknown-output-sequence aliases whose callbacks are removed. The source retains partial-chunk replay and requested-snapshot retry/admission at supplied snapshot/data seams, not equivalent replacement or measured retained-memory proof. Preserve the existing stream/pending-wait capacity aliases only to their retained owning multiplex callbacks with supplied PTY resolutions. Removing the sibling-PTY heartbeat dedup work-count sample does not establish a new decorative fanout budget; the unrelated-PTY cancellation fence survives. Preserve17 unrelated dated receipts. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, physical provider/PTY, rendered, live remote/mobile or cross-platform result is claimed. Historical prior account (preserved verbatim): Audit1983: Remove the reasonless-close selector and narrow census aliases to actual retained runtime and injected-inventory callbacks. The removed synthetic census-dedup and isolated pre-boundary timing cases receive no replacement credit; preserve17 unrelated dated receipts. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, provider/PTY, rendered, live remote/mobile or cross-platform result is claimed. Historical prior account (preserved verbatim): Audit1939 (sources1934/1935): Delete the synthetic watcher-synchronization and partial-reconciliation suites. Retire all affected host-fact, capability-owner, activation-residue and retention-cap aliases because the remaining callbacks do not establish those full claims. Keep exact local hook/timer, byte-stream side effects, exit/disposal, scroll-intent cleanup and deterministic local rank/TTL progression callbacks within their mocked-host scope. No physical paired-host retention, resource budget or equivalent replacement evidence is assigned. The generic broad test command remains a runnable discovery command, never a dated execution receipt. Preserve meaningful remaining selectors/assertions and unchanged dated receipts within their original scope. Reduced selectors have no fresh gate execution evidence in this metadata packet. No equivalent replacement, physical watcher/PTY/provider, live SSH/remote/mobile, rendered, cross-platform, soak or measured-budget result is claimed. Historical prior account (preserved verbatim): Audit1931 (sources1928/1929): Delete the complete hook-agent-status projection suite and its two behavioral assertion aliases. Supplied stored hook/title/resume/staleness/rename/question/generation/PTY-retirement/snapshot-version projections are intentionally lost. The separate osc-status-scan gate mentions this source only in existing historical notes and receives no mutation. No measured hidden-output retention, CPU/RSS or resource budget result is claimed by the deleted source or this audit. Preserve meaningful remaining selectors/assertions and unchanged dated receipts within their original scope. Reduced selectors have no fresh gate execution evidence in this metadata packet. No equivalent replacement, physical watcher/PTY/provider, live SSH/remote/mobile, rendered, cross-platform, soak or measured-budget result is claimed. Historical prior account (preserved verbatim): Module ownership cleanup retires the modeled cold-park private watcher/render/work-count suites under the current test policy. Their assertions are no longer active evidence. Retained lifecycle tests and headed/headless journeys do not establish equivalent coverage for those removed harness alarms. Audit1640 removes low-value IPC, SSH, workspace and transcript-watcher tests without equivalent replacement coverage. Per-source reviews record retained public refusal, cancellation, execution-host and real-filesystem outcomes with injected/native/remote limits. Affected historical receipts are retired; gates without active current evidence are downgraded. Historical scope before this audit: Audit1634 removes low-value IPC, filesystem and runtime projections, argument inventories and duplicate examples without equivalent replacement coverage. Per-source reviews record retained cancellation, ownership, authorization, filesystem and SQLite boundaries with injected/native/remote limits. Affected historical receipts are retired; gates without active current evidence are downgraded. Historical scope before this audit: Audit1627 removes low-value mocked daemon projections, scalar tables and duplicate terminal examples without equivalent replacement coverage. Per-source reviews record retained terminal content preservation, process ownership refusal and recovery boundaries together with injected/native/remote limits. Affected historical receipts are retired; gates without active current evidence are downgraded. Historical scope before this audit: Retained exact unchanged-source receipts remain the only current execution evidence. Reduced selectors have no fresh execution result. Audit1417 intentionally deletes src/renderer/src/components/terminal-pane/pty-connection-hidden-tui-snapshot-replay.spec.ts: Loses rich hidden snapshot sequence replay/grid resize and forced repaint after hidden snapshot alarms, including live-trigger deduplication. deletes src/renderer/src/components/terminal-pane/terminal-cold-park-verdict-loop.test.tsx: Loses modeled React oscillation alarm; real pane/watchers are not exercised. deletes src/renderer/src/runtime/remote-runtime-terminal-parse-backpressure.test.ts: Loses delayed credit until parsing, malformed frame/unknown stream handling, discarded transformed output recovery and failed ACK rearm alarms. No equivalent replacement coverage or fresh result is claimed. Historical coverage account: Audit1412 changes referenced sources. Retained unchanged-source receipts apply only to their unchanged commands; the reduced selectors have no new execution result. Historical coverage account (not a current claim for changed sources): This audit removes the browser response-after-manual-disconnect example. Its distinct in-flight response fencing alarm is lost; retained queued-call disconnect tests are not an equivalent replacement. Historical commands and results remain historical. Deterministic headed macOS runs launch an isolated Orca desktop server and a separate paired web client. A byte-identical headless run uses an isolated `orca serve` host. Both create six real paired host PTYs with bounded high-output scrollback, prove sustained output while all six client xterms are warm-mounted but hidden causes zero renderer scheduler work, then drive 180 timed real-PTY spinner frames through a second production `session.tabs.subscribeAll` transport and the real Zustand store. Each worktree must publish and apply only its exact initial-working and final-idle titles. Both topologies ordinary-park five xterms without enabling the lossy retention budget, assert bounded cells/heap/timer lag, then restore the bounded authoritative tail exactly once on the original PTY including output produced while parked and continued input/output. The headed oracle additionally keeps every remote target workspace and terminal unmounted in the host renderer before and after client recovery. Production-parity React contracts reproduce and remove the independent cold-park watcher/title feedback loop, including memo-cache discard, StrictMode cleanup replay, detached split-PTY replacement, and the zero-park fast path. A deterministic cross-boundary contract drives 24 real PTY trackers in distinct worktrees through 2,880 decorative spinner and bare-native redraw chunks and counts host publications, serialized bytes, renderer apply calls, and store mutations while preserving exact per-PTY raw chunks and per-worktree semantic transitions. Fake-clock continuation beyond the 30-minute stale boundary requires one deduplicated refresh per worktree every 15 minutes, globally spaced by at least 50 ms, with every mirrored working status still fresh. Exact-identical Pi plus rotating normalized Pi/Grok frames renew coarse working/permission state; rich prompt/tool/question fields retain their original 30-minute lease, and `stateStartedAt`/acknowledgement identity remain stable across title/hook source swaps. A separate headed desktop-client journey relaunches the host main process while retaining the daemon and a daemon-backgrounded PTY, then requires first-observation foreground sync and continued input/output on the same PTY. Unit coverage keeps layout versions out of snapshot output high-waters when a restored snapshot has no output sequence and retries an existing subscriber only after authoritative provider inventory proves the retained PTY exists. Separate headed/headless ACK-starvation tests recover one stalled stream without replacing its PTY. A headed output-drop oracle proves a successful snapshot probe cannot certify a stale live stream when the authoritative PTY sequence advanced beyond the client high-water. Unit coverage requires an unsequenced client to establish a first-probe baseline, remain attached at the same sequence, and recover only after a later probe advances. A capability-disabled run proves legacy hosts retain the prior lossy limit/TTL fallback. Unit tests cover exact-owner capability routing, raw-stream release, singleton side-effect facts with timed handoff cleanup, 64 simultaneous synthetic-title sources with zero decorative client events after convergence, per-client late-subscribe recovery, semantic bell/title transitions, local animation-frame preservation, legacy no-output-pause delivery gating with exact snapshot restore, provider-authoritative snapshots, 128 active streams plus retry after a 129th-stream capacity rejection, capacity-pressure backoff, full split-leaf remint reconciliation, truncation, and manual-disconnect queue fencing. Linux, Windows, live SSH, and production-scale paired hosts remain gaps. The audit removed the E2E parking override/debug-handle suite. Override exposure guards, delay/retention independence, TTL filtering and debug-handle forwarding alarms are lost. Retained real parking journeys do not establish equivalent test-harness configuration coverage. The test-value audit removes separate responsive-stream, intervening-control-frame and capacity-error classification examples. Retained multiplexer checks use fake clocks and captured transport callbacks; their per-stream isolation, deadline and probe-baseline assertions do not prove an actual host restart or equivalent replacement coverage. Historical run commands and results remain historical. The test-value audit removes the standalone retained/live metadata projection and additive-preference negative example from runtime-client-events.test.ts. Its remaining case checks partial-authority event admission through a captured callback; it does not establish full-pair projection, actual peer delivery or equivalent replacement coverage. Historical commands and results remain historical. The isolated accepted-handle-events observer suite is removed. Its runtime/worktree/pane membership, once-only delivery and listener-release ledger alarms are lost. Live hidden-retention evidence does not establish equivalent observer coverage. Audit1292: The entire host-session mirror empty-inventory settle suite is removed. Live-unpublished versus genuinely-empty hydration, same-ID revision response/in-flight fencing and cleared-tracking pending-probe invalidation alarms are lost. The singleton store and inventories were mocked; no real host census or resume delivery or equivalent replacement coverage is claimed. Audit1302: src/main/runtime/session-tabs-inventory-publication.test.ts: Keep actual runtime public inventory authority across renderer epochs/resync, fallback, aborts and invalid graph publishers. Remove copied nonempty publication, private scan counters/collector and known-host replacement, fake refresh that performs the restoration itself, environment capability flag, and private resume-store characterization whose fixture supplies reservation/rollback ordering. Lose isolated refresh/churn/receiver/capability and copied-payload wiring alarms; no equivalent replacement. Reduced selectors require fresh validation; prior full-suite receipts do not validate removed or changed coverage. No equivalent replacement or physical-provider claim. Audit1312 removes low-value imported specs from aggregate test drivers; old whole-driver execution receipts are archived. Retained registrations are unchanged. No equivalent replacement coverage is claimed. Audit1318: src/shared/remote-runtime/control/remote-runtime-shared-control-connection.test.ts: Retain real local encrypted WebSocket regressions for pre-ready socket replacement preserving a request, close-before-ready logical subscription cleanup, unrelated RPC surviving another deadline, and pending setup abort leaving a sibling pending. Host server is synthetic and uses production crypto; kept scenarios also retain internal diagnostics/memory assertions unchanged. Remove happy routing, authority-envelope, unknown-ID/log, cleanup enum, binary, keepalive/liveness and production-budget/private socket inventories; intentionally lose those alarms, no physical host authorization, independent crypto or remote provider claim. Reduced selectors need fresh validation; historical full-suite receipts do not validate the changed coverage. No equivalent replacement or physical-provider claim. Audit1346 removes the entire private-state-injected session-tabs rich-status boundary suite. Its stale task/question, neutral-title preservation, owner-change and provider-generation alarms are intentionally lost. Retained real terminal journeys do not establish equivalent replacement coverage. The prior mixed 42-contract execution receipt is historical and must not validate the reduced current selector; fresh validation remains required. Audit1352: focused Linux unit selections validated on source head b638e07d395b76af3ba6b03761a1a0fe7e5e026e. Historical statements above retain their original scope. Intentional test losses are recorded in knownGaps; these runs claim no physical-provider, p95, rendered E2E or cross-platform validation. Audit1360: focused Linux unit selections validated on source head 415c9cffc2d10024131ec4b6721767a22195de6d. Historical statements above retain their original scope. Intentional test losses are recorded in knownGaps; these runs claim no physical-provider, p95, rendered E2E or cross-platform validation. Audit1383: src/main/runtime/orca-runtime-tests/worktree-native-chat-setup.spec.ts reduced. Lose command-list/receipt/default/background activation matrices whose provider spawn returns canned IDs. Retained test proves actual tab allocation separation, not terminal startup. No equivalent replacement coverage is claimed. Existing exact unaffected receipts and their platform/provider/budget claims remain scoped to unchanged selectors. Changed optional selectors have no new execution result. Audit1391: src/renderer/src/components/terminal-pane/pty-connection-remote-pause-drop-notice.spec.ts reduced. Lose repeated quiet switches avoiding snapshots and legacy host without drop-report capability requesting a snapshot. Retained imported registrar uses fake transport/xterm writes, exercising real gap/restore lifecycle rather than actual terminal rendering. No equivalent replacement coverage is claimed. The existing primary-selector receipt and its claims remain scoped to their unchanged sources; the broad optional command has no new execution result. Audit1396: src/renderer/src/components/terminal-pane/terminal-parked-watcher-reconciliation.test.ts reduced. Retire standalone reconcile set output and positive untouchedFreshSpawn/workspaceOwner copying alarms; no native watcher execution claim. No equivalent replacement coverage is claimed. Existing exact unaffected receipts remain unchanged and apply only to their unchanged selectors. Changed optional selectors have no new execution result. Audit1401: src/main/runtime/orca-runtime-tests/mobile-session-tabs-duplicate-pty-repair.spec.ts removed. Loses listing-on-already-repaired-data alarm; it never established runtime repair invocation. src/main/runtime/orca-runtime-tests/mobile-session-tabs-split-agent-tab.spec.ts removed. Loses actual mobile split sibling shell versus recorded-agent resume command alarms; no equivalent coverage asserted. src/main/runtime/orca-runtime-tests/worktree-native-chat-setup.spec.ts removed. Loses separate setup/startup tab result in that manually assembled path. src/main/runtime/orca-runtime.test.ts reduced. Driver registration/import of a whole-deleted selected spec is removed; directly authored cases remain unchanged. No extra authored removal credit. No equivalent replacement coverage is claimed. Existing exact unaffected receipts remain unchanged and apply only to their unchanged selectors. Changed optional selectors have no new execution result. Audit1402: src/renderer/src/components/terminal-pane/pty-connection-park-reveal-model-snapshot.spec.ts reduced. Loses basic paired serialize/initial replay forwarding check; hidden alt-frame grid/precedence policy remains. No real terminal renderer claim. No equivalent replacement coverage is claimed. The existing primary-selector receipt and its claims remain scoped to their unchanged sources; the broad optional command has no new execution result. Audit1412: src/main/runtime/mobile-session-tabs-agent-status-heartbeat.test.ts reduced. Loses FIFO fairness/spacing, semantic bypass/cancellation, delayed-boundary scheduling and teardown timer-cleanup alarms. Retained fixed emission cases do not replace those outcomes. No equivalent replacement coverage is claimed.", + "coverageNotes": "Audit2003: Remove SSH/absent-inventory attach refusal, later-subscriber retry, no-view adoption/takeback, provider-tail/unknown-read projections, failed-keystroke retry and simpler loss reattach examples. Keep the existing-subscriber inventory-ready retry alias only at its retained callback, plus retained held-input cancellation/replacement and overlapping attach/loss fences. The inventory driver also loses the whole empty-worktree-publication registration and folder-host/unverifiable scalar examples. Retire the entire dated10-case attach receipt; retain16 independent receipts only in their original scopes. No retained-memory or real-daemon measurement follows from supplied provider inventories. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, physical provider/PTY, rendered, live remote/mobile or cross-platform result is claimed. Historical prior account (preserved verbatim): Audit1991: Remove initial-snapshot sequence-domain, limited-tail/truncation and unknown-output-sequence aliases whose callbacks are removed. The source retains partial-chunk replay and requested-snapshot retry/admission at supplied snapshot/data seams, not equivalent replacement or measured retained-memory proof. Preserve the existing stream/pending-wait capacity aliases only to their retained owning multiplex callbacks with supplied PTY resolutions. Removing the sibling-PTY heartbeat dedup work-count sample does not establish a new decorative fanout budget; the unrelated-PTY cancellation fence survives. Preserve17 unrelated dated receipts. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, physical provider/PTY, rendered, live remote/mobile or cross-platform result is claimed. Historical prior account (preserved verbatim): Audit1983: Remove the reasonless-close selector and narrow census aliases to actual retained runtime and injected-inventory callbacks. The removed synthetic census-dedup and isolated pre-boundary timing cases receive no replacement credit; preserve17 unrelated dated receipts. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, provider/PTY, rendered, live remote/mobile or cross-platform result is claimed. Historical prior account (preserved verbatim): Audit1939 (sources1934/1935): Delete the synthetic watcher-synchronization and partial-reconciliation suites. Retire all affected host-fact, capability-owner, activation-residue and retention-cap aliases because the remaining callbacks do not establish those full claims. Keep exact local hook/timer, byte-stream side effects, exit/disposal, scroll-intent cleanup and deterministic local rank/TTL progression callbacks within their mocked-host scope. No physical paired-host retention, resource budget or equivalent replacement evidence is assigned. The generic broad test command remains a runnable discovery command, never a dated execution receipt. Preserve meaningful remaining selectors/assertions and unchanged dated receipts within their original scope. Reduced selectors have no fresh gate execution evidence in this metadata packet. No equivalent replacement, physical watcher/PTY/provider, live SSH/remote/mobile, rendered, cross-platform, soak or measured-budget result is claimed. Historical prior account (preserved verbatim): Audit1931 (sources1928/1929): Delete the complete hook-agent-status projection suite and its two behavioral assertion aliases. Supplied stored hook/title/resume/staleness/rename/question/generation/PTY-retirement/snapshot-version projections are intentionally lost. The separate osc-status-scan gate mentions this source only in existing historical notes and receives no mutation. No measured hidden-output retention, CPU/RSS or resource budget result is claimed by the deleted source or this audit. Preserve meaningful remaining selectors/assertions and unchanged dated receipts within their original scope. Reduced selectors have no fresh gate execution evidence in this metadata packet. No equivalent replacement, physical watcher/PTY/provider, live SSH/remote/mobile, rendered, cross-platform, soak or measured-budget result is claimed. Historical prior account (preserved verbatim): Module ownership cleanup retires the modeled cold-park private watcher/render/work-count suites under the current test policy. Their assertions are no longer active evidence. Retained lifecycle tests and headed/headless journeys do not establish equivalent coverage for those removed harness alarms. Audit1640 removes low-value IPC, SSH, workspace and transcript-watcher tests without equivalent replacement coverage. Per-source reviews record retained public refusal, cancellation, execution-host and real-filesystem outcomes with injected/native/remote limits. Affected historical receipts are retired; gates without active current evidence are downgraded. Historical scope before this audit: Audit1634 removes low-value IPC, filesystem and runtime projections, argument inventories and duplicate examples without equivalent replacement coverage. Per-source reviews record retained cancellation, ownership, authorization, filesystem and SQLite boundaries with injected/native/remote limits. Affected historical receipts are retired; gates without active current evidence are downgraded. Historical scope before this audit: Audit1627 removes low-value mocked daemon projections, scalar tables and duplicate terminal examples without equivalent replacement coverage. Per-source reviews record retained terminal content preservation, process ownership refusal and recovery boundaries together with injected/native/remote limits. Affected historical receipts are retired; gates without active current evidence are downgraded. Historical scope before this audit: Retained exact unchanged-source receipts remain the only current execution evidence. Reduced selectors have no fresh execution result. Audit1417 intentionally deletes src/renderer/src/components/terminal-pane/pty-connection-hidden-tui-snapshot-replay.spec.ts: Loses rich hidden snapshot sequence replay/grid resize and forced repaint after hidden snapshot alarms, including live-trigger deduplication. deletes src/renderer/src/components/terminal-pane/terminal-cold-park-verdict-loop.test.tsx: Loses modeled React oscillation alarm; real pane/watchers are not exercised. deletes src/renderer/src/runtime/remote-runtime-terminal-parse-backpressure.test.ts: Loses delayed credit until parsing, malformed frame/unknown stream handling, discarded transformed output recovery and failed ACK rearm alarms. No equivalent replacement coverage or fresh result is claimed. Historical coverage account: Audit1412 changes referenced sources. Retained unchanged-source receipts apply only to their unchanged commands; the reduced selectors have no new execution result. Historical coverage account (not a current claim for changed sources): This audit removes the browser response-after-manual-disconnect example. Its distinct in-flight response fencing alarm is lost; retained queued-call disconnect tests are not an equivalent replacement. Historical commands and results remain historical. Deterministic headed macOS runs launch an isolated Orca desktop server and a separate paired web client. A byte-identical headless run uses an isolated `orca serve` host. Both create six real paired host PTYs with bounded high-output scrollback, prove sustained output while all six client xterms are warm-mounted but hidden causes zero renderer scheduler work, then drive 180 timed real-PTY spinner frames through a second production `session.tabs.subscribeAll` transport and the real Zustand store. Each worktree must publish and apply only its exact initial-working and final-idle titles. Both topologies ordinary-park five xterms without enabling the lossy retention budget, assert bounded cells/heap/timer lag, then restore the bounded authoritative tail exactly once on the original PTY including output produced while parked and continued input/output. The headed oracle additionally keeps every remote target workspace and terminal unmounted in the host renderer before and after client recovery. Production-parity React contracts reproduce and remove the independent cold-park watcher/title feedback loop, including memo-cache discard, StrictMode cleanup replay, detached split-PTY replacement, and the zero-park fast path. A deterministic cross-boundary contract drives 24 real PTY trackers in distinct worktrees through 2,880 decorative spinner and bare-native redraw chunks and counts host publications, serialized bytes, renderer apply calls, and store mutations while preserving exact per-PTY raw chunks and per-worktree semantic transitions. Fake-clock continuation beyond the 30-minute stale boundary requires one deduplicated refresh per worktree every 15 minutes, globally spaced by at least 50 ms, with every mirrored working status still fresh. Exact-identical Pi plus rotating normalized Pi/Grok frames renew coarse working/permission state; rich prompt/tool/question fields retain their original 30-minute lease, and `stateStartedAt`/acknowledgement identity remain stable across title/hook source swaps. A separate headed desktop-client journey relaunches the host main process while retaining the daemon and a daemon-backgrounded PTY, then requires first-observation foreground sync and continued input/output on the same PTY. Unit coverage keeps layout versions out of snapshot output high-waters when a restored snapshot has no output sequence and retries an existing subscriber only after authoritative provider inventory proves the retained PTY exists. Separate headed/headless ACK-starvation tests recover one stalled stream without replacing its PTY. A headed output-drop oracle proves a successful snapshot probe cannot certify a stale live stream when the authoritative PTY sequence advanced beyond the client high-water. Unit coverage requires an unsequenced client to establish a first-probe baseline, remain attached at the same sequence, and recover only after a later probe advances. A capability-disabled run proves legacy hosts retain the prior lossy limit/TTL fallback. Unit tests cover exact-owner capability routing, raw-stream release, singleton side-effect facts with timed handoff cleanup, 64 simultaneous synthetic-title sources with zero decorative client events after convergence, per-client late-subscribe recovery, semantic bell/title transitions, local animation-frame preservation, legacy no-output-pause delivery gating with exact snapshot restore, provider-authoritative snapshots, 128 active streams plus retry after a 129th-stream capacity rejection, capacity-pressure backoff, full split-leaf remint reconciliation, truncation, and manual-disconnect queue fencing. Linux, Windows, live SSH, and production-scale paired hosts remain gaps. The audit removed the E2E parking override/debug-handle suite. Override exposure guards, delay/retention independence, TTL filtering and debug-handle forwarding alarms are lost. Retained real parking journeys do not establish equivalent test-harness configuration coverage. The test-value audit removes separate responsive-stream, intervening-control-frame and capacity-error classification examples. Retained multiplexer checks use fake clocks and captured transport callbacks; their per-stream isolation, deadline and probe-baseline assertions do not prove an actual host restart or equivalent replacement coverage. Historical run commands and results remain historical. The test-value audit removes the standalone retained/live metadata projection and additive-preference negative example from runtime-client-events.test.ts. Its remaining case checks partial-authority event admission through a captured callback; it does not establish full-pair projection, actual peer delivery or equivalent replacement coverage. Historical commands and results remain historical. The isolated accepted-handle-events observer suite is removed. Its runtime/worktree/pane membership, once-only delivery and listener-release ledger alarms are lost. Live hidden-retention evidence does not establish equivalent observer coverage. Audit1292: The entire host-session mirror empty-inventory settle suite is removed. Live-unpublished versus genuinely-empty hydration, same-ID revision response/in-flight fencing and cleared-tracking pending-probe invalidation alarms are lost. The singleton store and inventories were mocked; no real host census or resume delivery or equivalent replacement coverage is claimed. Audit1302: src/main/runtime/session-tabs-inventory-publication.test.ts: Keep actual runtime public inventory authority across renderer epochs/resync, fallback, aborts and invalid graph publishers. Remove copied nonempty publication, private scan counters/collector and known-host replacement, fake refresh that performs the restoration itself, environment capability flag, and private resume-store characterization whose fixture supplies reservation/rollback ordering. Lose isolated refresh/churn/receiver/capability and copied-payload wiring alarms; no equivalent replacement. Reduced selectors require fresh validation; prior full-suite receipts do not validate removed or changed coverage. No equivalent replacement or physical-provider claim. Audit1312 removes low-value imported specs from aggregate test drivers; old whole-driver execution receipts are archived. Retained registrations are unchanged. No equivalent replacement coverage is claimed. Audit1318: src/shared/remote-runtime/control/remote-runtime-shared-control-connection.test.ts: Retain real local encrypted WebSocket regressions for pre-ready socket replacement preserving a request, close-before-ready logical subscription cleanup, unrelated RPC surviving another deadline, and pending setup abort leaving a sibling pending. Host server is synthetic and uses production crypto; kept scenarios also retain internal diagnostics/memory assertions unchanged. Remove happy routing, authority-envelope, unknown-ID/log, cleanup enum, binary, keepalive/liveness and production-budget/private socket inventories; intentionally lose those alarms, no physical host authorization, independent crypto or remote provider claim. Reduced selectors need fresh validation; historical full-suite receipts do not validate the changed coverage. No equivalent replacement or physical-provider claim. Audit1346 removes the entire private-state-injected session-tabs rich-status boundary suite. Its stale task/question, neutral-title preservation, owner-change and provider-generation alarms are intentionally lost. Retained real terminal journeys do not establish equivalent replacement coverage. The prior mixed 42-contract execution receipt is historical and must not validate the reduced current selector; fresh validation remains required. Audit1352: focused Linux unit selections validated on source head b638e07d395b76af3ba6b03761a1a0fe7e5e026e. Historical statements above retain their original scope. Intentional test losses are recorded in knownGaps; these runs claim no physical-provider, p95, rendered E2E or cross-platform validation. Audit1360: focused Linux unit selections validated on source head 415c9cffc2d10024131ec4b6721767a22195de6d. Historical statements above retain their original scope. Intentional test losses are recorded in knownGaps; these runs claim no physical-provider, p95, rendered E2E or cross-platform validation. Audit1383: src/main/runtime/orca-runtime-tests/worktree-native-chat-setup.spec.ts reduced. Lose command-list/receipt/default/background activation matrices whose provider spawn returns canned IDs. Retained test proves actual tab allocation separation, not terminal startup. No equivalent replacement coverage is claimed. Existing exact unaffected receipts and their platform/provider/budget claims remain scoped to unchanged selectors. Changed optional selectors have no new execution result. Audit1391: src/renderer/src/components/terminal-pane/pty-connection-remote-pause-drop-notice.spec.ts reduced. Lose repeated quiet switches avoiding snapshots and legacy host without drop-report capability requesting a snapshot. Retained imported registrar uses fake transport/xterm writes, exercising real gap/restore lifecycle rather than actual terminal rendering. No equivalent replacement coverage is claimed. The existing primary-selector receipt and its claims remain scoped to their unchanged sources; the broad optional command has no new execution result. Audit1396: src/renderer/src/components/terminal-pane/terminal-parked-watcher-reconciliation.test.ts reduced. Retire standalone reconcile set output and positive untouchedFreshSpawn/workspaceOwner copying alarms; no native watcher execution claim. No equivalent replacement coverage is claimed. Existing exact unaffected receipts remain unchanged and apply only to their unchanged selectors. Changed optional selectors have no new execution result. Audit1401: src/main/runtime/orca-runtime-tests/mobile-session-tabs-duplicate-pty-repair.spec.ts removed. Loses listing-on-already-repaired-data alarm; it never established runtime repair invocation. src/main/runtime/orca-runtime-tests/mobile-session-tabs-split-agent-tab.spec.ts removed. Loses actual mobile split sibling shell versus recorded-agent resume command alarms; no equivalent coverage asserted. src/main/runtime/orca-runtime-tests/worktree-native-chat-setup.spec.ts removed. Loses separate setup/startup tab result in that manually assembled path. src/main/runtime/orca-runtime.test.ts reduced. Driver registration/import of a whole-deleted selected spec is removed; directly authored cases remain unchanged. No extra authored removal credit. No equivalent replacement coverage is claimed. Existing exact unaffected receipts remain unchanged and apply only to their unchanged selectors. Changed optional selectors have no new execution result. Audit1402: src/renderer/src/components/terminal-pane/pty-connection-park-reveal-model-snapshot.spec.ts reduced. Loses basic paired serialize/initial replay forwarding check; hidden alt-frame grid/precedence policy remains. No real terminal renderer claim. No equivalent replacement coverage is claimed. The existing primary-selector receipt and its claims remain scoped to their unchanged sources; the broad optional command has no new execution result. Audit1412: src/main/runtime/mobile-session-tabs-agent-status-heartbeat.test.ts reduced. Loses FIFO fairness/spacing, semantic bypass/cancellation, delayed-boundary scheduling and teardown timer-cleanup alarms. Retained fixed emission cases do not replace those outcomes. No equivalent replacement coverage is claimed.", "motivatingLinks": [ "https://github.com/stablyai/orca/issues/8652", "https://github.com/stablyai/orca/pull/10625" ], "invariant": "A host advertising terminal.paired-parking.v1 keeps the PTY and bounded authoritative history alive while an ordinary hidden-view park destroys the client xterm and releases its raw per-PTY stream. Reveal must restore up to the requested 5,000 rows, parked-time side effects/output, the same PTY identity, and continued input/output. Parked watcher synchronization compares parking semantics rather than fresh object identity, owns setup and cleanup across StrictMode replay, reconciles real PTY/layout changes, and performs no terminal-state scan when nothing is parked. After a host main-process relaunch, unknown local delivery-sync state must not be treated as proof that a surviving daemon PTY is already foregrounded. An empty headed session-tab inventory is authoritative only after the current renderer graph generation publishes a complete inventory and every execution host answers the PTY census; unpublished, resync-pending, transport-failed, and partially scoped inventories remain unverifiable, while genuinely authoritative empty and non-empty inventories settle and a scope-less legacy empty keeps its pre-authority best-effort settle so outdated hosts retain agent auto-resume. The unpublished-empty regression must keep the resume dispatch parked with exactly one daemon process/writer; restoring unconditional empty hydration must release one dispatch and create two live writers. If subscription precedes provider readiness, authoritative inventory must reconsider that existing subscriber without spawning, resizing, or changing reconnect state. A snapshot without an output high-water must keep it unknown rather than borrowing a layout version that can suppress newer live output. Hosts without the capability must gate hidden raw output before xterm scheduling and repaint from the authoritative snapshot on reveal while retaining the existing limit/TTL force-parking fallback. After the first semantic title state, decorative spinner frequency must add zero paired client-event frames and zero full session-tabs work between status-freshness leases regardless of hidden worktree count. Continuous working or permission evidence may refresh each affected worktree once per 15 minutes through one globally spaced FIFO so current and legacy viewers do not decay before 30 minutes; sibling PTYs share that worktree refresh, while local title animation, raw output, and semantic title, status, bell, completion, and query facts remain intact. A paired terminal stream whose delivery credits stop progressing must replace only that stream; command silence first probes authoritative state and replaces the stream if the probe times out or proves the PTY advanced beyond the client's delivered output sequence. When no comparable delivery high-water exists, the first sequenced probe establishes a baseline and only later advancement proves staleness. A same-sequence snapshot remains valid proof of a responsive silent command. A successful status probe may replace a pre-ready shared-control socket without rejecting or duplicating calls already waiting for that transport. Manual disconnect must retain pairing while preventing queued or passive calls and subscriptions from recreating transport until explicit Connect. A paired pane reconnecting to a relaunched host that has not yet published its window graph (an unpublished frame, bare or client-projected) must stay in recovery rather than retire, and the host's first published snapshot carrying the surface must reattach it; a published frame lacking the surface still retires it. The placeholder is only ever sent before the host's graph first publishes; afterwards a worktree with no tabs gets a real empty answer, pushed once to any client that asked during startup, so a paired client opening a never-opened host worktree still gets its first terminal.", - "oracle": "Audit1991: Remove initial-snapshot sequence-domain, limited-tail/truncation and unknown-output-sequence aliases whose callbacks are removed. The source retains partial-chunk replay and requested-snapshot retry/admission at supplied snapshot/data seams, not equivalent replacement or measured retained-memory proof. Preserve the existing stream/pending-wait capacity aliases only to their retained owning multiplex callbacks with supplied PTY resolutions. Removing the sibling-PTY heartbeat dedup work-count sample does not establish a new decorative fanout budget; the unrelated-PTY cancellation fence survives. Preserve17 unrelated dated receipts. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, physical provider/PTY, rendered, live remote/mobile or cross-platform result is claimed. Historical prior account (preserved verbatim): Audit1983: Remove the reasonless-close selector and narrow census aliases to actual retained runtime and injected-inventory callbacks. The removed synthetic census-dedup and isolated pre-boundary timing cases receive no replacement credit; preserve17 unrelated dated receipts. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, provider/PTY, rendered, live remote/mobile or cross-platform result is claimed. Historical prior account (preserved verbatim): Audit1939 (sources1934/1935): Delete the synthetic watcher-synchronization and partial-reconciliation suites. Retire all affected host-fact, capability-owner, activation-residue and retention-cap aliases because the remaining callbacks do not establish those full claims. Keep exact local hook/timer, byte-stream side effects, exit/disposal, scroll-intent cleanup and deterministic local rank/TTL progression callbacks within their mocked-host scope. No physical paired-host retention, resource budget or equivalent replacement evidence is assigned. The generic broad test command remains a runnable discovery command, never a dated execution receipt. Preserve meaningful remaining selectors/assertions and unchanged dated receipts within their original scope. Reduced selectors have no fresh gate execution evidence in this metadata packet. No equivalent replacement, physical watcher/PTY/provider, live SSH/remote/mobile, rendered, cross-platform, soak or measured-budget result is claimed. Historical prior account (preserved verbatim): Audit1931 (sources1928/1929): Delete the complete hook-agent-status projection suite and its two behavioral assertion aliases. Supplied stored hook/title/resume/staleness/rename/question/generation/PTY-retirement/snapshot-version projections are intentionally lost. The separate osc-status-scan gate mentions this source only in existing historical notes and receives no mutation. No measured hidden-output retention, CPU/RSS or resource budget result is claimed by the deleted source or this audit. Preserve meaningful remaining selectors/assertions and unchanged dated receipts within their original scope. Reduced selectors have no fresh gate execution evidence in this metadata packet. No equivalent replacement, physical watcher/PTY/provider, live SSH/remote/mobile, rendered, cross-platform, soak or measured-budget result is claimed. Historical prior account (preserved verbatim): Retained exact unchanged-source receipts remain the only current execution evidence. Reduced selectors have no fresh execution result. Historical pre-audit oracle account for changed sources: The retained unchanged-source receipts are the only current execution evidence; reduced-source assertions await validation. Historical oracle account (not a current claim for changed sources): Existing exact unchanged-selector receipts remain the only current execution evidence. Changed source selectors have no fresh execution result. Historical prior-source oracle account: Existing exact unchanged-selector receipts remain the only current execution evidence. Changed source selectors have no fresh execution result. Historical prior-source oracle account: Run one byte-identical six-terminal oracle against an isolated headed desktop host and an isolated headless `orca serve` host. Stage at least 1,000,000 xterm cells, hide all six warm managers, emit an initial working title followed by 30 timed real-PTY spinner frames and a final idle title per worktree, and observe a second real session-tabs subscription plus the production renderer store. Require exactly two transport envelopes and two title mutations per worktree, no decorative title crossing either boundary, and positive decoded-envelope bytes. Enable ordinary parking with the lossy retention budget disabled, require exactly one mounted manager and five parked tabs, at most 45% retained cells, no more than 16 MiB heap growth, and under 500 ms timer drift. In headed mode, require the host renderer to remain on its original workspace with zero target terminal managers mounted throughout client park, reveal, and live I/O. While a tab is parked, require authoritative terminal.read to observe new PTY output; reveal it and require the original PTY, a marker within the requested 5,000-row history, the parked marker, and post-reveal input/output. Drive 24 real PTY title trackers in distinct worktrees through 40 decorative spinner frames each, then 40 spinner frames alternating with Cursor's bare native identity redraw, using fake clocks. Require all 2,880 raw chunks exactly and zero host session-tabs publications, serialized bytes, renderer apply calls, and renderer store mutations. Continue decorative evidence beyond 30 minutes, require exactly one refresh per worktree per 15-minute lease through a FIFO spaced by at least 50 ms, and require all 24 viewer statuses to remain working and fresh; then require one exact semantic idle transition and one visible-output chunk per PTY. Require ordinary and interior-Braille title changes to publish. Drive 64 host PTYs through ten 80 ms synthetic title frames, require zero paired client events after the first frame per PTY while all 640 local frames remain observable, attach a late client and require one current frame per PTY, then require semantic bell and idle transitions on both clients. Reconstruct a legacy subscribed stream with no outputPause capability, hide a chatty pane, require no hidden xterm writes, reveal it, and require one authoritative snapshot plus continued live output. Drop and acknowledge output only for one original paired-client stream, prove the fixture process consumed input while the host model advanced and the client stayed stale, then require the command snapshot probe to replace that stream, repaint exact fixture output, preserve authoritative PTY identity and target-tab cardinality, and resume live I/O. For a client without a delivered sequence, require the first numeric probe to establish a baseline without replacement, a same-sequence probe to remain attached, and a later advanced probe to replace only that stream. Withhold the first encrypted shared-control ready frame, start one RPC, trigger a status-probe refresh, and require two connections, one host delivery, successful response, and zero retained request bytes. Admit 128 active streams, reject the 129th as retryable, release one stream, then require the retry to attach and publish a snapshot without multiplying retained subscribers. Disable terminal.paired-parking.v1 and require the same oracle to fail before parking, while the legacy limit-one fallback separately passes. Also preserve truncated first paint, ACK-starved same-PTY recovery, responsive silent-command snapshot probes, dead-stream probe timeout recovery, and queued manual-disconnect fencing.", + "oracle": "Audit2003: Remove SSH/absent-inventory attach refusal, later-subscriber retry, no-view adoption/takeback, provider-tail/unknown-read projections, failed-keystroke retry and simpler loss reattach examples. Keep the existing-subscriber inventory-ready retry alias only at its retained callback, plus retained held-input cancellation/replacement and overlapping attach/loss fences. The inventory driver also loses the whole empty-worktree-publication registration and folder-host/unverifiable scalar examples. Retire the entire dated10-case attach receipt; retain16 independent receipts only in their original scopes. No retained-memory or real-daemon measurement follows from supplied provider inventories. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, physical provider/PTY, rendered, live remote/mobile or cross-platform result is claimed. Historical prior account (preserved verbatim): Audit1991: Remove initial-snapshot sequence-domain, limited-tail/truncation and unknown-output-sequence aliases whose callbacks are removed. The source retains partial-chunk replay and requested-snapshot retry/admission at supplied snapshot/data seams, not equivalent replacement or measured retained-memory proof. Preserve the existing stream/pending-wait capacity aliases only to their retained owning multiplex callbacks with supplied PTY resolutions. Removing the sibling-PTY heartbeat dedup work-count sample does not establish a new decorative fanout budget; the unrelated-PTY cancellation fence survives. Preserve17 unrelated dated receipts. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, physical provider/PTY, rendered, live remote/mobile or cross-platform result is claimed. Historical prior account (preserved verbatim): Audit1983: Remove the reasonless-close selector and narrow census aliases to actual retained runtime and injected-inventory callbacks. The removed synthetic census-dedup and isolated pre-boundary timing cases receive no replacement credit; preserve17 unrelated dated receipts. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, provider/PTY, rendered, live remote/mobile or cross-platform result is claimed. Historical prior account (preserved verbatim): Audit1939 (sources1934/1935): Delete the synthetic watcher-synchronization and partial-reconciliation suites. Retire all affected host-fact, capability-owner, activation-residue and retention-cap aliases because the remaining callbacks do not establish those full claims. Keep exact local hook/timer, byte-stream side effects, exit/disposal, scroll-intent cleanup and deterministic local rank/TTL progression callbacks within their mocked-host scope. No physical paired-host retention, resource budget or equivalent replacement evidence is assigned. The generic broad test command remains a runnable discovery command, never a dated execution receipt. Preserve meaningful remaining selectors/assertions and unchanged dated receipts within their original scope. Reduced selectors have no fresh gate execution evidence in this metadata packet. No equivalent replacement, physical watcher/PTY/provider, live SSH/remote/mobile, rendered, cross-platform, soak or measured-budget result is claimed. Historical prior account (preserved verbatim): Audit1931 (sources1928/1929): Delete the complete hook-agent-status projection suite and its two behavioral assertion aliases. Supplied stored hook/title/resume/staleness/rename/question/generation/PTY-retirement/snapshot-version projections are intentionally lost. The separate osc-status-scan gate mentions this source only in existing historical notes and receives no mutation. No measured hidden-output retention, CPU/RSS or resource budget result is claimed by the deleted source or this audit. Preserve meaningful remaining selectors/assertions and unchanged dated receipts within their original scope. Reduced selectors have no fresh gate execution evidence in this metadata packet. No equivalent replacement, physical watcher/PTY/provider, live SSH/remote/mobile, rendered, cross-platform, soak or measured-budget result is claimed. Historical prior account (preserved verbatim): Retained exact unchanged-source receipts remain the only current execution evidence. Reduced selectors have no fresh execution result. Historical pre-audit oracle account for changed sources: The retained unchanged-source receipts are the only current execution evidence; reduced-source assertions await validation. Historical oracle account (not a current claim for changed sources): Existing exact unchanged-selector receipts remain the only current execution evidence. Changed source selectors have no fresh execution result. Historical prior-source oracle account: Existing exact unchanged-selector receipts remain the only current execution evidence. Changed source selectors have no fresh execution result. Historical prior-source oracle account: Run one byte-identical six-terminal oracle against an isolated headed desktop host and an isolated headless `orca serve` host. Stage at least 1,000,000 xterm cells, hide all six warm managers, emit an initial working title followed by 30 timed real-PTY spinner frames and a final idle title per worktree, and observe a second real session-tabs subscription plus the production renderer store. Require exactly two transport envelopes and two title mutations per worktree, no decorative title crossing either boundary, and positive decoded-envelope bytes. Enable ordinary parking with the lossy retention budget disabled, require exactly one mounted manager and five parked tabs, at most 45% retained cells, no more than 16 MiB heap growth, and under 500 ms timer drift. In headed mode, require the host renderer to remain on its original workspace with zero target terminal managers mounted throughout client park, reveal, and live I/O. While a tab is parked, require authoritative terminal.read to observe new PTY output; reveal it and require the original PTY, a marker within the requested 5,000-row history, the parked marker, and post-reveal input/output. Drive 24 real PTY title trackers in distinct worktrees through 40 decorative spinner frames each, then 40 spinner frames alternating with Cursor's bare native identity redraw, using fake clocks. Require all 2,880 raw chunks exactly and zero host session-tabs publications, serialized bytes, renderer apply calls, and renderer store mutations. Continue decorative evidence beyond 30 minutes, require exactly one refresh per worktree per 15-minute lease through a FIFO spaced by at least 50 ms, and require all 24 viewer statuses to remain working and fresh; then require one exact semantic idle transition and one visible-output chunk per PTY. Require ordinary and interior-Braille title changes to publish. Drive 64 host PTYs through ten 80 ms synthetic title frames, require zero paired client events after the first frame per PTY while all 640 local frames remain observable, attach a late client and require one current frame per PTY, then require semantic bell and idle transitions on both clients. Reconstruct a legacy subscribed stream with no outputPause capability, hide a chatty pane, require no hidden xterm writes, reveal it, and require one authoritative snapshot plus continued live output. Drop and acknowledge output only for one original paired-client stream, prove the fixture process consumed input while the host model advanced and the client stayed stale, then require the command snapshot probe to replace that stream, repaint exact fixture output, preserve authoritative PTY identity and target-tab cardinality, and resume live I/O. For a client without a delivered sequence, require the first numeric probe to establish a baseline without replacement, a same-sequence probe to remain attached, and a later advanced probe to replace only that stream. Withhold the first encrypted shared-control ready frame, start one RPC, trigger a status-probe refresh, and require two connections, one host delivery, successful response, and zero retained request bytes. Admit 128 active streams, reject the 129th as retryable, release one stream, then require the retry to attach and publish a snapshot without multiplying retained subscribers. Disable terminal.paired-parking.v1 and require the same oracle to fail before parking, while the legacy limit-one fallback separately passes. Also preserve truncated first paint, ACK-starved same-PTY recovery, responsive silent-command snapshot probes, dead-stream probe timeout recovery, and queued manual-disconnect fencing.", "commands": [ "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-unpublished-host-graph.test.ts", "ORCA_BACKGROUND_LAUNCH=1 pnpm exec playwright test tests/e2e/paired-remote-terminal-host-quit-reconnect-input.spec.ts --config tests/playwright.config.ts --project electron-headless --workers=1", @@ -9312,16 +9118,6 @@ "summary": "The isolated headless `orca serve` host passed the same six-worktree real-title transport/store bound, parking, and exact-restoration oracle; a second scenario proved cold activation mounted only the visible paired terminal.", "currentCommand": "ORCA_E2E_WEB_CLIENT=1 SKIP_BUILD=1 pnpm exec playwright test tests/e2e/headless-paired-remote-terminal-retention-memory.spec.ts --config tests/playwright.config.ts --project electron-headless --workers=1" }, - { - "date": "2026-08-07", - "runner": "local", - "platform": "macos", - "command": "pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/terminal-subscriber-driven-daemon-attach.test.ts", - "result": "passed", - "durationSeconds": 4.29, - "summary": "All 10 subscriber-driven attach contracts passed. With two authoritative inventories overlapping one refused in-flight attach, removing only the post-settlement retry made the byte-identical focused assertion fail with one attach instead of two; restoring it retained one bounded waiter, retried once, and resumed live output on the existing stream.", - "currentCommand": "pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/terminal-subscriber-driven-daemon-attach.test.ts" - }, { "date": "2026-08-07", "runner": "local", @@ -9473,11 +9269,11 @@ }, "redGreenEvidence": { "status": "missing", - "evidence": "Audit1991: Remove initial-snapshot sequence-domain, limited-tail/truncation and unknown-output-sequence aliases whose callbacks are removed. The source retains partial-chunk replay and requested-snapshot retry/admission at supplied snapshot/data seams, not equivalent replacement or measured retained-memory proof. Preserve the existing stream/pending-wait capacity aliases only to their retained owning multiplex callbacks with supplied PTY resolutions. Removing the sibling-PTY heartbeat dedup work-count sample does not establish a new decorative fanout budget; the unrelated-PTY cancellation fence survives. Preserve17 unrelated dated receipts. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, physical provider/PTY, rendered, live remote/mobile or cross-platform result is claimed. Historical prior account (preserved verbatim): Retained exact unchanged-source receipts remain the only current execution evidence. Reduced selectors have no fresh execution result. No fresh red/green result for changed selectors. Historical account: Audit1412 changed referenced test sources; no new red/green validation of the reduced set. Historical account: The six-PTY ordinary-parking oracle was red in two independently observed stages: the parked paired remount first used detached attach and restored only rows 5,963–5,999, then the requested snapshot still lost to the current-screen replay until park reveals explicitly entered the capability-gated reattach coordinator. The candidate is green on headed and headless hosts, while disabling terminal.paired-parking.v1 makes the byte-identical oracle fail at its capability precondition and leaves the separately tested legacy lossy fallback green. The parked-title production-parity control throws Maximum update depth exceeded on the affected parking synchronization and settles with semantic dependencies; forced memo-cache discard, StrictMode cleanup replay, and split-PTY replacement are independently red before their corrections and green on the combined candidate. The real-PTY session-tabs oracle produces 96 full snapshots, 64,032 serialized bytes, 96 renderer applies, and 96 store mutations on unmodified main and on a one-line causal revert before reaching the alternating native-title phase; the candidate produces zero in all four dimensions while preserving all 2,880 redraw chunks and 24 exact semantic transitions. The 64-PTY decorative-title control delivered 576 client events after convergence on unmodified main; the candidate delivers zero while preserving all 640 local frames, one current frame to a late client, and bell/idle transitions to both clients. The legacy no-output-pause control failed to request a reveal snapshot and kept hidden bytes on the xterm path; the candidate gates those bytes locally and restores one authoritative snapshot before live output. With stalled-stream recovery disabled, the host cursor advanced but the client remained frozen; enabling it repainted the marker with the same PTY and tab. The sequence-gap unit and headed paired oracle are red on current main and with only the new probe decision reverted: the authoritative host advances while the client stays stale after a successful probe. The candidate replaces the stream, repaints the marker, preserves PTY identity, and resumes I/O. Reverting the active-stream limit to 64 makes the exact 128-stream contract fail at 64; restoring 128 admits every intended stream, rejects 129, and reattaches it after one slot releases. The host-relaunch oracle fails when unknown delivery state is coerced to foregrounded or a layout version becomes an output high-water; the candidate foregrounds the preserved daemon PTY once and resumes same-PTY input/output. When authoritative inventory overlaps a refused in-flight attach, removing only the post-settlement retry leaves the existing subscriber without daemon output; the candidate retains one waiter across repeated inventory publications, retries exactly once, and restores live output." + "evidence": "Audit2003: Remove SSH/absent-inventory attach refusal, later-subscriber retry, no-view adoption/takeback, provider-tail/unknown-read projections, failed-keystroke retry and simpler loss reattach examples. Keep the existing-subscriber inventory-ready retry alias only at its retained callback, plus retained held-input cancellation/replacement and overlapping attach/loss fences. The inventory driver also loses the whole empty-worktree-publication registration and folder-host/unverifiable scalar examples. Retire the entire dated10-case attach receipt; retain16 independent receipts only in their original scopes. No retained-memory or real-daemon measurement follows from supplied provider inventories. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, physical provider/PTY, rendered, live remote/mobile or cross-platform result is claimed. Historical prior account (preserved verbatim): Audit1991: Remove initial-snapshot sequence-domain, limited-tail/truncation and unknown-output-sequence aliases whose callbacks are removed. The source retains partial-chunk replay and requested-snapshot retry/admission at supplied snapshot/data seams, not equivalent replacement or measured retained-memory proof. Preserve the existing stream/pending-wait capacity aliases only to their retained owning multiplex callbacks with supplied PTY resolutions. Removing the sibling-PTY heartbeat dedup work-count sample does not establish a new decorative fanout budget; the unrelated-PTY cancellation fence survives. Preserve17 unrelated dated receipts. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, physical provider/PTY, rendered, live remote/mobile or cross-platform result is claimed. Historical prior account (preserved verbatim): Retained exact unchanged-source receipts remain the only current execution evidence. Reduced selectors have no fresh execution result. No fresh red/green result for changed selectors. Historical account: Audit1412 changed referenced test sources; no new red/green validation of the reduced set. Historical account: The six-PTY ordinary-parking oracle was red in two independently observed stages: the parked paired remount first used detached attach and restored only rows 5,963–5,999, then the requested snapshot still lost to the current-screen replay until park reveals explicitly entered the capability-gated reattach coordinator. The candidate is green on headed and headless hosts, while disabling terminal.paired-parking.v1 makes the byte-identical oracle fail at its capability precondition and leaves the separately tested legacy lossy fallback green. The parked-title production-parity control throws Maximum update depth exceeded on the affected parking synchronization and settles with semantic dependencies; forced memo-cache discard, StrictMode cleanup replay, and split-PTY replacement are independently red before their corrections and green on the combined candidate. The real-PTY session-tabs oracle produces 96 full snapshots, 64,032 serialized bytes, 96 renderer applies, and 96 store mutations on unmodified main and on a one-line causal revert before reaching the alternating native-title phase; the candidate produces zero in all four dimensions while preserving all 2,880 redraw chunks and 24 exact semantic transitions. The 64-PTY decorative-title control delivered 576 client events after convergence on unmodified main; the candidate delivers zero while preserving all 640 local frames, one current frame to a late client, and bell/idle transitions to both clients. The legacy no-output-pause control failed to request a reveal snapshot and kept hidden bytes on the xterm path; the candidate gates those bytes locally and restores one authoritative snapshot before live output. With stalled-stream recovery disabled, the host cursor advanced but the client remained frozen; enabling it repainted the marker with the same PTY and tab. The sequence-gap unit and headed paired oracle are red on current main and with only the new probe decision reverted: the authoritative host advances while the client stays stale after a successful probe. The candidate replaces the stream, repaints the marker, preserves PTY identity, and resumes I/O. Reverting the active-stream limit to 64 makes the exact 128-stream contract fail at 64; restoring 128 admits every intended stream, rejects 129, and reattaches it after one slot releases. The host-relaunch oracle fails when unknown delivery state is coerced to foregrounded or a layout version becomes an output high-water; the candidate foregrounds the preserved daemon PTY once and resumes same-PTY input/output. When authoritative inventory overlaps a refused in-flight attach, removing only the post-settlement retry leaves the existing subscriber without daemon output; the candidate retains one waiter across repeated inventory publications, retries exactly once, and restores live output." }, "performanceBudget": { "required": true, - "evidence": "Audit1991: Remove initial-snapshot sequence-domain, limited-tail/truncation and unknown-output-sequence aliases whose callbacks are removed. The source retains partial-chunk replay and requested-snapshot retry/admission at supplied snapshot/data seams, not equivalent replacement or measured retained-memory proof. Preserve the existing stream/pending-wait capacity aliases only to their retained owning multiplex callbacks with supplied PTY resolutions. Removing the sibling-PTY heartbeat dedup work-count sample does not establish a new decorative fanout budget; the unrelated-PTY cancellation fence survives. Preserve17 unrelated dated receipts. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, physical provider/PTY, rendered, live remote/mobile or cross-platform result is claimed. Historical prior account (preserved verbatim): Historical pre-audit selector account; changed selectors have no new measurement. Historical account for the pre-audit test set; removed work-count alarms and changed selectors have no fresh measurement. Before ordinary parking, six warm-mounted hidden paired xterms receive zero raw frames and schedule zero renderer output drains while host models continue ingesting sustained output. The headed and headless production transports each admit only the initial and final semantic title snapshots per worktree across 180 timed decorative frames, and the real store performs only those 12 exact title mutations. Ordinary parking then retains the host PTY and bounded 5,000-row provider model while destroying five client xterms. Parked watcher synchronization is keyed by bounded parking semantics rather than fresh object identities; unrelated store writes perform zero PTY-map reads when no tab is parked, while parked reconciliation remains bounded by the worktree's terminal tabs and assignments. Decorative title ticks are collapsed both on the paired client-event channel and at the authoritative real-PTY session-tabs classifier before snapshot construction, serialization, transport, recovery, or store application; semantic facts remain exact. Headed and headless oracles require exact reduction from six managers to one, at least 55% staged xterm-cell release, no more than 16 MiB heap growth, and under 500 ms timer drift. The forced hidden-worktree retention policy keeps at most four otherwise un-parkable worktrees mounted and force-parks all after 15 minutes; the separate ordinary hot-retain policy keeps at most four recently visible worktrees warm for five minutes. Legacy hosts still gate hidden raw output before xterm scheduling and restore from a bounded snapshot on reveal. Stream recovery adds no polling and is scoped to one stream. Provider-ready reconciliation runs only for PTYs returned by a successful controller inventory, requires an existing remote subscriber, and retains at most one post-settlement waiter per PTY while attach deduplication remains active." + "evidence": "Audit2003: Remove SSH/absent-inventory attach refusal, later-subscriber retry, no-view adoption/takeback, provider-tail/unknown-read projections, failed-keystroke retry and simpler loss reattach examples. Keep the existing-subscriber inventory-ready retry alias only at its retained callback, plus retained held-input cancellation/replacement and overlapping attach/loss fences. The inventory driver also loses the whole empty-worktree-publication registration and folder-host/unverifiable scalar examples. Retire the entire dated10-case attach receipt; retain16 independent receipts only in their original scopes. No retained-memory or real-daemon measurement follows from supplied provider inventories. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, physical provider/PTY, rendered, live remote/mobile or cross-platform result is claimed. Historical prior account (preserved verbatim): Audit1991: Remove initial-snapshot sequence-domain, limited-tail/truncation and unknown-output-sequence aliases whose callbacks are removed. The source retains partial-chunk replay and requested-snapshot retry/admission at supplied snapshot/data seams, not equivalent replacement or measured retained-memory proof. Preserve the existing stream/pending-wait capacity aliases only to their retained owning multiplex callbacks with supplied PTY resolutions. Removing the sibling-PTY heartbeat dedup work-count sample does not establish a new decorative fanout budget; the unrelated-PTY cancellation fence survives. Preserve17 unrelated dated receipts. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, physical provider/PTY, rendered, live remote/mobile or cross-platform result is claimed. Historical prior account (preserved verbatim): Historical pre-audit selector account; changed selectors have no new measurement. Historical account for the pre-audit test set; removed work-count alarms and changed selectors have no fresh measurement. Before ordinary parking, six warm-mounted hidden paired xterms receive zero raw frames and schedule zero renderer output drains while host models continue ingesting sustained output. The headed and headless production transports each admit only the initial and final semantic title snapshots per worktree across 180 timed decorative frames, and the real store performs only those 12 exact title mutations. Ordinary parking then retains the host PTY and bounded 5,000-row provider model while destroying five client xterms. Parked watcher synchronization is keyed by bounded parking semantics rather than fresh object identities; unrelated store writes perform zero PTY-map reads when no tab is parked, while parked reconciliation remains bounded by the worktree's terminal tabs and assignments. Decorative title ticks are collapsed both on the paired client-event channel and at the authoritative real-PTY session-tabs classifier before snapshot construction, serialization, transport, recovery, or store application; semantic facts remain exact. Headed and headless oracles require exact reduction from six managers to one, at least 55% staged xterm-cell release, no more than 16 MiB heap growth, and under 500 ms timer drift. The forced hidden-worktree retention policy keeps at most four otherwise un-parkable worktrees mounted and force-parks all after 15 minutes; the separate ordinary hot-retain policy keeps at most four recently visible worktrees warm for five minutes. Legacy hosts still gate hidden raw output before xterm scheduling and restore from a bounded snapshot on reveal. Stream recovery adds no polling and is scoped to one stream. Provider-ready reconciliation runs only for PTYs returned by a successful controller inventory, requires an existing remote subscriber, and retains at most one post-settlement waiter per PTY while attach deduplication remains active." }, "promotionCriteria": [ "Collect 100 consecutive CI passes or 14 days of soak history for the headed and headless parking commands.", @@ -9535,7 +9331,9 @@ "Audit1991: Remove initial-snapshot sequence-domain, limited-tail/truncation and unknown-output-sequence aliases whose callbacks are removed. The source retains partial-chunk replay and requested-snapshot retry/admission at supplied snapshot/data seams, not equivalent replacement or measured retained-memory proof. Preserve the existing stream/pending-wait capacity aliases only to their retained owning multiplex callbacks with supplied PTY resolutions. Removing the sibling-PTY heartbeat dedup work-count sample does not establish a new decorative fanout budget; the unrelated-PTY cancellation fence survives. Preserve17 unrelated dated receipts. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, physical provider/PTY, rendered, live remote/mobile or cross-platform result is claimed.", "Audit1991 historical original assertionRefs object, preserved verbatim; retired objects are not active execution evidence or replacement coverage:\n{\n \"file\": \"src/main/runtime/rpc/terminal-multiplex-initial-snapshot-buffering.test.ts\",\n \"assertions\": [\"keeps layout versions out of the output sequence domain\"]\n }", "Audit1991 historical original assertionRefs object, preserved verbatim; retired objects are not active execution evidence or replacement coverage:\n{\n \"file\": \"src/main/runtime/rpc/terminal-multiplex-snapshot-serialization.test.ts\",\n \"assertions\": [\n \"binary first paint remains valid when only retained history was truncated\"\n ]\n }", - "Audit1991 historical original assertionRefs object, preserved verbatim; retired objects are not active execution evidence or replacement coverage:\n{\n \"file\": \"src/main/runtime/rpc/terminal-subscribe-buffer.test.ts\",\n \"assertions\": [\n \"omits an unknown snapshot output sequence instead of publishing the layout version\"\n ]\n }" + "Audit1991 historical original assertionRefs object, preserved verbatim; retired objects are not active execution evidence or replacement coverage:\n{\n \"file\": \"src/main/runtime/rpc/terminal-subscribe-buffer.test.ts\",\n \"assertions\": [\n \"omits an unknown snapshot output sequence instead of publishing the layout version\"\n ]\n }", + "Audit2003: Remove SSH/absent-inventory attach refusal, later-subscriber retry, no-view adoption/takeback, provider-tail/unknown-read projections, failed-keystroke retry and simpler loss reattach examples. Keep the existing-subscriber inventory-ready retry alias only at its retained callback, plus retained held-input cancellation/replacement and overlapping attach/loss fences. The inventory driver also loses the whole empty-worktree-publication registration and folder-host/unverifiable scalar examples. Retire the entire dated10-case attach receipt; retain16 independent receipts only in their original scopes. No retained-memory or real-daemon measurement follows from supplied provider inventories. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, physical provider/PTY, rendered, live remote/mobile or cross-platform result is claimed.", + "Audit2003 historical original evidenceRuns object, preserved verbatim; retired objects are not active execution evidence or replacement coverage:\n{\n \"date\": \"2026-08-07\",\n \"runner\": \"local\",\n \"platform\": \"macos\",\n \"command\": \"pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/terminal-subscriber-driven-daemon-attach.test.ts\",\n \"result\": \"passed\",\n \"durationSeconds\": 4.29,\n \"summary\": \"All 10 subscriber-driven attach contracts passed. With two authoritative inventories overlapping one refused in-flight attach, removing only the post-settlement retry made the byte-identical focused assertion fail with one attach instead of two; restoring it retained one bounded waiter, retried once, and resumed live output on the existing stream.\",\n \"currentCommand\": \"pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/terminal-subscriber-driven-daemon-attach.test.ts\"\n }" ], "demotionRule": "Keep experimental or demote to protection none if decorative title frequency changes paired client-event or session-tabs publication volume after convergence, a semantic fact is dropped, paired first paint is blank, passive work reconnects after manual disconnect, a capable hidden paired manager survives ordinary parking, a legacy hidden pane schedules raw xterm output or bypasses its fallback, reveal loses requested bounded history or live PTY identity, retained buffer cells do not fall, or either headed/headless scenario flakes." }, @@ -11334,14 +11132,14 @@ "providers": ["local", "daemon", "ssh", "wsl", "remote-runtime"], "coveredPlatforms": ["macos"], "coveredProviders": ["local", "daemon", "ssh"], - "coverageNotes": "Audit1973: Remove scalar, private-shape and projection examples and check cases whose reconciler runs manually before check. Retain actual federation lost-ack/gap and lifecycle completion-ack/replay alarms; unrelated message-delivery-identity receipt remains exact. Whole original changed dated receipts are retired, never narrowed or transferred. This metadata review claims no fresh execution, equivalent replacement, provider/PTY, live remote/mobile, rendered, cross-platform, soak or measured-budget result. Historical prior account (preserved verbatim): Audit1948 (Audit1946 sources 14, 26, 31, 39, 40, 47): Pruning removes scalar Run binding/projection and worker-start round-trip examples, v28 self-convergence, simple relay-type/legacy-payload and peer-probe/checkpoint cleanup examples, and reservation-failure/Cursor Enter examples. Preserve the three coordinator migration aliases, duplicate-replay waiter suppression and refused-write drain alias. The reminted-pane SQLite lookup checks active owner replacement and malformed decoys, not lookup cardinality or a performance budget. Overlapping sync uses an injected RPC/DB harness: promise coalescing and modeled checkpoint/ACK progression do not prove real durable checkpoint persistence. The complete affected combined federation receipt is retired whole; the independent unchanged CLI receipt remains in its original scope. No historical passed receipt is narrowed or transferred to changed source bytes. Unchanged dated receipts and retained assertions keep only their original independently mapped scopes. No fresh execution, equivalent replacement, physical PTY/provider, live remote/mobile, rendered, cross-platform, soak or measured-budget result is claimed by this metadata review. Historical prior account (preserved verbatim): Audit1943 (sources1934/1935/1938): Pruned notification-consistency suite loses50-row counter/wake budget and named-index EXPLAIN inventory, unbound direct-row projection and synthetic parked-decoy deadline examples. Preserve exact genuine SQLite ownership, disk-reopen/staged pointer and controlled provider-transition callbacks; fake PTY/title/clock seams do not establish actual provider delivery or measured backlog latency. Also retire three preexisting unsupported full aliases (explicit-check reservation redrive, known-PTY-exit replacement, and filtered/wait later-page draining), absent from both the frozen21and retained17callbacks. These retire stale metadata with no additional source/removal credit. Existing aliases are retained only where independently mapped below; no equivalent replacement for removed callbacks. Preserve meaningful remaining selectors/assertions and unchanged dated receipts within their original scope. Reduced selectors have no fresh gate execution evidence in this metadata packet. No equivalent replacement, physical watcher/PTY/provider, live SSH/remote/mobile, rendered, cross-platform, soak or measured-budget result is claimed. Historical prior account (preserved verbatim): Audit1734 removes low-value test definitions and retires affected assertion or execution-receipt claims without equivalent replacement coverage. Per-source reviews bound retained behavior and injected/native/remote limits. Unchanged assertions and receipts keep their original scope; gates lacking current active evidence are downgraded. Historical scope before this audit: Audit1634 removes low-value IPC, filesystem and runtime projections, argument inventories and duplicate examples without equivalent replacement coverage. Per-source reviews record retained cancellation, ownership, authorization, filesystem and SQLite boundaries with injected/native/remote limits. Affected historical receipts are retired; gates without active current evidence are downgraded. Historical scope before this audit: Audit1623 removes low-value mocked daemon projections, scalar tables and duplicate terminal examples without equivalent replacement coverage. Per-source reviews record retained terminal content preservation, process ownership refusal and recovery boundaries together with injected/native/remote limits. Affected historical receipts are retired; gates without active current evidence are downgraded. Historical scope before this audit: A deterministic isolated runtime/SQLite/RPC harness covers Run and Dispatch ownership, exact handle and remint-stable pane recipients, fixed Delivery replay and acknowledgment, restart, concurrent pollers, filtered waiters, and built-CLI parity with direct DB state. Schema v28 durably records every coordinator handle through SQLite triggers, backfills current handles on open, recreates routing triggers after old table rebuilds, and atomically canonicalizes coordinator-addressed inserts to run:. A 125-row oracle and 151-row reconciliation suite cover paged old-handle backlogs, commit-without-notify recovery, duplicate Dispatch owners, cancellation, and lifecycle fencing. Federation tests use real databases, dropped acknowledgments, restart/reconnect, and protocol v1/v2 peers; duplicate read rows do not wake again. Daemon and SSH pointer settlement is deterministic, with SSH bounded at 60 seconds beyond the 20-second mux health window. No RPC parameter, stream opcode, or published payload changed. Live headed/headless paired-runtime, physical SSH, WSL, Linux, and Windows acceptance remain gaps. Audit1312 removes low-value imported specs from aggregate test drivers; old whole-driver execution receipts are archived. Retained registrations are unchanged. No equivalent replacement coverage is claimed. Audit1383: src/main/runtime/orca-runtime-tests/worktree-native-chat-setup.spec.ts reduced. Lose command-list/receipt/default/background activation matrices whose provider spawn returns canned IDs. Retained test proves actual tab allocation separation, not terminal startup. No equivalent replacement coverage is claimed. Existing exact unaffected receipts and their platform/provider/budget claims remain scoped to unchanged selectors. Changed optional selectors have no new execution result. Audit1401: src/main/runtime/orca-runtime-tests/mobile-session-tabs-duplicate-pty-repair.spec.ts removed. Loses listing-on-already-repaired-data alarm; it never established runtime repair invocation. src/main/runtime/orca-runtime-tests/mobile-session-tabs-split-agent-tab.spec.ts removed. Loses actual mobile split sibling shell versus recorded-agent resume command alarms; no equivalent coverage asserted. src/main/runtime/orca-runtime-tests/worktree-native-chat-setup.spec.ts removed. Loses separate setup/startup tab result in that manually assembled path. src/main/runtime/orca-runtime.test.ts reduced. Driver registration/import of a whole-deleted selected spec is removed; directly authored cases remain unchanged. No extra authored removal credit. No equivalent replacement coverage is claimed. Existing exact unaffected receipts remain unchanged and apply only to their unchanged selectors. Changed optional selectors have no new execution result. Audit1412: src/main/runtime/orchestration-mailbox-cold-park-idle.spec.ts removed. Loses fresh-output cold-park quiet continuation, same-incarnation deferred Enter, late pointer-write settlement and explicit-check batch-claim alarms, including durable delivered_at state checks mixed into registrations0/3. No equivalent coverage claim. src/main/runtime/orchestration-dispatch-mailbox-delivery.spec.ts reduced. Loses dispatch wake dedup, restart unread guidance, legacy ack behavior and ambiguous pointer write/Enter crash retention alarms. DB-only stable delivery identity does not replace those paths. src/main/runtime/orchestration-mailbox-delivery.test.ts reduced. Driver registration/import of a whole-deleted selected spec is removed; directly authored cases remain unchanged. No extra authored removal credit. No equivalent replacement coverage is claimed. Existing exact unaffected receipts remain unchanged and apply only to their unchanged selectors. Changed optional selectors have no new execution result.", + "coverageNotes": "Audit2003: Remove simpler proven-absent/unknown-delivery, synchronous double-trigger and exit-cleanup examples from the stale-leaf source. Retain controlled waiter-versus-probe races, queued-row progression, same-ID stale-Enter cancellation and orphaned-snapshot fences. Provider/process observations and database rows in this source are supplied, not actual process absence or SQLite delivery durability. Preserve the independent five-case SQLite/CLI receipt only at its unchanged command. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, physical provider/PTY, rendered, live remote/mobile or cross-platform result is claimed. Historical prior account (preserved verbatim): Audit1973: Remove scalar, private-shape and projection examples and check cases whose reconciler runs manually before check. Retain actual federation lost-ack/gap and lifecycle completion-ack/replay alarms; unrelated message-delivery-identity receipt remains exact. Whole original changed dated receipts are retired, never narrowed or transferred. This metadata review claims no fresh execution, equivalent replacement, provider/PTY, live remote/mobile, rendered, cross-platform, soak or measured-budget result. Historical prior account (preserved verbatim): Audit1948 (Audit1946 sources 14, 26, 31, 39, 40, 47): Pruning removes scalar Run binding/projection and worker-start round-trip examples, v28 self-convergence, simple relay-type/legacy-payload and peer-probe/checkpoint cleanup examples, and reservation-failure/Cursor Enter examples. Preserve the three coordinator migration aliases, duplicate-replay waiter suppression and refused-write drain alias. The reminted-pane SQLite lookup checks active owner replacement and malformed decoys, not lookup cardinality or a performance budget. Overlapping sync uses an injected RPC/DB harness: promise coalescing and modeled checkpoint/ACK progression do not prove real durable checkpoint persistence. The complete affected combined federation receipt is retired whole; the independent unchanged CLI receipt remains in its original scope. No historical passed receipt is narrowed or transferred to changed source bytes. Unchanged dated receipts and retained assertions keep only their original independently mapped scopes. No fresh execution, equivalent replacement, physical PTY/provider, live remote/mobile, rendered, cross-platform, soak or measured-budget result is claimed by this metadata review. Historical prior account (preserved verbatim): Audit1943 (sources1934/1935/1938): Pruned notification-consistency suite loses50-row counter/wake budget and named-index EXPLAIN inventory, unbound direct-row projection and synthetic parked-decoy deadline examples. Preserve exact genuine SQLite ownership, disk-reopen/staged pointer and controlled provider-transition callbacks; fake PTY/title/clock seams do not establish actual provider delivery or measured backlog latency. Also retire three preexisting unsupported full aliases (explicit-check reservation redrive, known-PTY-exit replacement, and filtered/wait later-page draining), absent from both the frozen21and retained17callbacks. These retire stale metadata with no additional source/removal credit. Existing aliases are retained only where independently mapped below; no equivalent replacement for removed callbacks. Preserve meaningful remaining selectors/assertions and unchanged dated receipts within their original scope. Reduced selectors have no fresh gate execution evidence in this metadata packet. No equivalent replacement, physical watcher/PTY/provider, live SSH/remote/mobile, rendered, cross-platform, soak or measured-budget result is claimed. Historical prior account (preserved verbatim): Audit1734 removes low-value test definitions and retires affected assertion or execution-receipt claims without equivalent replacement coverage. Per-source reviews bound retained behavior and injected/native/remote limits. Unchanged assertions and receipts keep their original scope; gates lacking current active evidence are downgraded. Historical scope before this audit: Audit1634 removes low-value IPC, filesystem and runtime projections, argument inventories and duplicate examples without equivalent replacement coverage. Per-source reviews record retained cancellation, ownership, authorization, filesystem and SQLite boundaries with injected/native/remote limits. Affected historical receipts are retired; gates without active current evidence are downgraded. Historical scope before this audit: Audit1623 removes low-value mocked daemon projections, scalar tables and duplicate terminal examples without equivalent replacement coverage. Per-source reviews record retained terminal content preservation, process ownership refusal and recovery boundaries together with injected/native/remote limits. Affected historical receipts are retired; gates without active current evidence are downgraded. Historical scope before this audit: A deterministic isolated runtime/SQLite/RPC harness covers Run and Dispatch ownership, exact handle and remint-stable pane recipients, fixed Delivery replay and acknowledgment, restart, concurrent pollers, filtered waiters, and built-CLI parity with direct DB state. Schema v28 durably records every coordinator handle through SQLite triggers, backfills current handles on open, recreates routing triggers after old table rebuilds, and atomically canonicalizes coordinator-addressed inserts to run:. A 125-row oracle and 151-row reconciliation suite cover paged old-handle backlogs, commit-without-notify recovery, duplicate Dispatch owners, cancellation, and lifecycle fencing. Federation tests use real databases, dropped acknowledgments, restart/reconnect, and protocol v1/v2 peers; duplicate read rows do not wake again. Daemon and SSH pointer settlement is deterministic, with SSH bounded at 60 seconds beyond the 20-second mux health window. No RPC parameter, stream opcode, or published payload changed. Live headed/headless paired-runtime, physical SSH, WSL, Linux, and Windows acceptance remain gaps. Audit1312 removes low-value imported specs from aggregate test drivers; old whole-driver execution receipts are archived. Retained registrations are unchanged. No equivalent replacement coverage is claimed. Audit1383: src/main/runtime/orca-runtime-tests/worktree-native-chat-setup.spec.ts reduced. Lose command-list/receipt/default/background activation matrices whose provider spawn returns canned IDs. Retained test proves actual tab allocation separation, not terminal startup. No equivalent replacement coverage is claimed. Existing exact unaffected receipts and their platform/provider/budget claims remain scoped to unchanged selectors. Changed optional selectors have no new execution result. Audit1401: src/main/runtime/orca-runtime-tests/mobile-session-tabs-duplicate-pty-repair.spec.ts removed. Loses listing-on-already-repaired-data alarm; it never established runtime repair invocation. src/main/runtime/orca-runtime-tests/mobile-session-tabs-split-agent-tab.spec.ts removed. Loses actual mobile split sibling shell versus recorded-agent resume command alarms; no equivalent coverage asserted. src/main/runtime/orca-runtime-tests/worktree-native-chat-setup.spec.ts removed. Loses separate setup/startup tab result in that manually assembled path. src/main/runtime/orca-runtime.test.ts reduced. Driver registration/import of a whole-deleted selected spec is removed; directly authored cases remain unchanged. No extra authored removal credit. No equivalent replacement coverage is claimed. Existing exact unaffected receipts remain unchanged and apply only to their unchanged selectors. Changed optional selectors have no new execution result. Audit1412: src/main/runtime/orchestration-mailbox-cold-park-idle.spec.ts removed. Loses fresh-output cold-park quiet continuation, same-incarnation deferred Enter, late pointer-write settlement and explicit-check batch-claim alarms, including durable delivered_at state checks mixed into registrations0/3. No equivalent coverage claim. src/main/runtime/orchestration-dispatch-mailbox-delivery.spec.ts reduced. Loses dispatch wake dedup, restart unread guidance, legacy ack behavior and ambiguous pointer write/Enter crash retention alarms. DB-only stable delivery identity does not replace those paths. src/main/runtime/orchestration-mailbox-delivery.test.ts reduced. Driver registration/import of a whole-deleted selected spec is removed; directly authored cases remain unchanged. No extra authored removal credit. No equivalent replacement coverage is claimed. Existing exact unaffected receipts remain unchanged and apply only to their unchanged selectors. Changed optional selectors have no new execution result.", "motivatingLinks": [ "https://linear.app/stably/issue/STA-4325", "https://github.com/stablyai/orca/issues/14522", "internal incident evidence: improve-vps-setup, 2026-08-10" ], "invariant": "Each message has one stable row ID and authoritative recipient; coordinator-addressed current-delivery inserts are atomically owned by run:. Pointer staging may set delivered_at but never consumes mail. Each Run consumer generation has at most one outstanding Delivery with a fixed ID and fixed message IDs; ordinary checks replay it until an explicit matching acknowledgment marks exactly those rows read. Rebinding fences the old generation, notification types/counts correspond to unread rows retrievable under the same authority, and federation replay imports each stable message identity once without re-waking an already-read duplicate.", - "oracle": "Audit1973: Remove scalar, private-shape and projection examples and check cases whose reconciler runs manually before check. Retain actual federation lost-ack/gap and lifecycle completion-ack/replay alarms; unrelated message-delivery-identity receipt remains exact. Whole original changed dated receipts are retired, never narrowed or transferred. This metadata review claims no fresh execution, equivalent replacement, provider/PTY, live remote/mobile, rendered, cross-platform, soak or measured-budget result. Historical prior account (preserved verbatim): Audit1948 (Audit1946 sources 14, 26, 31, 39, 40, 47): Pruning removes scalar Run binding/projection and worker-start round-trip examples, v28 self-convergence, simple relay-type/legacy-payload and peer-probe/checkpoint cleanup examples, and reservation-failure/Cursor Enter examples. Preserve the three coordinator migration aliases, duplicate-replay waiter suppression and refused-write drain alias. The reminted-pane SQLite lookup checks active owner replacement and malformed decoys, not lookup cardinality or a performance budget. Overlapping sync uses an injected RPC/DB harness: promise coalescing and modeled checkpoint/ACK progression do not prove real durable checkpoint persistence. The complete affected combined federation receipt is retired whole; the independent unchanged CLI receipt remains in its original scope. No historical passed receipt is narrowed or transferred to changed source bytes. Unchanged dated receipts and retained assertions keep only their original independently mapped scopes. No fresh execution, equivalent replacement, physical PTY/provider, live remote/mobile, rendered, cross-platform, soak or measured-budget result is claimed by this metadata review. Historical prior account (preserved verbatim): Audit1943 (sources1934/1935/1938): Pruned notification-consistency suite loses50-row counter/wake budget and named-index EXPLAIN inventory, unbound direct-row projection and synthetic parked-decoy deadline examples. Preserve exact genuine SQLite ownership, disk-reopen/staged pointer and controlled provider-transition callbacks; fake PTY/title/clock seams do not establish actual provider delivery or measured backlog latency. Also retire three preexisting unsupported full aliases (explicit-check reservation redrive, known-PTY-exit replacement, and filtered/wait later-page draining), absent from both the frozen21and retained17callbacks. These retire stale metadata with no additional source/removal credit. Existing aliases are retained only where independently mapped below; no equivalent replacement for removed callbacks. Preserve meaningful remaining selectors/assertions and unchanged dated receipts within their original scope. Reduced selectors have no fresh gate execution evidence in this metadata packet. No equivalent replacement, physical watcher/PTY/provider, live SSH/remote/mobile, rendered, cross-platform, soak or measured-budget result is claimed. Historical prior account (preserved verbatim): Audit1734 retires changed-source assertion and execution scope without equivalent replacement coverage. Existing unchanged-selector receipts remain scoped to their original sources. Historical prior-source oracle: Existing exact unchanged-selector receipts remain the only current execution evidence. Changed source selectors have no fresh execution result. Historical prior-source oracle account: Existing exact unchanged-selector receipts remain the only current execution evidence. Changed source selectors have no fresh execution result. Historical prior-source oracle account: Seed status, dispatch, and worker_done rows across direct-handle and canonical Run recipients in an isolated DB. Compare pointer count, RPC and built-CLI check output, direct SQLite rows, unread/peek/all/type filters, concurrent pollers, fixed Delivery IDs, explicit acknowledgment, restart, filtered check --wait, and coordinator remint. Route a 125-row old-handle backlog, inject a commit without notification, and require startup repair. Exercise duplicate Run/Dispatch owners, stale panes, 50-row pages, cancellation, lifecycle fencing, and absent PTYs. Drop a federation ACK, reconnect/restart v1/v2 peers, and require stable import plus no duplicate read-row wake. Hold a healthy SSH write past five seconds but below the 60-second settlement deadline, then distinguish the three settlement outcomes end to end: only a proven refusal releases the reservation and drains a delivery parked behind the watermark; a dropped in-flight settlement must surface as unverifiable with bytes handed to the transport, preserve the durable write-attempted reservation, and emit no duplicate pointer after restart; a settled write that throws mid-pointer is unverifiable, not a refusal; and an Enter whose settlement is lost stays at enter-attempted so restart emits no second Enter. Install the production PTY controller and verify that it routes settled writes through the owning provider and refuses before any byte when the routed provider cannot settle. Census every production PTY provider class and reject a settlement synthesized from the fire-and-forget write.", + "oracle": "Audit2003: Remove simpler proven-absent/unknown-delivery, synchronous double-trigger and exit-cleanup examples from the stale-leaf source. Retain controlled waiter-versus-probe races, queued-row progression, same-ID stale-Enter cancellation and orphaned-snapshot fences. Provider/process observations and database rows in this source are supplied, not actual process absence or SQLite delivery durability. Preserve the independent five-case SQLite/CLI receipt only at its unchanged command. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, physical provider/PTY, rendered, live remote/mobile or cross-platform result is claimed. Historical prior account (preserved verbatim): Audit1973: Remove scalar, private-shape and projection examples and check cases whose reconciler runs manually before check. Retain actual federation lost-ack/gap and lifecycle completion-ack/replay alarms; unrelated message-delivery-identity receipt remains exact. Whole original changed dated receipts are retired, never narrowed or transferred. This metadata review claims no fresh execution, equivalent replacement, provider/PTY, live remote/mobile, rendered, cross-platform, soak or measured-budget result. Historical prior account (preserved verbatim): Audit1948 (Audit1946 sources 14, 26, 31, 39, 40, 47): Pruning removes scalar Run binding/projection and worker-start round-trip examples, v28 self-convergence, simple relay-type/legacy-payload and peer-probe/checkpoint cleanup examples, and reservation-failure/Cursor Enter examples. Preserve the three coordinator migration aliases, duplicate-replay waiter suppression and refused-write drain alias. The reminted-pane SQLite lookup checks active owner replacement and malformed decoys, not lookup cardinality or a performance budget. Overlapping sync uses an injected RPC/DB harness: promise coalescing and modeled checkpoint/ACK progression do not prove real durable checkpoint persistence. The complete affected combined federation receipt is retired whole; the independent unchanged CLI receipt remains in its original scope. No historical passed receipt is narrowed or transferred to changed source bytes. Unchanged dated receipts and retained assertions keep only their original independently mapped scopes. No fresh execution, equivalent replacement, physical PTY/provider, live remote/mobile, rendered, cross-platform, soak or measured-budget result is claimed by this metadata review. Historical prior account (preserved verbatim): Audit1943 (sources1934/1935/1938): Pruned notification-consistency suite loses50-row counter/wake budget and named-index EXPLAIN inventory, unbound direct-row projection and synthetic parked-decoy deadline examples. Preserve exact genuine SQLite ownership, disk-reopen/staged pointer and controlled provider-transition callbacks; fake PTY/title/clock seams do not establish actual provider delivery or measured backlog latency. Also retire three preexisting unsupported full aliases (explicit-check reservation redrive, known-PTY-exit replacement, and filtered/wait later-page draining), absent from both the frozen21and retained17callbacks. These retire stale metadata with no additional source/removal credit. Existing aliases are retained only where independently mapped below; no equivalent replacement for removed callbacks. Preserve meaningful remaining selectors/assertions and unchanged dated receipts within their original scope. Reduced selectors have no fresh gate execution evidence in this metadata packet. No equivalent replacement, physical watcher/PTY/provider, live SSH/remote/mobile, rendered, cross-platform, soak or measured-budget result is claimed. Historical prior account (preserved verbatim): Audit1734 retires changed-source assertion and execution scope without equivalent replacement coverage. Existing unchanged-selector receipts remain scoped to their original sources. Historical prior-source oracle: Existing exact unchanged-selector receipts remain the only current execution evidence. Changed source selectors have no fresh execution result. Historical prior-source oracle account: Existing exact unchanged-selector receipts remain the only current execution evidence. Changed source selectors have no fresh execution result. Historical prior-source oracle account: Seed status, dispatch, and worker_done rows across direct-handle and canonical Run recipients in an isolated DB. Compare pointer count, RPC and built-CLI check output, direct SQLite rows, unread/peek/all/type filters, concurrent pollers, fixed Delivery IDs, explicit acknowledgment, restart, filtered check --wait, and coordinator remint. Route a 125-row old-handle backlog, inject a commit without notification, and require startup repair. Exercise duplicate Run/Dispatch owners, stale panes, 50-row pages, cancellation, lifecycle fencing, and absent PTYs. Drop a federation ACK, reconnect/restart v1/v2 peers, and require stable import plus no duplicate read-row wake. Hold a healthy SSH write past five seconds but below the 60-second settlement deadline, then distinguish the three settlement outcomes end to end: only a proven refusal releases the reservation and drains a delivery parked behind the watermark; a dropped in-flight settlement must surface as unverifiable with bytes handed to the transport, preserve the durable write-attempted reservation, and emit no duplicate pointer after restart; a settled write that throws mid-pointer is unverifiable, not a refusal; and an Enter whose settlement is lost stays at enter-attempted so restart emits no second Enter. Install the production PTY controller and verify that it routes settled writes through the owning provider and refuses before any byte when the routed provider cannot settle. Census every production PTY provider class and reject a settlement synthesized from the fire-and-forget write.", "commands": [ "pnpm run build:cli && pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/orchestration-message-delivery-identity.test.ts --reporter=dot --testTimeout=5000", "pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/orchestration-mailbox-routing.test.ts src/main/runtime/orchestration-mailbox-notification-consistency.test.ts src/main/runtime/orchestration-mailbox-delivery.test.ts src/main/ipc/pty/pty-controller-ownership-routing.test.ts src/main/runtime/orchestration/db/schema/run-coordinator-handle-migration.test.ts src/main/runtime/orchestration/db/runs/orchestration-run-delivery-db.test.ts src/main/runtime/orchestration/db/worker-dispatch/orchestration-worker-dispatch-db.test.ts src/main/runtime/orchestration/mailbox/terminal-pointer/mailbox-pointer-stage.test.ts src/main/daemon/transport/client/testing/client.test.ts src/main/daemon/providers/routing/testing/daemon-pty-router.test.ts src/main/daemon/providers/routing/testing/degraded-daemon-pty-provider.test.ts", @@ -11518,7 +11316,8 @@ "Audit1948 historical original evidenceRuns object, preserved verbatim; retired objects are not active execution evidence or replacement coverage:\n{\n \"date\": \"2026-08-14\",\n \"runner\": \"local\",\n \"platform\": \"macos\",\n \"command\": \"pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/orchestration/federation-sync.test.ts src/main/runtime/rpc/methods/orchestration/federation/federation.test.ts src/main/runtime/rpc/methods/orchestration/federation/federation-lifecycle-settlement.test.ts --reporter=dot\",\n \"result\": \"passed\",\n \"durationSeconds\": 8.99,\n \"summary\": \"52 tests passed with real OrchestrationDb rows, a deliberately dropped federation acknowledgment, reconnect/restart, forward-only checkpoints, duplicate read-row wake suppression, and protocol v1/v2 lifecycle settlement replay. The broader final federation/cross-version set passed 77/77.\",\n \"currentCommand\": \"pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/orchestration/federation/synchronization/federation-sync.test.ts src/main/runtime/rpc/methods/orchestration/federation/federation.test.ts src/main/runtime/rpc/methods/orchestration/federation/federation-lifecycle-settlement.test.ts --reporter=dot\"\n }", "Audit1948 historical original assertionRefs object, preserved verbatim; retired objects are not active execution evidence or replacement coverage:\n{\n \"file\": \"src/main/runtime/orchestration/db/worker-dispatch/orchestration-worker-dispatch-db.test.ts\",\n \"assertions\": [\n \"remote Dispatch attachment lookup remains bounded across pane remints and malformed suffix collisions\"\n ]\n }", "Audit1948 historical original assertionRefs object, preserved verbatim; retired objects are not active execution evidence or replacement coverage:\n{\n \"file\": \"src/main/runtime/orchestration/federation/synchronization/federation-sync.test.ts\",\n \"assertions\": [\n \"an acknowledged duplicate replay does not wake a waiter\",\n \"overlapping syncs preserve one durable forward-only checkpoint\"\n ]\n }", - "Audit1973: Remove scalar, private-shape and projection examples and check cases whose reconciler runs manually before check. Retain actual federation lost-ack/gap and lifecycle completion-ack/replay alarms; unrelated message-delivery-identity receipt remains exact. Whole original changed dated receipts are retired, never narrowed or transferred. This metadata review claims no fresh execution, equivalent replacement, provider/PTY, live remote/mobile, rendered, cross-platform, soak or measured-budget result." + "Audit1973: Remove scalar, private-shape and projection examples and check cases whose reconciler runs manually before check. Retain actual federation lost-ack/gap and lifecycle completion-ack/replay alarms; unrelated message-delivery-identity receipt remains exact. Whole original changed dated receipts are retired, never narrowed or transferred. This metadata review claims no fresh execution, equivalent replacement, provider/PTY, live remote/mobile, rendered, cross-platform, soak or measured-budget result.", + "Audit2003: Remove simpler proven-absent/unknown-delivery, synchronous double-trigger and exit-cleanup examples from the stale-leaf source. Retain controlled waiter-versus-probe races, queued-row progression, same-ID stale-Enter cancellation and orphaned-snapshot fences. Provider/process observations and database rows in this source are supplied, not actual process absence or SQLite delivery durability. Preserve the independent five-case SQLite/CLI receipt only at its unchanged command. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, physical provider/PTY, rendered, live remote/mobile or cross-platform result is claimed." ], "demotionRule": "Demote or quarantine if the deterministic oracle flakes without a product or harness bug owned by orchestration." }, @@ -11946,7 +11745,7 @@ "providers": ["local", "daemon", "ssh", "wsl", "remote-runtime"], "coveredPlatforms": ["macos"], "coveredProviders": [], - "coverageNotes": "Audit1983: Remove unsupported user-taken-over, transferred-owner, exact-reuse, host-scope, immutable-transcript, legacy-ambiguity, reset and50-worker aliases from their claimed sources. Remove the deleted pending-release ledger replay alias. Narrow surviving external-retention and recovery aliases to actual retained callbacks; no transfer to another file or process-restart proof. Preserve the independent receipt. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, provider/PTY, rendered, live remote/mobile or cross-platform result is claimed. Historical prior account (preserved verbatim): Audit1959 (sources 1955:34): Pruning removes unsupported-provider/out-of-range scalar refusals and the single Grok happy decoder fixture. Retain actual file cursor append/paging, same-inode checkpoint invalidation, opaque IDs and oversized-line progress. Retire the complete dated 3.48s combined receipt through its effective currentCommand, without changing its original command or transferring its 56-test result. Unchanged CLI/Electron receipt retains its independent scope. No historical passed receipt is narrowed or transferred to changed source bytes. No fresh execution, equivalent replacement, physical provider/PTY, live remote/mobile, rendered, cross-platform, soak or measured-budget result is claimed by this metadata packet. Historical prior account (preserved verbatim): Audit1379 changes src/main/runtime/orca-runtime-process-incarnation-liveness.test.ts (7 authored registrations removed; 0 retained). Intentional losses: Loses supplied host lists and record states mapped to liveness enums and routing counts; no live/dead process evidence. No equivalent replacement coverage is claimed. Remaining exact receipts validate only their unchanged selectors. Historical coverage account: Only Keep open keeps a worker; a phone or desktop names it by handle, pane or session, and older clients' typing reports record nothing. Host byte lanes perform zero orchestration SQL work; local and injected SSH Keep open tests fence release. Deterministic service tests cover release-versus-reuse ordering, transactional retain and takeover cancellation, exact host/pane/process identity, dead external/user-owned/transferred/stopped/abandoned reconciliation, host-partition persistence and legacy retirement replay with an absent web-terminal layout map, conservative unknown provider and legacy metadata handling, immutable transcript and bounded terminal archives, mutation restart, reset cleanup, replay idempotency, and 50-resource accounting. A macOS Electron journey invokes the freshly compiled worker-release CLI after the worker process disappears, then independently checks released SQLite state and coordinator liveness. Injected inventories cover local and SSH provider routing; live SSH, WSL, Windows, paired-runtime, and provider-close lost-ack journeys remain explicit gaps. Audit1396: src/main/runtime/rpc/methods/orchestration/worker/worker-release-mobile-report.test.ts reduced. Retires unary/stream no-database-work call counts and three mobile RPC allowlist membership checks. Ancillary input delivery assertions in the budget registration intentionally lost without equivalence claim. No equivalent replacement coverage is claimed. Existing exact unaffected receipts remain unchanged and apply only to their unchanged selectors. Changed optional selectors have no new execution result.", + "coverageNotes": "Audit2003: Remove3 preexisting unsupported320-live-PTY,400-retirement/256-history and replacement-lifecycle aliases from the inventory driver: neither its frozen original imported registrars nor proposed candidate owns those callbacks. This metadata correction adds no test-removal credit. The driver loses the empty-worktree-publication suite and3 folder-host/unverifiable scalar examples while retaining concurrent inventory freshness and provider-failure verdict fences. The retained inventory callbacks are not terminal-release, historical-cache capacity or performance-budget replacement coverage. Preserve the independent compiled-CLI Electron receipt exactly. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, physical provider/PTY, rendered, live remote/mobile or cross-platform result is claimed. Historical prior account (preserved verbatim): Audit1983: Remove unsupported user-taken-over, transferred-owner, exact-reuse, host-scope, immutable-transcript, legacy-ambiguity, reset and50-worker aliases from their claimed sources. Remove the deleted pending-release ledger replay alias. Narrow surviving external-retention and recovery aliases to actual retained callbacks; no transfer to another file or process-restart proof. Preserve the independent receipt. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, provider/PTY, rendered, live remote/mobile or cross-platform result is claimed. Historical prior account (preserved verbatim): Audit1959 (sources 1955:34): Pruning removes unsupported-provider/out-of-range scalar refusals and the single Grok happy decoder fixture. Retain actual file cursor append/paging, same-inode checkpoint invalidation, opaque IDs and oversized-line progress. Retire the complete dated 3.48s combined receipt through its effective currentCommand, without changing its original command or transferring its 56-test result. Unchanged CLI/Electron receipt retains its independent scope. No historical passed receipt is narrowed or transferred to changed source bytes. No fresh execution, equivalent replacement, physical provider/PTY, live remote/mobile, rendered, cross-platform, soak or measured-budget result is claimed by this metadata packet. Historical prior account (preserved verbatim): Audit1379 changes src/main/runtime/orca-runtime-process-incarnation-liveness.test.ts (7 authored registrations removed; 0 retained). Intentional losses: Loses supplied host lists and record states mapped to liveness enums and routing counts; no live/dead process evidence. No equivalent replacement coverage is claimed. Remaining exact receipts validate only their unchanged selectors. Historical coverage account: Only Keep open keeps a worker; a phone or desktop names it by handle, pane or session, and older clients' typing reports record nothing. Host byte lanes perform zero orchestration SQL work; local and injected SSH Keep open tests fence release. Deterministic service tests cover release-versus-reuse ordering, transactional retain and takeover cancellation, exact host/pane/process identity, dead external/user-owned/transferred/stopped/abandoned reconciliation, host-partition persistence and legacy retirement replay with an absent web-terminal layout map, conservative unknown provider and legacy metadata handling, immutable transcript and bounded terminal archives, mutation restart, reset cleanup, replay idempotency, and 50-resource accounting. A macOS Electron journey invokes the freshly compiled worker-release CLI after the worker process disappears, then independently checks released SQLite state and coordinator liveness. Injected inventories cover local and SSH provider routing; live SSH, WSL, Windows, paired-runtime, and provider-close lost-ack journeys remain explicit gaps. Audit1396: src/main/runtime/rpc/methods/orchestration/worker/worker-release-mobile-report.test.ts reduced. Retires unary/stream no-database-work call counts and three mobile RPC allowlist membership checks. Ancillary input delivery assertions in the budget registration intentionally lost without equivalence claim. No equivalent replacement coverage is claimed. Existing exact unaffected receipts remain unchanged and apply only to their unchanged selectors. Changed optional selectors have no new execution result.", "motivatingLinks": [ "https://github.com/stablyai/orca/pull/12355", "https://github.com/stablyai/orca/issues/13860", @@ -11955,7 +11754,7 @@ "STA-5122" ], "invariant": "A settled Dispatch may close only its one coordinator-created terminal lease. Explicit reuse, Keep open, retain, identity or host change, ambiguity, and another resource for the same exact host/pane/process must fence closure. Once the authoritative owning provider positively excludes the resource's exact immutable process incarnation, even an external, user-owned, or transferred dead resource must converge to released without any process close. Unknown host scope, missing incarnation metadata, or unavailable inventory must remain retained. Exact terminal-close persistence must settle when a host partition omits renderer-owned layout state. Output preservation and the requested-to-releasing transition are atomic, archives remain readable without the provider file, retries resume idempotently, and orchestration reset removes archive and authority state.", - "oracle": "Audit1983: Remove unsupported user-taken-over, transferred-owner, exact-reuse, host-scope, immutable-transcript, legacy-ambiguity, reset and50-worker aliases from their claimed sources. Remove the deleted pending-release ledger replay alias. Narrow surviving external-retention and recovery aliases to actual retained callbacks; no transfer to another file or process-restart proof. Preserve the independent receipt. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, provider/PTY, rendered, live remote/mobile or cross-platform result is claimed. Historical prior account (preserved verbatim): Audit1959 (sources 1955:34): Pruning removes unsupported-provider/out-of-range scalar refusals and the single Grok happy decoder fixture. Retain actual file cursor append/paging, same-inode checkpoint invalidation, opaque IDs and oversized-line progress. Retire the complete dated 3.48s combined receipt through its effective currentCommand, without changing its original command or transferring its 56-test result. Unchanged CLI/Electron receipt retains its independent scope. No historical passed receipt is narrowed or transferred to changed source bytes. No fresh execution, equivalent replacement, physical provider/PTY, live remote/mobile, rendered, cross-platform, soak or measured-budget result is claimed by this metadata packet. Historical prior account (preserved verbatim): Existing exact unchanged-selector receipts remain the only current execution evidence. Changed source selectors have no fresh execution result. Historical prior-source oracle account: Surviving unchanged-source receipts are the current oracle; changed-source assertions await validation. Historical oracle account: Record release intent for a settled owner, attempt exact reuse before close, and require worker-start to fail with terminal_release_in_progress while the terminal stays open; then release the original owner exactly once. Race retain against a controlled archive promise and require no committed archive or close. Rebase a closed web-terminal host partition without terminalLayoutsByTabId and require the persistence write to complete while preserving host-authoritative membership; replay a valid legacy retirement under the same omission and require exact membership removal plus revision advancement. For retained external, user-owned, transferred, stopped, and abandoned resources, run one fresh inventory against the exact local/WSL or SSH provider: an exact live incarnation and every unknown inventory shape stay retained, while positive absence atomically sets ownership_state and release_state to released with processAction none and zero closeTerminal calls. Change host or process identity and inject duplicate resource evidence to require retention. Freeze a structured transcript, delete its source file, and require archived worker-read to return the same bounded redacted messages. Restart a pending mutation, reset orchestration state, and create 50 resources while asserting replay convergence, zero orphan rows, two-query worker listing, and no unrelated close.", + "oracle": "Audit2003: Remove3 preexisting unsupported320-live-PTY,400-retirement/256-history and replacement-lifecycle aliases from the inventory driver: neither its frozen original imported registrars nor proposed candidate owns those callbacks. This metadata correction adds no test-removal credit. The driver loses the empty-worktree-publication suite and3 folder-host/unverifiable scalar examples while retaining concurrent inventory freshness and provider-failure verdict fences. The retained inventory callbacks are not terminal-release, historical-cache capacity or performance-budget replacement coverage. Preserve the independent compiled-CLI Electron receipt exactly. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, physical provider/PTY, rendered, live remote/mobile or cross-platform result is claimed. Historical prior account (preserved verbatim): Audit1983: Remove unsupported user-taken-over, transferred-owner, exact-reuse, host-scope, immutable-transcript, legacy-ambiguity, reset and50-worker aliases from their claimed sources. Remove the deleted pending-release ledger replay alias. Narrow surviving external-retention and recovery aliases to actual retained callbacks; no transfer to another file or process-restart proof. Preserve the independent receipt. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, provider/PTY, rendered, live remote/mobile or cross-platform result is claimed. Historical prior account (preserved verbatim): Audit1959 (sources 1955:34): Pruning removes unsupported-provider/out-of-range scalar refusals and the single Grok happy decoder fixture. Retain actual file cursor append/paging, same-inode checkpoint invalidation, opaque IDs and oversized-line progress. Retire the complete dated 3.48s combined receipt through its effective currentCommand, without changing its original command or transferring its 56-test result. Unchanged CLI/Electron receipt retains its independent scope. No historical passed receipt is narrowed or transferred to changed source bytes. No fresh execution, equivalent replacement, physical provider/PTY, live remote/mobile, rendered, cross-platform, soak or measured-budget result is claimed by this metadata packet. Historical prior account (preserved verbatim): Existing exact unchanged-selector receipts remain the only current execution evidence. Changed source selectors have no fresh execution result. Historical prior-source oracle account: Surviving unchanged-source receipts are the current oracle; changed-source assertions await validation. Historical oracle account: Record release intent for a settled owner, attempt exact reuse before close, and require worker-start to fail with terminal_release_in_progress while the terminal stays open; then release the original owner exactly once. Race retain against a controlled archive promise and require no committed archive or close. Rebase a closed web-terminal host partition without terminalLayoutsByTabId and require the persistence write to complete while preserving host-authoritative membership; replay a valid legacy retirement under the same omission and require exact membership removal plus revision advancement. For retained external, user-owned, transferred, stopped, and abandoned resources, run one fresh inventory against the exact local/WSL or SSH provider: an exact live incarnation and every unknown inventory shape stay retained, while positive absence atomically sets ownership_state and release_state to released with processAction none and zero closeTerminal calls. Change host or process identity and inject duplicate resource evidence to require retention. Freeze a structured transcript, delete its source file, and require archived worker-read to return the same bounded redacted messages. Restart a pending mutation, reset orchestration state, and create 50 resources while asserting replay convergence, zero orphan rows, two-query worker listing, and no unrelated close.", "commands": [ "ORCA_BACKGROUND_LAUNCH=1 pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/rpc/methods/orchestration/worker/worker-release-mobile-report.test.ts src/main/runtime/terminal/identity/orca-runtime-terminal-handle-incarnation.test.ts", "pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/terminal/query/runtime-terminal-inventory-contracts.test.ts src/main/runtime/terminal/surface/mobile-session-terminal-persistence-retirement.test.ts src/main/runtime/rpc/methods/orchestration/worker/worker-release.test.ts src/main/runtime/rpc/methods/orchestration/worker/worker-release-recovery.test.ts src/main/runtime/rpc/orchestration-mutation-ledger.test.ts src/main/runtime/orchestration/workers/transcripts/worker-transcript-read.test.ts --reporter=dot", @@ -11977,14 +11776,6 @@ "tests/e2e/orchestration-worker-settlement-release-cli.spec.ts" ], "assertionRefs": [ - { - "file": "src/main/runtime/terminal/query/runtime-terminal-inventory-contracts.test.ts", - "assertions": [ - "320 simultaneously live PTYs retain truthful verdicts with linear identity checks and no detached history", - "400 unresolved PTY retirements preserve active doubt while bounding history at 256 entries", - "a replacement lifecycle clears the retained historical verdict for the reused PTY id" - ] - }, { "file": "src/main/runtime/terminal/surface/mobile-session-terminal-persistence-retirement.test.ts", "assertions": [ @@ -12044,11 +11835,11 @@ }, "redGreenEvidence": { "status": "missing", - "evidence": "Audit1379 changes test sources; reduced selector has no fresh red/green result. Historical account: The version-skew legacy-retirement test deterministically threw at mobile-session-terminal-persistence-retirement.ts:75 before the null-safe layout read and passed with exact tab removal, tombstone cleanup, and topology-revision advancement after the fix. The byte-identical compiled-CLI Electron oracle left the dead resource external/retained on latest main 5ea7df1a5b, passed on combined candidate d697666ce8 with released/released SQLite state and processAction none, and reproduced external/retained after disabling the claimed production files at merge-base 64aec94cb2. The earlier unchanged three-case dead external/user-owned/transferred service oracle likewise failed 3/3 on main, passed 3/3 on candidate, and failed 3/3 with production restored; every run asserted durable state and zero terminal close calls. The 320-live-PTY oracle failed on the prior single-map implementation and passes with complete active evidence, zero detached history, and a linear identity-check bound after the cache split." + "evidence": "Audit2003: Remove3 preexisting unsupported320-live-PTY,400-retirement/256-history and replacement-lifecycle aliases from the inventory driver: neither its frozen original imported registrars nor proposed candidate owns those callbacks. This metadata correction adds no test-removal credit. The driver loses the empty-worktree-publication suite and3 folder-host/unverifiable scalar examples while retaining concurrent inventory freshness and provider-failure verdict fences. The retained inventory callbacks are not terminal-release, historical-cache capacity or performance-budget replacement coverage. Preserve the independent compiled-CLI Electron receipt exactly. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, physical provider/PTY, rendered, live remote/mobile or cross-platform result is claimed. Historical prior account (preserved verbatim): Audit1379 changes test sources; reduced selector has no fresh red/green result. Historical account: The version-skew legacy-retirement test deterministically threw at mobile-session-terminal-persistence-retirement.ts:75 before the null-safe layout read and passed with exact tab removal, tombstone cleanup, and topology-revision advancement after the fix. The byte-identical compiled-CLI Electron oracle left the dead resource external/retained on latest main 5ea7df1a5b, passed on combined candidate d697666ce8 with released/released SQLite state and processAction none, and reproduced external/retained after disabling the claimed production files at merge-base 64aec94cb2. The earlier unchanged three-case dead external/user-owned/transferred service oracle likewise failed 3/3 on main, passed 3/3 on candidate, and failed 3/3 with production restored; every run asserted durable state and zero terminal close calls. The 320-live-PTY oracle failed on the prior single-map implementation and passes with complete active evidence, zero detached history, and a linear identity-check bound after the cache split." }, "performanceBudget": { "required": true, - "evidence": "Historical prior-selector budget; reduced selector has no fresh measurement. Previous account: Normal owned release performs constant-count indexed resource and identity queries plus one bounded archive capture. Missing layout maps use constant-time empty-record fallbacks inside the existing explicit persistence pass, with no added scan or allocation proportional to terminal history. A retained release performs exactly one bounded inventory against its authoritative local/WSL or specific SSH provider, with no retry, polling, timer, subprocess, renderer subscription, or per-session follow-up fanout. Each liveness observation performs constant-time active-identity classification; retirement performs one historical insertion and at most one oldest-entry eviction, while active evidence scales only with supported PTYs and detached history is capped at 256. Worker-list uses two set queries rather than one resource lookup per worker." + "evidence": "Audit2003: Remove3 preexisting unsupported320-live-PTY,400-retirement/256-history and replacement-lifecycle aliases from the inventory driver: neither its frozen original imported registrars nor proposed candidate owns those callbacks. This metadata correction adds no test-removal credit. The driver loses the empty-worktree-publication suite and3 folder-host/unverifiable scalar examples while retaining concurrent inventory freshness and provider-failure verdict fences. The retained inventory callbacks are not terminal-release, historical-cache capacity or performance-budget replacement coverage. Preserve the independent compiled-CLI Electron receipt exactly. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, physical provider/PTY, rendered, live remote/mobile or cross-platform result is claimed. Historical prior account (preserved verbatim): Historical prior-selector budget; reduced selector has no fresh measurement. Previous account: Normal owned release performs constant-count indexed resource and identity queries plus one bounded archive capture. Missing layout maps use constant-time empty-record fallbacks inside the existing explicit persistence pass, with no added scan or allocation proportional to terminal history. A retained release performs exactly one bounded inventory against its authoritative local/WSL or specific SSH provider, with no retry, polling, timer, subprocess, renderer subscription, or per-session follow-up fanout. Each liveness observation performs constant-time active-identity classification; retirement performs one historical insertion and at most one oldest-entry eviction, while active evidence scales only with supported PTYs and detached history is capped at 256. Worker-list uses two set queries rather than one resource lookup per worker." }, "promotionCriteria": [ "Collect 100 consecutive focused CI passes or 14 days of soak history.", @@ -12071,7 +11862,9 @@ "Audit1983: Remove unsupported user-taken-over, transferred-owner, exact-reuse, host-scope, immutable-transcript, legacy-ambiguity, reset and50-worker aliases from their claimed sources. Remove the deleted pending-release ledger replay alias. Narrow surviving external-retention and recovery aliases to actual retained callbacks; no transfer to another file or process-restart proof. Preserve the independent receipt. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, provider/PTY, rendered, live remote/mobile or cross-platform result is claimed.", "Audit1983 historical original assertionRefs object, preserved verbatim; retired objects are not active execution evidence or replacement coverage:\n{\n \"file\": \"src/main/runtime/rpc/methods/orchestration/worker/worker-release.test.ts\",\n \"assertions\": [\n \"reconciles a dead external terminal without closing a process\",\n \"reconciles a dead user-taken-over terminal without closing a process\",\n \"reconciles a dead stopped or abandoned worker without closing a process\",\n \"reconciles dead transferred ownership after the current owner settles\",\n \"rejects exact reuse after release intent instead of closing the new worker\",\n \"lets an explicit retain cancel a release while output capture is pending\",\n \"retains when the terminal host scope changed instead of closing\",\n \"reads an immutable transcript snapshot after the provider file disappears\",\n \"backfills a legacy creator plus explicit reuser as ambiguous\",\n \"removes terminal authority and archived output on orchestration reset\"\n ]\n }", "Audit1983 historical original assertionRefs object, preserved verbatim; retired objects are not active execution evidence or replacement coverage:\n{\n \"file\": \"src/main/runtime/rpc/orchestration-mutation-ledger.test.ts\",\n \"assertions\": [\"resumes a pending idempotent worker release after restart\"]\n }", - "Audit1983 historical original assertionRefs object, preserved verbatim; retired objects are not active execution evidence or replacement coverage:\n{\n \"file\": \"src/main/runtime/rpc/methods/orchestration/worker/worker-release-recovery.test.ts\",\n \"assertions\": [\n \"finishes a requested release after restart-style interruption\",\n \"coalesces overlapping reconciliation passes and closes each resource once\",\n \"keeps live terminals bounded across 50 settled workers while controls survive\"\n ]\n }" + "Audit1983 historical original assertionRefs object, preserved verbatim; retired objects are not active execution evidence or replacement coverage:\n{\n \"file\": \"src/main/runtime/rpc/methods/orchestration/worker/worker-release-recovery.test.ts\",\n \"assertions\": [\n \"finishes a requested release after restart-style interruption\",\n \"coalesces overlapping reconciliation passes and closes each resource once\",\n \"keeps live terminals bounded across 50 settled workers while controls survive\"\n ]\n }", + "Audit2003: Remove3 preexisting unsupported320-live-PTY,400-retirement/256-history and replacement-lifecycle aliases from the inventory driver: neither its frozen original imported registrars nor proposed candidate owns those callbacks. This metadata correction adds no test-removal credit. The driver loses the empty-worktree-publication suite and3 folder-host/unverifiable scalar examples while retaining concurrent inventory freshness and provider-failure verdict fences. The retained inventory callbacks are not terminal-release, historical-cache capacity or performance-budget replacement coverage. Preserve the independent compiled-CLI Electron receipt exactly. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, physical provider/PTY, rendered, live remote/mobile or cross-platform result is claimed.", + "Audit2003 historical original assertionRefs object, preserved verbatim; retired objects are not active execution evidence or replacement coverage:\n{\n \"file\": \"src/main/runtime/terminal/query/runtime-terminal-inventory-contracts.test.ts\",\n \"assertions\": [\n \"320 simultaneously live PTYs retain truthful verdicts with linear identity checks and no detached history\",\n \"400 unresolved PTY retirements preserve active doubt while bounding history at 256 entries\",\n \"a replacement lifecycle clears the retained historical verdict for the reused PTY id\"\n ]\n }" ], "demotionRule": "Keep experimental or demote if release can overlap exact reuse, close a conflicting lease, lose immutable output, retain reset archives, fan out per worker, or any focused ordering test flakes without a product or harness defect." }, diff --git a/config/scripts/ci/sharding/ci-shard-timings.json b/config/scripts/ci/sharding/ci-shard-timings.json index 012b8e78cf9..26aa1e72dda 100644 --- a/config/scripts/ci/sharding/ci-shard-timings.json +++ b/config/scripts/ci/sharding/ci-shard-timings.json @@ -3408,7 +3408,6 @@ "src/main/runtime/headless-move-leaf-to-new-tab.test.ts": 3596, "src/main/runtime/terminal/output/headless-terminal-dispose-write-ordering.test.ts": 4309, "src/main/runtime/terminal/output/headless-terminal-memory-bound.test.ts": 5615, - "src/main/runtime/terminal/layout/headless-terminal-split-layout.test.ts": 55, "src/main/runtime/held-pane-reopen-layout-owner.test.ts": 5093, "src/main/runtime/hermes-slash-readiness-transcript.test.ts": 4931, "src/main/runtime/hook-status-resume-sleep-ordering.test.ts": 9614, @@ -3462,8 +3461,6 @@ "src/main/runtime/mobile-session-editor-projection.test.ts": 57, "src/main/runtime/session-lifecycle/publication/mobile-session-tabs-agent-status-heartbeat.test.ts": 65, "src/main/runtime/terminal/surface/mobile-session-terminal-persistence-retirement.test.ts": 278, - "src/main/runtime/terminal/surface/mobile-session-terminal-retirement-proof.test.ts": 48, - "src/main/runtime/terminal/surface/mobile-session-terminal-retirement.test.ts": 236, "src/main/runtime/mobile-session-window-tab-order.test.ts": 4194, "src/main/runtime/mobile-store-orphaned-temp-cleanup.test.ts": 320, "src/main/runtime/mobile-subscribe-idle-pane-hydrate.test.ts": 4782, @@ -3482,7 +3479,6 @@ "src/main/runtime/orca-runtime-codex-idle-title-chat-address.test.ts": 5781, "src/main/runtime/orca-runtime-cold-restore-terminal-membership-layout-owner.test.ts": 4130, "src/main/runtime/orca-runtime-cold-restore-terminal-membership.test.ts": 3716, - "src/main/runtime/workspaces/creation/orca-runtime-create-base-prefetch.test.ts": 4048, "src/main/runtime/orca-runtime-exact-worker-provider-session.test.ts": 3617, "src/main/runtime/orca-runtime-exited-split-pane-close.test.ts": 4235, "src/main/runtime/orca-runtime-failed-start-pane-layout-owner.test.ts": 4204, @@ -3516,12 +3512,10 @@ "src/main/runtime/terminal/identity/orca-runtime-terminal-handle-incarnation.test.ts": 4449, "src/main/runtime/terminal/title/orca-runtime-terminal-rename-retention.test.ts": 5757, "src/main/runtime/terminal/surface/orca-runtime-terminal-retirement.test.ts": 3714, - "src/main/runtime/terminal/title/orca-runtime-terminal-saved-name-respawn.test.ts": 4349, "src/main/runtime/terminal/layout/orca-runtime-terminal-split-authority.test.ts": 4420, "src/main/runtime/terminal/surface/orca-runtime-terminal-surface-close.test.ts": 5648, "src/main/runtime/orca-runtime-windowless-terminal-activity.test.ts": 3277, "src/main/runtime/orca-runtime-workspace-ports-host.test.ts": 3642, - "src/main/runtime/workspaces/catalog/orca-runtime-worktree-change-invalidation.test.ts": 4110, "src/main/runtime/workspaces/creation/orca-runtime-worktree-provisioning-availability.test.ts": 4411, "src/main/runtime/orca-runtime.test.ts": 43162, "src/main/runtime/orchestration-cli-restart-handoff.test.ts": 20192, @@ -3968,11 +3962,9 @@ "src/main/runtime/browser/client-pages/runtime-browser-page-registry.test.ts": 52, "src/main/runtime/client/settings/runtime-client-agent-launch-rollback.test.ts": 1577, "src/main/runtime/client/settings/runtime-client-agent-launch-settings-durability.test.ts": 3333, - "src/main/runtime/terminal/launch/runtime-created-worktree-terminal-target.test.ts": 5528, "src/main/runtime/runtime-desktop-pane-title-forwarding.test.ts": 6114, "src/main/runtime/workspaces/removal/runtime-failed-local-worktree-removal.test.ts": 1333, "src/main/runtime/runtime-file-target-execution-host.test.ts": 4187, - "src/main/runtime/workspaces/removal/runtime-folder-workspace-delete-lifecycle.test.ts": 4190, "src/main/runtime/workspaces/startup/runtime-folder-workspace-wsl-failed-start-pane.test.ts": 4472, "src/main/runtime/workspaces/creation/runtime-folder-workspace-wsl-launch-scope.test.ts": 3705, "src/main/runtime/integrations/git/runtime-git-generation-admission.test.ts": 595, @@ -3987,7 +3979,6 @@ "src/main/runtime/integrations/linear/runtime-linear-comment-edit.test.ts": 458, "src/main/runtime/integrations/linear/runtime-linear-write-lifetime.test.ts": 512, "src/main/runtime/workspaces/creation/runtime-local-worktree-create-pr-conflict.test.ts": 462, - "src/main/runtime/workspaces/creation/runtime-local-worktree-create-pr-conflicts.test.ts": 490, "src/main/runtime/workspaces/creation/runtime-local-worktree-create.test.ts": 693, "src/main/runtime/workspaces/startup/runtime-local-worktree-terminal-startup-layout-owner.test.ts": 76, "src/main/runtime/workspaces/startup/runtime-local-worktree-terminal-startup.test.ts": 79, @@ -4011,7 +4002,6 @@ "src/main/runtime/repositories/runtime-repository-clone-host-dedupe.test.ts": 874, "src/main/runtime/repositories/runtime-repository-issue-command.test.ts": 557, "src/main/runtime/repositories/runtime-repository-registration-controller.test.ts": 742, - "src/main/runtime/workspaces/catalog/runtime-resolved-worktree-cache.test.ts": 46, "src/main/runtime/rpc/server/runtime-rpc-browser-host-admission.test.ts": 4402, "src/main/runtime/rpc/server/runtime-rpc-delegated-device-sync.test.ts": 4986, "src/main/runtime/rpc/server/runtime-rpc-device-key-dispatch.test.ts": 3597, @@ -4037,14 +4027,10 @@ "src/main/runtime/terminal/identity/runtime-terminal-handle-contracts.test.ts": 3983, "src/main/runtime/terminal/wait/runtime-terminal-idle-polls.test.ts": 240, "src/main/runtime/terminal/query/runtime-terminal-inventory-contracts.test.ts": 4427, - "src/main/runtime/terminal/adoption/runtime-terminal-orphan-adoption-duplicate-pty.test.ts": 422, "src/main/runtime/terminal/adoption/runtime-terminal-orphan-adoption-rollback.test.ts": 1832, - "src/main/runtime/terminal/adoption/runtime-terminal-orphan-session-adoption.test.ts": 309, - "src/main/runtime/terminal/adoption/runtime-terminal-orphan-topology-validation.test.ts": 59, "src/main/runtime/terminal/surface/runtime-terminal-retirement-contracts.test.ts": 4505, "src/main/runtime/terminal/title/runtime-terminal-set-pane-title.test.ts": 5216, "src/main/runtime/terminal/input/runtime-terminal-writer.test.ts": 150, - "src/main/runtime/workspaces/catalog/runtime-visible-catalog.test.ts": 56, "src/main/runtime/runtime-workspace-launch-contracts.test.ts": 4059, "src/main/runtime/workspaces/session-hosts/runtime-workspace-session-controller-twin-repo.test.ts": 298, "src/main/runtime/workspaces/process-listing/runtime-worktree-agent-rows-verdict.test.ts": 1458, @@ -4052,9 +4038,6 @@ "src/main/runtime/workspaces/creation/runtime-worktree-create-git.test.ts": 600, "src/main/runtime/workspaces/creation/runtime-worktree-create-repo-host.test.ts": 4484, "src/main/runtime/workspaces/lineage/runtime-worktree-lineage-controller.test.ts": 1027, - "src/main/runtime/workspaces/process-listing/runtime-worktree-ps-summaries.test.ts": 191, - "src/main/runtime/workspaces/process-listing/runtime-worktree-ps-unverifiable-terminals.test.ts": 45, - "src/main/runtime/workspaces/catalog/runtime-worktree-selection.test.ts": 52, "src/main/runtime/workspaces/startup/runtime-worktree-startup-readiness.test.ts": 289, "src/main/runtime/workspaces/process-listing/runtime-worktree-structured-agent-rows-liveness.test.ts": 1412, "src/main/runtime/workspaces/startup/runtime-worktree-terminal-provisioning.test.ts": 84, @@ -4133,14 +4116,12 @@ "src/main/runtime/terminal-agent-interrupt.test.ts": 54, "src/main/runtime/terminal-agent-phone-push.test.ts": 172, "src/main/runtime/terminal-agent-prompt-answer.test.ts": 100, - "src/main/runtime/terminal-chat-owner-persistence.test.ts": 4139, "src/main/runtime/terminal-close-observed-exit.test.ts": 5914, "src/main/runtime/terminal-close-slow-agent-exit.test.ts": 15751, "src/main/runtime/terminal-create-closed-tab-durability.test.ts": 5767, "src/main/runtime/terminal-focus-navigation-coalescer.test.ts": 55, "src/main/runtime/terminal-foreground-capture.test.ts": 296, "src/main/runtime/terminal-identity-probe.test.ts": 46, - "src/main/runtime/terminal/launch/terminal-image-viewer-env.test.ts": 163, "src/main/runtime/terminal-intentional-stop-exit.test.ts": 9894, "src/main/runtime/terminal-intentional-stops.test.ts": 48, "src/main/runtime/terminal-interactive-wait-visibility.test.ts": 4552, @@ -4148,12 +4129,10 @@ "src/main/runtime/terminal-kitty-file-wsl-root.test.ts": 118, "src/main/runtime/terminal-list-payload-size.test.ts": 4185, "src/main/runtime/terminal-list-pending-pty-registration.test.ts": 4780, - "src/main/runtime/terminal/adoption/terminal-orphan-topology.test.ts": 46, "src/main/runtime/terminal-pane-recovery-liveness-gate.test.ts": 4411, "src/main/runtime/terminal-query-responder.test.ts": 27968, "src/main/runtime/terminal-restore-record-seed.test.ts": 4073, "src/main/runtime/terminal-retirement-async-durability.test.ts": 5903, - "src/main/runtime/terminal-retirement-proof-emitted-frame.test.ts": 3961, "src/main/runtime/terminal-retirement-proof-publication.test.ts": 4976, "src/main/runtime/terminal-run-facts-input.test.ts": 4370, "src/main/runtime/terminal-run-facts.test.ts": 68, @@ -4162,9 +4141,7 @@ "src/main/runtime/terminal-start-failure-readiness.test.ts": 3416, "src/main/runtime/terminal-subscribe-exit-waiter-leak.test.ts": 4118, "src/main/runtime/terminal-subscriber-driven-daemon-attach.test.ts": 5399, - "src/main/runtime/terminal/surface/terminal-surface-close.test.ts": 120, "src/main/runtime/terminal/output/tail/terminal-tail-crlf.test.ts": 4235, - "src/main/runtime/terminal/output/tail/terminal-tail-redraw-scaling.test.ts": 352, "src/main/runtime/terminal/output/tail/terminal-tail-redraw-spans.test.ts": 166, "src/main/runtime/terminal-tail-redraw-wall-time.test.ts": 43418, "src/main/runtime/terminal/output/tail/terminal-tail-sentinel-index.test.ts": 3649, @@ -4183,7 +4160,6 @@ "src/main/runtime/terminal/launch/mobile-terminal-chat-fork.test.ts": 3900, "src/main/runtime/terminal/launch/terminal-split-layout-owner.test.ts": 4503, "src/main/runtime/terminal/launch/terminal-split-retry.test.ts": 4038, - "src/main/runtime/terminal/launch/window-tab-start-switch-off.test.ts": 4475, "src/main/runtime/terminal/layout/agent-generated-tab-titles.test.ts": 5572, "src/main/runtime/terminal/lifecycle/main-pane-liveness.test.ts": 4871, "src/main/runtime/terminal/lifecycle/ssh-exit-code-pane-outcome.test.ts": 4334, @@ -4193,17 +4169,14 @@ "src/main/runtime/terminal/stop/terminal-update-admission.test.ts": 48, "src/main/runtime/terminal/surface/closed-pane-exit-cleanup-main.test.ts": 4281, "src/main/runtime/terminal/surface/main-terminal-acquisition.test.ts": 4330, - "src/main/runtime/terminal/surface/pty-pane-copies-untouched-main.test.ts": 4585, "src/main/runtime/terminal/surface/terminal-surface-replies-main.test.ts": 4324, "src/main/runtime/terminal/surface/terminal-surface-replies-window.test.ts": 4440, "src/main/runtime/trust-dialog-status-observer.test.ts": 4458, - "src/main/runtime/terminal/wait/tui-idle-claude-task-wakeup.test.ts": 185, "src/main/runtime/terminal/wait/tui-idle-codex-plan-menu.test.ts": 4441, "src/main/runtime/terminal/wait/tui-idle-command-paint.test.ts": 4148, "src/main/runtime/terminal/wait/tui-idle-hook-lane-runtime.test.ts": 7702, "src/main/runtime/terminal/wait/tui-idle-name-only-real-pty.integration.test.ts": 39042, "src/main/runtime/unreadable-secret-store-preservation.win32.test.ts": 51, - "src/main/runtime/wait-blocked-check-state.test.ts": 107, "src/main/runtime/windowless-main-layout-commands-layout-owner.test.ts": 4237, "src/main/runtime/windows-codex-held-prompt-resubmit.test.ts": 4446, "src/main/runtime/windows-default-route-interfaces.test.ts": 45, @@ -4216,15 +4189,12 @@ "src/main/runtime/workspace-layout-operation-frames.test.ts": 2136, "src/main/runtime/workspace-layout-projection.test.ts": 99, "src/main/runtime/workspace-layout-stream.test.ts": 427, - "src/main/runtime/workspace-notification-mute.test.ts": 99, "src/main/runtime/workspace-session-membership-scaling.test.ts": 72, - "src/main/runtime/terminal/identity/workspace-session-worktree-id.test.ts": 211, "src/main/runtime/workspaces/persisted-surfaces/live-terminal-placement-before-initial-tab.test.ts": 6111, "src/main/runtime/workspaces/persisted-surfaces/sweep-terminal-placement.test.ts": 12352, "src/main/runtime/workspaces/sleep/remote-workspace-terminal-sleep.test.ts": 46, "src/main/runtime/workspaces/workspace-host-notifications.test.ts": 4332, "src/main/runtime/workspaces/lifecycle/workspace-lifecycle-queue.test.ts": 116, - "src/main/runtime/workspaces/catalog/workspace-query-prototype.test.ts": 4444, "src/main/runtime/workspaces/sleep/workspace-sleep-execution.test.ts": 110, "src/main/runtime/workspaces/startup/worktree-agent-first-surface.test.ts": 5816, "src/main/runtime/workspaces/catalog/worktree-list-host-scope.test.ts": 1051, diff --git a/mobile/src/mobile-web-shell/bridge-host-verdict-hold.test.ts b/mobile/src/mobile-web-shell/bridge-host-verdict-hold.test.ts index 9c3eba30bf2..84de7a9ec9c 100644 --- a/mobile/src/mobile-web-shell/bridge-host-verdict-hold.test.ts +++ b/mobile/src/mobile-web-shell/bridge-host-verdict-hold.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest' import type { HostStatusReply } from '../transport/host-status-reply-schema' -import { bridgeId, clientFrame, flushBridge } from './bridge-host-test-fakes' +import { bridgeId, clientFrame } from './bridge-host-test-fakes' import { harness, subscribeFrame } from './bridge-host-test-harness' const PROVEN: HostStatusReply = { protocolVersion: 10, minCompatibleMobileVersion: 1 } @@ -13,66 +13,6 @@ describe('the connection verdict hold', () => { return { bridge, verdict } } - it('reports handshaking and keeps requests and streams off the desktop until the verdict', async () => { - const { bridge } = heldBridge() - const init = bridge.last() - expect(init).toMatchObject({ type: 'init', connection: { state: 'handshaking' } }) - - bridge.host.receive( - clientFrame({ type: 'request', id: bridgeId(1), method: 'worktree.ps', params: {} }) - ) - bridge.host.receive(subscribeFrame(bridgeId(2))) - // The PDF viewer downloads the file from the desktop, so it waits too. - bridge.host.receive( - clientFrame({ type: 'request', id: bridgeId(3), method: 'native.pdf.view', params: {} }) - ) - // status.get is how the page learns the verdict, so it alone goes through. - bridge.host.receive(clientFrame({ type: 'request', id: bridgeId(4), method: 'status.get' })) - bridge.client.pushState('connected') - await flushBridge() - - expect(bridge.client.requests.map((request) => request.method)).toEqual(['status.get']) - expect(bridge.client.streams).toEqual([]) - expect(bridge.nativeCalls).toEqual([]) - expect(bridge.last()).toMatchObject({ type: 'state', connection: { state: 'handshaking' } }) - }) - - it("serves the phone's own store before the verdict, so drafts typed offline are saved", async () => { - const { bridge } = heldBridge() - bridge.host.receive( - clientFrame({ - type: 'request', - id: bridgeId(1), - method: 'native.store.rows', - params: { action: 'read', table: 'draft', keys: ['draft:chat:wt:'] } - }) - ) - await flushBridge() - - expect(bridge.nativeCalls.map((call) => call.verb)).toEqual(['native.store.rows']) - }) - - it('re-sends init as connected once the verdict opens, then sends what waited', () => { - const { bridge, verdict } = heldBridge() - bridge.host.receive( - clientFrame({ type: 'request', id: bridgeId(1), method: 'worktree.ps', params: {} }) - ) - bridge.host.receive(subscribeFrame(bridgeId(2))) - bridge.host.receive( - clientFrame({ type: 'request', id: bridgeId(3), method: 'stats.summary', params: {} }) - ) - bridge.host.receive(clientFrame({ type: 'cancel', id: bridgeId(3), target: 'request' })) - - verdict.status = PROVEN - bridge.host.publishHostStatus() - - expect( - bridge.frames().find((frame) => frame.type === 'init' && frame.hostStatus) - ).toMatchObject({ connection: { state: 'connected' }, hostStatus: PROVEN }) - expect(bridge.client.requests.map((request) => request.method)).toEqual(['worktree.ps']) - expect(bridge.client.streams.map((stream) => stream.method)).toEqual(['terminal.subscribe']) - }) - it('keeps page order for a frame that lands after the status opens but before the flush', () => { const { bridge, verdict } = heldBridge() bridge.host.receive( diff --git a/mobile/src/mobile-web-shell/bridge/bridge-execution-host.test.ts b/mobile/src/mobile-web-shell/bridge/bridge-execution-host.test.ts deleted file mode 100644 index 8e1f28b6fb3..00000000000 --- a/mobile/src/mobile-web-shell/bridge/bridge-execution-host.test.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { onTestFinished, describe, expect, it, vi } from 'vitest' -import { scopeRpcClientToExecutionHost } from '../../transport/execution-host-scoped-rpc-client' -import type { RpcClient } from '../../transport/rpc-client' -import type { RpcResponse } from '../../transport/types' -import { createBridgePortPair } from './bridge-port-pair-test-harness' -import { createPageClient, INIT } from './bridge-page-client-test-harness' - -const SERVER = 'runtime:env-1' as const - -function recordingShellClient() { - const sendRequest = vi.fn((..._args: unknown[]) => new Promise(() => {})) - const subscribe = vi.fn( - (..._args: unknown[]) => - () => - undefined - ) - const client: RpcClient = { - sendRequest, - subscribe, - updateTerminalSubscriptionViewport: () => {}, - getState: () => 'connected', - getReconnectAttempt: () => 0, - getLastConnectedAt: () => 0, - onStateChange: () => () => undefined, - notifyForeground: () => {}, - close: () => {} - } - return { client, sendRequest, subscribe } -} - -describe('a server workspace call across the shell bridge', () => { - it('reaches the shell client with its executionHost on the envelope', async () => { - const shell = recordingShellClient() - const pair = createBridgePortPair({ rpc: shell.client }) - onTestFinished(() => { - pair.client.close() - pair.host.dispose() - }) - await pair.flush() - - const server = scopeRpcClientToExecutionHost(pair.client, SERVER) - void server.sendRequest('worktree.ps', { limit: 1 }).catch(() => undefined) - server.subscribe('terminal.subscribe', { terminal: 't1' }, () => {}) - await pair.flush() - - expect(shell.sendRequest.mock.calls).toEqual([ - ['worktree.ps', { limit: 1 }, { envelope: { executionHost: SERVER } }] - ]) - expect(shell.subscribe.mock.calls).toEqual([ - [ - 'terminal.subscribe', - { terminal: 't1' }, - expect.any(Function), - { envelope: { executionHost: SERVER }, holdReplay: expect.any(Function) } - ] - ]) - pair.client.close() - pair.host.dispose() - }) - - it('refuses it under a shell without the envelope, instead of running it on the desktop', async () => { - const page = createPageClient() - onTestFinished(() => page.client.close()) - page.deliver({ ...INIT, grants: { ...INIT.grants, native: [] } }) - expect(page.client.carriesRpcEnvelope?.()).toBe(false) - - const server = scopeRpcClientToExecutionHost(page.client, SERVER) - await expect(server.sendRequest('worktree.ps', {})).rejects.toThrow(/update the Orca app/) - expect(() => server.subscribe('terminal.subscribe', { terminal: 't1' }, () => {})).toThrow( - /update the Orca app/ - ) - expect(page.frames().filter((frame) => frame.type !== 'ready')).toEqual([]) - page.client.close() - }) -}) diff --git a/mobile/src/mobile-web-shell/bridge/page-device-store.test.ts b/mobile/src/mobile-web-shell/bridge/page-device-store.test.ts deleted file mode 100644 index d0df8c6f3aa..00000000000 --- a/mobile/src/mobile-web-shell/bridge/page-device-store.test.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { afterEach, describe, expect, it } from 'vitest' -import { createFakeBridgePortPair, disposeBridgePortPair } from './bridge-port-pair-test-harness' -import { storeParamsSchema } from './bridge-store-verb' -import { createPageDeviceStore } from './page-device-store' - -let pair: ReturnType | null = null - -afterEach(async () => { - if (pair) { - await disposeBridgePortPair(pair) - } - pair = null -}) - -// A shell whose store answers asynchronously, like the real one, so calls overlap in flight. -async function pageOverShellStore() { - const rows = new Map() - pair = createFakeBridgePortPair({ - serveNativeVerb: async (_verb, params) => { - await new Promise((resolve) => queueMicrotask(resolve)) - const call = storeParamsSchema.parse(params) - if (call.action === 'read') { - return { values: call.keys.map((key) => rows.get(key) ?? null), deferred: [] } - } - for (const op of call.ops) { - if (op.value === null) { - rows.delete(op.key) - } else { - rows.set(op.key, op.value) - } - } - return { written: true } - } - }) - await pair.flush() - return { store: createPageDeviceStore(pair.client), rows } -} - -describe('the page device store', () => { - it('writes a call of more batches than the bridge has request slots', async () => { - const { store, rows } = await pageOverShellStore() - // 64 ops a batch: 65 batches. - const ops = Array.from({ length: 65 * 64 }, (_op, at) => ({ key: `k-${at}`, value: 'eA==' })) - - expect(await store.write('cache', ops)).toEqual({ written: true }) - expect(rows.size).toBe(4160) - }) - - it('refuses a value over the store cap before sending any batch', async () => { - const { store, rows } = await pageOverShellStore() - - expect( - await store.write('cache', [ - { key: 'small', value: 'eA==' }, - { key: 'big', value: 'x'.repeat(540_000) } - ]) - ).toEqual({ written: false, refusal: 'too-large' }) - expect([...rows.keys()]).toEqual([]) - }) - - it('reads a key the store cannot hold as a miss, and refuses to write one', async () => { - const { store } = await pageOverShellStore() - const longKey = 'k'.repeat(513) - await store.write('draft', [{ key: 'kept', value: 'eA==' }]) - - expect(await store.read('draft', ['kept', longKey, ''])).toEqual({ - status: 'ok', - values: ['eA==', null, null] - }) - expect( - await store.write('draft', [ - { key: 'kept', value: null }, - { key: longKey, value: 'eA==' } - ]) - ).toEqual({ written: false, refusal: 'bad-key' }) - expect(await store.read('draft', ['kept'])).toEqual({ status: 'ok', values: ['eA=='] }) - }) -}) diff --git a/mobile/src/transport/rpc-client-stream-registry.test.ts b/mobile/src/transport/rpc-client-stream-registry.test.ts index ede5a344c85..6442e11ff1e 100644 --- a/mobile/src/transport/rpc-client-stream-registry.test.ts +++ b/mobile/src/transport/rpc-client-stream-registry.test.ts @@ -89,50 +89,6 @@ describe('RpcClientStreamRegistry', () => { ]) }) - it.each([ - ['a streamed end', (id: string) => streamingResponse(id, { type: 'end' })], - [ - 'a final end reply', - (id: string): RpcResponse => ({ - id, - ok: true, - result: { type: 'end' }, - _meta: { runtimeId: 'runtime-1' } - }) - ], - [ - 'an error reply', - (id: string): RpcResponse => ({ - id, - ok: false, - error: { code: 'runtime_error', message: 'failed' }, - _meta: { runtimeId: 'runtime-1' } - }) - ] - ])('sends no terminal unsubscribe when the listener disposes on %s', (_label, ending) => { - const { registry, sent } = createRegistry() - const events: unknown[] = [] - let dispose = (): void => {} - dispose = registry.subscribe( - 'terminal.subscribe', - { terminal: 'term-1', client: { id: 'phone', type: 'mobile' } }, - (event) => { - events.push(event) - // The session hook disposes synchronously on end/error; a slot-named unsubscribe here - // would retire a newer same-slot stream on the host. - dispose() - } - ) - const request = sent[0]! - - registry.handleResponse(ending(request.id)) - dispose() - - expect(events).toHaveLength(1) - expect(sent).toEqual([request]) - expect(registry.size()).toBe(0) - }) - it('re-sends a stream its owner holds only when the owner resends it, and a cancel before then sends nothing', () => { const { registry, sent } = createRegistry() const held: (() => void)[] = [] @@ -153,47 +109,6 @@ describe('RpcClientStreamRegistry', () => { ]) }) - it('does not replay a terminal stream the host already ended', () => { - const { registry, sent, setState } = createRegistry() - registry.subscribe( - 'terminal.subscribe', - { terminal: 'term-1', client: { id: 'phone' } }, - () => {} - ) - const request = sent[0]! - registry.handleResponse(streamingResponse(request.id, { type: 'subscribed', streamId: 7 })) - registry.handleResponse(streamingResponse(request.id, { type: 'end' })) - - setState('connecting') - registry.markForReplay() - setState('connected') - registry.replayAfterAuthentication() - - expect(sent).toEqual([request]) - }) - - it('unsubscribes a session tabs stream by its own request id', () => { - const { registry, sent } = createRegistry() - const disposeOlder = registry.subscribe( - 'session.tabs.subscribe', - { worktree: 'wt-1' }, - () => {} - ) - registry.subscribe('session.tabs.subscribe', { worktree: 'wt-1' }, () => {}) - const [older, newer] = sent - registry.handleResponse(streamingResponse(older!.id, { type: 'snapshot', tabs: [] })) - - disposeOlder() - - // Without the request id the host sweeps every stream for the worktree, including the newer one. - expect(sent[2]).toMatchObject({ - method: 'session.tabs.unsubscribe', - params: { worktree: 'wt-1', subscriptionId: older!.id } - }) - expect(newer!.id).not.toBe(older!.id) - expect(sent).toHaveLength(3) - }) - it('holds a session tabs unsubscribe until the host registers the stream', () => { const { registry, sent } = createRegistry() const events: unknown[] = [] @@ -219,119 +134,6 @@ describe('RpcClientStreamRegistry', () => { expect(registry.size()).toBe(0) }) - describe.each([ - ['browser.screencast', 'browser.screencast.unsubscribe', { page: 'page-1' }], - ['runtime.clientEvents.subscribe', 'runtime.clientEvents.unsubscribe', null], - ['notifications.subscribe', 'notifications.unsubscribe', null], - ['accounts.subscribe', 'accounts.unsubscribe', null], - ['session.tabs.subscribe', 'session.tabs.unsubscribe', { worktree: 'wt-1' }] - ])('canceled %s delivery', (method, cleanup, params) => { - it('ignores late scrollback and terminal registration while waiting for host cleanup', () => { - const { registry, sent } = createRegistry() - const events: unknown[] = [] - registry.subscribe(method, params, (event) => events.push(event))() - const request = sent[0]! - - expect( - registry.handleResponse({ - id: request.id, - ok: true, - result: { type: 'scrollback', serialized: 'late' } - }) - ).toBe(true) - registry.handleResponse(streamingResponse(request.id, { type: 'subscribed', streamId: 41 })) - registry.handleBinary(terminalOutput(41, 'late')) - expect(events).toEqual([]) - expect(registry.size()).toBe(1) - expect(sent).toHaveLength(1) - - registry.handleResponse( - streamingResponse( - request.id, - method === 'session.tabs.subscribe' - ? { type: 'snapshot', tabs: [] } - : { type: 'ready', subscriptionId: 'late-host-id' } - ) - ) - expect(sent[1]).toMatchObject({ - method: cleanup, - params: - method === 'session.tabs.subscribe' - ? { worktree: 'wt-1', subscriptionId: request.id } - : { subscriptionId: 'late-host-id' } - }) - expect(registry.size()).toBe(0) - expect(events).toEqual([]) - }) - - it('does not replay a canceled opener queued before connection', () => { - const { registry, sent, setState } = createRegistry('connecting') - const events: unknown[] = [] - const dispose = registry.subscribe(method, params, (event) => events.push(event)) - - dispose() - dispose() - registry.markForReplay() - setState('connected') - registry.replayAfterAuthentication() - - expect(sent).toEqual([]) - expect(events).toEqual([]) - expect(registry.size()).toBe(0) - }) - }) - - it('holds a session tabs unsubscribe again after a reconnect replays the stream', () => { - const { registry, sent } = createRegistry() - const dispose = registry.subscribe('session.tabs.subscribe', { worktree: 'wt-1' }, () => {}) - const subscribe = sent[0]! - registry.handleResponse(streamingResponse(subscribe.id, { type: 'snapshot', tabs: [] })) - - registry.markForReplay() - registry.replayAfterAuthentication() - dispose() - - expect(sent.map((request) => request.method)).toEqual([ - 'session.tabs.subscribe', - 'session.tabs.subscribe' - ]) - registry.handleResponse(streamingResponse(subscribe.id, { type: 'snapshot', tabs: [] })) - expect(sent[2]).toMatchObject({ - method: 'session.tabs.unsubscribe', - params: { worktree: 'wt-1', subscriptionId: subscribe.id } - }) - }) - - it('drops a held session tabs unsubscribe when the subscribe fails or reconnects first', () => { - const { registry, sent } = createRegistry() - const events: unknown[] = [] - const disposeFailed = registry.subscribe( - 'session.tabs.subscribe', - { worktree: 'wt-1' }, - (event) => events.push(event) - ) - const disposeReplayed = registry.subscribe( - 'session.tabs.subscribe', - { worktree: 'wt-2' }, - () => {} - ) - const failed = sent[0]! - disposeFailed() - disposeReplayed() - - registry.handleResponse({ - id: failed.id, - ok: false, - error: { code: 'worktree_not_found', message: 'Worktree not found' } - }) - registry.markForReplay() - registry.replayAfterAuthentication() - - expect(sent).toHaveLength(2) - expect(events).toEqual([]) - expect(registry.size()).toBe(0) - }) - it('ends one transcript stream on dispose and leaves a sibling on the same socket (U-03)', () => { const { registry, sent } = createRegistry() const disposeFirst = registry.subscribe('agentSession.subscribe', { sessionId: 's1' }, () => {}) @@ -352,27 +154,6 @@ describe('RpcClientStreamRegistry', () => { expect(registry.size()).toBe(1) }) - it('names the terminal request it sent when unsubscribing, and keeps the slot for older hosts', () => { - const { registry, sent } = createRegistry() - const dispose = registry.subscribe( - 'terminal.subscribe', - { terminal: 'term-1', client: { id: 'phone-1', type: 'mobile' } }, - () => {} - ) - const subscribe = sent[0]! - - dispose() - - expect(sent[1]).toMatchObject({ - method: 'terminal.unsubscribe', - params: { - subscriptionId: 'term-1:phone-1', - client: { id: 'phone-1' }, - requestId: subscribe.id - } - }) - }) - 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' }, () => {}) @@ -399,65 +180,4 @@ describe('RpcClientStreamRegistry', () => { ).toEqual([{ subscriptionId: 'page-1-new-connection' }]) expect(registry.size()).toBe(1) }) - - describe.each([ - ['runtime.clientEvents.subscribe', 'runtime.clientEvents.unsubscribe', null], - ['notifications.subscribe', 'notifications.unsubscribe', null], - ['accounts.subscribe', 'accounts.unsubscribe', null], - ['browser.screencast', 'browser.screencast.unsubscribe', { page: 'page-1' }] - ])('%s ready id across a replay', (method, unsubscribeMethod, params) => { - function unsubscribes(sent: SentRequest[]): unknown[] { - return sent.filter((request) => request.method === unsubscribeMethod).map((r) => r.params) - } - - function subscribeReady() { - const harness = createRegistry() - const dispose = harness.registry.subscribe(method, params, () => {}) - const requestId = harness.sent[0]!.id - harness.registry.handleResponse( - streamingResponse(requestId, { type: 'ready', subscriptionId: 'old-connection-id' }) - ) - return { ...harness, dispose, requestId } - } - - it('releases the replayed registration when disposed before its new ready', () => { - const { registry, sent, dispose, requestId } = subscribeReady() - - registry.markForReplay() - registry.replayAfterAuthentication() - dispose() - registry.handleResponse( - streamingResponse(requestId, { type: 'ready', subscriptionId: 'new-connection-id' }) - ) - - expect(unsubscribes(sent)).toEqual([{ subscriptionId: 'new-connection-id' }]) - }) - - it('forgets the previous connection id when marked for replay', () => { - const { registry, sent, dispose } = subscribeReady() - - registry.markForReplay() - dispose() - - // A disposal while disconnected has nothing to name on the next connection. - expect(unsubscribes(sent)).toEqual([]) - expect(registry.size()).toBe(0) - }) - - it('sends one unsubscribe however often the stream is disposed', () => { - const { registry, sent, dispose, requestId } = subscribeReady() - - registry.markForReplay() - registry.replayAfterAuthentication() - dispose() - dispose() - registry.handleResponse( - streamingResponse(requestId, { type: 'ready', subscriptionId: 'new-connection-id' }) - ) - dispose() - - expect(unsubscribes(sent)).toEqual([{ subscriptionId: 'new-connection-id' }]) - expect(registry.size()).toBe(0) - }) - }) }) diff --git a/mobile/src/transport/stable-logical-rpc-client.test.ts b/mobile/src/transport/stable-logical-rpc-client.test.ts index 7d1d8180600..16250eaeb86 100644 --- a/mobile/src/transport/stable-logical-rpc-client.test.ts +++ b/mobile/src/transport/stable-logical-rpc-client.test.ts @@ -1,14 +1,13 @@ import { describe, expect, it, vi } from 'vitest' -import { RelayDialStageTracker, type RelayDialStage } from './relay-dial-stage' +import './relay-dial-stage' import type { ConnectionState, RpcResponse } from './types' import type { RpcClient } from './rpc-client' -import { isRpcDeliveryUnknown, markRpcDeliveryUnknown } from './rpc-delivery-ambiguity' +import './rpc-delivery-ambiguity' import { createStableLogicalRpcClient, - LogicalClientCutoverError, - type MobileConnectionPath + LogicalClientCutoverError } from './stable-logical-rpc-client' -import { LogicalClientSuspendedError } from './logical-client-suspended-error' +import './logical-client-suspended-error' class FakeSession implements RpcClient { readonly sendRequest = @@ -107,127 +106,6 @@ describe('stable logical RPC client', () => { pending.resolve(success('late')) }) - it('attaches a subscription its owner holds at cutover only when the owner resumes it', async () => { - const oldSession = new FakeSession('connected') - const nextSession = new FakeSession('connected') - const client = createStableLogicalRpcClient(oldSession, 'lan') - const held: (() => void)[] = [] - client.subscribe('terminal.subscribe', { terminal: 'term-1' }, vi.fn(), { - holdReplay: (resend) => held.push(resend) > 0 - }) - oldSession.subscribe.mockClear() - - await client.migrateTo(nextSession, 'relay') - expect(nextSession.subscribe).not.toHaveBeenCalled() - - held[0]!() - expect(nextSession.subscribe).toHaveBeenCalledOnce() - expect(oldSession.subscribe).not.toHaveBeenCalled() - }) - - it('keeps replies that commit before cutover and carries viewport state into replay', async () => { - const oldSession = new FakeSession('connected') - const nextSession = new FakeSession('connected') - oldSession.sendRequest.mockResolvedValue(success('old')) - const client = createStableLogicalRpcClient(oldSession, 'lan') - client.subscribe( - 'terminal.subscribe', - { terminal: 'term-1', viewport: { cols: 80, rows: 24 } }, - vi.fn() - ) - client.updateTerminalSubscriptionViewport('term-1', { cols: 120, rows: 40 }) - - await expect(client.sendRequest('status.get')).resolves.toEqual(success('old')) - await client.migrateTo(nextSession, 'relay') - expect(nextSession.subscribe).toHaveBeenCalledWith( - 'terminal.subscribe', - { terminal: 'term-1', viewport: { cols: 120, rows: 40 } }, - expect.any(Function), - undefined - ) - }) - - it('suspends one physical session and replays subscriptions on foreground replacement', async () => { - const oldSession = new FakeSession('connected') - const nextSession = new FakeSession('connected') - nextSession.sendRequest.mockResolvedValue(success('next')) - const client = createStableLogicalRpcClient(oldSession, 'relay') - client.subscribe('session.tabs.subscribe', { worktree: 'id:wt-1' }, vi.fn()) - - client.suspendActiveSession() - - expect(oldSession.close).toHaveBeenCalledOnce() - expect(client.getState()).toBe('disconnected') - await expect(client.sendRequest('status.get')).rejects.toThrow('Client suspended') - - await client.migrateTo(nextSession, 'relay') - - expect(nextSession.subscribe).toHaveBeenCalledWith( - 'session.tabs.subscribe', - { worktree: 'id:wt-1' }, - expect.any(Function), - undefined - ) - await expect(client.sendRequest('status.get')).resolves.toEqual(success('next')) - }) - - it('reports requests the suspended session never sent as suspended, and written ones as ambiguous', async () => { - const session = new FakeSession('connected') - const unsent = deferred() - const written = deferred() - session.sendRequest.mockReturnValueOnce(unsent.promise).mockReturnValueOnce(written.promise) - const client = createStableLogicalRpcClient(session, 'relay') - const waiting = client.sendRequest('speech.dictation.chunk').catch((error: unknown) => error) - const inFlight = client.sendRequest('speech.dictation.chunk').catch((error: unknown) => error) - - client.suspendActiveSession() - unsent.reject(new Error('relay session closed')) - written.reject(markRpcDeliveryUnknown(new Error('Client closed'))) - - expect(await waiting).toBeInstanceOf(LogicalClientSuspendedError) - expect(isRpcDeliveryUnknown(await inFlight)).toBe(true) - }) - - it('preserves delivery ambiguity without replaying a mutation after relay replacement', async () => { - const session = new FakeSession('connected') - const replacement = new FakeSession('connected') - const inFlight = deferred() - session.sendRequest.mockReturnValue(inFlight.promise) - // Mirror the real physical contract: close() rejects post-write pendings - // with a delivery-unknown-marked error. - const closeError = markRpcDeliveryUnknown(new Error('Client closed')) - session.close.mockImplementation(() => inFlight.reject(closeError)) - const client = createStableLogicalRpcClient(session, 'relay') - const request = client.sendRequest('terminal.send', { terminal: 'term', text: 'hi' }) - - client.suspendActiveSession() - - await expect(request).rejects.toBe(closeError) - await expect(request.catch((error: unknown) => isRpcDeliveryUnknown(error))).resolves.toBe(true) - await client.migrateTo(replacement, 'relay') - expect(replacement.sendRequest).not.toHaveBeenCalled() - replacement.sendRequest.mockResolvedValue(success('next')) - await expect(client.sendRequest('status.get')).resolves.toEqual(success('next')) - }) - - it('lets the physical close settle in-flight requests on close, keeping pre-write failures definite', async () => { - const session = new FakeSession('connected') - const inFlight = deferred() - session.sendRequest.mockReturnValue(inFlight.promise) - // A request still waiting for connect wrote no frame and must remain unmarked on logical close. - const preWriteError = new Error('Connection closed') - session.close.mockImplementation(() => inFlight.reject(preWriteError)) - const client = createStableLogicalRpcClient(session, 'lan') - const request = client.sendRequest('terminal.send', { terminal: 'term', text: 'hi' }) - - client.close() - - await expect(request).rejects.toBe(preWriteError) - await expect(request.catch((error: unknown) => isRpcDeliveryUnknown(error))).resolves.toBe( - false - ) - }) - it('preserves a committed response when logical close wins the callback race', async () => { const session = new FakeSession('connected') const inFlight = deferred() @@ -240,204 +118,6 @@ describe('stable logical RPC client', () => { await expect(request).resolves.toEqual(success('accepted')) }) - - it('publishes the replacement dial phases while the client is suspended', async () => { - const oldSession = new FakeSession('connected') - const replacement = new FakeSession('connecting') - const client = createStableLogicalRpcClient(oldSession, 'relay') - const states: ConnectionState[] = [] - client.onStateChange((next) => states.push(next)) - - client.suspendActiveSession() - expect(client.getPendingPath()).toBeNull() - - const migrating = client.migrateTo(replacement, 'relay') - // The replacement never announces its initial connecting state; the client must still show it. - expect(client.getState()).toBe('connecting') - expect(client.getPendingPath()).toBe('relay') - replacement.setState('handshaking') - expect(client.getState()).toBe('handshaking') - replacement.setState('connected') - await migrating - - expect(states).toEqual(['disconnected', 'connecting', 'handshaking', 'connected']) - expect(client.getPendingPath()).toBeNull() - expect(client.getActivePath()).toBe('relay') - }) - - // Forward replacement reconnecting phases so the suspended client shows progress during the dial. - it('forwards a reconnecting phase published by the dialing session itself', async () => { - const oldSession = new FakeSession('connected') - const replacement = new FakeSession('connecting') - const client = createStableLogicalRpcClient(oldSession, 'relay') - client.suspendActiveSession() - const states: ConnectionState[] = [] - client.onStateChange((next) => states.push(next)) - - const migrating = client.migrateTo(replacement, 'relay') - replacement.setState('connecting') - replacement.setState('reconnecting') - expect(client.getState()).toBe('reconnecting') - replacement.setState('handshaking') - replacement.setState('connected') - await migrating - - expect(states).toEqual(['connecting', 'reconnecting', 'handshaking', 'connected']) - }) - - // A direct session already reconnecting needs no phase forwarding, but must still name the pending relay path. - it('names a relay dial started from reconnecting without disturbing the state', async () => { - const direct = new FakeSession('connected') - const replacement = new FakeSession('connecting') - const client = createStableLogicalRpcClient(direct, 'lan') - direct.setState('reconnecting') - const states: ConnectionState[] = [] - const paths: (MobileConnectionPath | null)[] = [] - client.onStateChange((next) => states.push(next)) - client.onConnectionPathChange(() => paths.push(client.getPendingPath())) - - const migrating = client.migrateTo(replacement, 'relay') - expect(client.getPendingPath()).toBe('relay') - expect(paths).toEqual(['relay']) - replacement.setState('connecting') - replacement.setState('handshaking') - - expect(client.getState()).toBe('reconnecting') - expect(states).toEqual([]) - - replacement.setState('connected') - await migrating - - expect(states).toEqual(['connected']) - expect(client.getPendingPath()).toBeNull() - expect(client.getActivePath()).toBe('relay') - }) - - it('publishes recovery-path changes and keeps Relay pending between failed dials', async () => { - const direct = new FakeSession('reconnecting') - const replacement = new FakeSession('connecting') - const client = createStableLogicalRpcClient(direct, 'tailscale') - const paths: (MobileConnectionPath | null)[] = [] - client.onConnectionPathChange(() => paths.push(client.getPendingPath())) - - client.setRecoveryPath('relay') - const migrating = client.migrateTo(replacement, 'relay') - replacement.setState('disconnected') - await expect(migrating).rejects.toThrow(/disconnected/) - - expect(client.getPendingPath()).toBe('relay') - expect(paths).toEqual(['relay']) - - client.setRecoveryPath(null) - expect(client.getPendingPath()).toBeNull() - expect(paths).toEqual(['relay', null]) - }) - - it('does not revive a stale recovery path after a connection later drops', () => { - const direct = new FakeSession('reconnecting') - const client = createStableLogicalRpcClient(direct, 'tailscale') - - client.setRecoveryPath('relay') - direct.setState('connected') - direct.setState('reconnecting') - - expect(client.getPendingPath()).toBeNull() - }) - - it('drops the pending path when the previous session recovers mid-dial', async () => { - const direct = new FakeSession('connected') - const replacement = new FakeSession('connecting') - const client = createStableLogicalRpcClient(direct, 'lan') - direct.setState('reconnecting') - - const migrating = client.migrateTo(replacement, 'relay') - expect(client.getPendingPath()).toBe('relay') - - direct.setState('connected') - expect(client.getPendingPath()).toBeNull() - - replacement.setState('connected') - await migrating - }) - - it('never downgrades a live session while a make-before-break replacement dials', async () => { - const oldSession = new FakeSession('connected') - const replacement = new FakeSession('connecting') - const client = createStableLogicalRpcClient(oldSession, 'relay') - const states: ConnectionState[] = [] - client.onStateChange((next) => states.push(next)) - - const migrating = client.migrateTo(replacement, 'relay') - replacement.setState('connecting') - replacement.setState('handshaking') - - expect(client.getState()).toBe('connected') - expect(client.getPendingPath()).toBeNull() - - replacement.setState('connected') - await migrating - - expect(states).toEqual(['connected']) - }) - - it('restores disconnected when a dial it was narrating fails', async () => { - const session = new FakeSession('connected') - const replacement = new FakeSession('connecting') - const client = createStableLogicalRpcClient(session, 'relay') - client.suspendActiveSession() - const states: ConnectionState[] = [] - client.onStateChange((next) => states.push(next)) - - const migrating = client.migrateTo(replacement, 'relay') - replacement.setState('handshaking') - replacement.setState('disconnected') - - await expect(migrating).rejects.toThrow(/disconnected/) - expect(states).toEqual(['connecting', 'handshaking', 'disconnected']) - expect(client.getState()).toBe('disconnected') - expect(client.getPendingPath()).toBeNull() - }) - - it('outlives the flat bound when the relay cell holds the dial', async () => { - vi.useFakeTimers() - try { - const oldSession = new FakeSession('connected') - const replacement = Object.assign(new FakeSession('connecting'), { - dialStage: new RelayDialStageTracker(), - getDialStage(): RelayDialStage { - return this.dialStage.getDialStage() - }, - onDialStageChange(listener: (stage: RelayDialStage) => void) { - return this.dialStage.onDialStageChange(listener) - } - }) - const client = createStableLogicalRpcClient(oldSession, 'lan') - const migrating = client.migrateTo(replacement, 'relay', 12_000) - await vi.advanceTimersByTimeAsync(1_000) - replacement.dialStage.advance('awaiting-hello') - await vi.advanceTimersByTimeAsync(20_000) - expect(replacement.close).not.toHaveBeenCalled() - replacement.setState('connected') - await migrating - expect(client.getActivePath()).toBe('relay') - } finally { - vi.useRealTimers() - } - }) - - it('closes a replacement that fails authentication and preserves the active session', async () => { - const oldSession = new FakeSession('connected') - const replacement = new FakeSession('connecting') - const client = createStableLogicalRpcClient(oldSession, 'lan') - const migrating = client.migrateTo(replacement, 'relay') - replacement.setState('auth-failed') - - await expect(migrating).rejects.toThrow(/auth-failed/) - expect(replacement.close).toHaveBeenCalledOnce() - expect(oldSession.close).not.toHaveBeenCalled() - expect(client.getActivePath()).toBe('lan') - expect(client.getGeneration()).toBe(1) - }) }) describe('stable logical RPC client subscription fencing', () => { diff --git a/src/main/runtime/orca-runtime-tests/claude-agent-teams-pty-exit-leak.spec.ts b/src/main/runtime/orca-runtime-tests/claude-agent-teams-pty-exit-leak.spec.ts deleted file mode 100644 index e84ee2744bf..00000000000 --- a/src/main/runtime/orca-runtime-tests/claude-agent-teams-pty-exit-leak.spec.ts +++ /dev/null @@ -1,80 +0,0 @@ -/** Natural PTY exit/disconnect must evict leader teams too; explicit close was once the only cleanup. */ -import { describe, it, expect } from 'vitest' -import { OrcaRuntimeService } from '../orca-runtime' -import type { ClaudeAgentTeamsService } from '../claude-agent-teams-service' - -export function registerClaudeAgentTeamsPtyExitLeakTests(): void { - type RuntimeInternals = { - claudeAgentTeams: ClaudeAgentTeamsService - handleByPtyId: Map - dropDisconnectedPtyRecord: (ptyId: string) => void - } - - function internals(runtime: OrcaRuntimeService): RuntimeInternals { - return runtime as unknown as RuntimeInternals - } - - function registerTeam(runtime: OrcaRuntimeService, ptyId: string, leaderHandle: string): void { - const { claudeAgentTeams, handleByPtyId } = internals(runtime) - claudeAgentTeams.createLaunchEnv({ - leaderHandle, - baseEnv: {}, - shimDir: '/tmp/orca-shim', - shimBin: 'orca' - }) - handleByPtyId.set(ptyId, leaderHandle) - } - - describe('ClaudeAgentTeams eviction on natural PTY exit (leak regression)', () => { - it('evicts the team when its leader PTY exits naturally (onPtyExit)', () => { - const runtime = new OrcaRuntimeService() - registerTeam(runtime, 'pty-leader', 'handle-leader') - expect(internals(runtime).claudeAgentTeams.getActiveTeamCount()).toBe(1) - - runtime.onPtyExit('pty-leader', 0) - - expect(internals(runtime).claudeAgentTeams.getActiveTeamCount()).toBe(0) - }) - - it('evicts the team when the disconnected PTY record is pruned', () => { - const runtime = new OrcaRuntimeService() - registerTeam(runtime, 'pty-leader', 'handle-leader') - expect(internals(runtime).claudeAgentTeams.getActiveTeamCount()).toBe(1) - - internals(runtime).dropDisconnectedPtyRecord('pty-leader') - - expect(internals(runtime).claudeAgentTeams.getActiveTeamCount()).toBe(0) - }) - - it('does not accumulate teams across many natural leader exits', () => { - const runtime = new OrcaRuntimeService() - for (let i = 0; i < 100; i++) { - registerTeam(runtime, `pty-${i}`, `handle-${i}`) - runtime.onPtyExit(`pty-${i}`, 0) - } - expect(internals(runtime).claudeAgentTeams.getActiveTeamCount()).toBe(0) - }) - - it('leaves a concurrently-launched team intact when only one leader exits', () => { - const runtime = new OrcaRuntimeService() - registerTeam(runtime, 'pty-a', 'handle-a') - registerTeam(runtime, 'pty-b', 'handle-b') - expect(internals(runtime).claudeAgentTeams.getActiveTeamCount()).toBe(2) - - runtime.onPtyExit('pty-a', 0) - - expect(internals(runtime).claudeAgentTeams.getActiveTeamCount()).toBe(1) - }) - - it('is a no-op for a PTY that never launched a team', () => { - const runtime = new OrcaRuntimeService() - const { handleByPtyId } = internals(runtime) - handleByPtyId.set('pty-plain', 'handle-plain') - expect(internals(runtime).claudeAgentTeams.getActiveTeamCount()).toBe(0) - - runtime.onPtyExit('pty-plain', 0) - - expect(internals(runtime).claudeAgentTeams.getActiveTeamCount()).toBe(0) - }) - }) -} diff --git a/src/main/runtime/orca-runtime-tests/session-tabs-empty-worktree-publication.spec.ts b/src/main/runtime/orca-runtime-tests/session-tabs-empty-worktree-publication.spec.ts deleted file mode 100644 index 0278ec16fff..00000000000 --- a/src/main/runtime/orca-runtime-tests/session-tabs-empty-worktree-publication.spec.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { describe, expect, it, vi } from 'vitest' -import type { RuntimeMobileSessionTabsResult } from '../../../shared/runtime-session-contracts' -import { OrcaRuntimeService } from '../orca-runtime' - -export function registerSessionTabsEmptyWorktreePublicationTests(): void { - const WORKTREE = 'repo::/never-opened' - - type WorktreeAnswerInternals = { - getMobileSessionTabsForWorktree: ( - worktreeId: string, - clientNavigationId?: string - ) => RuntimeMobileSessionTabsResult - } - - function createHeadedRuntime(): OrcaRuntimeService { - const runtime = new OrcaRuntimeService() - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: these tests only reach listProcesses. - runtime.setPtyController({ listProcesses: vi.fn(async () => []) } as never) - runtime.attachWindow(1) - return runtime - } - - function answerFor(runtime: OrcaRuntimeService, clientNavigationId?: string) { - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: reads the runtime's own per-worktree answer. - return (runtime as unknown as WorktreeAnswerInternals).getMobileSessionTabsForWorktree( - WORKTREE, - clientNavigationId - ) - } - - function recordAnswers(runtime: OrcaRuntimeService): RuntimeMobileSessionTabsResult[] { - const answers: RuntimeMobileSessionTabsResult[] = [] - runtime.onMobileSessionTabsChanged((snapshot) => { - if (snapshot.worktree === WORKTREE) { - answers.push(snapshot) - } - }, 'device-1') - return answers - } - - function publishGraphWithoutWorktree(runtime: OrcaRuntimeService): void { - runtime.syncWindowGraph(1, { tabs: [], leaves: [], mobileSessionTabs: [] }) - } - - describe('answering for a worktree the host has no tabs for', () => { - it('answers "ask me later" only until the renderer graph publishes', () => { - const runtime = createHeadedRuntime() - expect(answerFor(runtime)).toMatchObject({ publicationEpoch: 'none', tabs: [] }) - expect(answerFor(runtime, 'device-1')).toMatchObject({ - publicationEpoch: 'none:client-navigation', - tabs: [] - }) - - publishGraphWithoutWorktree(runtime) - - const published = answerFor(runtime, 'device-1') - expect(published.publicationEpoch).not.toMatch(/^none/) - expect(published.tabs).toEqual([]) - }) - - it('tells a client that asked during startup once the graph publishes', () => { - const runtime = createHeadedRuntime() - const answers = recordAnswers(runtime) - answerFor(runtime, 'device-1') - answerFor(runtime, 'device-1') - - publishGraphWithoutWorktree(runtime) - - expect(answers).toHaveLength(1) - expect(answers[0].publicationEpoch).not.toMatch(/^none/) - expect(answers[0].tabs).toEqual([]) - }) - }) -} diff --git a/src/main/runtime/terminal-agent-phone-push.test.ts b/src/main/runtime/terminal-agent-phone-push.test.ts index 7aefd6449f9..1b99898d794 100644 --- a/src/main/runtime/terminal-agent-phone-push.test.ts +++ b/src/main/runtime/terminal-agent-phone-push.test.ts @@ -6,11 +6,8 @@ import { type AgentTransition } from '../../shared/agent-status/lifecycle/agent-transitions' import type { NotificationSettings } from '../../shared/notification-settings-types' -import type { - MobileNotificationDispatchEvent, - MobileNotificationEvent -} from './push/notifications/runtime-mobile-notification-controller' -import { createNotificationDeliveryService } from '../notifications/notification-delivery-service' +import type { MobileNotificationDispatchEvent } from './push/notifications/runtime-mobile-notification-controller' +import '../notifications/notification-delivery-service' import { TerminalAgentPhonePush } from './terminal-agent-phone-push' const WT = 'repo-1::/tmp/wt' @@ -149,26 +146,6 @@ describe('TerminalAgentPhonePush', () => { ]) }) - it.each([null, 'host-a'])( - 'pushes windowless news with its execution owner: %s', - (connectionId) => { - const { sent, finish } = harness() - finish({ connectionId }) - expect(sent).toHaveLength(1) - expect(sent[0]?.workspaceExecutionHostId ?? 'local').toBe( - connectionId ? `ssh:${connectionId}` : 'local' - ) - } - ) - - it('waits out the quiet window for a Pi agent even with no live work seen', () => { - const { sent, advance, finish } = harness() - finish({ agentType: 'pi' }) - expect(sent).toEqual([]) - advance(AGENT_DONE_QUIET_MS) - expect(sent).toHaveLength(1) - }) - it('announces no milestone a goal agent resumes from within the quiet window', () => { const { push, sent, advance, working, finish } = harness() working() @@ -180,26 +157,6 @@ describe('TerminalAgentPhonePush', () => { expect(sent).toEqual([]) }) - it('pushes an agent that needs input at once, and a failed turn as finished', () => { - const { push, sent, state, advance } = harness({ - hookRow: row({ - state: 'waiting', - lastAssistantMessage: undefined, - toolName: 'Bash', - toolInput: 'rm -rf build' - }) - }) - push.observe(moved('needs-input', 'needs-you')) - expect(sent).toHaveLength(1) - advance(5_000) - state.hookRow = row({ mainAgent: { state: 'done', outcome: 'failure', stateStartedAt: 5_000 } }) - push.observe(moved('errored')) - expect(sent.map((event) => [event.agentState, event.title, event.body])).toEqual([ - ['waiting', 'orca / feature - Claude needs input', 'Using Bash: rm -rf build'], - ['done', 'orca / feature - Claude failed', 'Fixed it.'] - ]) - }) - it('stays silent while a window owns the host, including one that arrives mid quiet window', () => { const { sent, state, advance, working, finish } = harness() state.hasWindow = true @@ -214,66 +171,6 @@ describe('TerminalAgentPhonePush', () => { expect(sent).toEqual([]) }) - it('collapses a burst in one workspace as the window’s fan-out does', () => { - const { push, sent, finish } = harness() - finish() - push.observe(moved('prompt-submitted', 'working')) - finish({ stateStartedAt: 6_000 }) - expect(sent).toHaveLength(1) - }) - - it('pushes nothing for a prompt, a resume, a rest, a session boundary, or a structured chat’s row', () => { - const { push, sent, state, advance } = harness() - push.observe(moved('prompt-submitted', 'working')) - push.observe(moved(null, 'working')) - push.observe(moved(null, 'rest')) - // The detector names no news for a boundary's done. - push.observe(moved(null, 'done')) - push.observe({ - kind: 'seeded', - paneKey: PANE, - workspace: { worktreeId: WT, sshHostId: null }, - phase: 'done', - at: 1 - }) - state.hookRow = row({ structuredHost: 'owned' }) - push.observe(moved('turn-finished')) - advance(AGENT_DONE_QUIET_MS) - expect(sent).toEqual([]) - }) - - it('pushes nothing for a muted workspace', () => { - const { sent, finish } = harness({ muted: true }) - finish() - expect(sent).toEqual([]) - }) - - it('leaves a dispatched worker’s finish to its coordinator, and still pushes its prompt', () => { - const { push, sent, state, advance, finish } = harness({ dispatched: true }) - finish() - advance(AGENT_DONE_QUIET_MS) - expect(sent).toEqual([]) - state.hookRow = row({ state: 'waiting', stateStartedAt: 6_000 }) - push.observe(moved('needs-input', 'needs-you')) - expect(sent.map((event) => event.agentState)).toEqual(['waiting']) - }) - - it('pushes a dispatched worker’s finish whose title still waits for approval, as the window does', () => { - const { sent, advance, finish } = harness({ - dispatched: true, - title: 'Codex - waiting for approval' - }) - finish() - advance(AGENT_DONE_QUIET_MS) - expect(sent).toHaveLength(1) - }) - - it('marks the push not desktop-allowed when agent notifications are off', () => { - const { sent, finish } = harness({ settings: { agentTaskComplete: false } }) - finish() - expect(sent[0]?.desktopAllowed).toBe(false) - }) - it('announces a lead agent’s finish while background work runs, and not its later all-clear', () => { const { sent, advance, working, finish } = harness() working() @@ -289,28 +186,6 @@ describe('TerminalAgentPhonePush', () => { expect(sent).toHaveLength(1) }) - it('waits out the quiet window for a pane restored mid-work, as the window seeds it', () => { - const { push, sent, advance, finish } = harness({ hookRow: row({ state: 'working' }) }) - const workspace = { worktreeId: WT, sshHostId: null } - push.observe({ kind: 'seeded', paneKey: PANE, workspace, phase: 'working', at: 1 }) - finish() - expect(sent).toEqual([]) - advance(AGENT_DONE_QUIET_MS) - expect(sent).toHaveLength(1) - }) - - it('counts a pane restored mid background work as work seen, as the window seeds it', () => { - const { push, sent, advance, finish } = harness({ - hookRow: row({ state: 'working', turnCompletedAt: 7_000 }) - }) - const workspace = { worktreeId: WT, sshHostId: null } - push.observe({ kind: 'seeded', paneKey: PANE, workspace, phase: 'working', at: 1 }) - finish() - expect(sent).toEqual([]) - advance(AGENT_DONE_QUIET_MS) - expect(sent).toHaveLength(1) - }) - it('announces no finish restored with the pane', () => { const { push, sent, advance, working, finish } = harness({ hookRow: row({ state: 'working', turnCompletedAt: 7_000 }) @@ -322,48 +197,4 @@ describe('TerminalAgentPhonePush', () => { advance(AGENT_DONE_QUIET_MS) expect(sent).toEqual([]) }) - - it('sends phones, old ones included, the alert the window’s own request would have', () => { - const { sent, finish } = harness() - finish() - const fromWindow: MobileNotificationEvent[] = [] - const service = createNotificationDeliveryService({ - readNotificationSettings: () => ({ - enabled: false, - agentTaskComplete: true, - agentNeedsInput: true, - terminalBell: true, - suppressWhenFocused: true, - customSoundId: 'system', - customSoundPath: null, - customSoundVolume: 1, - mutedNotificationSourceIds: [], - notifyHiddenWorkspaces: true - }), - findActiveWindow: () => null, - isWindowVisible: () => false, - setTrayAttention: () => {}, - isNotificationSupported: () => false, - dispatchMobileNotification: (event) => fromWindow.push(event), - readAuthorizationStatus: async () => null, - recordDeliveryOutcome: () => {}, - deliverNative: () => ({ delivered: false }), - platform: 'linux', - now: () => 10_000 - }) - void service.dispatch({ - source: 'agent-task-complete', - notificationId: sent[0]?.notificationId, - worktreeId: WT, - paneKey: PANE, - repoLabel: 'orca', - worktreeLabel: 'feature', - agentType: 'claude', - agentState: 'done', - agentPrompt: 'fix the bug', - agentLastAssistantMessage: 'Fixed it.' - }) - // The window's request was made with desktop banners off; only that flag differs. - expect(fromWindow).toEqual([{ ...sent[0], desktopAllowed: false }]) - }) }) diff --git a/src/main/runtime/terminal-agent-prompt-answer.test.ts b/src/main/runtime/terminal-agent-prompt-answer.test.ts index 66a5b0a2856..bec314134b6 100644 --- a/src/main/runtime/terminal-agent-prompt-answer.test.ts +++ b/src/main/runtime/terminal-agent-prompt-answer.test.ts @@ -68,18 +68,6 @@ function harness() { } describe('TerminalAgentPromptAnswers', () => { - it('refuses a second answer to the same prompt before the agent reports moving on', async () => { - const h = harness() - const { promptId } = h.answers.pending(h.runtime, target)! - await h.answer(promptId) - expect(h.answers.pending(h.runtime, target)).toBeNull() - await expect(h.answer(promptId)).resolves.toEqual({ - outcome: 'refused', - reason: 'prompt_not_pending' - }) - expect(h.keys).toEqual(['1']) - }) - it('allows only one concurrent answer to claim the prompt', async () => { const h = harness() const { promptId } = h.answers.pending(h.runtime, target)! @@ -118,16 +106,6 @@ describe('TerminalAgentPromptAnswers', () => { expect(h.answers.pending(h.runtime, target)).toBeNull() }) - it('refuses a prompt the agent has moved on from, or a later one', async () => { - const h = harness() - const { promptId } = h.answers.pending(h.runtime, target)! - h.state.row = approvalRow(20) - await expect(h.answer(promptId)).resolves.toMatchObject({ reason: 'prompt_not_pending' }) - h.state.row = { ...approvalRow(20), state: 'working' } - await expect(h.answer(promptId)).resolves.toMatchObject({ reason: 'prompt_not_pending' }) - expect(h.keys).toEqual([]) - }) - it('refuses when the prompt or the process changes between the read and the write', async () => { const h = harness() const first = h.answers.pending(h.runtime, target)!.promptId @@ -144,25 +122,6 @@ describe('TerminalAgentPromptAnswers', () => { expect(h.keys).toEqual([]) }) - it('refuses an option the prompt does not offer, and a prompt only Orca can answer', async () => { - const h = harness() - const { promptId } = h.answers.pending(h.runtime, target)! - await expect(h.answer(promptId, 'allow_always')).resolves.toMatchObject({ - reason: 'option_not_offered' - }) - const hookless = { ...target, answerable: false } - const unsupported = h.answers.pending(h.runtime, hookless)! - expect(unsupported.kind).toBe('unsupported') - await expect( - h.answers.answer(h.runtime, hookless, { - promptId: unsupported.promptId, - optionId: 'allow_once', - signal: new AbortController().signal - }) - ).resolves.toMatchObject({ reason: 'prompt_unsupported' }) - expect(h.keys).toEqual([]) - }) - it('keeps a write the transport refused answerable, and an unprovable one answered', async () => { const h = harness() const { promptId } = h.answers.pending(h.runtime, target)! @@ -191,20 +150,4 @@ describe('TerminalAgentPromptAnswers', () => { }) expect(h.answers.pending(h.runtime, target)?.promptId).toBe(promptId) }) - - it('refuses before any key when the caller’s own check fails', async () => { - const h = harness() - const { promptId } = h.answers.pending(h.runtime, target)! - await expect( - h.answers.answer(h.runtime, target, { - promptId, - optionId: 'allow_once', - signal: new AbortController().signal, - beforeWrite: async () => { - throw new Error('agent_not_in_foreground') - } - }) - ).resolves.toEqual({ outcome: 'refused', reason: 'agent_not_reachable' }) - expect(h.keys).toEqual([]) - }) }) diff --git a/src/main/runtime/terminal-chat-owner-persistence.test.ts b/src/main/runtime/terminal-chat-owner-persistence.test.ts deleted file mode 100644 index 505444b11c5..00000000000 --- a/src/main/runtime/terminal-chat-owner-persistence.test.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { describe, expect, it } from 'vitest' -import './orca-runtime-tests/runtime-test-module-registration.spec' -import { OrcaRuntimeService } from './orca-runtime' -import { - HEADLESS_LEAF_ID, - TEST_WORKTREE_ID, - makeRuntimeStoreWithWorkspaceSession, - makeWorkspaceSessionWithHeadlessTerminal -} from './orca-runtime-tests/orca-runtime-test-fixtures.spec' -import { UpdatePaneLayout } from '../../shared/rpc-contract/session-tabs-schemas-params' - -describe('remote terminal chat ownership', () => { - it('preserves, changes, and explicitly clears the owner across the RPC and restart boundary', async () => { - const { runtimeStore, getSession } = makeRuntimeStoreWithWorkspaceSession( - makeWorkspaceSessionWithHeadlessTerminal() - ) - const runtime = new OrcaRuntimeService(runtimeStore) - const update = (owner: string | null | undefined) => { - const params = UpdatePaneLayout.parse({ - worktree: `id:${TEST_WORKTREE_ID}`, - tabId: 'host-tab', - root: getSession().terminalLayoutsByTabId['host-tab']!.root, - expandedLeafId: null, - ...(owner !== undefined ? { chatLeafId: owner } : {}) - }) - return runtime.updateMobileSessionPaneLayout(params.worktree, { - ...params, - expandedLeafId: params.expandedLeafId ?? null - }) - } - await update(HEADLESS_LEAF_ID) - expect(getSession().terminalLayoutsByTabId['host-tab']?.chatLeafId).toBe(HEADLESS_LEAF_ID) - // An older client changing geometry must not clear the owner. - await update(undefined) - expect(getSession().terminalLayoutsByTabId['host-tab']?.chatLeafId).toBe(HEADLESS_LEAF_ID) - runtime['mobileSessionTabsByWorktree'].delete(TEST_WORKTREE_ID) - runtime['hydrateHeadlessMobileSessionTabsFromWorkspaceSession'](TEST_WORKTREE_ID) - const rehydrated = await runtime.listMobileSessionTabs(`id:${TEST_WORKTREE_ID}`) - const surface = rehydrated.tabs.find( - (tab) => tab.type === 'terminal' && tab.parentTabId === 'host-tab' - ) - expect(surface?.type === 'terminal' && surface.parentLayout?.chatLeafId).toBe(HEADLESS_LEAF_ID) - await update(null) - expect(getSession().terminalLayoutsByTabId['host-tab']?.chatLeafId).toBeUndefined() - const cleared = await runtime.listMobileSessionTabs(`id:${TEST_WORKTREE_ID}`) - expect( - cleared.tabs - .filter((tab) => tab.type === 'terminal') - .every((tab) => !tab.parentLayout?.chatLeafId) - ).toBe(true) - }) -}) diff --git a/src/main/runtime/terminal-intentional-stop-exit.test.ts b/src/main/runtime/terminal-intentional-stop-exit.test.ts index 2820964970c..cb409d02020 100644 --- a/src/main/runtime/terminal-intentional-stop-exit.test.ts +++ b/src/main/runtime/terminal-intentional-stop-exit.test.ts @@ -7,7 +7,7 @@ import { makePaneKey } from '../../shared/workspace-layout/stable-pane-id' import { Store } from '../persistence/loading-store/store' import { ProfileStateSqliteAuthority } from '../persistence/profile-state/profile-state-sqlite-authority' import { wirePtyIpcSession } from '../ipc/pty/delivery/wire-session' -import { SYNTHETIC_KILL_EXIT_DUPLICATE_WINDOW_MS } from '../ipc/pty/delivery/visibility-state' +import '../ipc/pty/delivery/visibility-state' import { bindProviderListeners } from '../ipc/pty/provider/bind-listeners' import { stopRendererOwnedPty, @@ -149,24 +149,6 @@ describe('intentional stops keep the pane through the exit', () => { ]) }) - it('keeps the tab and its wake binding when the renderer hibernates the pane', async () => { - const harness = createHarness() - - await stopRendererOwnedPty(harness.deps, { id: PTY_ID, keepHistory: true }) - - expect(harness.tabIds()).toEqual([TAB_ID]) - expect(harness.boundPtyId()).toBe(PTY_ID) - expect(harness.rendererExits()).toEqual([ - { - id: PTY_ID, - code: 0, - incarnationId: INCARNATION_ID, - hostId: 'local', - preserveRendererBinding: true - } - ]) - }) - it('keeps a typed pane that a restart replaces, and binds the replacement', async () => { const harness = createHarness() harness.runtime.terminalRunFacts.recordSpawnCommit({ @@ -204,48 +186,6 @@ describe('intentional stops keep the pane through the exit', () => { ]) }) - it('labels the exit for both a sleep and a restart that stop the same process', async () => { - const harness = createHarness() - const settleSleep = harness.runtime.intentionalPtyStops.mark( - PTY_ID, - 'reversible', - INCARNATION_ID - ) - - await stopReplacedPanePty(harness.deps, PTY_ID) - settleSleep(true) - - expect(harness.boundPtyId()).toBe(PTY_ID) - expect(harness.rendererExits()).toEqual([ - { - id: PTY_ID, - code: 0, - incarnationId: INCARNATION_ID, - hostId: 'local', - preserveRendererBinding: true, - replacedByRestart: true - } - ]) - }) - - it('keeps the pane when main restarts the daemon under it', () => { - const harness = createHarness() - - harness.emitDaemonRestartExit(true) - - expect(harness.tabIds()).toEqual([TAB_ID]) - expect(harness.boundPtyId()).toBe(PTY_ID) - expect(harness.rendererExits()).toEqual([ - { - id: PTY_ID, - code: -1, - incarnationId: INCARNATION_ID, - hostId: 'local', - replacedByRestart: true - } - ]) - }) - it('retires the pane on a daemon-wide exit that no restart claims', () => { const harness = createHarness() @@ -308,20 +248,6 @@ describe('intentional stops keep the pane through the exit', () => { }) }) - it('forgets the stop once the duplicate-exit window after it closes', async () => { - vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout'] }) - const harness = createHarness({ lateProviderExit: true }) - await stopRendererOwnedPty(harness.deps, { id: PTY_ID, keepHistory: true }) - - vi.advanceTimersByTime(SYNTHETIC_KILL_EXIT_DUPLICATE_WINDOW_MS - 1) - expect(harness.runtime.intentionalPtyStops.claimExit(PTY_ID, INCARNATION_ID)).toEqual([ - 'reversible' - ]) - vi.advanceTimersByTime(1) - - expect(harness.runtime.intentionalPtyStops.claimExit(PTY_ID, INCARNATION_ID)).toEqual([]) - }) - it('keeps the tab and its wake binding when the runtime puts the worktree to sleep', async () => { const harness = createHarness({ folder: true }) const inventories = [[{ id: PTY_ID, worktreeId: WORKTREE_ID, cwd: WORKTREE_PATH, title: 'a' }]] diff --git a/src/main/runtime/terminal-interactive-wait-visibility.test.ts b/src/main/runtime/terminal-interactive-wait-visibility.test.ts index 686a8008dd6..37c8543fb2b 100644 --- a/src/main/runtime/terminal-interactive-wait-visibility.test.ts +++ b/src/main/runtime/terminal-interactive-wait-visibility.test.ts @@ -1,15 +1,10 @@ -import type { TerminalWaiter } from './terminal/wait/terminal-waiter' -import type { - RuntimeTerminalWait, - RuntimeTerminalAgentStatus -} from '../../shared/runtime-terminal-contracts' -import { RuntimeMachineName } from './hosts/platform-probe/runtime-machine-name' -import { OrcaRuntimeService } from './orca-runtime' -import { makeTuiIdlePty } from './terminal/wait/tui-idle-wait-test-harness' +import './hosts/platform-probe/runtime-machine-name' +import './orca-runtime' +import './terminal/wait/tui-idle-wait-test-harness' import { readFileSync } from 'node:fs' import { makeAgentStatusStoreWiring } from './agent-status-store-wiring.test-fixture' import { join } from 'node:path' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { afterEach, describe, expect, it, vi } from 'vitest' import { TERMINAL_INTERACTIVE_WAIT_PROBE_TIMEOUT_MS } from './terminal/wait/terminal-interactive-probe-budget' import { createTranscriptPane, @@ -36,8 +31,6 @@ describe('Interactive wait visibility and terminal admission', () => { return readFileSync(join(__dirname, '__fixtures__', `${name}.txt`), 'utf8') } const CURSOR_APPROVAL = fixture('cursor-agent-approval-prompt') - const CURSOR_LONG_TOOL_CALL = fixture('cursor-agent-long-tool-call') - const CURSOR_IDLE = fixture('cursor-agent-idle-after-approval') // Claude Code 2.1.234's own trust screen, which the runtime already matched by shape. const CLAUDE_TRUST = [ @@ -67,23 +60,6 @@ describe('Interactive wait visibility and terminal admission', () => { describe('terminal interactive-wait visibility (STA-4513, STA-3714)', () => { describe('cursor-agent approval menu, the case no hook reports', () => { - it('names the pending approval on the pane a coordinator inspects', async () => { - const { runtime, handle } = await createPane({ - paneTitle: CURSOR_TITLE, - foregroundProcess: 'cursor-agent', - data: CURSOR_APPROVAL - }) - - await expect(runtime.showTerminal(handle)).resolves.toMatchObject({ - agentWait: { source: 'prompt-text', reason: 'agent-approval-prompt' } - }) - await expect(runtime.getTerminalInteractiveWait(handle)).resolves.toMatchObject({ - source: 'prompt-text', - reason: 'agent-approval-prompt', - since: expect.any(Number) - }) - }) - it('refuses a guarded send and a dispatch preamble while the approval is up', async () => { // Refuse both injection paths so coordinator preambles cannot land in approval dialogs (STA-2631). const { runtime, handle } = await createPane({ @@ -104,18 +80,6 @@ describe('Interactive wait visibility and terminal admission', () => { ).rejects.toThrow('agent_prompt_blocked') }) - it('lets a dispatch preamble through once the same lane is working', async () => { - const { runtime, handle } = await createPane({ - paneTitle: CURSOR_TITLE, - foregroundProcess: 'cursor-agent', - data: CURSOR_LONG_TOOL_CALL - }) - - await expect( - assertTerminalAgentSendable({ runtime, handle, assertWritable: () => {} }) - ).resolves.toBeUndefined() - }) - it('refuses to call the lane idle while the approval is unanswered', async () => { const { runtime, handle } = await createPane({ paneTitle: CURSOR_TITLE, @@ -128,39 +92,6 @@ describe('Interactive wait visibility and terminal admission', () => { ).resolves.toMatchObject({ satisfied: false, blockedReason: 'agent-approval-prompt' }) }) - it('reports no wait for the same agent inside a long tool call', async () => { - // The control: identical vendor, identical spinner title, a real `sleep 60` running. - const { runtime, handle } = await createPane({ - paneTitle: CURSOR_TITLE, - foregroundProcess: 'cursor-agent', - data: CURSOR_LONG_TOOL_CALL - }) - - await expect(runtime.getTerminalInteractiveWait(handle)).resolves.toBeNull() - await expect(runtime.showTerminal(handle)).resolves.toMatchObject({ agentWait: null }) - }) - - it('reports no wait once the agent is idle again', async () => { - const { runtime, handle } = await createPane({ - paneTitle: CURSOR_TITLE, - foregroundProcess: 'cursor-agent', - data: CURSOR_IDLE - }) - - await expect(runtime.getTerminalInteractiveWait(handle)).resolves.toBeNull() - }) - - it('treats an answered menu still in scrollback as scrollback', async () => { - // A live dialog must still own the screen bottom; stale menu text can remain in scrollback. - const { runtime, handle } = await createPane({ - paneTitle: CURSOR_TITLE, - foregroundProcess: 'cursor-agent', - data: `${CURSOR_APPROVAL}\n${CURSOR_IDLE}` - }) - - await expect(runtime.getTerminalInteractiveWait(handle)).resolves.toBeNull() - }) - it('treats an answered menu followed by any later output as scrollback', async () => { // Any output after a menu can disprove it; matching only Cursor's follow-up text would block healthy prompt injection. const { runtime, handle } = await createPane({ @@ -178,80 +109,9 @@ describe('Interactive wait visibility and terminal admission', () => { await expect(runtime.getTerminalInteractiveWait(handle)).resolves.toBeNull() }) - - it('is not anchored by the agent narrating a choice after the menu', async () => { - // Independent marker matches can let later prose revive an answered menu at screen bottom. - const { runtime, handle } = await createPane({ - paneTitle: CURSOR_TITLE, - foregroundProcess: 'cursor-agent', - data: `${CURSOR_APPROVAL}\nApproved. Next time I will suggest Run Everything instead.\n` - }) - - await expect(runtime.getTerminalInteractiveWait(handle)).resolves.toBeNull() - }) - - it('is not anchored by the agent narrating a choice before the menu', async () => { - const { runtime, handle } = await createPane({ - paneTitle: CURSOR_TITLE, - foregroundProcess: 'cursor-agent', - data: `You can pick Run Everything or Run (once) if you prefer.\nStill thinking.\n` - }) - - await expect(runtime.getTerminalInteractiveWait(handle)).resolves.toBeNull() - }) - - it('requires the dialog to be the last thing on the screen', async () => { - // One line of slack revives answered menus; live captures end exactly at the dialog. - const { runtime, handle } = await createPane({ - paneTitle: CURSOR_TITLE, - foregroundProcess: 'cursor-agent', - data: `${CURSOR_APPROVAL}\n Auto · 6.1%\n` - }) - - await expect(runtime.getTerminalInteractiveWait(handle)).resolves.toBeNull() - }) - - it('reports the same wait for a pane whose PTY is not local', async () => { - // The verdict is derived from retained tail and title state, so an SSH or WSL pane - // reaches it the same way a local one does. - const { runtime, handle } = await createPane({ - paneTitle: CURSOR_TITLE, - foregroundProcess: 'cursor-agent', - data: CURSOR_APPROVAL, - connectionId: 'ssh:build-host' - }) - - await expect(runtime.getTerminalInteractiveWait(handle)).resolves.toMatchObject({ - source: 'prompt-text', - reason: 'agent-approval-prompt' - }) - }) - - it('ignores a partial menu that names no decision keys', async () => { - const { runtime, handle } = await createPane({ - paneTitle: CURSOR_TITLE, - foregroundProcess: 'cursor-agent', - data: 'The agent asked: Run this command? I said yes and it worked.\n' - }) - - await expect(runtime.getTerminalInteractiveWait(handle)).resolves.toBeNull() - }) }) describe('prompts the runtime already matched but never surfaced', () => { - it('surfaces a startup trust screen on the pane, not only on terminal wait', async () => { - // A pane on its trust screen still wears Orca's tab title; the agent has set none. - const { runtime, handle } = await createPane({ - paneTitle: 'sta4513-claude', - foregroundProcess: 'claude', - data: CLAUDE_TRUST - }) - - await expect(runtime.showTerminal(handle)).resolves.toMatchObject({ - agentWait: { source: 'prompt-text', reason: 'agent-trust-workspace' } - }) - }) - it('still lets a live working title clear a stale startup prompt', async () => { // Startup prompt text lacks dismissal markers; a live non-permission title proves staleness for both status and send guards. const { runtime, handle } = await createPane({ @@ -281,16 +141,6 @@ describe('Interactive wait visibility and terminal admission', () => { }) }) - it('reports no wait for a hook-reported working turn', async () => { - const { runtime, handle } = await createPane({ - paneTitle: '✻ Claude Code', - foregroundProcess: 'claude', - data: agentStatusOsc('working') - }) - - await expect(runtime.getTerminalInteractiveWait(handle)).resolves.toBeNull() - }) - it('drops a retained permission row once the agent stops owning the pane', async () => { // Shell titles may match no rule; a fresh hook row could otherwise report a dead agent waiting for 30 minutes. const { runtime, handle } = await createPane({ @@ -301,16 +151,6 @@ describe('Interactive wait visibility and terminal admission', () => { await expect(runtime.getTerminalInteractiveWait(handle)).resolves.toBeNull() }) - - it('drops a retained permission row once a shell owns the pane', async () => { - const { runtime, handle } = await createPane({ - paneTitle: 'zsh', - foregroundProcess: 'zsh', - data: agentStatusOsc('blocked') - }) - - await expect(runtime.getTerminalInteractiveWait(handle)).resolves.toBeNull() - }) }) it('still reports a live dialog restored from terminal history', async () => { @@ -384,32 +224,6 @@ describe('Interactive wait visibility and terminal admission', () => { } }, 20_000) - it('accepts a menu whose keys are rendered as glyphs', async () => { - const { runtime, handle } = await createPane({ - paneTitle: CURSOR_TITLE, - foregroundProcess: 'cursor-agent', - data: [ - 'Run this command?\n', - ' → Run (once) (\u21b5)\n', - ' Run Everything (\u21e7\u21b9)\n' - ].join('') - }) - - await expect(runtime.getTerminalInteractiveWait(handle)).resolves.toMatchObject({ - reason: 'agent-approval-prompt' - }) - }) - - it('still rejects prose that merely ends in parentheses', async () => { - const { runtime, handle } = await createPane({ - paneTitle: CURSOR_TITLE, - foregroundProcess: 'cursor-agent', - data: `${CURSOR_APPROVAL}\nNext time I will suggest Run Everything (as before)\n` - }) - - await expect(runtime.getTerminalInteractiveWait(handle)).resolves.toBeNull() - }) - it('leaves the wait unevaluated when the foreground probe wedges', async () => { // Bound remote PTY probes; a wedged host must leave wait unknown without stalling showTerminal. const { runtime, handle } = await createPane({ @@ -434,18 +248,6 @@ describe('Interactive wait visibility and terminal admission', () => { vi.useRealTimers() } }, 15_000) - - it('answers undefined rather than "not waiting" for a pane it cannot read', async () => { - const { runtime } = await createPane({ - paneTitle: CURSOR_TITLE, - foregroundProcess: 'cursor-agent', - data: CURSOR_APPROVAL - }) - - await expect( - runtime.getTerminalInteractiveWait('term_does_not_exist') - ).resolves.toBeUndefined() - }) }) }) @@ -472,224 +274,3 @@ describe('Screen rule readiness scope', () => { }, 20_000) }) }) - -/** Stands in for a mailbox with work pending, which these timer mechanics take as given. */ -function withPendingDelivery(runtime: OrcaRuntimeService): OrcaRuntimeService { - runtime['getOrchestrationMailboxTerminal'] = (ptyId) => ({ - ptyId, - handle: 'term_pending', - paneKeys: [], - lastAgentStatus: 'idle', - lastAgentStatusObservedLive: true - }) - vi.spyOn(runtime['orchestrationMailboxNotifications'], 'hasDeliverableMail').mockReturnValue(true) - return runtime -} - -describe('terminal wait ownership', () => { - beforeEach(() => { - vi.spyOn(RuntimeMachineName.prototype, 'start').mockImplementation(() => {}) - vi.useFakeTimers() - vi.setSystemTime(10_000) - }) - afterEach(() => { - vi.useRealTimers() - vi.restoreAllMocks() - }) - - function pendingStatus() { - const { promise, resolve } = Promise.withResolvers() - return { status: promise, finish: resolve } - } - - function interactiveWait(getStatus: (handle: string) => Promise) { - const runtime = new OrcaRuntimeService() - runtime['getTerminalAgentStatusPtyId'] = () => 'pty-1' - runtime['getTerminalWaitPermissionInputs'] = () => ({ - terminal: { - waitText: '', - waitBlockedAt: null, - title: null, - titleStatus: null, - titleStatusIsLive: false - }, - lifecycle: null - }) - runtime['getFreshExplicitAgentStatusForHandle'] = () => ({ - status: 'permission', - updatedAt: 42, - stateStartedAt: 42 - }) - runtime['resolveAuthoritativeTerminalWaitPermission'] = () => null - runtime.getTerminalAgentStatus = getStatus - return runtime - } - - it('shares permission probes per PTY and releases the completed probe', async () => { - const pending = pendingStatus() - const getStatus = vi.fn(() => pending.status) - const wait = interactiveWait(getStatus) - const first = wait.getTerminalInteractiveWait('handle-1') - const second = wait.getTerminalInteractiveWait('handle-2') - expect(getStatus).toHaveBeenCalledOnce() - pending.finish({ handle: 'handle-1', isRunningAgent: true, status: 'permission' }) - expect(await first).toEqual({ source: 'hook', since: 42 }) - expect(await second).toEqual({ source: 'hook', since: 42 }) - await wait.getTerminalInteractiveWait('handle-3') - expect(getStatus).toHaveBeenCalledTimes(2) - }) - - it('keeps a timed-out permission probe in flight until its host responds', async () => { - const pending = pendingStatus() - const getStatus = vi.fn(() => pending.status) - const wait = interactiveWait(getStatus) - const first = wait.getTerminalInteractiveWait('handle-1') - await vi.advanceTimersByTimeAsync(2000) - expect(await first).toBeUndefined() - const second = wait.getTerminalInteractiveWait('handle-2') - expect(getStatus).toHaveBeenCalledOnce() - pending.finish({ handle: 'handle-1', isRunningAgent: true, status: 'permission' }) - expect(await second).toEqual({ source: 'hook', since: 42 }) - }) - - it('isolates permission probes between runtimes with the same PTY id', async () => { - const pending = pendingStatus() - const getStatus = vi.fn(() => pending.status) - const first = interactiveWait(getStatus).getTerminalInteractiveWait('handle-1') - const second = interactiveWait(getStatus).getTerminalInteractiveWait('handle-1') - expect(getStatus).toHaveBeenCalledTimes(2) - pending.finish({ handle: 'handle-1', isRunningAgent: false, status: null }) - expect(await first).toBeNull() - expect(await second).toBeNull() - }) - - it('rechecks current terminal evidence before delivery and coalesces repeated refusals', async () => { - const pty = makeTuiIdlePty({ - lastOutputAt: Date.now(), - lastAgentStatus: 'idle', - lastOscTitle: 'Codex' - }) - const deliver = vi.fn() - const runtime = withPendingDelivery(new OrcaRuntimeService()) - const ownedPty = runtime['terminalIdentity'].createPty(pty) - runtime['deliverPendingMessagesForPty'] = deliver - expect(runtime['checkDeliverySettledAndArmRecheck'](pty.ptyId)).toBe(false) - expect(runtime['checkDeliverySettledAndArmRecheck'](pty.ptyId)).toBe(false) - expect(vi.getTimerCount()).toBe(1) - ownedPty.lastOscTitle = null - await vi.advanceTimersByTimeAsync(3050) - expect(deliver).toHaveBeenCalledExactlyOnceWith(pty.ptyId) - expect(vi.getTimerCount()).toBe(0) - }) - - it('cancels only the delivery recheck belonging to its runtime', async () => { - const pty = makeTuiIdlePty({ - lastOutputAt: Date.now(), - lastAgentStatus: 'idle', - lastOscTitle: 'Codex' - }) - const first = withPendingDelivery(new OrcaRuntimeService()) - const second = withPendingDelivery(new OrcaRuntimeService()) - first['terminalIdentity'].createPty(pty) - second['terminalIdentity'].createPty(pty) - first['checkDeliverySettledAndArmRecheck'](pty.ptyId) - second['checkDeliverySettledAndArmRecheck'](pty.ptyId) - expect(vi.getTimerCount()).toBe(2) - first['clearDeliveryRecheck'](pty.ptyId) - expect(vi.getTimerCount()).toBe(1) - second['clearDeliveryRecheck'](pty.ptyId) - expect(vi.getTimerCount()).toBe(0) - }) - - it('clears a pending blocked scan without cancelling another runtime scan', async () => { - const pty = makeTuiIdlePty({ waitBlockedAt: null }) - const getPty = vi.fn(() => pty) - const first = new OrcaRuntimeService() - const second = new OrcaRuntimeService() - vi.spyOn(first['ptysById'], 'get').mockImplementation(getPty) - vi.spyOn(second['ptysById'], 'get').mockImplementation(getPty) - first['scheduleWaitBlockedCheck']('pty-1', 'output', 10_000) - second['scheduleWaitBlockedCheck']('pty-1', 'output', 10_000) - first['scheduleWaitBlockedCheck']('pty-1', 'more', 10_001) - second['scheduleWaitBlockedCheck']('pty-1', 'more', 10_001) - expect(vi.getTimerCount()).toBe(2) - first['clearWaitBlockedCheckState']('pty-1') - expect(vi.getTimerCount()).toBe(1) - await vi.advanceTimersByTimeAsync(50) - expect(getPty).toHaveBeenCalledTimes(3) - expect(vi.getTimerCount()).toBe(0) - }) - - it('routes settlement through the current runtime verdict and resolver', async () => { - const runtime = new OrcaRuntimeService() - const pending = Promise.withResolvers() - const waiter: TerminalWaiter = { - handle: 'handle-1', - ptyId: 'pty-1', - condition: 'tui-idle', - resolve: pending.resolve, - reject: pending.reject, - timeout: null, - cancelIdlePoll: null, - abortCleanup: null - } - runtime['terminalWaiters'].add(waiter) - const verdict = vi.fn(() => ({ - kind: 'ready-strong' - })) - runtime['evaluateTuiIdleForPty'] = verdict - const original = runtime['resolveWaiter'] - const resolve = vi.fn((...args) => - original.call(runtime, ...args) - ) - runtime['resolveWaiter'] = resolve - const pty = makeTuiIdlePty({ lastAgentStatus: 'working' }) - runtime['resolvePtyTuiIdleWaiters'](pty, 'pty-1') - expect(verdict).toHaveBeenCalledExactlyOnceWith(pty) - expect(resolve).toHaveBeenCalledOnce() - await expect(pending.promise).resolves.toMatchObject({ satisfied: true, condition: 'tui-idle' }) - }) - - it('keeps an absent launcher target as a rejected promise', async () => { - const runtime = new OrcaRuntimeService() - const result = runtime.resolveOrchestrationAgentLauncherForTarget('custom', {}) - expect(result).toBeInstanceOf(Promise) - await expect(result).rejects.toThrow() - }) - - it('reads the launcher resolver before forwarding an absent target', async () => { - const runtime = new OrcaRuntimeService() - const events: string[] = [] - const rejection = new Error('original_resolver_rejection') - const resolve = vi.fn( - async function (this: OrcaRuntimeService) { - events.push('original-call') - expect(this).toBe(runtime) - throw rejection - } - ) - const replacement = vi.fn() - Object.defineProperty(runtime, 'resolveTerminalWorkspaceLaunchScope', { - configurable: true, - get() { - events.push('callee') - return resolve - } - }) - const target = { - get worktree() { - events.push('argument') - Object.defineProperty(runtime, 'resolveTerminalWorkspaceLaunchScope', { - value: replacement - }) - return undefined - } - } - const result = runtime.resolveOrchestrationAgentLauncherForTarget('custom', target) - expect(result).toBeInstanceOf(Promise) - await expect(result).rejects.toBe(rejection) - expect(events).toEqual(['callee', 'argument', 'original-call']) - expect(resolve).toHaveBeenCalledExactlyOnceWith(undefined) - expect(replacement).not.toHaveBeenCalled() - }) -}) diff --git a/src/main/runtime/terminal-pane-recovery-liveness-gate.test.ts b/src/main/runtime/terminal-pane-recovery-liveness-gate.test.ts index c8f0684302e..87352429275 100644 --- a/src/main/runtime/terminal-pane-recovery-liveness-gate.test.ts +++ b/src/main/runtime/terminal-pane-recovery-liveness-gate.test.ts @@ -59,27 +59,6 @@ describe('terminal.recoverPane liveness gate', () => { expect(createTerminal).toHaveBeenCalledOnce() }) - it('refuses to recreate a shell for an expired lease the host proved is still live', async () => { - // Successful relay reattach followed by binding refusal records live then expired; another spawn would duplicate a proven-live agent. - const tabId = 'tab-proved-live' - const ptyId = 'ssh:ssh-target@@pty-11' - const runtime = createRuntimeWithSshLease(ptyId, tabId) - const paneKey = makePaneKey(tabId, HEADLESS_LEAF_ID) - registerBoundPty(runtime, ptyId, TEST_WORKTREE_ID, 'ssh-target', { - tabId, - leafId: HEADLESS_LEAF_ID - }) - const handle = runtime.resolveTerminalPane(paneKey, TEST_WORKTREE_ID).handle - runtime.onPtyExit(ptyId, -1) - runtime.markPtyLivenessLive(ptyId) - const createTerminal = vi.spyOn(runtime, 'createTerminal') - - await expect(runtime.recoverTerminalPane(paneKey, TEST_WORKTREE_ID, handle)).rejects.toThrow( - 'terminal_not_recoverable' - ) - expect(createTerminal).not.toHaveBeenCalled() - }) - it('refuses to recreate a shell for an expired lease whose PTY liveness is unverifiable', async () => { // An unconfirmed SSH -1 exit plus expired lease proves no process exit and cannot authorize replacement. const tabId = 'tab-unverifiable-gate' @@ -184,38 +163,6 @@ function replacementTerminal(tabId: string): RuntimeTerminalCreate { describe('terminal pane recovery uses the current Root state', () => { afterEach(() => vi.restoreAllMocks()) - it('reads the replacement pending map before connected-pane and lease checks', async () => { - const tabId = 'tab-current-recovery-map' - const { runtime, paneKey, handle, recoveryKey } = createRecoveryPane(tabId) - const original = runtime.pendingRecoveries() - original.set( - recoveryKey, - Promise.resolve({ - handle: 'term-old-map', - tabId, - leafId: HEADLESS_LEAF_ID, - ptyId: null - }) - ) - const pending = Promise.resolve({ - handle: 'term-current-map', - tabId, - leafId: HEADLESS_LEAF_ID, - ptyId: null - }) - const current = new Map([[recoveryKey, pending]]) - runtime.usePendingRecoveries(current) - const create = vi.spyOn(runtime, 'createTerminal') - - await expect( - runtime.recoverTerminalPane(paneKey, TEST_WORKTREE_ID, handle) - ).resolves.toMatchObject({ handle: 'term-current-map' }) - expect(runtime.pendingRecoveries()).toBe(current) - expect(current.get(recoveryKey)).toBe(pending) - expect(original.has(recoveryKey)).toBe(true) - expect(create).not.toHaveBeenCalled() - }) - it('shares a rejected recovery and removes it before a later retry', async () => { const tabId = 'tab-rejected-recovery' const { runtime, paneKey, ptyId, handle, recoveryKey } = createRecoveryPane(tabId) @@ -243,37 +190,4 @@ describe('terminal pane recovery uses the current Root state', () => { ).resolves.toMatchObject({ handle: 'term-replacement' }) expect(create).toHaveBeenCalledTimes(2) }) - - it('keeps a newer pending entry when an earlier recovery settles', async () => { - const tabId = 'tab-newer-recovery' - const { runtime, paneKey, ptyId, handle, recoveryKey } = createRecoveryPane(tabId) - runtime.onPtyExit(ptyId, -1, undefined, { hostExitConfirmed: true }) - const creation = deferred() - const create = vi.spyOn(runtime, 'createTerminal').mockImplementation(function ( - this: PaneRecoveryRuntime, - selector, - options - ) { - expect(this).toBe(runtime) - expect(arguments.length).toBe(2) - expect(selector).toBe(`id:${TEST_WORKTREE_ID}`) - expect(options).toEqual({ tabId, leafId: HEADLESS_LEAF_ID, focus: false, reopensPane: true }) - return creation.promise - }) - const first = runtime.recoverTerminalPane(paneKey, TEST_WORKTREE_ID, handle) - const newer = Promise.resolve({ - handle: 'term-newer', - tabId, - leafId: HEADLESS_LEAF_ID, - ptyId: null - }) - const current = new Map([[recoveryKey, newer]]) - runtime.usePendingRecoveries(current) - creation.resolve(replacementTerminal(tabId)) - - await expect(first).resolves.toMatchObject({ handle: 'term-replacement' }) - expect(create).toHaveBeenCalledOnce() - expect(runtime.pendingRecoveries()).toBe(current) - expect(current.get(recoveryKey)).toBe(newer) - }) }) diff --git a/src/main/runtime/terminal-query-responder.test.ts b/src/main/runtime/terminal-query-responder.test.ts index 5142f1a6776..86497cdce5e 100644 --- a/src/main/runtime/terminal-query-responder.test.ts +++ b/src/main/runtime/terminal-query-responder.test.ts @@ -5,7 +5,6 @@ import { HeadlessEmulator } from '../daemon/emulation/headless-emulator' import { _resetHiddenRendererPtyDeliveryGateForTest, markHiddenRendererPty, - setRendererPtyDeliveryInterest, unmarkHiddenRendererPty } from '../ipc/pty/delivery/pty-hidden-delivery-gate' import { @@ -116,116 +115,7 @@ afterEach(() => { settingsState.terminalModelQueryAuthority = true }) -describe('reply parity for hidden-dropped chunks', () => { - // Pin design replies against bundled xterm/headless, which shares renderer core. - it.each([ - ['DA1 CSI c', '\x1b[c', ['\x1b[?1;2c']], - ['DA1 CSI 0 c variant', '\x1b[0c', ['\x1b[?1;2c']], - ['DA2', '\x1b[>c', ['\x1b[>0;276;0c']], - ['DSR 5n operating status', '\x1b[5n', ['\x1b[0n']], - ['CPR 6n at origin', '\x1b[6n', ['\x1b[1;1R']], - ['CPR 6n reports the model cursor position', 'hello\r\nworld\x1b[6n', ['\x1b[2;6R']], - ['DECXCPR ?6n', '\x1b[?6n', ['\x1b[?1;1R']], - ['DECRPM ?1 DECCKM default', '\x1b[?1$p', ['\x1b[?1;2$y']], - ['DECRPM ?6 DECOM default', '\x1b[?6$p', ['\x1b[?6;2$y']], - ['DECRPM ?7 DECAWM default', '\x1b[?7$p', ['\x1b[?7;1$y']], - ['DECRPM ?25 DECTCEM default', '\x1b[?25$p', ['\x1b[?25;1$y']], - ['DECRPM ?1004 focus events default', '\x1b[?1004$p', ['\x1b[?1004;2$y']], - ['DECRPM ?1006 SGR mouse default', '\x1b[?1006$p', ['\x1b[?1006;2$y']], - ['DECRPM ?1016 SGR pixels default', '\x1b[?1016$p', ['\x1b[?1016;2$y']], - ['DECRPM ?1049 alt screen default', '\x1b[?1049$p', ['\x1b[?1049;2$y']], - ['DECRPM ?2004 bracketed paste default', '\x1b[?2004$p', ['\x1b[?2004;2$y']], - ['DECRPM ?2026 synchronized output default', '\x1b[?2026$p', ['\x1b[?2026;2$y']], - ['DECRPM reports a set mode as enabled', '\x1b[?2004h\x1b[?2004$p', ['\x1b[?2004;1$y']], - ['DECRPM unknown mode reports 0', '\x1b[?12345$p', ['\x1b[?12345;0$y']], - ['DECRQM ANSI insert mode', '\x1b[4$p', ['\x1b[4;2$y']], - ['DECRQSS DECSTBM default margins', '\x1bP$qr\x1b\\', ['\x1bP1$r1;24r\x1b\\']], - ['DECRQSS DECSTBM after margin set', '\x1b[5;20r\x1bP$qr\x1b\\', ['\x1bP1$r5;20r\x1b\\']], - ['DECRQSS DECSCUSR default cursor', '\x1bP$q q\x1b\\', ['\x1bP1$r2 q\x1b\\']], - ['DECRQSS DECSCA', '\x1bP$q"q\x1b\\', ['\x1bP1$r0"q\x1b\\']], - ['DECRQSS SGR', '\x1bP$qm\x1b\\', ['\x1bP1$r0m\x1b\\']], - ['XTVERSION', '\x1b[>0q', ['\x1bP>|xterm.js(6.0.0)\x1b\\']], - ['kitty CSI ? u default flags', '\x1b[?u', ['\x1b[?0u']], - ['kitty CSI ? u reports pushed flags', '\x1b[=5;1u\x1b[?u', ['\x1b[?5u']] - ])('%s', async (_label, chunk, expectedReplies) => { - const { runtime, replies, inputKinds } = createResponderRuntime() - markHiddenRendererPty('pty-q') - - runtime.onPtyData('pty-q', chunk, Date.now()) - await settle(runtime, 'pty-q') - - expect(replies.map((reply) => reply.data)).toEqual(expectedReplies) - expect(replies.every((reply) => reply.ptyId === 'pty-q')).toBe(true) - // Why: a reply written as driving input would read the untouched run as typed. - expect(inputKinds).toEqual(expectedReplies.map(() => 'query-reply')) - }) - - it.each([ - ['XTWINOPS', '\x1b[14t'], - ['XTGETTCAP', '\x1bP+q544e\x1b\\'], - ['DSR ?15n printer status', '\x1b[?15n'], - ['DSR ?25n UDK status', '\x1b[?25n'], - ['DSR ?26n keyboard status', '\x1b[?26n'], - ['DSR ?53n locator status', '\x1b[?53n'], - // No renderer attributes means silence; fabricated colors would revive the default-black OSC-11 bug. - ['OSC 10 foreground query', '\x1b]10;?\x07'], - ['OSC 11 background query', '\x1b]11;?\x07'], - ['OSC 12 cursor-color query', '\x1b]12;?\x1b\\'], - ['OSC 4 palette query', '\x1b]4;1;?\x07'], - ['DSR ?996n color-scheme query', '\x1b[?996n'] - ])('stays silent for %s', async (_label, chunk) => { - const { runtime, replies } = createResponderRuntime() - markHiddenRendererPty('pty-q') - - runtime.onPtyData('pty-q', chunk, Date.now()) - await settle(runtime, 'pty-q') - - expect(replies).toEqual([]) - }) -}) - describe('reply ownership matrix', () => { - it('never answers delivered (unmarked) chunks — the visible xterm owns them', async () => { - const { runtime, replies } = createResponderRuntime() - setTerminalViewAttributes(viewAttributes()) - - runtime.onPtyData('pty-v', QUERY_BATCH, Date.now()) - await settle(runtime, 'pty-v') - - expect(replies).toEqual([]) - }) - - it('never answers while renderer delivery interest holds the chunk delivered', async () => { - const { runtime, replies } = createResponderRuntime() - setTerminalViewAttributes(viewAttributes()) - markHiddenRendererPty('pty-i') - setRendererPtyDeliveryInterest('pty-i', true) - - runtime.onPtyData('pty-i', QUERY_BATCH, Date.now()) - await settle(runtime, 'pty-i') - - expect(replies).toEqual([]) - }) - - it.each([ - ['terminalModelQueryAuthority', () => (settingsState.terminalModelQueryAuthority = false)], - ['terminalHiddenDeliveryGate', () => (settingsState.terminalHiddenDeliveryGate = false)], - [ - 'terminalMainSideEffectAuthority', - () => (settingsState.terminalMainSideEffectAuthority = false) - ] - ])('never answers with kill switch %s off', async (_label, flip) => { - flip() - const { runtime, replies } = createResponderRuntime() - setTerminalViewAttributes(viewAttributes()) - markHiddenRendererPty('pty-k') - - runtime.onPtyData('pty-k', QUERY_BATCH, Date.now()) - await settle(runtime, 'pty-k') - - expect(replies).toEqual([]) - }) - it('yields until every remote view subscriber releases, including repeated release', async () => { const { runtime, replies } = createResponderRuntime() setTerminalViewAttributes(viewAttributes()) @@ -286,38 +176,6 @@ describe('reply ownership matrix', () => { expect(replies).toHaveLength(2) }) - // #32944: a paused view's discarded queries fall back to main only when that view alone owned them. - it('reports sole remote query ownership and answers yielded stateless queries', async () => { - const { runtime, replies } = createResponderRuntime() - markHiddenRendererPty('pty-yield') - const releaseA = runtime.registerRemoteTerminalViewSubscriber('pty-yield', () => false) - expect(runtime.isSoleRemoteTerminalQueryOwner('pty-yield')).toBe(true) - const releaseB = runtime.registerRemoteTerminalViewSubscriber('pty-yield', () => false) - expect(runtime.isSoleRemoteTerminalQueryOwner('pty-yield')).toBe(false) - releaseB() - releaseA() - - runtime.onPtyData('pty-yield', 'prompt', 0) - runtime.answerYieldedTerminalQueries('pty-yield', '\x1b[c') - await settle(runtime, 'pty-yield') - expect(replies.map((reply) => reply.data)).toEqual(['\x1b[?1;2c']) - }) - - it('keeps raw preview presence separate from terminal query authority', async () => { - const { runtime, replies } = createResponderRuntime() - markHiddenRendererPty('pty-preview') - const release = runtime.registerRawTerminalViewSubscriber('pty-preview') - - expect(runtime.hasRawTerminalViewSubscriber('pty-preview')).toBe(true) - expect(runtime.hasRemoteTerminalViewSubscriber('pty-preview')).toBe(false) - runtime.onPtyData('pty-preview', '\x1b[c', Date.now()) - await settle(runtime, 'pty-preview') - expect(replies.map((reply) => reply.data)).toEqual(['\x1b[?1;2c']) - - release() - expect(runtime.hasRawTerminalViewSubscriber('pty-preview')).toBe(false) - }) - it('yields to a phone-fitted mobile view but answers for a passive desktop-size one', async () => { const { runtime, replies } = createResponderRuntime() markHiddenRendererPty('pty-fit') @@ -355,20 +213,6 @@ describe('main-side replay guard', () => { '\x1b[?997;1n' ]) }) - - it('never answers queries replayed by renderer-buffer hydration', async () => { - setTerminalViewAttributes(viewAttributes()) - const { runtime, replies } = createResponderRuntime({ - rendererBuffer: { data: `restored screen${QUERY_BATCH}`, cols: 80, rows: 24 } - }) - markHiddenRendererPty('pty-hyd') - - // Hydrated snapshots must not answer embedded terminal queries. - runtime.onPtyData('pty-hyd', 'live output', Date.now()) - await settle(runtime, 'pty-hyd') - - expect(replies).toEqual([]) - }) }) describe('kitty flag re-seed parity (terminal-query-authority.md §kitty)', () => { @@ -387,17 +231,6 @@ describe('kitty flag re-seed parity (terminal-query-authority.md §kitty)', () = await settle(runtime, 'pty-kitty') expect(replies.map((reply) => reply.data)).toEqual(['\x1b[?5u']) }) - - it('answers ?0u when the snapshot carried no flags (fresh-shell paths)', async () => { - const { runtime, replies } = createResponderRuntime() - markHiddenRendererPty('pty-kitty0') - - runtime.seedHeadlessTerminal('pty-kitty0', 'restored prompt') - runtime.onPtyData('pty-kitty0', '\x1b[?u', Date.now()) - await settle(runtime, 'pty-kitty0') - - expect(replies.map((reply) => reply.data)).toEqual(['\x1b[?0u']) - }) }) describe('ingestion-time ownership capture', () => { @@ -414,33 +247,11 @@ describe('ingestion-time ownership capture', () => { expect(replies.map((reply) => reply.data)).toEqual(['\x1b[?1;2c']) }) - - it('stays silent when the hidden mark lands after ingestion', async () => { - const { runtime, replies } = createResponderRuntime() - - runtime.onPtyData('pty-race2', DA1, Date.now()) - markHiddenRendererPty('pty-race2') - await settle(runtime, 'pty-race2') - - expect(replies).toEqual([]) - }) }) describe('stale writeChain links after dispose', () => { const DA1 = '\x1b[c' - it('never forwards a queued reply once the PTY state is disposed', async () => { - const { runtime, replies } = createResponderRuntime() - markHiddenRendererPty('pty-stale') - - // Queue a forward-flagged chain link, then dispose before it runs. - runtime.onPtyData('pty-stale', DA1, Date.now()) - runtime.onPtyExit('pty-stale', 0) - await new Promise((resolve) => setTimeout(resolve, 0)) - - expect(replies).toEqual([]) - }) - it('never injects a stale reply into a successor PTY reusing the session id', async () => { const { runtime, replies } = createResponderRuntime() markHiddenRendererPty('pty-reuse') @@ -471,60 +282,9 @@ describe('ConPTY DA1 override', () => { expect(replies.map((reply) => reply.data)).toEqual(['\x1b[?61;4c']) }) - - it('answers once at creation and after a repeated native-Windows mark', async () => { - const { runtime, replies } = createResponderRuntime() - markNativeWindowsConptyPty('pty-win-idem') - markHiddenRendererPty('pty-win-idem') - - runtime.onPtyData('pty-win-idem', '\x1b[c', Date.now()) - await settle(runtime, 'pty-win-idem') - expect(replies.map((reply) => reply.data)).toEqual(['\x1b[?61;4c']) - - markNativeWindowsConptyPty('pty-win-idem') - runtime.onPtyData('pty-win-idem', '\x1b[c', Date.now()) - await settle(runtime, 'pty-win-idem') - expect(replies.map((reply) => reply.data)).toEqual(['\x1b[?61;4c', '\x1b[?61;4c']) - }) - - it('lets non-primary device-attribute queries fall through to the core', async () => { - const { runtime, replies } = createResponderRuntime() - markNativeWindowsConptyPty('pty-win2') - markHiddenRendererPty('pty-win2') - - runtime.onPtyData('pty-win2', '\x1b[>c', Date.now()) - await settle(runtime, 'pty-win2') - - expect(replies.map((reply) => reply.data)).toEqual(['\x1b[>0;276;0c']) - }) - - it('keeps the override silent for delivered chunks', async () => { - const { runtime, replies } = createResponderRuntime() - markNativeWindowsConptyPty('pty-win3') - - runtime.onPtyData('pty-win3', '\x1b[c', Date.now()) - await settle(runtime, 'pty-win3') - - expect(replies).toEqual([]) - }) }) describe('HeadlessEmulator forwarding window', () => { - it('forwards replies only for writes flagged forwardQueryReplies', async () => { - const onQueryReply = vi.fn() - const emulator = new HeadlessEmulator({ cols: 80, rows: 24, onQueryReply }) - try { - await emulator.write('\x1b[c') - expect(onQueryReply).not.toHaveBeenCalled() - - await emulator.write('\x1b[c', { forwardQueryReplies: true }) - expect(onQueryReply).toHaveBeenCalledTimes(1) - expect(onQueryReply).toHaveBeenCalledWith('\x1b[?1;2c') - } finally { - emulator.dispose() - } - }) - it('scopes the async-fallback forwarding window to the flagged chunk parse', async () => { const onQueryReply = vi.fn() const emulator = new HeadlessEmulator({ cols: 80, rows: 24, onQueryReply }) @@ -594,48 +354,6 @@ describe('view-attribute bridge replies (after renderer push)', () => { expect(replies.map((reply) => reply.data)).toEqual(expectedReplies) }) - it('answers ?996n from palette luminance, not the pushed app mode (dark palette, light app mode)', async () => { - const { runtime, replies } = createResponderRuntime() - markHiddenRendererPty('pty-lum-dark') - // Separate-light-theme off can leave dark terminal colors in light app mode; match xterm luminance-based ?996n. - setTerminalViewAttributes(viewAttributes({ colorSchemeMode: 'light' })) - - runtime.onPtyData('pty-lum-dark', '\x1b[?996n', Date.now()) - await settle(runtime, 'pty-lum-dark') - - expect(replies.map((reply) => reply.data)).toEqual(['\x1b[?997;1n']) - }) - - it('answers ?996n light for a light palette regardless of the pushed app mode', async () => { - const { runtime, replies } = createResponderRuntime() - markHiddenRendererPty('pty-lum-light') - setTerminalViewAttributes( - viewAttributes({ - foreground: [0x33, 0x33, 0x33], - background: [0xfa, 0xfa, 0xfa], - colorSchemeMode: 'dark' - }) - ) - - runtime.onPtyData('pty-lum-light', '\x1b[?996n', Date.now()) - await settle(runtime, 'pty-lum-light') - - expect(replies.map((reply) => reply.data)).toEqual(['\x1b[?997;2n']) - }) - - it('answers ?996n from OSC-SET-mutated colors like a visible xterm', async () => { - // _reportColorScheme reads the CURRENT theme-service colors, which include - // OSC 10/11 SET mutations — the per-PTY overlays layer the same way. - const { runtime, replies } = createResponderRuntime() - markHiddenRendererPty('pty-lum-set') - setTerminalViewAttributes(viewAttributes()) - - runtime.onPtyData('pty-lum-set', '\x1b]11;#ffffff\x07\x1b]10;#101010\x07\x1b[?996n', Date.now()) - await settle(runtime, 'pty-lum-set') - - expect(replies.map((reply) => reply.data)).toEqual(['\x1b[?997;2n']) - }) - it('stays silent before the first push, then answers the same query after it', async () => { const { runtime, replies } = createResponderRuntime() markHiddenRendererPty('pty-first') @@ -650,21 +368,6 @@ describe('view-attribute bridge replies (after renderer push)', () => { await settle(runtime, 'pty-first') expect(replies.map((reply) => reply.data)).toEqual(['\x1b]11;rgb:1e1e/1e1e/2e2e\x1b\\']) }) - - it('retrofits cursor options onto already-live emulators when the push lands late', async () => { - const { runtime, replies } = createResponderRuntime() - markHiddenRendererPty('pty-late') - - // Emulator exists before any push: core default DECSCUSR is steady block. - runtime.onPtyData('pty-late', '\x1bP$q q\x1b\\', Date.now()) - await settle(runtime, 'pty-late') - expect(replies.map((reply) => reply.data)).toEqual(['\x1bP1$r2 q\x1b\\']) - - setTerminalViewAttributes(viewAttributes({ cursorStyle: 'underline', cursorBlink: false })) - runtime.onPtyData('pty-late', '\x1bP$q q\x1b\\', Date.now()) - await settle(runtime, 'pty-late') - expect(replies.map((reply) => reply.data)).toEqual(['\x1bP1$r2 q\x1b\\', '\x1bP1$r4 q\x1b\\']) - }) }) describe('per-PTY OSC color SET layering', () => { @@ -685,60 +388,6 @@ describe('per-PTY OSC color SET layering', () => { ]) }) - it('restores a single indexed color via OSC 104;', async () => { - const { runtime, replies } = createResponderRuntime() - markHiddenRendererPty('pty-104') - setTerminalViewAttributes(viewAttributes()) - - runtime.onPtyData('pty-104', '\x1b]4;1;#00ff00\x07\x1b]104;1\x07\x1b]4;1;?\x07', Date.now()) - await settle(runtime, 'pty-104') - - expect(replies.map((reply) => reply.data)).toEqual(['\x1b]4;1;rgb:cccc/0000/0000\x1b\\']) - }) - - it('restores the whole indexed table via bare OSC 104', async () => { - const { runtime, replies } = createResponderRuntime() - markHiddenRendererPty('pty-104all') - setTerminalViewAttributes(viewAttributes()) - - runtime.onPtyData( - 'pty-104all', - '\x1b]4;1;#00ff00;196;#0000ff\x07\x1b]104\x07\x1b]4;1;?;196;?\x07', - Date.now() - ) - await settle(runtime, 'pty-104all') - - expect(replies.map((reply) => reply.data)).toEqual([ - '\x1b]4;1;rgb:cccc/0000/0000\x1b\\', - '\x1b]4;196;rgb:c4c4/8888/4c4c\x1b\\' - ]) - }) - - it('layers OSC 10/11/12 SETs and restores them via 110/111/112', async () => { - const { runtime, replies } = createResponderRuntime() - markHiddenRendererPty('pty-special') - setTerminalViewAttributes(viewAttributes()) - - runtime.onPtyData( - 'pty-special', - '\x1b]10;#010203\x07\x1b]11;rgb:ff/ff/ff\x07\x1b]12;#0a0b0c\x07' + - '\x1b]10;?\x07\x1b]11;?\x07\x1b]12;?\x07' + - '\x1b]110\x07\x1b]111\x07\x1b]112\x07' + - '\x1b]10;?\x07\x1b]11;?\x07\x1b]12;?\x07', - Date.now() - ) - await settle(runtime, 'pty-special') - - expect(replies.map((reply) => reply.data)).toEqual([ - '\x1b]10;rgb:0101/0202/0303\x1b\\', - '\x1b]11;rgb:ffff/ffff/ffff\x1b\\', - '\x1b]12;rgb:0a0a/0b0b/0c0c\x1b\\', - '\x1b]10;rgb:d0d0/d0d0/d0d0\x1b\\', - '\x1b]11;rgb:1e1e/1e1e/2e2e\x1b\\', - '\x1b]12;rgb:ffff/9999/0000\x1b\\' - ]) - }) - it('tracks SET mutations parsed from a seed without replying, like renderer replay', async () => { // Restored OSC SETs update theme state like visible xterm, while replay remains silent. const { runtime, replies } = createResponderRuntime() diff --git a/src/main/runtime/terminal-restore-record-seed.test.ts b/src/main/runtime/terminal-restore-record-seed.test.ts index 6ebc4498f37..42d3e3a3119 100644 --- a/src/main/runtime/terminal-restore-record-seed.test.ts +++ b/src/main/runtime/terminal-restore-record-seed.test.ts @@ -1,17 +1,10 @@ -import { describe, expect, it, vi, afterEach } from 'vitest' +import { describe, expect, it, vi } from 'vitest' import { OrcaRuntimeService } from './orca-runtime' import { getDefaultWorkspaceSession } from '../../shared/constants' import type { WorkspaceSessionState } from '../../shared/workspace-session/workspace-session-state-types' -import { makePaneKey } from '../../shared/workspace-layout/stable-pane-id' -import { ORCA_LAYOUT_OWNER_IS_MAIN } from '../../shared/layout-owner-switch' -import { bindPaneFirst } from './orca-runtime-tests/bind-first-pane.test-fixture' -import type { - RuntimePtyTabCloseAuthority, - HeadlessSeedMetadata, - RuntimeTerminalBufferSnapshot -} from './terminal/records/runtime-terminal-state-records' -import type { RuntimeMobileSessionCreateTerminalResult } from '../../shared/runtime-session-contracts' -import type { PtyProviderBufferSnapshot } from '../providers/pty-provider-contract' +import '../../shared/workspace-layout/stable-pane-id' +import '../../shared/layout-owner-switch' +import './orca-runtime-tests/bind-first-pane.test-fixture' // Reattach restore arrives as RPC data, not onPtyData; seed preview/tail/title without inventing recency or side-effect facts. @@ -152,40 +145,6 @@ describe('seedTerminalRestoreTail', () => { expect(terminals[0]!.lastOutputAt).toBe(42) }) - it('keeps a live-tracked title over the payload title', async () => { - const runtime = makeRuntimeWithLeaf() - - runtime.onPtyData(PTY_ID, '\x1b]0;live-title\x07', 42) - runtime.seedTerminalRestoreTail(PTY_ID, { lastTitle: 'stale-persisted-title' }) - - const internals = runtime as unknown as RuntimeRecordInternals - expect(internals.ptysById.get(PTY_ID)!.lastOscTitle).toBe('live-title') - }) - - it('seeds title-only payloads (no restore text)', async () => { - const runtime = makeRuntimeWithLeaf() - - runtime.seedTerminalRestoreTail(PTY_ID, { lastTitle: 'title-only-restore' }) - - const { terminals } = await runtime.listTerminals(`id:${WORKTREE_ID}`) - expect(terminals[0]!.title).toBe('title-only-restore') - expect(terminals[0]!.preview).toBe('') - expect(terminals[0]!.lastOutputAt).toBeNull() - }) - - it('republishes session tabs when a seeded title lands', () => { - const runtime = makeRuntimeWithLeaf() - const touch = vi.spyOn( - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: OrcaRuntimeService defines this protected method; the spy only observes it. - runtime as unknown as { touchMobileSessionSnapshotsForPty: (ptyId: string) => void }, - 'touchMobileSessionSnapshotsForPty' - ) - - runtime.seedTerminalRestoreTail(PTY_ID, { lastTitle: 'restored-agent-title' }) - - expect(touch).toHaveBeenCalledWith(PTY_ID) - }) - it('caps the parsed suffix of an oversized payload at a line boundary', async () => { const runtime = makeRuntimeWithLeaf() @@ -221,420 +180,4 @@ describe('seedTerminalRestoreTail', () => { runtime.onPtyData(PTY_ID, 'Do you trust this workspace?\r\nPress t to trust\r\n', 2_000_000) expect(pty.waitBlockedAt).toBe(2_000_000) }) - - it('seeds the pty record for a daemon-scoped id with no synced leaf', async () => { - const runtime = makeRuntimeWithLeaf() - const orphanPtyId = `${WORKTREE_ID}@@session-orphan` - - runtime.seedTerminalRestoreTail(orphanPtyId, { text: 'orphan restored\r\n' }) - - const internals = runtime as unknown as RuntimeRecordInternals - const pty = internals.ptysById.get(orphanPtyId)! - expect(pty.preview).toBe('orphan restored') - expect(pty.lastOutputAt).toBeNull() - }) -}) - -describe('terminal query dispatch characterization', () => { - class QueryDispatchRuntime extends OrcaRuntimeService { - prepareMobileCreate() { - this.registerPty('pty-query', 'workspace') - const pty = this.ptysById.get('pty-query') - if (!pty) { - throw new Error('missing_test_pty') - } - this.terminalIdentity.setRecordedSurface( - pty, - 'tab', - makePaneKey('tab', '00000000-0000-4000-8000-000000000001'), - undefined - ) - if (ORCA_LAYOUT_OWNER_IS_MAIN) { - // Main's layout is the only answer to which pane holds the terminal; it needs a session. - Reflect.set(this, 'store', {}) - bindPaneFirst(this, { - worktreeId: 'workspace', - tabId: 'tab', - leafId: '00000000-0000-4000-8000-000000000001', - ptyId: 'pty-query' - }) - } - this.pendingMobileTerminalCreatesByKey.set('workspace::tab', { - activate: false, - paired: false, - selectIfNoActiveTab: false - }) - return pty - } - - ensureSurface() { - return this.ensurePtyBackedMobileSurfaceForRendererTab('workspace', 'tab') - } - - resolveAuthority(authority: RuntimePtyTabCloseAuthority) { - return this.resolvePtyTabCloseSurfaceAuthority(authority) - } - } - - afterEach(() => vi.restoreAllMocks()) - - describe('terminal query dispatch', () => { - it('preserves the two arguments in both protected mobile surface lookups', () => { - const runtime = new QueryDispatchRuntime(null) - const pty = runtime.prepareMobileCreate() - const find = vi.fn(function (this: typeof runtime) { - expect(this).toBe(runtime) - expect(arguments.length).toBe(2) - return null - }) - Object.defineProperty(runtime, 'findMobileTerminalSurface', { value: find }) - Object.defineProperty(runtime, 'findLiveRegisteredPtyForRendererTab', { value: () => pty }) - Object.defineProperty(runtime, 'publishPtyBackedMobileSessionTerminal', { value: () => {} }) - - try { - expect(runtime.ensureSurface()).toBeNull() - expect(find).toHaveBeenCalledTimes(2) - expect(pty.runtimeSessionOwned).toBe(true) - } finally { - runtime.onPtyExit('pty-query', 0) - } - }) - - it('captures the protected live PTY lookup before reading authority.handle', () => { - const runtime = new QueryDispatchRuntime(null) - const original = vi.fn(function (this: typeof runtime) { - expect(this).toBe(runtime) - expect(arguments.length).toBe(1) - return null - }) - const replacement = vi.fn(() => null) - Object.defineProperty(runtime, 'getLivePtyForHandle', { - configurable: true, - get: () => original - }) - const authority: RuntimePtyTabCloseAuthority = { - get handle() { - Object.defineProperty(runtime, 'getLivePtyForHandle', { value: replacement }) - return 'handle' - }, - ptyId: 'pty-query', - incarnationId: null, - worktreeId: 'workspace' - } - - expect(runtime.resolveAuthority(authority)).toBeNull() - expect(original).toHaveBeenCalledExactlyOnceWith('handle') - expect(replacement).not.toHaveBeenCalled() - }) - - it.each([ - ['seedHeadlessTerminal', 4], - ['resizeHeadlessTerminal', 3], - ['trackHeadlessTerminalData', 3] - ] as const)('captures recovery %s before its argument getters', (method, argc) => { - const runtime = new QueryDispatchRuntime(null) - const original = vi.fn(function (this: typeof runtime) { - expect(this).toBe(runtime) - expect(arguments.length).toBe(argc) - }) - const replacement = vi.fn() - Object.defineProperty(runtime, 'disposeHeadlessTerminal', { value: () => {} }) - Object.defineProperty(runtime, 'seedHeadlessTerminal', { - configurable: true, - value: () => {} - }) - Object.defineProperty(runtime, 'resizeHeadlessTerminal', { - configurable: true, - value: () => {} - }) - Object.defineProperty(runtime, 'trackHeadlessTerminalData', { - configurable: true, - value: () => {} - }) - Object.defineProperty(runtime, method, { get: () => original }) - const replace = () => Object.defineProperty(runtime, method, { value: replacement }) - let reads = 0 - Object.defineProperty(runtime, 'getTerminalSize', { - value: () => ({ - get cols() { - if (method === 'resizeHeadlessTerminal') { - replace() - } - return 100 - }, - rows: 40 - }) - }) - - runtime.replaceHeadlessTerminalFromRendererSnapshotForRecovery( - 'pty-query', - { - get data() { - if (++reads === 2 && method === 'seedHeadlessTerminal') { - replace() - } - return 'snapshot' - }, - cols: 80, - rows: 24 - }, - [ - { - get data() { - if (method === 'trackHeadlessTerminalData') { - replace() - } - return 'tail' - }, - seq: 7 - } - ] - ) - - expect(original).toHaveBeenCalledOnce() - expect(replacement).not.toHaveBeenCalled() - }) - - it.each(['resizeHeadlessTerminal', 'trackHeadlessTerminalData'] as const)( - 'preserves invoke-time Root lookup for recovery %s', - (method) => { - const runtime = new QueryDispatchRuntime(null) - const original = vi.fn() - const replacement = vi.fn(function (this: typeof runtime) { - expect(this).toBe(runtime) - expect(arguments.length).toBe(3) - }) - Object.defineProperty(runtime, method, { configurable: true, value: original }) - const argument = { - get value() { - Object.defineProperty(runtime, method, { value: replacement }) - return 'pty-query' - } - } - - Object.defineProperty(runtime, 'disposeHeadlessTerminal', { value: () => {} }) - Object.defineProperty(runtime, 'seedHeadlessTerminal', { value: () => {} }) - Object.defineProperty(runtime, 'getTerminalSize', { - value: () => (method === 'resizeHeadlessTerminal' ? { cols: 80, rows: 24 } : null) - }) - runtime.replaceHeadlessTerminalFromRendererSnapshotForRecovery( - argument.value, - { data: 'snapshot', cols: 80, rows: 24 }, - method === 'trackHeadlessTerminalData' ? [{ data: 'tail', seq: 7 }] : [] - ) - - expect(original).not.toHaveBeenCalled() - expect(replacement).toHaveBeenCalledOnce() - expect(replacement).toHaveBeenCalledWith( - 'pty-query', - method === 'resizeHeadlessTerminal' ? 80 : 'tail', - method === 'resizeHeadlessTerminal' ? 24 : 7 - ) - } - ) - }) -}) - -describe('terminal query reentry characterization', () => { - class QueryRuntime extends OrcaRuntimeService { - readonly calls: string[] = [] - surfaceReads = 0 - - override getRendererTerminalSerializerGeneration(ptyId: string): number { - this.calls.push(`generation:${ptyId}`) - return 27 - } - - override getTerminalSize(ptyId: string): { cols: number; rows: number } { - this.calls.push(`grid:${ptyId}`) - return { cols: 100, rows: 40 } - } - - protected override disposeHeadlessTerminal(ptyId: string): void { - this.calls.push(`dispose:${ptyId}`) - } - - override seedHeadlessTerminal( - ptyId: string, - data: string, - size?: { cols: number; rows: number }, - metadata: HeadlessSeedMetadata = {} - ): void { - this.calls.push(`seed:${ptyId}:${data}:${size?.cols}:${metadata.cwd}`) - } - - protected override resizeHeadlessTerminal(ptyId: string, cols: number, rows: number): void { - this.calls.push(`resize:${ptyId}:${cols}:${rows}`) - } - - protected override trackHeadlessTerminalData(ptyId: string, data: string, seq?: number): void { - this.calls.push(`data:${ptyId}:${data}:${seq}`) - } - - protected override findMobileTerminalSurface( - _worktreeId: string, - _parentTabId: string, - _options: { requireReady?: boolean } = {} - ): RuntimeMobileSessionCreateTerminalResult | null { - this.surfaceReads += 1 - return null - } - - ptyHandle(ptyId: string): string { - this.registerPty(ptyId, 'repo::/tmp/query-workspace') - const pty = this.ptysById.get(ptyId) - if (!pty) { - throw new Error('missing_test_pty') - } - return this.issuePtyHandle(pty) - } - - recoveryState(ptyId: string): { preferred: boolean; hydration: string | undefined } { - return { - preferred: this.providerSnapshotPreferredPtys.has(ptyId), - hydration: this.headlessHydrationState.get(ptyId) - } - } - - waitForSurface(signal: AbortSignal): Promise { - return this.waitForMobileTerminalSurface('repo::/tmp/query-workspace', 'tab-query', { - signal - }) - } - - get pendingGraphChecks(): number { - return this.graphSyncCallbacks.length - } - } - - describe('terminal query runtime re-entry', () => { - it('uses the public renderer generation override for a handle', () => { - const runtime = new QueryRuntime(null) - const handle = runtime.ptyHandle('pty-query') - expect(runtime.getRendererTerminalSerializerGenerationForHandle(handle)).toBe(27) - expect(runtime.calls).toEqual(['generation:pty-query']) - runtime.onPtyExit('pty-query', 0) - }) - - it('recovers through the current runtime overrides in the original output order', () => { - const runtime = new QueryRuntime(null) - runtime.replaceHeadlessTerminalFromRendererSnapshotForRecovery( - 'pty-recovery', - { data: 'snapshot', cols: 80, rows: 24, cwd: '/tmp/query' }, - [{ data: 'tail', seq: 7 }] - ) - expect(runtime.calls).toEqual([ - 'dispose:pty-recovery', - 'seed:pty-recovery:snapshot:80:/tmp/query', - 'grid:pty-recovery', - 'resize:pty-recovery:100:40', - 'data:pty-recovery:tail:7' - ]) - expect(runtime.recoveryState('pty-recovery')).toEqual({ preferred: true, hydration: 'done' }) - runtime.onPtyExit('pty-recovery', 0) - }) - - it('leaves output ownership untouched when the recovery snapshot is empty', () => { - const runtime = new QueryRuntime(null) - runtime.replaceHeadlessTerminalFromRendererSnapshotForRecovery('pty-empty', { - data: '', - cols: 80, - rows: 24 - }) - expect(runtime.calls).toEqual([]) - expect(runtime.recoveryState('pty-empty')).toEqual({ preferred: false, hydration: undefined }) - }) - - it('uses the protected surface override and removes its graph subscription on abort', async () => { - const runtime = new QueryRuntime(null) - const client = new AbortController() - const waiting = runtime.waitForSurface(client.signal) - expect(runtime.surfaceReads).toBe(2) - expect(runtime.pendingGraphChecks).toBe(1) - client.abort() - await expect(waiting).rejects.toThrow('client_disconnected') - expect(runtime.pendingGraphChecks).toBe(0) - }) - }) -}) - -describe('terminal view promise characterization', () => { - const snapshot: RuntimeTerminalBufferSnapshot = { data: 'screen', cols: 80, rows: 24 } - it('returns the current available-state promise with the runtime receiver and original arity', () => { - const runtime = new OrcaRuntimeService() - const expected = Promise.resolve(snapshot) - const receivers: OrcaRuntimeService[] = [] - const arities: number[] = [] - runtime['serializeTerminalBufferFromAvailableState'] = function ( - this: OrcaRuntimeService, - ...args - ) { - receivers.push(this) - arities.push(args.length) - expect(args).toEqual(['view-pty', {}]) - return expected - } - expect(runtime.serializeTerminalBuffer('view-pty')).toBe(expected) - expect(receivers).toEqual([runtime]) - expect(arities).toEqual([2]) - }) - it('reads the current fallback after provider completion and keeps provider-first options', async () => { - const runtime = new OrcaRuntimeService() - const provider = Promise.withResolvers() - const receivers: OrcaRuntimeService[] = [] - const opts = { scrollbackRows: 12 } - runtime['serializeProviderTerminalBuffer'] = function (this: OrcaRuntimeService, ...args) { - receivers.push(this) - expect(args).toEqual(['view-pty', opts, { timeoutMs: 8000, retireOnTimeout: true }]) - return provider.promise - } - const firstFallback = vi.fn(async () => null) - runtime['serializeTerminalBufferFromAvailableState'] = firstFallback - const pending = runtime.serializeAuthoritativeTerminalBuffer('view-pty', opts) - runtime['serializeTerminalBufferFromAvailableState'] = function ( - this: OrcaRuntimeService, - ...args - ) { - receivers.push(this) - expect(args).toEqual(['view-pty', opts]) - return Promise.resolve(snapshot) - } - provider.resolve(null) - await expect(pending).resolves.toBe(snapshot) - expect(firstFallback).not.toHaveBeenCalled() - expect(receivers).toEqual([runtime, runtime]) - }) - it('skips driver reads for empty input and rechecks the current driver before a write', async () => { - const runtime = new OrcaRuntimeService() - const initialDriver = vi.fn(() => ({ kind: 'desktop' as const })) - runtime.getDriver = initialDriver - await expect(runtime.writeTerminalPreviewInput('view-pty', '')).resolves.toBe(false) - expect(initialDriver).not.toHaveBeenCalled() - const receivers: OrcaRuntimeService[] = [] - const writeChunks = runtime['writeTerminalInputChunks'] - const currentDriver = vi.fn(function (this: OrcaRuntimeService, _ptyId: string) { - receivers.push(this) - return { kind: 'mobile' as const, clientId: 'phone' } - }) - runtime['writeTerminalInputChunks'] = vi.fn(function ( - this: OrcaRuntimeService, - ...args: Parameters - ) { - receivers.push(this) - runtime.getDriver = currentDriver - return writeChunks.apply(this, args) - }) - await expect(runtime.writeTerminalPreviewInput('view-pty', 'paste')).resolves.toBe(false) - expect(runtime['writeTerminalInputChunks']).toHaveBeenCalledExactlyOnceWith( - 'view-pty', - 'paste', - { - inputKind: 'driving', - inputSource: 'desktop' - } - ) - expect(initialDriver).not.toHaveBeenCalled() - expect(currentDriver).toHaveBeenCalledExactlyOnceWith('view-pty') - expect(receivers).toEqual([runtime, runtime]) - }) }) diff --git a/src/main/runtime/terminal-retirement-proof-emitted-frame.test.ts b/src/main/runtime/terminal-retirement-proof-emitted-frame.test.ts deleted file mode 100644 index 01380b3342c..00000000000 --- a/src/main/runtime/terminal-retirement-proof-emitted-frame.test.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { expect, it, vi } from 'vitest' -import type { - RuntimeMobileSessionTabsResult, - RuntimeMobileSessionTabsSnapshot -} from '../../shared/runtime-types' - -await import('./orca-runtime-tests/runtime-test-module-registration.spec') -const { OrcaRuntimeService } = await import('./orca-runtime') -await import('./orca-runtime-tests/orca-runtime-test-lifecycle.spec') -const { store, TEST_WORKTREE_ID } = - await import('./orca-runtime-tests/orca-runtime-test-fixtures.spec') -const { committingSpawn } = await import('./orca-runtime-tests/bind-first-pane.test-fixture') - -const retired = { - parentTabId: 'tab', - leafId: 'leaf', - ptyId: 'pty', - terminal: 'term_old', - incarnationId: 'inc' -} - -type RuntimeInternals = { - mobileSessionTabsByWorktree: Map - storeMobileSessionSnapshot: ( - worktreeId: string, - snapshot: RuntimeMobileSessionTabsSnapshot - ) => RuntimeMobileSessionTabsSnapshot -} - -function seedRuntimeWithStoredProof(): { - runtime: InstanceType - internals: RuntimeInternals -} { - const runtime = new OrcaRuntimeService(store) - runtime.setPtyController({ - // As the PTY host's spawn commit: the new pane is bound before the terminal registers. - spawn: vi.fn(committingSpawn(runtime, async () => ({ id: 'pty-runtime-fallback' }))), - write: () => true, - kill: () => true, - getForegroundProcess: async () => null - }) - runtime.syncWindowGraph(0, { - tabs: [], - leaves: [], - mobileSessionTabs: [ - { - worktree: TEST_WORKTREE_ID, - publicationEpoch: 'headless:active-generation', - snapshotVersion: 7, - activeGroupId: null, - activeTabId: null, - activeTabType: null, - tabs: [] - } - ] - }) - const internals = runtime as unknown as RuntimeInternals - const stored = internals.mobileSessionTabsByWorktree.get(TEST_WORKTREE_ID)! - internals.storeMobileSessionSnapshot(TEST_WORKTREE_ID, { - ...stored, - snapshotVersion: stored.snapshotVersion + 1, - retiredTerminalSurfaces: [retired] - }) - return { runtime, internals } -} - -// Subscribers dedupe epoch/version; emitting a pre-store frame at the stored version would hide proofs until another bump. -it('emits the stored retirement proofs on the frame a runtime-owned create publishes', async () => { - const { runtime, internals } = seedRuntimeWithStoredProof() - const events: RuntimeMobileSessionTabsResult[] = [] - const unsubscribe = runtime.onMobileSessionTabsChanged( - (frame) => events.push(frame), - 'paired-client' - ) - - try { - await runtime.createMobileSessionTerminal(`id:${TEST_WORKTREE_ID}`, { - activate: false, - select: false, - navigation: 'caller', - clientNavigationId: 'paired-client' - }) - - const storedAfter = internals.mobileSessionTabsByWorktree.get(TEST_WORKTREE_ID)! - const emitted = events.at(-1)! - expect(storedAfter.retiredTerminalSurfaces).toEqual([retired]) - expect(emitted.snapshotVersion).toBe(storedAfter.snapshotVersion) - expect(emitted.retiredTerminalSurfaces).toEqual([retired]) - } finally { - unsubscribe() - } -}) diff --git a/src/main/runtime/terminal-run-facts-input.test.ts b/src/main/runtime/terminal-run-facts-input.test.ts index 2b268d2eca0..473d9a7593e 100644 --- a/src/main/runtime/terminal-run-facts-input.test.ts +++ b/src/main/runtime/terminal-run-facts-input.test.ts @@ -133,33 +133,6 @@ describe('run facts: the controller write funnel', () => { } }) - it('records stream input from a client', async () => { - const run = await createFreshRun() - - await sendTerminalStreamInput(run.runtime, { - terminal: run.handle, - text: 'l', - client: undefined, - isMobile: false - }) - - expect(run.firstUserInputAt()).not.toBeNull() - }) - - it('records a dispatched agent prompt before its first write', async () => { - vi.useFakeTimers() - const run = await createFreshRun() - - const submission = run.runtime.sendTerminalAgentPrompt(run.handle, 'review this', { - inputKind: 'driving' - }) - await vi.runAllTimersAsync() - await submission.catch(() => undefined) - - expect(run.firstUserInputAt()).not.toBeNull() - expect(run.writes[0]?.inputRecorded).toBe(true) - }) - it('reads a run launched with a prompt as untyped until someone drives it', async () => { vi.useFakeTimers() const run = await createFreshRun() @@ -191,14 +164,6 @@ describe('run facts: the controller write funnel', () => { expect(run.runtime.terminalRunFacts.readLastDrivingInputAt(PTY_ID)).toBeNull() }) - it('records nothing for a client query reply', async () => { - const run = await createFreshRun() - - await run.runtime.sendTerminal(run.handle, { text: 'y' }, { inputKind: 'query-reply' }) - - expect(run.firstUserInputAt()).toBeNull() - }) - it.each([ ['a terminal reply', '\x1b[3;4R'], ['focus reports', '\x1b[I\x1b[O'], @@ -217,14 +182,6 @@ describe('run facts: the controller write funnel', () => { expect(run.firstUserInputAt()).toBeNull() }) - it('records a reply mixed with a keystroke', async () => { - const run = await createFreshRun() - - await run.runtime.sendTerminal(run.handle, { text: '\x1b[3;4Rx' }, { inputKind: 'driving' }) - - expect(run.firstUserInputAt()).not.toBeNull() - }) - it('records dashboard preview typing before the write that could end the process', async () => { const run = await createFreshRun() @@ -232,16 +189,6 @@ describe('run facts: the controller write funnel', () => { expect(run.writes.map((write) => write.inputRecorded)).toEqual([true]) }) - - it('records nothing for dashboard preview bytes that are only a reply or focus reports', async () => { - const run = await createFreshRun() - - await run.runtime.writeTerminalPreviewInput(PTY_ID, '\x1b[3;4R') - await run.runtime.writeTerminalPreviewInput(PTY_ID, '\x1b[O\x1b[I') - - expect(run.writes).toHaveLength(2) - expect(run.firstUserInputAt()).toBeNull() - }) }) describe('run facts: a created worktree’s startup writes', () => { diff --git a/src/main/runtime/terminal-send-stale-leaf-liveness.test.ts b/src/main/runtime/terminal-send-stale-leaf-liveness.test.ts index 91e7f312731..aff406f1e49 100644 --- a/src/main/runtime/terminal-send-stale-leaf-liveness.test.ts +++ b/src/main/runtime/terminal-send-stale-leaf-liveness.test.ts @@ -100,17 +100,6 @@ describe('sendTerminal absence gate for leaf-branch writes', () => { expect(write).not.toHaveBeenCalled() }) - it('gates agent prompt sends behind the same proven-absence check', async () => { - const probe = vi.fn(async () => false) - const { runtime, handle, write } = await makeRuntimeWithLeafHandle({ probePtyLiveness: probe }) - - await expect( - runtime.sendTerminalAgentPrompt(handle, 'do the thing', { inputKind: 'driving' }) - ).rejects.toThrow('terminal_not_writable') - - expect(write).not.toHaveBeenCalled() - }) - it('proceeds when the probe answers unknown (null) — unknown is not absence', async () => { const { runtime, handle, write } = await makeRuntimeWithLeafHandle({ probePtyLiveness: async () => null @@ -126,79 +115,6 @@ describe('sendTerminal absence gate for leaf-branch writes', () => { expect(write).toHaveBeenCalledWith(STALE_PTY_ID, 'ping', 'driving') }) - it('treats a throwing probe as unknown and proceeds', async () => { - const { runtime, handle, write } = await makeRuntimeWithLeafHandle({ - probePtyLiveness: async () => { - throw new Error('probe transport down') - } - }) - - await expect( - runtime.sendTerminal(handle, { text: 'ping' }, { inputKind: 'driving' }) - ).resolves.toMatchObject({ - accepted: true - }) - - expect(write).toHaveBeenCalledWith(STALE_PTY_ID, 'ping', 'driving') - }) - - it('proceeds when the probe answers live (restored session before its pane remounts)', async () => { - const { runtime, handle, write } = await makeRuntimeWithLeafHandle({ - probePtyLiveness: async () => true - }) - - await expect( - runtime.sendTerminal(handle, { text: 'ping' }, { inputKind: 'driving' }) - ).resolves.toMatchObject({ - accepted: true - }) - - expect(write).toHaveBeenCalledWith(STALE_PTY_ID, 'ping', 'driving') - }) - - it('proceeds unchanged when the controller exposes no probe', async () => { - const { runtime, handle, write } = await makeRuntimeWithLeafHandle({}) - - await expect( - runtime.sendTerminal(handle, { text: 'ping' }, { inputKind: 'driving' }) - ).resolves.toMatchObject({ - accepted: true - }) - - expect(write).toHaveBeenCalledWith(STALE_PTY_ID, 'ping', 'driving') - }) - - it('never probes when the provider synchronously knows the id (live pty)', async () => { - const probe = vi.fn(async () => false) - const { runtime, handle, write } = await makeRuntimeWithLeafHandle({ - probePtyLiveness: probe, - hasPty: (ptyId) => ptyId === STALE_PTY_ID - }) - - await expect( - runtime.sendTerminal(handle, { text: 'ping' }, { inputKind: 'driving' }) - ).resolves.toMatchObject({ - accepted: true - }) - - expect(probe).not.toHaveBeenCalled() - expect(write).toHaveBeenCalledWith(STALE_PTY_ID, 'ping', 'driving') - }) - - it('reuses a proven-absent verdict across repeated sends instead of re-probing', async () => { - const probe = vi.fn(async () => false) - const { runtime, handle } = await makeRuntimeWithLeafHandle({ probePtyLiveness: probe }) - - await expect( - runtime.sendTerminal(handle, { text: 'a' }, { inputKind: 'driving' }) - ).rejects.toThrow('terminal_not_writable') - await expect( - runtime.sendTerminal(handle, { text: 'b' }, { inputKind: 'driving' }) - ).rejects.toThrow('terminal_not_writable') - - expect(probe).toHaveBeenCalledTimes(1) - }) - it('drops the cached absent verdict once the provider re-learns the id', async () => { const probe = vi.fn(async () => false) const livePtyIds = new Set() @@ -615,37 +531,6 @@ describe('push-on-idle orchestration delivery absence gate', () => { expect(payloads.some((data) => data.includes('late completion'))).toBe(false) }) - it('keeps messages queued instead of marking a proven-absent pty delivered', async () => { - const { runtime, handle, write, stub } = await makeIdleLeafWithoutPtyRecord({ - probePtyLiveness: async () => false - }) - stub.insert('lost forever?') - - runtime.deliverPendingMessagesForHandle(handle) - await new Promise((resolve) => setTimeout(resolve, 0)) - - expect(write).not.toHaveBeenCalled() - expect(stub.stageMailboxPointerEnter).not.toHaveBeenCalled() - expect(stub.rows[0].delivered_at).toBeNull() - }) - - it('still delivers on unknown liveness after the probe resolves', async () => { - const { runtime, handle, write, stub } = await makeIdleLeafWithoutPtyRecord({ - probePtyLiveness: async () => null - }) - stub.insert('hello') - - runtime.deliverPendingMessagesForHandle(handle) - await new Promise((resolve) => setTimeout(resolve, 0)) - // Why twice: the probe continuation yields a turn before delivering. - await new Promise((resolve) => setTimeout(resolve, 0)) - - expect(write).toHaveBeenCalledWith( - STALE_PTY_ID, - expect.stringContaining('You have 1 orchestration message') - ) - }) - // Single-flight must span pointer through Enter; park new triggers until the sequence watermark advances. it('delivers once across concurrent probe triggers and an in-window re-trigger, then flushes parked rows', async () => { vi.useFakeTimers() @@ -703,45 +588,6 @@ describe('push-on-idle orchestration delivery absence gate', () => { } }) - it('sync-path double-trigger inside the Enter window delivers the first batch once and parks the rest', async () => { - vi.useFakeTimers() - try { - const probe = vi.fn(async () => null) - const { runtime, handle, write, stub } = await makeIdleLeafWithoutPtyRecord({ - probePtyLiveness: probe, - // Provider knows the id: delivery takes the pure synchronous path. - hasPty: (ptyId) => ptyId === STALE_PTY_ID - }) - stub.insert('first') - - runtime.deliverPendingMessagesForHandle(handle) - stub.insert('second') - runtime.deliverPendingMessagesForHandle(handle) - - const pointerWrites = () => - write.mock.calls.filter( - ([, data]) => typeof data === 'string' && data.includes('orchestration check') - ) - expect(pointerWrites()).toHaveLength(1) - expect(pointerWrites()[0]?.[1]).toContain('You have 1 orchestration message') - expect(probe).not.toHaveBeenCalled() - - // Settle flushes the parked trigger; both still-pending rows are counted, - // while the newer sequence authorizes exactly one fresh pointer. - await vi.advanceTimersByTimeAsync(500) - expect(pointerWrites()).toHaveLength(2) - expect(pointerWrites()[1]?.[1]).toContain('You have 1 orchestration message') - - await vi.advanceTimersByTimeAsync(500) - expect(stub.stageMailboxPointerEnter).toHaveBeenCalledTimes(2) - expect(stub.rows.map((row) => row.delivered_at)).toEqual( - stub.rows.map(() => expect.any(String)) - ) - } finally { - vi.useRealTimers() - } - }) - // Cold restore reuses the id; an Enter authorized by the dead incarnation must not reach its replacement. it('retires an armed Enter when the pty exits and respawns under the same id inside the window', async () => { vi.useFakeTimers() @@ -786,33 +632,6 @@ describe('push-on-idle orchestration delivery absence gate', () => { } }) - it('cleans all delivery state on exit without id reuse — no leak, no stray settle effects', async () => { - vi.useFakeTimers() - try { - const { runtime, handle, write, stub } = await makeIdleLeafWithoutPtyRecord({ - probePtyLiveness: async () => null, - hasPty: (ptyId) => ptyId === STALE_PTY_ID - }) - stub.insert('first') - runtime.deliverPendingMessagesForHandle(handle) - stub.insert('second') - runtime.deliverPendingMessagesForHandle(handle) - await Promise.resolve() - - runtime.onPtyExit(STALE_PTY_ID, 0) - - await vi.advanceTimersByTimeAsync(500) - expect(write.mock.calls.filter(([, data]) => data === '\r')).toHaveLength(0) - expect(stub.stageMailboxPointerEnter).toHaveBeenCalledOnce() - expect(stub.releaseMailboxPointerEnter).toHaveBeenCalledOnce() - // No stray settle flushed the parked trigger into the dead pty. - expect(write).toHaveBeenCalledTimes(1) - expect(stub.rows.every((row) => row.delivered_at === null)).toBe(true) - } finally { - vi.useRealTimers() - } - }) - // Graph resync replaces leaf objects; read current writable state rather than a stale closure before Enter. it('does not fire a stale Enter through an orphaned leaf snapshot after resync and exit', async () => { vi.useFakeTimers() diff --git a/src/main/runtime/terminal-subscriber-driven-daemon-attach.test.ts b/src/main/runtime/terminal-subscriber-driven-daemon-attach.test.ts index ce3375c452e..9f9a270a27a 100644 --- a/src/main/runtime/terminal-subscriber-driven-daemon-attach.test.ts +++ b/src/main/runtime/terminal-subscriber-driven-daemon-attach.test.ts @@ -364,62 +364,6 @@ describe('subscriber-driven daemon attach (never-activated tab)', () => { expect(model.attachCalls).toEqual([PTY_ID]) }) - it('never attaches SSH-scoped sessions or sessions absent from the daemon inventory', async () => { - const { runtime, model } = setupNeverAttachedDaemonSession({ snapshotCapable: false }) - const sshPtyId = 'ssh:conn-9@@relay-pty-4' - const sshRecord = internals(runtime).recordPtyWorktree(sshPtyId, WORKTREE_ID, { - connected: true - }) - const sshHandle = internals(runtime).issuePtyHandle(sshRecord) - const absentPtyId = `${WORKTREE_ID}@@99999999` - const absentRecord = internals(runtime).recordPtyWorktree(absentPtyId, WORKTREE_ID, { - connected: true - }) - const absentHandle = internals(runtime).issuePtyHandle(absentRecord) - - const harness = startMultiplex(runtime) - await vi.waitFor(() => expect(harness.handlers.has(0)).toBe(true)) - - sendSubscribe(harness, 1, sshHandle, 'client-ssh') - await waitForSubscribed(harness, 1) - expect(model.attachCalls).toEqual([]) - - // Absent session: subscribing must not create it or surface a new error. - sendSubscribe(harness, 2, absentHandle, 'client-absent') - await waitForSubscribed(harness, 2) - await Promise.resolve() - expect(model.sessions.has(absentPtyId)).toBe(false) - expect(model.emitData(absentPtyId, 'ghost')).toBe(false) - expect(harness.messages.some((message) => message.result?.type === 'error')).toBe(false) - - // Unrelated live sessions are untouched by another pty's subscribers. - expect(model.sessions.get(PTY_ID)?.attached).toBe(false) - expect(model.attachCalls).not.toContain(PTY_ID) - expect(model.resizeCalls).toEqual([]) - }) - - it('retries a refused attach for a later subscriber once the daemon learns the session', async () => { - const { runtime, model, handle } = setupNeverAttachedDaemonSession({ snapshotCapable: false }) - // Degraded-daemon shape: main knows the record, the daemon does not own the - // id yet, and the controller answers false rather than a no-op success. - model.sessions.delete(PTY_ID) - - const harness = startMultiplex(runtime) - await vi.waitFor(() => expect(harness.handlers.has(0)).toBe(true)) - sendSubscribe(harness, 1, handle, 'client-a') - await waitForSubscribed(harness, 1) - await vi.waitFor(() => expect(model.attachCalls).toEqual([PTY_ID])) - // A refused attach must not pin sticky success while the stream is blank. - expect(model.emitData(PTY_ID, 'ghost')).toBe(false) - - model.sessions.set(PTY_ID, { cols: 100, rows: 30, attached: false, screen: '' }) - sendSubscribe(harness, 2, handle, 'client-b') - await waitForSubscribed(harness, 2) - await vi.waitFor(() => expect(model.attachCalls).toEqual([PTY_ID, PTY_ID])) - expect(model.emitData(PTY_ID, 'late daemon hello\r\n')).toBe(true) - await vi.waitFor(() => expect(outputText(harness)).toContain('late daemon hello')) - }) - it('retries an existing subscriber when provider inventory becomes ready', async () => { const { runtime, model, handle } = setupNeverAttachedDaemonSession({ snapshotCapable: false @@ -446,17 +390,6 @@ describe('subscriber-driven daemon attach (never-activated tab)', () => { await vi.waitFor(() => expect(outputText(harness)).toContain('after inventory')) }) - it('attaches a surviving session at inventory adoption with no view subscribed', async () => { - const { runtime, model, handle } = setupNeverAttachedDaemonSession({ snapshotCapable: false }) - - await internals(runtime).refreshPtyWorktreeRecordsWithControllerInventory([], null) - - await vi.waitFor(() => expect(model.attachCalls).toEqual([PTY_ID])) - expect(model.emitData(PTY_ID, 'worker still printing\r\n')).toBe(true) - const read = await runtime.readTerminal(handle) - expect(read.tail.join('\n')).toContain('worker still printing') - }) - it('streams a survivor a terminal.create retry adopts after a restart', async () => { const runtime = new OrcaRuntimeService() const model = createDaemonProviderModel({ snapshotCapable: false }) @@ -519,25 +452,6 @@ describe('subscriber-driven daemon attach (never-activated tab)', () => { expect(read.tail).toEqual([]) }) - it('terminal read falls back to the provider tail for a never-attached live pty', async () => { - const { runtime, handle } = setupNeverAttachedDaemonSession({ - snapshotCapable: true, - screen: 'retained daemon line\r\n' - }) - - const read = await runtime.readTerminal(handle) - - expect(read.tail.join('\n')).toContain('retained daemon line') - }) - - it('terminal read stays empty (not an error) when provider state is unprovable', async () => { - const { runtime, handle } = setupNeverAttachedDaemonSession({ snapshotCapable: false }) - - const read = await runtime.readTerminal(handle) - - expect(read.tail).toEqual([]) - }) - it('terminal read keeps preferring the ingested model over provider state once attached', async () => { const { runtime, model, handle } = setupNeverAttachedDaemonSession({ snapshotCapable: true, @@ -577,19 +491,6 @@ describe('re-attach after a daemon reconnect detached a spawned session', () => internals(runtime).notePtyEndpointLoss?.(PTY_ID) } - it('takes back the stream of a session no view shows', async () => { - const { runtime, model, handle } = setupSpawnedAttachedSession() - - loseEndpoint(runtime, model) - - await vi.waitFor(() => expect(model.attachCalls).toEqual([PTY_ID])) - expect(model.emitData(PTY_ID, 'background agent output\r\n')).toBe(true) - await expect( - runtime.sendTerminal(handle, { text: 'typed' }, { inputKind: 'driving' }) - ).resolves.toMatchObject({ accepted: true }) - expect(model.writes).toEqual(['typed']) - }) - it('holds input sent while the re-attach is in flight instead of refusing it', async () => { const { runtime, model, handle } = setupSpawnedAttachedSession() const releaseAttach = model.deferNextAttach(true) @@ -646,41 +547,6 @@ describe('re-attach after a daemon reconnect detached a spawned session', () => expect(model.writes).toEqual([]) }) - it('leaves a failed re-attach to the next view or inventory pass, not to each keystroke', async () => { - const { runtime, model, handle } = setupSpawnedAttachedSession() - const releaseAttach = model.deferNextAttach(false) - loseEndpoint(runtime, model) - releaseAttach() - await vi.waitFor(() => expect(model.attachCalls).toEqual([PTY_ID])) - - await runtime.sendTerminal(handle, { text: 'a' }, { inputKind: 'driving' }).catch(() => {}) - await runtime.sendTerminal(handle, { text: 'b' }, { inputKind: 'driving' }).catch(() => {}) - expect(model.attachCalls).toEqual([PTY_ID]) - - const harness = startMultiplex(runtime) - await vi.waitFor(() => expect(harness.handlers.has(0)).toBe(true)) - sendSubscribe(harness, 1, handle) - await waitForSubscribed(harness, 1) - await vi.waitFor(() => expect(model.attachCalls).toEqual([PTY_ID, PTY_ID])) - expect(model.emitData(PTY_ID, 'live again\r\n')).toBe(true) - }) - - it('redoes an earlier subscriber-driven attach that the loss undid', async () => { - // Never-attached session: the first remote subscribe attaches it (the restored-tab path). - const { runtime, model, handle } = setupNeverAttachedDaemonSession({ snapshotCapable: false }) - const harness = startMultiplex(runtime) - await vi.waitFor(() => expect(harness.handlers.has(0)).toBe(true)) - sendSubscribe(harness, 1, handle) - await waitForSubscribed(harness, 1) - await vi.waitFor(() => expect(model.attachCalls).toEqual([PTY_ID])) - - loseEndpoint(runtime, model) - - await vi.waitFor(() => expect(model.attachCalls).toEqual([PTY_ID, PTY_ID])) - expect(model.emitData(PTY_ID, 'second life\r\n')).toBe(true) - await vi.waitFor(() => expect(outputText(harness)).toContain('second life')) - }) - it('attaches again when another loss lands while the re-attach is in flight', async () => { const { runtime, model } = setupSpawnedAttachedSession() const releaseFirst = model.deferNextAttach(true) diff --git a/src/main/runtime/terminal-update-admission-census.test.ts b/src/main/runtime/terminal-update-admission-census.test.ts index 0bbb872d0b4..5c663c94dee 100644 --- a/src/main/runtime/terminal-update-admission-census.test.ts +++ b/src/main/runtime/terminal-update-admission-census.test.ts @@ -47,23 +47,6 @@ afterEach(() => { }) describe("an update's last terminal census", () => { - it('refuses every new terminal until the stop, with a retryable message', async () => { - collect.mockResolvedValue(idle) - const runtime = new OrcaRuntimeService(store) - await expect(census(runtime, updateRecount)).resolves.toMatchObject({ - liveSessions: 0, - admissionClosedUntil: expect.any(Number) - }) - - await expect(runtime.acquireWorktreeTerminalSpawn(target)).rejects.toThrow( - TERMINAL_UPDATE_ADMISSION_CLOSED_MESSAGE - ) - // A spawn outside any worktree dies with the stop all the same. - await expect(runtime.acquireWorktreeTerminalSpawn(undefined)).rejects.toThrow( - TERMINAL_UPDATE_ADMISSION_CLOSED_MESSAGE - ) - }) - it('counts only after a spawn admitted before it has finished starting', async () => { collect.mockResolvedValue({ ...idle, liveSessions: 1 }) const runtime = new OrcaRuntimeService(store) @@ -96,19 +79,4 @@ describe("an update's last terminal census", () => { runtime.acquireWorktreeTerminalSpawn(target, { reattachSessionId: 'pty-live' }) ).rejects.toThrow(TERMINAL_UPDATE_ADMISSION_CLOSED_MESSAGE) }) - - it('reopens admission when a deferred update asks with 0, and a plain census never closes it', async () => { - collect.mockResolvedValue(idle) - const runtime = new OrcaRuntimeService(store) - await expect(census(runtime, { releaseFinishedAutomationTerminals: true })).resolves.toEqual( - idle - ) - ;(await runtime.acquireWorktreeTerminalSpawn(target))() - - await census(runtime, updateRecount) - await expect(census(runtime, { closeAdmissionForMs: 0 })).resolves.toMatchObject({ - admissionClosedUntil: 0 - }) - ;(await runtime.acquireWorktreeTerminalSpawn(target))() - }) }) diff --git a/src/main/runtime/terminal/adoption/runtime-terminal-orphan-adoption-duplicate-pty.test.ts b/src/main/runtime/terminal/adoption/runtime-terminal-orphan-adoption-duplicate-pty.test.ts deleted file mode 100644 index 81d2369f749..00000000000 --- a/src/main/runtime/terminal/adoption/runtime-terminal-orphan-adoption-duplicate-pty.test.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { describe, expect, it, vi } from 'vitest' -import { LOCAL_EXECUTION_HOST_ID } from '../../../../shared/execution-host' -import type { RuntimeMobileSessionTabsResult } from '../../../../shared/runtime-session-contracts' -import type { RuntimeTerminalOrphanAdoptionRequest } from '../../../../shared/runtime-terminal-contracts' -import type { WorkspaceSessionState } from '../../../../shared/workspace-session/workspace-session-state-types' -import { - GIT_KEY, - addWorkspace, - emptySession, - leaf -} from '../../../../shared/workspace-layout/workspace-layout-session.test-fixture' -import { adoptRuntimeTerminalOrphansFromInventory } from './runtime-terminal-orphan-adoption' -import type { RuntimePtyWorktreeRecord } from '../records/runtime-terminal-state-records' -import { paneOfPty } from '../surface/pty-pane-query' - -const TAB = 'tab-1' -const KEPT = leaf(1) -const DUPLICATE = leaf(2) - -function duplicateSession(): WorkspaceSessionState { - const session = addWorkspace(emptySession(), GIT_KEY, [ - { - id: 'group-1', - tabs: [ - { - id: TAB, - leaves: [ - [KEPT, 'pty-1'], - [DUPLICATE, 'pty-1'] - ] - } - ] - } - ]) - return { ...session, terminalPtyIncarnationsByPaneKey: { [`${TAB}:${KEPT}`]: 'inc-1' } } -} - -function adoptKeptPane(session: WorkspaceSessionState) { - const request: RuntimeTerminalOrphanAdoptionRequest = { - worktree: GIT_KEY, - expectedTopologyRevision: 0, - claims: [ - { terminal: 'term-1', ptyId: 'pty-1', incarnationId: 'inc-1', tabId: TAB, leafId: KEPT } - ] - } - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: adoption reads only these PTY fields before the persisted-binding check. - const pty = { - ptyId: 'pty-1', - incarnationId: 'inc-1', - worktreeId: GIT_KEY, - connectionId: null, - connected: true, - tabId: null, - paneKey: null - } as RuntimePtyWorktreeRecord - const replayPersistedSurface = vi.fn() - const adopted = adoptRuntimeTerminalOrphansFromInventory({ - hostId: LOCAL_EXECUTION_HOST_ID, - request, - workspace: { - id: GIT_KEY, - path: '/Users/dev/orca', - connectionId: null, - repo: null, - folderWorkspace: null - }, - inventory: { - livePtyIds: new Set(['pty-1']), - allLivePtyIds: new Set(['pty-1']), - terminalIdentityByPtyId: new Map([ - ['pty-1', { handle: 'term-1', incarnationId: 'inc-1', wslDistro: null }] - ]), - queriedHostIds: new Set([LOCAL_EXECUTION_HOST_ID]) - }, - repoId: 'repo-1', - worktreeWslDistro: null, - ports: { - readSession: () => ({ session, sessionWorktreeId: GIT_KEY, currentRevision: 0 }), - getPty: () => pty, - paneOf: (record) => paneOfPty(record, () => null), - getLeaves: () => [], - getLeaf: () => undefined, - getDisplayTitle: () => null, - replayPersistedSurface, - recordAdoptedSurface: vi.fn(), - getMobileSnapshots: () => [], - getSession: () => session, - setSession: vi.fn(), - flushSession: async () => {}, - hydrateSession: vi.fn(), - notifySessionChanged: vi.fn(), - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the replay path returns this value untouched. - getSnapshot: () => ({}) as RuntimeMobileSessionTabsResult - } - }) - return { adopted, replayPersistedSurface } -} - -describe('orphan adoption of a terminal main repaired at load', () => { - it('refuses the stored duplicate as a competing owner (the reader that skips the Loader)', async () => { - await expect(adoptKeptPane(duplicateSession()).adopted).rejects.toThrow( - 'terminal_orphan_competing_owner' - ) - }) -}) diff --git a/src/main/runtime/terminal/adoption/runtime-terminal-orphan-session-adoption.test.ts b/src/main/runtime/terminal/adoption/runtime-terminal-orphan-session-adoption.test.ts deleted file mode 100644 index 1e0aabaec62..00000000000 --- a/src/main/runtime/terminal/adoption/runtime-terminal-orphan-session-adoption.test.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { expect, it } from 'vitest' -import { LOCAL_EXECUTION_HOST_ID } from '../../../../shared/execution-host' -import { makePaneKey } from '../../../../shared/workspace-layout/stable-pane-id' -import type { RuntimeTerminalOrphanAdoptionRequest } from '../../../../shared/runtime-terminal-contracts' -import { collectLayoutLeafIdsInOrder } from '../../../../shared/workspace-layout/terminal-pane-tree' -import { - GIT_KEY, - addWorkspace, - emptySession, - leaf -} from '../../../../shared/workspace-layout/workspace-layout-session.test-fixture' -import { buildRuntimeTerminalOrphanSession } from './runtime-terminal-orphan-session-adoption' -import type { RuntimePtyWorktreeRecord } from '../records/runtime-terminal-state-records' - -const TAB = 'tab-1' -const HEALTHY = leaf(1) -const ORPHAN = leaf(2) - -function adopt(existingLeaves: [string, string?][]) { - const session = addWorkspace(emptySession(), GIT_KEY, [ - { id: 'group-1', tabs: [{ id: TAB, leaves: existingLeaves }] } - ]) - const claim = { - terminal: 'terminal-orphan', - ptyId: 'pty-orphan', - incarnationId: 'incarnation-orphan', - tabId: TAB, - leafId: ORPHAN - } - const topology: NonNullable = { - tabs: [ - { - tabId: TAB, - root: { type: 'leaf', leafId: ORPHAN }, - activeLeafId: ORPHAN, - expandedLeafId: null - } - ], - groups: [{ id: 'group-1', activeTabId: TAB, tabOrder: [TAB] }] - } - const request: RuntimeTerminalOrphanAdoptionRequest = { - worktree: GIT_KEY, - expectedTopologyRevision: 0, - claims: [claim], - topology - } - return buildRuntimeTerminalOrphanSession({ - hostId: LOCAL_EXECUTION_HOST_ID, - session, - sessionWorktreeId: GIT_KEY, - worktreeId: GIT_KEY, - request, - validated: [ - { - claim, - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the tab already exists, so the builder never reads the PTY record. - pty: {} as RuntimePtyWorktreeRecord, - paneKey: makePaneKey(TAB, ORPHAN) - } - ], - topologyTabsById: new Map(topology.tabs.map((tab) => [tab.tabId, tab])), - topologyGroups: topology.groups, - getDisplayTitle: () => null - }) -} - -it('keeps the healthy sibling pane and its PTY when one split pane is recovered', () => { - const layout = adopt([[HEALTHY, 'pty-healthy']]).terminalLayoutsByTabId[TAB] - - expect(collectLayoutLeafIdsInOrder(layout?.root)).toEqual([HEALTHY, ORPHAN]) - expect(layout?.ptyIdsByLeafId).toEqual({ [HEALTHY]: 'pty-healthy', [ORPHAN]: 'pty-orphan' }) - expect(layout?.activeLeafId).toBe(ORPHAN) -}) diff --git a/src/main/runtime/terminal/adoption/runtime-terminal-orphan-topology-validation.test.ts b/src/main/runtime/terminal/adoption/runtime-terminal-orphan-topology-validation.test.ts deleted file mode 100644 index 0cad94ddc4f..00000000000 --- a/src/main/runtime/terminal/adoption/runtime-terminal-orphan-topology-validation.test.ts +++ /dev/null @@ -1,136 +0,0 @@ -import { expect, it } from 'vitest' -import type { RuntimeTerminalOrphanAdoptionRequest } from '../../../../shared/runtime-terminal-contracts' -import { validateRuntimeTerminalOrphanTopology } from './runtime-terminal-orphan-topology-validation' - -function fixture(count: number): RuntimeTerminalOrphanAdoptionRequest { - const ids = Array.from({ length: count }, (_, index) => `tab-${index}`) - return { - worktree: 'folder-workspace', - expectedTopologyRevision: 1, - claims: ids.map((tabId) => ({ - tabId, - leafId: tabId, - terminal: tabId, - ptyId: tabId, - incarnationId: tabId - })) as RuntimeTerminalOrphanAdoptionRequest['claims'], - topology: { - tabs: ids.map((tabId) => ({ - tabId, - root: { type: 'leaf', leafId: tabId }, - activeLeafId: tabId, - expandedLeafId: null - })), - groups: [{ id: 'g', activeTabId: ids[0], tabOrder: ids, recentTabIds: ids.toReversed() }] - } - } -} - -it('validates large restored MRU lists with linear tab-order reads', () => { - const request = fixture(1000) - let reads = 0 - const group = request.topology!.groups[0] - group.tabOrder = new Proxy(group.tabOrder, { - get(target, key, receiver) { - if (typeof key === 'string' && /^\d+$/.test(key)) { - reads += 1 - } - // oxlint-disable-next-line anti-slop/no-reflect-get -- Proxy get trap default forward. - return Reflect.get(target, key, receiver) - } - }) - expect( - validateRuntimeTerminalOrphanTopology( - request, - request.claims.map((claim) => ({ claim })) - ).topologyTabsById.size - ).toBe(1000) - expect(reads).toBeLessThanOrEqual(3000) -}) - -it.each(['duplicate', 'foreign-recent', 'foreign-active'])( - 'rejects %s group membership', - (kind) => { - const request = fixture(2) - const group = request.topology!.groups[0] - if (kind === 'duplicate') { - group.tabOrder.push(group.tabOrder[0]) - } - if (kind === 'foreign-recent') { - group.recentTabIds = ['foreign'] - } - if (kind === 'foreign-active') { - group.activeTabId = 'foreign' - } - expect(() => - validateRuntimeTerminalOrphanTopology( - request, - request.claims.map((claim) => ({ claim })) - ) - ).toThrow('terminal_orphan_topology_invalid') - } -) - -function validate(request: RuntimeTerminalOrphanAdoptionRequest) { - return validateRuntimeTerminalOrphanTopology( - request, - request.claims.map((claim) => ({ claim })) - ) -} - -type Groups = NonNullable['groups'] - -function withGroups(count: number, groups: Groups): RuntimeTerminalOrphanAdoptionRequest { - const request = fixture(count) - request.topology!.groups = groups - return request -} - -// Tab uniqueness is global across groups; per-group membership alone permits cross-wired duplicate adoption. -it('rejects a tab claimed by two different groups', () => { - expect(() => - validate( - withGroups(2, [ - { id: 'g1', activeTabId: 'tab-0', tabOrder: ['tab-0', 'tab-1'], recentTabIds: [] }, - { id: 'g2', activeTabId: 'tab-1', tabOrder: ['tab-1'], recentTabIds: [] } - ]) - ) - ).toThrow('terminal_orphan_topology_invalid') -}) - -it('rejects a duplicated group id', () => { - expect(() => - validate( - withGroups(2, [ - { id: 'g', activeTabId: 'tab-0', tabOrder: ['tab-0'], recentTabIds: [] }, - { id: 'g', activeTabId: 'tab-1', tabOrder: ['tab-1'], recentTabIds: [] } - ]) - ) - ).toThrow('terminal_orphan_topology_invalid') -}) - -// Empty tab order cannot hold its active tab; fail closed instead of selecting an arbitrary pane. -it('rejects an empty tab order', () => { - expect(() => - validate(withGroups(2, [{ id: 'g', activeTabId: 'tab-0', tabOrder: [], recentTabIds: [] }])) - ).toThrow('terminal_orphan_topology_invalid') -}) - -it('rejects a claimed tab that no group lists', () => { - expect(() => - validate( - withGroups(2, [{ id: 'g', activeTabId: 'tab-0', tabOrder: ['tab-0'], recentTabIds: [] }]) - ) - ).toThrow('terminal_orphan_topology_invalid') -}) - -it.each([ - ['omitted', undefined], - ['empty', [] as string[]] -])('accepts %s recentTabIds', (_label, recentTabIds) => { - expect( - validate( - withGroups(2, [{ id: 'g', activeTabId: 'tab-0', tabOrder: ['tab-0', 'tab-1'], recentTabIds }]) - ).topologyTabsById.size - ).toBe(2) -}) diff --git a/src/main/runtime/terminal/adoption/terminal-orphan-topology.test.ts b/src/main/runtime/terminal/adoption/terminal-orphan-topology.test.ts deleted file mode 100644 index 321ea5be7be..00000000000 --- a/src/main/runtime/terminal/adoption/terminal-orphan-topology.test.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { mergeTerminalOrphanGroupLayout } from './terminal-orphan-topology' - -describe('terminal orphan topology', () => { - it('keeps current host layout while preserving an unrelated proposed subtree', () => { - expect( - mergeTerminalOrphanGroupLayout({ - existingLayout: { type: 'leaf', groupId: 'group-live' }, - existingGroupIds: ['group-live'], - proposedLayout: { - type: 'split', - direction: 'vertical', - ratio: 0.6, - first: { type: 'leaf', groupId: 'group-old-left' }, - second: { type: 'leaf', groupId: 'group-old-right' } - }, - proposedGroupIds: ['group-old-left', 'group-old-right'], - mergedGroupIds: ['group-live', 'group-old-left', 'group-old-right'] - }) - ).toEqual({ - type: 'split', - direction: 'vertical', - ratio: 0.6, - first: { type: 'leaf', groupId: 'group-live' }, - second: { - type: 'split', - direction: 'vertical', - ratio: 0.6, - first: { type: 'leaf', groupId: 'group-old-left' }, - second: { type: 'leaf', groupId: 'group-old-right' } - } - }) - }) - - it('grafts proposed groups beside their one current anchor without duplicating it', () => { - expect( - mergeTerminalOrphanGroupLayout({ - existingLayout: { - type: 'split', - direction: 'vertical', - first: { type: 'leaf', groupId: 'group-live' }, - second: { type: 'leaf', groupId: 'group-other' } - }, - existingGroupIds: ['group-live', 'group-other'], - proposedLayout: { - type: 'split', - direction: 'horizontal', - ratio: 0.7, - first: { type: 'leaf', groupId: 'group-live' }, - second: { type: 'leaf', groupId: 'group-recovered' } - }, - proposedGroupIds: ['group-live', 'group-recovered'], - mergedGroupIds: ['group-live', 'group-other', 'group-recovered'] - }) - ).toEqual({ - type: 'split', - direction: 'vertical', - first: { - type: 'split', - direction: 'horizontal', - ratio: 0.7, - first: { type: 'leaf', groupId: 'group-live' }, - second: { type: 'leaf', groupId: 'group-recovered' } - }, - second: { type: 'leaf', groupId: 'group-other' } - }) - }) - - it('appends new groups when multiple shared anchors make placement ambiguous', () => { - expect( - mergeTerminalOrphanGroupLayout({ - existingLayout: { - type: 'split', - direction: 'horizontal', - first: { type: 'leaf', groupId: 'group-a' }, - second: { type: 'leaf', groupId: 'group-b' } - }, - existingGroupIds: ['group-a', 'group-b'], - proposedLayout: { - type: 'split', - direction: 'vertical', - ratio: 0.7, - first: { - type: 'split', - direction: 'horizontal', - first: { type: 'leaf', groupId: 'group-a' }, - second: { type: 'leaf', groupId: 'group-b' } - }, - second: { type: 'leaf', groupId: 'group-recovered' } - }, - proposedGroupIds: ['group-a', 'group-b', 'group-recovered'], - mergedGroupIds: ['group-a', 'group-b', 'group-recovered'] - }) - ).toEqual({ - type: 'split', - direction: 'vertical', - ratio: 0.7, - first: { - type: 'split', - direction: 'horizontal', - first: { type: 'leaf', groupId: 'group-a' }, - second: { type: 'leaf', groupId: 'group-b' } - }, - second: { type: 'leaf', groupId: 'group-recovered' } - }) - }) -}) diff --git a/src/main/runtime/terminal/identity/main-pane-record-arrival.test.ts b/src/main/runtime/terminal/identity/main-pane-record-arrival.test.ts index 01ef4816e6b..cd6a5331f52 100644 --- a/src/main/runtime/terminal/identity/main-pane-record-arrival.test.ts +++ b/src/main/runtime/terminal/identity/main-pane-record-arrival.test.ts @@ -55,16 +55,6 @@ async function hostTabRows(runtime: ReturnType) { } describe('a terminal record arriving after main composed its panes (switch on)', () => { - it('lists the restored pane once, with the terminal its saved layout binds', async () => { - const runtime = restartedHost('inc-now', () => [ - { id: 'persisted-pty', incarnationId: 'inc-now', connected: true } - ]) - - expect(await hostTabRows(runtime)).toEqual([ - { ptyId: 'persisted-pty', incarnationId: 'inc-now' } - ]) - }) - it('re-composes when the record learns the incarnation the pane is stamped with', async () => { let incarnationId = 'inc-before' const runtime = restartedHost('inc-now', () => [ @@ -78,39 +68,4 @@ describe('a terminal record arriving after main composed its panes (switch on)', { ptyId: 'persisted-pty', incarnationId: 'inc-now' } ]) }) - - it('publishes nothing when an unchanged inventory refresh records the same terminal', async () => { - const runtime = restartedHost('inc-now', () => [ - { id: 'persisted-pty', incarnationId: 'inc-now', connected: true } - ]) - await hostTabRows(runtime) - await new Promise((resolve) => setImmediate(resolve)) - const published = vi.spyOn(runtime, 'syncWindowGraph') - - await hostTabRows(runtime) - await new Promise((resolve) => setImmediate(resolve)) - - expect(published).not.toHaveBeenCalled() - }) - - it('re-composes when the record moves to another workspace', async () => { - const runtime = restartedHost('inc-now', () => [ - { id: 'persisted-pty', incarnationId: 'inc-now', connected: true } - ]) - // A window publishes, so main republishes its panes when a record changes. - runtime.attachWindow(1) - runtime.syncWindowGraph(1, { tabs: [], leaves: [], mountedLeaves: [] }) - expect(await hostTabRows(runtime)).toEqual([ - { ptyId: 'persisted-pty', incarnationId: 'inc-now' } - ]) - - await new Promise((resolve) => setImmediate(resolve)) - const published = vi.spyOn(runtime, 'syncWindowGraph') - // Simulate a worktree-identity migration. - runtime['recordPtyWorktree']('persisted-pty', 'repo-1::/tmp/worktree-b') - await new Promise((resolve) => setImmediate(resolve)) - - expect(runtime['paneOfPty'](runtime['ptysById'].get('persisted-pty')).tabId).toBeNull() - expect(published).toHaveBeenCalled() - }) }) diff --git a/src/main/runtime/terminal/identity/workspace-session-worktree-id.test.ts b/src/main/runtime/terminal/identity/workspace-session-worktree-id.test.ts deleted file mode 100644 index ca292862621..00000000000 --- a/src/main/runtime/terminal/identity/workspace-session-worktree-id.test.ts +++ /dev/null @@ -1,33 +0,0 @@ -/** Absent tab rows mean never initialized; explicit empty rows are closed-last-tab tombstones. Re-keying must preserve that distinction. */ -import { describe, expect, it } from 'vitest' -import { getDefaultWorkspaceSession } from '../../../../shared/constants' -import type { WorkspaceSessionState } from '../../../../shared/workspace-session/workspace-session-state-types' -import { canonicalizeTerminalSessionWorktreeId } from './workspace-session-worktree-id' - -const SOURCE = 'repo-1::/old/path' -const TARGET = 'repo-1::/new/path' - -function session(overrides: Partial = {}): WorkspaceSessionState { - return { ...getDefaultWorkspaceSession(), ...overrides } -} - -describe('canonicalizeTerminalSessionWorktreeId', () => { - it('does not fabricate a tombstone for a workspace that had no tabs row', () => { - const next = session({ tabsByWorktree: {} }) - - canonicalizeTerminalSessionWorktreeId(next, SOURCE, TARGET) - - expect(Object.hasOwn(next.tabsByWorktree, TARGET)).toBe(false) - }) - - it('carries a real closed-last-terminal tombstone across the re-key', () => { - // Explicit empty rows preserve the user's closed-last-terminal intent. - const next = session({ tabsByWorktree: { [SOURCE]: [] } }) - - canonicalizeTerminalSessionWorktreeId(next, SOURCE, TARGET) - - expect(Object.hasOwn(next.tabsByWorktree, TARGET)).toBe(true) - expect(next.tabsByWorktree[TARGET]).toEqual([]) - expect(Object.hasOwn(next.tabsByWorktree, SOURCE)).toBe(false) - }) -}) diff --git a/src/main/runtime/terminal/launch/mobile-terminal-chat-fork.test.ts b/src/main/runtime/terminal/launch/mobile-terminal-chat-fork.test.ts index ff401913f89..43cf15281a6 100644 --- a/src/main/runtime/terminal/launch/mobile-terminal-chat-fork.test.ts +++ b/src/main/runtime/terminal/launch/mobile-terminal-chat-fork.test.ts @@ -1,9 +1,9 @@ -import { describe, expect, it, vi } from 'vitest' +import { expect, it, vi } from 'vitest' import type { RuntimeNotifier } from '../../runtime-notifier-contract' -import { buildMobileTerminalChatFork } from './mobile-terminal-chat-fork' +import './mobile-terminal-chat-fork' import { prepareStructuredChatCliFork } from '../../../ai-vault/structured-chat-cli-fork' -import { OrchestrationError } from '../../orchestration/orchestration-error' -import { PROJECT_RUNTIME_REPAIR_REQUIRED_CODE } from '../../../../shared/project-runtime-repair-message' +import '../../orchestration/orchestration-error' +import '../../../../shared/project-runtime-repair-message' import '../../orca-runtime-tests/orca-runtime-test-lifecycle.spec' import { committingSpawn } from '../../orca-runtime-tests/bind-first-pane.test-fixture' import { ORCA_LAYOUT_OWNER_IS_MAIN } from '../../../../shared/layout-owner-switch' @@ -14,65 +14,6 @@ vi.mock('../../../ai-vault/structured-chat-cli-fork', () => ({ })) const cwd = process.platform === 'win32' ? 'C:\\repo' : '/tmp/repo' const filePath = process.platform === 'win32' ? 'C:\\sessions\\chat.jsonl' : '/tmp/chat.jsonl' -const runtime = { - platform: process.platform, - pty: { shellOverride: undefined, terminalWindowsWslDistro: null } -} - -describe('native chat fork launch recipe', () => { - it('keeps Manual empty args, custom command and environment in an independent fork', () => { - const startup = buildMobileTerminalChatFork({ - prepared: { - useRealCodexHome: false, - cliForkSource: { - agent: 'claude', - sourceAgent: 'claude', - sessionId: 'original-chat', - filePath, - cwd, - codexHome: null, - executionHostPlatform: process.platform - } - }, - settings: { - agentCmdOverrides: { claude: 'my-claude' }, - agentDefaultArgs: { claude: '' }, - agentDefaultEnv: { claude: { MY_SETTING: 'present' } } - }, - runtime - }) - expect(startup.command).toContain('my-claude') - expect(startup.command).toContain('original-chat') - expect(startup.command).toContain('fork-session') - expect(startup.env).toMatchObject({ MY_SETTING: 'present' }) - expect(startup.cwd).toBe(cwd) - expect(startup.launchRuntime).toBe(runtime) - expect(startup).not.toHaveProperty('resumeProviderSession') - }) - - it('opens the host copy without reusing the chat identity', () => { - const startup = buildMobileTerminalChatFork({ - prepared: { - useRealCodexHome: false, - forkedSession: { agent: 'opencode', sessionId: 'copy', filePath }, - cliForkSource: { - agent: 'opencode', - sourceAgent: 'opencode', - sessionId: 'copy', - filePath, - cwd, - codexHome: null, - executionHostPlatform: process.platform - } - }, - settings: { agentDefaultArgs: { opencode: '' } }, - runtime - }) - expect(startup.command).toContain('copy') - expect(startup.command).not.toContain('original-chat') - expect(startup.resumeProviderSession?.id).toBe('copy') - }) -}) it.each([true, false])( 'forks a source-selected chat with saved placement available: %s', @@ -223,53 +164,3 @@ it.each([true, false])( ) } ) - -it('keeps preparation and runtime failures from starting a terminal', async () => { - await import('../../orca-runtime-tests/runtime-test-module-registration.spec') - const { OrcaRuntimeService } = await import('../../orca-runtime') - const { store, TEST_WORKTREE_ID } = - await import('../../orca-runtime-tests/orca-runtime-test-fixtures.spec') - const runtime = new OrcaRuntimeService(store) - const spawn = vi.fn() - runtime.setPtyController({ - spawn, - write: () => true, - kill: () => true, - getForegroundProcess: async () => null - }) - const ensure = vi.spyOn(runtime, 'ensureStructuredAgentSessionHost') - for (const [failure, expected] of [ - [ - new Error( - 'Could not confirm this conversation was saved. Try again after the agent responds.' - ), - { message: 'chat_fork_source_unavailable' } - ], - [ - new Error('This chat has no saved conversation to copy.'), - { message: 'chat_fork_source_unavailable' } - ], - [ - new OrchestrationError(PROJECT_RUNTIME_REPAIR_REQUIRED_CODE, 'Repair project settings.'), - { code: PROJECT_RUNTIME_REPAIR_REQUIRED_CODE } - ] - ] as const) { - ensure.mockResolvedValueOnce(undefined) - vi.mocked(prepareStructuredChatCliFork).mockRejectedValueOnce(failure) - await expect( - runtime['createMobileChatForkTerminal']({ - workspace: { - id: TEST_WORKTREE_ID, - path: cwd, - connectionId: null, - repo: null, - folderWorkspace: null - }, - worktreeId: TEST_WORKTREE_ID, - opts: { sourceChatFork: { sessionId: 'unsaved-chat' }, clientMutationId: 'next-create' }, - graphEpoch: 0 - }) - ).rejects.toMatchObject(expected) - } - expect(spawn).not.toHaveBeenCalled() -}) diff --git a/src/main/runtime/terminal/launch/runtime-created-worktree-terminal-target.test.ts b/src/main/runtime/terminal/launch/runtime-created-worktree-terminal-target.test.ts deleted file mode 100644 index 7bf8c1aca11..00000000000 --- a/src/main/runtime/terminal/launch/runtime-created-worktree-terminal-target.test.ts +++ /dev/null @@ -1,183 +0,0 @@ -import { describe, expect, it, vi } from 'vitest' -import type { ExecutionHostId } from '../../../../shared/execution-host' -import type { Repo } from '../../../../shared/repo-types' -import type { WorktreeMeta } from '../../../../shared/worktree/meta-types' -import type { Worktree } from '../../../../shared/worktree/types' -import type { WorktreeLineage } from '../../../../shared/worktree/lineage-types' -import type { RuntimeStore } from '../../runtime-store-contract' -import type { ResolvedWorktree } from '../../workspaces/catalog/runtime-worktree-path-identity' -import { mergeWorktree } from '../../../ipc/worktree-metadata-merge' -import { resolveCreatedWorktreeTerminalTarget } from './runtime-created-worktree-terminal-target' -import { committingSpawn } from '../../orca-runtime-tests/bind-first-pane.test-fixture' - -vi.mock('electron', () => ({ - BrowserWindow: { fromId: vi.fn(() => null) }, - webContents: { fromId: vi.fn(() => null) }, - ipcMain: { on: vi.fn(), removeListener: vi.fn() }, - app: { getPath: vi.fn(() => '/tmp'), isPackaged: false } -})) - -import { OrcaRuntimeService } from '../../orca-runtime' - -class StartupRuntime extends OrcaRuntimeService { - readonly lookup = vi.fn<(selector: string) => Promise>() - readonly repoLookup = vi.fn<(selector: string) => Promise>() - - protected override resolveRepoSelector(selector: string): Promise { - return this.repoLookup(selector) - } - - protected override resolveGitWorktreeSelector(selector: string): Promise { - return this.lookup(selector) - } -} - -function owner(hostId: ExecutionHostId = 'local'): Repo { - return { - id: 'repo-1', - path: '/repos/app', - displayName: 'app', - badgeColor: 'blue', - addedAt: 1, - executionHostId: hostId - } -} - -function makeFixture(repos: Repo[] = [owner()], hostId: ExecutionHostId = 'local') { - const meta: WorktreeMeta = { - instanceId: 'new-instance', - hostId, - displayName: '', - comment: '', - linkedIssue: null, - linkedPR: null, - linkedLinearIssue: null, - isArchived: false, - isUnread: false, - isPinned: false, - sortOrder: 0, - lastActivityAt: 0 - } - const worktree = mergeWorktree( - 'repo-1', - { - path: '/workspaces/new', - head: 'abc123', - branch: 'refs/heads/new', - isBare: false, - isMainWorktree: false - }, - meta - ) - const metadata: Record = { [worktree.id]: meta } - const lineageById: Record = {} - const store: RuntimeStore = { - getRepos: () => repos, - getRepo: (id) => repos.find((repo) => repo.id === id), - addRepo: vi.fn(), - updateRepo: vi.fn(), - getAllWorktreeMeta: () => metadata, - getWorktreeMeta: (id) => metadata[id], - getAllWorktreeLineage: () => lineageById, - setWorktreeMeta: (target, patch) => - (metadata[target.id] = { ...meta, ...metadata[target.id], ...patch }), - removeWorktreeMeta: (target) => { - delete metadata[target.id] - }, - getGitHubCache: vi.fn(), - getSettings: () => ({ - workspaceDir: '/workspaces', - nestWorkspaces: false, - refreshLocalBaseRefOnWorktreeCreate: false, - branchPrefix: 'none', - branchPrefixCustom: '' - }) - } - const runtime = new StartupRuntime(store) - const fallback: ResolvedWorktree = { - ...worktree, - git: worktree, - parentWorktreeId: null, - childWorktreeIds: [], - lineage: null - } - runtime.lookup.mockResolvedValue(fallback) - let ptySequence = 0 - const spawn = vi.fn(async (_args: { worktreeId?: string; tabId?: string; leafId?: string }) => ({ - id: `new-pty-${++ptySequence}` - })) - runtime.setPtyController({ - spawn: committingSpawn(runtime, spawn), - write: () => true, - kill: () => true, - getForegroundProcess: async () => null - }) - return { runtime, store, worktree, meta, metadata, lineageById, spawn } -} - -describe('created worktree terminal evidence', () => { - it('does not reuse creation evidence for a different selector or a replaced instance', async () => { - const { store, worktree, metadata } = makeFixture() - expect(resolveCreatedWorktreeTerminalTarget(store, 'branch:new', worktree)).toBeNull() - expect( - resolveCreatedWorktreeTerminalTarget(store, 'id:repo-1::/elsewhere', worktree) - ).toBeNull() - metadata[worktree.id] = { ...metadata[worktree.id]!, instanceId: 'replacement' } - expect(resolveCreatedWorktreeTerminalTarget(store, `id:${worktree.id}`, worktree)).toBeNull() - }) - - it('rejects another host metadata and ambiguous legacy ownership', async () => { - const { store, worktree, metadata } = makeFixture([owner(), owner('ssh:builder')]) - metadata[worktree.id] = { ...metadata[worktree.id]!, hostId: 'ssh:builder' } - expect(resolveCreatedWorktreeTerminalTarget(store, `id:${worktree.id}`, worktree)).toBeNull() - metadata[worktree.id] = { ...metadata[worktree.id]!, hostId: undefined } - const unstamped: Worktree = { ...worktree, hostId: undefined } - expect(resolveCreatedWorktreeTerminalTarget(store, `id:${worktree.id}`, unstamped)).toBeNull() - expect(resolveCreatedWorktreeTerminalTarget(store, `id:${worktree.id}`, worktree)).toBeNull() - }) - - it('keeps folder workspaces on their existing launch lookup', () => { - const { store, worktree } = makeFixture([{ ...owner(), kind: 'folder' }]) - expect(resolveCreatedWorktreeTerminalTarget(store, `id:${worktree.id}`, worktree)).toBeNull() - }) - - it('preserves stored lineage while rejecting stale parents and cycles', () => { - const { store, worktree, meta, metadata, lineageById } = makeFixture() - const parentId = 'repo-1::/workspaces/parent' - const childId = 'repo-1::/workspaces/child' - metadata[parentId] = { ...meta, instanceId: 'parent-instance' } - metadata[childId] = { ...meta, instanceId: 'child-instance' } - const lineage: WorktreeLineage = { - worktreeId: worktree.id, - worktreeInstanceId: 'new-instance', - parentWorktreeId: parentId, - parentWorktreeInstanceId: 'parent-instance', - origin: 'cli', - capture: { source: 'explicit-cli-flag', confidence: 'explicit' }, - createdAt: 1 - } - lineageById[worktree.id] = lineage - lineageById[childId] = { - ...lineage, - worktreeId: childId, - worktreeInstanceId: 'child-instance', - parentWorktreeId: worktree.id, - parentWorktreeInstanceId: 'new-instance' - } - const resolve = () => resolveCreatedWorktreeTerminalTarget(store, `id:${worktree.id}`, worktree) - expect(resolve()).toEqual( - expect.objectContaining({ parentWorktreeId: parentId, childWorktreeIds: [childId], lineage }) - ) - metadata[parentId] = { ...meta, instanceId: 'replacement' } - expect(resolve()).toEqual(expect.objectContaining({ parentWorktreeId: null, lineage: null })) - metadata[parentId] = { ...meta, instanceId: 'parent-instance' } - lineageById[parentId] = { - ...lineage, - worktreeId: parentId, - worktreeInstanceId: 'parent-instance', - parentWorktreeId: worktree.id, - parentWorktreeInstanceId: 'new-instance' - } - expect(resolve()).toEqual(expect.objectContaining({ parentWorktreeId: null, lineage: null })) - }) -}) diff --git a/src/main/runtime/terminal/launch/terminal-image-viewer-env.test.ts b/src/main/runtime/terminal/launch/terminal-image-viewer-env.test.ts deleted file mode 100644 index 141e8fb8b88..00000000000 --- a/src/main/runtime/terminal/launch/terminal-image-viewer-env.test.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { afterEach, describe, expect, it, vi } from 'vitest' -import { ELECTRON_REMOTE_RUNTIME_CLIENT_CAPABILITIES } from '../../../../shared/electron-remote-runtime-client-capabilities' -import { NATIVE_REMOTE_RUNTIME_CLIENT_CAPABILITIES } from '../../../../shared/protocol-version' -import { TERMINAL_IMAGE_VIEWER_CAPABILITY } from '../../../../shared/terminal/images/terminal-image-viewer-capability' -import { - applyClaudeTerminalImageViewerEnv, - terminalImageViewerSupported -} from './terminal-image-viewer-env' - -const paired = { caller: { kind: 'paired-device', deviceId: 'phone' } as const } -afterEach(() => vi.unstubAllEnvs()) -describe('Claude images follow the launching viewer', () => { - it.each([undefined, [], NATIVE_REMOTE_RUNTIME_CLIENT_CAPABILITIES])( - 'keeps older and native clients on old output %#', - (clientCapabilities) => { - expect( - terminalImageViewerSupported({ ...paired, clientKind: 'runtime', clientCapabilities }) - ).toBe(false) - } - ) - it('enables a capable paired Electron viewer', () => { - expect( - terminalImageViewerSupported({ - ...paired, - clientKind: 'runtime', - clientCapabilities: ELECTRON_REMOTE_RUNTIME_CLIENT_CAPABILITIES - }) - ).toBe(true) - }) - it('does not treat a mobile viewer as an image renderer', () => { - expect( - terminalImageViewerSupported({ - ...paired, - clientKind: 'mobile', - clientCapabilities: [TERMINAL_IMAGE_VIEWER_CAPABILITY] - }) - ).toBe(false) - }) - it.each([ - {}, - { ...paired }, - { ...paired, clientCapabilities: [TERMINAL_IMAGE_VIEWER_CAPABILITY] }, - { clientKind: 'runtime' as const, clientCapabilities: [TERMINAL_IMAGE_VIEWER_CAPABILITY] }, - { caller: { kind: 'local-cli' as const }, connectionId: 'remote' }, - { caller: { kind: 'local-cli' as const }, pairedDeviceId: 'remote' }, - { caller: { kind: 'local-cli' as const }, clientId: 'remote' } - ])('fails closed for an unclassified or remote context %#', (context) => { - expect(terminalImageViewerSupported(context)).toBe(false) - }) - it.each([ - { caller: { kind: 'desktop' as const }, clientKind: 'runtime' as const }, - { caller: { kind: 'local-cli' as const } } - ])('keeps same-build local launches on their existing policy %#', (context) => { - expect(terminalImageViewerSupported(context)).toBeUndefined() - }) - it('disables automatic image forcing only for unsupported Claude', () => { - vi.stubEnv('CLAUDE_CODE_FORCE_TERMINAL_IMAGES', undefined) - const launch = { launchAgent: 'claude' as const, env: { KEEP: 'yes' } } - expect(applyClaudeTerminalImageViewerEnv(launch, false).env).toEqual({ - KEEP: 'yes', - CLAUDE_CODE_FORCE_TERMINAL_IMAGES: '0' - }) - expect(launch.env).toEqual({ KEEP: 'yes' }) - const codex = { launchAgent: 'codex' as const } - expect(applyClaudeTerminalImageViewerEnv(codex, false)).toBe(codex) - }) -}) diff --git a/src/main/runtime/terminal/launch/terminal-pty-exit-waiter.spec.ts b/src/main/runtime/terminal/launch/terminal-pty-exit-waiter.spec.ts index 092c20aee16..3ed42ab5ddd 100644 --- a/src/main/runtime/terminal/launch/terminal-pty-exit-waiter.spec.ts +++ b/src/main/runtime/terminal/launch/terminal-pty-exit-waiter.spec.ts @@ -70,74 +70,6 @@ export function registerTerminalPtyExitWaiterTests(): void { }) describe('fresh composer lifetime', () => { - it.each(['exit', 'replacement', 'prune', 'graph-retirement', 'abort', 'timeout'] as const)( - 'retires its scanner, listeners and deadline on %s', - async (end) => { - const { runtime, handle } = await createTranscriptPane({ - paneTitle: 'shell', - foregroundProcess: 'bash', - data: '' - }) - const subscribe = vi.spyOn(runtime, 'subscribeToTerminalData') - const stop = new AbortController() - const removeAbort = vi.spyOn(stop.signal, 'removeEventListener') - vi.useFakeTimers() - try { - const deadlines = vi.spyOn(globalThis, 'setTimeout') - const clearDeadline = vi.spyOn(globalThis, 'clearTimeout') - const pending = runtime.waitForFreshWorkerComposer(handle, 'claude', 60_000, { - signal: stop.signal - }) - const outcome = pending.catch((error: unknown) => error) - const settled = vi.fn() - outcome.then(settled) - expect(runtime['terminalWaiters'].forPty(TRANSCRIPT_PANE_PTY_ID)).toHaveLength(1) - expect(subscribe).toHaveBeenCalledOnce() - const deadline = - deadlines.mock.results[deadlines.mock.calls.findIndex(([, ms]) => ms === 60_000)].value - if (end === 'exit') { - runtime.onPtyExit(TRANSCRIPT_PANE_PTY_ID, 137, 'inc-1', { providerExitObserved: true }) - } else if (end === 'replacement') { - runtime.onPtySpawned(TRANSCRIPT_PANE_PTY_ID, 'inc-2') - runtime['recordPtyWorktree'](TRANSCRIPT_PANE_PTY_ID, 'wt-1', { - connected: true, - incarnationId: 'inc-2' - }) - runtime.onPtyExit(TRANSCRIPT_PANE_PTY_ID, 137, 'inc-1', { providerExitObserved: true }) - } else if (end === 'prune') { - runtime['dropDisconnectedPtyRecord'](TRANSCRIPT_PANE_PTY_ID) - } else if (end === 'graph-retirement') { - runtime['restoreHeadlessGraphAuthority']() - } else if (end === 'abort') { - stop.abort() - } else { - await vi.advanceTimersByTimeAsync(60_000) - } - await vi.advanceTimersByTimeAsync(0) - expect(settled).toHaveBeenCalledOnce() - expect(await outcome).toMatchObject({ - message: - end === 'exit' - ? 'terminal_exited' - : end === 'abort' - ? 'request_aborted' - : end === 'timeout' - ? 'timeout' - : 'terminal_handle_stale' - }) - expect(runtime['terminalWaiters'].forPty(TRANSCRIPT_PANE_PTY_ID)).toHaveLength(0) - expect( - runtime['terminalStreamConsumers']['dataListeners'].get(TRANSCRIPT_PANE_PTY_ID) - ).toBeUndefined() - expect(removeAbort).toHaveBeenCalledWith('abort', expect.any(Function)) - expect(clearDeadline).toHaveBeenCalledWith(deadline) - } finally { - vi.useRealTimers() - vi.restoreAllMocks() - } - } - ) - it.each(['accept-exit', 'exit', 'replacement'] as const)( 'ignores a late acceptance/foreground success after %s', async (end) => { diff --git a/src/main/runtime/terminal/launch/terminal-split-layout-owner.test.ts b/src/main/runtime/terminal/launch/terminal-split-layout-owner.test.ts index 02f68909c2f..48dbb0fa884 100644 --- a/src/main/runtime/terminal/launch/terminal-split-layout-owner.test.ts +++ b/src/main/runtime/terminal/launch/terminal-split-layout-owner.test.ts @@ -125,22 +125,6 @@ describe('a split of a pane the window draws, in a layout-owner build', () => { expect(split.tabId).toBe(TAB_ID) }) - it('lists the new pane to phones and session.tabs with no window report', async () => { - const { runtime, handle } = setup() - // A phone already lists the tab, so the new pane must be published, not just hydrated. - await runtime.listMobileSessionTabs(`id:${TEST_WORKTREE_ID}`) - - const split = await runtime.splitTerminal(handle, { direction: 'vertical' }) - - const listed = await runtime.listMobileSessionTabs(`id:${TEST_WORKTREE_ID}`) - expect( - listed.tabs.some( - (tab) => - tab.type === 'terminal' && tab.parentTabId === TAB_ID && tab.leafId === split.leafId - ) - ).toBe(true) - }) - it('a split no one asked to see leaves the phones’ active tab alone; one asked for takes it', async () => { const { runtime, handle } = setup() const listed = () => runtime.listMobileSessionTabs(`id:${TEST_WORKTREE_ID}`) @@ -153,30 +137,6 @@ describe('a split of a pane the window draws, in a layout-owner build', () => { expect((await listed()).activeTabId).toBe(`${TAB_ID}::${shown.leafId}`) }) - it('the window’s own split keeps its proposed pane id, folder and size', async () => { - const { runtime, leafIds, spawn } = setup() - - const split = await runtime.splitTerminalPane( - { worktreeId: TEST_WORKTREE_ID, tabId: TAB_ID, leafId: HEADLESS_LEAF_ID }, - { - direction: 'horizontal', - newLeafId: NEW_LEAF_ID, - cwd: TEST_WORKTREE_PATH, - initialSize: { cols: 90, rows: 20 }, - keepPaneOnStartFailure: true - } - ) - - expect(split.leafId).toBe(NEW_LEAF_ID) - expect(leafIds()).toEqual([HEADLESS_LEAF_ID, NEW_LEAF_ID]) - expect(spawn.mock.calls[0]![0]).toMatchObject({ - leafId: NEW_LEAF_ID, - cwd: TEST_WORKTREE_PATH, - cols: 90, - rows: 20 - }) - }) - it('a window split whose terminal fails to start keeps its pane, showing why', async () => { const { runtime, leafIds, spawn } = setup() spawn.mockRejectedValueOnce(new Error('shell not found')) @@ -206,126 +166,4 @@ describe('a split of a pane the window draws, in a layout-owner build', () => { expect(leafIds()).toEqual([HEADLESS_LEAF_ID]) expect(notifier.closeTerminalPane).not.toHaveBeenCalled() }) - - it('starts nothing when main cannot write the pane', async () => { - const { runtime, runtimeStore, handle, splitTerminal, leafIds, spawn } = setup() - Reflect.set( - runtimeStore, - 'admitTerminalPane', - vi.fn(async () => 'pane_not_found') - ) - - await expect(runtime.splitTerminal(handle, { direction: 'vertical' })).rejects.toThrow( - 'terminal_split_source_not_found' - ) - expect(spawn).not.toHaveBeenCalled() - expect(splitTerminal).not.toHaveBeenCalled() - expect(leafIds()).toEqual([HEADLESS_LEAF_ID]) - }) - - it('starts nothing for a pane-named split of a tab main does not hold', async () => { - const { runtime, spawn } = setup() - - await expect( - runtime.splitTerminalPane( - { worktreeId: TEST_WORKTREE_ID, tabId: 'tab-elsewhere', leafId: HEADLESS_LEAF_ID }, - { direction: 'vertical' } - ) - ).rejects.toThrow('terminal_split_source_not_found') - expect(spawn).not.toHaveBeenCalled() - }) - - // The real spawn commit refuses a pane that left the layout while it started (spawn-commit.ts). - function closeWhileStarting(setupResult: ReturnType, close: () => Promise) { - const { spawn, getSession } = setupResult - spawn.mockImplementationOnce(async (args: { tabId?: string; leafId?: string }) => { - await close() - const root = getSession().terminalLayoutsByTabId[TAB_ID]?.root - if (!root || !collectLayoutLeafIdsInOrder(root).includes(args.leafId!)) { - throw new Error('terminal_split_source_not_found') - } - return { id: 'pty-late' } - }) - } - - it.each([ - ['its pane', { kind: 'pane', tabId: TAB_ID, leafId: NEW_LEAF_ID }], - ['its tab', { kind: 'tab', tabId: TAB_ID }] - ] as const)( - 'answers a split once, and leaves nothing, when the user closes %s while it starts', - async (_, target) => { - const context = setup() - const { runtime, getSession } = context - closeWhileStarting(context, () => - runtime.closeTerminalSurfaceFromRenderer({ - worktreeId: TEST_WORKTREE_ID, - target, - reason: 'user' - }) - ) - - await expect( - runtime.splitTerminalPane( - { worktreeId: TEST_WORKTREE_ID, tabId: TAB_ID, leafId: HEADLESS_LEAF_ID }, - { direction: 'vertical', newLeafId: NEW_LEAF_ID, keepPaneOnStartFailure: true } - ) - ).rejects.toThrow('terminal_split_source_not_found') - - const root = getSession().terminalLayoutsByTabId[TAB_ID]?.root - expect(root ? collectLayoutLeafIdsInOrder(root) : []).not.toContain(NEW_LEAF_ID) - expect(runtime.startStatusOf(makePaneKey(TAB_ID, NEW_LEAF_ID))).toBeNull() - } - ) - - it('refuses a named pane id another pane already holds, starting nothing', async () => { - const { runtime, spawn } = setup() - - await expect( - runtime.splitTerminalPane( - { worktreeId: TEST_WORKTREE_ID, tabId: TAB_ID, leafId: HEADLESS_LEAF_ID }, - { direction: 'vertical', newLeafId: HEADLESS_LEAF_ID } - ) - ).rejects.toThrow('pane_exists') - expect(spawn).not.toHaveBeenCalled() - expect(runtime.startStatusOf(makePaneKey(TAB_ID, HEADLESS_LEAF_ID))).toBeNull() - }) - - it('a window split main cannot start at all still shows its pane, failed, with why', async () => { - const { runtime, leafIds } = setup() - runtime.setPtyController({ - write: () => true, - kill: () => true, - getForegroundProcess: async () => null - }) - - await expect( - runtime.splitTerminalPane( - { worktreeId: TEST_WORKTREE_ID, tabId: TAB_ID, leafId: HEADLESS_LEAF_ID }, - { direction: 'vertical', newLeafId: NEW_LEAF_ID, keepPaneOnStartFailure: true } - ) - ).rejects.toThrow('runtime_unavailable') - - expect(leafIds()).toEqual([HEADLESS_LEAF_ID, NEW_LEAF_ID]) - expect(runtime.startStatusOf(makePaneKey(TAB_ID, NEW_LEAF_ID))).toEqual({ - status: 'failed', - reason: 'runtime_unavailable' - }) - }) - - it('refuses a taken pane id before anything else, even when main cannot start', async () => { - const { runtime } = setup() - runtime.setPtyController({ - write: () => true, - kill: () => true, - getForegroundProcess: async () => null - }) - - await expect( - runtime.splitTerminalPane( - { worktreeId: TEST_WORKTREE_ID, tabId: TAB_ID, leafId: HEADLESS_LEAF_ID }, - { direction: 'vertical', newLeafId: HEADLESS_LEAF_ID, keepPaneOnStartFailure: true } - ) - ).rejects.toThrow('pane_exists') - expect(runtime.startStatusOf(makePaneKey(TAB_ID, HEADLESS_LEAF_ID))).toBeNull() - }) }) diff --git a/src/main/runtime/terminal/launch/window-tab-start-switch-off.test.ts b/src/main/runtime/terminal/launch/window-tab-start-switch-off.test.ts deleted file mode 100644 index 9493d95120f..00000000000 --- a/src/main/runtime/terminal/launch/window-tab-start-switch-off.test.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { describe, expect, it, vi } from 'vitest' -import { - setupLayoutOwnerRuntime, - WORKTREE_ID -} from '../../orca-runtime-tests/orca-runtime-layout-owner-start-fixture' - -// Why: pinned off, so a run with ORCA_LAYOUT_OWNER=main still checks the switch-off refusal. -vi.mock('../../../../shared/layout-owner-switch', () => ({ ORCA_LAYOUT_OWNER_IS_MAIN: false })) - -describe("the window's new-tab start with the layout-owner build off", () => { - it('is refused, starting nothing, so the window starts its tab as today', async () => { - const { runtime, spawn } = setupLayoutOwnerRuntime() - - await expect( - runtime.startWindowTabTerminal(WORKTREE_ID, { tabId: 'tab', leafId: 'leaf' }, {}) - ).rejects.toThrow('host_not_owned') - expect(spawn).not.toHaveBeenCalled() - }) -}) diff --git a/src/main/runtime/terminal/layout/agent-generated-tab-titles.test.ts b/src/main/runtime/terminal/layout/agent-generated-tab-titles.test.ts index fcc79f63a71..900c3c296dd 100644 --- a/src/main/runtime/terminal/layout/agent-generated-tab-titles.test.ts +++ b/src/main/runtime/terminal/layout/agent-generated-tab-titles.test.ts @@ -79,15 +79,6 @@ Your task ID is: ${taskId} ${task}` describe('main titles agent tabs from its own hook status', () => { - it('titles a headless host’s tab, which no window ever opened', async () => { - const { runtime, titleOf } = await hostWith([GIT]) - - runtime.recordHookStatusTitle(status(GIT)) - await runtime.hookStatusTitlesSettled() - - expect(titleOf(GIT)).toBe('Refactor the auth middleware to use JWT') - }) - it('gives a batch’s titles of several workspaces in one rename and one frame', async () => { const { runtime, apply, titleOf } = await hostWith([GIT, FOLDER]) const frames: WorkspaceLayoutStreamEvent[] = [] @@ -106,36 +97,6 @@ describe('main titles agent tabs from its own hook status', () => { ]) }) - it('gives nothing with the setting off', async () => { - const { runtime, apply, titleOf } = await hostWith([GIT], { - titles: false - }) - - runtime.recordHookStatusTitle(status(GIT)) - await runtime.hookStatusTitlesSettled() - - expect(apply).not.toHaveBeenCalled() - expect(titleOf(GIT)).toBeUndefined() - }) - - it('leaves a renamed tab and a quick command’s tab untitled', async () => { - const { runtime, apply, titleOf } = await hostWith([GIT, FOLDER], { - edit: (session) => { - const [git] = session.session.tabsByWorktree[GIT]! - const [folder] = session.session.tabsByWorktree[FOLDER]! - session.session.tabsByWorktree[GIT] = [{ ...git!, customTitle: 'Status tests' }] - session.session.tabsByWorktree[FOLDER] = [{ ...folder!, quickCommandLabel: 'Run tests' }] - } - }) - - runtime.recordHookStatusTitle(status(GIT)) - runtime.recordHookStatusTitle(status(FOLDER)) - await runtime.hookStatusTitlesSettled() - - expect(apply).not.toHaveBeenCalled() - expect([titleOf(GIT), titleOf(FOLDER)]).toEqual([undefined, undefined]) - }) - it('has the titles in the first snapshot a window that starts after them reads', async () => { const { runtime } = await hostWith([GIT]) @@ -181,25 +142,6 @@ describe('main keeps the window’s generated-title rules', () => { } }) - it('keeps the first prompt’s title', async () => { - const { titleOf } = await titled([ - [status(GIT)], - [status(GIT, { prompt: 'Replace this with a later task name' })] - ]) - expect(titleOf(GIT)).toBe('Refactor the auth middleware to use JWT') - }) - - it('titles a dispatch from its task, not its preamble', async () => { - const { titleOf } = await titled([ - [ - status(GIT, { - prompt: dispatch('task-1', 'Implement the detailed worker instructions') - }) - ] - ]) - expect(titleOf(GIT)).toBe('Implement the detailed worker') - }) - it('replaces a dispatch’s preamble title once main has its labels', async () => { const prompt = dispatch('task-1', 'Implement the detailed worker instructions') const host = await titled( @@ -213,39 +155,6 @@ describe('main keeps the window’s generated-title rules', () => { expect(host.titleOf(GIT)).toBe('Better worker label') }) - it('keeps a dispatch label when a later non-dispatch prompt comes', async () => { - const { titleOf } = await titled( - [ - [ - status(GIT, { - prompt: dispatch('task-1', 'Implement the worker instructions') - }) - ], - [ - status(GIT, { - prompt: 'Refactor the auth middleware to use JWT tokens' - }) - ] - ], - labelsOf('task-1', 'Better worker label') - ) - expect(titleOf(GIT)).toBe('Better worker label') - }) - - it('titles a later non-dispatch prompt while sticky labels remain but no title exists', async () => { - const { titleOf } = await titled( - [ - [ - status(GIT, { - orchestration: labelsOf('task-1', 'Stale worker label')[paneOf(GIT)] - }) - ] - ], - labelsOf('task-1', 'Stale worker label') - ) - expect(titleOf(GIT)).toBe('Refactor the auth middleware to use JWT') - }) - it('does not re-pin task A’s label onto a later dispatch of task B', async () => { const { titleOf } = await titled( [ @@ -264,17 +173,4 @@ describe('main keeps the window’s generated-title rules', () => { ) expect(titleOf(GIT)).toBe('Implement task B worker instructions') }) - - it('titles a paste-sized prompt from its start', async () => { - const prompt = `Fix the flaky status tests ${'large pasted text '.repeat(5000)}` - const { titleOf } = await titled([[status(GIT, { prompt })]]) - expect(titleOf(GIT)).toBe('Fix the flaky status tests large pasted') - }) - - it('keeps a title given in the same batch, as the window’s batch does', async () => { - const { titleOf } = await titled([ - [status(GIT), status(GIT, { prompt: 'Replace this with a later task name' })] - ]) - expect(titleOf(GIT)).toBe('Refactor the auth middleware to use JWT') - }) }) diff --git a/src/main/runtime/terminal/layout/headless-terminal-split-layout.test.ts b/src/main/runtime/terminal/layout/headless-terminal-split-layout.test.ts deleted file mode 100644 index b3b50a9ac1c..00000000000 --- a/src/main/runtime/terminal/layout/headless-terminal-split-layout.test.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { - buildHeadlessTerminalSplitLayout, - countTerminalLayoutLeaves -} from './headless-terminal-split-layout' - -describe('buildHeadlessTerminalSplitLayout (headless split persistence)', () => { - it('replaces the provisional admission leaf with the requested split direction', () => { - const next = buildHeadlessTerminalSplitLayout( - { - root: { - type: 'split', - direction: 'vertical', - first: { type: 'leaf', leafId: 'leaf-a' }, - second: { type: 'leaf', leafId: 'leaf-c' } - }, - activeLeafId: 'leaf-c', - expandedLeafId: null, - ptyIdsByLeafId: { 'leaf-a': 'pty-a', 'leaf-c': 'provisional-pty' } - }, - { leafId: 'leaf-c', ptyId: 'pty-c', splitFromLeafId: 'leaf-a', direction: 'horizontal' } - ) - - expect(next.root).toEqual({ - type: 'split', - direction: 'horizontal', - first: { type: 'leaf', leafId: 'leaf-a' }, - second: { type: 'leaf', leafId: 'leaf-c' } - }) - expect(next.ptyIdsByLeafId).toEqual({ 'leaf-a': 'pty-a', 'leaf-c': 'pty-c' }) - }) - - it('preserves a sibling committed by a concurrent split', () => { - const next = buildHeadlessTerminalSplitLayout( - { - root: { - type: 'split', - direction: 'vertical', - first: { - type: 'split', - direction: 'horizontal', - first: { type: 'leaf', leafId: 'leaf-a' }, - second: { type: 'leaf', leafId: 'leaf-b' } - }, - second: { type: 'leaf', leafId: 'leaf-c' } - }, - activeLeafId: 'leaf-c', - expandedLeafId: null, - ptyIdsByLeafId: { - 'leaf-a': 'pty-a', - 'leaf-b': 'pty-b', - 'leaf-c': 'provisional-pty' - } - }, - { leafId: 'leaf-c', ptyId: 'pty-c', splitFromLeafId: 'leaf-a', direction: 'vertical' } - ) - - expect(next.root).toEqual({ - type: 'split', - direction: 'horizontal', - first: { - type: 'split', - direction: 'vertical', - first: { type: 'leaf', leafId: 'leaf-a' }, - second: { type: 'leaf', leafId: 'leaf-c' } - }, - second: { type: 'leaf', leafId: 'leaf-b' } - }) - expect(countTerminalLayoutLeaves(next.root)).toBe(3) - expect(next.ptyIdsByLeafId).toEqual({ - 'leaf-a': 'pty-a', - 'leaf-b': 'pty-b', - 'leaf-c': 'pty-c' - }) - }) -}) diff --git a/src/main/runtime/terminal/lifecycle/main-pane-liveness.test.ts b/src/main/runtime/terminal/lifecycle/main-pane-liveness.test.ts index 2c3fe9cfeb2..864899bc626 100644 --- a/src/main/runtime/terminal/lifecycle/main-pane-liveness.test.ts +++ b/src/main/runtime/terminal/lifecycle/main-pane-liveness.test.ts @@ -18,10 +18,6 @@ const { HEADLESS_SECOND_LEAF_ID, TEST_WORKTREE_ID, TEST_WORKTREE_PATH, - makeFolderProjectGroup, - makeFolderWorkspace, - TEST_FOLDER_WORKSPACE_KEY, - TEST_FOLDER_WORKSPACE_PATH, makeHeadlessTerminalLayout, makeRuntimeStoreWithWorkspaceSession, makeWorkspaceSessionWithHeadlessTerminal @@ -135,16 +131,6 @@ afterEach(() => { }) describe('a pane main owns does not vouch for its terminal (switch on)', () => { - it('reads a headless pane disconnected once the host stops listing its shell', async () => { - const { runtime, host } = savedPaneHost('saved-pty') - expect(await rowStatus(runtime, TEST_WORKTREE_ID)).toBe('ready') - - host.live.clear() - - expect(await rowStatus(runtime, TEST_WORKTREE_ID)).toBe('pending-handle') - expect(record(runtime, 'saved-pty')?.connected).toBe(false) - }) - it('reads a windowed pane disconnected, and a window republish does not reconnect it', async () => { const { runtime, host, publishWindowGraph } = savedPaneHost('saved-pty', { windowed: true }) expect(await rowStatus(runtime, TEST_WORKTREE_ID)).toBe('ready') @@ -158,24 +144,6 @@ describe('a pane main owns does not vouch for its terminal (switch on)', () => { expect(await rowStatus(runtime, TEST_WORKTREE_ID)).toBe('pending-handle') }) - it('never shows a restored pane not ready while its shell is listed', async () => { - const { runtime } = savedPaneHost('saved-pty', { windowed: true }) - const statuses: string[] = [] - const unsubscribe = runtime.onMobileSessionTabsChanged((snapshot) => { - for (const tab of snapshot.tabs) { - if (tab.type === 'terminal') { - statuses.push(tab.status) - } - } - }) - - expect(await rowStatus(runtime, TEST_WORKTREE_ID)).toBe('ready') - await new Promise((resolve) => setImmediate(resolve)) - unsubscribe() - - expect(statuses.filter((status) => status !== 'ready')).toEqual([]) - }) - it('leaves an SSH pane to the relay: a local sweep is not absence, the relay omitting it is', async () => { const { runtime, host } = savedPaneHost(SSH_PTY) runtime.registerPty(SSH_PTY, TEST_WORKTREE_ID, 'ssh-target', { @@ -195,63 +163,6 @@ describe('a pane main owns does not vouch for its terminal (switch on)', () => { expect(record(runtime, SSH_PTY)?.connected).toBe(false) expect(runtime.getPtyLivenessVerdict(SSH_PTY)?.status).not.toBe('exited') }) - - it('applies the same rule to a folder workspace', async () => { - const host: Host = { live: new Set(['folder-pty']) } - const session = makeWorkspaceSessionWithHeadlessTerminal({ - activeWorktreeId: TEST_FOLDER_WORKSPACE_KEY, - tabsByWorktree: { - [TEST_FOLDER_WORKSPACE_KEY]: [ - { - id: 'folder-tab', - ptyId: 'folder-pty', - worktreeId: TEST_FOLDER_WORKSPACE_KEY, - title: 'Folder', - customTitle: null, - color: null, - sortOrder: 0, - createdAt: 1 - } - ] - }, - terminalLayoutsByTabId: { - 'folder-tab': makeHeadlessTerminalLayout({ [HEADLESS_LEAF_ID]: 'folder-pty' }) - } - }) - const { runtimeStore } = makeRuntimeStoreWithWorkspaceSession(session) - const runtime = new OrcaRuntimeService({ - ...runtimeStore, - getFolderWorkspaces: () => [makeFolderWorkspace()], - getProjectGroups: () => [makeFolderProjectGroup()] - }) - runtime.setPtyController( - hostController(host, TEST_FOLDER_WORKSPACE_PATH, TEST_FOLDER_WORKSPACE_KEY) - ) - runtime.syncWindowGraph(0, { tabs: [], leaves: [] }) - expect(await rowStatus(runtime, TEST_FOLDER_WORKSPACE_KEY)).toBe('ready') - - host.live.clear() - - expect(await rowStatus(runtime, TEST_FOLDER_WORKSPACE_KEY)).toBe('pending-handle') - }) -}) - -describe('a launch into a pane through a listing gap (switch on)', () => { - it('treats a disconnected terminal main still binds as running until its exit is certified', async () => { - const { runtime, host } = savedPaneHost('saved-pty', { windowed: true }) - const paneKey = `host-tab:${HEADLESS_LEAF_ID}` - await rowStatus(runtime, TEST_WORKTREE_ID) - expect(runtime.hasLiveTerminalForPaneKey(paneKey)).toBe(true) - - // The host omits the shell once: an agent launch must attach or refuse, never start a second. - host.live.clear() - await rowStatus(runtime, TEST_WORKTREE_ID) - expect(record(runtime, 'saved-pty')?.connected).toBe(false) - expect(runtime.hasLiveTerminalForPaneKey(paneKey)).toBe(true) - - await runtime.onPtyExit('saved-pty', 0) - expect(runtime.hasLiveTerminalForPaneKey(paneKey)).toBe(false) - }) }) describe('stopping a workspace through a listing gap (switch on)', () => { @@ -426,16 +337,3 @@ describe('a terminal registered while a listing is in flight (switch on)', () => expect(record(runtime, 'split-pty')?.connected).toBe(true) }) }) - -describe('switch off: a window leaf still vouches for its terminal', () => { - it('keeps the window record but withholds a ready mobile handle when the host loses its PTY', async () => { - layoutOwner.isMain = false - const { runtime, host } = savedPaneHost('saved-pty', { windowed: true }) - expect(await rowStatus(runtime, TEST_WORKTREE_ID)).toBe('ready') - - host.live.clear() - - expect(await rowStatus(runtime, TEST_WORKTREE_ID)).toBe('pending-handle') - expect(record(runtime, 'saved-pty')?.connected).toBe(true) - }) -}) diff --git a/src/main/runtime/terminal/output/tail/terminal-tail-redraw-scaling.test.ts b/src/main/runtime/terminal/output/tail/terminal-tail-redraw-scaling.test.ts deleted file mode 100644 index 502c34de066..00000000000 --- a/src/main/runtime/terminal/output/tail/terminal-tail-redraw-scaling.test.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { appendNormalizedToTailBuffer } from './terminal-tail-buffer' - -// #11315: an SSH TUI (Pi ask_user) repainting full-width rows pinned Electron main at 100% CPU -// because every character written behind the row end rebuilt the whole row string. - -const ESC = '\x1b' - -// One frame: climb to the top of a three-row panel and overwrite every row in place. -function overwriteFrame(width: number): string { - return [ - `${ESC}[3A\r${'a'.repeat(width)}\n`, - `\r${ESC}[5G${'b'.repeat(width)}\n`, - `\r${'c'.repeat(width)}\n` - ].join('') -} - -describe('terminal tail redraw cost', () => { - it('keeps the overwritten rows exact', () => { - const width = 40 - const seed = appendNormalizedToTailBuffer([], '', `${'x'.repeat(width)}\n`.repeat(3), null) - const next = appendNormalizedToTailBuffer( - seed.lines, - seed.partialLine, - overwriteFrame(width).repeat(2), - seed.redrawCursor - ) - expect(next.lines.slice(-3)).toEqual([ - 'a'.repeat(width), - ` ${'b'.repeat(width)}`, - 'c'.repeat(width) - ]) - }) -}) diff --git a/src/main/runtime/terminal/query/runtime-terminal-inventory-contracts.test.ts b/src/main/runtime/terminal/query/runtime-terminal-inventory-contracts.test.ts index b8875689dc1..309967fe5c3 100644 --- a/src/main/runtime/terminal/query/runtime-terminal-inventory-contracts.test.ts +++ b/src/main/runtime/terminal/query/runtime-terminal-inventory-contracts.test.ts @@ -1,7 +1,5 @@ import { registerTerminalListExecutionHostScopeTests } from './terminal-list-execution-host-scope.spec' import { registerPtyInventoryLivenessVerdictTests } from '../../orca-runtime-tests/pty-inventory-liveness-verdict.spec' -import { registerSessionTabsEmptyWorktreePublicationTests } from '../../orca-runtime-tests/session-tabs-empty-worktree-publication.spec' registerTerminalListExecutionHostScopeTests() registerPtyInventoryLivenessVerdictTests() -registerSessionTabsEmptyWorktreePublicationTests() diff --git a/src/main/runtime/terminal/query/terminal-identity-read-context.test.ts b/src/main/runtime/terminal/query/terminal-identity-read-context.test.ts index 1dcab5836bf..e8af70e3d38 100644 --- a/src/main/runtime/terminal/query/terminal-identity-read-context.test.ts +++ b/src/main/runtime/terminal/query/terminal-identity-read-context.test.ts @@ -1,4 +1,4 @@ -import { identityFixture as fixture, LEAF, PANE } from './terminal-identity.test-fixture' +import { identityFixture as fixture, PANE } from './terminal-identity.test-fixture' import { OrchestrationDb } from '../../orchestration/db/orchestration-db' import '../../orchestration/db/contract-constants' import '../../orchestration/db/messages/mailbox-routing-page' @@ -9,11 +9,10 @@ import { readCrewRuntimeInputs } from '../../orchestration/crew/crew-runtime-wat import { expect, it, vi } from 'vitest' import '../../orca-runtime-tests/runtime-test-module-registration.spec' import { TEST_WORKTREE_ID } from '../../orca-runtime-tests/orca-runtime-test-fixtures.spec' -import { createBoundRun } from '../../rpc/methods/orchestration/runs/runs' -import { resolveOrchestrationCaller } from '../../rpc/methods/orchestration/runs/run-scope' -import { formatOrcaSessionAddress } from '../../../../shared/orca-session-address' -import { testOrcaSessionId } from '../../../../shared/orca-session-address-test-fixture' -import type { RuntimeSyncWindowGraph } from '../../../../shared/runtime-types' +import '../../rpc/methods/orchestration/runs/runs' +import '../../rpc/methods/orchestration/runs/run-scope' +import '../../../../shared/orca-session-address' +import '../../../../shared/orca-session-address-test-fixture' function countIterations( map: ReadonlyMap, @@ -51,59 +50,6 @@ function countIterations( } } -it('charges actual native map visits at N/10N for missing, moved and cold identities and leaves fast hits lazy', () => { - const measure = (population: number, indexed: boolean, fast: boolean) => { - const f = fixture() - for (let i = 0; i < population; i++) { - const pane = `tab-${i}:aaaaaaaa-aaaa-4aaa-8aaa-${String(i).padStart(12, '0')}` - f.add(`pty-${i}`, `term_${i}`, pane) - f.stamp(`term_${i}`, `pty-${i}`) - } - const counts = { entries: 0, get: 0, set: 0, delete: 0 } - countIterations(f.owner.handles, counts) - countIterations(f.owner.handleByPtyId, counts) - countIterations(f.owner.leaves, counts) - countIterations(f.owner.ptysById, counts) - const context = indexed ? f.runtime.createTerminalIdentityReadContext() : undefined - for (let i = 0; i < population; i++) { - f.owner.getLivePtyForHandle(fast ? `term_${i}` : `term_missing_${i}`, context) - if (!fast) { - expect(f.owner.getPtyRecordForPaneKey(`missing-${i}:${LEAF}`, context)).toBeNull() - } - } - return counts - } - const baseline = [measure(3, false, false), measure(30, false, false)] - const candidate = [measure(3, true, false), measure(30, true, false)] - expect(baseline[1].entries).toBe(baseline[0].entries * 100) - expect(candidate[1].entries).toBe(candidate[0].entries * 10) - expect(measure(30, true, true).entries).toBe(0) - const cold = (population: number, indexed: boolean) => { - const f = fixture() - for (let i = 0; i < population; i++) { - f.add(`cold-${i}`, `term_cold_${i}`, `cold-pane-${i}`) - f.stamp(`term_cold_${i}`, `cold-${i}`) - f.owner.source.unbindPty(`cold-${i}`) - } - const counts = { entries: 0, get: 0, set: 0, delete: 0 } - countIterations(f.owner.handles, counts) - countIterations(f.owner.ptysById, counts) - countIterations(f.owner.handleByPtyId, counts) - const context = indexed ? f.runtime.createTerminalIdentityReadContext() : undefined - for (let i = 0; i < population; i++) { - expect(f.runtime.getTerminalHandleForPaneKey(`cold-pane-${i}`, context)).toBe( - `term_cold_${i}` - ) - expect(f.owner.getLivePtyForHandle(`term_cold_${i}`, context)?.pty.ptyId).toBe(`cold-${i}`) - } - return counts - } - const coldBaseline = [cold(3, false), cold(30, false)] - const coldCandidate = [cold(3, true), cold(30, true)] - expect(coldBaseline[1].entries).toBeGreaterThan(coldBaseline[0].entries * 50) - expect(coldCandidate[1].entries).toBe(coldCandidate[0].entries * 10) -}) - it('invalidates actual SSH-held facts on register and finally-release with equal and conflicting claims', async () => { const f = fixture() const wake = vi.fn() @@ -287,112 +233,3 @@ it('reuses raw inputs for new historical parties through the production Crew bou vi.useRealTimers() } }) - -it('charges retained incarnation materialization through actual pending-pane Run producers and the complete Crew read', async () => { - vi.useFakeTimers({ now: new Date('2026-10-28T20:00:00Z') }) - const measure = async (peers: number, inventory: number) => { - const f = fixture() - const db = new OrchestrationDb(':memory:') - f.runtime.setOrchestrationDb(db) - const graph: RuntimeSyncWindowGraph = { - tabs: [ - { - tabId: 'tab-cost', - worktreeId: TEST_WORKTREE_ID, - title: 'Cost', - activeLeafId: null, - layout: null - } - ], - leaves: [] - } - for (let i = 0; i < peers + inventory; i++) { - const leafId = `aaaaaaaa-aaaa-4aaa-8aaa-${String(i).padStart(12, '0')}` - const ptyId = i < peers ? null : `inventory-${i}` - graph.leaves.push({ - tabId: 'tab-cost', - worktreeId: TEST_WORKTREE_ID, - leafId, - paneRuntimeId: i + 1, - ptyId - }) - if (ptyId) { - f.runtime.registerPty(ptyId, TEST_WORKTREE_ID, null, { - incarnationId: `process-${i}`, - tabId: 'tab-cost', - leafId - }) - } - } - f.runtime.attachWindow(1) - f.runtime.syncWindowGraph(1, graph) - for (const leaf of graph.leaves.slice(peers)) { - expect(f.runtime.getTerminalHandleForPaneKey(`tab-cost:${leaf.leafId}`)).toMatch(/^term_/) - } - const self = formatOrcaSessionAddress(testOrcaSessionId('eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee')) - const pendingHandles: string[] = [] - for (const leaf of graph.leaves.slice(0, peers)) { - graph.tabs[0].activeLeafId = leaf.leafId - f.runtime.syncWindowGraph(1, graph) - const handle = await f.runtime.resolveActiveTerminal() - const caller = resolveOrchestrationCaller(f.runtime, { - callerTerminalHandle: handle, - callerSession: undefined, - requireStablePane: true - }) - expect(caller.paneKey).toBe(`tab-cost:${leaf.leafId}`) - expect(f.owner.handles.get(handle)?.ptyId).toBeNull() - const run = createBoundRun(f.runtime, caller, handle, 'Pending coordinator') - db.insertMessage({ - from: `run:${run.id}`, - to: self, - subject: 'Retained Run', - body: `peer-${pendingHandles.length}` - }) - pendingHandles.push(handle) - } - expect(f.owner.handleByPtyIncarnation.size).toBe(inventory) - const counts = { invocations: 0, yielded: 0 } - const incarnations = f.owner.handleByPtyIncarnation - const iterate = incarnations[Symbol.iterator].bind(incarnations) - Object.defineProperty(incarnations, Symbol.iterator, { - configurable: true, - value: function* () { - counts.invocations++ - for (const entry of iterate()) { - counts.yielded++ - yield entry - } - } - }) - const process = vi.spyOn(f.runtime, 'getTerminalProcessIncarnation') - const model = new CrewReadModel( - (previous) => readCrewRuntimeInputs(f.owner.crewReaders, db, true, 'cost-host', previous), - f.owner.crewChanges.subscribe - ) - const stop = model.subscribe(() => {}) - try { - const output = model.read(self) - expect(output.status).toBe('ok') - expect(output.sections.talkingWith).toHaveLength(peers) - for (const handle of pendingHandles) { - expect(process.mock.calls.some(([current]) => current === handle)).toBe(true) - } - expect(counts.yielded).toBeLessThanOrEqual(4 * inventory + 4 * peers) - } finally { - stop() - process.mockRestore() - db.close() - } - } - try { - for (const [peers, inventory] of [ - [3, 3], - [30, 30] - ]) { - await measure(peers, inventory) - } - } finally { - vi.useRealTimers() - } -}) diff --git a/src/main/runtime/terminal/query/terminal-list-execution-host-scope.spec.ts b/src/main/runtime/terminal/query/terminal-list-execution-host-scope.spec.ts index 4f9b4be8d77..a047ffdd124 100644 --- a/src/main/runtime/terminal/query/terminal-list-execution-host-scope.spec.ts +++ b/src/main/runtime/terminal/query/terminal-list-execution-host-scope.spec.ts @@ -2,7 +2,7 @@ import { describe, expect, it, vi } from 'vitest' import { OrcaRuntimeService } from '../../orca-runtime' import { getDefaultWorkspaceSession } from '../../../../shared/constants' import type { WorkspaceSessionState } from '../../../../shared/workspace-session/workspace-session-state-types' -import { folderWorkspaceKey } from '../../../../shared/workspace-scope' +import '../../../../shared/workspace-scope' import type { FolderWorkspace } from '../../../../shared/folder-workspaces/folder-workspace-types' import { type ExecutionHostId, LOCAL_EXECUTION_HOST_ID } from '../../../../shared/execution-host' import type { PtyProcessInfo } from '../../../providers/pty-process-info' @@ -49,35 +49,6 @@ export function registerTerminalListExecutionHostScopeTests(): void { } } - function makeSshFolderWorkspace() { - return { - id: 'folder-ssh', - projectGroupId: 'group-1', - name: 'SSH folder', - folderPath: '/remote/folder', - connectionId: 'box-2', - linkedTask: null, - comment: '', - isArchived: false, - isUnread: false, - isPinned: false, - sortOrder: 0, - lastActivityAt: 0, - createdAt: 0, - updatedAt: 0 - } - } - - function makeRuntimeFolderWorkspace() { - return { - ...makeSshFolderWorkspace(), - id: 'folder-runtime', - name: 'Runtime folder', - connectionId: 'stale-box', - executionHostId: 'runtime:env-9' as const - } - } - type GraphLeaf = { worktreeId: string; leafId: string; ptyId: string } function makeRuntime(leaves: GraphLeaf[], store = makeStore()): OrcaRuntimeService { @@ -111,67 +82,6 @@ export function registerTerminalListExecutionHostScopeTests(): void { } describe('listTerminals scope declaration', () => { - it('covers a queried SSH host used only by a folder workspace', async () => { - const baseStore = makeStore() - const folderWorkspace = makeSshFolderWorkspace() - const runtime = makeRuntime([], { - ...baseStore, - getRepos: vi.fn(() => [REPOS[0]!]), - getRepo: vi.fn((id: string) => (id === REPOS[0]!.id ? REPOS[0] : undefined)), - getWorkspaceSessionHostIds: vi.fn(() => ['local']), - getFolderWorkspaces: vi.fn(() => [folderWorkspace]) - }) - runtime.setPtyController({ - spawn: vi.fn(async () => ({ id: 'never' })), - write: () => true, - kill: () => true, - listProcesses: vi.fn(async () => []), - listProcessesWithHostScope: vi.fn(async () => ({ - processes: [], - hostIds: ['local', 'ssh:box-2'] - })) - } as never) - - const result = await runtime.listTerminals(`id:${folderWorkspaceKey(folderWorkspace.id)}`) - - expect(result.hostScope?.hostIds).toEqual(['ssh:box-2']) - expect(result.hostScope?.omittedHostIds).toEqual(['local']) - }) - - it('does not claim local coverage for a paired-runtime folder workspace', async () => { - const baseStore = makeStore() - const folderWorkspace = makeRuntimeFolderWorkspace() - const runtime = makeRuntime([], { - ...baseStore, - getRepos: vi.fn(() => [REPOS[0]!]), - getRepo: vi.fn((id: string) => (id === REPOS[0]!.id ? REPOS[0] : undefined)), - getWorkspaceSessionHostIds: vi.fn(() => ['local']), - getFolderWorkspaces: vi.fn(() => [folderWorkspace]) - }) - - const result = await runtime.listTerminals(`id:${folderWorkspaceKey(folderWorkspace.id)}`) - - expect(result.hostScope?.hostIds).toEqual([]) - expect(result.hostScope?.omittedHostIds).toEqual(['local', 'runtime:env-9']) - }) - - it('marks every known host omitted when process inventory is unverifiable', async () => { - const runtime = makeRuntime([]) - runtime.setPtyController({ - spawn: vi.fn(async () => ({ id: 'never' })), - write: () => true, - kill: () => true, - listProcesses: vi.fn(async () => { - throw new Error('relay unavailable') - }) - } as never) - - const result = await runtime.listTerminals() - - expect(result.hostScope?.hostIds).toEqual([]) - expect(result.hostScope?.omittedHostIds).toEqual(['local', 'ssh:box-1']) - }) - // A targeted listing a newer census overtook joins that census instead of answering for no // host; re-listing would overtake the newer one in turn (two workspaces sleeping at once). it('answers from a newer concurrent inventory refresh instead of an empty scope', async () => { diff --git a/src/main/runtime/terminal/records/runtime-terminal-exit-contracts.test.ts b/src/main/runtime/terminal/records/runtime-terminal-exit-contracts.test.ts index 8f02ebb0a23..ef639df588a 100644 --- a/src/main/runtime/terminal/records/runtime-terminal-exit-contracts.test.ts +++ b/src/main/runtime/terminal/records/runtime-terminal-exit-contracts.test.ts @@ -1,5 +1,3 @@ import { registerTerminalPtyExitWaiterTests } from '../launch/terminal-pty-exit-waiter.spec' -import { registerClaudeAgentTeamsPtyExitLeakTests } from '../../orca-runtime-tests/claude-agent-teams-pty-exit-leak.spec' registerTerminalPtyExitWaiterTests() -registerClaudeAgentTeamsPtyExitLeakTests() diff --git a/src/main/runtime/terminal/surface/mobile-session-terminal-retirement-proof.test.ts b/src/main/runtime/terminal/surface/mobile-session-terminal-retirement-proof.test.ts deleted file mode 100644 index 6891945e74c..00000000000 --- a/src/main/runtime/terminal/surface/mobile-session-terminal-retirement-proof.test.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { preserveTerminalRetirementProofs } from './mobile-session-terminal-retirement-proof' -import type { RuntimeMobileSessionTabsSnapshot } from '../../../../shared/runtime-session-contracts' - -const retired = { - parentTabId: 'tab', - leafId: 'leaf', - ptyId: 'pty', - terminal: 'term', - incarnationId: 'inc' -} -function snapshot( - overrides: Partial = {} -): RuntimeMobileSessionTabsSnapshot { - return { - worktree: 'worktree', - worktreeInstanceId: 'instance', - publicationEpoch: 'epoch', - snapshotVersion: 1, - activeGroupId: null, - activeTabId: null, - activeTabType: null, - tabs: [], - ...overrides - } -} - -describe('mobile session terminal retirement proofs', () => { - // Identity-less host writes must inherit the occupant, preventing renderer(A) -> host -> renderer(B) proof laundering. - it('does not launder proofs across occupants through an identity-less host write', () => { - const occupantA = snapshot({ - worktreeInstanceId: 'instance-a', - retiredTerminalSurfaces: [retired] - }) - const hostWrite = preserveTerminalRetirementProofs( - snapshot({ worktreeInstanceId: undefined, snapshotVersion: 2 }), - occupantA - ) - expect(hostWrite.worktreeInstanceId).toBe('instance-a') - expect(hostWrite.retiredTerminalSurfaces).toEqual([retired]) - - const occupantB = snapshot({ worktreeInstanceId: 'instance-b', snapshotVersion: 3 }) - expect(preserveTerminalRetirementProofs(occupantB, hostWrite)).toBe(occupantB) - }) -}) diff --git a/src/main/runtime/terminal/surface/mobile-session-terminal-retirement.test.ts b/src/main/runtime/terminal/surface/mobile-session-terminal-retirement.test.ts deleted file mode 100644 index 9f65be275b0..00000000000 --- a/src/main/runtime/terminal/surface/mobile-session-terminal-retirement.test.ts +++ /dev/null @@ -1,196 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { getDefaultWorkspaceSession } from '../../../../shared/constants' -import type { RuntimeMobileSessionTabsSnapshot } from '../../../../shared/runtime-session-contracts' -import type { RuntimeMobileSessionTerminalTab } from '../../../../shared/runtime-mobile-session-tab-contracts' -import { retireTerminalSurfacesFromSnapshot } from './mobile-session-terminal-retirement' -import { retireTerminalSurfaceFromPersistence } from './mobile-session-terminal-persistence-retirement' - -const WORKTREE_ID = 'repo::/worktree' - -function splitSnapshot(): RuntimeMobileSessionTabsSnapshot { - const parentLayout = { - root: { - type: 'split' as const, - direction: 'vertical' as const, - first: { type: 'leaf' as const, leafId: 'left' }, - second: { type: 'leaf' as const, leafId: 'right' } - }, - activeLeafId: 'left', - expandedLeafId: 'left', - ptyIdsByLeafId: { left: 'pty-left', right: 'pty-right' }, - buffersByLeafId: { left: 'left buffer', right: 'right buffer' }, - titlesByLeafId: { left: 'Left', right: 'Right' } - } - return { - worktree: WORKTREE_ID, - publicationEpoch: 'host', - snapshotVersion: 4, - activeGroupId: 'terminals', - activeTabId: 'terminal::left', - activeTabType: 'terminal', - tabGroups: [ - { - id: 'terminals', - activeTabId: 'terminal', - tabOrder: ['terminal', 'notes'], - recentTabIds: ['notes', 'terminal'] - }, - { id: 'secondary', activeTabId: 'other', tabOrder: ['other'] } - ], - tabGroupLayout: { - type: 'split', - direction: 'horizontal', - first: { type: 'leaf', groupId: 'terminals' }, - second: { type: 'leaf', groupId: 'secondary' } - }, - tabs: [ - { - type: 'terminal', - id: 'terminal::left', - parentTabId: 'terminal', - leafId: 'left', - ptyId: 'pty-left', - title: 'Left', - parentLayout, - isActive: true - }, - { - type: 'terminal', - id: 'terminal::right', - parentTabId: 'terminal', - leafId: 'right', - ptyId: 'pty-right', - title: 'Right', - parentLayout, - isActive: false - }, - { - type: 'markdown', - id: 'notes', - title: 'Notes', - filePath: '/worktree/notes.md', - relativePath: 'notes.md', - language: 'markdown', - mode: 'edit', - isDirty: false, - sourceFileId: 'notes.md', - sourceFilePath: '/worktree/notes.md', - sourceRelativePath: 'notes.md', - documentVersion: '1', - isActive: false - }, - { - type: 'file', - id: 'other', - title: 'Other', - filePath: '/worktree/other.ts', - relativePath: 'other.ts', - language: 'typescript', - isDirty: false, - isActive: false - } - ] - } -} - -describe('mobile session terminal retirement', () => { - it('retires one split leaf and rewrites every surviving sibling layout', () => { - const result = retireTerminalSurfacesFromSnapshot({ - snapshot: splitSnapshot(), - ptyId: 'pty-left' - }) - - expect(result?.snapshot).toMatchObject({ - snapshotVersion: 5, - activeGroupId: 'terminals', - activeTabId: 'terminal::right', - activeTabType: 'terminal' - }) - expect(result?.snapshot.tabs).toHaveLength(3) - expect(result?.snapshot.tabs[0]).toMatchObject({ - id: 'terminal::right', - isActive: true, - parentLayout: { - root: { type: 'leaf', leafId: 'right' }, - activeLeafId: 'right', - expandedLeafId: null, - ptyIdsByLeafId: { right: 'pty-right' }, - buffersByLeafId: { right: 'right buffer' }, - titlesByLeafId: { right: 'Right' } - } - }) - }) - - it('does not retire an exact surface rebound to a replacement PTY', () => { - const snapshot = splitSnapshot() - const rebound = { - ...snapshot, - tabs: snapshot.tabs.map((tab) => - tab.type === 'terminal' && tab.leafId === 'left' - ? { - ...tab, - ptyId: 'pty-replacement', - parentLayout: tab.parentLayout - ? { - ...tab.parentLayout, - ptyIdsByLeafId: { - ...tab.parentLayout.ptyIdsByLeafId, - left: 'pty-replacement' - } - } - : undefined - } - : tab - ) - } - - expect( - retireTerminalSurfacesFromSnapshot({ - snapshot: rebound, - ptyId: 'pty-left', - exactSurfaces: [{ parentTabId: 'terminal', leafId: 'left' }] - }) - ).toBeNull() - }) - - it('de-persists an exact leaf without removing its live sibling', () => { - const session = { - ...getDefaultWorkspaceSession(), - activeTabId: 'terminal', - activeTabIdByWorktree: { [WORKTREE_ID]: 'terminal' }, - tabsByWorktree: { - [WORKTREE_ID]: [ - { - id: 'terminal', - ptyId: 'pty-left', - worktreeId: WORKTREE_ID, - title: 'Terminal', - customTitle: null, - color: null, - sortOrder: 0, - createdAt: 1 - } - ] - }, - terminalLayoutsByTabId: { - terminal: (splitSnapshot().tabs[0] as RuntimeMobileSessionTerminalTab).parentLayout! - } - } - - const result = retireTerminalSurfaceFromPersistence(session, { - worktreeId: WORKTREE_ID, - parentTabId: 'terminal', - leafId: 'left', - ptyId: 'pty-left' - }) - - expect(result.tabsByWorktree[WORKTREE_ID]).toEqual([ - expect.objectContaining({ id: 'terminal', ptyId: 'pty-right' }) - ]) - expect(result.terminalLayoutsByTabId.terminal).toMatchObject({ - root: { type: 'leaf', leafId: 'right' }, - activeLeafId: 'right', - ptyIdsByLeafId: { right: 'pty-right' } - }) - }) -}) diff --git a/src/main/runtime/terminal/surface/pty-pane-copies-untouched-main.test.ts b/src/main/runtime/terminal/surface/pty-pane-copies-untouched-main.test.ts deleted file mode 100644 index a17a149a527..00000000000 --- a/src/main/runtime/terminal/surface/pty-pane-copies-untouched-main.test.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { describe, expect, it, vi } from 'vitest' -import { - HEADLESS_LEAF_ID, - TEST_WORKTREE_ID -} from '../../orca-runtime-tests/orca-runtime-test-fixtures.spec' - -import type { RuntimePtyWorktreeRecord } from '../records/runtime-terminal-state-records' -import { SURFACE_TAB_ID, surfaceRuntime } from './terminal-surface-replies.test-fixture' - -vi.mock('../../../../shared/layout-owner-switch', () => ({ ORCA_LAYOUT_OWNER_IS_MAIN: true })) - -const COPIES = ['tabId', 'paneKey', 'surfaceRecordedAtGraphSequence'] as const - -/** Every touch of a record's pane copies, which a layout-owner build must never make (L80). */ -function trapPaneCopies(ptysById: Map): string[] { - const touched: string[] = [] - const trap = (record: RuntimePtyWorktreeRecord): void => { - for (const field of COPIES) { - expect(Object.hasOwn(record, field)).toBe(false) - Object.defineProperty(record, field, { - configurable: true, - get() { - touched.push(`read ${record.ptyId}.${field}`) - throw new Error(`pane copy read: ${field}`) - }, - set() { - touched.push(`write ${record.ptyId}.${field}`) - throw new Error(`pane copy write: ${field}`) - } - }) - } - } - for (const record of ptysById.values()) { - trap(record) - } - // A record made later (a spawn) is trapped as it is stored. - const set = ptysById.set.bind(ptysById) - ptysById.set = (ptyId, record) => { - const stored = set(ptyId, record) - trap(record) - return stored - } - return touched -} - -describe('PTY record pane copies (switch on)', () => { - it('are not needed with no window: main answers before any graph is installed', async () => { - const { runtime } = surfaceRuntime() - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: ptysById is the runtime's PTY registry; the test traps the records it keeps. - const { ptysById } = runtime as unknown as { ptysById: Map } - const touched = trapPaneCopies(ptysById) - const published = vi.spyOn(runtime, 'syncWindowGraph') - - expect(runtime['paneOfPty'](ptysById.get('pty-a')!)).toMatchObject({ - tabId: SURFACE_TAB_ID, - paneKey: `${SURFACE_TAB_ID}:${HEADLESS_LEAF_ID}` - }) - const listed = await runtime.listTerminals(`id:${TEST_WORKTREE_ID}`) - expect( - listed.terminals.map(({ tabId, ptyId, orphaned }) => ({ tabId, ptyId, orphaned })) - ).toEqual([ - { tabId: SURFACE_TAB_ID, ptyId: 'pty-a', orphaned: false }, - { tabId: SURFACE_TAB_ID, ptyId: 'pty-b', orphaned: false } - ]) - for (const terminal of listed.terminals) { - await expect(runtime.showTerminal(terminal.handle)).resolves.toMatchObject({ - tabId: SURFACE_TAB_ID - }) - } - runtime.registerPty('pty-c', TEST_WORKTREE_ID) - // Answering is not publishing: no graph is installed until a window or the host sends one. - expect(published).not.toHaveBeenCalled() - expect(touched).toEqual([]) - }) -}) diff --git a/src/main/runtime/terminal/surface/terminal-surface-close.test.ts b/src/main/runtime/terminal/surface/terminal-surface-close.test.ts deleted file mode 100644 index 49dd0d0122a..00000000000 --- a/src/main/runtime/terminal/surface/terminal-surface-close.test.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { describe, expect, it } from 'vitest' -import type { TerminalLayoutSnapshot } from '../../../../shared/workspace-layout/terminal-tab-types' -import { - resolveTerminalCloseTarget, - type TerminalCloseLayoutCopies -} from './terminal-surface-close' - -const PANE = { kind: 'pane', tabId: 'tab-1', leafId: 'leaf-a' } as const - -function layout(...leafIds: string[]): TerminalLayoutSnapshot { - const [first, second] = leafIds - return { - root: - first === undefined - ? null - : second === undefined - ? { type: 'leaf', leafId: first } - : { - type: 'split', - direction: 'horizontal', - first: { type: 'leaf', leafId: first }, - second: { type: 'leaf', leafId: second } - }, - activeLeafId: first ?? null, - expandedLeafId: null - } -} - -function copies(overrides: Partial): TerminalCloseLayoutCopies { - return { - rendererListsTab: false, - snapshotRows: [], - graphLeafIds: [], - sessionLayout: undefined, - ...overrides - } -} - -describe('resolveTerminalCloseTarget', () => { - it('never widens a pane close when the owner copy records no panes', () => { - // A renderer tab registered before its panes, with nothing published for it yet. - expect(resolveTerminalCloseTarget(PANE, copies({ rendererListsTab: true }))).toBe('absent') - // A main-owned tab whose layout was saved before its pane mounted, and no other copy. - expect(resolveTerminalCloseTarget(PANE, copies({ sessionLayout: layout() }))).toBe('absent') - }) - - it('reads past an empty copy to one that records the split', () => { - expect( - resolveTerminalCloseTarget( - PANE, - copies({ - sessionLayout: layout(), - snapshotRows: [{ leafId: 'leaf-a', parentLayout: layout('leaf-a', 'leaf-b') }] - }) - ) - ).toBe('pane') - }) - - it('treats a pane the owner copy lacks as absent', () => { - expect( - resolveTerminalCloseTarget(PANE, copies({ rendererListsTab: true, graphLeafIds: ['leaf-b'] })) - ).toBe('absent') - }) -}) diff --git a/src/main/runtime/terminal/surface/terminal-surface-replies-main.test.ts b/src/main/runtime/terminal/surface/terminal-surface-replies-main.test.ts index 03e03ed59ba..9c2f19afd32 100644 --- a/src/main/runtime/terminal/surface/terminal-surface-replies-main.test.ts +++ b/src/main/runtime/terminal/surface/terminal-surface-replies-main.test.ts @@ -1,4 +1,4 @@ -import path from 'node:path' +import 'node:path' import { describe, expect, it, vi } from 'vitest' import { @@ -10,17 +10,11 @@ import { import { SURFACE_PTYS, SURFACE_TAB_ID, - captureSurfaceReplies, - mountedSurfaceGraph, - recordedSurfaceReplies, surfaceRuntime } from './terminal-surface-replies.test-fixture' vi.mock('../../../../shared/layout-owner-switch', () => ({ ORCA_LAYOUT_OWNER_IS_MAIN: true })) -const golden = (name: string): string => - recordedSurfaceReplies(path.join(__dirname, '__fixtures__', name)) - const MOUNTED = Object.keys(SURFACE_PTYS).map((leafId, index) => ({ tabId: SURFACE_TAB_ID, leafId, @@ -29,19 +23,6 @@ const MOUNTED = Object.keys(SURFACE_PTYS).map((leafId, index) => ({ })) describe('terminal replies from main (switch on)', () => { - it('match the window-owned build when the window sends only what it mounts', async () => { - const { runtime } = surfaceRuntime() - runtime.attachWindow(1) - runtime.syncWindowGraph(1, { tabs: [], leaves: [], mountedLeaves: MOUNTED }) - - // Older readers skip the added verdict; every field they read is unchanged. - expect(await captureSurfaceReplies(runtime, { ignore: ['verdict'] })).toBe( - golden('terminal-surface-replies-desktop.json') - ) - const listed = await runtime.listTerminals(`id:${TEST_WORKTREE_ID}`) - expect(listed.terminals.map((terminal) => terminal.verdict)).toEqual(['live', 'live']) - }) - it('keep answering after the window closes', async () => { const { runtime } = surfaceRuntime() runtime.attachWindow(1) @@ -70,49 +51,6 @@ describe('terminal replies from main (switch on)', () => { expect(shown).toMatchObject({ tabId: SURFACE_TAB_ID, ptyId: 'pty-a', paneRuntimeId: -1 }) }) - it('list a pane split into the layout before any window shows it', async () => { - const { runtime, store, setSession } = surfaceRuntime() - runtime.attachWindow(1) - runtime.syncWindowGraph(1, { tabs: [], leaves: [], mountedLeaves: MOUNTED }) - const session = store.getSession() - const layout = session.terminalLayoutsByTabId[SURFACE_TAB_ID]! - setSession({ - ...session, - terminalLayoutsByTabId: { - [SURFACE_TAB_ID]: { - ...layout, - root: { - type: 'split', - direction: 'vertical', - first: layout.root!, - second: { type: 'leaf', leafId: THIRD_LEAF_ID } - }, - ptyIdsByLeafId: { ...layout.ptyIdsByLeafId, [THIRD_LEAF_ID]: 'pty-c' } - } - } - }) - runtime.registerPty('pty-c', TEST_WORKTREE_ID) - await Promise.resolve() - - const listed = await runtime.listTerminals(`id:${TEST_WORKTREE_ID}`) - expect(listed.terminals.map((terminal) => terminal.ptyId)).toEqual(['pty-a', 'pty-b', 'pty-c']) - }) - - it('close a tab by a pane handle in main, with no session row from the window', async () => { - const { runtime, store, killed } = surfaceRuntime() - runtime.attachWindow(1) - runtime.syncWindowGraph(1, { tabs: [], leaves: [], mountedLeaves: MOUNTED }) - const listed = await runtime.listTerminals(`id:${TEST_WORKTREE_ID}`) - const second = listed.terminals.find((terminal) => terminal.ptyId === 'pty-b')! - - await expect(runtime.closeTerminalTab(second.handle)).resolves.toMatchObject({ - tabId: SURFACE_TAB_ID, - closeMode: 'tab' - }) - expect(store.getSession().tabsByWorktree[TEST_WORKTREE_ID] ?? []).toEqual([]) - expect(killed.toSorted()).toEqual(['pty-a', 'pty-b']) - }) - it("close a tab by an unbound pane's handle, stopping the tab's other terminals", async () => { const { runtime, store, setSession, killed } = surfaceRuntime() runtime.attachWindow(1) @@ -156,32 +94,4 @@ describe('terminal replies from main (switch on)', () => { expect(layout?.root).toEqual({ type: 'leaf', leafId: HEADLESS_LEAF_ID }) expect(killed).toEqual(['pty-b']) }) - - // Main-owned liveness comes from host inventory, so graph sync must not reconnect disconnected records. - it("a graph sync leaves a disconnected terminal of main's pane disconnected, with no reinstall", async () => { - const { runtime } = surfaceRuntime() - runtime.attachWindow(1) - runtime.syncWindowGraph(1, mountedSurfaceGraph()) - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: ptysById is the runtime's PTY registry; the test edits the record it keeps. - const { ptysById } = runtime as unknown as { ptysById: Map } - ptysById.get('pty-a')!.connected = false - const installs: { depth: number; connected: boolean | undefined }[] = [] - let depth = 0 - const sync = runtime.syncWindowGraph.bind(runtime) - vi.spyOn(runtime, 'syncWindowGraph').mockImplementation((windowId, graph) => { - installs.push({ depth, connected: ptysById.get('pty-a')?.connected }) - depth += 1 - try { - return sync(windowId, graph) - } finally { - depth -= 1 - } - }) - - runtime.syncWindowGraph(1, mountedSurfaceGraph()) - await Promise.resolve() - - expect(installs).toEqual([{ depth: 0, connected: false }]) - expect(ptysById.get('pty-a')?.connected).toBe(false) - }) }) diff --git a/src/main/runtime/terminal/title/orca-runtime-terminal-saved-name-respawn.test.ts b/src/main/runtime/terminal/title/orca-runtime-terminal-saved-name-respawn.test.ts deleted file mode 100644 index 411b679dce4..00000000000 --- a/src/main/runtime/terminal/title/orca-runtime-terminal-saved-name-respawn.test.ts +++ /dev/null @@ -1,122 +0,0 @@ -import '../../orca-runtime-tests/orca-runtime-test-lifecycle.spec' -import { describe, expect, it, vi } from 'vitest' -import '../../orca-runtime-tests/runtime-test-module-registration.spec' -import { OrcaRuntimeService } from '../../orca-runtime' -import { - HEADLESS_LEAF_ID, - TEST_WORKTREE_ID, - makeRuntimeStoreWithWorkspaceSession, - makeWorkspaceSessionWithHeadlessTerminal -} from '../../orca-runtime-tests/orca-runtime-test-fixtures.spec' -import type { RuntimeNotifier } from '../../runtime-notifier-contract' - -// After a restart, a reconnecting client restarts each saved tab's terminal. The mobile row for -// that tab must carry the tab's saved name, not only the new terminal's own title. -function notifier(): RuntimeNotifier { - return { - worktreesChanged() {}, - reposChanged() {}, - activateWorktree() {}, - createTerminal() {}, - splitTerminal() {}, - renameTerminal() {}, - focusTerminal() {}, - closeTerminal() {}, - terminalFitOverrideChanged() {}, - terminalDriverChanged() {} - } -} - -async function phoneSurface(runtime: OrcaRuntimeService) { - const listed = await runtime.listMobileSessionTabs(`id:${TEST_WORKTREE_ID}`) - const surface = listed.tabs.find( - (tab) => tab.type === 'terminal' && tab.parentTabId === 'host-tab' - ) - return surface?.type === 'terminal' ? surface : undefined -} - -function runtimeWithSession(savedName?: string | null): OrcaRuntimeService { - const session = makeWorkspaceSessionWithHeadlessTerminal() - if (savedName !== undefined) { - session.tabsByWorktree[TEST_WORKTREE_ID]![0]!.customTitle = savedName - } - const { runtimeStore } = makeRuntimeStoreWithWorkspaceSession(session) - return new OrcaRuntimeService(runtimeStore) -} - -function controller(prefix: string) { - let spawned = 0 - return { - spawn: vi.fn(async () => ({ id: `${prefix}-${++spawned}` })), - write: () => true, - kill: () => true, - getForegroundProcess: async () => null - } -} - -describe('a saved tab name after its terminal is restarted', () => { - it.each([ - ['with a notifier', true], - ['headless', false] - ])('reaches the phone %s', async (_label, withNotifier) => { - const runtime = runtimeWithSession('Saved name') - if (withNotifier) { - runtime.setNotifier(notifier()) - } - runtime.setPtyController(controller('respawned-pty')) - - await runtime.createTerminal(`id:${TEST_WORKTREE_ID}`, { - tabId: 'host-tab', - leafId: HEADLESS_LEAF_ID, - activate: true - }) - - expect(await phoneSurface(runtime)).toMatchObject({ - title: 'Saved name', - customTitle: 'Saved name' - }) - }) - - // The path a reconnecting client takes after a restart: the host restarts the saved tab's - // terminal for the client by the tab's identity. - it('reaches the phone when a client restarts the saved tab by its identity', async () => { - const runtime = runtimeWithSession('Saved name') - runtime.setNotifier(notifier()) - runtime.setPtyController(controller('client-restarted-pty')) - - await runtime['createRuntimeOwnedMobileSessionTerminal'](TEST_WORKTREE_ID, true, undefined, { - identity: { tabId: 'host-tab', leafId: HEADLESS_LEAF_ID } - }) - - expect(await phoneSurface(runtime)).toMatchObject({ - title: 'Saved name', - customTitle: 'Saved name' - }) - }) - - it('keeps a name cleared on the phone row cleared when the terminal restarts', async () => { - const runtime = runtimeWithSession('Saved name') - runtime.setNotifier(notifier()) - runtime.setPtyController(controller('client-restarted-pty')) - const identity = { tabId: 'host-tab', leafId: HEADLESS_LEAF_ID } - await runtime['createRuntimeOwnedMobileSessionTerminal'](TEST_WORKTREE_ID, true, undefined, { - identity - }) - // A desktop clear reaches the row before its debounced session write drops the old name. - const snapshot = runtime['mobileSessionTabsByWorktree'].get(TEST_WORKTREE_ID)! - runtime['mobileSessionTabsByWorktree'].set(TEST_WORKTREE_ID, { - ...snapshot, - tabs: snapshot.tabs.map((tab) => - tab.type === 'terminal' && tab.parentTabId === 'host-tab' - ? { ...tab, customTitle: null } - : tab - ) - }) - - await runtime['createRuntimeOwnedMobileSessionTerminal'](TEST_WORKTREE_ID, true, undefined, { - identity - }) - - expect((await phoneSurface(runtime))?.customTitle).toBeNull() - }) -}) diff --git a/src/main/runtime/terminal/wait/active-worktree-context-parity.test.ts b/src/main/runtime/terminal/wait/active-worktree-context-parity.test.ts index 20f9a8b7d6d..4e47f707bb3 100644 --- a/src/main/runtime/terminal/wait/active-worktree-context-parity.test.ts +++ b/src/main/runtime/terminal/wait/active-worktree-context-parity.test.ts @@ -217,16 +217,6 @@ describe('active workspace context for a focused desktop-catalog workspace (real } }) - it('answers the real branch and display name for the linked worktree', async () => { - const id = `${REPO_ID}::${linkedPath}` - await expect(runtimeFor(focused(id, 'local')).resolveActiveWorktreeContext()).resolves.toEqual({ - worktreeId: id, - path: linkedPath, - branch: 'refs/heads/feature/parity', - displayName: 'Parity feature' - }) - }) - it('matches the pre-#38653 answer for a focused SSH worktree whose host is not connected', async () => { const id = 'ssh-repo::/home/me/app-feature' for (const displayName of ['', 'Named on the box']) { @@ -238,17 +228,6 @@ describe('active workspace context for a focused desktop-catalog workspace (real } }) - it('still resolves a workspace whose stored focus host is stale (local) as it did before', async () => { - // The focus was stamped local; the repo row now routes to an SSH host (e.g. after a move). - const id = 'moved-repo::/home/me/app' - const rows = sshRows('moved-repo', id, 'Moved main') - const before = await contextBefore(id, rows) - expect(before).toMatchObject({ worktreeId: id, displayName: 'Moved main' }) - await expect( - runtimeFor(focused(id, 'local'), rows).resolveActiveWorktreeContext() - ).resolves.toEqual(before) - }) - it("never answers the local twin's context for a paired-server focus with the same id", async () => { const id = `${REPO_ID}::${linkedPath}` const session = focused(id, 'runtime:env-1') diff --git a/src/main/runtime/terminal/wait/launched-agent-write-guard.test.ts b/src/main/runtime/terminal/wait/launched-agent-write-guard.test.ts index 13cb91175a1..0d80d3cce5a 100644 --- a/src/main/runtime/terminal/wait/launched-agent-write-guard.test.ts +++ b/src/main/runtime/terminal/wait/launched-agent-write-guard.test.ts @@ -25,22 +25,6 @@ function guardHarness(foregrounds: LaunchedAgentForeground[]) { } describe('the check before each write of a launch prompt', () => { - it.each([ - ['a shell turning bracketed paste on', '$ \x1b[?2004h'], - ['a shell integration prompt mark', '\x1b]133;D;1\x07\x1b]133;A\x07$ '], - ['a mark split across two chunks', ['\x1b[?20', '04h']] - ] as const)('reads again after %s, and refuses a shell', async (_label, output) => { - const { runtime, guard, emit } = guardHarness(['agent', 'shell']) - - await guard.beforeWrite('pty-1') - for (const chunk of typeof output === 'string' ? [output] : output) { - emit(chunk) - } - - await expect(guard.beforeWrite('pty-1')).rejects.toThrow('agent_not_in_foreground') - expect(runtime.readLaunchedAgentForeground).toHaveBeenCalledTimes(2) - }) - it('catches a shell that returns while the first read is still running', async () => { const { runtime, guard, emit } = guardHarness([]) vi.mocked(runtime.readLaunchedAgentForeground) @@ -54,20 +38,6 @@ describe('the check before each write of a launch prompt', () => { await expect(guard.beforeWrite('pty-1')).rejects.toThrow('agent_not_in_foreground') }) - - it('stops watching when it refuses and when it is disposed', async () => { - const refused = guardHarness(['shell']) - await expect(refused.guard.beforeWrite('pty-1')).rejects.toThrow('agent_not_in_foreground') - expect(refused.unsubscribe).toHaveBeenCalledTimes(1) - - const cleared = guardHarness(['agent']) - await cleared.guard.beforeWrite('pty-1') - const scheduled = Promise.resolve().then(() => cleared.guard.beforeWrite('pty-1')) - cleared.guard.dispose() - await expect(scheduled).rejects.toThrow('request_aborted') - expect(cleared.runtime.subscribeToTerminalData).toHaveBeenCalledTimes(1) - expect(cleared.unsubscribe).toHaveBeenCalledTimes(1) - }) }) it('invalidates reused and pending foreground proof when the terminal session is replaced', async () => { diff --git a/src/main/runtime/terminal/wait/tui-idle-claude-task-wakeup.test.ts b/src/main/runtime/terminal/wait/tui-idle-claude-task-wakeup.test.ts deleted file mode 100644 index 74ae6bb25a1..00000000000 --- a/src/main/runtime/terminal/wait/tui-idle-claude-task-wakeup.test.ts +++ /dev/null @@ -1,116 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { AGENT_STATUS_STALE_AFTER_MS } from '../../../../shared/agent-status/contracts/agent-status-freshness' -import type { AgentStatusIpcPayload } from '../../../../shared/agent-status/contracts/agent-status-ipc-payload' -import { wslHookRelayConnectionId } from '../../../../shared/wsl-hook-relay-contract' -import { evaluateHookTurn, readTuiIdleHookTurn } from './tui-idle-hook-lane' - -const PANE = 'tab:11111111-1111-4111-8111-111111111111' - -function pendingRow(overrides: Partial = {}): AgentStatusIpcPayload { - const now = Date.now() - return { - paneKey: PANE, - state: 'working', - mainAgent: { state: 'done', stateStartedAt: now }, - prompt: '', - agentType: 'claude', - connectionId: 'host-a', - launchToken: 'launch-a', - claudeTaskWakeupPending: 'notification', - providerSession: { key: 'session_id', id: 'session-a' }, - observation: { - origin: 'hook', - authorityId: 'host-a', - incarnation: 1, - revision: 1, - observedAt: now - }, - receivedAt: now, - stateStartedAt: now, - ...overrides - } -} - -function verdict(row: AgentStatusIpcPayload, blocked = false, titleObservedAtEpochMs?: number) { - return evaluateHookTurn('claude', () => - readTuiIdleHookTurn({ - agent: 'claude', - handles: [], - paneKeys: [PANE], - hookRows: [row], - connectionId: 'host-a', - launchToken: 'launch-a', - titleObservedAtEpochMs, - hasExplicitIdleTitle: titleObservedAtEpochMs !== undefined, - resolveBlockedText: () => (blocked ? 'agent-interactive-prompt' : null) - }) - ) -} - -describe('Claude task wake-up readiness authority', () => { - it('vetoes rest from its own fresh hook without promoting ordinary Claude done to ready', () => { - expect(verdict(pendingRow())).toEqual({ kind: 'working' }) - expect(verdict(pendingRow({ state: 'done', claudeTaskWakeupPending: undefined }))).toBeNull() - expect(verdict(pendingRow({ claudeTaskWakeupPending: undefined }))).toBeNull() - }) - - it('reports an opaque child permission wait during the finishing turn', () => { - expect(verdict(pendingRow({ state: 'waiting' }), true)).toEqual({ - kind: 'blocked', - reason: 'agent-interactive-prompt' - }) - }) - - it.each([ - { connectionId: 'host-b' }, - { launchToken: 'old-launch' }, - { agentType: 'codex' }, - { providerSession: undefined }, - { providerSessionOnly: true }, - { restoredUnconfirmed: true }, - { observation: undefined }, - { paneKey: 'another-pane' } - ])('does not take authority from an unmatched or unverifiable row: %j', (overrides) => { - expect(verdict(pendingRow(overrides))).toBeNull() - }) - - it('allows a finishing turn’s fresh native rest when its Stop was lost, while owing still holds', () => { - const finishing = pendingRow({ claudeTaskWakeupPending: 'finishing-turn', turnStartedAt: 10 }) - expect(verdict(finishing, false, 9)).toEqual({ kind: 'working' }) - expect(verdict(finishing, false, 10)).toEqual({ kind: 'working' }) - expect(verdict(finishing, false, 11)).toBeNull() - expect(verdict(pendingRow(), false, 11)).toEqual({ kind: 'working' }) - const replay = pendingRow({ claudeTaskWakeupPending: 'finishing-turn' }) - expect(verdict(replay)).toEqual({ kind: 'working' }) - expect(verdict(replay, false, replay.receivedAt)).toEqual({ kind: 'working' }) - expect(verdict(replay, false, replay.receivedAt + 1)).toBeNull() - }) - - it('joins native, SSH and WSL execution hosts without accepting another distro', () => { - for (const [connectionId, wslDistro, rowConnectionId, held] of [ - [null, null, null, true], - ['ssh-a', 'Ubuntu', 'ssh-a', true], - [null, 'Ubuntu', wslHookRelayConnectionId('Ubuntu'), true], - [null, 'Ubuntu', wslHookRelayConnectionId('Debian'), false], - ['ssh-a', 'Ubuntu', wslHookRelayConnectionId('Ubuntu'), false] - ] as const) { - const turn = readTuiIdleHookTurn({ - agent: 'claude', - handles: [], - paneKeys: [PANE], - hookRows: [pendingRow({ connectionId: rowConnectionId })], - connectionId, - wslDistro, - launchToken: 'launch-a', - resolveBlockedText: () => null - }) - expect(evaluateHookTurn('claude', () => turn)).toEqual(held ? { kind: 'working' } : null) - } - }) - - it('uses evidence age rather than a reconnect receipt clock', () => { - expect( - verdict(pendingRow({ evidenceObservedAt: Date.now() - AGENT_STATUS_STALE_AFTER_MS - 1 })) - ).toBeNull() - }) -}) diff --git a/src/main/runtime/terminal/wait/tui-idle-codex-plan-menu.test.ts b/src/main/runtime/terminal/wait/tui-idle-codex-plan-menu.test.ts index facbd9e140d..12b478e824b 100644 --- a/src/main/runtime/terminal/wait/tui-idle-codex-plan-menu.test.ts +++ b/src/main/runtime/terminal/wait/tui-idle-codex-plan-menu.test.ts @@ -21,15 +21,6 @@ const PANE_KEY = makePaneKey('tab-1', '11111111-1111-4111-8111-111111111111') const FIXTURE = readRuntimeFixture('codex-0-160-0-plan-implement-menu') const MENU_START = FIXTURE.indexOf('\x1b[30;1H\x1b[2m Worked for') const MENU_END = FIXTURE.indexOf('\x1b[?2026l', FIXTURE.indexOf('esc\x1b[22m', MENU_START)) -// A plan turn whose output quotes the menu as its last lines, with no menu on screen. -const QUOTED_MENU = [ - '• Proposed Plan', - '• The plan menu reads:', - ' Implement this plan?', - ' 3. No, stay in Plan mode', - ' enter select · esc back', - '' -].join('\r\n') const QUIESCENCE_MS = 3000 function doneRow(receivedAt: number): AgentStatusIpcPayload { @@ -116,15 +107,4 @@ describe('Codex plan menu painted after its Stop hook', () => { expect((await waiting).blockedReason).toBe('agent-interactive-prompt') }) - - it('settles on a done that follows output quoting the menu', async () => { - let rows: AgentStatusIpcPayload[] = [] - const { runtime, handle } = await codexPane(() => rows) - paint(runtime, QUOTED_MENU) - await vi.advanceTimersByTimeAsync(100) - rows = [doneRow(Date.now())] - - // Held for the turn's last frame, then settled before the next poll sweep. - expect(await waitOutcome(runtime, handle, 1000)).toBe('ready') - }) }) diff --git a/src/main/runtime/unreadable-secret-store-preservation.win32.test.ts b/src/main/runtime/unreadable-secret-store-preservation.win32.test.ts index 5e3502f41d5..c5ff8ef16fb 100644 --- a/src/main/runtime/unreadable-secret-store-preservation.win32.test.ts +++ b/src/main/runtime/unreadable-secret-store-preservation.win32.test.ts @@ -202,30 +202,4 @@ describeOnWindows('a secure store that exists but cannot be read', () => { expectUntouched(filePath, locked) }) - - /** Refresh failures must not delete unreadable account sessions; only found sessions permit that path. */ - it('does not delete the account session it could not read', async () => { - vi.doMock('electron', () => ({ - safeStorage: { - isEncryptionAvailable: () => true, - encryptString: (value: string) => Buffer.from(value), - decryptString: (buffer: Buffer) => buffer.toString() - } - })) - const { readOrcaCloudSession, getOrcaCloudSessionPath } = - await import('./../orca-profiles/profile-cloud-session-store') - const dir = join(root, 'profiles') - mkdirSync(dir, { recursive: true }) - const filePath = getOrcaCloudSessionPath('profile-1', dir) - mkdirSync(join(filePath, '..'), { recursive: true }) - const original = JSON.stringify({ version: 1, format: 'dev-plaintext-v1', savedAt: 1 }) - writeFileSync(filePath, original) - const locked = makeUnreadable(filePath) - - // The status the delete path keys off: `unreadable`, never `decrypt-failed`. - expect(readOrcaCloudSession('profile-1', dir).status).toBe('unreadable') - - expectUntouched(filePath, locked) - vi.doUnmock('electron') - }) }) diff --git a/src/main/runtime/wait-blocked-check-state.test.ts b/src/main/runtime/wait-blocked-check-state.test.ts deleted file mode 100644 index 711f74bdf16..00000000000 --- a/src/main/runtime/wait-blocked-check-state.test.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { - appendWaitBlockedCarry, - createWaitBlockedAppendedCarry, - readWaitBlockedCarry -} from './wait-blocked-check-state' - -import { MAX_TAIL_CHARS } from './terminal/output/tail/terminal-tail-limits' - -/** The accumulation this replaced, verbatim, as the equivalence oracle. */ -function referenceAppend(previous: string, chunk: string): string { - return previous.length + chunk.length > MAX_TAIL_CHARS - ? `${previous}${chunk}`.slice(-MAX_TAIL_CHARS) - : `${previous}${chunk}` -} - -function mulberry32(seed: number): () => number { - let a = seed >>> 0 - return () => { - a = (a + 0x6d2b79f5) | 0 - let t = Math.imul(a ^ (a >>> 15), 1 | a) - t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t - return ((t ^ (t >>> 14)) >>> 0) / 4294967296 - } -} - -describe('wait-blocked appended carry', () => { - it('is byte-identical to the concat+slice carry across a 1MB flood in one 50ms window', () => { - // A 50ms TUI flood must retain exactly the reference rolling window's bytes. - const carry = createWaitBlockedAppendedCarry() - let reference = '' - const rng = mulberry32(20260902) - let produced = 0 - let chunkIndex = 0 - while (produced < 1024 * 1024) { - const size = 1 + Math.floor(rng() * 8192) - const chunk = `${chunkIndex}:${'█'.repeat(Math.max(0, size - 3))}\n` - chunkIndex += 1 - produced += chunk.length - appendWaitBlockedCarry(carry, chunk) - reference = referenceAppend(reference, chunk) - expect(carry.chars).toBe(reference.length) - } - expect(readWaitBlockedCarry(carry)).toBe(reference) - expect(reference.length).toBe(MAX_TAIL_CHARS) - }) - - it('matches the reference for boundary shapes: empty, exact-cap, and over-cap single chunks', () => { - const cases: string[][] = [ - [], - [''], - ['abc', '', 'def'], - ['x'.repeat(MAX_TAIL_CHARS)], - ['x'.repeat(MAX_TAIL_CHARS), 'y'], - ['a', 'b'.repeat(MAX_TAIL_CHARS + 5)], - ['a'.repeat(MAX_TAIL_CHARS - 1), 'bc'], - ['a'.repeat(10), 'b'.repeat(MAX_TAIL_CHARS - 10)], - ['a'.repeat(10), 'b'.repeat(MAX_TAIL_CHARS - 10), 'c'] - ] - for (const chunks of cases) { - const carry = createWaitBlockedAppendedCarry() - let reference = '' - for (const chunk of chunks) { - appendWaitBlockedCarry(carry, chunk) - reference = referenceAppend(reference, chunk) - } - expect(readWaitBlockedCarry(carry)).toBe(reference) - expect(carry.chars).toBe(reference.length) - } - }) -}) diff --git a/src/main/runtime/worker-terminal-archive-release.test.ts b/src/main/runtime/worker-terminal-archive-release.test.ts index 48d790e1634..e3da6e00f40 100644 --- a/src/main/runtime/worker-terminal-archive-release.test.ts +++ b/src/main/runtime/worker-terminal-archive-release.test.ts @@ -125,86 +125,6 @@ describe('released worker archives preserve the host screen', () => { } }) - it.each([ - { cols: 100, prefix: 89, provider: false }, - { cols: 100, prefix: 97, provider: false }, - { cols: 10, prefix: 7, provider: false }, - { cols: 3, prefix: 0, provider: false }, - { cols: 100, prefix: 89, provider: true }, - { cols: 10, prefix: 7, provider: true } - ])( - 'masks wrapped tokens in stored and post-close output (%j)', - async ({ cols, prefix, provider }) => { - h.setup() - const { dispatchId } = await h.startSettledWorker('failed') - const secret = `dcap_${'s'.repeat(24)}` - const text = `${'x'.repeat(prefix)} ${secret}` - const fixture = await createTerminalArchiveFixture({ - hydrate: false, - cols, - provider, - screenLines: [text, 'Safe diagnostic'] - }) - fixture.runtime.onPtyData( - 'pty-1', - provider ? 'retained history\r\n' : `${text}\r\nSafe diagnostic\r\n`, - Date.now() - ) - const screen = await fixture.runtime.readTerminal(fixture.handle, { screen: true }) - expect(screen.tail.join('')).toContain(secret) - expect(screen.tail).not.toContain(secret) - vi.mocked(h.runtime.readTerminal).mockImplementation((_handle, opts) => - fixture.runtime.readTerminal(fixture.handle, opts) - ) - vi.mocked(h.runtime.closeTerminal).mockImplementation(async (handle) => { - fixture.runtime.onPtyExit('pty-1', 0) - return { handle, tabId: 'tab-worker', ptyKilled: true } - }) - - await expect( - h.call('orchestration.workerRelease', { dispatch: dispatchId }) - ).resolves.toMatchObject({ state: 'released' }) - const stored = h.db.getWorkerTerminalArchive(dispatchId) - if (!stored) { - throw new Error('No stored archive') - } - expect(stored.content).not.toContain(secret) - const content: unknown = JSON.parse(stored.content) - if ( - typeof content !== 'object' || - content === null || - !('lines' in content) || - !Array.isArray(content.lines) - ) { - throw new Error('No stored terminal lines') - } - expect(content.lines.join('')).not.toContain(secret) - expect(content.lines.join('')).toContain('Safe diagnostic') - vi.mocked(h.runtime.readTerminal).mockRejectedValue(new Error('Live output gone')) - const read = await h.call('orchestration.workerRead', { dispatch: dispatchId, limit: 2000 }) - expect(read).toMatchObject({ - archived: true, - terminal: { nextCursor: null }, - warnings: expect.arrayContaining([ - 'Dispatch capability tokens were redacted from terminal output.' - ]) - }) - if ( - typeof read !== 'object' || - read === null || - !('terminal' in read) || - typeof read.terminal !== 'object' || - read.terminal === null || - !('tail' in read.terminal) || - !Array.isArray(read.terminal.tail) - ) { - throw new Error('No archived terminal output') - } - expect(read.terminal.tail.join('')).not.toContain(secret) - expect(read.terminal.tail.join('')).toContain('Safe diagnostic') - } - ) - it.each(['soft wrap', 'multiple lines'] as const)( 'masks a real composer draft after release (%s)', async (layout) => { diff --git a/src/main/runtime/worker-terminal-archive-screen.test.ts b/src/main/runtime/worker-terminal-archive-screen.test.ts index 63977eaeb2a..3d484f3526a 100644 --- a/src/main/runtime/worker-terminal-archive-screen.test.ts +++ b/src/main/runtime/worker-terminal-archive-screen.test.ts @@ -1,8 +1,5 @@ import { afterEach, describe, expect, it, vi } from 'vitest' -import { - ARCHIVE_SCREEN_LINES, - createTerminalArchiveFixture -} from './worker-terminal-archive.test-support' +import { createTerminalArchiveFixture } from './worker-terminal-archive.test-support' import { syncSinglePty } from './orca-runtime-tests/orca-runtime-test-fixtures.spec' afterEach(() => vi.restoreAllMocks()) @@ -26,79 +23,6 @@ describe('worker archive through real terminal read routing', () => { ) }) - it('keeps an ordinary live stream without duplicating an identical screen', async () => { - const fixture = await createTerminalArchiveFixture({ hydrate: false }) - fixture.runtime.onPtyData('pty-1', `${ARCHIVE_SCREEN_LINES.join('\r\n')}\r\n`, Date.now()) - const archive = await fixture.capture() - if (archive.kind !== 'terminal_tail') { - throw new Error('Expected terminal archive') - } - expect(archive.content.lines).toEqual(ARCHIVE_SCREEN_LINES) - expect(archive.content.warnings).toEqual([]) - }) - - it.each(['\r\n'.repeat(7), `host % codex\r\n${'\r\n'.repeat(6)}`])( - 'preserves a populated normal screen alongside a short stream (%j)', - async (stream) => { - const fixture = await createTerminalArchiveFixture() - fixture.runtime.onPtyData('pty-1', stream, Date.now()) - const preview = await fixture.runtime.readTerminal(fixture.handle) - expect(preview.source).toBe('stream') - expect(preview.tail).not.toContain(ARCHIVE_SCREEN_LINES[1]) - - const archive = await fixture.capture() - - expect(archive.kind).toBe('terminal_tail') - expect(archive.status).toBe('captured') - if (archive.kind !== 'terminal_tail') { - throw new Error('Expected terminal archive') - } - expect(archive.content.lines).toEqual(expect.arrayContaining(ARCHIVE_SCREEN_LINES)) - expect(archive.content.warnings.join(' ')).toContain('screen') - if (stream.includes('host % codex')) { - expect(archive.content.lines).toContain('host % codex') - } - } - ) - - it('preserves retained history beyond the preview and the current screen', async () => { - const fixture = await createTerminalArchiveFixture({ provider: true }) - const history = Array.from({ length: 180 }, (_, index) => `history ${index}`) - fixture.runtime.onPtyData('pty-1', `${history.join('\r\n')}\r\n`, Date.now()) - expect((await fixture.runtime.readTerminal(fixture.handle)).tail).not.toContain('history 0') - - const archive = await fixture.capture() - - if (archive.kind !== 'terminal_tail') { - throw new Error('Expected terminal archive') - } - expect(archive.content.lines).toEqual( - expect.arrayContaining([...history, ...ARCHIVE_SCREEN_LINES]) - ) - expect(archive.content.warnings.join(' ')).toContain('screen') - }) - - it('preserves the alternate screen using the same capture path', async () => { - const fixture = await createTerminalArchiveFixture({ alternate: true }) - fixture.runtime.onPtyData('pty-1', '\r\n'.repeat(7), Date.now()) - const archive = await fixture.capture() - if (archive.kind !== 'terminal_tail') { - throw new Error('Expected terminal archive') - } - expect(archive.content.lines).toEqual(expect.arrayContaining(ARCHIVE_SCREEN_LINES)) - }) - - it('allows a genuinely empty terminal with honest unavailable-screen provenance', async () => { - const fixture = await createTerminalArchiveFixture({ screenLines: [], hydrate: false }) - const archive = await fixture.capture() - expect(archive.status).toBe('empty') - if (archive.kind !== 'terminal_tail') { - throw new Error('Expected terminal archive') - } - expect(archive.content.lines.every((line) => line.trim() === '')).toBe(true) - expect(archive.content.warnings.join(' ')).toContain('screen was unavailable') - }) - it.each(['unavailable', 'stale'] as const)( 'preserves useful stream output when the provider screen is %s', async (authority) => { @@ -130,21 +54,6 @@ describe('worker archive through real terminal read routing', () => { } ) - it('preserves and redacts a composer draft from the authoritative screen', async () => { - const fixture = await createTerminalArchiveFixture({ hydrate: false }) - const secret = `dcap_${'d'.repeat(24)}` - fixture.runtime.onPtyData('pty-1', `output\r\n────────\r\n❯ ${secret}`, Date.now()) - - const archive = await fixture.capture() - - if (archive.kind !== 'terminal_tail') { - throw new Error('Expected terminal archive') - } - expect(archive.content.draft).toBe('[dispatch capability redacted]') - expect(JSON.stringify(archive.content)).not.toContain(secret) - expect(archive.content.lines).toContain('output') - }) - it('refuses capture when the terminal handle changes during a host screen read', async () => { const fixture = await createTerminalArchiveFixture({ provider: true }) fixture.runtime.onPtyData('pty-1', 'retained diagnostic\r\n', Date.now()) diff --git a/src/main/runtime/worker-terminal-wrap-provenance.test.ts b/src/main/runtime/worker-terminal-wrap-provenance.test.ts index f623dbf9dd8..d0d716d14c9 100644 --- a/src/main/runtime/worker-terminal-wrap-provenance.test.ts +++ b/src/main/runtime/worker-terminal-wrap-provenance.test.ts @@ -108,23 +108,6 @@ describe('host wrap provenance in worker output', () => { } ) - it('preserves a whitespace token boundary removed from raw display rows', async () => { - const fixture = await createTerminalArchiveFixture({ - provider: true, - cols: 25, - screenLines: [`dcap_sssss${' '.repeat(15)}PermissionDenied`, 'Safe diagnostic'] - }) - fixture.runtime.onPtyData('pty-1', 'useful history\r\n', Date.now()) - const raw = await fixture.runtime.readTerminal(fixture.handle, { screen: true }) - expect(raw.tail).toEqual(['dcap_sssss', 'PermissionDenied', 'Safe diagnostic']) - expect(raw.tailRows?.[1].wrapped).toBe(true) - expect(raw.tailRows?.[0].trailingSpaces).toBe(15) - const archive = await fixture.capture() - expect(archive.content).toMatchObject({ - lines: ['useful history', 'dcap_sssss', 'PermissionDenied', 'Safe diagnostic'] - }) - }) - it.each(['screen' as const, undefined])( 'retains complete-line semantics and coverage limits without wrap metadata (source=%s)', async (source) => { @@ -160,50 +143,6 @@ describe('host wrap provenance in worker output', () => { } ) - it.each([false, true])( - 'keeps hard screen diagnostics in live and archived reads (legacy=%s)', - async (legacy) => { - h.setup() - const { dispatchId } = await h.startSettledWorker('failed') - const fixture = await createTerminalArchiveFixture({ - provider: true, - screenLines: ['dcap_sssss', 'PermissionDenied', 'Safe diagnostic'] - }) - fixture.runtime.onPtyData('pty-1', 'useful history\r\n', Date.now()) - vi.mocked(h.runtime.readTerminal).mockImplementation(async (_handle, opts) => { - const read = await fixture.runtime.readTerminal(fixture.handle, opts) - if (!legacy) { - return read - } - // Older responses carry neither source nor physical wrap provenance. - const { source: _source, tailRows: _tailRows, ...old } = read - return old - }) - const live = await h.call('orchestration.workerRead', { - dispatch: dispatchId, - source: 'terminal', - limit: 2000 - }) - expect(readTail(live)).toContain('useful history') - vi.mocked(h.runtime.closeTerminal).mockImplementation(async (handle) => { - fixture.runtime.onPtyExit('pty-1', 0) - return { handle, tabId: 'tab-worker', ptyKilled: true } - }) - await h.call('orchestration.workerRelease', { dispatch: dispatchId }) - const stored = h.db.getWorkerTerminalArchive(dispatchId) - expect(stored?.content).toContain('PermissionDenied') - vi.mocked(h.runtime.readTerminal).mockRejectedValue(new Error('Live output gone')) - const read = await h.call('orchestration.workerRead', { dispatch: dispatchId, limit: 2000 }) - expect(read).toMatchObject({ archived: true, terminal: { nextCursor: null } }) - expect(readTail(read)).toEqual([ - 'useful history', - 'dcap_sssss', - 'PermissionDenied', - 'Safe diagnostic' - ]) - } - ) - it('keeps hard composer breaks in stored and full archived draft output', async () => { h.setup() const { dispatchId } = await h.startSettledWorker('failed') diff --git a/src/main/runtime/workspace-layout-operation-frames.test.ts b/src/main/runtime/workspace-layout-operation-frames.test.ts index f80376efbbd..204e8e018f9 100644 --- a/src/main/runtime/workspace-layout-operation-frames.test.ts +++ b/src/main/runtime/workspace-layout-operation-frames.test.ts @@ -12,20 +12,13 @@ import type { Store } from '../persistence/loading-store/store' import { WindowSession } from '../persistence/terminal-topology/terminal-topology-window-session-fixture' import { emptyTerminalSessionProfile, - FIXTURE_FOLDER_WORKTREE_ID as FOLDER, FIXTURE_GIT_WORKTREE_ID as GIT, openTopologyStore } from '../persistence/terminal-topology/terminal-topology-profile-fixture' import { applyWindowLayoutCommand } from './terminal/layout/runtime-layout-apply' -import { - layoutFrameModelErrors, - logLayoutFrameModelErrors -} from './workspace-layout-frame-model-check' +import './workspace-layout-frame-model-check' import { installLayoutFrameModelCheck } from './workspace-layout-frame-model-check.test-fixture' -import { - observeWorkspaceLayoutFrames, - type LayoutFrameObservation -} from './workspace-layout-frame-observer' +import './workspace-layout-frame-observer' import { WorkspaceLayoutStream, type LayoutOperationCommit @@ -113,35 +106,6 @@ afterEach(() => { }) describe('one records frame per main operation', () => { - const SPREAD = { [GIT]: 50, [FOLDER]: 49, [SSH_KEY]: 1 } - - it('publishes 100 generated titles in three workspaces and two partitions as one frame', async () => { - const store = await storeWith(SPREAD) - const frames: WorkspaceLayoutStreamEvent[] = [] - const { seq } = streamOver(store).subscribe((frame) => frames.push(frame)) - - await expect(renameTabs(store, titles(SPREAD))).resolves.toEqual({ ok: true, result: {} }) - - expect(frames).toHaveLength(1) - const frame = frames[0] - if (frame?.type !== 'records') { - throw new Error('expected one records frame') - } - expect(frame.seq).toBe(seq + 1) - expect( - frame.workspaces.map(({ key, change, ...records }) => [key, change, Object.keys(records)]) - ).toEqual([ - [GIT, 'records', ['tabs']], - [FOLDER, 'records', ['tabs']], - [SSH_KEY, 'records', ['tabs']] - ]) - expect( - frame.workspaces.flatMap((change) => ('tabs' in change ? change.tabs!.set! : [])) - ).toHaveLength(100) - expect(check.failures).toEqual([]) - expect(check.checked()).toBe(1) - }) - it('publishes a pane bind as one binding record, and a same-id respawn as one more', async () => { const store = await storeWith({ [GIT]: 2 }) const frames: WorkspaceLayoutStreamEvent[] = [] @@ -171,16 +135,6 @@ describe('one records frame per main operation', () => { ]) expect(check.failures).toEqual([]) }) - - it('sends nothing for an operation that changes no record', async () => { - const store = await storeWith({ [GIT]: 1 }) - const frames: WorkspaceLayoutStreamEvent[] = [] - streamOver(store).subscribe((frame) => frames.push(frame)) - - await renameTabs(store, [{ workspace: GIT, tabId: 'missing', title: 'Gone' }]) - - expect(frames).toEqual([]) - }) }) describe('a throwing operation listener', () => { @@ -201,50 +155,6 @@ describe('a throwing operation listener', () => { }) }) -describe("main's model check on every frame", () => { - it('passes the frame main sent and catches a broken copy of it', async () => { - check.uninstall() - const store = await storeWith({ [GIT]: 3 }) - const observed: LayoutFrameObservation[] = [] - observeWorkspaceLayoutFrames((observation) => observed.push(observation)) - streamOver(store).subscribe(() => {}) - - await renameTabs(store, titles({ [GIT]: 3 })) - - expect(observed).toHaveLength(1) - const observation = observed[0]! - expect(layoutFrameModelErrors(observation)).toEqual([]) - const broken = structuredClone(observation.frame) - const change = broken.workspaces[0]! - if (change.change !== 'records') { - throw new Error('expected a records change') - } - change.tabs!.set!.pop() - expect(layoutFrameModelErrors({ ...observation, frame: broken })).toEqual([ - `frame ${broken.seq}: ${GIT} differs from a fresh projection` - ]) - observeWorkspaceLayoutFrames(null) - }) -}) - -describe("the dev build's model check", () => { - it('skips a frame that only changed live titles, and checks a structural one', () => { - const warn = vi.spyOn(console, 'warn').mockImplementation(() => {}) - const fresh = vi.fn(() => new Map()) - const frame = { - type: 'records' as const, - epoch: 'epoch-1', - seq: 1, - workspaces: [{ key: GIT, change: 'removed' as const }] - } - logLayoutFrameModelErrors({ frame, before: new Map(), fresh, titlesOnly: true }) - expect(fresh).not.toHaveBeenCalled() - logLayoutFrameModelErrors({ frame, before: new Map(), fresh, titlesOnly: false }) - expect(fresh).toHaveBeenCalledTimes(1) - warn.mockRestore() - }) -}) - describe('a headless main', () => { it('hears each operation with no layout subscriber, from one projection it shares', async () => { const store = await storeWith({ [GIT]: 2, [SSH_KEY]: 1 }) @@ -271,27 +181,4 @@ describe('a headless main', () => { 'Next' ) }) - - it('sends a window-style and a phone-style subscriber the same frames', async () => { - const store = await storeWith({ [GIT]: 2, [FOLDER]: 1 }) - const stream = streamOver(store) - const window: WorkspaceLayoutStreamEvent[] = [] - const phone: WorkspaceLayoutStreamEvent[] = [] - const windowSub = stream.subscribe((frame) => window.push(frame)) - const phoneSub = stream.subscribe((frame) => phone.push(frame)) - - await renameTabs(store, titles({ [GIT]: 2, [FOLDER]: 1 })) - await store.persistPtyBinding({ - worktreeId: GIT, - tabId: tabIdOf(GIT, 0), - leafId: leafOf(0), - ptyId: 'pty-0' - }) - - expect(window).toHaveLength(2) - expect(phone).toEqual(window) - expect(phoneSub.snapshot).toEqual(windowSub.snapshot) - expect(check.failures).toEqual([]) - expect(check.checked()).toBe(2) - }) }) diff --git a/src/main/runtime/workspace-layout-stream.test.ts b/src/main/runtime/workspace-layout-stream.test.ts index 7960ddbb80f..e09161a412e 100644 --- a/src/main/runtime/workspace-layout-stream.test.ts +++ b/src/main/runtime/workspace-layout-stream.test.ts @@ -3,27 +3,21 @@ import { LOCAL_EXECUTION_HOST_ID, type ExecutionHostId } from '../../shared/exec import type { WorkspaceSessionState } from '../../shared/workspace-session/workspace-session-state-types' import { localDesktopSession, - relaySshSession, - serverRuntimeSession + relaySshSession } from '../../shared/workspace-layout/workspace-layout-profile.test-fixture' import { emptySession, FOLDER_KEY, GIT_KEY, - SERVER_KEY, SSH_KEY } from '../../shared/workspace-layout/workspace-layout-session.test-fixture' -import { loadWorkspaceLayout } from '../../shared/workspace-layout/workspace-layout-load' -import { nameBasedLoadContext } from '../../shared/workspace-layout/workspace-layout-minted-ids' -import { - publishWorkspaceLayout, - type PublishedWorkspaceLayout -} from '../../shared/workspace-layout/workspace-layout-published' +import '../../shared/workspace-layout/workspace-layout-load' +import '../../shared/workspace-layout/workspace-layout-minted-ids' +import '../../shared/workspace-layout/workspace-layout-published' import { WorkspaceLayoutStream } from './terminal/surface/workspace-layout-stream' import { collectLayoutEvents } from './orca-runtime-tests/workspace-layout-stream-events.test-fixture' const SSH_HOST: ExecutionHostId = 'ssh:target-1' -const SERVER_HOST: ExecutionHostId = 'runtime:env-1' function fakeStore(partitions: Map) { const listeners = new Set<(hostId: ExecutionHostId | null) => void>() @@ -54,23 +48,6 @@ function fakeStore(partitions: Map) { } } -/** Full uncached projection is the reference for partition-scoped updates. */ -function fullProjection( - partitions: Map, - homeHostId: (key: string) => ExecutionHostId | null -): [string, PublishedWorkspaceLayout][] { - const projected = new Map() - for (const [hostId, session] of partitions) { - const { layout } = loadWorkspaceLayout(hostId, session, nameBasedLoadContext()) - for (const [key, workspace] of Object.entries(layout.workspaces)) { - if (homeHostId(key) === hostId) { - projected.set(key, publishWorkspaceLayout(workspace, hostId)) - } - } - } - return [...projected] -} - function setup() { const partitions = new Map([ [LOCAL_EXECUTION_HOST_ID, localDesktopSession()], @@ -102,18 +79,6 @@ function subscribeEvents(stream: WorkspaceLayoutStream) { } describe('WorkspaceLayoutStream', () => { - it('does nothing until someone subscribes, and stops when the last one leaves', () => { - const { stream, store, listeners } = setup() - expect(store.onWorkspaceSessionWritten).not.toHaveBeenCalled() - const first = stream.subscribe(() => {}) - const second = stream.subscribe(() => {}) - expect(listeners.size).toBe(1) - first.unsubscribe() - expect(listeners.size).toBe(1) - second.unsubscribe() - expect(listeners.size).toBe(0) - }) - it('snapshots each workspace from its owning partition only', () => { const { stream, homes } = setup() const { snapshot } = stream.subscribe(() => {}) @@ -133,45 +98,6 @@ describe('WorkspaceLayoutStream', () => { expect(strayless.map((entry) => entry.key)).toEqual([GIT_KEY, FOLDER_KEY]) }) - it('publishes a workspace when a write changes it by value, and its removal', async () => { - const { stream, write } = setup() - const { events } = subscribeEvents(stream) - const session = localDesktopSession() - write(LOCAL_EXECUTION_HOST_ID, session) - await flush() - expect(events).toEqual([]) - - session.tabGroups![GIT_KEY]![0]!.tabOrder.reverse() - write(LOCAL_EXECUTION_HOST_ID, session) - write(LOCAL_EXECUTION_HOST_ID, session) - await flush() - expect(events.map((event) => [event.type, event.key])).toEqual([['workspace', GIT_KEY]]) - - const withoutFolder = structuredClone(session) - delete withoutFolder.tabsByWorktree[FOLDER_KEY] - delete withoutFolder.unifiedTabs![FOLDER_KEY] - delete withoutFolder.tabGroups![FOLDER_KEY] - delete withoutFolder.tabGroupLayouts![FOLDER_KEY] - write(LOCAL_EXECUTION_HOST_ID, withoutFolder) - await flush() - expect(events.at(-1)).toEqual({ type: 'removed', key: FOLDER_KEY, seq: events[0]!.seq! + 1 }) - }) - - it("publishes nothing for a pane's grid, which is main's own record", async () => { - const { stream, write } = setup() - const { events } = subscribeEvents(stream) - const session = localDesktopSession() - const [tabId, layout] = Object.entries(session.terminalLayoutsByTabId).find( - ([, candidate]) => candidate.root?.type === 'leaf' - )! - const leafId = layout.root!.type === 'leaf' ? layout.root!.leafId : '' - layout.lastGridsByLeafId = { [leafId]: { cols: 91, rows: 33 } } - write(LOCAL_EXECUTION_HOST_ID, session) - await flush() - expect(session.terminalLayoutsByTabId[tabId]?.lastGridsByLeafId).toBeDefined() - expect(events).toEqual([]) - }) - it('numbers each change frame, so a reply can name the frame holding its write', async () => { const { stream, write } = setup() const { events, subscription: first } = subscribeEvents(stream) @@ -196,23 +122,6 @@ describe('WorkspaceLayoutStream', () => { second.unsubscribe() }) - it('logs a failing listener once and never throws into the write', async () => { - const { stream, write } = setup() - const warn = vi.spyOn(console, 'warn').mockImplementation(() => {}) - stream.subscribe(() => { - throw new Error('listener failed') - }) - const session = localDesktopSession() - session.tabGroups![GIT_KEY]![0]!.tabOrder.reverse() - expect(() => write(LOCAL_EXECUTION_HOST_ID, session)).not.toThrow() - await flush() - session.tabGroups![GIT_KEY]![0]!.tabOrder.reverse() - write(LOCAL_EXECUTION_HOST_ID, structuredClone(session)) - await flush() - expect(warn).toHaveBeenCalledTimes(1) - warn.mockRestore() - }) - it('keeps delivering to every listener when one throws', async () => { const { stream, write } = setup() const warn = vi.spyOn(console, 'warn').mockImplementation(() => {}) @@ -280,58 +189,6 @@ describe('WorkspaceLayoutStream', () => { } }) - it('re-projects only the partition a write names', async () => { - const { stream, store, write, writeUnnamed } = setup() - stream.subscribe(() => {}) - store.getWorkspaceSession.mockClear() - write(SSH_HOST, relaySshSession('target-1')) - await flush() - expect(store.getWorkspaceSession.mock.calls).toEqual([[SSH_HOST]]) - store.getWorkspaceSession.mockClear() - writeUnnamed(SSH_HOST, relaySshSession('target-1')) - await flush() - expect(store.getWorkspaceSession.mock.calls).toEqual([[LOCAL_EXECUTION_HOST_ID], [SSH_HOST]]) - }) - - it('publishes what a full re-projection with the load report would, write after write', async () => { - const { stream, write, writeUnnamed, removePartition, homes, partitions } = setup() - const homeHostId = (key: string) => homes[key] ?? null - const { collector } = subscribeEvents(stream) - const expectFullProjection = async (): Promise => { - await flush() - const expected = fullProjection(partitions, homeHostId) - expect(Object.fromEntries(collector.layouts)).toEqual(Object.fromEntries(expected)) - const late = stream.subscribe(() => {}) - expect(late.snapshot.map((entry) => [entry.key, entry.layout])).toEqual(expected) - late.unsubscribe() - } - await expectFullProjection() - - const local = localDesktopSession() - local.tabGroups![GIT_KEY]![0]!.tabOrder.reverse() - write(LOCAL_EXECUTION_HOST_ID, local) - await expectFullProjection() - - // A named write and an unnamed one in the same task. - const ssh = relaySshSession('target-1') - delete ssh.tabsByWorktree[SSH_KEY] - delete ssh.unifiedTabs![SSH_KEY] - write(SSH_HOST, ssh) - writeUnnamed(LOCAL_EXECUTION_HOST_ID, localDesktopSession()) - await expectFullProjection() - - // A new partition, a home that moves between partitions, then the partition removed. - homes[SERVER_KEY] = SERVER_HOST - write(SERVER_HOST, serverRuntimeSession('env-1')) - await expectFullProjection() - homes[GIT_KEY] = SERVER_HOST - write(SSH_HOST, relaySshSession('target-1')) - await expectFullProjection() - homes[GIT_KEY] = LOCAL_EXECUTION_HOST_ID - removePartition(SERVER_HOST) - await expectFullProjection() - }) - it('re-projects every partition after a failed projection', async () => { const { stream, store, write } = setup() const warn = vi.spyOn(console, 'warn').mockImplementation(() => {}) @@ -351,89 +208,4 @@ describe('WorkspaceLayoutStream', () => { expect(warn).toHaveBeenCalledTimes(1) warn.mockRestore() }) - - describe('live pane titles', () => { - const panesOf = (layout: PublishedWorkspaceLayout) => - layout.tabs.flatMap((tab) => tab.terminal?.panes ?? []) - const ptyIdsOf = (layout: PublishedWorkspaceLayout) => - panesOf(layout).flatMap((pane) => (pane.ptyId ? [pane.ptyId] : [])) - - it('lays each pane terminal title over its pane, and only there', () => { - const { stream, liveTitles } = setup() - const bare = stream.subscribe(() => {}).snapshot - const [first, second] = ptyIdsOf(bare.find((entry) => entry.key === GIT_KEY)!.layout) - liveTitles.set(first, 'claude: working') - liveTitles.set(second, 'vim') - const late = setup() - late.liveTitles.set(first, 'claude: working') - late.liveTitles.set(second, 'vim') - const titled = late.stream.subscribe(() => {}).snapshot - const git = titled.find((entry) => entry.key === GIT_KEY)!.layout - const byPty = new Map(panesOf(git).map((pane) => [pane.ptyId, pane.liveTitle])) - expect(byPty.get(first)).toBe('claude: working') - expect(byPty.get(second)).toBe('vim') - const stripped = titled.map((entry) => ({ - key: entry.key, - layout: JSON.parse( - JSON.stringify(entry.layout, (k, v) => (k === 'liveTitle' ? undefined : v)) - ) - })) - expect(stripped).toEqual(bare) - }) - - it('publishes a title change without reading the Store, one frame per burst', async () => { - const { stream, store, liveTitles, write } = setup() - const { - events, - subscription: { snapshot } - } = subscribeEvents(stream) - const [ptyId] = ptyIdsOf(snapshot.find((entry) => entry.key === GIT_KEY)!.layout) - store.getWorkspaceSession.mockClear() - for (let frame = 0; frame < 50; frame++) { - liveTitles.set(ptyId, `build ${frame}`) - stream.markLiveTitlesDirty() - } - await flush() - expect(store.getWorkspaceSession).not.toHaveBeenCalled() - expect(events).toHaveLength(1) - const event = events[0] - expect(event.type === 'workspace' && event.key).toBe(GIT_KEY) - expect( - event.type === 'workspace' && panesOf(event.layout).find((p) => p.ptyId === ptyId) - ).toMatchObject({ liveTitle: 'build 49' }) - - // An unchanged title sends nothing; a structural write keeps the title on. - stream.markLiveTitlesDirty() - await flush() - expect(events).toHaveLength(1) - const session = localDesktopSession() - session.tabGroups![GIT_KEY]![0]!.tabOrder.reverse() - write(LOCAL_EXECUTION_HOST_ID, session) - await flush() - const rewritten = events.at(-1)! - expect( - rewritten.type === 'workspace' && panesOf(rewritten.layout).find((p) => p.ptyId === ptyId) - ).toMatchObject({ liveTitle: 'build 49' }) - - liveTitles.delete(ptyId) - stream.markLiveTitlesDirty() - await flush() - const cleared = events.at(-1)! - expect(cleared.type === 'workspace' && panesOf(cleared.layout).some((p) => p.liveTitle)).toBe( - false - ) - }) - - it('reads no title while nobody subscribes', async () => { - const { stream, liveTitle } = setup() - stream.markLiveTitlesDirty() - await flush() - expect(liveTitle).not.toHaveBeenCalled() - stream.subscribe(() => {}).unsubscribe() - liveTitle.mockClear() - stream.markLiveTitlesDirty() - await flush() - expect(liveTitle).not.toHaveBeenCalled() - }) - }) }) diff --git a/src/main/runtime/workspace-notification-mute.test.ts b/src/main/runtime/workspace-notification-mute.test.ts deleted file mode 100644 index bca2ae32bd2..00000000000 --- a/src/main/runtime/workspace-notification-mute.test.ts +++ /dev/null @@ -1,242 +0,0 @@ -import { describe, expect, it } from 'vitest' -import type { NotificationSettings } from '../../shared/notification-settings-types' -import type { PersistedState } from '../../shared/persisted-state-types' -import type { Worktree } from '../../shared/worktree/types' -import type { WorktreeMeta } from '../../shared/worktree/meta-types' -import type { WorktreeLineage } from '../../shared/worktree/lineage-types' -import type { FolderWorkspace } from '../../shared/folder-workspaces/folder-workspace-types' -import { getDefaultNotificationSettings } from '../../shared/notification-settings-defaults' -import { normalizeNotificationSettings } from '../persistence/applying-settings/onboarding-normalization' -import { - isWorkspaceHiddenBySidebarFilters, - isWorkspaceNotificationMuted, - type WorkspaceNotificationMuteDeps -} from './push/notifications/workspace-notification-mute' - -const WORKSPACE_ID = 'repo-1::/work/feature' -const SCOPE = WORKSPACE_ID - -function listed(overrides: Partial = {}): Worktree { - return { - id: WORKSPACE_ID, - repoId: 'repo-1', - path: '/work/feature', - head: 'abc1234', - branch: 'refs/heads/feature', - isBare: false, - isMainWorktree: false, - displayName: 'feature', - comment: '', - linkedIssue: null, - linkedPR: null, - linkedLinearIssue: null, - isArchived: false, - isUnread: false, - isPinned: false, - sortOrder: 0, - lastActivityAt: 0, - ...overrides - } -} - -function deps(options: { - ui?: Partial | null - resolved?: Worktree[] | null - meta?: Partial - metaById?: Record> - folders?: FolderWorkspace[] - lineage?: Record> - notifications?: NotificationSettings -}): WorkspaceNotificationMuteDeps { - return { - store: { - getSettings: () => ({ notifications: options.notifications }), - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the check reads only the hide* switches of this partial UI. - getUI: () => (options.ui === null ? undefined : options.ui) as PersistedState['ui'], - getRepo: () => undefined, - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the check reads only provenance fields of this partial meta. - getWorktreeMeta: (id) => (options.metaById?.[id] ?? options.meta) as WorktreeMeta | undefined, - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the check reads only worktreeId and parentWorktreeId. - getAllWorktreeLineage: () => (options.lineage ?? {}) as Record, - getFolderWorkspaces: () => options.folders ?? [] - }, - resolvedWorktrees: options.resolved === undefined ? [listed()] : options.resolved - } -} - -const CLI_META: Partial = { cliProvenance: { kind: 'created-by-cli', createdAt: 0 } } - -describe('isWorkspaceHiddenBySidebarFilters', () => { - it('hides a CLI-created workspace from its provenance even with no Git listing', () => { - expect( - isWorkspaceHiddenBySidebarFilters( - deps({ ui: { hideCliCreatedWorkspaces: true }, resolved: null, meta: CLI_META }), - SCOPE - ) - ).toBe(true) - }) - - it('hides an automation-created, default-branch or detached-HEAD workspace by its switch', () => { - const automation: Partial = { - automationProvenance: { - kind: 'created-by-automation', - automationId: 'a', - automationNameSnapshot: 'a', - automationRunId: 'r', - automationRunTitleSnapshot: 'r', - createdAt: 0, - executionTargetType: 'local', - executionTargetId: 'local', - projectId: 'p' - } - } - expect( - isWorkspaceHiddenBySidebarFilters( - deps({ ui: { hideAutomationGeneratedWorkspaces: true }, meta: automation, resolved: null }), - SCOPE - ) - ).toBe(true) - expect( - isWorkspaceHiddenBySidebarFilters( - deps({ - ui: { hideDefaultBranchWorkspace: true }, - resolved: [listed({ isMainWorktree: true, branch: 'refs/heads/main' })] - }), - SCOPE - ) - ).toBe(true) - expect( - isWorkspaceHiddenBySidebarFilters( - deps({ ui: { hideDetachedHeadWorkspaces: true }, resolved: [listed({ branch: '' })] }), - SCOPE - ) - ).toBe(true) - }) - - it('is not hidden when no switch matches or the switch is off', () => { - const on = { hideCliCreatedWorkspaces: true } - expect(isWorkspaceHiddenBySidebarFilters(deps({ ui: on }), SCOPE)).toBe(false) - expect(isWorkspaceHiddenBySidebarFilters(deps({ ui: {}, meta: CLI_META }), SCOPE)).toBe(false) - }) - - it('fails open without persisted UI, a workspace record, or for a folder or shown lineage parent', () => { - const on = { hideCliCreatedWorkspaces: true } - expect(isWorkspaceHiddenBySidebarFilters(deps({ ui: null, meta: CLI_META }), SCOPE)).toBe(false) - expect(isWorkspaceHiddenBySidebarFilters(deps({ ui: on, resolved: null }), SCOPE)).toBe(false) - expect( - isWorkspaceHiddenBySidebarFilters(deps({ ui: on, meta: CLI_META }), 'folder:missing') - ).toBe(false) - expect( - isWorkspaceHiddenBySidebarFilters( - deps({ - ui: on, - resolved: null, - meta: CLI_META, - metaById: { child: {} }, - lineage: { child: { worktreeId: 'child', parentWorktreeId: WORKSPACE_ID } } - }), - SCOPE - ) - ).toBe(false) - }) - - it('hides a lineage parent whose children are all hidden too', () => { - expect( - isWorkspaceHiddenBySidebarFilters( - deps({ - ui: { hideCliCreatedWorkspaces: true }, - resolved: null, - meta: CLI_META, - lineage: { child: { worktreeId: 'child', parentWorktreeId: WORKSPACE_ID } } - }), - SCOPE - ) - ).toBe(true) - }) - - it('hides another client’s worktree or folder workspace under Hide other-client workspaces', () => { - const fromPhone = { kind: 'paired-device', deviceId: 'phone' } as const - const hide = { hideWorkspacesFromOtherDevices: true } - expect( - isWorkspaceHiddenBySidebarFilters( - deps({ ui: hide, resolved: [listed({ creatorProvenance: fromPhone })] }), - SCOPE - ) - ).toBe(true) - expect( - isWorkspaceHiddenBySidebarFilters( - deps({ ui: hide, meta: { creatorProvenance: { kind: 'host' } } }), - SCOPE - ) - ).toBe(false) - const folder: FolderWorkspace = { - id: 'folder-1', - projectGroupId: 'g1', - name: 'notes', - folderPath: '/notes', - creatorProvenance: fromPhone, - linkedTask: null, - comment: '', - isArchived: false, - isUnread: false, - isPinned: false, - sortOrder: 0, - lastActivityAt: 0, - createdAt: 0, - updatedAt: 0 - } - const folderScope = 'folder:folder-1' - expect( - isWorkspaceHiddenBySidebarFilters(deps({ ui: hide, folders: [folder] }), folderScope) - ).toBe(true) - expect( - isWorkspaceHiddenBySidebarFilters( - deps({ ui: { hideCliCreatedWorkspaces: true }, folders: [folder] }), - folderScope - ) - ).toBe(false) - }) -}) - -describe('isWorkspaceNotificationMuted', () => { - const hidden = { ui: { hideCliCreatedWorkspaces: true }, resolved: null, meta: CLI_META } - const notify = (notifyHiddenWorkspaces: boolean) => ({ - ...getDefaultNotificationSettings(), - notifyHiddenWorkspaces - }) - - it('mutes a hidden workspace only while "Notify About Hidden Workspaces" is off', () => { - expect( - isWorkspaceNotificationMuted(deps({ ...hidden, notifications: notify(false) }), SCOPE) - ).toBe(true) - expect( - isWorkspaceNotificationMuted(deps({ ...hidden, notifications: notify(true) }), SCOPE) - ).toBe(false) - expect(isWorkspaceNotificationMuted(deps({ notifications: notify(false) }), SCOPE)).toBe(false) - }) - - it('reads the saved value as loaded from disk, and the default (off) for a file without it', () => { - const saved = normalizeNotificationSettings({ notifyHiddenWorkspaces: true }) - expect(isWorkspaceNotificationMuted(deps({ ...hidden, notifications: saved }), SCOPE)).toBe( - false - ) - const older = normalizeNotificationSettings({ enabled: true }) - expect(older.notifyHiddenWorkspaces).toBe(false) - expect(isWorkspaceNotificationMuted(deps({ ...hidden, notifications: older }), SCOPE)).toBe( - true - ) - }) - - it('reads the persisted default (off), and fails open with no notification settings', () => { - expect(getDefaultNotificationSettings().notifyHiddenWorkspaces).toBe(false) - expect( - isWorkspaceNotificationMuted( - deps({ ...hidden, notifications: getDefaultNotificationSettings() }), - SCOPE - ) - ).toBe(true) - expect(isWorkspaceNotificationMuted(deps({ ...hidden, notifications: undefined }), SCOPE)).toBe( - false - ) - }) -}) diff --git a/src/main/runtime/workspaces/catalog/external-worktree-paired-client-discovery.integration.test.ts b/src/main/runtime/workspaces/catalog/external-worktree-paired-client-discovery.integration.test.ts index 67203cb4091..7212738ae59 100644 --- a/src/main/runtime/workspaces/catalog/external-worktree-paired-client-discovery.integration.test.ts +++ b/src/main/runtime/workspaces/catalog/external-worktree-paired-client-discovery.integration.test.ts @@ -479,59 +479,4 @@ describe('external worktree discovery for paired clients', () => { runtime.notifyReposChangedForRemoteClients() expect(resultType(await reader.next('collision-events'))).toBe('reposChanged') }) - - it('keeps the shared runtime publication valid without a headed renderer', async () => { - vi.mocked(listWorktrees).mockResolvedValue([initialWorktree]) - vi.mocked(listWorktreesStrict).mockResolvedValue([initialWorktree]) - const runtime = new OrcaRuntimeService(makeStore() as never) - const userDataPath = mkdtempSync(join(tmpdir(), 'o-ewd-h-')) - tempDirs.push(userDataPath) - const server = new OrcaRuntimeRpcServer({ - runtime, - userDataPath, - enableWebSocket: true, - wsPort: 0 - }) - servers.push(server) - await server.start() - const offer = server.createPairingOffer({ - address: '127.0.0.1', - name: 'headless-client', - scope: 'runtime' - }) - if (!offer.available) { - throw new Error('pairing_unavailable') - } - const client = await authenticate(offer.pairingUrl) - const reader = createReader(client) - sessions.push(client) - readers.push(reader) - send(client, { id: 'headless-events', method: 'runtime.clientEvents.subscribe' }) - await reader.next('headless-events', (response) => resultType(response) === 'ready') - send(client, { - id: 'headless-initial-catalog', - method: 'worktree.list', - params: { repo: REPO_ID, limit: 100 } - }) - expect(catalogPaths(await reader.next('headless-initial-catalog'))).toEqual([ - initialWorktreePath - ]) - - vi.mocked(listWorktrees).mockResolvedValue([initialWorktree, externalWorktree]) - vi.mocked(listWorktreesStrict).mockResolvedValue([initialWorktree, externalWorktree]) - runtime.notifyWorktreeCatalogChangedForRemoteClients(REPO_ID) - - await expect( - reader.next('headless-events', (response) => resultType(response) === 'worktreesChanged') - ).resolves.toMatchObject({ result: { repoId: REPO_ID } }) - send(client, { - id: 'headless-refreshed-catalog', - method: 'worktree.list', - params: { repo: REPO_ID, limit: 100 } - }) - expect(catalogPaths(await reader.next('headless-refreshed-catalog'))).toEqual([ - initialWorktreePath, - externalWorktreePath - ]) - }) }) diff --git a/src/main/runtime/workspaces/catalog/orca-runtime-worktree-change-invalidation.test.ts b/src/main/runtime/workspaces/catalog/orca-runtime-worktree-change-invalidation.test.ts deleted file mode 100644 index 87e5c89bda3..00000000000 --- a/src/main/runtime/workspaces/catalog/orca-runtime-worktree-change-invalidation.test.ts +++ /dev/null @@ -1,133 +0,0 @@ -import type * as WorktreeListingModule from '../../../git/worktrees/catalog/worktree-listing' -// Headless hosts never load ipc/worktrees; runtime changes must invalidate both scan generation and cache. -import { beforeEach, describe, expect, it, vi } from 'vitest' -import type { Repo } from '../../../../shared/repo-types' -import type { DetectedWorktreeListResult } from '../../../../shared/worktree/types' - -const electronMocks = vi.hoisted(() => { - const ipcMain = { - on: vi.fn(() => ipcMain), - removeListener: vi.fn(() => ipcMain), - emit: vi.fn(() => true) - } - return { - BrowserWindow: { fromId: vi.fn((): unknown => null) }, - webContents: { fromId: vi.fn((): unknown => null) }, - ipcMain, - app: { getPath: vi.fn(() => '/tmp'), isPackaged: false } - } -}) -vi.mock('../../../git/worktrees/catalog/worktree-listing', async (importOriginal) => { - return { - ...(await importOriginal()), - listWorktreesStrict: listWorktreesStrictMock - } -}) - -vi.mock('electron', () => electronMocks) - -const listWorktreesStrictMock = vi.hoisted(() => vi.fn()) - -vi.mock('./repo-worktree-admin-fingerprint', () => ({ - readRepoWorktreeAdminFingerprint: vi.fn(async () => null) -})) - -import { OrcaRuntimeService } from '../../orca-runtime' -import { runWorktreeChangeInvalidators } from '../../../ipc/worktrees/listing/worktree-change-invalidators' -import { getLocalWorktreeScanGeneration } from '../../../workspaces/catalog/local-worktree-scan-generation' -import { partialRuntimeStore } from '../../orca-runtime-tests/runtime-store-partial-fixture' - -const REPO_ID = 'repo-local' -const REPO_PATH = '/Users/me/dev/app' -const CREATED_PATH = '/Users/me/dev/app-created' - -const repo: Repo = { - id: REPO_ID, - path: REPO_PATH, - displayName: 'app', - badgeColor: 'blue', - addedAt: 1 -} - -function row(path: string) { - return { path, head: 'abc', branch: 'main', isBare: false, isMainWorktree: path === REPO_PATH } -} - -function makeStore() { - return { - getRepo: (id: string) => (id === REPO_ID ? repo : undefined), - getRepos: () => [repo], - getAllWorktreeMeta: () => ({}), - getWorktreeMeta: () => undefined, - setWorktreeMeta: vi.fn(), - removeWorktreeMeta: () => {}, - getAllWorktreeLineage: () => ({}), - getAllWorkspaceLineage: () => ({}), - removeWorktreeLineage: vi.fn(), - removeWorkspaceLineage: vi.fn(), - getSettings: () => ({ - workspaceDir: '/tmp/workspaces', - nestWorkspaces: false, - refreshLocalBaseRefOnWorktreeCreate: false, - branchPrefix: 'none', - branchPrefixCustom: '' - }), - getProjects: () => [] - } -} - -function makeRuntimeService(): OrcaRuntimeService { - return new OrcaRuntimeService(partialRuntimeStore(makeStore())) -} - -function makeRuntime(): () => Promise { - const runtime = makeRuntimeService() - return () => runtime.listDetectedManagedWorktrees(`id:${REPO_ID}`) -} - -describe('runtime worktree change invalidation', () => { - beforeEach(() => { - listWorktreesStrictMock.mockReset() - listWorktreesStrictMock.mockResolvedValue([row(REPO_PATH)]) - }) - - it('bumps that generation from the change event the runtime itself sends, with no window notifier attached', () => { - const runtime = makeRuntimeService() - const before = getLocalWorktreeScanGeneration(REPO_ID) - - runtime.notifyBranchRenamed(REPO_ID) - - expect(getLocalWorktreeScanGeneration(REPO_ID)).not.toBe(before) - }) - - it('clears the runtime scan cache so the next listing scans again', async () => { - const list = makeRuntime() - - await list() - await list() - expect(listWorktreesStrictMock).toHaveBeenCalledTimes(1) - - runWorktreeChangeInvalidators(REPO_ID) - await list() - expect(listWorktreesStrictMock).toHaveBeenCalledTimes(2) - }) - - it('re-lists a scan a worktree change overtook and publishes the rows that include the change', async () => { - const list = makeRuntime() - listWorktreesStrictMock - .mockImplementationOnce(async () => { - runWorktreeChangeInvalidators(REPO_ID) - return [row(REPO_PATH)] - }) - .mockResolvedValueOnce([row(REPO_PATH), row(CREATED_PATH)]) - - const result = await list() - - expect(listWorktreesStrictMock).toHaveBeenCalledTimes(2) - expect(result).toMatchObject({ authoritative: true, source: 'git' }) - expect(result.worktrees.map((worktree) => worktree.path).sort()).toEqual([ - REPO_PATH, - CREATED_PATH - ]) - }) -}) diff --git a/src/main/runtime/workspaces/catalog/repo-worktree-row-resolution.test.ts b/src/main/runtime/workspaces/catalog/repo-worktree-row-resolution.test.ts index 813bd662ee9..7662858b89f 100644 --- a/src/main/runtime/workspaces/catalog/repo-worktree-row-resolution.test.ts +++ b/src/main/runtime/workspaces/catalog/repo-worktree-row-resolution.test.ts @@ -1,17 +1,16 @@ -import { joinResolvedWorktreeScans } from './resolved-worktree-scan-join' -import { getRepoHostIdentityForParts } from '../../../../shared/repo-host-identity' -import { WorkspaceGitIdentity } from '../../sidebar-inputs/workspace-git-identity' +import './resolved-worktree-scan-join' +import '../../../../shared/repo-host-identity' +import '../../sidebar-inputs/workspace-git-identity' import { describe, expect, it, vi } from 'vitest' -import type { ExecutionHostId } from '../../../../shared/execution-host' + import type { Repo } from '../../../../shared/repo-types' import type { WorktreeMeta } from '../../../../shared/worktree/meta-types' -import type { WorktreeLineage } from '../../../../shared/worktree/lineage-types' + import type { GitWorktreeInfo, Worktree } from '../../../../shared/worktree/types' import type { Store } from '../../../persistence/loading-store/store' import { mergeWorktreeMetaForWrite } from '../../../persistence/loading-store/worktree-meta-write-normalization' import { buildDetectedGitWorktrees } from '../../../ipc/worktrees/listing/ssh-worktree-fallback' import { - listStoredWorktreeRowsForRepo, resolveRepoWorktreeRows, resolveScopedWorktreeIdRow, type RepoWorktreeRowDeps @@ -72,80 +71,6 @@ function createDeps(repos: Repo[]): RepoWorktreeRowDeps & { } describe('host-qualified scoped worktree resolution', () => { - it('projects cross-repository lineage by scanning only related repos on the selected host', async () => { - const childRepo = repo('child-repo', '/child-repo', { executionHostId: 'local' }) - const parentRepo = repo('parent-repo', '/parent-repo', { executionHostId: 'local' }) - const remoteParent = repo('parent-repo', '/remote-parent-repo', { - connectionId: 'builder', - executionHostId: 'ssh:builder' - }) - const unrelated = repo('unrelated', '/unrelated', { executionHostId: 'local' }) - const deps = createDeps([childRepo, parentRepo, remoteParent, unrelated]) - const childId = 'child-repo::/child' - const parentId = 'parent-repo::/parent' - deps.metaById[childId] = mergeWorktreeMetaForWrite(undefined, { - instanceId: 'child-instance', - hostId: 'local', - projectId: 'child-project' - }) - deps.metaById[parentId] = mergeWorktreeMetaForWrite(undefined, { - instanceId: 'parent-instance', - hostId: 'local', - projectId: 'parent-project' - }) - const lineage: WorktreeLineage = { - worktreeId: childId, - worktreeInstanceId: 'child-instance', - parentWorktreeId: parentId, - parentWorktreeInstanceId: 'parent-instance', - origin: 'cli', - capture: { source: 'explicit-cli-flag', confidence: 'explicit' }, - createdAt: 1 - } - Object.assign(deps.store, { getAllWorktreeLineage: () => ({ [childId]: lineage }) }) - deps.scanRepo.mockImplementation(async (owner) => ({ - ok: true, - worktrees: [gitWorktree(owner.id === 'child-repo' ? '/child' : '/parent')] - })) - - await expect(resolveScopedWorktreeIdRow(deps, childId, 'local')).resolves.toMatchObject({ - parentWorktreeId: parentId, - lineage - }) - expect(deps.scanRepo.mock.calls.map(([owner]) => owner)).toEqual([childRepo, parentRepo]) - - deps.scanRepo.mockClear() - await expect(resolveScopedWorktreeIdRow(deps, parentId, 'local')).resolves.toMatchObject({ - childWorktreeIds: [childId] - }) - expect(deps.scanRepo.mock.calls.map(([owner]) => owner)).toEqual([parentRepo, childRepo]) - }) - - it('scans only the selected local or SSH owner when repo ids and paths collide', async () => { - const owners = [ - repo('shared', '/local/repo', { executionHostId: 'local' }), - repo('shared', '/remote/repo', { - connectionId: 'builder', - executionHostId: 'ssh:builder' - }), - repo('unrelated', '/unrelated/repo', { connectionId: 'slow-box' }) - ] - const deps = createDeps(owners) - const worktreeId = 'shared::/same/worktree' - - await expect( - resolveScopedWorktreeIdRow(deps, worktreeId, 'ssh:builder') - ).resolves.toMatchObject({ id: worktreeId, hostId: 'ssh:builder' }) - expect(deps.scanRepo.mock.calls.map(([owner]) => owner)).toEqual([owners[1]]) - - deps.scanRepo.mockClear() - await expect(resolveScopedWorktreeIdRow(deps, worktreeId, 'local')).resolves.toMatchObject({ - id: worktreeId, - hostId: 'local' - }) - expect(deps.scanRepo.mock.calls.map(([owner]) => owner)).toEqual([owners[0]]) - }) - it('does not project another host metadata onto a colliding scoped row', async () => { const deps = createDeps([ repo('shared', '/local/repo', { executionHostId: 'local' }), @@ -170,94 +95,6 @@ describe('host-qualified scoped worktree resolution', () => { displayName: 'feature' }) }) - it('uses the host-qualified metadata owner when a legacy row has another host', async () => { - const deps = createDeps([ - repo('shared', '/remote/repo', { - connectionId: 'builder', - executionHostId: 'ssh:builder' - }) - ]) - const worktreeId = 'shared::/same/worktree' - const remoteMeta = { - displayName: 'remote workspace', - hostId: 'ssh:builder', - instanceId: 'remote-instance' - } as unknown as WorktreeMeta - deps.metaById[worktreeId] = { - displayName: 'stale local workspace', - hostId: 'local', - instanceId: 'local-instance' - } as unknown as WorktreeMeta - ;( - deps.store as Store & { - getWorktreeMetaForHost: () => WorktreeMeta - } - ).getWorktreeMetaForHost = () => remoteMeta - - const rows = await resolveRepoWorktreeRows( - deps, - deps.store.getRepos()[0]!, - deps.metaById, - new Map() - ) - - expect(rows[0]).toMatchObject({ - displayName: 'remote workspace', - hostId: 'ssh:builder', - instanceId: 'remote-instance' - }) - }) - - it('restores canonical-only SSH rows without leaking colliding local metadata', () => { - const local = repo('shared', '/local/repo', { executionHostId: 'local' }) - const remote = repo('shared', '/remote/repo', { - connectionId: 'builder', - executionHostId: 'ssh:builder' - }) - const deps = createDeps([local, remote]) - deps.metaById['shared::/local/worktree'] = { - displayName: 'local workspace', - hostId: 'local' - } as WorktreeMeta - ;( - deps.store as Store & { - getAllWorktreeMetaForHost: () => Record - } - ).getAllWorktreeMetaForHost = () => ({ - 'shared::/remote/worktree': { - displayName: 'remote workspace', - hostId: 'ssh:builder' - } as unknown as WorktreeMeta - }) - - expect(listStoredWorktreeRowsForRepo(deps.store, remote, 2)).toEqual([ - expect.objectContaining({ path: '/remote/worktree' }) - ]) - }) - - it.each([ - ['runtime:windows', String.raw`C:\Users\dev\orca worktree`], - ['local', '/mnt/c/Users/dev/orca worktree'] - ] satisfies [ExecutionHostId, string][])( - 'keeps %s path resolution scoped', - async (hostId, path) => { - const target = repo('shared', path, { executionHostId: hostId }) - const deps = createDeps([ - target, - repo('shared', '/other-host/repo', { executionHostId: 'ssh:unrelated' }), - repo('unrelated', '/unrelated/repo') - ]) - deps.scanRepo.mockImplementation(async () => ({ ok: true, worktrees: [gitWorktree(path)] })) - const worktreeId = `shared::${path}` - - await expect(resolveScopedWorktreeIdRow(deps, worktreeId, hostId)).resolves.toMatchObject({ - id: worktreeId, - hostId, - path - }) - expect(deps.scanRepo.mock.calls.map(([owner]) => owner)).toEqual([target]) - } - ) it('resolves only the selected folder-workspace owner without scanning Git providers', async () => { const local = repo('folders', '/local/folders', { @@ -296,65 +133,10 @@ describe('host-qualified scoped worktree resolution', () => { expect(deps.listFolderWorkspaces).toHaveBeenCalledExactlyOnceWith(remote, 2) expect(deps.scanRepo).not.toHaveBeenCalled() }) - - it('does not scan when the requested host does not own the repo id', async () => { - const deps = createDeps([ - repo('shared', '/local/repo', { executionHostId: 'local' }), - repo('unrelated', '/unrelated/repo', { executionHostId: 'runtime:other' }) - ]) - - await expect( - resolveScopedWorktreeIdRow(deps, 'shared::/same/worktree', 'ssh:builder') - ).resolves.toBeNull() - expect(deps.scanRepo).not.toHaveBeenCalled() - }) - - it('reuses fleet owner counts without reloading repos per row', async () => { - const owners = Array.from({ length: 100 }, (_, index) => - repo(`repo-${index}`, `/repos/${index}`, { executionHostId: 'local' }) - ) - const deps = createDeps(owners) - const getRepos = vi.spyOn(deps.store, 'getRepos') - - await Promise.all( - owners.map((owner) => resolveRepoWorktreeRows(deps, owner, deps.metaById, new Map(), 1)) - ) - - expect(getRepos).not.toHaveBeenCalled() - }) }) /** Normalize id-selector paths like path selectors so UI removal resolves the same host-qualified row as CLI (#16243). */ describe('scoped worktree id resolution across path spellings (#16243)', () => { - it.each([ - ['a trailing slash', '/same/worktree', 'shared::/same/worktree/'], - ['a doubled separator', '/same/worktree', 'shared::/same//worktree'], - ['an NFD name', '/same/café', `shared::${'/same/café'.normalize('NFD')}`] - ])('resolves the scanned row when the id carries %s', async (_label, scannedPath, worktreeId) => { - const owner = repo('shared', '/local/repo', { executionHostId: 'local' }) - const deps = createDeps([owner]) - deps.scanRepo.mockImplementation(async () => ({ - ok: true, - worktrees: [gitWorktree(scannedPath)] - })) - - await expect(resolveScopedWorktreeIdRow(deps, worktreeId, 'local')).resolves.toMatchObject({ - id: `shared::${scannedPath}`, - path: scannedPath - }) - }) - - it('still refuses the same path under a different repo id', async () => { - const deps = createDeps([ - repo('shared', '/local/repo', { executionHostId: 'local' }), - repo('unrelated', '/unrelated/repo', { executionHostId: 'local' }) - ]) - - await expect( - resolveScopedWorktreeIdRow(deps, 'unrelated::/same/worktree/', 'local') - ).resolves.toBeNull() - }) - it('refuses rather than guessing when two rows spell one path', async () => { const owner = repo('shared', '/local/repo', { executionHostId: 'local' }) const deps = createDeps([owner]) @@ -368,19 +150,6 @@ describe('scoped worktree id resolution across path spellings (#16243)', () => { ).resolves.toBeNull() }) - it('prefers the exactly matching row over an equivalent spelling', async () => { - const owner = repo('shared', '/local/repo', { executionHostId: 'local' }) - const deps = createDeps([owner]) - deps.scanRepo.mockImplementation(async () => ({ - ok: true, - worktrees: [gitWorktree('/same//worktree'), gitWorktree('/same/worktree')] - })) - - await expect( - resolveScopedWorktreeIdRow(deps, 'shared::/same//worktree', 'local') - ).resolves.toMatchObject({ id: 'shared::/same//worktree' }) - }) - // #15598/#15616: the backslash spelling is what a pre-restart Windows registration recorded. it('resolves a Windows backslash id against the forward-slash spelling git reports', async () => { const path = 'D:/Agentic/game2/battle-core' @@ -392,16 +161,6 @@ describe('scoped worktree id resolution across path spellings (#16243)', () => { resolveScopedWorktreeIdRow(deps, 'shared::D:\\Agentic\\game2\\battle-core', 'runtime:windows') ).resolves.toMatchObject({ id: `shared::${path}`, path }) }) - - it.each([ - ['no repo boundary', 'not-an-id'], - ['an empty path', 'shared::'] - ])('keeps exact matching for a malformed id with %s', async (_label, worktreeId) => { - const deps = createDeps([repo('shared', '/local/repo', { executionHostId: 'local' })]) - - await expect(resolveScopedWorktreeIdRow(deps, worktreeId, 'local')).resolves.toBeNull() - expect(deps.scanRepo).not.toHaveBeenCalled() - }) }) describe('folder-to-Git checkout identity', () => { @@ -442,31 +201,3 @@ describe('folder-to-Git checkout identity', () => { } ) }) - -it('stamps the scan/sidebar join once and keeps the title across detach', () => { - const owner = repo('row-owner', '/repo', { executionHostId: 'local' }) - const deps = createDeps([owner]) - const writes = vi.spyOn(deps.store, 'setWorktreeMeta').mockImplementation(({ id }, updates) => { - const next = mergeWorktreeMetaForWrite(deps.metaById[id], updates) - deps.metaById[id] = next - return next - }) - const key = getRepoHostIdentityForParts(owner.id, 'local') - const scans = new Map([ - [key, { ok: true as const, worktrees: [gitWorktree('/repo/work')], gitRun: {} }] - ]) - const first = joinResolvedWorktreeScans(deps.store, () => deps, scans) - expect(writes).toHaveBeenCalledOnce() - const identity = new WorkspaceGitIdentity() - const rows = (joined: typeof first) => joined.worktrees.map((row) => ({ ...row, gitRun: {} })) - identity.observeScan(rows(first), first.scannedRepoKeys) - scans.set(key, { - ok: true, - worktrees: [{ ...gitWorktree('/repo/work'), branch: '' }], - gitRun: {} - }) - const detached = joinResolvedWorktreeScans(deps.store, () => deps, scans) - identity.observeScan(rows(detached), detached.scannedRepoKeys) - expect(identity.title(detached.worktrees[0]!)).toBe(first.worktrees[0]!.displayName) - expect(writes).toHaveBeenCalledOnce() -}) diff --git a/src/main/runtime/workspaces/catalog/runtime-managed-worktree-queries.test.ts b/src/main/runtime/workspaces/catalog/runtime-managed-worktree-queries.test.ts index 601eb8ac15f..27be7ed781e 100644 --- a/src/main/runtime/workspaces/catalog/runtime-managed-worktree-queries.test.ts +++ b/src/main/runtime/workspaces/catalog/runtime-managed-worktree-queries.test.ts @@ -36,14 +36,12 @@ vi.mock( import { RuntimeManagedWorktreeQueries } from './runtime-managed-worktree-queries' import type { RuntimeStore } from '../../runtime-store-contract' -import { partialRuntimeStore } from '../../orca-runtime-tests/runtime-store-partial-fixture' -import { getRepoHostIdentityForParts } from '../../../../shared/repo-host-identity' +import '../../orca-runtime-tests/runtime-store-partial-fixture' +import '../../../../shared/repo-host-identity' import { bumpLocalWorktreeScanGeneration, - getLocalWorktreeScanGeneration, resetLocalWorktreeScanGenerationsForTests } from '../../../workspaces/catalog/local-worktree-scan-generation' -import type { HostWorktreeTarget } from '../../../../shared/worktree/host-qualified-identity' const settings: ReturnType = { workspaceDir: '/worktrees', @@ -96,101 +94,7 @@ function queries( }) } -describe('RuntimeManagedWorktreeQueries.buildVisibilityMatchers', () => { - it.each([false, true])( - 'keeps host-qualified matcher inputs and defaults (reversed=%s)', - (reversed) => { - const local = folderRepo({ - customWorktreeVisibilitySources: [{ id: 'local-source', rootPath: '/local-source' }] - }) - const remote = folderRepo({ - connectionId: 'builder', - path: '/remote/app', - worktreeBasePath: '.claude/worktrees', - customWorktreeVisibilitySources: [{ id: 'remote-source', rootPath: '/remote-source' }] - }) - const repos = reversed ? [remote, local] : [local, remote] - const worktrees = ['/checkout/first', '/checkout/second'].map((path, index) => ({ - ...mergeWorktree( - local.id, - { path, head: 'abc', branch: 'topic', isBare: false, isMainWorktree: false }, - undefined - ), - hostId: index === 0 ? ('local' as const) : ('ssh:builder' as const) - })) - const defaults = { customSources: [{ id: 'default-source', rootPath: '/default-source' }] } - const catalog = queries( - partialRuntimeStore({ - getRepos: () => [...repos, folderRepo({ id: 'without-checkout' })], - getSettings: () => ({ ...settings, worktreeVisibilityDefaults: defaults }) - }) - ) - const matchers = catalog.buildVisibilityMatchers(worktrees) - expect([...matchers.keys()]).toEqual([ - ...repos.map((repo) => - getRepoHostIdentityForParts(repo.id, repo.connectionId ? 'ssh:builder' : 'local') - ), - getRepoHostIdentityForParts('without-checkout', 'local') - ]) - for (const [index, hostId] of (['local', 'ssh:builder'] as const).entries()) { - const key = getRepoHostIdentityForParts(local.id, hostId) - const matcher = matchers.get(key)! - expect(matcher(`${worktrees[index]!.path}/.claude/worktrees/topic`)).toEqual({ - kind: 'built-in', - id: 'claude' - }) - expect(matcher(`${worktrees[1 - index]!.path}/.claude/worktrees/topic`)).toBeNull() - expect(matcher('/local-source/topic')).toEqual( - index === 0 ? { kind: 'custom', id: 'local-source' } : null - ) - expect(matcher('/remote-source/topic')).toEqual( - index === 1 ? { kind: 'custom', id: 'remote-source' } : null - ) - expect( - matcher(`${index === 0 ? local.path : remote.path}/.claude/worktrees/topic`) - ).toEqual(index === 0 ? { kind: 'built-in', id: 'claude' } : null) - expect(matcher('/default-source/topic')).toEqual({ kind: 'custom', id: 'default-source' }) - expect( - catalog.buildVisibilityMatchers(worktrees, false).get(key)!('/default-source/topic') - ).toBeNull() - expect( - catalog.buildVisibilityMatchers(worktrees, true, settings).get(key)!( - '/default-source/topic' - ) - ).toBeNull() - } - } - ) -}) - describe('RuntimeManagedWorktreeQueries.listDetected', () => { - it('stamps a scanned listing with the generation its scan began at', async () => { - resetLocalWorktreeScanGenerationsForTests() - const repo = folderRepo({ kind: 'git', path: '/source/git-app' }) - const store = { - getRepos: () => [repo], - getRepo: () => repo, - getAllWorktreeMeta: () => ({}), - getWorktreeMeta: () => undefined, - setWorktreeMeta: vi.fn(), - getAllWorktreeLineage: () => ({}), - getSettings: () => settings - } as unknown as RuntimeStore - const atScanStart = getLocalWorktreeScanGeneration(repo.id) - const scanRepo = vi.fn(async () => { - // A mutation lands after the scan settles; the listing still names the catalog it scanned. - return { ok: true as const, worktrees: [] } - }) - - const listing = await queries(store, { scanRepo }).listDetected(repo) - bumpLocalWorktreeScanGeneration(repo.id) - - expect(listing.authoritative).toBe(true) - expect(listing.catalogVersion?.sequence).toBe(atScanStart) - expect(listing.catalogVersion?.epoch).toEqual(expect.any(String)) - expect(getLocalWorktreeScanGeneration(repo.id)).toBeGreaterThan(atScanStart) - }) - it("does not project another host's folder metadata", async () => { const local = folderRepo() const remote = folderRepo({ connectionId: 'build-box', displayName: 'Remote app' }) @@ -215,149 +119,9 @@ describe('RuntimeManagedWorktreeQueries.listDetected', () => { displayName: 'Local app' }) }) - - it('omits host-owned source defaults for clients that do not support them', async () => { - const repo = folderRepo({ path: '/source/app' }) - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: a partial store mock; the code under test reads only the members it defines. - const store = { - getRepos: () => [repo], - getRepo: () => repo, - getAllWorktreeMeta: () => ({}), - getWorktreeMeta: () => undefined, - setWorktreeMeta: vi.fn((_target: HostWorktreeTarget, updates) => metadata(updates)), - getAllWorktreeLineage: () => ({}), - getSettings: () => ({ - ...settings, - worktreeVisibilityDefaults: { - external: 'show' as const, - customSources: [{ id: 'host-source', rootPath: '/source' }], - sourcePreferences: { custom: { 'host-source': 'show' as const } } - } - }) - } as unknown as RuntimeStore - - const current = await queries(store).listDetected(repo, true) - const legacy = await queries(store).listDetected(repo, false) - - expect(current.worktrees[0]).toMatchObject({ - visibilitySource: { kind: 'custom', id: 'host-source' } - }) - expect(legacy.worktrees[0]).not.toHaveProperty('visibilitySource') - }) }) describe('RuntimeManagedWorktreeQueries.list host scope', () => { - it('uses the resolved legacy host matcher and omits ambiguous legacy rows', async () => { - const repo = folderRepo({ - kind: 'git', - connectionId: 'builder', - customWorktreeVisibilitySources: [{ id: 'remote-source', rootPath: '/source' }], - worktreeVisibilitySourcePreferences: { custom: { 'remote-source': 'hide' } } - }) - const git: GitWorktreeInfo = { - path: '/source/topic', - head: '', - branch: 'topic', - isBare: false, - isMainWorktree: false - } - const row: ResolvedWorktree = { - ...mergeWorktree(repo.id, git, undefined), - git, - parentWorktreeId: null, - childWorktreeIds: [], - lineage: null - } - let repos = [repo] - let rows = [row] - const listing = queries( - partialRuntimeStore({ getRepos: () => repos, getSettings: () => settings }), - { - listResolved: async () => rows - } - ) - expect((await listing.list(undefined, 10)).worktrees).toEqual([]) - repos = [repo, folderRepo({ kind: 'git' })] - expect((await listing.list(undefined, 10)).worktrees).toEqual([]) - rows = [ - { ...row, hostId: 'ssh:builder' }, - { ...row, hostId: 'local' } - ] - expect((await listing.list('remote', 10)).worktrees).toEqual([]) - }) - - it.each([5, 50])('bounds listing hydration and ownership work at scale %s', async (size) => { - let idReads = 0 - let hydrated = 0 - const local = folderRepo({ kind: 'git', externalWorktreeVisibility: 'hide' }) - const ssh = folderRepo({ - kind: 'git', - connectionId: 'builder', - externalWorktreeVisibility: 'show' - }) - const repos = [ - local, - ssh, - ...Array.from({ length: size }, (_, index) => folderRepo({ id: `unrelated-${index}` })) - ] - const getRepos = vi.fn(() => - repos.map((repo) => { - hydrated++ - return { - ...repo, - get id() { - idReads++ - return repo.id - } - } - }) - ) - const rows: ResolvedWorktree[] = [local, ssh].flatMap((repo) => - Array.from({ length: size }, (_, index) => { - const git: GitWorktreeInfo = { - path: index === 0 ? repo.path : `/outside/work-${index}`, - head: 'abc', - branch: `branch-${index}`, - isBare: false, - isMainWorktree: index === 0 - } - return { - ...mergeWorktree(repo.id, git, undefined), - hostId: repo.connectionId ? ('ssh:builder' as const) : ('local' as const), - parentWorktreeId: null, - childWorktreeIds: [], - lineage: null, - git - } - }) - ) - const store: RuntimeStore = { - getRepo: vi.fn(), - addRepo: vi.fn(), - updateRepo: vi.fn(), - setWorktreeMeta: vi.fn(), - removeWorktreeMeta: vi.fn(), - getGitHubCache: vi.fn(), - getRepos, - getAllWorktreeMeta: () => ({}), - getWorktreeMeta: () => undefined, - getSettings: () => settings - } - const listing = queries(store, { - listResolved: async () => rows, - listKnownHostIds: () => ['local', 'ssh:builder'] - }) - - const page = await listing.list(undefined, 2) - - expect(page.totalCount).toBe(size + 1) - expect(page.truncated).toBe(true) - expect(page.worktrees.map((row) => row.hostId)).toEqual(['local', 'ssh:builder']) - expect(getRepos.mock.calls.length).toBeLessThanOrEqual(3) - expect(hydrated).toBeLessThanOrEqual(3 * repos.length) - expect(idReads).toBeLessThanOrEqual(20 * (repos.length + rows.length)) - }) - it.each([false, true])( 'preserves host twins, expandable coverage and per-host visibility (reversed=%s)', async (reversed) => { @@ -465,82 +229,6 @@ describe('RuntimeManagedWorktreeQueries.list host scope', () => { hiddenHostIds.clear() } }) - - it('does not report the scoped host as omitted once it contributes rows', async () => { - const store = sshStore() - const result = await queries(store, { - listResolved: async () => - [ - { - id: 'repo-ssh::/home/dev/app', - repoId: 'repo-ssh', - path: '/home/dev/app', - hostId: 'ssh:conn-1' - } - ] as never - }).list('repo-ssh', 50) - - expect(result.hostScope?.hostIds).toEqual(['ssh:conn-1']) - expect(result.hostScope?.omittedHostIds).toEqual([]) - }) - - // The caller scoped the listing, so the hosts they excluded must not come back as gaps. - it('never names a host the caller scoped out', async () => { - const scoped = await queries(sshStore(), { - listKnownHostIds: () => ['local', 'ssh:other', 'runtime:elsewhere'] as never - }).list('repo-ssh', 50) - - expect(scoped.hostScope?.omittedHostIds).toEqual(['ssh:conn-1']) - }) - - // Check both host spellings so scoped coverage names the actual execution host. - it('names the local host for a scoped local repo', async () => { - const repo = folderRepo({ id: 'repo-local', kind: 'git', path: '/workspace/local' }) - const store = { - getRepos: () => [repo], - getRepo: () => repo, - getAllWorktreeMeta: () => ({}), - getWorktreeMeta: () => undefined, - setWorktreeMeta: vi.fn(), - getAllWorktreeLineage: () => ({}), - getSettings: () => settings - } as unknown as RuntimeStore - - const result = await queries(store).list('repo-local', 50) - - expect(result.hostScope?.omittedHostIds).toEqual(['local']) - }) - - it('prefers executionHostId over connectionId for the scoped host', async () => { - const repo = folderRepo({ - id: 'repo-runtime', - kind: 'git', - connectionId: 'conn-legacy', - executionHostId: 'runtime:env-1', - path: '/workspace/runtime' - }) - const store = { - getRepos: () => [repo], - getRepo: () => repo, - getAllWorktreeMeta: () => ({}), - getWorktreeMeta: () => undefined, - setWorktreeMeta: vi.fn(), - getAllWorktreeLineage: () => ({}), - getSettings: () => settings - } as unknown as RuntimeStore - - const result = await queries(store).list('repo-runtime', 50) - - expect(result.hostScope?.omittedHostIds).toEqual(['runtime:env-1']) - }) - - it('still reports every configured host when the listing is unscoped', async () => { - const unscoped = await queries(sshStore(), { - listKnownHostIds: () => ['local', 'ssh:conn-1'] as never - }).list(undefined, 50) - - expect(unscoped.hostScope?.omittedHostIds).toEqual(['local', 'ssh:conn-1']) - }) }) describe('RuntimeManagedWorktreeQueries.listDetected overtaken scan', () => { @@ -601,35 +289,6 @@ describe('RuntimeManagedWorktreeQueries.listDetected overtaken scan', () => { expect(pruneLineageMock.mock.calls[0]?.[2]).toEqual(rows([repo.path, created])) }) - it('prunes metadata against the expectation and generation of the scan it publishes', async () => { - const expectations: unknown[] = [] - const generations: number[] = [] - const store = gitStore({ - pruneSessionlessMissingLocalWorktreeMetadataForRepo: vi.fn(() => []), - getProfileStorageDirectory: () => '/profile', - captureNativeLocalWorktreeMetadataScanExpectation: vi.fn(() => { - const expectation = { repo, metadata: [] } - expectations.push(expectation) - generations.push(getLocalWorktreeScanGeneration(repo.id)) - return expectation - }) - }) - const scanRepo = vi - .fn() - .mockImplementationOnce(overtakenScan([repo.path])) - .mockResolvedValueOnce({ ok: true, worktrees: rows([repo.path, created]) }) - - await queries(store, { scanRepo }).listDetected(repo) - - expect(expectations).toHaveLength(2) - expect(pruneMetadataMock).toHaveBeenCalledTimes(1) - expect(pruneMetadataMock.mock.calls[0]?.[0]).toMatchObject({ - scan: expectations[1], - scanGeneration: generations[1], - gitWorktrees: rows([repo.path, created]) - }) - }) - it('publishes the last scan non-authoritatively once the re-scan bound is spent', async () => { const scanRepo = vi.fn(overtakenScan([repo.path])) @@ -644,16 +303,4 @@ describe('RuntimeManagedWorktreeQueries.listDetected overtaken scan', () => { expect(pruneLineageMock).not.toHaveBeenCalled() expect(pruneMetadataMock).not.toHaveBeenCalled() }) - - it('does not re-run a failed scan the change overtook', async () => { - const scanRepo = vi.fn(async () => { - bumpLocalWorktreeScanGeneration(repo.id) - return { ok: false as const, worktrees: [] } - }) - - const result = await queries(gitStore(), { scanRepo }).listDetected(repo) - - expect(scanRepo).toHaveBeenCalledTimes(1) - expect(result).toMatchObject({ authoritative: false, source: 'metadata-fallback' }) - }) }) diff --git a/src/main/runtime/workspaces/catalog/runtime-resolved-worktree-cache.test.ts b/src/main/runtime/workspaces/catalog/runtime-resolved-worktree-cache.test.ts deleted file mode 100644 index ee32b361c87..00000000000 --- a/src/main/runtime/workspaces/catalog/runtime-resolved-worktree-cache.test.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { RuntimeResolvedWorktreeCache } from './runtime-resolved-worktree-cache' -import type { ResolvedWorktreeSnapshot } from './runtime-resolved-worktree-cache' -import { - getLocalWorktreeScanGeneration, - getWorktreeScanMutationRevision -} from '../../../workspaces/catalog/local-worktree-scan-generation' - -function snapshotOf(ids: string[]): ResolvedWorktreeSnapshot { - return { - worktrees: ids.map((id) => ({ id }) as ResolvedWorktreeSnapshot['worktrees'][number]), - platformByRepoId: new Map() - } -} - -describe('RuntimeResolvedWorktreeCache', () => { - it('recomputes when the repo inventory moved, even well inside the TTL', async () => { - // Repo registration invalidates earlier snapshots: absence there cannot prove workspace not found. - const cache = new RuntimeResolvedWorktreeCache() - const results = [snapshotOf(['repo-1::/a']), snapshotOf(['repo-1::/a', 'repo-2::/b'])] - let computes = 0 - const compute = async (): Promise => results[computes++] - - await cache.getSnapshot(compute, 60_000, 7) - const afterRegistration = await cache.getSnapshot(compute, 60_000, 8) - - expect(computes).toBe(2) - expect(afterRegistration.worktrees.map((worktree) => worktree.id)).toEqual([ - 'repo-1::/a', - 'repo-2::/b' - ]) - }) - - it('does not join an in-flight compute that started under a stale inventory', async () => { - const cache = new RuntimeResolvedWorktreeCache() - const computed: number[] = [] - const compute = async (): Promise => { - computed.push(computed.length) - return snapshotOf([]) - } - - const first = cache.getSnapshot(compute, 60_000, 7) - const second = cache.getSnapshot(compute, 60_000, 8) - await Promise.all([first, second]) - - expect(computed).toHaveLength(2) - }) - - it('keeps a primed snapshot servable when nothing mutated', async () => { - // Headless reattach resolves from a primed cache without Git; non-mutation reads must preserve its freshness. - const cache = new RuntimeResolvedWorktreeCache() - let computes = 0 - const prime = async (): Promise => { - computes += 1 - return snapshotOf(['repo-restore::/tmp/restore-records']) - } - await cache.getSnapshot(prime, 60_000, getWorktreeScanMutationRevision()) - - // A read that mints a scan generation for a repo nothing has scanned yet is not a mutation. - getLocalWorktreeScanGeneration(`repo-never-scanned-${Math.random()}`) - - expect(cache.isFresh(getWorktreeScanMutationRevision())).toBe(true) - const served = await cache.getSnapshot(prime, 60_000, getWorktreeScanMutationRevision()) - expect(computes).toBe(1) - expect(served.worktrees.map((worktree) => worktree.id)).toEqual([ - 'repo-restore::/tmp/restore-records' - ]) - }) -}) diff --git a/src/main/runtime/workspaces/catalog/runtime-visible-catalog.test.ts b/src/main/runtime/workspaces/catalog/runtime-visible-catalog.test.ts deleted file mode 100644 index 98220a761ea..00000000000 --- a/src/main/runtime/workspaces/catalog/runtime-visible-catalog.test.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { describe, expect, it, vi } from 'vitest' -import type { Repo } from '../../../../shared/repo-types' - -const { visibleRepos } = vi.hoisted(() => ({ - visibleRepos: - vi.fn<(store: { getRepos: () => Repo[]; getSshTargets: () => unknown[] }) => Repo[]>() -})) -vi.mock('../../../ssh/orcad-retained-source', () => ({ - visibleRepos, - visibleProjectGroups: vi.fn(() => []), - visibleFolderWorkspaces: vi.fn(() => []) -})) - -const { runtimeHiddenHostIds, runtimeHiddenRepos } = await import('./runtime-visible-catalog') - -const sshTarget = { id: 'target-1', label: 'dev', host: 'dev', port: 22, username: 'dev' } -const repo = (id: string, connectionId?: string): Repo => ({ - id, - ...(connectionId ? { connectionId } : {}), - path: `/src/${id}`, - displayName: id, - badgeColor: '#000', - addedAt: 0 -}) -const storeWith = (repos: Repo[], withTargets: boolean) => ({ - getRepos: () => repos, - ...(withTargets ? { getSshTargets: () => [sshTarget] } : {}) -}) - -describe('runtime visible catalog', () => { - it('hides a moved local row by host while the WSL host row with the same id stays listed', () => { - const local = repo('moved') - const onHost: Repo = { ...repo('moved'), executionHostId: 'runtime:wsl-ubuntu' } - visibleRepos.mockImplementation((store) => store.getRepos().filter((r) => r !== local)) - const hidden = runtimeHiddenRepos(storeWith([local, onHost], true)) - expect(hidden.size).toBe(1) - expect(hidden.hasRepo(local)).toBe(true) - expect(hidden.hasRepo(onHost)).toBe(false) - expect(hidden.hasWorktree({ repoId: 'moved', hostId: 'local' })).toBe(true) - expect(hidden.hasWorktree({ repoId: 'moved', hostId: 'runtime:wsl-ubuntu' })).toBe(false) - }) - - it('keeps a host that still has a visible row', () => { - visibleRepos.mockImplementation((store) => store.getRepos().filter((r) => r.id !== 'a')) - const store = storeWith([repo('a', 'target-2'), repo('b', 'target-2')], true) - expect(runtimeHiddenHostIds(store).size).toBe(0) - }) - - describe('a repo id shared by a visible host and a retained SSH source', () => { - const local = repo('same-project') - const retained = repo('same-project', 'target-1') - const store = storeWith([local, retained], true) - // Same id on both rows: only the SSH row is the retired source. - const hideRetained = (s: { getRepos: () => Repo[] }): Repo[] => - s.getRepos().filter((r) => r.connectionId !== 'target-1') - - it('still names the retired SSH host as hidden', () => { - visibleRepos.mockImplementation(hideRetained) - expect([...runtimeHiddenHostIds(store)]).toEqual(['ssh:target-1']) - }) - - it('hides only the worktrees on the retained host', () => { - visibleRepos.mockImplementation(hideRetained) - const hidden = runtimeHiddenRepos(store) - expect(hidden.hasRepo(retained)).toBe(true) - expect(hidden.hasRepo(local)).toBe(false) - expect(hidden.hasWorktree({ repoId: 'same-project', hostId: 'ssh:target-1' })).toBe(true) - expect(hidden.hasWorktree({ repoId: 'same-project', hostId: 'local' })).toBe(false) - // A legacy worktree with no host cannot be placed on either row, and one row is visible. - expect(hidden.hasWorktree({ repoId: 'same-project' })).toBe(false) - }) - - it('hides a hostless worktree whose every row is hidden', () => { - visibleRepos.mockImplementation(() => []) - expect(runtimeHiddenRepos(store).hasWorktree({ repoId: 'same-project' })).toBe(true) - }) - }) -}) diff --git a/src/main/runtime/workspaces/catalog/runtime-worktree-selection.test.ts b/src/main/runtime/workspaces/catalog/runtime-worktree-selection.test.ts deleted file mode 100644 index f6cffacb176..00000000000 --- a/src/main/runtime/workspaces/catalog/runtime-worktree-selection.test.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { - getRuntimeWorktreeRemovalOptionsKey, - runtimeRepoMatchesExecutionHost -} from './runtime-worktree-selection' - -describe('getRuntimeWorktreeRemovalOptionsKey', () => { - it('separates a waived archive-hook retry from the attempt about to refuse on it (#19334)', () => { - const strict = getRuntimeWorktreeRemovalOptionsKey({ runHooks: true }) - expect( - getRuntimeWorktreeRemovalOptionsKey({ runHooks: true, allowFailedArchiveHook: true }) - ).not.toBe(strict) - }) - - it('keeps every waiver on its own axis, so none of them coalesce', () => { - const keys = [ - {}, - { force: true }, - { deleteBranch: false }, - { deleteBranch: true }, - { runHooks: true }, - { allowUnverifiedPtyStop: true }, - { allowFailedArchiveHook: true } - ].map(getRuntimeWorktreeRemovalOptionsKey) - expect(new Set(keys).size).toBe(keys.length) - }) -}) - -describe('runtimeRepoMatchesExecutionHost', () => { - it('matches an unstamped SSH repo against its own host (#11163)', () => { - // Legacy connectionId must match ssh: requests or repo-add/clone dedupe creates duplicate rows. - expect(runtimeRepoMatchesExecutionHost({ connectionId: 'target-1' }, 'ssh:target-1')).toBe(true) - }) - - it('rejects an unstamped SSH repo against a different SSH host', () => { - expect(runtimeRepoMatchesExecutionHost({ connectionId: 'target-1' }, 'ssh:target-2')).toBe( - false - ) - }) - - it('rejects an unstamped SSH repo against local and runtime hosts', () => { - expect(runtimeRepoMatchesExecutionHost({ connectionId: 'target-1' }, 'local')).toBe(false) - expect(runtimeRepoMatchesExecutionHost({ connectionId: 'target-1' }, 'runtime:env-1')).toBe( - false - ) - }) - - it('keeps a stamped repo bound to the host it names', () => { - expect(runtimeRepoMatchesExecutionHost({ executionHostId: 'runtime:env-1' }, 'local')).toBe( - false - ) - expect( - runtimeRepoMatchesExecutionHost( - { executionHostId: 'local', connectionId: 'target-1' }, - 'local' - ) - ).toBe(true) - }) -}) diff --git a/src/main/runtime/workspaces/catalog/workspace-query-prototype.test.ts b/src/main/runtime/workspaces/catalog/workspace-query-prototype.test.ts deleted file mode 100644 index 45b3d5e7763..00000000000 --- a/src/main/runtime/workspaces/catalog/workspace-query-prototype.test.ts +++ /dev/null @@ -1,548 +0,0 @@ -import { describe, expect, it, vi } from 'vitest' -import '../../orca-runtime-tests/runtime-test-module-registration.spec' -import { OrcaRuntimeService } from '../../orca-runtime' -import { store, TEST_WORKTREE_ID } from '../../orca-runtime-tests/orca-runtime-test-fixtures.spec' -import type { Repo } from '../../../../shared/repo-types' -import type { ExecutionHostId } from '../../../../shared/execution-host' -import type { ResolvedWorktree } from './runtime-worktree-path-identity' -import type { ResolvedWorktreeSnapshot } from './runtime-resolved-worktree-cache' -import type { DetectedWorktreeListResult } from '../../../../shared/worktree/types' -import { tmpdir } from 'node:os' -import { join } from 'node:path' -import { - FLOATING_TERMINAL_WORKTREE_ID, - getDefaultWorkspaceSession -} from '../../../../shared/constants' -import type { BrowserTabInfo } from '../../../../shared/runtime-browser-contracts' -import type { RuntimeMobileSessionTabsSnapshot } from '../../../../shared/runtime-session-contracts' -import type { WorkspaceSessionState } from '../../../../shared/workspace-session/workspace-session-state-types' -import { firstWorkRenameDeps } from '../../../agent-hooks/first-work-rename-runtime' - -const repo: Repo = { - id: 'workspace-query', - path: '/tmp/workspace-query', - displayName: 'Workspace query', - addedAt: 1, - badgeColor: 'blue' -} -const resolved: ResolvedWorktree = { - ...store.getWorktreeMeta(TEST_WORKTREE_ID), - id: TEST_WORKTREE_ID, - repoId: 'repo-1', - path: '/tmp/worktree-a', - branch: 'main', - displayName: 'Workspace', - linkedIssue: null, - comment: '', - parentWorktreeId: null, - childWorktreeIds: [], - lineage: null, - git: { - path: '/tmp/worktree-a', - head: '', - branch: 'main', - isBare: false, - isMainWorktree: false - } -} -const snapshot: ResolvedWorktreeSnapshot = { - worktrees: [resolved], - platformByRepoId: new Map() -} - -describe('workspace query prototype dispatch', () => { - it('re-enters the runtime repo selector', async () => { - const seen: unknown[][] = [] - class Probe extends OrcaRuntimeService { - protected override selectReposBySelector(selector: string): Repo[] { - seen.push([this, selector]) - return [repo] - } - run() { - return this.resolveRepoSelector('id:workspace-query') - } - } - const runtime = new Probe(store) - await expect(runtime.run()).resolves.toBe(repo) - expect(seen).toEqual([[runtime, 'id:workspace-query']]) - }) - - it('re-enters the runtime snapshot from async listing', async () => { - const seen: OrcaRuntimeService[] = [] - class Probe extends OrcaRuntimeService { - protected override async listResolvedWorktreeSnapshot(): Promise { - seen.push(this) - return snapshot - } - run() { - return this.listResolvedWorktrees() - } - } - const runtime = new Probe(store) - await expect(runtime.run()).resolves.toEqual([resolved]) - expect(seen).toEqual([runtime]) - }) - - it('calls the runtime scoped resolver with the original single argument', async () => { - const seen: { receiver: OrcaRuntimeService; args: unknown[] }[] = [] - class Probe extends OrcaRuntimeService { - protected override hasFreshResolvedWorktreeCache(): boolean { - return false - } - protected override async resolveExplicitWorktreeIdScoped( - _worktreeId: string, - _hostId?: ExecutionHostId - ): Promise { - seen.push({ receiver: this, args: Array.from(arguments) }) - return resolved - } - run() { - return this.resolveGitWorktreeSelector(`id:${TEST_WORKTREE_ID}`) - } - } - const runtime = new Probe(store) - await expect(runtime.run()).resolves.toBe(resolved) - expect(seen).toEqual([{ receiver: runtime, args: [TEST_WORKTREE_ID] }]) - }) - - it('keeps disconnected SSH stored rows with a failed scan verdict', async () => { - class Probe extends OrcaRuntimeService { - run(remote: Repo) { - return this.listRepoWorktreesForResolutionUncached(remote, undefined) - } - } - const remote: Repo = { ...repo, executionHostId: 'ssh:workspace-query' } - const id = `${repo.id}::/remote/stored-worktree` - const meta = { - ...store.getWorktreeMeta(TEST_WORKTREE_ID), - hostId: remote.executionHostId, - customName: 'Stored', - comment: '', - issueNumber: null, - issueUrl: null, - issueTitle: null, - mergeStatus: null - } - const runtime = new Probe({ - ...store, - getRepos: () => [remote], - getAllWorktreeMeta: () => ({ [id]: meta }), - getWorktreeMeta: () => meta - }) - const result = await runtime.run(remote) - expect(result.ok).toBe(false) - expect(result.worktrees.map((row) => row.path)).toEqual(['/remote/stored-worktree']) - }) -}) - -describe('workspace-detected-listing-capture characterization', () => { - describe('detected workspace listing during repo resolution', () => { - it('captures the listing method before awaiting resolution and keeps its runtime receiver', async () => { - const pending = Promise.withResolvers() - const events: string[] = [] - const repo: Repo = { - id: 'repo', - path: '/repo', - displayName: 'repo', - badgeColor: 'blue', - addedAt: 0 - } - const result: DetectedWorktreeListResult = { - repoId: repo.id, - authoritative: true, - source: 'git', - worktrees: [] - } - const runtime = new (class extends OrcaRuntimeService { - override resolveRepoSelectorForConnection(selector: string, connectionId?: string | null) { - events.push('resolve') - expect([selector, connectionId]).toEqual(['repo', 'ssh:box']) - return pending.promise - } - })() - const original = vi.fn(function ( - this: OrcaRuntimeService, - resolved: Repo, - sourceDefaultsSupported?: boolean - ) { - events.push('invoke original') - expect(this).toBe(runtime) - expect([resolved, sourceDefaultsSupported]).toEqual([repo, false]) - return Promise.resolve(result) - }) - const replacement = vi.fn(() => Promise.resolve({ ...result, repoId: 'replacement' })) - let listing: ( - repo: Repo, - sourceDefaultsSupported?: boolean - ) => Promise = original - Object.defineProperty(runtime, 'listDetectedWorktreesForResolvedRepo', { - get: () => { - events.push('capture listing') - return listing - } - }) - - const listingPromise = runtime.listDetectedManagedWorktrees('repo', 'ssh:box', false) - listing = replacement - pending.resolve(repo) - - await expect(listingPromise).resolves.toBe(result) - expect(events).toEqual(['capture listing', 'resolve', 'invoke original']) - expect(original).toHaveBeenCalledOnce() - expect(replacement).not.toHaveBeenCalled() - }) - }) - - describe('workspace methods captured before request property reads', () => { - it('captures port probes before reading repoId', async () => { - const runtime = new OrcaRuntimeService() - const events: string[] = [] - const original = vi.fn(async () => []) - const replacement = vi.fn(async () => []) - let probes = original - Object.defineProperty(runtime, 'getWorkspacePortProbes', { - get: () => { - events.push('capture probes') - return probes - } - }) - await runtime.killWorkspacePort({ - pid: 4242, - port: 3020, - get repoId() { - events.push('read repoId') - probes = replacement - return 'repo' - } - }) - expect(events).toEqual(['capture probes', 'read repoId']) - expect(original).toHaveBeenCalledWith('repo') - expect(original.mock.contexts[0]).toBe(runtime) - expect(replacement).not.toHaveBeenCalled() - }) - - it('captures file routing before reading the requested workspace', async () => { - const runtime = new OrcaRuntimeService() - const events: string[] = [] - const original = vi.fn(async () => ({ executionHostId: 'ssh:box' })) - const replacement = vi.fn(async () => ({ executionHostId: 'ssh:box' })) - let routing = original - Object.defineProperty(runtime, 'resolveRuntimeFileTarget', { - get: () => { - events.push('capture routing') - return routing - } - }) - await runtime.killWorkspacePortOnHost({ - executionHostId: 'ssh:box', - pid: 4242, - port: 3020, - get worktree() { - events.push('read worktree') - routing = replacement - return 'folder:remote' - } - }) - expect(events).toEqual(['capture routing', 'read worktree']) - expect(original).toHaveBeenCalledWith('folder:remote') - expect(original.mock.contexts[0]).toBe(runtime) - expect(replacement).not.toHaveBeenCalled() - }) - - it('captures scan invalidation before reading the resolved repo id', async () => { - const runtime = new OrcaRuntimeService() - const events: string[] = [] - const original = vi.fn() - const replacement = vi.fn() - let invalidate = original - const repo: Repo = { - get id() { - events.push('read repo id') - invalidate = replacement - return 'repo' - }, - path: '/repo', - displayName: 'repo', - badgeColor: 'blue', - addedAt: 0 - } - Object.defineProperty(runtime, 'invalidateWorktreeScanCacheForRepo', { - get: () => { - events.push('capture invalidation') - return invalidate - } - }) - Object.defineProperty(runtime, 'resolveRepoSelectorForConnection', { - value: async () => repo - }) - const detected: DetectedWorktreeListResult = { - repoId: 'repo', - authoritative: false, - source: 'metadata-fallback', - worktrees: [] - } - Object.defineProperty(runtime, 'listDetectedWorktreesForResolvedRepo', { - value: async () => detected - }) - - await expect(runtime.teardownMissingManagedWorktreeTerminals('repo', [])).resolves.toEqual({ - stoppedWorktreeIds: [] - }) - expect(events).toEqual(['capture invalidation', 'read repo id']) - expect(original).toHaveBeenCalledWith('repo') - expect(original.mock.contexts[0]).toBe(runtime) - expect(replacement).not.toHaveBeenCalled() - }) - }) -}) - -describe('workspace-file-target-capture characterization', () => { - describe('floating workspace file target resolution', () => { - it('captures the converter before awaiting the path and keeps its runtime receiver', async () => { - const runtime = new OrcaRuntimeService() - const pending = Promise.withResolvers() - const events: string[] = [] - const convert = runtime['floatingWorkspaceToResolvedWorktree'] - const original = vi.fn(function (this: OrcaRuntimeService, path: string) { - events.push('invoke original') - expect(this).toBe(runtime) - return convert.call(this, path) - }) - const replacement = vi.fn((path: string) => convert.call(runtime, path)) - let converter: typeof convert = original - Object.defineProperty(runtime, 'floatingWorkspaceToResolvedWorktree', { - get: () => { - events.push('capture converter') - return converter - } - }) - Object.defineProperty(runtime, 'resolveFloatingWorkspacePath', { - value: () => { - events.push('resolve path') - return pending.promise - } - }) - const result = runtime['resolveRuntimeFileTarget'](FLOATING_TERMINAL_WORKTREE_ID) - converter = replacement - const path = join(tmpdir(), 'floating') - pending.resolve(path) - - await expect(result).resolves.toMatchObject({ - worktree: { path }, - executionHostId: 'local' - }) - expect(events).toEqual(['capture converter', 'resolve path', 'invoke original']) - expect(original).toHaveBeenCalledWith(path) - expect(replacement).not.toHaveBeenCalled() - }) - }) -}) - -describe('workspace-session-read-order characterization', () => { - const WORKTREE_ID = 'repo::/workspace' - - describe('workspace session callable lookup order', () => { - it('keeps the native refusal for a launcher without a workspace selector', async () => { - const runtime = new OrcaRuntimeService(null) - await expect( - runtime.resolveOrchestrationAgentLauncherForTarget('missing-launcher', {}) - ).rejects.toBeInstanceOf(TypeError) - }) - - it('captures the browser reader before evaluating the snapshot workspace getter', () => { - const runtime = new OrcaRuntimeService(null) - const order: string[] = [] - const replacement = vi.fn(() => new Map()) - const original = function ( - this: InstanceType, - worktreeId: string - ) { - expect(this).toBe(runtime) - expect(worktreeId).toBe(WORKTREE_ID) - order.push('original call') - return new Map() - } - Object.defineProperty(runtime, 'getListedBrowserTabsByPageId', { - configurable: true, - get: () => { - order.push('reader lookup') - return original - } - }) - const snapshot: RuntimeMobileSessionTabsSnapshot = { - worktree: WORKTREE_ID, - publicationEpoch: 'seed', - snapshotVersion: 1, - tabs: [], - activeGroupId: null, - activeTabId: null, - activeTabType: null - } - Object.defineProperty(snapshot, 'worktree', { - get: () => { - order.push('argument lookup') - Object.defineProperty(runtime, 'getListedBrowserTabsByPageId', { value: replacement }) - return WORKTREE_ID - } - }) - expect(runtime['collectPublicMobileSessionTabIds'](snapshot)).toEqual(new Set()) - expect(order).toEqual(['reader lookup', 'argument lookup', 'original call']) - expect(replacement).not.toHaveBeenCalled() - }) - - it('captures the title writer before constructing a session with getter-backed fields', () => { - const runtime = new OrcaRuntimeService(null) - const order: string[] = [] - const replacement = vi.fn() - const original = function ( - this: InstanceType, - worktreeId: string, - session: WorkspaceSessionState - ) { - expect(this).toBe(runtime) - expect(worktreeId).toBe(WORKTREE_ID) - expect(session.tabsByWorktree[WORKTREE_ID]?.[0]?.customTitle).toBe('renamed') - order.push('original write') - } - const session = getDefaultWorkspaceSession() - const tabs: WorkspaceSessionState['tabsByWorktree'] = { - [WORKTREE_ID]: [ - { - id: 'tab', - ptyId: null, - worktreeId: WORKTREE_ID, - title: 'Terminal', - customTitle: null, - color: null, - sortOrder: 0, - createdAt: 1 - } - ] - } - let tabReads = 0 - Object.defineProperty(session, 'tabsByWorktree', { - enumerable: true, - get: () => { - tabReads += 1 - if (tabReads === 2) { - order.push('session spread') - Object.defineProperty(runtime, 'setWorkspaceSessionForWorktree', { value: replacement }) - } - return tabs - } - }) - Object.defineProperty(runtime, 'store', { value: { setWorkspaceSession: () => {} } }) - Object.defineProperty(runtime, 'getWorkspaceSessionForWorktree', { value: () => session }) - Object.defineProperty(runtime, 'setWorkspaceSessionForWorktree', { - configurable: true, - get: () => { - order.push('writer lookup') - return original - } - }) - runtime['persistHeadlessTerminalTitle'](WORKTREE_ID, 'tab', 'renamed') - expect(order).toEqual(['writer lookup', 'session spread', 'original write']) - expect(replacement).not.toHaveBeenCalled() - }) - }) -}) - -describe('first-work rename lazy Store capabilities', () => { - type RenameStore = Parameters[0] - type RenameDeps = ReturnType - - function renameStore(): RenameStore { - return { - getSettings: () => ({ - workspaceDir: '/workspace', - nestWorkspaces: false, - refreshLocalBaseRefOnWorktreeCreate: false, - branchPrefix: 'none', - branchPrefixCustom: '' - }), - getRepo: () => undefined, - getRepos: () => [], - getWorktreeMeta: () => undefined, - setWorktreeMeta: vi.fn() - } - } - - const missingCalls: { - name: string - invoke: (deps: RenameDeps) => unknown - provideFolder?: true - }[] = [ - { name: 'display name', invoke: (deps) => deps.getCurrentDisplayName('folder:sample') }, - { name: 'folder path', invoke: (deps) => deps.getFolderWorkspacePath?.('folder:sample') }, - { - name: 'pending rename', - invoke: (deps) => deps.isPendingFirstAgentMessageRename?.('folder:sample') - }, - { name: 'display write', invoke: (deps) => deps.setDisplayName('folder:sample', 'New') }, - { name: 'error read', invoke: (deps) => deps.setRenameError('folder:sample', 'Failure') }, - { - name: 'error write', - provideFolder: true, - invoke: (deps) => deps.setRenameError('folder:sample', 'Failure') - }, - { name: 'tab owner', invoke: (deps) => deps.resolveWorktreeIdForTab('tab') } - ] - - it.each(missingCalls)( - 'preserves the direct missing $name capability failure', - ({ invoke, provideFolder }) => { - const store = renameStore() - if (provideFolder) { - store.getFolderWorkspace = () => undefined - } - const runtime = new OrcaRuntimeService(null) - let notified = false - runtime.notifyFolderWorkspaceChanged = () => { - notified = true - } - const deps = firstWorkRenameDeps(store, runtime) - expect(() => invoke(deps)).toThrow(TypeError) - expect(notified).toBe(false) - expect(deps.renameWorktreeFolder).toBeUndefined() - } - ) - - it('looks up the current writer on each call and retains Store and Root receivers', () => { - const store = renameStore() - const runtime = new OrcaRuntimeService(null) - const order: string[] = [] - runtime.notifyFolderWorkspaceChanged = function () { - expect(this).toBe(runtime) - order.push('notify') - } - const deps = firstWorkRenameDeps(store, runtime) - const write: NonNullable = function ( - this: RenameStore, - id, - updates - ) { - expect(this).toBe(store) - expect(arguments).toHaveLength(2) - expect(id).toBe('sample') - expect(updates).toEqual({ - name: 'New', - pendingFirstAgentMessageRename: false, - firstAgentMessageRenameError: null - }) - order.push('write') - return null - } - Object.defineProperty(store, 'updateFolderWorkspace', { - configurable: true, - get() { - order.push('lookup') - return write - } - }) - deps.setDisplayName('folder:sample', 'New') - expect(order).toEqual(['lookup', 'write', 'notify']) - delete store.updateFolderWorkspace - expect(() => deps.setDisplayName('folder:sample', 'Again')).toThrow(TypeError) - expect(order).toEqual(['lookup', 'write', 'notify']) - }) -}) diff --git a/src/main/runtime/workspaces/catalog/worktree-list-host-scope.test.ts b/src/main/runtime/workspaces/catalog/worktree-list-host-scope.test.ts index a3db387c12a..769ee0e6025 100644 --- a/src/main/runtime/workspaces/catalog/worktree-list-host-scope.test.ts +++ b/src/main/runtime/workspaces/catalog/worktree-list-host-scope.test.ts @@ -93,24 +93,4 @@ describe('worktree.list host coverage under the row cap', () => { expect(remote).toHaveLength(24) expect(result.hostScope).toEqual({ hostIds: ['local', 'ssh:box-1'], omittedHostIds: [] }) }) - - it('names a configured host that contributed no rows at all', async () => { - // Failed repo scans and empty hosts both contribute no rows; scope must expose the uncertainty. - const result = await queries(fleet(3, 0), ['local', 'ssh:box-1', 'runtime:paired']).list( - undefined, - 200 - ) - - expect(result.hostScope).toEqual({ - hostIds: ['local'], - omittedHostIds: ['runtime:paired', 'ssh:box-1'] - }) - }) - - it('does not report configured hosts as omitted from a --repo listing', async () => { - // Caller-scoped listings must omit unrelated hosts. - const result = await queries(fleet(0, 5)).list('id:repo-ssh', 200) - - expect(result.hostScope).toEqual({ hostIds: ['ssh:box-1'], omittedHostIds: [] }) - }) }) diff --git a/src/main/runtime/workspaces/catalog/worktree-scan-admin-fingerprint-gate.test.ts b/src/main/runtime/workspaces/catalog/worktree-scan-admin-fingerprint-gate.test.ts index b7eb87708b6..855e9fb26a3 100644 --- a/src/main/runtime/workspaces/catalog/worktree-scan-admin-fingerprint-gate.test.ts +++ b/src/main/runtime/workspaces/catalog/worktree-scan-admin-fingerprint-gate.test.ts @@ -31,12 +31,9 @@ vi.mock('./repo-worktree-admin-fingerprint', () => ({ })) import { OrcaRuntimeService } from '../../orca-runtime' -import { - WORKTREE_SCAN_ADMIN_FINGERPRINT_TIMEOUT_MS, - WORKTREE_SCAN_ADMIN_RECONCILE_INTERVAL_MS -} from './worktree-scan-admin-budget' -import { RESOLVED_WORKTREE_REPO_TIMEOUT_MS } from './repo-worktree-row-resolution' -import { canonicalWorktreeIdentity } from '../../../../shared/worktree/identity' +import { WORKTREE_SCAN_ADMIN_FINGERPRINT_TIMEOUT_MS } from './worktree-scan-admin-budget' +import './repo-worktree-row-resolution' +import '../../../../shared/worktree/identity' import type { HostWorktreeTarget } from '../../../../shared/worktree/host-qualified-identity' const REPO_ID = 'repo-local' @@ -45,8 +42,6 @@ const WORKTREE_PATH = '/Users/me/dev/app-feature' const WORKTREE_ID = `${REPO_ID}::${WORKTREE_PATH}` const MAIN_WORKTREE_ID = `${REPO_ID}::${REPO_PATH}` const SCAN_TTL_MS = 30_000 -// Slow healthy stat work must permit reuse rather than trigger repeating Git scans. -const SLOW_BUT_HEALTHY_PROBE_MS = 3_000 function makeMeta(overrides: Record = {}) { return { @@ -182,87 +177,6 @@ describe('worktree scan admin-fingerprint gate', () => { readRepoWorktreeAdminFingerprintMock.mockResolvedValue('fp-1') }) - it('skips the Git scan past the TTL while the admin fingerprint is unchanged', async () => { - vi.useFakeTimers() - try { - const { list } = makeRuntime() - - await list() - expect(scanCount()).toBe(1) - - vi.advanceTimersByTime(SCAN_TTL_MS + 1_000) - await list() - expect(scanCount()).toBe(1) - - vi.advanceTimersByTime(1_000) - await list() - expect(scanCount()).toBe(1) - expect(readRepoWorktreeAdminFingerprintMock).toHaveBeenCalledTimes(2) - } finally { - vi.useRealTimers() - } - }) - - it('rescans at the TTL once the admin fingerprint changes', async () => { - vi.useFakeTimers() - try { - const { list } = makeRuntime() - - await list() - expect(scanCount()).toBe(1) - - readRepoWorktreeAdminFingerprintMock.mockResolvedValue('fp-2') - vi.advanceTimersByTime(SCAN_TTL_MS + 1_000) - await list() - expect(scanCount()).toBe(2) - } finally { - vi.useRealTimers() - } - }) - - it('reconciles with a real scan once the bounded interval elapses', async () => { - vi.useFakeTimers() - try { - const { list } = makeRuntime() - - await list() - expect(scanCount()).toBe(1) - - // Keep polling past the TTL with an unchanged fingerprint; only the reconcile deadline rescans. - for ( - let elapsed = 31_000; - elapsed < WORKTREE_SCAN_ADMIN_RECONCILE_INTERVAL_MS; - elapsed += 31_000 - ) { - vi.advanceTimersByTime(31_000) - await list() - } - expect(scanCount()).toBe(1) - - vi.advanceTimersByTime(WORKTREE_SCAN_ADMIN_RECONCILE_INTERVAL_MS) - await list() - expect(scanCount()).toBe(2) - } finally { - vi.useRealTimers() - } - }) - - it('still rescans immediately when an event invalidates the repo', async () => { - vi.useFakeTimers() - try { - const { runtime, list } = makeRuntime() - - await list() - expect(scanCount()).toBe(1) - - runtime.notifyBranchRenamed(REPO_ID) - await list() - expect(scanCount()).toBe(2) - } finally { - vi.useRealTimers() - } - }) - it('resolves a just-created id after invalidation even within both cache TTLs', async () => { const { runtime, list } = makeRuntime() listWorktreesStrictMock.mockResolvedValueOnce([ @@ -279,96 +193,6 @@ describe('worktree scan admin-fingerprint gate', () => { expect(scanCount()).toBe(2) }) - it('scans when the probe cannot describe the repo', async () => { - vi.useFakeTimers() - try { - readRepoWorktreeAdminFingerprintMock.mockResolvedValue(null) - const { list } = makeRuntime() - - await list() - vi.advanceTimersByTime(SCAN_TTL_MS + 1_000) - await list() - - expect(scanCount()).toBe(2) - } finally { - vi.useRealTimers() - } - }) - - it('never probes a repo whose scan TTL already reaches the reconcile interval', async () => { - vi.useFakeTimers() - try { - const { list } = makeRuntime({ repoPath: '/tmp/.codex-tmp/capsule-a' }) - - await list() - vi.advanceTimersByTime(WORKTREE_SCAN_ADMIN_RECONCILE_INTERVAL_MS + 1_000) - await list() - - expect(scanCount()).toBe(2) - expect(readRepoWorktreeAdminFingerprintMock).not.toHaveBeenCalled() - } finally { - vi.useRealTimers() - } - }) - - it('never consults the probe for SSH repos', async () => { - vi.useFakeTimers() - try { - const { list } = makeRuntime({ connectionId: 'ssh-remote-1' }) - - await list() - vi.advanceTimersByTime(SCAN_TTL_MS + 1_000) - await list() - - expect(listWorktreesStrictMock).not.toHaveBeenCalled() - expect(readRepoWorktreeAdminFingerprintMock).not.toHaveBeenCalled() - } finally { - vi.useRealTimers() - } - }) - - it('does not extend a failed scan on an unchanged fingerprint', async () => { - vi.useFakeTimers() - try { - listWorktreesStrictMock.mockRejectedValue(new Error('git unavailable')) - const { list } = makeRuntime() - - await list() - expect(scanCount()).toBe(1) - - vi.advanceTimersByTime(SCAN_TTL_MS + 1_000) - await list() - expect(scanCount()).toBe(2) - } finally { - vi.useRealTimers() - } - }) - - it('bounds Git fan-out under a sustained 1 Hz polling workload', async () => { - // Ten idle repos polled faster than snapshot TTL formerly spawned git worktree list every 30s. - vi.useFakeTimers() - try { - const REPOS = 10 - const POLL_SECONDS = 30 * 60 - const { list } = makeRuntime({ repoCount: REPOS }) - - for (let second = 0; second < POLL_SECONDS; second += 1) { - await list() - vi.advanceTimersByTime(1_000) - } - - // Reconciliation cuts this half-hour workload from 600 scans to 60. - const ttlOnlyScans = (REPOS * POLL_SECONDS * 1_000) / SCAN_TTL_MS - const reconcileScans = - (REPOS * POLL_SECONDS * 1_000) / WORKTREE_SCAN_ADMIN_RECONCILE_INTERVAL_MS - expect(ttlOnlyScans).toBe(600) - expect(reconcileScans).toBe(60) - expect(scanCount()).toBe(reconcileScans) - } finally { - vi.useRealTimers() - } - }) - it('keeps refreshing when a probe on a wedged filesystem never settles', async () => { vi.useFakeTimers() try { @@ -400,137 +224,6 @@ describe('worktree scan admin-fingerprint gate', () => { } }) - it('issues no further probes while one is still outstanding', async () => { - vi.useFakeTimers() - try { - readRepoWorktreeAdminFingerprintMock.mockReturnValue(new Promise(() => {})) - const { list } = makeRuntime() - - await list() - expect(readRepoWorktreeAdminFingerprintMock).toHaveBeenCalledTimes(1) - - const startedAt = Date.now() - for (let refresh = 0; refresh < 20; refresh += 1) { - vi.advanceTimersByTime(SCAN_TTL_MS + 1_000) - await list() - } - - // Each abandoned probe can pin a libuv filesystem worker indefinitely. - expect(readRepoWorktreeAdminFingerprintMock).toHaveBeenCalledTimes(1) - // Only the reconcile ceiling rescans while the probe stays pending, never every refresh. - const elapsed = Date.now() - startedAt - expect(scanCount()).toBeGreaterThan(1) - expect(scanCount()).toBeLessThanOrEqual( - 1 + Math.ceil(elapsed / WORKTREE_SCAN_ADMIN_RECONCILE_INTERVAL_MS) - ) - } finally { - vi.useRealTimers() - } - }) - - it('resumes gating once the filesystem recovers', async () => { - vi.useFakeTimers() - try { - const releaseStalledProbe = stallProbeOnce() - const { list } = makeRuntime() - - await list() - vi.advanceTimersByTime(SCAN_TTL_MS + 1_000) - await list() - expect(scanCount()).toBe(1) - - // The mount unwedges; its stale answer must not gate anything, but the repo must probe again. - releaseStalledProbe('fp-stale') - await drainMicrotasks() - vi.advanceTimersByTime(SCAN_TTL_MS + 1_000) - await list() - expect(scanCount()).toBe(2) - - vi.advanceTimersByTime(SCAN_TTL_MS + 1_000) - await list() - expect(scanCount()).toBe(2) - } finally { - vi.useRealTimers() - } - }) - - it('reuses the scanned rows when the awaited probe outlives its deadline', async () => { - vi.useFakeTimers() - try { - const { list } = makeRuntime() - - await list() - expect(scanCount()).toBe(1) - - stallProbeOnce() - vi.advanceTimersByTime(SCAN_TTL_MS + 1_000) - const second = list() - const secondSettled = trackSettled(second) - await drainMicrotasks() - expect(secondSettled()).toBe(false) - expect(scanCount()).toBe(1) - - await vi.advanceTimersByTimeAsync(WORKTREE_SCAN_ADMIN_FINGERPRINT_TIMEOUT_MS) - await drainMicrotasks() - // #19312: a timeout is "unknown", never a mismatch, so it must not start a full scan. - expect(scanCount()).toBe(1) - expect(heads(await second)).toEqual(['abc', 'def']) - } finally { - vi.useRealTimers() - } - }) - - it('still reconciles on schedule while every probe times out', async () => { - vi.useFakeTimers() - try { - readRepoWorktreeAdminFingerprintMock.mockImplementation(() => new Promise(() => {})) - const { list } = makeRuntime() - const scannedAt = Date.now() - await list() - expect(scanCount()).toBe(1) - - while (Date.now() - scannedAt < WORKTREE_SCAN_ADMIN_RECONCILE_INTERVAL_MS - SCAN_TTL_MS) { - vi.advanceTimersByTime(SCAN_TTL_MS + 1_000) - await list() - } - expect(scanCount()).toBe(1) - - vi.advanceTimersByTime(2 * SCAN_TTL_MS) - await list() - expect(scanCount()).toBe(2) - } finally { - vi.useRealTimers() - } - }) - - it('bounds the awaited probe by the caller per-repo budget', () => { - // The wait runs inside that budget, so outlasting it can only strand the caller on persisted rows. - expect(WORKTREE_SCAN_ADMIN_FINGERPRINT_TIMEOUT_MS).toBeLessThan( - RESOLVED_WORKTREE_REPO_TIMEOUT_MS - ) - }) - - it('still reuses the scan when a slow probe answers inside its deadline', async () => { - vi.useFakeTimers() - try { - const { list } = makeRuntime() - await list() - expect(scanCount()).toBe(1) - - const releaseSlowProbe = stallProbeOnce() - vi.advanceTimersByTime(SCAN_TTL_MS + 1_000) - const second = list() - - // Use an absolute healthy latency so shortening the deadline cannot make a self-referential test pass. - await vi.advanceTimersByTimeAsync(SLOW_BUT_HEALTHY_PROBE_MS) - releaseSlowProbe('fp-1') - await second - expect(scanCount()).toBe(1) - } finally { - vi.useRealTimers() - } - }) - it('still returns scanned rows within the per-repo budget when the probe stalls', async () => { vi.useFakeTimers() try { @@ -552,36 +245,6 @@ describe('worktree scan admin-fingerprint gate', () => { vi.useRealTimers() } }) - - it('does not publish an already-expired snapshot after a slow compute', async () => { - vi.useFakeTimers() - try { - const { list, store } = makeRuntime() - await list() - - stallProbeOnce() - vi.advanceTimersByTime(SCAN_TTL_MS + 1_000) - const second = list() - await vi.advanceTimersByTimeAsync(WORKTREE_SCAN_ADMIN_FINGERPRINT_TIMEOUT_MS) - await second - - // Stamp completion time: a probe slower than snapshot TTL otherwise creates an already-expired entry. - const getRepos = vi.spyOn(store, 'getRepos') - await list() - expect(getRepos).not.toHaveBeenCalled() - } finally { - vi.useRealTimers() - } - }) - - it('shares one probe and one scan across concurrent callers', async () => { - const { list } = makeRuntime() - - await Promise.all([list(), list(), list()]) - - expect(scanCount()).toBe(1) - expect(readRepoWorktreeAdminFingerprintMock).toHaveBeenCalledTimes(1) - }) }) describe('scoped explicit worktree-id resolution', () => { @@ -605,43 +268,6 @@ describe('scoped explicit worktree-id resolution', () => { return listWorktreesStrictMock.mock.calls.map((call) => call[0] as string) } - it('scans only the owning repo for an id: selector on a cold cache', async () => { - const runtime = new OrcaRuntimeService(makeStore({ repoCount: 10 }) as never) - const resolve = (selector: string): Promise<{ id: string }> => - runtime['resolveGitWorktreeSelector'](selector) - - const resolved = await resolve(`id:${MAIN_WORKTREE_ID}`) - - expect(resolved.id).toBe(MAIN_WORKTREE_ID) - expect(scannedRepoPaths()).toEqual([REPO_PATH]) - }) - it('resolves an exact canonical identity without relying on the mutable locator', async () => { - const runtime = new OrcaRuntimeService(makeStore({ repoCount: 10 }) as never) - const resolve = (selector: string): Promise<{ id: string }> => - runtime['resolveGitWorktreeSelector'](selector) - const identityKey = canonicalWorktreeIdentity({ - worktreeId: MAIN_WORKTREE_ID, - executionHostId: 'local', - instanceId: '22222222-2222-4222-8222-222222222222' - }) - - const resolved = await resolve(`identity:${identityKey}`) - - expect(resolved.id).toBe(MAIN_WORKTREE_ID) - }) - - it('still finds worktrees in other repos through the fleet path', async () => { - const runtime = new OrcaRuntimeService(makeStore({ repoCount: 10 }) as never) - const resolve = (selector: string): Promise<{ id: string }> => - runtime['resolveGitWorktreeSelector'](selector) - - const otherId = `${REPO_ID}-3::${REPO_PATH}-3` - const resolved = await resolve(`id:${otherId}`) - - expect(resolved.id).toBe(otherId) - expect(scannedRepoPaths()).toEqual([`${REPO_PATH}-3`]) - }) - it('keeps cross-repo selectors on the fleet path so ambiguity still throws', async () => { const runtime = new OrcaRuntimeService(makeStore({ repoCount: 10 }) as never) const resolve = (selector: string): Promise => @@ -650,13 +276,4 @@ describe('scoped explicit worktree-id resolution', () => { await expect(resolve('branch:main')).rejects.toThrow('selector_ambiguous') expect(new Set(scannedRepoPaths()).size).toBe(10) }) - - it('falls back to the fleet path when the id names no registered repo', async () => { - const runtime = new OrcaRuntimeService(makeStore({ repoCount: 10 }) as never) - const resolve = (selector: string): Promise => - runtime['resolveGitWorktreeSelector'](selector) - - await expect(resolve('id:missing-repo::/nowhere')).rejects.toThrow('selector_not_found') - expect(new Set(scannedRepoPaths()).size).toBe(10) - }) }) diff --git a/src/main/runtime/workspaces/catalog/worktree-scan-repeated-path.test.ts b/src/main/runtime/workspaces/catalog/worktree-scan-repeated-path.test.ts index 217dceb0f04..889db5c99e2 100644 --- a/src/main/runtime/workspaces/catalog/worktree-scan-repeated-path.test.ts +++ b/src/main/runtime/workspaces/catalog/worktree-scan-repeated-path.test.ts @@ -45,14 +45,6 @@ const MAIN_ROW = { isBare: false, isMainWorktree: true } -// Linux paths are case-sensitive: a different spelling is a different checkout. -const OTHER_SPELLING_ROW = { - path: '/home/me/FileLoc', - head: 'def', - branch: 'refs/heads/feat', - isBare: false, - isMainWorktree: false -} function makeRuntime(options: { connectionId?: string } = {}): OrcaRuntimeService { const metaById: Record = {} @@ -103,33 +95,6 @@ describe('worktree scan with a repeated path', () => { localScanMock.mockReset() }) - describe('rows that agree, as reported', () => { - beforeEach(() => { - gitLists([MAIN_ROW, { ...MAIN_ROW, isMainWorktree: false }, OTHER_SPELLING_ROW]) - }) - - it('resolves the id the CLI sends for `active` and `current` after a listing', async () => { - const runtime = makeRuntime() - await runtime.listManagedWorktrees() - - await expect(runtime.showManagedWorktree(`id:${MAIN_WORKTREE_ID}`)).resolves.toMatchObject({ - id: MAIN_WORKTREE_ID, - isMainWorktree: true - }) - }) - - it('lists each path once for the CLI and for paired clients', async () => { - const runtime = makeRuntime() - const expectedIds = [MAIN_WORKTREE_ID, `${REPO_ID}::/home/me/FileLoc`] - - const listed = await runtime.listManagedWorktrees() - expect(listed.worktrees.map((worktree) => worktree.id)).toEqual(expectedIds) - - const detected = await runtime.listDetectedManagedWorktrees(`id:${REPO_ID}`) - expect(detected.worktrees.map((worktree) => worktree.id)).toEqual(expectedIds) - }) - }) - it('keeps the main checkout over a stale registration on another branch', async () => { gitLists([ MAIN_ROW, diff --git a/src/main/runtime/workspaces/creation/managed-worktree-create-registry.test.ts b/src/main/runtime/workspaces/creation/managed-worktree-create-registry.test.ts index b6c509ad307..22f219565d9 100644 --- a/src/main/runtime/workspaces/creation/managed-worktree-create-registry.test.ts +++ b/src/main/runtime/workspaces/creation/managed-worktree-create-registry.test.ts @@ -156,18 +156,4 @@ describe('runtime creation registry', () => { ).rejects.toThrow('could not create') expect(creationsOf(runtime)).toEqual({}) }) - - it('lists a folder workspace create too', async () => { - const runtime = makeRuntime({ id: 'folder', path: '/notes', kind: 'folder' }) - let during: Record = {} - mocks.createFolder.mockReset().mockImplementation(async () => { - during = creationsOf(runtime) - return { worktree: { id: 'folder::/notes' } } - }) - - await runtime.createManagedWorktree({ repoSelector: 'folder', name: 'notes' }) - - expect(Object.values(during)).toEqual([{ repoId: 'folder' }]) - expect(creationsOf(runtime)).toEqual({}) - }) }) diff --git a/src/main/runtime/workspaces/creation/managed-worktree-create-telemetry.test.ts b/src/main/runtime/workspaces/creation/managed-worktree-create-telemetry.test.ts index e1be5004d3d..4d12e81854a 100644 --- a/src/main/runtime/workspaces/creation/managed-worktree-create-telemetry.test.ts +++ b/src/main/runtime/workspaces/creation/managed-worktree-create-telemetry.test.ts @@ -47,11 +47,7 @@ vi.mock('../../../ipc/worktrees/removal/registered-worktree-roots-scoped-invalid import { OrcaRuntimeService } from '../../orca-runtime' import type { WorktreeCreateTimingRecorder } from '../../../workspaces/creation/worktree-create-timing' import type { PreparationRearmHolder } from '../../../workspaces/creation/preparation/worktree-create-preparation' -import { - _resetWorktreeCreateConcurrencyForTests, - beginPreparationWork, - type PreparationWork -} from '../../../workspaces/creation/worktree-create-concurrency' +import { _resetWorktreeCreateConcurrencyForTests } from '../../../workspaces/creation/worktree-create-concurrency' import { partialRuntimeStore } from '../../orca-runtime-tests/runtime-store-partial-fixture' const gitRepo = { @@ -163,24 +159,6 @@ describe('runtime create events', () => { expect(events('workspace_created')).toHaveLength(0) }) - it('counts preparation work running during the create but not the re-arm the create starts', async () => { - let competing: PreparationWork | undefined - mocks.createLocal.mockImplementation(async ({ rearm }: LocalCreateArgs) => { - competing = beginPreparationWork() - rearm.fire = () => { - beginPreparationWork() - beginPreparationWork() - } - return localCreateResult() - }) - - await makeRuntime().createManagedWorktree({ repoSelector: 'repo-1', name: 'app' }) - competing?.end() - await vi.waitFor(() => expect(events('workspace_created')).toHaveLength(1)) - - expect(events('workspace_created')[0]).toMatchObject({ concurrent_preparations: 1 }) - }) - it('still returns the create when sending its event throws', async () => { vi.spyOn(console, 'warn').mockImplementation(() => {}) mocks.track.mockImplementation(() => { @@ -205,36 +183,4 @@ describe('runtime create events', () => { ).rejects.toThrow('could not create work tree') expect(mocks.track).toHaveBeenCalledOnce() }) - - it('refuses an SSH create and still names its host on the failed event', async () => { - const runtime = makeRuntime({ - id: 'repo-remote', - path: '/srv/app', - kind: 'git', - executionHostId: 'ssh:remote-1' - }) - - await expect( - runtime.createManagedWorktree({ repoSelector: 'repo-remote', name: 'app' }) - ).rejects.toThrow('Remote connection dropped') - await vi.waitFor(() => expect(events('workspace_create_failed')).toHaveLength(1)) - - expect(events('workspace_create_failed')[0]).toMatchObject({ - create_entry_point: 'runtime', - execution_host: 'ssh' - }) - expect(mocks.probe).not.toHaveBeenCalled() - }) - - it('sends nothing when the request is rejected before any create starts', async () => { - await expect( - makeRuntime().createManagedWorktree({ - repoSelector: 'repo-1', - name: 'app', - startupAgent: 'codex' - }) - ).rejects.toThrow('Selected agent is disabled') - expect(mocks.createLocal).not.toHaveBeenCalled() - expect(mocks.track).not.toHaveBeenCalled() - }) }) diff --git a/src/main/runtime/workspaces/creation/orca-runtime-create-base-prefetch.test.ts b/src/main/runtime/workspaces/creation/orca-runtime-create-base-prefetch.test.ts deleted file mode 100644 index 022f3babe39..00000000000 --- a/src/main/runtime/workspaces/creation/orca-runtime-create-base-prefetch.test.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import type * as WorktreeCreatePreparation from '../../../workspaces/creation/preparation/worktree-create-preparation' -import type { Project } from '../../../../shared/project-types' -import type { Repo } from '../../../../shared/repo-types' -import { - _resetWslCachesForTests, - _setWslCachesForTests -} from '../../../wsl/discovery/distro-catalog' - -const mocks = vi.hoisted(() => ({ - prefetchWorktreeCreateBase: vi.fn(), - prepareWorktreeCreateForRepo: vi.fn() -})) - -vi.mock('../../../workspaces/creation/worktree-create-base-prefetch', () => ({ - prefetchWorktreeCreateBase: mocks.prefetchWorktreeCreateBase -})) -vi.mock( - '../../../workspaces/creation/preparation/worktree-create-preparation', - async (importOriginal) => ({ - ...(await importOriginal()), - prepareWorktreeCreateForRepo: mocks.prepareWorktreeCreateForRepo - }) -) - -import { OrcaRuntimeService } from '../../orca-runtime' - -const repo: Repo = { - id: 'repo-1', - displayName: 'Repo', - path: String.raw`C:\workspace\repo`, - badgeColor: '#000000', - addedAt: 0 -} - -const project: Project = { - id: 'project-1', - displayName: 'Project', - badgeColor: '#000000', - sourceRepoIds: ['repo-1'], - createdAt: 0, - updatedAt: 0, - localWindowsRuntimePreference: { kind: 'wsl', distro: 'Ubuntu' } -} - -function makeStore(overrides: Partial = {}): unknown { - return { - getRepos: () => [repo], - getRepo: (id: string) => (id === repo.id ? repo : undefined), - getProjects: () => [{ ...project, ...overrides }], - getSettings: () => ({ localWindowsRuntimeDefault: { kind: 'windows-host' } }) - } -} - -function setPlatform(platform: NodeJS.Platform): void { - Object.defineProperty(process, 'platform', { configurable: true, value: platform }) -} - -const hostPlatform = process.platform - -beforeEach(() => { - mocks.prefetchWorktreeCreateBase.mockReset().mockResolvedValue(undefined) - mocks.prepareWorktreeCreateForRepo.mockReset().mockResolvedValue(undefined) -}) - -afterEach(() => { - setPlatform(hostPlatform) - _resetWslCachesForTests() -}) - -// RPC prefetch must match IPC project routing; construct through the barrel for later selector subclasses. -describe('prefetchManagedWorktreeCreateBase (orca-runtime-get-worktree-terminal-provisioning-host)', () => { - it('warms up in the distro a WSL-routed project runs in', async () => { - _setWslCachesForTests({ available: true, distros: ['Ubuntu'] }) - setPlatform('win32') - const runtime = new OrcaRuntimeService(makeStore() as never) - - await runtime.prefetchManagedWorktreeCreateBase({ repoSelector: 'repo-1' }) - - expect(mocks.prefetchWorktreeCreateBase).toHaveBeenCalledWith( - expect.objectContaining({ gitOptions: { wslDistro: 'Ubuntu' } }) - ) - }) - - it('does not surface a repair-required project runtime as a prefetch failure', async () => { - _setWslCachesForTests({ available: true, distros: ['Debian'] }) - setPlatform('win32') - const runtime = new OrcaRuntimeService(makeStore() as never) - - await expect( - runtime.prefetchManagedWorktreeCreateBase({ repoSelector: 'repo-1' }) - ).resolves.toBeUndefined() - - expect(mocks.prefetchWorktreeCreateBase).toHaveBeenCalledWith( - expect.objectContaining({ gitOptions: {} }) - ) - }) -}) diff --git a/src/main/runtime/workspaces/creation/orca-runtime-worktree-provisioning-availability.test.ts b/src/main/runtime/workspaces/creation/orca-runtime-worktree-provisioning-availability.test.ts index 7c700e9b2ba..1011720746c 100644 --- a/src/main/runtime/workspaces/creation/orca-runtime-worktree-provisioning-availability.test.ts +++ b/src/main/runtime/workspaces/creation/orca-runtime-worktree-provisioning-availability.test.ts @@ -13,9 +13,7 @@ import { listWorktrees } from '../../../git/worktrees/catalog/worktree-scan-cach import { shouldRunSetupForCreate } from '../../../workspace-hooks/effective-hook-config' import { store } from '../../orca-runtime-tests/orca-runtime-test-fixtures.spec' import { createMobileCreateTestNotifier } from '../../orca-runtime-tests/orca-runtime-test-scenario-builders.spec' -import { tmpdir } from 'node:os' -import type { Worktree } from '../../../../shared/worktree/types' -import type { RuntimePtyController } from '../../runtime-pty-controller-contract' +import 'node:os' describe('host worktree creation while its renderer is unavailable', () => { it.each([ @@ -96,104 +94,3 @@ describe('host worktree creation while its renderer is unavailable', () => { } ) }) - -describe('startup hosts retain current runtime capabilities', () => { - it('refuses base prefetch without a runtime store', async () => { - const runtime = new OrcaRuntimeService(null) - await expect( - runtime.prefetchManagedWorktreeCreateBase({ repoSelector: 'id:missing' }) - ).rejects.toThrow('runtime_unavailable') - }) - - it('reads the current create override with the captured worktree and three arguments', async () => { - const runtime = new OrcaRuntimeService(store) - onTestFinished(() => { - vi.restoreAllMocks() - }) - const created: Worktree = { - id: 'created-worktree', - repoId: 'repo-1', - displayName: 'Created', - comment: '', - linkedIssue: null, - linkedPR: null, - linkedLinearIssue: null, - isArchived: false, - isUnread: false, - isPinned: false, - sortOrder: 0, - lastActivityAt: 0, - path: tmpdir(), - head: 'abc', - branch: 'created', - isBare: false, - isMainWorktree: false - } - const host = runtime['getWorktreeTerminalProvisioningHost'](created) - const options = { command: 'echo startup' } - const create = vi.spyOn(runtime, 'createTerminal').mockImplementation(async function ( - this: OrcaRuntimeService, - selector, - input, - evidence - ) { - expect(this).toBe(runtime) - expect(arguments.length).toBe(3) - expect([selector, input, evidence]).toEqual(['id:created-worktree', options, created]) - expect(evidence).toBe(created) - return { handle: 'created-handle', worktreeId: created.id, title: null } - }) - await host.createTerminal('id:created-worktree', options) - expect(create).toHaveBeenCalledOnce() - }) - - it('reads replacement providers and preserves readiness failures after removal', async () => { - const runtime = new OrcaRuntimeService(store) - const host = runtime['getWorktreeStartupReadinessHost']() - const hasChildProcesses = host.hasChildProcesses - if (!hasChildProcesses) { - throw new Error('missing readiness child-process callback') - } - const provisioning = runtime['getWorktreeTerminalProvisioningHost']() - expect(provisioning.canSpawn()).toBe(false) - const controller: RuntimePtyController = { - write: () => true, - kill: () => true, - getForegroundProcess: vi.fn(async function (this: RuntimePtyController, ptyId: string) { - expect(this).toBe(controller) - expect(arguments.length).toBe(1) - expect(ptyId).toBe('startup-pty') - return 'late provider' - }) - } - runtime.setPtyController(controller) - await expect(host.getForegroundProcess('startup-pty')).resolves.toBe('late provider') - await expect(hasChildProcesses('startup-pty')).resolves.toBe(false) - const replacement: RuntimePtyController = { - write: () => true, - kill: () => true, - spawn: async () => ({ id: 'spawned' }), - getForegroundProcess: async () => 'replacement provider', - hasChildProcesses: async () => true - } - runtime.setPtyController(replacement) - expect(provisioning.canSpawn()).toBe(true) - await expect(host.getForegroundProcess('startup-pty')).resolves.toBe('replacement provider') - await expect(hasChildProcesses('startup-pty')).resolves.toBe(true) - runtime.setPtyController(null) - expect(provisioning.canSpawn()).toBe(false) - expect(() => host.getForegroundProcess('startup-pty')).toThrow(TypeError) - expect(() => hasChildProcesses('startup-pty')).toThrow(TypeError) - }) - - it('writes the canonical setup-token map when invoked', () => { - const runtime = new OrcaRuntimeService(store) - const host = runtime['getWorktreeTerminalProvisioningHost']() - const original = runtime['setupCompletionTokenByPtyId'] - const replacement = new Map() - Object.defineProperty(runtime, 'setupCompletionTokenByPtyId', { value: replacement }) - host.recordSetupCompletionToken?.('startup-pty', 'late-token') - expect(replacement.get('startup-pty')).toBe('late-token') - expect(original.has('startup-pty')).toBe(false) - }) -}) diff --git a/src/main/runtime/workspaces/creation/runtime-local-worktree-create-pr-conflict.test.ts b/src/main/runtime/workspaces/creation/runtime-local-worktree-create-pr-conflict.test.ts index 197097f84d6..77b3f606dc3 100644 --- a/src/main/runtime/workspaces/creation/runtime-local-worktree-create-pr-conflict.test.ts +++ b/src/main/runtime/workspaces/creation/runtime-local-worktree-create-pr-conflict.test.ts @@ -1,10 +1,8 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' -import { resolve } from 'node:path' +import 'node:path' const { runtimeCreateMocks: mocks, - worktreePath, - pickedBranch, createWorktree, resetRuntimeCreateMocks } = await vi.hoisted(async () => await import('./runtime-local-worktree-create-test-harness')) @@ -94,142 +92,6 @@ describe('runtime create PR conflict probe', () => { mocks.branchName.mockImplementation(async (_path, override, name: string) => override ?? name) }) - it.each([1, 10])( - 'keeps the selected name and source through %i occupied paths, without PR eligibility', - async (occupied) => { - const options = { wslDistro: 'Ubuntu' } - mocks.routing.mockReturnValue(options) - mocks.remoteBase.mockResolvedValue(remoteTrackingBase) - mocks.githubPr.mockResolvedValue({ number: 42 }) - let pathChecks = 0 - mocks.pathExists.mockImplementation(async () => pathChecks++ < occupied) - - await createWorktree({ ...pickedBranch, sparseCheckout: { directories: ['src'] } }) - - expect(mocks.remoteBase.mock.calls).toEqual([['/repo', 'origin/main', options]]) - expect(mocks.branchName).toHaveBeenCalledOnce() - expect(mocks.branchConflict.mock.calls).toEqual([ - ['/repo', 'main', remoteTrackingBase.ref, options] - ]) - expect(mocks.pathExists).toHaveBeenCalledTimes(occupied + 1) - expect(mocks.githubPr).not.toHaveBeenCalled() - expect(mocks.consume).not.toHaveBeenCalled() - expect(mocks.addSparse).toHaveBeenCalledExactlyOnceWith( - '/repo', - resolve('/worktrees', `app-${occupied + 1}`), - 'main', - ['src'], - remoteTrackingBase.base, - false, - expect.objectContaining({ remoteTrackingBase, trackRemoteBranch: true, ...options }) - ) - } - ) - - it.each([true, false])( - 'keeps local eligibility with picker intent (reusable: %s)', - async (reusable) => { - mocks.branchConflict.mockImplementation(async (_path, branch: string) => - branch === 'main' ? 'local' : null - ) - mocks.canCheckout.mockImplementation( - async (_path, branch: string) => reusable && branch === 'main' - ) - mocks.pathExists.mockImplementation(async (path) => path === worktreePath) - mocks.consume.mockResolvedValue({ status: 'miss', reason: 'not_prepared' }) - - if (!reusable) { - await expect(createWorktree(pickedBranch)).rejects.toThrow('was not found locally') - expect(mocks.add).not.toHaveBeenCalled() - expect(mocks.consume).not.toHaveBeenCalled() - return - } - await createWorktree(pickedBranch) - expect(mocks.add).toHaveBeenCalledWith( - '/repo', - resolve('/worktrees', 'app-2'), - 'main', - 'origin/main', - false, - false, - expect.objectContaining({ checkoutExistingBranch: true }) - ) - expect(mocks.githubPr).not.toHaveBeenCalled() - } - ) - - it('keeps unflagged legacy, mobile New branch and Advanced collisions on baseline allocation', async () => { - mocks.consume.mockResolvedValue({ status: 'miss', reason: 'not_prepared' }) - mocks.branchName.mockImplementation(async (_path, override, name: string) => override ?? name) - mocks.branchConflict.mockImplementation(async (_path, branch: string) => - branch === 'feature/test' ? 'remote' : null - ) - - await createWorktree({ branchNameOverride: 'feature/test', baseBranch: 'origin/main' }) - - expect(mocks.add.mock.calls[0][2]).toBe('feature/test-2') - }) - - it('runs the first name PR lookup while the base fetch is still in flight', async () => { - mocks.remoteBase.mockResolvedValue(remoteTrackingBase) - let finishFetch!: () => void - mocks.refresh.mockReturnValue( - new Promise((resolve) => { - finishFetch = () => resolve({ ok: true }) - }) - ) - let fetchPendingAtLookup = false - mocks.githubPr.mockImplementation(async () => { - fetchPendingAtLookup = mocks.refresh.mock.calls.length === 1 - finishFetch?.() - return null - }) - - await createWorktree() - - expect(fetchPendingAtLookup).toBe(true) - expect(mocks.githubPr).toHaveBeenCalledWith('/repo', 'app', {}) - expect(mocks.consume).toHaveBeenCalledWith( - expect.objectContaining({ - branch: 'app', - options: expect.objectContaining({ remoteTrackingBase }) - }) - ) - }) - - it('suffixes past a PR on the first name', async () => { - mocks.githubPr.mockResolvedValueOnce({ number: 7, state: 'closed' }).mockResolvedValue(null) - - await createWorktree() - - expect(mocks.githubPr.mock.calls.map(([, branch]) => branch)).toEqual(['app', 'app-2']) - expect(mocks.consume).toHaveBeenCalledWith(expect.objectContaining({ branch: 'app-2' })) - }) - - it.each([undefined, 'Ubuntu'])( - 'probes the next name for a PR once the first name collides, routed for %s', - async (wslDistro) => { - const routing = wslDistro ? { wslDistro } : {} - mocks.routing.mockReturnValue(routing) - mocks.branchConflict.mockResolvedValueOnce('local').mockResolvedValue(null) - - await createWorktree() - - expect(mocks.githubPr).toHaveBeenCalledOnce() - expect(mocks.githubPr).toHaveBeenCalledWith('/repo', 'app-2', routing) - } - ) - - it('skips a later name that already has a PR', async () => { - mocks.branchConflict.mockResolvedValueOnce('local').mockResolvedValue(null) - mocks.githubPr.mockResolvedValueOnce({ number: 7, state: 'closed' }).mockResolvedValue(null) - - await createWorktree() - - expect(mocks.githubPr.mock.calls.map(([, branch]) => branch)).toEqual(['app-2', 'app-3']) - expect(mocks.consume).toHaveBeenCalledWith(expect.objectContaining({ branch: 'app-3' })) - }) - it('reports the naming failure when the started base fetch also fails', async () => { mocks.remoteBase.mockResolvedValue(remoteTrackingBase) mocks.refresh.mockRejectedValue(new Error('offline')) @@ -247,28 +109,6 @@ describe('runtime create PR conflict probe', () => { } }) - it('runs the first name PR lookup while the origin fetch is still in flight', async () => { - mocks.hasBase.mockResolvedValue(false) - let finishFetch!: () => void - mocks.fetch.mockReturnValue( - new Promise((resolve) => { - finishFetch = resolve - }) - ) - let fetchPendingAtLookup = false - mocks.githubPr.mockImplementation(async () => { - fetchPendingAtLookup = mocks.fetch.mock.calls.length === 1 - finishFetch?.() - return null - }) - - await createWorktree() - - expect(fetchPendingAtLookup).toBe(true) - expect(mocks.fetch).toHaveBeenCalledWith('/repo', 'origin', {}) - expect(mocks.consume).toHaveBeenCalledWith(expect.objectContaining({ branch: 'app' })) - }) - it('reports the naming failure when the started origin fetch also fails', async () => { mocks.hasBase.mockResolvedValue(false) mocks.fetch.mockRejectedValue(new Error('offline')) diff --git a/src/main/runtime/workspaces/creation/runtime-local-worktree-create-pr-conflicts.test.ts b/src/main/runtime/workspaces/creation/runtime-local-worktree-create-pr-conflicts.test.ts deleted file mode 100644 index 5cdb0d38999..00000000000 --- a/src/main/runtime/workspaces/creation/runtime-local-worktree-create-pr-conflicts.test.ts +++ /dev/null @@ -1,427 +0,0 @@ -import { beforeEach, describe, expect, it, vi } from 'vitest' -import { resolve } from 'node:path' -import type { Store } from '../../../persistence/loading-store/store' -import type { WorktreeMeta } from '../../../../shared/worktree/meta-types' -import type { HostWorktreeTarget } from '../../../../shared/worktree/host-qualified-identity' -import type { Repo } from '../../../../shared/repo-types' -import type { RuntimeManagedWorktreeCreateArgs } from './runtime-managed-worktree-create-types' -import { resolveGitAdmissionTier } from '../../../git/command-runner/git-operation-executor' - -const mocks = vi.hoisted(() => ({ - git: vi.fn(), - rearm: vi.fn(), - routing: vi.fn<() => { wslDistro?: string }>(), - defaultBase: vi.fn(), - hasBase: vi.fn(), - branchName: vi.fn(), - canCheckout: vi.fn(), - pathExists: vi.fn(), - branchConflict: vi.fn(), - githubPr: vi.fn(), - consume: vi.fn(), - add: vi.fn(), - addSparse: vi.fn(), - pushTarget: vi.fn(), - listing: vi.fn(), - remoteBase: vi.fn(), - hasRemoteRef: vi.fn(), - refresh: vi.fn(), - fetch: vi.fn(), - resolveShared: vi.fn<() => Promise>(), - resolveInclude: vi.fn<() => Promise>(), - copyPaths: vi.fn<() => Promise>(), - effectiveHooks: vi.fn(), - created: { - path: '', - head: 'abc123', - branch: 'app', - isBare: false, - isMainWorktree: false - } -})) - -vi.mock('../../../text-generation/commit-message-text-generation', () => ({ - resolveTextGenerationParams: () => ({ - ok: true, - params: { agentId: 'claude', model: 'fixture' } - }), - generateBranchNameFromContext: async () => ({ success: true, slug: 'fix-auth' }) -})) -vi.mock('../../../text-generation/commit-message-agent-environment', () => ({ - prepareLocalCommitMessageAgentEnv: async () => ({ ok: true }) -})) - -vi.mock('../../../git/command-runner/git-exec-file', async (importOriginal) => ({ - ...(await importOriginal>()), - gitExecFileAsync: mocks.git -})) -vi.mock('../../../workspaces/execution-host/project-runtime-git-options', () => ({ - getLocalProjectGitExecOptions: () => ({ cwd: '/repo', ...mocks.routing() }), - getLocalProjectWorktreeGitOptions: mocks.routing, - getWorktreeMirrorDistro: () => undefined -})) -vi.mock('../../../git/repo-default-base-ref', () => ({ - getBaseRefDefault: mocks.defaultBase, - resolveDefaultBaseRefWithLocalGit: mocks.defaultBase -})) -vi.mock('../../../git/repo-branch-conflict', () => ({ - getBranchConflictKind: mocks.branchConflict -})) -vi.mock('../../../git/git-username', () => ({ resolveLocalGitUsername: async () => '' })) -vi.mock('../../../workspace-hooks/hooks', () => ({ getEffectiveHooks: mocks.effectiveHooks })) -vi.mock('../../../git/worktrees/creation/worktree-base-ref-probe', () => ({ - hasLocalWorktreeBaseRef: mocks.hasBase -})) -vi.mock('./runtime-worktree-create-git', () => ({ - resolveCreateBranchName: mocks.branchName, - canCheckoutExistingLocalBranch: mocks.canCheckout, - getLocalGitHubPrForBranch: mocks.githubPr, - getSelectedHostedReviewForBranch: vi.fn() -})) -vi.mock('../catalog/runtime-worktree-filesystem', () => ({ runtimePathExists: mocks.pathExists })) -vi.mock('../../../workspaces/creation/preparation/worktree-create-preparation', () => ({ - consumePreparedWorktreeCreate: mocks.consume -})) -vi.mock('../../../git/worktrees/creation/worktree-add', () => ({ - addWorktree: mocks.add -})) - -vi.mock('../../../git/worktrees/creation/worktree-sparse-add', () => ({ - addSparseWorktree: mocks.addSparse -})) -vi.mock('../../../ipc/worktree-remote', () => ({ - configureCreatedWorktreePushTarget: mocks.pushTarget -})) -vi.mock('../../../ipc/created-worktree-reconciliation', () => ({ - resolveCreatedWorktree: mocks.listing -})) -vi.mock('../../../workspaces/removal/retirement/worktree-name-retirement', () => ({ - failedWorktreeCreationNeedsRetirement: vi.fn(), - retireGeneratedWorktreeName: vi.fn() -})) -vi.mock('../../../git/worktrees/content-paths/worktree-shared-directories', () => ({ - resolveWorktreeSharedDirectories: mocks.resolveShared -})) -vi.mock('../../../git/worktrees/content-paths/worktree-include-file', () => ({ - resolveWorktreeIncludePaths: mocks.resolveInclude -})) -vi.mock('../../../ipc/worktree-symlinks', () => ({ - createWorktreeCopiedPaths: mocks.copyPaths, - createWorktreeLinkedPaths: vi.fn(), - createWorktreeSharedPaths: vi.fn() -})) - -import { createRuntimeLocalManagedWorktree } from './runtime-local-worktree-create' -import type { PreparationRearmHolder } from '../../../workspaces/creation/preparation/worktree-create-preparation' -import { - createWorktreeCreateTimingRecorder, - type WorktreeCreateTimingRecorder -} from '../../../workspaces/creation/worktree-create-timing' -import type { SparsePreset } from '../../../../shared/worktree/create-types' - -const worktreePath = resolve('/worktrees', 'app') - -const pickedBranch = { - baseBranch: 'origin/main', - branchNameOverride: 'main', - branchIntent: 'open-existing-v2' -} as const - -const writtenMeta: Partial[] = [] -const WEB_PRESET: SparsePreset = { - id: 'preset-web', - repoId: 'repo-1', - name: 'web', - directories: ['apps/web', 'packages/ui'], - createdAt: 0, - updatedAt: 0 -} -const readSparsePresets = vi.fn<(repoId: string) => SparsePreset[]>() - -function createWorktree( - request: Partial = {}, - rearm: PreparationRearmHolder = { fire: () => {} }, - timing: WorktreeCreateTimingRecorder = createWorktreeCreateTimingRecorder(), - repoOverrides: Partial = {}, - autoRenameBranchFromWork = false -) { - const store = { - getSettings: () => ({ - workspaceDir: '/worktrees', - nestWorkspaces: false, - refreshLocalBaseRefOnWorktreeCreate: false, - branchPrefix: '', - autoRenameBranchFromWork - }), - setWorktreeMeta: (_target: HostWorktreeTarget, updates: Partial) => { - writtenMeta.push(updates) - return updates - }, - getSparsePresets: (repoId: string): SparsePreset[] => readSparsePresets(repoId) - } - return createRuntimeLocalManagedWorktree({ - request: { repoSelector: 'repo-1', name: 'app', baseBranch: 'main', ...request }, - repo: { - id: 'repo-1', - path: '/repo', - displayName: 'Repo', - badgeColor: '#000000', - addedAt: 0, - ...repoOverrides - }, - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: All store methods reached by this isolated create path are supplied above. - store: store as Store, - createdWithAgent: undefined, - resolveRemoteTrackingBase: mocks.remoteBase, - hasRemoteTrackingRef: mocks.hasRemoteRef, - // vi.fn marks returned promises handled; a fresh promise exposes unawaited fetch rejection. - refreshRemoteTrackingBase: async (...refreshArgs) => await mocks.refresh(...refreshArgs), - fetchRemote: async (...fetchArgs) => await mocks.fetch(...fetchArgs), - onWorktreeMetadataPersisted: () => undefined, - rearm, - timing - }) -} - -beforeEach(() => { - vi.resetAllMocks() - writtenMeta.length = 0 - mocks.created.branch = 'app' - mocks.git.mockResolvedValue({ stdout: '', stderr: '' }) - mocks.created.path = worktreePath - mocks.routing.mockReturnValue({}) - mocks.defaultBase.mockImplementation(async () => { - expect(resolveGitAdmissionTier()).toBe('interactive') - return 'main' - }) - mocks.hasBase.mockResolvedValue(true) - mocks.branchName.mockResolvedValue('app') - mocks.canCheckout.mockResolvedValue(false) - mocks.pathExists.mockResolvedValue(false) - mocks.branchConflict.mockResolvedValue(null) - mocks.githubPr.mockResolvedValue(null) - mocks.consume.mockResolvedValue({ status: 'hit', result: {}, rearm: mocks.rearm }) - mocks.add.mockResolvedValue({}) - mocks.addSparse.mockResolvedValue({}) - mocks.listing.mockImplementation(async () => { - expect(resolveGitAdmissionTier()).toBe('interactive') - return { created: mocks.created } - }) - mocks.remoteBase.mockImplementation(async () => { - expect(resolveGitAdmissionTier()).toBe('interactive') - return null - }) - mocks.hasRemoteRef.mockResolvedValue(true) - mocks.refresh.mockResolvedValue({ ok: true }) - mocks.fetch.mockResolvedValue(undefined) - mocks.resolveShared.mockResolvedValue([]) - mocks.resolveInclude.mockResolvedValue(['.env']) - mocks.copyPaths.mockResolvedValue([]) - mocks.effectiveHooks.mockReturnValue(null) - readSparsePresets.mockReturnValue([WEB_PRESET]) -}) - -describe('runtime create PR conflict probe', () => { - const remoteTrackingBase = { - remote: 'origin', - branch: 'main', - base: 'origin/main', - ref: 'refs/remotes/origin/main' - } - - beforeEach(() => { - mocks.branchName.mockImplementation(async (_path, override, name: string) => override ?? name) - }) - - it.each([1, 10])( - 'keeps the selected name and source through %i occupied paths, without PR eligibility', - async (occupied) => { - const options = { wslDistro: 'Ubuntu' } - mocks.routing.mockReturnValue(options) - mocks.remoteBase.mockResolvedValue(remoteTrackingBase) - mocks.githubPr.mockResolvedValue({ number: 42 }) - let pathChecks = 0 - mocks.pathExists.mockImplementation(async () => pathChecks++ < occupied) - - await createWorktree({ ...pickedBranch, sparseCheckout: { directories: ['src'] } }) - - expect(mocks.remoteBase.mock.calls).toEqual([['/repo', 'origin/main', options]]) - expect(mocks.branchName).toHaveBeenCalledOnce() - expect(mocks.branchConflict.mock.calls).toEqual([ - ['/repo', 'main', remoteTrackingBase.ref, options] - ]) - expect(mocks.pathExists).toHaveBeenCalledTimes(occupied + 1) - expect(mocks.githubPr).not.toHaveBeenCalled() - expect(mocks.consume).not.toHaveBeenCalled() - expect(mocks.addSparse).toHaveBeenCalledExactlyOnceWith( - '/repo', - resolve('/worktrees', `app-${occupied + 1}`), - 'main', - ['src'], - remoteTrackingBase.base, - false, - expect.objectContaining({ remoteTrackingBase, trackRemoteBranch: true, ...options }) - ) - } - ) - - it.each([true, false])( - 'keeps local eligibility with picker intent (reusable: %s)', - async (reusable) => { - mocks.branchConflict.mockImplementation(async (_path, branch: string) => - branch === 'main' ? 'local' : null - ) - mocks.canCheckout.mockImplementation( - async (_path, branch: string) => reusable && branch === 'main' - ) - mocks.pathExists.mockImplementation(async (path) => path === worktreePath) - mocks.consume.mockResolvedValue({ status: 'miss', reason: 'not_prepared' }) - - if (!reusable) { - await expect(createWorktree(pickedBranch)).rejects.toThrow('was not found locally') - expect(mocks.add).not.toHaveBeenCalled() - expect(mocks.consume).not.toHaveBeenCalled() - return - } - await createWorktree(pickedBranch) - expect(mocks.add).toHaveBeenCalledWith( - '/repo', - resolve('/worktrees', 'app-2'), - 'main', - 'origin/main', - false, - false, - expect.objectContaining({ checkoutExistingBranch: true }) - ) - expect(mocks.githubPr).not.toHaveBeenCalled() - } - ) - - it('keeps unflagged legacy, mobile New branch and Advanced collisions on baseline allocation', async () => { - mocks.consume.mockResolvedValue({ status: 'miss', reason: 'not_prepared' }) - mocks.branchName.mockImplementation(async (_path, override, name: string) => override ?? name) - mocks.branchConflict.mockImplementation(async (_path, branch: string) => - branch === 'feature/test' ? 'remote' : null - ) - - await createWorktree({ branchNameOverride: 'feature/test', baseBranch: 'origin/main' }) - - expect(mocks.add.mock.calls[0][2]).toBe('feature/test-2') - }) - - it('runs the first name PR lookup while the base fetch is still in flight', async () => { - mocks.remoteBase.mockResolvedValue(remoteTrackingBase) - let finishFetch!: () => void - mocks.refresh.mockReturnValue( - new Promise((resolve) => { - finishFetch = () => resolve({ ok: true }) - }) - ) - let fetchPendingAtLookup = false - mocks.githubPr.mockImplementation(async () => { - fetchPendingAtLookup = mocks.refresh.mock.calls.length === 1 - finishFetch?.() - return null - }) - - await createWorktree() - - expect(fetchPendingAtLookup).toBe(true) - expect(mocks.githubPr).toHaveBeenCalledWith('/repo', 'app', {}) - expect(mocks.consume).toHaveBeenCalledWith( - expect.objectContaining({ - branch: 'app', - options: expect.objectContaining({ remoteTrackingBase }) - }) - ) - }) - - it('suffixes past a PR on the first name', async () => { - mocks.githubPr.mockResolvedValueOnce({ number: 7, state: 'closed' }).mockResolvedValue(null) - - await createWorktree() - - expect(mocks.githubPr.mock.calls.map(([, branch]) => branch)).toEqual(['app', 'app-2']) - expect(mocks.consume).toHaveBeenCalledWith(expect.objectContaining({ branch: 'app-2' })) - }) - - it.each([undefined, 'Ubuntu'])( - 'probes the next name for a PR once the first name collides, routed for %s', - async (wslDistro) => { - const routing = wslDistro ? { wslDistro } : {} - mocks.routing.mockReturnValue(routing) - mocks.branchConflict.mockResolvedValueOnce('local').mockResolvedValue(null) - - await createWorktree() - - expect(mocks.githubPr).toHaveBeenCalledOnce() - expect(mocks.githubPr).toHaveBeenCalledWith('/repo', 'app-2', routing) - } - ) - - it('skips a later name that already has a PR', async () => { - mocks.branchConflict.mockResolvedValueOnce('local').mockResolvedValue(null) - mocks.githubPr.mockResolvedValueOnce({ number: 7, state: 'closed' }).mockResolvedValue(null) - - await createWorktree() - - expect(mocks.githubPr.mock.calls.map(([, branch]) => branch)).toEqual(['app-2', 'app-3']) - expect(mocks.consume).toHaveBeenCalledWith(expect.objectContaining({ branch: 'app-3' })) - }) - - it('reports the naming failure when the started base fetch also fails', async () => { - mocks.remoteBase.mockResolvedValue(remoteTrackingBase) - mocks.refresh.mockRejectedValue(new Error('offline')) - mocks.branchConflict.mockResolvedValue('local') - const unhandled = vi.fn() - process.on('unhandledRejection', unhandled) - try { - await expect(createWorktree()).rejects.toThrow('Branch "app-') - await new Promise((resolve) => setImmediate(resolve)) - expect(mocks.refresh).toHaveBeenCalledOnce() - expect(unhandled).not.toHaveBeenCalled() - expect(mocks.consume).not.toHaveBeenCalled() - } finally { - process.off('unhandledRejection', unhandled) - } - }) - - it('runs the first name PR lookup while the origin fetch is still in flight', async () => { - mocks.hasBase.mockResolvedValue(false) - let finishFetch!: () => void - mocks.fetch.mockReturnValue( - new Promise((resolve) => { - finishFetch = resolve - }) - ) - let fetchPendingAtLookup = false - mocks.githubPr.mockImplementation(async () => { - fetchPendingAtLookup = mocks.fetch.mock.calls.length === 1 - finishFetch?.() - return null - }) - - await createWorktree() - - expect(fetchPendingAtLookup).toBe(true) - expect(mocks.fetch).toHaveBeenCalledWith('/repo', 'origin', {}) - expect(mocks.consume).toHaveBeenCalledWith(expect.objectContaining({ branch: 'app' })) - }) - - it('reports the naming failure when the started origin fetch also fails', async () => { - mocks.hasBase.mockResolvedValue(false) - mocks.fetch.mockRejectedValue(new Error('offline')) - mocks.branchConflict.mockResolvedValue('local') - const unhandled = vi.fn() - process.on('unhandledRejection', unhandled) - try { - await expect(createWorktree()).rejects.toThrow('Branch "app-') - await new Promise((resolve) => setImmediate(resolve)) - expect(mocks.fetch).toHaveBeenCalledOnce() - expect(unhandled).not.toHaveBeenCalled() - expect(mocks.consume).not.toHaveBeenCalled() - } finally { - process.off('unhandledRejection', unhandled) - } - }) -}) diff --git a/src/main/runtime/workspaces/creation/runtime-local-worktree-create.test.ts b/src/main/runtime/workspaces/creation/runtime-local-worktree-create.test.ts index f826393b545..8c1e79180ad 100644 --- a/src/main/runtime/workspaces/creation/runtime-local-worktree-create.test.ts +++ b/src/main/runtime/workspaces/creation/runtime-local-worktree-create.test.ts @@ -167,51 +167,6 @@ describe('runtime concurrent creates on one name', () => { }) describe('runtime prepared-worktree replenishment', () => { - it('keeps the failed claim release available when the fallback also fails', async () => { - mocks.consume.mockResolvedValue({ - status: 'miss', - reason: 'prepare_failed', - rearm: mocks.rearm - }) - mocks.add.mockRejectedValue(new Error('normal add failed')) - const rearm: PreparationRearmHolder = { fire: () => {} } - await expect(createWorktree({}, rearm)).rejects.toThrow('normal add failed') - - expect(mocks.rearm).not.toHaveBeenCalled() - rearm.fire() - expect(mocks.rearm).toHaveBeenCalledOnce() - }) - - it('leaves the re-arm holder armed but unfired once probes and include copies finish', async () => { - const rearm: PreparationRearmHolder = { fire: () => {} } - let finishProbe!: (paths: string[]) => void - mocks.resolveShared.mockImplementation( - () => - new Promise((resolve) => { - finishProbe = resolve - }) - ) - let finishCopy!: (paths: string[]) => void - mocks.copyPaths.mockImplementation( - () => - new Promise((resolve) => { - finishCopy = resolve - }) - ) - const creation = createWorktree({}, rearm) - await vi.waitFor(() => expect(mocks.resolveShared).toHaveBeenCalledOnce()) - expect(mocks.rearm).not.toHaveBeenCalled() - finishProbe([]) - await vi.waitFor(() => expect(mocks.copyPaths).toHaveBeenCalledOnce()) - expect(mocks.rearm).not.toHaveBeenCalled() - finishCopy([]) - await creation - // The caller launches terminals before arming, so create must not fire it itself. - expect(mocks.rearm).not.toHaveBeenCalled() - rearm.fire() - expect(mocks.rearm).toHaveBeenCalledOnce() - }) - it('arms the holder even when materialization fails', async () => { mocks.copyPaths.mockRejectedValue(new Error('copy failed')) const rearm: PreparationRearmHolder = { fire: () => {} } @@ -273,27 +228,6 @@ describe('runtime create base without a tracking ref', () => { mocks.hasBase.mockImplementation(async (_repo: string, ref: string) => ref === 'main') }) - it('names the workspace only after the fetch has settled', async () => { - let refreshSettled = false - mocks.refresh.mockImplementation(async () => { - await Promise.resolve() - mocks.hasRemoteRef.mockResolvedValue(true) - refreshSettled = true - return { ok: true } - }) - const settledAtNaming: boolean[] = [] - mocks.canCheckout.mockImplementation(async () => { - settledAtNaming.push(refreshSettled) - return false - }) - - await createWorktree({ baseBranch: 'origin/main', branchNameOverride: 'app' }) - - expect(settledAtNaming).toEqual([true]) - expect(mocks.canCheckout).toHaveBeenCalledWith('/repo', 'app', 'origin/main', {}) - expect(mocks.branchConflict).toHaveBeenCalledWith('/repo', 'app', 'origin/main', {}, undefined) - }) - it('creates from the local branch and reports it when the fetch fails', async () => { mocks.refresh.mockResolvedValue({ ok: false, errorKind: 'git_error' }) @@ -309,22 +243,6 @@ describe('runtime create base without a tracking ref', () => { expect(result.addResult).not.toHaveProperty('baseFallback') expect(result.worktree.baseRef).toBe('main') }) - - it('decides the base before branch reuse and conflict checks', async () => { - mocks.refresh.mockResolvedValue({ ok: false, errorKind: 'git_error' }) - - await createWorktree({ - baseBranch: 'origin/main', - branchNameOverride: 'app', - allowLocalBaseFallback: true - }) - - expect(mocks.refresh.mock.invocationCallOrder[0]).toBeLessThan( - mocks.canCheckout.mock.invocationCallOrder[0] - ) - expect(mocks.canCheckout).toHaveBeenCalledWith('/repo', 'app', 'main', {}) - expect(mocks.branchConflict).toHaveBeenCalledWith('/repo', 'app', 'main', {}, undefined) - }) }) describe('runtime create setup decision', () => { diff --git a/src/main/runtime/workspaces/creation/runtime-local-worktree-setup-trust.test.ts b/src/main/runtime/workspaces/creation/runtime-local-worktree-setup-trust.test.ts index a25148ef94a..fbc1ac7d2e7 100644 --- a/src/main/runtime/workspaces/creation/runtime-local-worktree-setup-trust.test.ts +++ b/src/main/runtime/workspaces/creation/runtime-local-worktree-setup-trust.test.ts @@ -12,8 +12,6 @@ vi.mock('../../../workspace-hooks/worktree-runner-script', () => ({ createSetupRunnerScript: runner.createSetupRunnerScript, resolveSetupRunnerShell: () => undefined })) - -const PRIMARY_YAML = 'scripts:\n setup: pnpm install\n' const PRIMARY_TRUST_CONTENT = 'pnpm install' const sha256 = (text: string): string => createHash('sha256').update(text).digest('hex') @@ -50,14 +48,6 @@ afterEach(() => { }) describe('created worktree setup trust', () => { - it('runs setup when the created branch matches the approved content', async () => { - writeFileSync(join(worktreePath, 'orca.yaml'), PRIMARY_YAML) - const result = await prepare(sha256(PRIMARY_TRUST_CONTENT)) - expect(runner.createSetupRunnerScript).toHaveBeenCalledOnce() - expect(result.setup).toBeDefined() - expect(result.setupTrustMismatch).toBeUndefined() - }) - it('skips setup and default tab commands and reports the branch content when it differs', async () => { writeFileSync( join(worktreePath, 'orca.yaml'), @@ -86,13 +76,6 @@ describe('created worktree setup trust', () => { expect(result.setupTrustMismatch).toBeUndefined() }) - it('skips setup a branch adds when the primary checkout had none', async () => { - writeFileSync(join(worktreePath, 'orca.yaml'), PRIMARY_YAML) - const result = await prepare(sha256('')) - expect(runner.createSetupRunnerScript).not.toHaveBeenCalled() - expect(result.setupTrustMismatch?.scriptContent).toBe(PRIMARY_TRUST_CONTENT) - }) - it('runs the created branch setup as before when an older client sends no approval', async () => { writeFileSync(join(worktreePath, 'orca.yaml'), 'scripts:\n setup: make bootstrap\n') const result = await prepare() diff --git a/src/main/runtime/workspaces/creation/runtime-provisioned-root-adoption.test.ts b/src/main/runtime/workspaces/creation/runtime-provisioned-root-adoption.test.ts index d6e478f07e8..747cf42fbcc 100644 --- a/src/main/runtime/workspaces/creation/runtime-provisioned-root-adoption.test.ts +++ b/src/main/runtime/workspaces/creation/runtime-provisioned-root-adoption.test.ts @@ -75,13 +75,6 @@ describe('adoptRuntimeProvisionedRoot', () => { expect(setWorktreeMeta).not.toHaveBeenCalled() }) - it('refuses a project whose root is not the recipe root', async () => { - const { value } = ports([checkout()]) - await expect( - adoptRuntimeProvisionedRoot('repo-vm', { name: 'fix-login', expectedPath: '/other' }, value) - ).rejects.toThrow('does not match') - }) - it('refuses a project a WSL move is taking before reading its checkout', async () => { const userDataPath = mkdtempSync(join(tmpdir(), 'wsl-move-adopt-')) try { diff --git a/src/main/runtime/workspaces/creation/runtime-worktree-create-git.test.ts b/src/main/runtime/workspaces/creation/runtime-worktree-create-git.test.ts index cbc8370bf12..42e33c8b45a 100644 --- a/src/main/runtime/workspaces/creation/runtime-worktree-create-git.test.ts +++ b/src/main/runtime/workspaces/creation/runtime-worktree-create-git.test.ts @@ -27,32 +27,6 @@ beforeEach(() => { }) describe('explicit local branch reuse on the execution host', () => { - it('routes all probes through the WSL distro and does not compare or reset the remote tip', async () => { - git.mockResolvedValueOnce({ stdout: 'local-ahead\n' }) - git.mockResolvedValueOnce({ stdout: 'refs/heads/feature/test\n' }) - worktrees.mockResolvedValue([]) - await expect( - canCheckoutExistingLocalBranch( - '/repo', - 'feature/test', - 'origin/feature/test', - { wslDistro: 'Ubuntu' }, - true - ) - ).resolves.toBe(true) - expect(git.mock.calls).toEqual([ - [ - ['rev-parse', '--verify', '--quiet', 'refs/heads/feature/test^{commit}'], - { cwd: '/repo', wslDistro: 'Ubuntu' } - ], - [ - ['for-each-ref', '--format=%(refname)', 'refs/heads/feature/test'], - { cwd: '/repo', wslDistro: 'Ubuntu' } - ] - ]) - expect(worktrees).toHaveBeenCalledWith('/repo', { wslDistro: 'Ubuntu' }) - }) - it('rejects a filesystem alias before attempting checkout through WSL', async () => { git.mockResolvedValueOnce({ stdout: 'local-ahead\n' }) git.mockResolvedValueOnce({ stdout: '' }) diff --git a/src/main/runtime/workspaces/creation/runtime-worktree-create-repo-host.test.ts b/src/main/runtime/workspaces/creation/runtime-worktree-create-repo-host.test.ts index e7c936e8bf8..a456737e54c 100644 --- a/src/main/runtime/workspaces/creation/runtime-worktree-create-repo-host.test.ts +++ b/src/main/runtime/workspaces/creation/runtime-worktree-create-repo-host.test.ts @@ -5,7 +5,7 @@ import { RpcDispatcher } from '../../rpc/dispatcher' import type { RpcRequest } from '../../rpc/core' import { REPO_METHODS } from '../../rpc/methods/projects/repo' import { WORKTREE_METHODS } from '../../rpc/methods/workspaces/worktree' -import { agentLaunchWorkspaceFactory } from '../../rpc/methods/agent-launch/agent-launch-worktree-creation' +import '../../rpc/methods/agent-launch/agent-launch-worktree-creation' import { OrcaRuntimeService } from '../../orca-runtime' import { partialRuntimeStore } from '../../orca-runtime-tests/runtime-store-partial-fixture' @@ -93,29 +93,4 @@ describe('a repo id set up on two hosts, named with repoHostId', () => { expect(reached).toHaveBeenCalledTimes(1) expect(reached.mock.calls[0]?.[1]).toMatchObject({ path: '/laptop/dup' }) }) - - it('agent.launch create-worktree keeps the host', async () => { - const reached = stopAtResolvedProject() - const runtime = createRuntime() - const factory = agentLaunchWorkspaceFactory({ runtime }, 'claude') - - await expect( - factory.createWorktree({ - create: { repo: 'id:dup', repoHostId: 'ssh:gpu', name: 'x' }, - startupAgent: 'claude' - }) - ).rejects.toThrow('stop after resolution') - - expect(reached.mock.calls[0]?.[1]).toMatchObject({ path: '/remote/dup' }) - }) - - it('a retried create for one host never coalesces with the same retry for another', async () => { - const runtime = createRuntime() - const run = vi.fn(async () => 'created') - - await runtime.dedupeWorktreeCreate({ repo: 'id:dup', repoHostId: 'local' }, 'retry-1', run) - await runtime.dedupeWorktreeCreate({ repo: 'id:dup', repoHostId: 'ssh:gpu' }, 'retry-1', run) - - expect(run).toHaveBeenCalledTimes(2) - }) }) diff --git a/src/main/runtime/workspaces/lifecycle/workspace-lifecycle-queue.test.ts b/src/main/runtime/workspaces/lifecycle/workspace-lifecycle-queue.test.ts index 0854563f726..a3df2ab3228 100644 --- a/src/main/runtime/workspaces/lifecycle/workspace-lifecycle-queue.test.ts +++ b/src/main/runtime/workspaces/lifecycle/workspace-lifecycle-queue.test.ts @@ -85,13 +85,6 @@ describe('WorkspaceLifecycleQueue', () => { expect(queue.hints()).toEqual({}) }) - it('runs a repeated command again rather than merging it into the first', async () => { - const queue = new WorkspaceLifecycleQueue() - let runs = 0 - const sleep: LifecycleCommand = { kind: 'sleep', run: async () => ++runs } - expect(await Promise.all([queue.submit(KEY, sleep), queue.submit(KEY, sleep)])).toEqual([1, 2]) - }) - it('keeps separate workspaces apart, including one id on two hosts', async () => { const queue = new WorkspaceLifecycleQueue() const log: string[] = [] @@ -193,30 +186,6 @@ describe('WorkspaceLifecycleQueue', () => { expect(queue.hints()).toEqual({}) }) - it('re-runs a repeated command once contact is back', async () => { - const { queue } = manualRetryQueue() - let reachable = false - let sleeps = 0 - const sleep: LifecycleCommand = { - kind: 'sleep', - run: async () => { - sleeps += 1 - return reachable - }, - outcome: (ok) => (ok ? 'settled' : 'unverifiable'), - observe: async () => { - if (!reachable) { - throw new Error('lost') - } - } - } - expect(await queue.submit(KEY, sleep)).toBe(false) - reachable = true - expect(await queue.submit(KEY, sleep)).toBe(true) - expect(sleeps).toBe(2) - expect(queue.pausedKeys().includes(KEY)).toBe(false) - }) - it('pauses on lost contact mid-run, but not on a definite failure', async () => { const queue = new WorkspaceLifecycleQueue() const lost = (message: string): LifecycleCommand => ({ @@ -333,62 +302,9 @@ describe('WorkspaceLifecycleQueue', () => { expect(await other).toBe('woken') expect(clock).toBe(500) }) - - it('never cancels a command because its requester went away', async () => { - const queue = new WorkspaceLifecycleQueue() - const log: string[] = [] - const sleep = gated(log, 'sleep', 'sleep', 'slept') - // A disconnected requester must not cancel the running command. - void queue.submit(KEY, sleep.command).catch(() => {}) - await flush() - sleep.release() - await flush() - expect(log).toEqual(['start sleep', 'end sleep']) - }) - - it('leaves nothing pending in a fresh queue, as after a restart', () => { - const queue = new WorkspaceLifecycleQueue() - expect(queue.hints()).toEqual({}) - expect(queue.creationsSnapshot().creations).toEqual({}) - }) }) describe('WorkspaceLifecycleQueue creates', () => { - it('lists a creation once opened, names its workspace, and drops it on close', () => { - const queue = new WorkspaceLifecycleQueue() - const creation = queue.openCreate({ creationId: 'c1', repoId: 'repo-1' }) - expect(queue.creationsSnapshot().creations).toEqual({ c1: { repoId: 'repo-1' } }) - const opened = queue.creationsSnapshot().revision - creation.named('repo-1::/wt') - expect(queue.creationsSnapshot().creations).toEqual({ - c1: { repoId: 'repo-1', worktreeId: 'repo-1::/wt' } - }) - expect(queue.creationsSnapshot().revision).toBeGreaterThan(opened) - creation.close() - expect(queue.creationsSnapshot().creations).toEqual({}) - }) - - it('mints an id for a caller that sends none', () => { - const queue = new WorkspaceLifecycleQueue() - const creation = queue.openCreate({ repoId: 'repo-1' }) - const ids = Object.keys(queue.creationsSnapshot().creations) - expect(ids).toHaveLength(1) - expect(ids[0]).not.toBe('') - creation.close() - expect(queue.creationsSnapshot().creations).toEqual({}) - }) - - it('leaves a concurrent retry under the same id listed when the first create closes', () => { - const queue = new WorkspaceLifecycleQueue() - const first = queue.openCreate({ creationId: 'c1', repoId: 'repo-1' }) - const second = queue.openCreate({ creationId: 'c1', repoId: 'repo-1' }) - first.named('repo-1::/stale') - first.close() - expect(queue.creationsSnapshot().creations).toEqual({ c1: { repoId: 'repo-1' } }) - second.close() - expect(queue.creationsSnapshot().creations).toEqual({}) - }) - it('frees the workspace queue when a create closes after a retry took over its id', async () => { const queue = new WorkspaceLifecycleQueue() const first = queue.openCreate({ creationId: 'c1', repoId: 'repo-1', keyOf: () => KEY }) @@ -400,15 +316,6 @@ describe('WorkspaceLifecycleQueue creates', () => { retry.close() }) - it('closes once', () => { - const queue = new WorkspaceLifecycleQueue() - const creation = queue.openCreate({ creationId: 'c1', repoId: 'repo-1' }) - creation.close() - const closed = queue.creationsSnapshot().revision - creation.close() - expect(queue.creationsSnapshot().revision).toBe(closed) - }) - it('binds to the workspace once named, so commands on the Creating row wait behind it', async () => { const queue = new WorkspaceLifecycleQueue() const log: string[] = [] @@ -494,22 +401,4 @@ describe('WorkspaceLifecycleQueue membership', () => { // Publish membership and creation changes together in one revision. expect(bumps).toHaveLength(1) }) - - it('drops a failed create at once, with nothing to list', () => { - const queue = new WorkspaceLifecycleQueue() - const rows = listing() - queue.setMembership(rows.membership) - queue.openCreate({ creationId: 'c1', repoId: 'repo-1' }).close() - expect(queue.creationsSnapshot().creations).toEqual({}) - }) - - it('shows a finished delete until a fresh listing drops the row', async () => { - const queue = new WorkspaceLifecycleQueue() - const rows = listing() - queue.setMembership(rows.membership) - expect(await queue.submit(KEY, { kind: 'delete', run: async () => 'deleted' })).toBe('deleted') - expect(queue.hints()).toEqual({ [KEY]: { kind: 'delete', queuedBehind: 0 } }) - rows.applyFreshScan() - expect(queue.hints()).toEqual({}) - }) }) diff --git a/src/main/runtime/workspaces/lineage/runtime-worktree-lineage-controller.test.ts b/src/main/runtime/workspaces/lineage/runtime-worktree-lineage-controller.test.ts index d2d53054b69..12dd22adc2b 100644 --- a/src/main/runtime/workspaces/lineage/runtime-worktree-lineage-controller.test.ts +++ b/src/main/runtime/workspaces/lineage/runtime-worktree-lineage-controller.test.ts @@ -1,7 +1,7 @@ import { afterEach, describe, expect, it, vi } from 'vitest' -import { ALL_TUI_AGENTS } from '../../../../shared/agent-launch/catalog/tui-agent-display-names' +import '../../../../shared/agent-launch/catalog/tui-agent-display-names' import type { CliWorktreeBuilder } from '../../rpc/methods/agent-launch/agent-launch-cwd-cli.test-fixture' -import { toSshExecutionHostId } from '../../../../shared/execution-host' +import '../../../../shared/execution-host' import { WorktreeCreate } from '../../../../shared/rpc-contract/worktree-create-params' import { WorktreeCreate as PreObservationCreate } from '../../../../shared/rpc-contract/worktree-create-params.pre-observation.test-fixture' import { AgentLaunch, AgentLaunchReplay } from '../../../../shared/rpc-contract/agent-launch-params' @@ -10,7 +10,7 @@ import { AgentLaunchReplay as PreObservationReplay } from '../../../../shared/rpc-contract/agent-launch-params.pre-observation.test-fixture' import { RuntimeWorktreeLineageController } from './runtime-worktree-lineage-controller' -import type { FolderWorkspace } from '../../../../shared/folder-workspaces/folder-workspace-types' + import type { ResolvedWorktree } from '../catalog/runtime-worktree-path-identity' import { formatAmbiguousCwdParentMessage, @@ -104,80 +104,6 @@ function parseVersionedCreate( afterEach(() => vi.restoreAllMocks()) describe('consumed cwd parent at the production authority boundary', () => { - it.each(['terminal', 'env', 'task', 'conflict', 'cross-host', 'exact', 'no-parent'] as const)( - 'preserves %s authority/provenance and warnings when choices are unused', - async (kind) => { - const { controller, db, showTerminal, resolveWorktree } = fixture() - const input: WorktreeLineageInput = { ...unresolved } - if (kind === 'env') { - input.envParentWorkspace = 'id:outside-cwd' - } - if (kind === 'task') { - input.comment = 'created for task_creator' - vi.spyOn(db, 'getDispatchContext').mockReturnValue(undefined) - vi.spyOn(db, 'getTask').mockReturnValue( - taskLineageRow({ id: 'task_creator', created_by_terminal_handle: 'term_creator' }) - ) - showTerminal.mockImplementation(async (handle) => { - if (handle !== 'term_creator') { - throw new Error('terminal_not_found') - } - return { worktreeId: root.id } - }) - } else if (kind !== 'terminal' && kind !== 'conflict') { - showTerminal.mockRejectedValue(new Error('terminal_not_found')) - } - if (kind === 'conflict') { - input.envParentWorkspace = choices[0] - } - if (kind === 'cross-host') { - resolveWorktree.mockImplementation(async (selector) => ({ - ...worktree(selector.slice(3)), - hostId: toSshExecutionHostId('other') - })) - } - if (kind === 'exact') { - input.parentWorktree = choices[1] - } - if (kind === 'no-parent') { - input.noParent = true - } - const withoutChoices = { ...input, cwdParentAmbiguous: undefined } - const before = await controller.resolveCreate(withoutChoices, 'local') - const after = await controller.resolveCreate(input, 'local') - expect(after).toEqual(before) - if (kind === 'conflict' || kind === 'cross-host') { - expect(after).toMatchObject({ - kind: 'none', - warnings: [{ code: 'LINEAGE_PARENT_CONTEXT_CONFLICT' }] - }) - } else if (kind === 'no-parent') { - expect(after).toEqual({ kind: 'none', warnings: [] }) - } else { - const source = - kind === 'env' - ? 'env-workspace' - : kind === 'task' - ? 'orchestration-context' - : kind === 'exact' - ? 'explicit-cli-flag' - : 'terminal-context' - expect(after).toMatchObject({ - kind: 'lineage', - origin: kind === 'task' ? 'orchestration' : 'cli', - capture: { source, confidence: kind === 'exact' ? 'explicit' : 'inferred' }, - parent: { worktree: { id: kind === 'env' ? 'outside-cwd' : root.id } } - }) - if (kind === 'terminal') { - expect(after).toHaveProperty('createdByTerminalHandle', 'term_R') - } - if (kind === 'task') { - expect(after).toHaveProperty('taskId', 'task_creator') - } - } - } - ) - it.each(['absent', 'stale-terminal', 'stale-env', 'stale-task', 'terminal-disappears'])( 'refuses a retained positive cwd tie (%s), preserving exact RPC recovery', async (kind) => { @@ -225,29 +151,6 @@ describe('consumed cwd parent at the production authority boundary', () => { } ) - it('retains unique/legacy cwd and missing-parent behavior without inventing authority', async () => { - const { controller, showTerminal } = fixture() - showTerminal.mockRejectedValue(new Error('terminal_not_found')) - for (const cwdParentAmbiguous of [undefined]) { - expect( - await controller.resolveCreate({ ...unresolved, cwdParentAmbiguous }, 'local') - ).toMatchObject({ - kind: 'lineage', - origin: 'cli', - capture: { source: 'cwd-context', confidence: 'inferred' }, - parent: { worktree: { id: siblingId } } - }) - } - expect( - await controller.resolveCreate({ ...unresolved, cwdParentWorktree: 'id:stale' }, 'local') - ).toMatchObject({ - kind: 'none', - warnings: expect.arrayContaining([ - expect.objectContaining({ code: 'LINEAGE_PARENT_CONTEXT_MISSING' }) - ]) - }) - }) - it.each([false, true])( 'executes old/new client and host schema compatibility (newClient=%s)', async (newClient) => { @@ -286,222 +189,4 @@ describe('consumed cwd parent at the production authority boundary', () => { } } ) - it('retains complete historical transforms, refinements and actual launch embeddings', () => { - for (const newHost of [false, true]) { - for (const route of ['create', 'launch', 'replay'] as const) { - for (const value of [undefined, true, false, null, 'true', 1, [], {}]) { - const request = { repo: 'id:repo-a', cwdParentAmbiguous: value, futureField: 1 } - if (newHost && value !== undefined && value !== true) { - expect(() => parseVersionedCreate(newHost, route, request)).toThrow() - } else { - const parsed = parseVersionedCreate(newHost, route, request) - expect(parsed).not.toHaveProperty('futureField') - expect('cwdParentAmbiguous' in parsed ? parsed.cwdParentAmbiguous : undefined).toBe( - newHost ? value : undefined - ) - } - } - for (const invalid of [ - { repo: 123 }, - { parentWorktree: 'id:p', noParent: true }, - { parentWorkspace: 'folder:p', noParent: true }, - { parentWorkspace: 'folder:p', parentWorktree: 'id:p' }, - { startupPrompt: 'hello' }, - { linkedWorkItem: { invalid: true }, linkedTaskSourceContext: { invalid: true } } - ]) { - expect(() => - parseVersionedCreate(newHost, route, { repo: 'id:repo-a', ...invalid }) - ).toThrow() - } - const linkedWorkItem = { - provider: 'github', - type: 'issue', - number: 1, - title: 'Issue', - url: 'https://github.com/example/repo/issues/1' - } - const linkedTaskSourceContext = { - provider: 'github', - projectId: 'project', - hostId: 'local' - } - expect( - parseVersionedCreate(newHost, route, { - repo: 'id:repo-a', - linkedWorkItem, - linkedTaskSourceContext - }) - ).toMatchObject({ linkedWorkItem, linkedTaskSourceContext }) - expect(() => - parseVersionedCreate(newHost, route, { - repo: 'id:repo-a', - linkedWorkItem, - linkedTaskSourceContext: { ...linkedTaskSourceContext, provider: 'gitlab' } - }) - ).toThrow('Linked work item and source context identities must match') - expect( - parseVersionedCreate(newHost, route, { - repo: 'id:repo-a', - setupDecision: 'unknown', - telemetrySource: 'invalid-telemetry-source', - createdWithAgent: 'unknown', - startupAgent: 'codex', - startupPrompt: 'hello' - }) - ).toMatchObject({ - repo: 'id:repo-a', - setupDecision: undefined, - telemetrySource: undefined, - createdWithAgent: undefined, - startupAgent: 'codex', - startupPrompt: 'hello' - }) - expect( - parseVersionedCreate(newHost, route, { - repo: 'id:repo-a', - telemetrySource: 'unknown' - }) - ).toMatchObject({ repo: 'id:repo-a', telemetrySource: 'unknown' }) - } - } - for (const schema of [ - PreObservationLaunch, - PreObservationReplay, - AgentLaunch, - AgentLaunchReplay - ]) { - expect( - schema.safeParse({ - agent: 'codex', - operationId: 'malformed', - target: { kind: 'existing', worktree: 'id:r' } - }).success - ).toBe(false) - expect(() => - schema.parse({ - agent: 'codex', - operationId: `${Date.now()}-${'a'.repeat(32)}`, - sessionId: 'claude_session', - target: { kind: 'existing', worktree: 'id:r' } - }) - ).toThrow('Launch session id must be named for its agent') - } - expect( - PreObservationReplay.safeParse({ - agent: 'codex', - target: { kind: 'existing', worktree: 'id:r' } - }).success - ).toBe(false) - }) - it.each([false, null, 'true', 1, [], {}])( - 'rejects malformed positive literal %j before runtime reads', - (value) => { - const { resolveWorktree, showTerminal } = fixture() - expect( - WorktreeCreate.safeParse({ repo: 'id:repo-a', name: 'child', cwdParentAmbiguous: value }) - .success - ).toBe(false) - expect(resolveWorktree).not.toHaveBeenCalled() - expect(showTerminal).not.toHaveBeenCalled() - } - ) - it('does not recheck terminal liveness after its existing validation read', async () => { - const { controller, showTerminal } = fixture() - showTerminal.mockImplementationOnce(async () => { - showTerminal.mockRejectedValue(new Error('terminal_not_found')) - return { worktreeId: root.id } - }) - expect(await controller.resolveCreate(unresolved, 'local')).toMatchObject({ - capture: { source: 'terminal-context' }, - createdByTerminalHandle: 'term_R' - }) - expect(showTerminal).toHaveBeenCalledTimes(1) - }) - it('keeps the complete original refusal bounded for plain creation and every finite agent', () => { - const codex = [ - 'Orca did not create the workspace or start codex because more than one workspace matched the current directory when this request was prepared.', - 'Run `orca worktree list --limit 10000 --json` with the same server and host selection. Match path and host; if truncated, repeat with a larger --limit using totalCount. Choose id: (or identity: for a shared ID), then add --parent-worktree to your original invocation, or deliberately use --no-parent. Pass the selector as one argument quoted for your shell.', - 'To choose a parent, start a fresh request without --retry-launch; retrying the old ID returns this refusal.' - ].join('\n') - expect(formatAmbiguousCwdParentMessage('codex')).toBe(codex) - expect(formatAmbiguousCwdParentMessage()).toBe(codex.replace(' or start codex', '')) - expect(formatAmbiguousCwdParentMessage()).toHaveLength(622) - expect( - Math.max(...ALL_TUI_AGENTS.map((agent) => formatAmbiguousCwdParentMessage(agent).length)) - ).toBe(650) - for (const agent of ALL_TUI_AGENTS) { - expect(formatAmbiguousCwdParentMessage(agent)).toBe( - codex.replace('or start codex', `or start ${agent}`) - ) - expect(formatAmbiguousCwdParentMessage(agent).length).toBeLessThan(4224) - } - }) -}) - -describe('caller parent for a create on another machine', () => { - it('skips a stale terminal and keeps the valid environment parent, as a same-machine create does', async () => { - const { controller, showTerminal } = fixture() - showTerminal.mockRejectedValue(new Error('terminal_not_found')) - await expect( - controller.resolveCallerParentForAnotherStore( - { - envParentWorkspace: 'id:repo-a::/tmp/env-parent', - callerTerminalHandle: 'term_stale' - }, - 'mac-install' - ) - ).resolves.toEqual({ - parent: { - installationId: 'mac-install', - workspaceKey: 'worktree:repo-a::/tmp/env-parent', - instanceId: 'repo-a::/tmp/env-parent' - }, - source: 'env-workspace' - }) - }) -}) - -describe('a task id in the note of a create on another machine', () => { - it('names the folder workspace the task was made in', async () => { - const db = makeRuntimeOrchestrationDb() - vi.spyOn(db, 'getDispatchContext').mockReturnValue(undefined) - vi.spyOn(db, 'getTask').mockReturnValue( - taskLineageRow({ id: 'task_folder', created_by_terminal_handle: 'term_folder' }) - ) - const controller = new RuntimeWorktreeLineageController({ - getStore: () => null, - getDb: () => db, - showTerminal: async () => ({ worktreeId: 'folder:f-1' }), - resolveWorktree: async () => { - throw new Error('selector_not_found') - } - }) - const folderWorkspace: FolderWorkspace = { - id: 'f-1', - projectGroupId: 'group-1', - name: 'Release', - folderPath: '/work/multi', - linkedTask: null, - comment: '', - isArchived: false, - isUnread: false, - isPinned: false, - sortOrder: 0, - lastActivityAt: 0, - createdAt: 0, - updatedAt: 0 - } - vi.spyOn(controller, 'resolveParent').mockImplementation(async (selector) => { - if (selector !== 'id:folder:f-1') { - throw new Error('selector_not_found') - } - return { type: 'folder', workspaceKey: 'folder:f-1', folderWorkspace, instanceId: null } - }) - await expect( - controller.resolveCallerParentForAnotherStore({ comment: 'for task_folder' }, 'desktop-1') - ).resolves.toEqual({ - parent: { installationId: 'desktop-1', workspaceKey: 'folder:f-1', instanceId: 'f-1' }, - source: 'orchestration-context' - }) - }) }) diff --git a/src/main/runtime/workspaces/lineage/runtime-worktree-lineage-resolution.test.ts b/src/main/runtime/workspaces/lineage/runtime-worktree-lineage-resolution.test.ts deleted file mode 100644 index 3ee33542636..00000000000 --- a/src/main/runtime/workspaces/lineage/runtime-worktree-lineage-resolution.test.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { worktreeWorkspaceKey } from '../../../../shared/workspace-scope' -import { resolveRuntimeWorktreeCreateLineage } from './runtime-worktree-lineage-resolution' - -const unused = async (): Promise => { - throw new Error('not reached') -} - -describe('resolveRuntimeWorktreeCreateLineage', () => { - it('records an app-picked parent on another machine as manual', async () => { - const parent = { - installationId: 'mac-install', - workspaceKey: worktreeWorkspaceKey('repo::/src/parent'), - instanceId: 'parent-instance' - } - - await expect( - resolveRuntimeWorktreeCreateLineage( - { crossStoreParent: parent, parentWorkspaceOrigin: 'manual' }, - { - resolveParent: unused, - resolveWorktreeParent: unused, - resolveTaskCandidate: unused, - resolveCaller: unused - } - ) - ).resolves.toEqual({ - kind: 'crossStore', - parent, - origin: 'manual', - capture: { source: 'manual-action', confidence: 'explicit' } - }) - }) -}) diff --git a/src/main/runtime/workspaces/metadata/runtime-managed-worktree-metadata-wsl-move.test.ts b/src/main/runtime/workspaces/metadata/runtime-managed-worktree-metadata-wsl-move.test.ts index 0a6de5608cf..016831311b5 100644 --- a/src/main/runtime/workspaces/metadata/runtime-managed-worktree-metadata-wsl-move.test.ts +++ b/src/main/runtime/workspaces/metadata/runtime-managed-worktree-metadata-wsl-move.test.ts @@ -100,13 +100,6 @@ describe('updateRuntimeManagedWorktreeMetadata under a WSL move', () => { expect(p.notifyChanged).not.toHaveBeenCalled() }) - it('refuses a user edit', async () => { - await expect(update(MOVING, { comment: 'mine' })).rejects.toBeInstanceOf( - WslMoveProjectFencedError - ) - expect(store.getWorktreeMeta(MOVING)).toBeUndefined() - }) - it('refuses a lineage change even alongside passive fields', async () => { await expect( update(MOVING, { isUnread: true, lineage: { noParent: true } }) diff --git a/src/main/runtime/workspaces/persisted-surfaces/sweep-terminal-placement.test.ts b/src/main/runtime/workspaces/persisted-surfaces/sweep-terminal-placement.test.ts index fb06711a685..10e19b330d0 100644 --- a/src/main/runtime/workspaces/persisted-surfaces/sweep-terminal-placement.test.ts +++ b/src/main/runtime/workspaces/persisted-surfaces/sweep-terminal-placement.test.ts @@ -120,18 +120,6 @@ describe('main places the swept workspace’s live terminals, layout-owner build expect(listed?.tabId).not.toMatch(/^pty:/) }) - it('a listing that does not ask places nothing', async () => { - const { store, runtime } = await hostWith([session('pty-swept')]) - - const context = { runtime } - await terminalListHandler()( - { worktree: `id:${SWEPT}`, limit: 200, requireFreshPtyLiveness: true }, - context - ) - - expect(boundPtyIds(store, SWEPT)).toEqual([]) - }) - it('leaves a terminal whose registration is still binding its pane, as a census does', async () => { const { store, runtime } = await hostWith([session('pty-registering')]) runtime.beginPtyRegistration('pty-registering', 'pty-registering-inc') diff --git a/src/main/runtime/workspaces/process-listing/runtime-worktree-ps-summaries.test.ts b/src/main/runtime/workspaces/process-listing/runtime-worktree-ps-summaries.test.ts deleted file mode 100644 index ba245867bb7..00000000000 --- a/src/main/runtime/workspaces/process-listing/runtime-worktree-ps-summaries.test.ts +++ /dev/null @@ -1,269 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { buildRuntimeWorktreePsSummaries } from './runtime-worktree-ps-summaries' -import type { ResolvedWorktree } from '../catalog/runtime-worktree-path-identity' -import type { RuntimeStore } from '../../runtime-store-contract' - -describe('buildRuntimeWorktreePsSummaries', () => { - it('preserves persisted host ownership over the resolved row fallback', () => { - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the summary builder reads only these worktree fields. - const worktree = { - id: 'repo-1::/workspace/app', - repoId: 'repo-1', - hostId: 'ssh:resolved-host', - path: '/workspace/app', - branch: 'feature', - isArchived: false, - isMainWorktree: false, - parentWorktreeId: null, - childWorktreeIds: [], - lineage: null, - lastActivityAt: 0 - } as unknown as ResolvedWorktree - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the summary builder reads only these store methods. - const store = { - getRepos: () => [], - getWorktreeMeta: () => ({ - hostId: 'ssh:persisted-host', - linkedPR: 42, - linkedItems: [{ provider: 'github', type: 'pr', number: 57 }] - }), - getAllWorktreeMeta: () => ({}), - getFolderWorkspaces: () => [], - getProjectGroups: () => [] - } as unknown as RuntimeStore - - const summary = buildRuntimeWorktreePsSummaries({ - store, - resolvedWorktrees: [worktree], - platformByRepoId: new Map() - }).get(worktree.id) - - expect(summary?.hostId).toBe('ssh:persisted-host') - expect(summary?.linkedItems).toEqual([ - { provider: 'github', type: 'pr', number: 57 }, - { provider: 'github', type: 'pr', number: 42 } - ]) - }) - - it("sends a renamed workspace's older ids so phone history still finds its sessions", () => { - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the builder reads only these fields for a git row. - const worktree = { - id: 'repo-1::/workspace/app', - repoId: 'repo-1', - path: '/workspace/app', - branch: 'feature', - isArchived: false, - isMainWorktree: false, - parentWorktreeId: null, - childWorktreeIds: [], - lineage: null, - lastActivityAt: 0 - } as unknown as ResolvedWorktree - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the summary builder reads only these store methods. - const store = { - getRepos: () => [], - getWorktreeMeta: () => ({ priorWorktreeIds: ['repo-1::/workspace/old-app'] }), - getAllWorktreeMeta: () => ({}), - getFolderWorkspaces: () => [], - getProjectGroups: () => [] - } as unknown as RuntimeStore - - const summary = buildRuntimeWorktreePsSummaries({ - store, - resolvedWorktrees: [worktree], - platformByRepoId: new Map() - }).get(worktree.id) - - expect(summary?.priorWorktreeIds).toEqual(['repo-1::/workspace/old-app']) - }) - - it('carries the error of a failed delete the host still lists', () => { - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the builder reads only these fields for a git row. - const worktree = { - id: 'repo-1::/workspace/app', - repoId: 'repo-1', - path: '/workspace/app', - branch: 'feature', - isArchived: false, - isMainWorktree: false, - parentWorktreeId: null, - childWorktreeIds: [], - lineage: null, - lastActivityAt: 0, - removalError: 'Operation not permitted' - } as unknown as ResolvedWorktree - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the builder reads only these store members. - const store = { - getRepos: () => [], - getWorktreeMeta: () => undefined, - getAllWorktreeMeta: () => ({}), - getFolderWorkspaces: () => [], - getProjectGroups: () => [] - } as unknown as RuntimeStore - - const summary = buildRuntimeWorktreePsSummaries({ - store, - resolvedWorktrees: [worktree], - platformByRepoId: new Map() - }).get(worktree.id) - - expect(summary?.removalError).toBe('Operation not permitted') - }) - - it('sends the Workspace options row facts the sidebar reads', () => { - const row = (id: string, fields: Record) => - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the builder reads only these fields for a git row. - ({ - id, - repoId: 'repo-1', - path: `/workspace/${id}`, - branch: 'feature', - head: 'abc1234', - isArchived: false, - isMainWorktree: false, - parentWorktreeId: null, - childWorktreeIds: [], - lineage: null, - lastActivityAt: 0, - ...fields - }) as unknown as ResolvedWorktree - const worktrees = [ - row('main', { isMainWorktree: true, branch: 'refs/heads/main' }), - // The phone's own guess calls both of these default-branch rows; the host says not. - row('main-detached', { isMainWorktree: true, branch: '' }), - row('provisioned', { isMainWorktree: true, ephemeralVmCheckoutMode: 'provisioned-root' }), - row('automation', { automationProvenance: { kind: 'created-by-automation' } }), - row('cli', { cliProvenance: { kind: 'created-by-cli', createdAt: 0 } }), - row('folder-root', { repoId: 'folder-repo', isMainWorktree: true, branch: '', head: '' }) - ] - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the builder reads only these store members. - const store = { - getRepos: () => [ - { id: 'repo-1', path: '/r', displayName: 'Repo', badgeColor: '#000', addedAt: 0 }, - { - id: 'folder-repo', - path: '/f', - displayName: 'Folder', - badgeColor: '#000', - addedAt: 0, - kind: 'folder' - } - ], - getWorktreeMeta: () => undefined, - getAllWorktreeMeta: () => ({}), - getFolderWorkspaces: () => [], - getProjectGroups: () => [] - } as unknown as RuntimeStore - - const summaries = buildRuntimeWorktreePsSummaries({ - store, - resolvedWorktrees: worktrees, - platformByRepoId: new Map() - }) - const summary = (id: string) => summaries.get(id) - - expect(summary('main')?.defaultBranch).toBe(true) - expect(summary('folder-root')?.defaultBranch).toBe(false) - expect(summary('main-detached')?.defaultBranch).toBe(false) - expect(summary('main-detached')?.detachedHead).toBe(true) - expect(summary('provisioned')?.defaultBranch).toBe(false) - expect(summary('automation')?.automationCreated).toBe(true) - expect(summary('cli')?.cliCreated).toBe(true) - expect(summary('main')).not.toHaveProperty('automationCreated') - expect(summary('main')).not.toHaveProperty('cliCreated') - expect(summary('main')).not.toHaveProperty('detachedHead') - expect(summary('folder-root')).not.toHaveProperty('detachedHead') - }) - - it("names the automation that made a row, and carries the branch PR's title, link and checks", () => { - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the builder reads only these fields for a git row. - const worktree = { - id: 'repo-1::/workspace/app', - repoId: 'repo-1', - path: '/workspace/app', - branch: 'refs/heads/feature', - head: 'abc1234', - isArchived: false, - isMainWorktree: false, - parentWorktreeId: null, - childWorktreeIds: [], - lineage: null, - lastActivityAt: 0, - automationProvenance: { - kind: 'created-by-automation', - automationNameSnapshot: ' Nightly dependency bump ' - } - } as unknown as ResolvedWorktree - const pr = { - number: 7, - title: 'Bump deps', - state: 'open', - url: 'https://github.com/o/r/pull/7', - checksStatus: 'success', - updatedAt: '', - mergeable: 'MERGEABLE', - headSha: 'not-sent' - } - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the builder reads only these store members. - const store = { - getRepos: () => [ - { id: 'repo-1', path: '/r', displayName: 'Repo', badgeColor: '#000', addedAt: 0 } - ], - getWorktreeMeta: () => undefined, - getAllWorktreeMeta: () => ({}), - getFolderWorkspaces: () => [], - getProjectGroups: () => [], - getGitHubCache: () => ({ pr: { 'repo-1::feature': { data: pr, fetchedAt: 1 } }, issue: {} }) - } as unknown as RuntimeStore - - const summary = buildRuntimeWorktreePsSummaries({ - store, - resolvedWorktrees: [worktree], - platformByRepoId: new Map() - }).get(worktree.id) - - expect(summary?.automationName).toBe('Nightly dependency bump') - expect(summary?.linkedPR).toEqual({ - number: 7, - state: 'open', - title: 'Bump deps', - url: 'https://github.com/o/r/pull/7', - checksStatus: 'success' - }) - }) - - it('leaves the automation name off when the record has none, as older hosts do', () => { - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the builder reads only these fields for a git row. - const worktree = { - id: 'repo-1::/workspace/app', - repoId: 'repo-1', - path: '/workspace/app', - branch: 'feature', - isArchived: false, - isMainWorktree: false, - parentWorktreeId: null, - childWorktreeIds: [], - lineage: null, - lastActivityAt: 0, - // Blank reads as no name. - automationProvenance: { kind: 'created-by-automation', automationNameSnapshot: ' ' } - } as unknown as ResolvedWorktree - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the builder reads only these store members. - const store = { - getRepos: () => [], - getWorktreeMeta: () => undefined, - getAllWorktreeMeta: () => ({}), - getFolderWorkspaces: () => [], - getProjectGroups: () => [] - } as unknown as RuntimeStore - - const summary = buildRuntimeWorktreePsSummaries({ - store, - resolvedWorktrees: [worktree], - platformByRepoId: new Map() - }).get(worktree.id) - - expect(summary?.automationCreated).toBe(true) - expect(summary).not.toHaveProperty('automationName') - }) -}) diff --git a/src/main/runtime/workspaces/process-listing/runtime-worktree-ps-unverifiable-terminals.test.ts b/src/main/runtime/workspaces/process-listing/runtime-worktree-ps-unverifiable-terminals.test.ts deleted file mode 100644 index 67d79a614aa..00000000000 --- a/src/main/runtime/workspaces/process-listing/runtime-worktree-ps-unverifiable-terminals.test.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { describe, expect, it } from 'vitest' -import type { RuntimeWorktreePsSummary } from '../../../../shared/runtime-worktree-contracts' -import { applyRuntimeWorktreePsUnverifiableTerminals } from './runtime-worktree-ps-unverifiable-terminals' - -describe('unverifiable terminal attribution', () => { - it('follows the host PTY record, not a stale pane, as the live pass does', () => { - const summaries = new Map>([ - ['wt-old', {}], - ['wt-new', {}] - ]) - applyRuntimeWorktreePsUnverifiableTerminals({ - countedPtyIds: new Set(), - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the pass reads only ptyId and worktreeId. - leaves: [{ ptyId: 'pty-1', worktreeId: 'wt-old' } as never], - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the pass reads only ptyId and worktreeId. - ptysById: new Map([['pty-1', { ptyId: 'pty-1', worktreeId: 'wt-new' } as never]]), - getLivenessVerdict: () => ({ status: 'unverifiable', reason: 'lost contact' }), - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: only the counters this pass writes are read back. - getSummary: (id) => (summaries.get(id) as RuntimeWorktreePsSummary | undefined) ?? null - }) - expect(summaries.get('wt-new')?.unverifiableTerminalCount).toBe(1) - expect(summaries.get('wt-old')?.unverifiableTerminalCount).toBeUndefined() - }) -}) diff --git a/src/main/runtime/workspaces/process-listing/runtime-worktree-structured-agent-rows-liveness.test.ts b/src/main/runtime/workspaces/process-listing/runtime-worktree-structured-agent-rows-liveness.test.ts index 78c493715d1..ecfb497fb17 100644 --- a/src/main/runtime/workspaces/process-listing/runtime-worktree-structured-agent-rows-liveness.test.ts +++ b/src/main/runtime/workspaces/process-listing/runtime-worktree-structured-agent-rows-liveness.test.ts @@ -22,7 +22,7 @@ vi.mock('../../../telemetry/cohort-classifier', () => ({ /** Exercise journal to worktree-status liveness: feed summaries never retract, but the store drops closed chats, including unanswered approvals whose retained status stays attention. */ const WORKTREE_ID = 'repo-1::/workspace/app' const SESSION = 'a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d' -const SUBJECT = makeStructuredAgentStatusSubject( +makeStructuredAgentStatusSubject( { executionHostId: 'local', wslDistro: null, @@ -164,29 +164,4 @@ describe('worktree ps and a closed structured chat', () => { expect(worktreeFor(store).agents).toHaveLength(0) }) - - it('keeps an aged host-held approval state authoritative', async () => { - const { store, published } = await awaitingApproval() - const aged = { - ...published.at(-1)!, - hostExecutionOwned: true as const, - updatedAt: Date.now() - 30 * 60 * 1000 - 1 - } - store.ingestStructuredStatus(aged, SUBJECT) - const row = worktreeFor(store) - expect(row.agents).toHaveLength(1) - expect(row.agents[0]?.state).toBe('blocked') - expect(row.status).toBe('permission') - expect(row.agents[0]?.updatedAt).toBe(aged.updatedAt) - }) - - it('lets an aged approval decay once the host no longer owns the child', async () => { - const { store, published } = await awaitingApproval() - const { hostExecutionOwned: _owned, ...held } = published.at(-1)! - store.ingestStructuredStatus({ ...held, updatedAt: Date.now() - 30 * 60 * 1000 - 1 }, SUBJECT) - const row = worktreeFor(store) - expect(row.agents).toHaveLength(1) - expect(row.agents[0]?.state).toBe('blocked') - expect(row.status).toBe('inactive') - }) }) diff --git a/src/main/runtime/workspaces/process-listing/worktree-ps-degraded-repo-scan.test.ts b/src/main/runtime/workspaces/process-listing/worktree-ps-degraded-repo-scan.test.ts index 639725c5b4b..be50abb4757 100644 --- a/src/main/runtime/workspaces/process-listing/worktree-ps-degraded-repo-scan.test.ts +++ b/src/main/runtime/workspaces/process-listing/worktree-ps-degraded-repo-scan.test.ts @@ -313,25 +313,6 @@ describe('successful scan cache on timeout', () => { } ) - it('does not reuse failed cached local rows on timeout', async () => { - vi.useFakeTimers() - try { - const { runtime, repo, successfulScan } = cachedScanFixture(null) - runtime.scanGit - .mockResolvedValueOnce({ ...successfulScan, ok: false }) - .mockImplementation(neverSettles) - await runtime.scanRows(repo) - await vi.advanceTimersByTimeAsync(31_000) - - const result = await advancePastRepoScanBudget(runtime.scanRows(repo)) - - expect(result.rows[0]).toMatchObject({ branch: '', head: '', displayName: 'app' }) - expect(result.ok).toBe(false) - } finally { - vi.useRealTimers() - } - }) - it('keeps a failed-removal checkout while the successful Git result awaits its listing overlay', async () => { vi.useFakeTimers() const checkoutExists = vi @@ -374,45 +355,6 @@ describe('worktree.ps on a degraded repo scan', () => { listWorktreesStrictMock.mockResolvedValue([]) }) - it('keeps persisted worktrees when a remote scan stalls past the per-repo budget', async () => { - vi.useFakeTimers() - try { - const runtime = new OrcaRuntimeService( - partialRuntimeStore(makeStore({ connectionId: 'ssh-remote-1' })) - ) - - const result = await advancePastRepoScanBudget(runtime.getWorktreePs(10_000)) - - expect(result.worktrees.map((worktree) => worktree.worktreeId)).toContain(WORKTREE_ID) - } finally { - vi.useRealTimers() - } - }) - - it('keeps persisted worktrees when a remote repo is unreachable', async () => { - const runtime = new OrcaRuntimeService( - partialRuntimeStore(makeStore({ connectionId: 'ssh-remote-1' })) - ) - - const result = await runtime.getWorktreePs(10_000) - - expect(result.worktrees.map((worktree) => worktree.worktreeId)).toContain(WORKTREE_ID) - }) - - it('keeps persisted worktrees when a local scan stalls past the per-repo budget', async () => { - vi.useFakeTimers() - try { - listWorktreesStrictMock.mockImplementation(neverSettles) - const runtime = new OrcaRuntimeService(partialRuntimeStore(makeStore())) - - const result = await advancePastRepoScanBudget(runtime.getWorktreePs(10_000)) - - expect(result.worktrees.map((worktree) => worktree.worktreeId)).toContain(WORKTREE_ID) - } finally { - vi.useRealTimers() - } - }) - // withTimeout also falls back on rejection; absorb real scan failures before they can restore stale rows. it('treats a rejected scan as a real answer instead of restoring persisted worktrees', async () => { listWorktreesStrictMock.mockRejectedValue(new Error('git unavailable')) @@ -423,47 +365,6 @@ describe('worktree.ps on a degraded repo scan', () => { expect(result.worktrees.map((worktree) => worktree.worktreeId)).not.toContain(WORKTREE_ID) }) - it('still reports selector_not_found for a local worktree when the scan rejects', async () => { - listWorktreesStrictMock.mockRejectedValue(new Error('git unavailable')) - const runtime = new OrcaRuntimeService(partialRuntimeStore(makeStore())) - - await expect(runtime.showManagedWorktree(`id:${WORKTREE_ID}`)).rejects.toThrow( - 'selector_not_found' - ) - }) - - it('caches a zero-row local scan instead of rescanning on every poll', async () => { - vi.useFakeTimers() - try { - listWorktreesStrictMock.mockResolvedValue([]) - const runtime = new OrcaRuntimeService(partialRuntimeStore(makeStore())) - - await runtime.getWorktreePs(10_000) - await vi.advanceTimersByTimeAsync(2_000) - await runtime.getWorktreePs(10_000) - - expect(listWorktreesStrictMock).toHaveBeenCalledTimes(1) - } finally { - vi.useRealTimers() - } - }) - - it('caches a failed local scan instead of respawning Git on every poll', async () => { - vi.useFakeTimers() - try { - listWorktreesStrictMock.mockRejectedValue(new Error('spawn git EAGAIN')) - const runtime = new OrcaRuntimeService(partialRuntimeStore(makeStore())) - - await runtime.getWorktreePs(10_000) - await vi.advanceTimersByTimeAsync(2_000) - await runtime.getWorktreePs(10_000) - - expect(listWorktreesStrictMock).toHaveBeenCalledTimes(1) - } finally { - vi.useRealTimers() - } - }) - it('does not overwrite another execution host’s healthy rows when this host’s scan stalls', async () => { vi.useFakeTimers() try { @@ -529,21 +430,6 @@ describe('worktree.ps on a degraded repo scan', () => { } }) - it('does not re-spawn the git scan on every poll while a scan stays stalled', async () => { - vi.useFakeTimers() - try { - listWorktreesStrictMock.mockImplementation(neverSettles) - const runtime = new OrcaRuntimeService(partialRuntimeStore(makeStore())) - - await advancePastRepoScanBudget(runtime.getWorktreePs(10_000)) - await advancePastRepoScanBudget(runtime.getWorktreePs(10_000)) - - expect(listWorktreesStrictMock).toHaveBeenCalledTimes(1) - } finally { - vi.useRealTimers() - } - }) - it('drops and prunes a worktree that a healthy scan no longer reports', async () => { const removeWorktreeLineage = vi.fn() listWorktreesStrictMock.mockResolvedValue([ @@ -578,24 +464,6 @@ describe('worktree.ps on a degraded repo scan', () => { } }) - it('does not re-read persisted worktree metadata on a healthy scan', async () => { - listWorktreesStrictMock.mockResolvedValue([ - { path: REPO_PATH, head: 'abc', branch: 'main', isBare: false, isMainWorktree: true } - ]) - const store = makeStore() - const getWorktreeMetaForHost = vi.fn((id: string, hostId: string) => - hostId === 'local' ? store.getWorktreeMeta(id) : undefined - ) - const runtime = new OrcaRuntimeService( - partialRuntimeStore({ ...store, getWorktreeMetaForHost }) - ) - - await runtime.getWorktreePs(10_000) - - expect(getWorktreeMetaForHost).toHaveBeenCalledWith(MAIN_WORKTREE_ID, 'local') - expect(store.getAllWorktreeMeta).toHaveBeenCalledTimes(1) - }) - it('lists restored worktrees while a scan is stalled and still hides agent scratch', async () => { vi.useFakeTimers() try { diff --git a/src/main/runtime/workspaces/process-listing/worktree-ps-host-scope.test.ts b/src/main/runtime/workspaces/process-listing/worktree-ps-host-scope.test.ts index 547849065bb..20820a20d24 100644 --- a/src/main/runtime/workspaces/process-listing/worktree-ps-host-scope.test.ts +++ b/src/main/runtime/workspaces/process-listing/worktree-ps-host-scope.test.ts @@ -105,15 +105,6 @@ describe('worktree.ps host coverage', () => { ]) }) - it('names every host the page covers', async () => { - const runtime = new OrcaRuntimeService(makeStore() as never) - - const result = await runtime.getWorktreePs(10_000) - - expect(result.hostScope?.hostIds).toEqual(['local', `ssh:${SSH_CONNECTION_ID}`]) - expect(result.hostScope?.omittedHostIds).toEqual([]) - }) - it('keeps a remote row in the page when the cap cannot hold every local row', async () => { const runtime = new OrcaRuntimeService(makeStore() as never) diff --git a/src/main/runtime/workspaces/removal/missing-worktree-terminal-reconciliation.test.ts b/src/main/runtime/workspaces/removal/missing-worktree-terminal-reconciliation.test.ts index 8aba557b85a..f75ba99cb50 100644 --- a/src/main/runtime/workspaces/removal/missing-worktree-terminal-reconciliation.test.ts +++ b/src/main/runtime/workspaces/removal/missing-worktree-terminal-reconciliation.test.ts @@ -81,42 +81,6 @@ describe('stopMissingWorktreeTerminals', () => { expect(localProvider.shutdown).not.toHaveBeenCalled() }) - it('still stops graph-visible sessions when the owning provider is unavailable', async () => { - const deletedId = 'repo-1::/workspace/deleted' - const runtime = createRuntime() - - const result = await stopMissingWorktreeTerminals( - { ...localRepo, connectionId: 'ssh-1' }, - [deletedId], - [], - { - runtime, - getLocalProvider: () => null - } - ) - - expect(result).toEqual({ stoppedWorktreeIds: [deletedId] }) - expect(runtime.stopTerminalsForWorktree).toHaveBeenCalledWith(deletedId, { - resolvedWorktreeId: deletedId, - resolvedConnectionId: 'ssh-1' - }) - }) - - // Per-workspace scans cost O(N) round trips carrying O(N²) rows; share one host inventory. - it('enumerates the host once for the whole sweep', async () => { - const ids = Array.from({ length: 25 }, (_, index) => `repo-1::/workspace/wt-${index}`) - const provider = createProvider(ids.map((id) => `${id}@@session`)) - - const result = await stopMissingWorktreeTerminals(localRepo, ids, [], { - runtime: createRuntime(), - getLocalProvider: () => provider - }) - - expect(result.stoppedWorktreeIds).toHaveLength(ids.length) - expect(provider.listProcesses).toHaveBeenCalledTimes(1) - expect(provider.shutdown).toHaveBeenCalledTimes(ids.length) - }) - it.each([false, true])( 'stops multiple workspaces through scoped daemon listings (unrelated duplicate: %s)', async (duplicate) => { @@ -148,63 +112,6 @@ describe('stopMissingWorktreeTerminals', () => { } ) - it('keeps provider method binding intact while batching', async () => { - class PrototypeProvider { - listCalls = 0 - shutdownCalls: string[] = [] - private readonly sessions: { id: string; cwd: string; title: string }[] - constructor(sessionIds: string[]) { - this.sessions = sessionIds.map((id) => ({ id, cwd: '/workspace', title: 'shell' })) - } - async listProcesses(): Promise<{ id: string; cwd: string; title: string }[]> { - this.listCalls += 1 - return this.sessions - } - async shutdown(sessionId: string): Promise { - this.shutdownCalls.push(sessionId) - } - } - const ids = ['repo-1::/workspace/a', 'repo-1::/workspace/b', 'repo-1::/workspace/c'] - const provider = new PrototypeProvider(ids.map((id) => `${id}@@session`)) - - const result = await stopMissingWorktreeTerminals(localRepo, ids, [], { - runtime: createRuntime(), - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the sweep calls only listProcesses and shutdown, which this class implements. - getLocalProvider: () => provider as unknown as IPtyProvider - }) - - expect(result.stoppedWorktreeIds).toHaveLength(ids.length) - expect(provider.listCalls).toBe(1) - expect(provider.shutdownCalls).toHaveLength(ids.length) - }) - - // Provider-internal this.listProcesses must bypass the sweep cache and read the live host. - it('does not serve the shared snapshot to provider-internal listProcesses', async () => { - class SelfListingProvider { - listCalls = 0 - constructor(private readonly sessions: { id: string; cwd: string; title: string }[]) {} - async listProcesses(): Promise<{ id: string; cwd: string; title: string }[]> { - this.listCalls += 1 - return this.sessions - } - async shutdown(): Promise { - await this.listProcesses() - } - } - const ids = ['repo-1::/workspace/a', 'repo-1::/workspace/b'] - const provider = new SelfListingProvider( - ids.map((id) => ({ id: `${id}@@session`, cwd: '/workspace', title: 'shell' })) - ) - - await stopMissingWorktreeTerminals(localRepo, ids, [], { - runtime: createRuntime(), - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the sweep calls only listProcesses and shutdown, which this class implements. - getLocalProvider: () => provider as unknown as IPtyProvider - }) - - expect(provider.listCalls).toBe(1 + ids.length) - }) - it('does not reuse a failed process scan', async () => { const ids = ['repo-1::/workspace/a', 'repo-1::/workspace/b'] const listProcesses = vi diff --git a/src/main/runtime/workspaces/removal/runtime-failed-local-worktree-removal.test.ts b/src/main/runtime/workspaces/removal/runtime-failed-local-worktree-removal.test.ts index 9f159416039..14efd9f792b 100644 --- a/src/main/runtime/workspaces/removal/runtime-failed-local-worktree-removal.test.ts +++ b/src/main/runtime/workspaces/removal/runtime-failed-local-worktree-removal.test.ts @@ -262,16 +262,6 @@ describe.skipIf(process.platform !== 'darwin')('a worktree delete Git fails part beginTerminalInstall(worktreePath)() }) - it('keeps the leftover listed with Git’s error after an in-session delete fails', async () => { - const error = await failInSession() - - expect(String(error)).toMatch(/Operation not permitted/) - expect(await isRegistered(worktreePath)).toBe(false) - expect(await listedRows()).toEqual([ - { path: worktreePath, removalError: expect.stringMatching(/Operation not permitted/) } - ]) - }) - it('does not retry it at the next start', async () => { await failStartupFinish() _resetPendingWorktreeRemovalsForTests() @@ -332,25 +322,6 @@ describe.skipIf(process.platform !== 'darwin')('a worktree delete Git fails part ]) }) - it('deletes the merged branch when the delete fails, and only the folder is left to the user', async () => { - await failInSession() - - expect(await git(['branch', '--list', 'feature'])).toBe('') - expect(existsSync(lockedFile)).toBe(true) - expect(removeHostTree).not.toHaveBeenCalled() - expect(await listedRows()).toEqual([ - { path: worktreePath, removalError: expect.stringMatching(/Operation not permitted/) } - ]) - - // Once the folder is gone, listing finishes the remaining record. - await setImmutable(false) - await rm(worktreePath, { recursive: true }) - expect(await listedRows()).toEqual([]) - await vi.waitFor(async () => expect(await readWorktreeRemovalRecords(recordsDir)).toEqual([])) - expect(await git(['branch', '--list', 'feature'])).toBe('') - expect(await git(['worktree', 'list', '--porcelain'])).not.toContain(worktreePath) - }) - it('leaves another worktree registered while its folder is missing, as on an unmounted drive', async () => { const parent = join(scratchDir, 'volume') const other = join(parent, 'other') @@ -406,15 +377,6 @@ describe.skipIf(process.platform !== 'darwin')('a worktree delete Git fails part expect(existsSync(lockedFile)).toBe(true) }) - it('removes the push-target remote Orca added when the startup finish fails', async () => { - const pushTarget = await addOrcaPushTargetRemote() - - expect(String(await finishAtStartup([], pushTarget))).toMatch(LOCKED_FILE_ERROR) - - expect(await git(['remote'])).not.toContain(pushTarget.remoteName) - expect(existsSync(lockedFile)).toBe(true) - }) - it('keeps an unmerged branch when the delete fails, as a normal delete does', async () => { await writeFile(join(worktreePath, 'work.txt'), 'work\n') await git(['add', 'work.txt'], worktreePath) diff --git a/src/main/runtime/workspaces/removal/runtime-folder-workspace-delete-lifecycle.test.ts b/src/main/runtime/workspaces/removal/runtime-folder-workspace-delete-lifecycle.test.ts deleted file mode 100644 index 7bad639efd3..00000000000 --- a/src/main/runtime/workspaces/removal/runtime-folder-workspace-delete-lifecycle.test.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { describe, expect, it, vi } from 'vitest' - -// Fragments stay side-effect ordered: mocks, then lifecycle, then fixtures. -await import('../../orca-runtime-tests/runtime-test-module-registration.spec') -const { OrcaRuntimeService } = await import('../../orca-runtime') -await import('../../orca-runtime-tests/orca-runtime-test-lifecycle.spec') -const { createFolderWorkspaceRuntimeStore, makeFolderWorkspace } = - await import('../../orca-runtime-tests/orca-runtime-test-fixtures.spec') - -describe('deleting a folder workspace', () => { - it('announces the removal to worktree lifecycle listeners under its folder key', async () => { - const folderWorkspace = makeFolderWorkspace({ folderPath: '/tmp/folder-ws' }) - const runtime = new OrcaRuntimeService({ - ...createFolderWorkspaceRuntimeStore(folderWorkspace), - removeFolderWorkspace: () => true - }) - const listener = vi.fn() - runtime.onWorktreeLifecycle(listener) - await expect(runtime.deleteFolderWorkspace(folderWorkspace.id)).resolves.toEqual({ - deleted: true - }) - expect(listener).toHaveBeenCalledWith({ - kind: 'removed', - worktreeId: `folder:${folderWorkspace.id}`, - path: '/tmp/folder-ws' - }) - }) -}) diff --git a/src/main/runtime/workspaces/removal/runtime-interrupted-local-worktree-removal.test.ts b/src/main/runtime/workspaces/removal/runtime-interrupted-local-worktree-removal.test.ts index 3a377917103..b9c69a6d3d5 100644 --- a/src/main/runtime/workspaces/removal/runtime-interrupted-local-worktree-removal.test.ts +++ b/src/main/runtime/workspaces/removal/runtime-interrupted-local-worktree-removal.test.ts @@ -783,17 +783,6 @@ describe('finishing an interrupted worktree removal after a restart', () => { expect(records).toMatchObject([{ failure: { message: expect.any(String) } }]) }) - it('deletes the branch when Git finished the checkout but the quit came before the branch', async () => { - await git(['worktree', 'remove', worktreePath]) - - const { outcome, purged, records } = await finishAfterRestart() - - expect(outcome).toMatchObject({ status: 'removed' }) - expect(records).toEqual([]) - expect(await git(['branch', '--list', 'feature'])).toBe('') - expect(purged).toEqual([`repo-1::${worktreePath}`]) - }) - it('keeps an unmerged branch, as a normal removal does', async () => { await writeFile(join(worktreePath, 'work.txt'), 'work\n') await git(['add', '-A'], worktreePath) @@ -818,21 +807,6 @@ describe('finishing an interrupted worktree removal after a restart', () => { ) }) - it('treats a removal that fully finished as done', async () => { - const head = (await git(['rev-parse', 'feature'])).trim() - await git(['worktree', 'remove', worktreePath]) - await git(['branch', '-d', 'feature']) - - const { outcome, purged, records } = await finishAfterRestart({ head }) - - expect(records).toEqual([]) - expect(outcome).toMatchObject({ status: 'removed' }) - expect( - outcome && 'preservedBranch' in outcome ? outcome.preservedBranch : undefined - ).toBeUndefined() - expect(purged).toEqual([`repo-1::${worktreePath}`]) - }) - it('drops a record whose repo Orca no longer has', async () => { vi.spyOn(console, 'warn').mockImplementation(() => {}) diff --git a/src/main/runtime/workspaces/removal/structured-session-worktree-teardown.test.ts b/src/main/runtime/workspaces/removal/structured-session-worktree-teardown.test.ts index 02fb71b633e..f03604f4958 100644 --- a/src/main/runtime/workspaces/removal/structured-session-worktree-teardown.test.ts +++ b/src/main/runtime/workspaces/removal/structured-session-worktree-teardown.test.ts @@ -17,10 +17,9 @@ const { isProvenLiveStructuredSessionRemovalError, isUnstoppedPtyRemovalError } = await import('../../../../shared/worktree/removal') -const { listStructuredSessionsForWorktree } = await import('./structured-session-worktree-teardown') +await import('./structured-session-worktree-teardown') const WORKTREE = 'repo_1::/tmp/wt-a' -const OTHER_WORKTREE = 'repo_1::/tmp/wt-b' function record( sessionId: string, @@ -188,39 +187,6 @@ describe('worktree teardown and structured agent sessions', () => { hostRef.current = null }) - it('finds sessions by workspace, and ignores a sibling worktree', () => { - installHost({ records: [record('s1', WORKTREE), record('s2', OTHER_WORKTREE)] }) - expect(listStructuredSessionsForWorktree(WORKTREE, {})).toEqual({ - members: [{ sessionId: 's1', agent: 'claude' }], - live: [{ sessionId: 's1', agent: 'claude' }] - }) - }) - - it('counts a chat with no attached child as a member but never as live', () => { - // Chats at rest remain workspace members even when no child is attached. - installHost({ records: [record('s1', WORKTREE)], detached: new Set(['s1']) }) - expect(listStructuredSessionsForWorktree(WORKTREE, {})).toEqual({ - members: [{ sessionId: 's1', agent: 'claude' }], - live: [] - }) - }) - - it('closes a live session on an ordinary removal instead of refusing it', async () => { - // Structured sessions appear on none of the three PTY surfaces; ordinary removal must close them separately. - const host = installHost({ records: [record('s1', WORKTREE)] }) - await expect(killAllProcessesForWorktree(WORKTREE, destructiveDeps())).resolves.toMatchObject({ - structuredStopped: 1 - }) - expect(host.closed).toEqual(['s1']) - }) - - it('refuses only when the close does not settle', async () => { - installHost({ records: [record('s1', WORKTREE)], stuck: new Set(['s1']) }) - await expect(killAllProcessesForWorktree(WORKTREE, destructiveDeps())).rejects.toThrow( - /still live: 1 agent session \(claude\)/ - ) - }) - it('puts the chat tab back when the removal refuses over the session', async () => { // A refused removal preserves the workspace and must restore its chat tabs. const host = installHost({ @@ -247,74 +213,6 @@ describe('worktree teardown and structured agent sessions', () => { warn.mockRestore() }) - it('leaves the chat tab dropped for a folder-workspace removal, which never refuses', async () => { - // Folder removal cannot refuse, so do not restore tabs for the forgotten workspace. - const warn = vi.spyOn(console, 'warn').mockImplementation(() => {}) - const host = installHost({ - records: [record('s1', WORKTREE)], - stuck: new Set(['s1']), - visible: ['s1'] - }) - await killAllProcessesForWorktree(WORKTREE, { - localProvider, - includeProviderInventory: false as const, - includeLocalRegistry: false as const, - closeStructuredSessions: true - }) - expect([...host.visible]).toEqual([]) - warn.mockRestore() - }) - - it('names the force escape hatch in the refusal, like the unstopped-PTY gate', async () => { - installHost({ records: [record('s1', WORKTREE)], stuck: new Set(['s1']) }) - await expect(killAllProcessesForWorktree(WORKTREE, destructiveDeps())).rejects.toThrow(/force/i) - }) - - it('classifies for the desktop Force Delete button, not just the CLI', async () => { - // Ordinary desktop deletion already passes force for dirty files; the classifier must separately offer the running-session waiver (#11960). - installHost({ records: [record('s1', WORKTREE)], stuck: new Set(['s1']) }) - const error = await killAllProcessesForWorktree(WORKTREE, destructiveDeps()).catch( - (thrown: Error) => thrown.message - ) - expect(classifyWorktreeForceDeleteReason(error as string, true)).toBe('running-agent-session') - // Once waived, suppress the button just as for unstopped PTYs. - expect(classifyWorktreeForceDeleteReason(error as string, true, true)).toBeNull() - }) - - it('keeps session ids out of a message users and agents read', async () => { - // Session ids lead to mailbox-gating pane keys; CLI/toast text must expose only counts and providers. - installHost({ records: [record('s1', WORKTREE)], stuck: new Set(['s1']) }) - const error = await killAllProcessesForWorktree(WORKTREE, destructiveDeps()).catch( - (thrown: Error) => thrown.message - ) - expect(error).not.toContain('s1') - expect(error).toContain('1 agent session (claude)') - }) - - it('closes best-effort for a folder-workspace removal, which requires no stop proof', async () => { - // Folder paths forget shared roots without stop proof; close best-effort without refusing removal. - const host = installHost({ records: [record('s1', WORKTREE)] }) - await expect( - killAllProcessesForWorktree(WORKTREE, { - localProvider, - includeProviderInventory: false, - includeLocalRegistry: false, - closeStructuredSessions: true - }) - ).resolves.toMatchObject({ structuredStopped: 1 }) - expect(host.closed).toEqual(['s1']) - }) - - it('closes them under force instead of orphaning the child', async () => { - const host = installHost({ records: [record('s1', WORKTREE), record('s2', WORKTREE)] }) - const result = await killAllProcessesForWorktree( - WORKTREE, - destructiveDeps({ allowUnverifiedStop: true }) - ) - expect(host.closed).toEqual(['s1', 's2']) - expect(result.structuredStopped).toBe(2) - }) - it('still removes under force when a close does not settle, and says so', async () => { const warn = vi.spyOn(console, 'warn').mockImplementation(() => {}) const retired: string[] = [] @@ -358,18 +256,6 @@ describe('worktree teardown and structured agent sessions', () => { expect(retired).toEqual(['s1']) }) - it('leaves the best-effort reconciliation paths alone', async () => { - // Those callers repair state and delete nothing, so a refusal there would wedge a repair. - installHost({ records: [record('s1', WORKTREE)] }) - await expect( - killAllProcessesForWorktree(WORKTREE, { - localProvider, - includeProviderInventory: false, - includeLocalRegistry: false - }) - ).resolves.toMatchObject({ runtimeStopped: 0 }) - }) - it('leaves a same-id workspace on another execution host alone', async () => { // A workspace id is `repoId::path` with no host component, so the local, SSH and paired-runtime // copies of one id are DIFFERENT workspaces. Unfenced, deleting the local one closed a chat @@ -383,25 +269,6 @@ describe('worktree teardown and structured agent sessions', () => { expect(host.closed).toEqual([]) }) - it('reads an explicit local fence the way the PTY sweeps do', () => { - // This helper reuses the PTY fence's own type, so the two cannot answer `null` differently: - // there it means this machine, and it has to mean this machine here. ABSENT is the one - // deliberate difference — no fence at all for the PTY sweeps, narrowed to local here, because - // a single-host-id comparison cannot express match-all and closing every host's chats is - // destructive. Latent today only because `WorktreeTeardownDeps` cannot yet carry the `null`. - installHost({ - records: [record('s1', WORKTREE, { executionHostId: 'ssh:host-a' }), record('s2', WORKTREE)] - }) - const local = { - members: [{ sessionId: 's2', agent: 'claude' }], - live: [{ sessionId: 's2', agent: 'claude' }] - } - expect(listStructuredSessionsForWorktree(WORKTREE, { resolvedConnectionId: null })).toEqual( - local - ) - expect(listStructuredSessionsForWorktree(WORKTREE, {})).toEqual(local) - }) - it('closes only the session on the host the removal resolved to', async () => { const host = installHost({ records: [record('s1', WORKTREE, { executionHostId: 'ssh:host-a' }), record('s2', WORKTREE)] @@ -415,21 +282,6 @@ describe('worktree teardown and structured agent sessions', () => { expect(host.closed).toEqual(['s1']) }) - it('names only the sessions that stayed, and every provider still there', async () => { - installHost({ - records: [ - record('s1', WORKTREE), - record('s2', WORKTREE, { provider: 'codex' }), - record('s3', WORKTREE) - ], - stuck: new Set(['s2', 's3']) - }) - const error = await killAllProcessesForWorktree(WORKTREE, destructiveDeps()).catch( - (thrown: Error) => thrown.message - ) - expect(error).toContain('still live: 2 agent sessions (claude, codex)') - }) - it('names the unconfirmed sessions too, instead of counting only the live ones', async () => { // The PTY sibling may drop everything outside its live list because a fresh inventory PROVED // those exited. Nothing proves that here: an `unverifiable` session is unclosed as well, so @@ -449,46 +301,6 @@ describe('worktree teardown and structured agent sessions', () => { expect(isProvenLiveStructuredSessionRemovalError(error as string)).toBe(true) }) - it('still reports what it closed when a forced removal skips the PTY verdict', async () => { - // A sweep that fails outright short-circuits the per-PTY verdict — but not the structured - // close that already ran, so the count has to survive that return or the removal log claims - // `structured=0` for chats it just ended. - const warn = vi.spyOn(console, 'warn').mockImplementation(() => {}) - const runtime: TeardownRuntime = { - ...presentationRuntime(), - getPtyLivenessVerdict: () => null, - stopTerminalsForWorktree: async () => { - throw new Error('the terminal sweep died') - } - } - const host = installHost({ records: [record('s1', WORKTREE)] }) - const result = await killAllProcessesForWorktree(WORKTREE, { - ...destructiveDeps({ allowUnverifiedStop: true }), - runtime - }) - expect(host.closed).toEqual(['s1']) - expect(result.structuredStopped).toBe(1) - warn.mockRestore() - }) - - it('separates a close it could not confirm from one it watched stay attached', async () => { - // `src/shared/worktree/removal.ts` keeps these two apart on purpose: a user waiving "we could - // not confirm" is making a different decision than one discarding a conversation Orca just saw - // running. The toast branches on this marker, so flattening them makes one of the two a lie. - installHost({ records: [record('s1', WORKTREE)], unverifiable: new Set(['s1']) }) - const unconfirmed = await killAllProcessesForWorktree(WORKTREE, destructiveDeps()).catch( - (thrown: Error) => thrown.message - ) - expect(unconfirmed).toContain('could not confirm these closed: 1 agent session (claude)') - expect(isProvenLiveStructuredSessionRemovalError(unconfirmed as string)).toBe(false) - - installHost({ records: [record('s1', WORKTREE)], stuck: new Set(['s1']) }) - const live = await killAllProcessesForWorktree(WORKTREE, destructiveDeps()).catch( - (thrown: Error) => thrown.message - ) - expect(isProvenLiveStructuredSessionRemovalError(live as string)).toBe(true) - }) - it('refuses in agent-session wording when the close outlives the sweep budget', async () => { vi.useFakeTimers({ toFake: ['Date', 'setTimeout', 'clearTimeout'] }) try { @@ -530,61 +342,6 @@ describe('worktree teardown and structured agent sessions', () => { } }) - it('names only the sessions still open when the budget expires mid-close', async () => { - vi.useFakeTimers({ toFake: ['Date', 'setTimeout', 'clearTimeout'] }) - try { - // Record serial-loop progress before timeout so already-closed children are not reported as unclosed. - installHost({ - records: [record('s1', WORKTREE), record('s2', WORKTREE, { provider: 'codex' })], - closeGates: { s2: new Promise(() => {}) } - }) - const outcome = killAllProcessesForWorktree( - WORKTREE, - destructiveDeps({ timeoutMs: 40 }) - ).catch((thrown: Error) => thrown.message) - await vi.advanceTimersByTimeAsync(40) - const error = await outcome - expect(error).toContain('could not confirm these closed: 1 agent session (codex)') - expect(error).not.toContain('claude') - } finally { - vi.useRealTimers() - } - }) - - it('counts the closes that landed before the budget expired', async () => { - vi.useFakeTimers({ toFake: ['Date', 'setTimeout', 'clearTimeout'] }) - // Preserve completed-close counts in timeout logs. - const warn = vi.spyOn(console, 'warn').mockImplementation(() => {}) - const slowClose = new Promise((resolve) => { - setTimeout(resolve, 300) - }) - try { - installHost({ - records: [record('s1', WORKTREE), record('s2', WORKTREE, { provider: 'codex' })], - closeGates: { s2: slowClose } - }) - const outcome = killAllProcessesForWorktree( - WORKTREE, - destructiveDeps({ allowUnverifiedStop: true, timeoutMs: 40 }) - ) - void outcome.catch(() => undefined) - await vi.advanceTimersByTimeAsync(300) - const result = await outcome - expect(result.structuredStopped).toBe(1) - expect(structuredSessionWarning(warn)).toContain( - 'could not confirm these closed: 1 agent session (codex)' - ) - } finally { - try { - await vi.advanceTimersByTimeAsync(300) - await slowClose - } finally { - warn.mockRestore() - vi.useRealTimers() - } - } - }) - it('stops issuing new closes once the budget is spent', async () => { // Stop issuing closes at deadline; the uncancellable in-flight close and untouched tail both remain unverified. let releaseFirstClose: () => void = () => {} @@ -611,25 +368,6 @@ describe('worktree teardown and structured agent sessions', () => { } }) - it('leaves the terminals already stopped when it refuses over a stuck session', async () => { - // PTY sweeps run concurrently to share the deadline: structured refusal can follow already-stopped PTYs. - // Serializing again would starve terminal stops and let force-delete remove files with open PTY handles. - let terminalSweeps = 0 - const runtime: TeardownRuntime = { - ...presentationRuntime(), - getPtyLivenessVerdict: () => null, - stopTerminalsForWorktree: async () => { - terminalSweeps += 1 - return { stopped: 2 } - } - } - installHost({ records: [record('s1', WORKTREE)], stuck: new Set(['s1']) }) - await expect( - killAllProcessesForWorktree(WORKTREE, { ...destructiveDeps(), runtime }) - ).rejects.toThrow(/still live: 1 agent session \(claude\)/) - expect(terminalSweeps).toBe(1) - }) - it('starts the terminal sweeps while the structured close is still in flight', async () => { // Starting PTY sweeps after serial structured closes would spend their shared budget before they can attempt a stop. let releaseClose: () => void = () => {} @@ -654,20 +392,6 @@ describe('worktree teardown and structured agent sessions', () => { await expect(removal).resolves.toMatchObject({ structuredStopped: 1 }) }) - it('retires the chat tab of a chat that had no child to close', async () => { - // Chats at rest are absent from the close list; their durable tab index must not resurrect a deleted workspace. - const host = installHost({ - records: [record('s1', WORKTREE)], - detached: new Set(['s1']), - visible: ['s1'] - }) - await expect(killAllProcessesForWorktree(WORKTREE, destructiveDeps())).resolves.toMatchObject({ - runtimeStopped: 0 - }) - expect(host.closed).toEqual([]) - expect([...host.visible]).toEqual([]) - }) - it('retires it from the live tab snapshot as well as the durable index', async () => { // Durable index hiding alone leaves the live snapshot tab able to reattach this session. const retired: string[] = [] @@ -734,32 +458,6 @@ describe('worktree teardown and structured agent sessions', () => { expect([...host.visible]).toEqual(['s1']) }) - it('retires the chat tab under force, which deletes the workspace anyway', async () => { - const host = installHost({ - records: [record('s1', WORKTREE)], - detached: new Set(['s1']), - visible: ['s1'] - }) - await killAllProcessesForWorktree(WORKTREE, destructiveDeps({ allowUnverifiedStop: true })) - expect([...host.visible]).toEqual([]) - }) - - it('retires the chat tab for a folder-workspace removal too', async () => { - // Forgetting folder metadata must retire detached tabs even though the shared checkout remains. - const host = installHost({ - records: [record('s1', WORKTREE)], - detached: new Set(['s1']), - visible: ['s1'] - }) - await killAllProcessesForWorktree(WORKTREE, { - localProvider, - includeProviderInventory: false as const, - includeLocalRegistry: false as const, - closeStructuredSessions: true - }) - expect([...host.visible]).toEqual([]) - }) - it('retires a live snapshot tab when folder cleanup cannot close its child', async () => { const retired: string[] = [] const host = installHost({ @@ -849,11 +547,4 @@ describe('worktree teardown and structured agent sessions', () => { await killAllProcessesForWorktree(WORKTREE, destructiveDeps()) expect([...host.visible]).toEqual(['s1']) }) - - it('does not block removal when no structured host is installed', async () => { - // Reading persisted storage would force-install a host during teardown; an unavailable host proves no child. - await expect(killAllProcessesForWorktree(WORKTREE, destructiveDeps())).resolves.toMatchObject({ - runtimeStopped: 0 - }) - }) })