Repository navigation
test(ssh): prove connect-time conversion to managed orcad on real Linux and Windows hosts - #24981
Conversation
There was a problem hiding this comment.
Caution
The Windows host lane cannot provision the conversion cell this PR adds: $cells now holds six entries, but those are passed as -Accounts 6 to a script whose parameter is capped at 5. The x64/inbox ssh-windows-hosts job fails before any test runs.
Reviewed changes
- New e2e spec
ssh-orcad-auto-convert.spec.ts— real-host proof that a relay-era target converts to managed orcad on connect: relay era (managedServerkind:'relay'/reason:'orcad_unavailable') seeds a repo, folder workspace and dormant tab; after the template is copied in, a reconnect reachesmanagedand the new server'srepo.list/folderWorkspace.list/session.tabs.listAllcontain those rows; the cutover journal movesdestination-committed→source-retiredonceorcad-source-retirementis on. - New host helper
orcad-convert-host.ts— resolves thedockerfixture or a Windows host-cell descriptor. - Linux CI — new
orcad-auto-convert-dockerjob builds thelinux-x64-glibctemplate outside the default lookup and runs the spec; registered as a dedicated spec across the job-selection, source-routing, sharding, and release-permission contract scripts. - Windows CI — new
orcad-convertcell (x64/inbox only, appended last because it switches native modules to Electron) plus descriptor-parser and relay-lane guard updates. - Downgrade unit test — loads v1.4.218's own
normalizeSshTarget/SshConnectionStoreand pins that this build'sorcadFencekeeps the host visible over its relay whilevisibleReposhides the retained repo. - One product hook —
readE2ERolloutConfigOverride, read on everyisRolloutFlagActivebut gated onORCA_E2E_USER_DATA_DIR, so production is unaffected.
The conversion mechanism itself reads sound and the assertions are tied to the real server catalog rather than a re-implementation. Two issues with the lane wiring would prevent the new coverage from running as intended.
Technical details — Windows cell count exceeds the provider's account cap
# The provider rejects the sixth account
## Affected sites
- `.github/workflows/ssh-windows-hosts.yml:156` — appends `orcad-convert`, making `$cells.Count` 6 for x64/inbox.
- `.github/workflows/ssh-windows-hosts.yml:169` — `-Accounts $cells.Count` passed to `prove-preview-openssh.ps1`.
- `config/ci/windows-ssh-provider/preview-ssh/prove-preview-openssh.ps1:5` — `[ValidateRange(1,5)][int]$Accounts=1`.
- `config/ci/windows-ssh-provider/preview-ssh/prove-preview-openssh.ps1:35` — account names derive 1:1 from `$Accounts`.
## Required outcome
- The x64/inbox job provisions six private accounts and runs the conversion cell.
## Suggested approach
- Widen `ValidateRange` to admit six (e.g. `1,6`), or avoid consuming a sixth slot. The provider already
derives account names from `$Accounts`, so widening the range is sufficient.Technical details — dedicated Docker job is skipped for test-only edits
# ORCAD_AUTO_CONVERT_E2E_SPEC missing from e2e_needs_build
## Affected sites
- `config/scripts/ci-e2e-job-selection.mjs:43` — new dedicated spec constant.
- `config/scripts/ci-e2e-job-selection.mjs:85-90` — `e2e_needs_build` OR-list names `dockerSpecs`, `LOCALHOST_SSH_E2E_SPEC` and `ORCAD_SERVE_MODE_SWITCH_E2E_SPEC`, but not the new spec.
- `config/scripts/ci-e2e-job-selection.test.mjs:31-37` — build-prerequisite case does not cover either orcad spec.
- `.github/workflows/e2e.yml:560` — `orcad-auto-convert-docker.if` cannot compensate: its `needs` are skipped.
## Required outcome
- A PR that changes only `tests/e2e/ssh-orcad-auto-convert.spec.ts` (or `orcad-convert-host.ts`) still runs the
`orcad-auto-convert-docker` job.
## Suggested approach
- Add `spec === ORCAD_AUTO_CONVERT_E2E_SPEC` to the `e2e_needs_build` `specs.some(...)` list, and extend the
`ci-e2e-job-selection.test.mjs` iteration to assert it (as `ORCAD_SERVE_MODE_SWITCH_E2E_SPEC` already is in the
production list).
## Open questions for the human
- Is the Docker job intentionally gated on `ssh_source_changed == 'true'` (so it runs on any SSH source change),
unlike `orcad-serve-mode-switch`? If so that is only a CI-cost decision; the test-only gap above still applies
when no source changes.deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
| // Needs the packaged orcad slot, which only its own job builds. | ||
| export const ORCAD_SERVE_MODE_SWITCH_E2E_SPEC = 'tests/e2e/orcad-serve-mode-switch.spec.ts' | ||
| // Needs the orcad template for its host's target, which only its own job builds. | ||
| export const ORCAD_AUTO_CONVERT_E2E_SPEC = 'tests/e2e/ssh-orcad-auto-convert.spec.ts' |
There was a problem hiding this comment.
This dedicated spec depends on build/prepare-native-cache like ORCAD_SERVE_MODE_SWITCH_E2E_SPEC, but it was not added to classifyE2eJobs's e2e_needs_build specs.some(...) list (lines 85-90). A PR that only edits tests/e2e/ssh-orcad-auto-convert.spec.ts (or orcad-convert-host.ts) yields e2e_needs_build=false, so build/prepare-native-cache skip and the dependent orcad-auto-convert-docker job is skipped despite contains(inputs.test_files, …) being true. Add spec === ORCAD_AUTO_CONVERT_E2E_SPEC there and cover it in ci-e2e-job-selection.test.mjs's build-prerequisite case.
Technical details
# Dedicated Docker job never runs on test-only edits
## Affected sites
- `config/scripts/ci-e2e-job-selection.mjs:85-90` — `e2e_needs_build` list omits the new spec.
- `config/scripts/ci-e2e-job-selection.test.mjs:31-37` — build-prerequisite iteration omits both orcad specs.
- `.github/workflows/e2e.yml:560` — job `if` cannot rescue a skipped `needs`.
## Required outcome
- Editing only the spec or its helper still runs `orcad-auto-convert-docker`.
## Suggested approach
- Add `spec === ORCAD_AUTO_CONVERT_E2E_SPEC` to the OR-list and extend the test iteration.2348137 to
3e5ebdc
Compare
There was a problem hiding this comment.
ℹ️ No new issues in the new commits. The Windows provisioning blocker from the prior review is fixed; one earlier thread (the dedicated Docker job's
e2e_needs_buildgating) is still open and unaddressed, so this run cannot approve.
Reviewed changes
- Windows conversion cell can provision —
prove-preview-openssh.ps1raises the private-account cap from five to six ([ValidateRange(1,6)]), so the x64/inbox lane that appendsorcad-convertno longer fails PowerShell parameter binding before any cell runs. - Account-cap contract test —
ssh-windows-hosts-workflow.test.mjsnow asserts the workflow appendsorcad-convertand that the provisioner's cap is at leastWINDOWS_HOST_CELL_IDS.length + WINDOWS_ORCAD_CELL_IDS.length + 1, so the count can't silently drift past the range again. - Spec reads the host's session partition — the dormant-tab check in
ssh-orcad-auto-convert.spec.tsnow callswindow.api.session.get(toSshExecutionHostId(remote.targetId))instead of the local partition, matching howssh-relay-session.tswrites an SSH worktree's workspace session. - Packaged builds refuse the E2E rollout override —
readE2ERolloutConfigOverridetakes anisPackagedflag defaulting to!hasAppEnvironment() || getAppEnvironment().isPackaged(), so an env-named flags file cannot move rollout flags in a shipped build; tests cover the packaged, unpackaged, and missing-file cases.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
ℹ️ No new issues in the newest commit. The prior review's one open thread — the dedicated Docker job's
e2e_needs_buildgating (config/scripts/ci-e2e-job-selection.mjs:85-90) — is still unaddressed, so this run cannot approve.
Reviewed changes
- Convert poll reports the full relay state on timeout —
tests/e2e/ssh-orcad-auto-convert.spec.tsreplaces thetoMatchObject({ kind: 'managed' })poll with anisManagedhelper that returns'managed'orJSON.stringify(server), so a timeout failure shows why the host stayed on the relay rather than a bare matcher diff. Test-only; no product surface.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
ℹ️ No new issues in the newest commit. The one open thread from the initial review — the dedicated Docker job's
e2e_needs_buildgating (config/scripts/ci-e2e-job-selection.mjs:85-90) — is still unaddressed, so this run cannot approve.
Reviewed changes
- Require the relay to list no terminals before the converting connect —
tests/e2e/ssh-orcad-auto-convert.spec.tsnow pollswindow.api.pty.listSessions({ connectionId })to'[]'after the shell exits, so the reconnect that triggers conversion starts from a state the relay has confirmed is terminal-free.pty:listSessionsroutes the scoped call to the sameprovider.listProcesses()the conversion gate's relay lister uses (orcad-migration-relay-pty-lister.ts), so this is a faithful precondition, not a local-only shadow. If a relay PTY lingers, the poll fails with the relay's actual session JSON rather than surfacing later as an opaque conversion timeout. Test-only; no product surface.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
ℹ️ No new issues in the newest commits. The one open thread from the initial review — the dedicated Docker job's
e2e_needs_buildgating (config/scripts/ci-e2e-job-selection.mjs:85-90) — is still unaddressed, so this run cannot approve.
Reviewed changes
- The terminal gate's lease input is now asserted before conversion —
tests/e2e/ssh-orcad-auto-convert.spec.tspolls the target's persistedsshRemotePtyLeasesuntil none readattached/detached, mirroring the live-lease filter inorcad-migration-terminal-gate.ts:25. Together with the earlierpty.listSessions === '[]'poll (the relay's process list, which keeps theexpired-lease path from blocking asunverifiable), both gate inputs are now proven before the reconnect. - The convert poll's timeout failure names the leases — a
targetLeaseshelper is extracted and reused, and the non-managed branch now returnsJSON.stringify({ server, leases }), so a timeout shows why the host stayed on the relay. - The convert poll timeout drops from 8 minutes to 30 seconds — the removed
CONVERT_TIMEOUT_MSis replaced by a fixed30_000. This is safe:ssh-connect-flow.ts:172awaitsdecideHostServer(andpublishManagedServerConnectbroadcasts) beforewindow.api.ssh.connectresolves, so the poll is only waiting on the renderer broadcast, not on the conversion.
Test-only; no product surface.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
ℹ️ No new issues in the newest commit. The one open thread from the initial review — the dedicated Docker job's
e2e_needs_buildgating (config/scripts/ci-e2e-job-selection.mjs:85-90) — is still unaddressed, so this run cannot approve.
Reviewed changes
- The relay era now ends with no relay shell left to respawn —
tests/e2e/ssh-orcad-auto-convert.spec.tsswitches to the local worktree (switchToWorktree(page, localWorktreeId)) before sendingexit, then creates the dormant session tab after the exit and both gate polls, with{ activate: false }only. Previously the tab was created up front withpendingActivationSpawn: true; now it is created while its worktree is hidden, so it never mounts a shell. Closing the exited tab can no longer activate and spawn a live relay shell on the remote worktree, which would otherwise keep the host on the relay. Test-only; no product surface.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
ℹ️ No new issues in the newest commit. The one open thread from the initial review — the dedicated Docker job's
e2e_needs_buildgating (config/scripts/ci-e2e-job-selection.mjs:85-90) — is still unaddressed, so this run cannot approve.
Reviewed changes
- Settles before the converting connect —
tests/e2e/ssh-orcad-auto-convert.spec.tsnow waits 5s after copying the template and re-asserts thatpty.listSessionsis empty and no lease readsattached/detached, catching a relay shell that starts after the earlier gate polls have already passed. - Names the tabs a lingering shell belongs to — the seeded tab's id is captured via
getActiveTabId, and both it and the dormantsessionTabIdare included in the pre-convert snapshot and the convert-poll failure JSON, so a timeout shows which sessions a resurrected relay shell is tied to.
Test-only; no product surface.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
ℹ️ No new issues in the newest commit. The one open thread from the initial review — the dedicated Docker job's
e2e_needs_buildgating (config/scripts/ci-e2e-job-selection.mjs:85-90) — is still unaddressed, so this run cannot approve.
Reviewed changes
- The session tab is now the exited relay tab —
tests/e2e/ssh-orcad-auto-convert.spec.tsdrops thecreateTab(remote.worktreeId, …, { activate: false })dormant tab and instead captures the active terminal tab (getActiveTabId) assessionTabId, runs the marker andexitin it, then asserts that same id appears in the host's session partition and in the managed server'ssession.tabs.listAllafter conversion. Any tab that mounts starts a shell, so reusing the tab already exited removes the risk that the "dormant" tab mounts and blocks the conversion; the pre-convert snapshot and poll failure JSON now carry onlysessionTabId.
Test-only; no product surface.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
ℹ️ No new issues in the newest commit. The one open thread from the initial review — the dedicated Docker job's
e2e_needs_buildgating (config/scripts/ci-e2e-job-selection.mjs:85-90) — is still unaddressed, so this run cannot approve.
Reviewed changes
- The converted session tab is now an editor tab —
tests/e2e/ssh-orcad-auto-convert.spec.tsopens<remoteRepo>/README.mdviastore.openFileon the remote worktree instead of reusing the exited relay terminal tab. Because editor tabs never mount a shell, this removes the last way the relay era could respawn a terminal and hold the host back, while still exercising session migration: the spec asserts the host's session partition includes the file path, and the managed server'ssession.tabs.listAllcontains it after conversion. Editor rows are part of the orcad workspace-session migration (orcad-destination-workspace-session.ts/orcad-source-workspace-session-fragments.ts), so the assertion is on the migrated session rather than a local shadow. The terminal is still run and exited only to satisfy the conversion gate; the header comment and poll-failure JSON were updated to match.
Test-only; no product surface.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
ℹ️ No new issues in the newest commit. The one open thread from the initial review — the dedicated Docker job's
e2e_needs_buildgating (config/scripts/ci-e2e-job-selection.mjs:85-90) — is still unaddressed, so this run cannot approve.
Reviewed changes
- Refused conversions now log their blockers —
src/main/ssh/orcad-runtime-conversion.tsreplaces thefenced/refuseternary infenceOrResumewith an explicit early return; on the refusal path itconsole.warns the preflight blocker codes (anddependenciesfororcad_migration_dependent_state) before returningrefuse(...). The connect still surfaces onlyresult.reason, so this is main-process support diagnostics. The success path is unchanged, and the[ssh]-prefixedconsole.warnmatches the convention used acrosssrc/main/ssh.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
ℹ️ No new issues in the newest commit. The one open thread from the initial review — the dedicated Docker job's
e2e_needs_buildgating (config/scripts/ci-e2e-job-selection.mjs:85-90) — is still unaddressed, so this run cannot approve.
Reviewed changes
- Pre-convert diagnostics now print both session partitions —
tests/e2e/ssh-orcad-auto-convert.spec.tsadds aconsole.logthat reads the host partition (window.api.session.get(hostId)) plus the local partition's fields that mention the remote worktree, beside the existing census-input log, right before the converting connect. Diagnostic-only; no assertion or app behavior changes.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
|
This run hit its time limit while still working, so it stopped before finishing. Give runs longer with
|
There was a problem hiding this comment.
ℹ️ No new issues in the new commits. Reviewed the three product fixes and the spec diagnostics; the one open thread from the initial review — the dedicated Docker job's
e2e_needs_buildgating (config/scripts/ci-e2e-job-selection.mjs:85-90) — is still unaddressed, so this run cannot approve.
Reviewed changes
- A converted host's managed state now reaches the renderer —
sshConnectionStatesEqualalso comparesmanagedServerandplainSsh(viaJSON.stringify), so a connect that ends on the same status/epoch it set up under no longer gets dropped by the store's dedupe; the function is extracted intossh-connection-state-equality.ts, both callers are updated, and store-level tests cover thesetting-up→managedtransition. - Port-0 forwards report the port the OS bound —
Ssh2PortForwardProviderreadsserver.address().portafterlisten, soOrcadManagedTunnelManager.start(which requests port 0 for an OS-assigned tunnel) records a usablelocalPortfor pairing instead of0. - Out-of-scope focus no longer blocks conversion —
countUnsupportedSessionStatestops counting the source host partition'sactiveWorktreeId/activeWorkspaceKeywhen they point at another host's workspace; that scalar is a copy of client-wide focus and is not carried anyway (an in-scope copy is). Covered by a new census test that fails before the change. - Conversion poll regains its budget and richer stall diagnostics — the spec's poll timeout returns to
8 * 60_000(CONVERT_TIMEOUT_MS) and now reports the connect's own result, main's managed state, the cutover journal phase, and both session partitions on timeout;serverCallprints the response in its failure message. - Migrated editor tab is host-stamped — the spec calls
setActiveWorktree(worktreeId, sshHostId)beforeopenFile, matching a sidebar click, so the tab is attributed to the remote host and carried by the workspace-session migration.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
ℹ️ No new issues in the two commits pushed during the prior review. They are test-only robustness changes; the earlier open thread (the dedicated Docker job's
e2e_needs_buildgating) remains unaddressed, so this run cannot approve.
Reviewed changes
- Server calls get a longer budget —
serverCallnow forwardsparamsand setstimeoutMs: 120_000, because a fresh managed server's first session inventory restores every migrated tab and can outlast the default call timeout. Both the preload bridge (runtime-environments-bridge.ts) and the main IPC handler acceptparams/timeoutMs. - Session-tab assertion is scoped to the converted worktree — the spec calls
session.tabs.listwith{ worktree: 'id:<worktreeId>' }instead ofsession.tabs.listAll, matching the host RPC method's scoped form (theid:worktree key is the shape mobile clients use).
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
Important
The converted-host editor-tab assertion was replaced with a log, so the spec no longer proves the editor-tab migration the PR advertises. The surface it checked (session.tabs.list) is terminal/browser-only and can never contain an editor path, so this reads as a pending server bug but is actually an oracle mismatch.
Reviewed changes
- Editor-tab assertion became a diagnostic log —
tests/e2e/ssh-orcad-auto-convert.spec.tsno longer asserts that the managed server lists the migratedREADME.md; it logssession.tabs.listfor the worktree plus the runtime host partition and continues, "pending the server-side fix". Added thetoRuntimeExecutionHostIdimport for the partition read.
ℹ️ Nitpicks
- The spec header still says "the new server lists that repository, folder and editor tab", but the editor tab is no longer asserted. Align the header (and the PR body's
session.tabs.listAllclaim) once the assertion is restored or the gap is tracked.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
| // Not asserted yet: the server lists no migrated editor tab (see the PR); logged for the fix. | ||
| console.log( | ||
| `[orcad-convert] server tabs ${await serverCall(page, environment!.id, 'session.tabs.list', { | ||
| worktree: `id:${remote.worktreeId}` | ||
| })} runtime partition ${await page.evaluate( | ||
| async (hostId) => JSON.stringify(await window.api.session.get(hostId)), | ||
| toRuntimeExecutionHostId(environment!.id) | ||
| )}` | ||
| ) |
There was a problem hiding this comment.
The deleted .toContain(sessionFilePath) targeted session.tabs.list, whose projection carries only terminal and browser tabs — see mobile-session-terminal-projection.ts:11 (RuntimeMobileSessionTerminalTab) and orca-runtime-hydrate-headless-mobile-session-tabs-from-workspace-session.ts:114-128 (snapshot = terminal tabs + browser tabs); the e2e helpers host-session-tabs.ts:24-25 and paired-html-preview-inventory.ts:14-15 state the same. An editor file path therefore could not appear there whether or not the tab migrated, so "the server lists no migrated editor tab" is not evidence of a server-side bug. The log already reads the runtime partition via session.get(toRuntimeExecutionHostId(environment!.id)), so assert the migrated editor row there (or on whatever surface carries openFilesByWorktree) to keep the coverage; if the real plan is to extend the server's session-tab projection to editor tabs, track that follow-up explicitly.
Technical details
# Migrated editor tab is invisible to session.tabs.list
## Affected sites
- `tests/e2e/ssh-orcad-auto-convert.spec.ts:286-294` — assertion replaced by a log.
- `src/main/runtime/mobile-session-terminal-projection.ts:11` — projects only `RuntimeMobileSessionTerminalTab`.
- `src/main/runtime/orca-runtime-hydrate-headless-mobile-session-tabs-from-workspace-session.ts:114-128` — snapshot is terminal tabs + browser tabs only.
- `tests/e2e/helpers/host-session-tabs.ts:24-25`, `tests/e2e/helpers/paired-html-preview-inventory.ts:14-15` — document that `session.tabs.list` carries only terminals.
## Required outcome
- The spec again proves the migrated editor tab reaches the converted host, on a surface that can represent editor tabs, or an explicit tracked follow-up records the missing server capability.
## Suggested approach
- Assert against the runtime host partition already logged, or the destination workspace-session record holding `openFilesByWorktree`.There was a problem hiding this comment.
ℹ️ No new issues in the new commits. Both are test-only: a stronger retirement assertion and a diagnostics cleanup. The two earlier open threads — the Docker job's
e2e_needs_buildgating and the editor-tab oracle mismatch — remain unaddressed, so this run cannot approve.
Reviewed changes
- Retirement is now proven by the dropped source rows —
sourceRows(...)counts the relay-erarepos/folderWorkspacesbyconnectionId; the spec asserts{repos:1, folderWorkspaces:1}while the journal isdestination-committed, then polls to{repos:0, folderWorkspaces:0}with the journal absent orsource-retiredonceorcad-source-retirementis on. - Conversion diagnostics removed — drops the
logConversionInputshelper and the session-partitionconsole.lognow that the cell passes, and simplifies the retirement poll while keeping the compacted-or-source-retiredequivalence. - Header aligned with the oracle — the phase-2 comment now states the editor tab is logged, not asserted.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
ℹ️ No new issues in the new commit. The change is test-only; the two earlier open threads remain unaddressed, so this run cannot approve.
Reviewed changes
- The conversion reconnect fails per step —
tests/e2e/ssh-orcad-auto-convert.spec.tssplitsreconnectinto separatedisconnectandconnectpage.evaluatesteps, each raced against a 6-minute timeout. A hung step now throws with main'sssh.getStateJSON instead of consuming the whole test budget; a successful disconnect returns''and the loop proceeds to the connect step, whose resolved managed-server string feeds the poll's diagnostics.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
…ux and Windows hosts, and the downgrade view
…t cell's account, and keep rollout file overrides out of packaged builds
…ect keeps the relay
…locking shell belongs to
…ger blocks conversion
…aged tunnels pair
…conversion stalls
…ad of staying on 'converting'
…m, pending the server-side fix
…ompacts away after it
…d of the whole budget
96c9819 to
8e93409
Compare
…connect, on Docker and Windows
There was a problem hiding this comment.
ℹ️ No new issues in this delta. It extracts the conversion flow into shared e2e helpers and adds a Docker + Windows test for a relay-era profile that converts on its first connect after upgrading. The two earlier open threads — the Docker job's
e2e_needs_buildgating and the editor-tab oracle mismatch — remain unaddressed, so this run cannot approve.
Reviewed changes
- Conversion flow extracted into
orcad-convert-flow.ts—reconnect, the managed-server poll, the source-row/lease reads,serverCall, and the convert → retain → retire sequence move out of the spec unchanged, so the relay-era cell and the new upgraded-profile cell share one implementation. - Relay-era profile seeder
orcad-upgrade-profile.ts—seedRelayEraProfilewrites a fence-less SSH target (normalizeSshTarget, noorcadFence), its repo, project group, folder workspace, and a host-partitioned editor tab into a stopped profile, then the spec relaunches against it. - New upgraded-profile test — launches once to establish the profile, seeds the relay-era rows, relaunches, and runs the same convert-then-retire assertions; it runs on both Docker and Windows, while the runtime relay-era test is now skipped off Docker (the Windows lane hides host Node).
- Windows trigger paths —
ssh-windows-hosts.ymlnow also fires onorcad-convert-flow.tsandorcad-upgrade-profile.ts.
Verified locally: oxlint and oxfmt --check clean on the four test files; ssh-windows-hosts-workflow, ci-e2e-job-selection, and pr-e2e-gate-contract suites pass (59 tests). The Docker and Windows cells were still running at review time.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
ℹ️ No new issues in this delta. The commit only tightens the upgraded-profile fixture; the two earlier open threads remain unaddressed, so this run cannot approve.
Reviewed changes
- Seeded relay-era target now matches
SshConnectionStore.addTarget—tests/e2e/helpers/orcad-upgrade-profile.tsadds anextGenerationhelper (high-water mark oversshTargetGenerationCounterand the existing targets' generations) and writesconfigHost,source: 'manual'and that freshgenerationthroughnormalizeSshTarget, so the upgraded-profile fixture's target carries the same defaults a real registration would instead of relying on load-time normalization.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
…shing main on 'Not connected'
There was a problem hiding this comment.
ℹ️ No new issues in this commit. It is a main-process crash fix plus a diagnostic log; the two earlier open threads — the Docker job's
e2e_needs_buildgating and the editor-tab oracle mismatch — remain unaddressed, so this run cannot approve.
Reviewed changes
- A stale ssh2 forward no longer crashes main on
'Not connected'—src/main/ssh/ssh2-port-forward-provider.tswrapsclient.forwardOutin a try/catch and destroys the local socket when it throws synchronously after the SSH transport has dropped, mirroring the existing async error path; the new case inssh-port-forward.test.tsstubs that throw and asserts the server's connection handler neither throws nor leaves the socket open. - The upgraded-profile launch forwards its own SSH logs —
tests/e2e/ssh-orcad-auto-convert.spec.tspasses anonStderrhandler relaying[ssh]-prefixed main-process chunks on the second launch, which bypasses the fixture's log relay.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
…gh the host script
There was a problem hiding this comment.
ℹ️ No new issues in the new commit. It fixes the Windows active-slot readiness probe; the two earlier open threads — the dedicated Docker job's
e2e_needs_buildgating and the editor-tab oracle mismatch — remain unaddressed, so this run cannot approve.
Reviewed changes
- Windows active-slot readiness probe reads through the host script — new
readOrcadReadinessNowCommandinorcad-remote-readiness-wait.tsselects the Windows host-scriptreadiness-waitop with no wait vs POSIXhead;probeActiveOrcadReadinessandreadRemoteOrcadManagedStopTargetnow both call it. PreviouslyprobeActiveOrcadReadinesscalledreadOrcadReadinessCommand, whoseassertPosixHostthrows on Windows, so the active-slot probe could never run there; POSIX output is byte-identical to before. A neworcad-remote-windows-commands.test.tscase asserts the second exec is the host-script wait op and would fail before the fix (the throw happened before any exec).
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
…al forward its managed server needs
There was a problem hiding this comment.
ℹ️ No new issues in the new commit. It grants the Windows conversion cell's SSH account the local forwarding the managed tunnel needs. The two earlier threads — the dedicated Docker job's
e2e_needs_buildgating and the editor-tab oracle mismatch — remain unaddressed, so this run cannot approve.
Reviewed changes
- Only the convert cell's account may open an SSH local forward —
ssh-windows-hosts.ymlcomputes$forwardingas1only when the last cell isorcad-convertand passes-ForwardingAccounts $forwardingto the provisioner.prove-preview-openssh.ps1adds aMatch User <convert account>block withAllowTcpForwarding local, overriding the globalAllowTcpForwarding no, so the managed server's tunnel can pair while every other cell stays forwarding-disabled. - Contract test pins the wiring —
ssh-windows-hosts-workflow.test.mjsasserts the workflow emits$forwarding=if($cells[-1] -eq 'orcad-convert'){1}else{0}and passes-ForwardingAccounts $forwarding.
Verified locally: the generated Match User block passes sshd -t, and sshd -T -C user=<last> resolves allowtcpforwarding local for the last account while a non-last account resolves no; Select-Object -Last 0 emits nothing, so -ForwardingAccounts 0 leaves the config unchanged. The forwarding account is accounts[-1] = Cells[-1] (invoke-pinned-relay-cells.ps1:92), matching the workflow's last-cell check, and AllowTcpForwarding local is documented in the OpenSSH 8.1-era man page, so the Windows inbox baseline supports it.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
ℹ️ No new issues in the JSON-escaping fix. The two earlier open threads — the dedicated Docker job's
e2e_needs_buildgating and the editor-tab oracle mismatch — remain unaddressed, so this run cannot approve.
Reviewed changes
- Server path assertions now match the JSON-escaped form —
tests/e2e/helpers/orcad-convert-flow.tsaddsjsonText(value)(JSON.stringify(value).slice(1, -1)) and checks therepo.listandfolderWorkspace.listresponses againstjsonText(host.repoPath)/jsonText(host.folderPath).serverCallreturnsJSON.stringify(response)whereresponseis a parsedRuntimeRpcResponseobject, so a Windows path's backslashes arrive doubled in that text and the raw path no longer matched; the escaped form matches, and Linux paths are unchanged.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
… the relay (#24863) * Revert "revert: take the 26 Phase 3 (#16741 port) PRs back out of main (#24559)" This reverts commit 5f308bfa9c796f33e17c9e1cff180a2247450d0a. * feat(orcad): Windows remote primitives for managed orcad hosts (W1) (#24525) * feat(orcad): Windows remote primitives for managed orcad hosts (W1) * refactor(orcad): run Windows host ops as node.exe with plain argv, no PowerShell hop * fix(orcad): refuse secret-shaped names on the breakaway launcher's --env * fix(orcad): name the secret env guard for its role --------- Co-authored-by: m4air <m4air@Mac.localdomain> * feat(orcad): stage and commit a dormant migration catalog on the managed server (#16741 T6-9) (#24521) * feat(orcad): stage and commit a dormant migration catalog on the managed server (#16741 T6-9) The destination half of a catalog migration: an orcad stages a T6-7 manifest (repositories, project groups, folder workspaces, dormant session, client, automation and worktree metadata, retired names, scrollback snapshots) with exclusive claims, then commits it with a receipt so a retried commit returns the same receipt and never imports twice. Served as orcad.migration.* runtime RPC behind the orcad.migration-catalog.v1 capability; the client refuses a host without it or with method-not-found, and any other failure is left for the caller to recheck. Dormant only: no live PTY projection. Inert on the desktop until T6-10. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(rpc): catalog the orcad.migration params in the shared contract; name the catalog-import install target Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: m4air <m4air@Mac.localdomain> Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * feat(ssh): journal and fence an SSH host for dormant migration, gated on proven terminal exit (#16741 T8-c1+c2) (#24522) A migration from a relay-hosted SSH target into a managed orcad now starts with a journal in its own sidecar directory, then the target's managed-owner fence, then a profile flush, before any remote call. A fence with no journal is unverifiable and never released; a journal whose fence is gone is stale and grants nothing; an unreadable journal fails closed. The fence requires every terminal the target ever leased to be proven exited, checked before the fence (with the relay's process list) and again under it. Same-owner claims now need the durable record that explains them. Inert until T8-c4/T6-10. Co-authored-by: m4air <m4air@Mac.localdomain> Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * feat(orcad): run orcad itself on Windows hosts (W2) (#24529) * feat(orcad): run orcad itself on Windows hosts (W2) * test(orcad): load the ConPTY smoke's addon from out/orcad so the temp slot can be removed * test(orcad): skip the foreign-uid lock case when running as root --------- Co-authored-by: m4air <m4air@Mac.localdomain> * fix(orcad): a connected but unused SSH host previews as movable (#24609) The untransferred-dependency census counted activeConnectionIdsAtShutdown naming the target as workspace-session state. The renderer rewrites that list on every connection change, so merely connecting to an empty host blocked the move. It is a reconnect hint; the remote work it can stand for is counted on its own. The empty-target claim check likewise ignores global-field copies inside the host's session partition. Co-authored-by: m4air <m4air@Mac.localdomain> * feat(ssh): stage, commit and abort a dormant migration against its destination (#16741 T8-c3) (#24523) * feat(ssh): stage, commit and abort a dormant migration against its destination (#16741 T8-c3) The coordinator re-checks before every stage and commit that the fenced source still exports the journaled manifest, carries no untransferable state and started no terminal. A lost answer is re-read from the destination's catalog state; only a committed read whose receipt matches the journal advances it, and the journal is on disk before anything returns. Abort releases the fence only on proof the destination holds nothing, or on an unsupported destination before anything was staged, and never once the destination committed. Codes against T6-9's catalog client through an injected interface. Inert until T8-c4/T6-10. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * refactor(ssh): import the T6-9 client's unsupported refusal instead of mirroring it --------- Co-authored-by: m4air <m4air@Mac.localdomain> Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * feat(orcad): deploy, activate and roll back orcad on Windows SSH hosts (W3) (#24563) * feat(orcad): deploy, activate and roll back orcad on Windows SSH hosts (W3) * test(orcad): exhaustive op switch in the Windows lifecycle fake * test(ssh): narrow the Windows host-cell descriptor by lane before building a relay cell --------- Co-authored-by: m4air <m4air@Mac.localdomain> * feat(serve): run orca serve on the local orcad slot behind ORCA_SERVE_RUNTIME=orcad (T6-11) (#24608) * feat(serve): run orca serve on the local orcad slot behind ORCA_SERVE_RUNTIME=orcad (T6-11) * fix(serve): keep orcad selection app-side and wait out Windows temp cleanup * refactor(orcad): move the data-root privacy check out of the instance lock --------- Co-authored-by: m4air <m4air@Mac.localdomain> * test(serve): prove D7 and the profile lock across a real Electron/orcad serve switch (#24619) * test(serve): prove D7 and the profile lock across a real Electron/orcad serve switch * ci(e2e): install ripgrep for the serve mode-switch job's window-manager wait --------- Co-authored-by: m4air <m4air@Mac.localdomain> * feat(ssh): convert an SSH host with Orca state into a managed server through a journaled migration (#16741 T8-c4) (#24562) * feat(ssh): convert an SSH host with Orca state into a managed server through a journaled migration (#16741 T8-c4) The conversion entry resumes or takes the fence, deploys and pairs the managed server into it, marks the server as migrated, then stages and commits the dormant catalog. Every step is keyed by the journal, so a repeat after a crash, deferral or lost reply resumes the same migration. Status reports an unfinished migration, and rollback is refused while one runs or when the rollback snapshot predates the migrated catalog. Inert until T6-10. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * style: oxfmt the c4 conversion and maintenance files * fix(ssh): name the fake migration destination's type so declarations stay portable --------- Co-authored-by: m4air <m4air@Mac.localdomain> Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * feat(orcad): decommission, managed stop and GC on Windows SSH hosts (W4) (#24570) * feat(orcad): decommission, managed stop and GC on Windows SSH hosts (W4) * fix(orcad): accept a managed stop request whose lock path is spelled with Windows client separators --------- Co-authored-by: m4air <m4air@Mac.localdomain> * feat(ssh): retire a migrated SSH host's source state only after a proven commit (#16741 T8-c5) (#24565) * feat(ssh): retire a migrated SSH host's source state only after a proven commit (#16741 T8-c5) Once the journal records destination-committed, the source profile drops the manifest's repositories, folder workspaces and unreferenced project groups, its dormant session, automation, client and worktree state, and the leases the fence proved exited. The profile flushes, the retirement is verified, the journal moves to source-retired and compacts once the server matches. A retry after any crash repeats idempotent work. The fenced target stays: it carries the managed server's tunnel. Conversion now ends retired. Inert until T6-10. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(orcad): retirement drops the migrated host from the reconnect hint The census no longer treats activeConnectionIdsAtShutdown as untransferable (#24609), so retirement must remove the target from it; otherwise a restart dials a host that is now a managed server. * style: oxfmt the c5 conversion file --------- Co-authored-by: m4air <m4air@Mac.localdomain> Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * feat(orcad): convert Windows relay-hosted SSH targets to managed orcad (W5 part 1) (#24579) * feat(orcad): convert Windows relay-hosted SSH targets to managed orcad (W5 part 1) * test(orcad): start the Windows lane's exec spy after the relay gate prelude restores its own --------- Co-authored-by: m4air <m4air@Mac.localdomain> * feat(settings): managed servers and "Move to managed server", behind an experimental setting (#16741 T6-6 + T6-10 UI) (#24590) * feat(settings): managed servers and "Move to managed server", behind an experimental setting (#16741 T6-6 + T6-10 UI) Adds a Managed servers section under Remote servers (deploy an empty server, status with deferred-update and migration states, update, rollback, recover, stop and cancel-stop, and SSH access for paired servers), and a Move to managed server action on connected macOS and Linux SSH hosts with a preflight summary, a terminals-closed confirmation and a resumable progress view. Main wires the conversion to the relay's process list, the direct session and the T6-9 catalog client. Everything is hidden until the new experimental setting is turned on, and Windows SSH hosts are never offered. Merges the T6-9 branch (#24521) until it lands on the integration branch. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(settings): align managed-server form controls and name the section the setting reveals Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(orcad): ask the relay with an absolute deadline via the W5 terminal-gate lister The conversion wiring passed a relative 10 s as listProcesses' deadlineMs, which the provider reads as an absolute time, so every relay inventory timed out after 1 ms and the terminal gate could never prove exit. Adopt #24579's lister verbatim so the stacks merge cleanly. * feat(settings): name blocking saved state in plain, localized words The move preview listed internal dependency ids such as workspace-session; each kind now has its own catalog entry. * feat(settings): offer managed servers and the move on Windows SSH hosts W1-W5 are on the integration branch, so a Windows relay-hosted host can deploy, convert and retire like a POSIX one. The move still waits for a connected relay that reported its platform. --------- Co-authored-by: m4air <m4air@Mac.localdomain> Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * test(ci): list the serve mode-switch e2e job in the release-cut permissions matrix (#24865) * test(ci): list the serve mode-switch e2e job in the release-cut permissions matrix * test(ci): expect the orcad Windows host cells in the SSH Windows hosts workflow --------- Co-authored-by: m4air <m4air@Mac.localdomain> * test(orcad): wait for killed terminal daemons to exit before removing their temp profiles (#24871) Co-authored-by: m4air <m4air@Mac.localdomain> * feat(updater): read rollout kill switches from the update-campaign payload, all inactive (#24867) The nudge request Orca already polls may now carry an optional versioned rollout block naming the Node runtime flips. A typed reader resolves each flip with kill-switch, version range and install-id-bucketed percent semantics, falling back to the baked value (every flip inactive) when the block is absent, invalid or never read. No consumer reads it yet. Co-authored-by: m4air <m4air@Mac.localdomain> * fix(telemetry): report Windows security-software refusals and unverifiable or failed runtime checks (#24866) ssh_remote_runtime_resolved dropped the self-test's security_software refusal to 'none' and sent nothing when a self-test was unverifiable or failed, because those attempts throw before a rung settles. Add the refusal value, self_test 'unverifiable', and an outcome field (resolved | unverifiable | failed) deduplicated per host and outcome per session. Co-authored-by: m4air <m4air@Mac.localdomain> * refactor(settings): move WorktreeVisibilityDefaults into its own module (#24884) global-settings-types.ts sits at the 300-line max-lines ceiling; merging main's two new agent-state-rules settings with experimentalManagedServers put it at 301. The worktree visibility defaults type moves next to the other visibility types and is re-exported so its 38 importers are unchanged. Co-authored-by: m4air <m4air@Mac.localdomain> * refactor(watcher): move the supervisor's child, terminating child and canary into a child slot (#24887) * refactor(watcher): move the supervisor's child, terminating child and canary into a child slot * fix(watcher,runtime): take the child slot's child type from the shared wrapper, and stub main's title-display clear in the projection test --------- Co-authored-by: m4air <m4air@Mac.localdomain> Co-authored-by: m4air <m4air@m4airs-Air.localdomain> * ci(adhoc): build and ship the orcad template in adhoc macOS and Windows builds (#24969) Co-authored-by: m4air <m4air@m4airs-Air.localdomain> * feat(orcad): bound the desktop slot cache and prune proven-stopped orcad versions after each managed deploy (#24973) * feat(orcad): bound the desktop slot cache and prune proven-stopped orcad versions after each managed deploy * fix(orcad): keep the in-use slot plus the two most recent others, and prove same-version reuse survives eviction --------- Co-authored-by: m4air <m4air@m4airs-Air.localdomain> * Phase 3: managed orcad on every SSH connect, with downgrade-safe fence (#24975, #24979) * feat(orcad): fence managed hosts outside owner and keep converted projects for downgrades Shipped builds hide any SSH target with an owner, so a downgrade made a converted host and its projects vanish. The managed fence moves to a new orcadFence field (older builds keep but ignore it), phase-3 owner fences migrate on load, and managed hosts stay visible but refuse a direct relay. Conversion now stops at destination-committed with sourceRetainedAt; source retirement waits on the baked-off orcad-source-retirement rollout flag. This build hides retained rows, and a start that finds an older build changed them marks the host sourceChangedAt (relay, needs a new move) instead of merging a second manifest. * feat(ssh): every SSH host runs managed orcad, decided on each connect (#24979) * feat(settings): managed servers are no longer experimental Remove experimentalManagedServers and its gates; the Managed servers section always shows. Loading drops a stored value, which an older build reads back as its own default (off). * feat(ssh): every SSH host runs managed orcad, decided on each connect Before a relay is started, the connect decides the host's server: a converted host connects through its tunnel (retiring a retained source once orcad-source-retirement is on), an empty host deploys orcad, and a host with Orca state converts through the journaled migration. A host with live or unproven relay terminals keeps the relay this session and converts later. A refusal for any other reason keeps the relay and names the blocker. A host orcad can't run on (unsupported target, no template, native preflight, runtime self-test) releases any claim or fence it took, records why with this app version, and keeps the pinned-relay ladder. Progress and the decision ride an optional SshConnectionState.managedServer field (dropped by older clients' admission). SSH Hosts shows each host's server status; the manual move dialog is removed. * fix(ssh): always await the connect's server decision, after the provider authority rotates The decision now runs after the old session and transport are torn down and the authority has rotated synchronously, so concurrent connects still join one attempt; a shutdown that began during the decision wins over the rotation. The IPC tests use an async double, no sync branch. * test(renderer): the IPC events store double carries its SSH connection states --------- Co-authored-by: m4air <m4air@m4airs-Air.localdomain> --------- Co-authored-by: m4air <m4air@m4airs-Air.localdomain> * test(ssh): prove connect-time conversion to managed orcad on real Linux and Windows hosts (#24981) * test(ssh): prove connect-time conversion to managed orcad on real Linux and Windows hosts, and the downgrade view * fix(e2e): read the SSH host's session partition, provision the convert cell's account, and keep rollout file overrides out of packaged builds * test(e2e): report the full relay state when the convert poll times out * test(e2e): require the relay to list no terminals before the converting connect * test(e2e): require no running-terminal lease before the converting connect * test(e2e): report the host's terminal leases when the converting connect keeps the relay * test(e2e): end the relay era with no relay shell left to respawn * test(e2e): settle before the converting connect and name the tabs a blocking shell belongs to * test(e2e): use the exited relay tab as the session tab, since any mounted tab starts a shell * test(e2e): carry an editor tab through the conversion instead of a terminal tab * test(e2e): log the conversion census inputs before the converting connect * fix(orcad): log which state blocked a refused conversion * test(e2e): log both session partitions before the converting connect * fix(orcad): a source partition's copy of focus on another host no longer blocks conversion * fix(ssh): an ssh2 forward on port 0 reports the port it bound, so managed tunnels pair * test(e2e): give the conversion its full budget again * test(e2e): report the migration journal phase when the conversion stalls * test(e2e): report the connect's own result and main's state when the conversion stalls * fix(ssh): a converted host's managed state reaches the renderer instead of staying on 'converting' * test(e2e): print a failed server call's response * test(e2e): give server calls the budget a fresh server's first inventory needs * test(e2e): read the converted worktree's tabs with a scoped session.tabs.list * test(e2e): log the converted worktree's tabs instead of asserting them, pending the server-side fix * test(e2e): prove retirement by the dropped source rows; the journal compacts away after it * test(e2e): drop the conversion diagnostics now the cell passes * test(e2e): fail a hung disconnect or connect with main's state instead of the whole budget * test(e2e): convert an upgraded relay-era profile's host on its first connect, on Docker and Windows * test(e2e): seed the relay-era target the way addTarget registers it * fix(ssh): a stale ssh2 forward drops a late connection instead of crashing main on 'Not connected' * fix(orcad): the active-slot readiness probe reads Windows hosts through the host script * ci(ssh-windows): let only the convert cell's account open the SSH local forward its managed server needs * test(e2e): match server paths in their JSON-escaped form, for Windows backslashes --------- Co-authored-by: m4air <m4air@m4airs-Air.localdomain> Co-authored-by: m4air <m4air@Mac.localdomain> * feat(orcad): move what an older build added to a converted host, or keep the server's version (#24980) * feat(orcad): move what an older build added to a converted host, or keep the server's version A host an older build changed stays on the relay with two actions. 'Move the new projects' runs a fresh journaled conversion of only what the host's earlier migrations didn't move: the source is viewed with those migrations retired from it, so nothing that overlaps the server is merged, and a row the server already holds fails the whole move at stage, before any commit. Its journal supersedes the chain head; retained-source checks compare against its baseline, and retirement retires every manifest in the chain only after the newest committed. 'Keep the server's version' records the current source as the baseline and returns the host to its managed server. * test(ipc): the runtime environment handler contract lists the delta-move channels * test(native-chat): snapshot the journal directory after the attach's restart-offer lock is released --------- Co-authored-by: m4air <m4air@m4airs-Air.localdomain> Co-authored-by: m4air <m4air@Mac.localdomain> * fix(orcad): converted hosts keep their editor tabs, forwarding-refusing hosts stay on the relay, listAll settles (#25099) * fix(orcad): publish the headless graph so session.tabs.listAll settles instead of hanging * fix(ssh): a system SSH forward on port 0 picks a free port first and reports it * fix(runtime): a headless host lists and closes the editor tabs its session holds, so migrated editors reach clients * fix(ssh): keep a host that refuses TCP forwarding on the relay, and release a conversion it stranded * test(e2e): assert the migrated editor tab and a settled listAll, and keep a forwarding-refusing host on the relay * fix: restore the journal import after rebase, type the probe's failure code, and update headless-graph test seams --------- Co-authored-by: m4air <m4air@Mac.localdomain> * fix(orcad): client focus and 'local'-stamped tabs no longer refuse a host's move to its managed server (#25110) * fix(orcad): client focus and 'local'-stamped tabs no longer refuse a host's move to its managed server * fix(orcad): a v1.4.218 profile focused on the SSH worktree converts, and a refusal names what blocks it The debounced session writer never patched activeWorkspaceKey or activeWorkspaceExecutionHostId, so the first focus stayed on disk. The all-dependency census also counted global focus copies in every non-source partition. --------- Co-authored-by: m4air <m4air@Mac.localdomain> * feat(ssh): offer to move a host whose open terminals keep it on the relay (#25097) A host with any open SSH terminal never converted: the connect gate keeps the relay while relay terminals are live, and an open tab respawns them on every connect. The first such connect per host per app version now marks the relay status with offerMove (recorded as managedServerMoveOffered), which toasts "Move <host> ... Its N open terminals will restart." The SSH Hosts status line keeps a "Move to managed server" action while terminals are live. Confirming runs ssh:moveToManagedServer: stop the host's relay terminals through the extracted ssh:terminateSessions path, re-run the connect gate's terminal census, refuse on anything but exited, then reconnect so the connect-time decision runs the journaled conversion. Co-authored-by: m4air <m4air@Mac.localdomain> * feat(orcad): reach managed orcad over an SSH stdio bridge where sshd refuses port forwarding (#25120) * feat(orcad): reach managed orcad over an SSH stdio bridge where sshd refuses port forwarding Hosts with AllowTcpForwarding no were kept on the relay. The managed tunnel now probes forwarding each time it starts and, on refusal, serves the same local port through a second provider: each accepted socket opens one SSH exec channel running a small bridge on the host's pinned Node, which dials orcad's loopback port. POSIX hosts run it with node -e; Windows hosts run it as the content-addressed host script's stdio-bridge op with base64 line framing. Bridges are capped at 8 per connection under sshd's MaxSessions default, and a lost channel only drops its socket. Only a host where even the bridge cannot run keeps the relay, recorded as ssh_tunnel_unavailable; the older tcp_forwarding_refused record is retried. * test(e2e): prove the stdio bridge by refused forwarding plus a working call * test(e2e): connect the refused-forwarding host without the relay-only repo step --------- Co-authored-by: m4air <m4air@Mac.localdomain> * feat(ssh): report each connect's server decision, and show orcad.log's last lines when setup fails (#25118) * telemetry: one enum-only event per connect decision (outcome, reason, tunnel transport, host platform, duration), plus conversion start/commit/fail, deploy failures, and the per-host move offer and its result * errors: deploy, launch and rollback failures carry the last 40 redacted lines of the host's orcad.log, read over SSH (Windows through the node.exe host script) * SSH Hosts: a deferred or failed setup offers its reason, log tail included, under Details Co-authored-by: m4air <m4air@Mac.localdomain> * fix(startup): Windows never crashes resolving userData when roaming AppData is unavailable (#25113) * fix(startup): pin Windows appData and userData before anything resolves them A Windows session without a loaded profile (e.g. orca serve over SSH) can fail the roaming AppData known-folder lookup. Electron 43 then falls through to Chromium's userData provider and crashes natively. Resolve appData first (falling back to APPDATA, then USERPROFILE\AppData\Roaming), and set userData explicitly so Electron's provider never runs. * test(startup): remove the AppData fixture through the retrying helper --------- Co-authored-by: m4air <m4air@Mac.localdomain> * fix(ssh): keep a terminal the previous Orca version's relay still runs instead of replacing it (#25124) After an app update the new relay answers "not found" for a PTY the previous build's relay still runs, because the old relay refuses this build's handshake. The client read that as absence: it expired the lease and the pane cold-restored into an empty shell while the user's shell kept running, unreachable. Each deploy now takes a census of this target's older relay endpoints; while one is live or unverifiable, a not-found reattach keeps the lease and the pane binding, and the pane says the terminal is still running under the previous Orca version. A detached lease also keeps blocking managed-server conversion until that terminal exits. The cross-version harness now extracts src/relay, and a new test drives v1.4.218's relay socket and grace lifecycle with this build's endpoint probe: the probe leaves no grace deadline and reads the old relay as live work. Co-authored-by: m4air <m4air@Mac.localdomain> * feat(orcad): update a managed server on connect when it runs an older build and is idle (#25122) * feat(orcad): update a managed server on connect when it runs an older build and is idle A connect to a managed SSH host now runs the Managed servers update when the host's orcad differs from this app's bundled build, the template carries the host's target, and the update planner finds no live or uncounted terminals. A rejected candidate is restored through the activation journal; the reason is recorded per app version so later connects don't retry it. A host a newer Orca activated is never downgraded: the activation record now names the app version behind each build, and an explicit rollback holds the build it left. * test(e2e): connect without a racing disconnect after relaunch, and report each attempt On launch the app already reaches the managed server through its tunnel; a disconnect racing that restore cancelled the connect that runs the update. * feat(orcad): run the update check when the launch restores a managed server's tunnel An auto-restored host may never see an SSH connect, so its server would never update. The tunnel restore now runs the same check, once per server per session and off the caller's path, through the shared update-check module; a server mid-migration is left alone. --------- Co-authored-by: m4air <m4air@Mac.localdomain> * fix(orcad): retry the pinned Node download through transient network failures (#25154) A Chromium network change (net::ERR_NETWORK_CHANGED) during the pinned Node download failed the deploy and sent the host back to the relay. The archive download now retries up to three more times, after 1s, 3s and 9s, on dropped connections, timeouts, stalls and retryable HTTP statuses, removing the partial file first; checksum mismatches, other HTTP errors and cancels stay final. The transient-error classifier moves from the speech download to src/main/network/transient-download-error.ts so both share it. Co-authored-by: m4air <m4air@Mac.localdomain> * feat(serve): run orca serve on orcad by default, with ORCA_SERVE_RUNTIME=electron as the opt-out (#24972) * feat(serve): run orca serve on orcad by default, with ORCA_SERVE_RUNTIME=electron as the opt-out * test(serve): read Electron serve's pretty-printed readiness in the CLI mode-switch e2e * feat(serve): gate D7 on Windows and serve on orcad there by default * test(serve): take the profile lock in the CLI mode-switch e2e, and keep Windows profile logs on failure * test(serve): tell Electron and orcad serve apart by readiness health, and trace Windows startup * ci(e2e): dump Electron's native log and stack on the Windows serve mode-switch job * fix(serve): keep Windows on Electron serve until it can adopt orcad's daemon The Windows D7 job shows Electron serve exiting before its window when it relaunches onto a terminal daemon orcad forked. Restore the win32 fallback and skip that case there as a known gap; the follow-up PR fixes it and re-flips Windows. * fix(serve): let ORCA_SERVE_RUNTIME=orcad opt in on Windows while Electron stays the default * test(serve): skip the Windows D7 cases where orcad forks the daemon, and stop cleanup hiding a failed relaunch Test 3 hits the same Windows gap as test 2: orcad forks its own daemon there, and Electron crashes at startup beside it. A failed relaunch also made dispose close the old, already closed app, whose throw replaced the launch error. * test(serve): run every Windows D7 case, with the isolated home's AppData in place Electron 43 crashes natively (0xFFFF7003) when it resolves userData and Windows cannot find roaming AppData. The e2e home isolation points USERPROFILE at a fresh folder with no AppData, so later launches hit that. The harness now creates it, every D7 case runs on Windows again, and a new case proves Electron serve starts beside another profile's live orcad daemon. * test(serve): retry removing a Windows e2e profile while a killed daemon releases it --------- Co-authored-by: m4air <m4air@m4airs-Air.localdomain> Co-authored-by: m4air <m4air@Mac.localdomain> * feat(ssh): resume a terminal the previous Orca version's relay still runs, through that relay's own bridge (#25170) * feat(ssh): resume a terminal the previous Orca version's relay still runs, through that relay's own bridge After an app update the previous relay keeps the user's shells alive but refuses this build's handshake. Its own relay.js --connect, run from its own version directory, presents its own bundle hash, so on POSIX hosts the client now reaches it that way: a pane whose reattach the current relay held for an older relay opens a route through the old bridge, takes the PTY owner role without output flow control, reattaches the PTY with its replay, and routes every later operation on that id to the old relay. When the last pane a route serves exits, the route hangs up and the old relay's own idle grace retires it. Windows hosts, relocated short sockets and unreachable bridges keep the held-pane behaviour. The cross-version harness now builds v1.4.218's relay from its tagged sources and runs it as the real detached daemon: a shipped client leaves a shell in it, this build resumes the pane through the old bridge, types into it, sees its output, and watches the old relay exit on its own after the shell does. * fix(ssh): read the legacy relay router through its instance --------- Co-authored-by: m4air <m4air@Mac.localdomain> * fix(orcad): a host re-upgraded after a downgrade can move what the older build added (#25179) The delta view left the moved projects' session state in place whenever it held anything unmovable, so it then counted against the delta. Relay PTY bindings, shutdown markers and the relay consumer's recovery record blocked every move although the terminal gate already proves those terminals exited before any move commits; the move now drops them instead. Co-authored-by: m4air <m4air@Mac.localdomain> * fix(worktree-ps): report a lost-contact host's terminals as unverifiable, not live:0 pty:no (#25167) During a network drop to a relay-served SSH host, every PTY on it reads as an unconfirmed exit, so worktree ps skipped them and printed live:0 pty:no for a terminal that was still running. The listing now counts terminals whose liveness verdict is unverifiable into a new optional unverifiableTerminalCount, and the CLI prints live:unverifiable pty:unverifiable (JSON carries the same word) instead of zero. A host-confirmed exit still reads as zero. Co-authored-by: m4air <m4air@Mac.localdomain> * fix(terminal): a held pane shows no client OS or shell, and the boundary doc says POSIX hosts resume it (#25194) Co-authored-by: m4air <m4air@Mac.localdomain> * fix(orcad): tunnel to the port managed orcad bound and verify it is ours (#25182) When another Orca already listens on 6768, orcad binds a different port. The tunnel kept forwarding to 6768, reached the other runtime, was rejected with 4001, and the connect still reported a managed server. The tunnel now reads orcad's bound port from its active slot's readiness (falling back to the persisted port for slots without one) and, after the forward is up, proves the server answering is the paired runtime. On a mismatch it re-reads the port once and fails with orcad_identity_mismatch instead of reporting managed. Co-authored-by: m4air <m4air@Mac.localdomain> * fix(orcad): a converted host shows only its managed server's rows, and its old editor tabs load there (#25193) Retained relay-era project groups and the per-host SSH catalog now hide like repos and folders. On the managed transition the renderer reloads server names, groups, folders and worktrees, then drops the host's relay-era rows a local refresh would keep. A restored tab with no host stamp in a workspace now owned by a managed server takes that server as owner, in place. Co-authored-by: m4air <m4air@Mac.localdomain> Co-authored-by: Claude <noreply@anthropic.com> * fix(orcad): ship the port-scan worker so managed servers detect workspace ports (#25197) orcad resolves port-scan-command-worker-entry.js beside orcad.js, but the orcad build never emitted it and ORCAD_ARTIFACTS never listed it, so every managed server logged 'probe worker unavailable' and had no port detection. The build now emits it with the other children, and the artifact list carries it, so it is uploaded, hashed and covered by the template contract. A new test bundles orcad and its children and fails when the bundle names a worker or child entry the slot does not ship. Two existing gaps it found, session-scanner-service-entry.js and wsl-transcript-fs-process-entry.js, are listed as known and may only shrink. Co-authored-by: m4air <m4air@Mac.localdomain> * fix(ssh): keep retrying when a reconnect loses the socket before the SSH banner (#25195) After a network outage, a port forwarder or NAT can accept the TCP connection and then close it before the server sends its banner. ssh2 reports that as 'Connection lost before handshake' with no errno, so the reconnect ladder classified it as permanent and published 'error' with no retry scheduled. Treat it as recoverable on the bounded ladder only; the initial connect keeps its narrow classifier. Co-authored-by: m4air <m4air@Mac.localdomain> * feat(serve): serve on orcad by default on Windows too (#25162) D7 now runs every case on Windows (orcad-serve-mode-switch-windows), including Electron serve adopting a daemon orcad forked, so Windows no longer needs the Electron default. Co-authored-by: m4air <m4air@Mac.localdomain> * fix(orcad): the terminal gate asks the relays before treating a detached terminal as running (#25200) * fix(orcad): the terminal gate asks the relays before treating a detached terminal as running, and retiring a host drops its relay recovery record * fix(ssh): earlier-relay census gaps and an unanswered relay stay unverifiable; asking the terminal gate changes nothing - the gate is read-only; the conversion and delta move retire proven detached leases themselves - an expired lease also needs every earlier-build relay to answer before it reads as exited - a failed, input-less or truncated census marks its older relays unverifiable, so reattach holds - a legacy relay route closes only when no attach or listing still awaits it - a disposed session forgets the census it started --------- Co-authored-by: m4air <m4air@Mac.localdomain> * fix(migration): recover interrupted conversions and delta moves, and trim dead migration code (#25226) * refactor(migration): drop the unused full dependency census * refactor(migration): one table for the routed UI fields a migration carries * refactor(migration): share the session focus field list and fix stale claim comments * fix(session): keep a fenced SSH host's source session partition intact through renderer saves The renderer cannot see a fenced host's repos or folders, so hydration drops their worktree rows; a save that still routes any row to ssh:<target> (a folder workspace key stays valid) rewrote that partition without them. That changes the migration's source manifest and loses the session a downgraded build reads back. Main now ignores renderer writes to a fenced, unchanged host's partition. * fix(migration): resume or back out unfinished conversions and delta moves, serialize delta moves, clean stale journals - On connect, a registered server whose conversion never committed resumes the commit; a failure aborts it through the destination, unregisters the server and releases the fence. - An interrupted delta move keeps its journal and gets its changed mark back; the next move resumes it from the journaled manifest or aborts it when the source has moved on. - Delta moves run under the target lifecycle queue and re-check the head, so two concurrent moves cannot write two journal heads. - Delta checks re-read the live source instead of a frozen copy. - Stale journals from a stopped or never-registered server are cleared. - Retiring a chain goes newest first and compacts only at the end. - The journal schema tolerates fields a newer build adds. * fix(migration): hide source rows only after commit, and retire what an older build added to a moved project - Source rows and the renderer session guard share one rule: hidden once the server committed, shown while a migration is still in flight. - A delta move a crash interrupted gets its changed mark back on the next start. - Retirement removes worktree metadata and automations by moved-project scope, so an older build's additions no longer fail the leftover check forever; that check ignores rows of other migrations in the chain. --------- Co-authored-by: m4air <m4air@Mac.localdomain> * fix(orcad): survive a slow first daemon start and close review gaps (#25213) - Give the terminal daemon 30 s to start on Windows and retry the spawn once before falling back, so a cold first deploy is not refused as daemonless. - Say in the activation refusal that orcad.log holds the daemon's error and that the next connect retries. - Managed stop completion now waits out daemon retirement plus the shutdown deadline. - Publish the instance lock atomically and reclaim an abandoned torn lock. - Fix the stop listener closing before it was defined on an already-present request. - `orca serve`'s cache prune keeps the slot a running local orcad uses. - A timed-out daemon retirement reopens admission once it is refused; a retirement that may have reached the daemon stays fenced. - A headless host keeps an editor tab's unsaved draft unless the close is forced. - Unverifiable terminals are attributed by the host's PTY record, like live ones. - An explicit --user-data-dir is no longer overridden on Windows. - Read Windows daemon process identity from the process table, not PowerShell. - Remove the unread data-event incarnationId and daemon health runtime fields, share one process-alive and error-code check, and fold the websocket limits file back. Co-authored-by: m4air <m4air@Mac.localdomain> * feat(cli): stop, update, roll back and recover managed Orca servers from the CLI (#25201) * feat(cli): stop, update, roll back and recover managed Orca servers from the CLI orca environment status|update|rollback|recover|stop|cancel-stop call the same managed-server actions as Settings > Managed servers, over new managedServer.* runtime RPC methods. The desktop main process registers those actions; the runtime advertises managedServer.v1 only then, and the CLI refuses on a runtime without it or one that answers method_not_found. stop requires --yes. * fix(ipc): keep a missing managed-server selector a rejection, not a synchronous throw --------- Co-authored-by: m4air <m4air@Mac.localdomain> * fix(ssh): Move to managed server converts the host it is offered for (#25196) The move stops the relay terminals and passes the terminal census; with #25179 the conversion no longer refuses on the stopped terminal's saved tab, layout leaf and pane incarnation. A new test drives one live relay terminal through Move to a conversion whose manifest carries the tab without its relay PTY, so it spawns a fresh shell on orcad. ssh:terminateSessions also no longer records a not-found shutdown as terminated while an older Orca build's relay may still run the PTY (#25124): that terminal is reported unverifiable and its lease kept, so the move refuses instead of converting over a running shell. Co-authored-by: m4air <m4air@Mac.localdomain> * fix(orcad): deploy the glibc 2.17 runtime on old-glibc hosts, and fall back to the pinned relay (#25180) * fix(orcad): deploy the glibc 2.17 runtime on old-glibc hosts, and fall back to the pinned relay Managed orcad picked its runtime from the host's libc flavour alone, so a CentOS 7 host (glibc 2.17) got the default linux-x64-glibc Node, whose self-test fails there. The deploy now picks the runtime by glibc the same way the relay ladder picks rung A or B, and the host-side slot checks accept the compat target it ships. When orcad still can't run, the host is recorded as such and the relay connect that follows now runs the pinned-runtime ladder instead of defaulting to the host-Node relay, so a host with no Node lands on rung B rather than failing. The hostile-host lane deploys managed orcad on a fresh CentOS 7 host and asserts it runs on the compat runtime with no default runtime uploaded. * test(ssh): CentOS 7 cell asserts the compat runtime pick, then the refusal and relay rung B fallback The compat template still ships the base @parcel/watcher binary, which needs GLIBCXX_3.4.20; CentOS 7's libstdc++ stops at 3.4.19, so the candidate's preflight refuses. The cell now pins that end-to-end behaviour: compat runtime picked and uploaded alone, refusal classified native_preflight, and the relay that follows settles on rung B with no runtime setting. --------- Co-authored-by: m4air <m4air@Mac.localdomain> * test(serve): prove D7 on an installed Windows app whose daemon runs from the relocated daemon-host (#24976) * test(serve): prove D7 on an installed Windows app whose daemon runs from the relocated daemon-host * test(serve): read the pre-switch scrollback best-effort and wait for it after reattach * test(serve): opt the packaged Windows serve switch into orcad explicitly --------- Co-authored-by: m4air <m4air@m4airs-Air.localdomain> Co-authored-by: m4air <m4air@Mac.localdomain> * fix(migration): a retirement that fails after deleting rows resumes instead of reading as changed (#25234) The chain head records sourceRetiringAt before any row is retired; the startup change check and the chain's own comparison skip a head that carries it. Co-authored-by: m4air <m4air@Mac.localdomain> * feat(orcad): managed orcad idles out after 15 minutes and is started again whenever it is down (#25121) * feat(orcad): a managed orcad stops after 15 idle minutes and starts again on the next connect A client-launched orcad now exits, like the relay, once no client, terminal, working agent, staged migration or activation fence has been seen for 15 minutes. The exit is the normal graceful shutdown, which leaves the terminal daemon running; the daemon retires only if it proves itself empty. A record in the data root tells the next start (and its readiness health) that the stop was an idle one rather than a crash. On connect and after host resume, a fresh tunnel whose server does not answer starts the activated slot under the activation fence, but only on a proven exit, so a stopped server reads as not running rather than a failure. * fix(orcad): keep orcad-entry under max-lines; idle e2e connects without a relay repo * fix(orcad): deploy and rollback launches carry the managed idle-exit fence The candidate launch in activation and the rollback launch built their own launch spec without the activation root, so a freshly deployed orcad never enabled idle exit; only the wake path did. The field is now required on every launch spec, so the type system covers each launch site. * feat(ssh): start a stopped managed orcad on connect, on restore and after resume Once orcad stopped (idle, kill or host reboot), a connect still resolved managed over a forward to a dead port and every call failed. Every connect now checks the server behind its tunnel, as does a call through a restored environment; a server proven stopped is started from its activated slot under the activation fence, adopting a surviving daemon and its terminals. The status line shows the start, and a start that fails keeps the host managed with the reason and orcad.log's tail, never as a terminal verdict. * fix(ssh): reuse a serving verdict only on the same SSH transport, for 5s A reconnect right after a reboot was answered from the previous transport's cached verdict, so the stopped server was never started. * fix(ssh): key the serving verdict on the tunnel's remote port too * test(ssh): a stopped server starts before the update counts its terminals * feat(ssh): check serving at the bound port, and follow a restarted orcad to a new one The serving check uses the port the tunnel forwards to (the one orcad bound). A restart that binds a different port drops the forward and rebuilds it at the new port, within the same ensure or on the explicit connect check. The tunnel manager class moves to its own file to stay under max-lines. --------- Co-authored-by: m4air <m4air@Mac.localdomain> * fix(ssh): a connect with no leases asks the host's relay endpoints before converting (#25223) * fix(ssh): a connect with no leases and no relay session asks the host's relay endpoints before converting * refactor(ssh): one isLiveSshPtyLease for every lease-liveness check * fix(ssh): the host relay census asks a relay its PTYs before reading it as live work An accepting relay whose holders or children the probe could not read (no lsof, an unrecognised service child) read as live work, so a relay-era host that had exited its last shell never converted. The relay's own bridge answers pty.listProcesses without the owner role. * fix(ssh): the host relay census runs each relay's probe and bridge on the runtime it runs on Pinned-ladder hosts often have no Node on PATH, so a PATH Node read every relay as unverifiable. Each daemon's own argv names its pinned runtime, or the host Node a legacy relay started with. * fix(ssh): an older relay's bridge runs on that relay's own runtime --------- Co-authored-by: m4air <m4air@Mac.localdomain> * feat(orcad): build @parcel/watcher into the glibc 2.17 compat slot, so CentOS 7 runs managed orcad (#25199) The compat target swapped in only node-pty, so it shipped the base target's upstream watcher.node, which needs GLIBCXX_3.4.20; CentOS 7 stops at 3.4.19. orcad's preflight refused it, and relay rung B lost file watching without saying so. The compat slot now compiles @parcel/watcher from the package's own sources against the pinned headers with the C++ runtime static, like node-pty. The slot gates (glibc 2.17 symbol floor, no shared libstdc++, N-API 8) and the smoke load cover it, the template stages it into the compat target, and both orcad and relay rung B pick it up from there. COMPAT_SLOT_ADDONS names a compat slot's own addons: a compat slot missing one fails --require-slots, and a compat template target that would ship any native file without a compat build fails the template build. The CentOS 7 cell expects an activated managed server again, and every launched relay cell loads its watcher directly. Co-authored-by: m4air <m4air@Mac.localdomain> * fix(phase3): close e2e routing holes, localize managed-server outcomes, trim harness and CI (#25206) * fix(phase3): close e2e routing holes, localize managed-server outcomes, trim harness and CI - Route the auto-convert spec into needs_build and route the new orcad/serve e2e helpers to the specs that use them, with routing tests. - Run the auto-convert Docker lane only when routed; fold the missing-AppData check into the Windows mode-switch job and drop crash-hunt diagnostic env. - Delta-move dialog keys its preview on the target id and cannot close or resubmit mid-move; Resume has an in-flight guard. - Managed-server toasts and status lines show localized messages instead of raw codes or main-process English; add singular and zero-count wording. - Settings style fixes (quiet Cancel, section header, labelled fields, progress labels); delete dead i18n keys and the unused previewConversion. - Harness: kill the serve child on readiness timeout, share the isolated profile and spawn-until-ready helpers, hooks and a condition wait in the auto-convert spec, shared cross-version exec helper. Co-authored-by: m4air <m4air@Mac.localdomain> * fix(phase3): localize the refusal toast, share serve readiness and liveness helpers, correct D7 docs - The refusal toast no longer shows main's English blocker detail; the SSH Hosts status line keeps it under Details. A missing terminal count is no longer defaulted to 0. - startOrcadServe uses spawnUntilReady, so a readiness timeout kills orcad; one isPidAlive replaces the spec's copy and the lock-holder loop. - The port-6768 auto-convert test uses the shared hooks. - The docs say the Windows D7 job checks rather than gates. Co-authored-by: m4air <m4air@Mac.localdomain> * fix(ci): give the idle-exit spec its app build and route the convert harness to it Co-authored-by: m4air <m4air@Mac.localdomain> --------- Co-authored-by: m4air <m4air@Mac.localdomain> * ci(adhoc): pin every job of an adhoc build to the commit resolved at dispatch (#25311) Each job read the requested branch name and checked out whatever it pointed at when that job started. A push mid-run mixed commits: in run 37234548638 the glibc217 slot lane built 2ddea8736e, then #25199 merged, and desktop_template checked out 70948d597f, whose merge step requires the compat watcher that lane never built. A first resolve-ref job (no secrets) resolves the branch, tag or full SHA once, and every job checks out that commit. The mac job still vets it for reachability before signing; the requested name stays the concurrency key and the release label. Co-authored-by: m4air <m4air@Mac.localdomain> * fix(terminal): report a confirmed relay PTY exit as exited and give a cold Windows PTY probe more time (#25304) A worktree terminal close returned as soon as the relay confirmed the stop, but the exit frame reaches the runtime record only after the SSH output intake drains, so the verdict read straight after saw a still-connected PTY and answered unverifiable. The stop now waits, bounded by its deadline or 10 s, for that record. The bundled runtime's PTY probe gives the first Windows spawn 15 s and retries once after a timeout only; spawn errors and non-zero exits still fail at once. Co-authored-by: m4air <m4air@Mac.localdomain> * fix(relay,orcad): keep new wire messages forward compatible, drop unshipped relay.reset, fix shutdown and serve gaps (#25298) * fix(orcad-migration): keep migration wire forward compatible with newer peers * docs(pty): record why pty.resumeClient negotiates by method-not-found * fix(relay): remove the unused relay.reset method and keep a deferred shutdown serving * refactor: drop dead hold API, release gate and migration pass-throughs; fix serve and delegation gaps * fix(lint): keep reopen hooks within file size limits * revert type dedupe in orcad-incumbent-recovery to avoid a parallel conflict * fix(lint): name stop-reply request fields for their role --------- Co-authored-by: m4air <m4air@Mac.localdomain> * refactor(ssh): one managed-tunnel ownership check and forward bookkeeping (#25316) Co-authored-by: m4air <m4air@Mac.localdomain> * fix(ssh): a stuck managed server can recover, cancels finish their change, unknown stays unknown; delete unused reset/drain code (#25227) * refactor(ssh): delete the unused connection reset and drain machinery * fix(ssh): surface a stuck managed server, finish activations past their first change, and fall back from Windows launch refusals - A rejected build that changed profile state now refuses as orcad_recovery_changed_state (unverifiable) with a Recover path that restores the snapshot once the operator accepts; an interrupted activation is no longer reported as a quiet update deferral. - Activation and rollback drop the abort signal after their first journaled change. - Windows launch refusals and unsafe command lines send the host back to the relay. - Fence refusals keep unverifiable blockers unverifiable; the POSIX liveness probe reads kill errors in the C locale and treats permission errors as unknown. - A host with no relay fallback surfaces its real connect error; disconnect and removal clear the setting-up status, and a cancelled decision's progress is dropped. - Remove tcp_forwarding_refused leftovers, dedupe incumbent stop/restore, exec-or-empty, census client, errorMessage and the blocking-blocker predicate; split activation and snapshot files under 300 lines. * fix(ssh): one import per module in the rollback transition --------- Co-authored-by: m4air <m4air@Mac.localdomain> * fix(migration): committed reads trust the receipt, conversions survive UI churn, journals are cached, and round-2 low items (#25327) * fix(ssh): keep a newer build's fence and note fields, and validate a fence beside a legacy owner Normalization now validates known fields but passes unknown ones through on orcadFence and the managed-server notes. A legacy managed owner next to a malformed fence falls back to the owner's environment id instead of keeping the malformed fence. * fix(migration): delete a retired migration's scrollback files once retirement is durable Retirement dropped the moved terminals' scrollback refs from session state but kept the files forever. After the journal records source-retired, the files the manifest names are deleted, except refs any session partition or pending export still names. A crash before the delete repeats it on the next retirement pass. * fix(orcad): a committed migration reads as committed from its receipt, survives receipt eviction, and abandoned stages expire - Committed-state reads trusted only a byte-identical copy of every moved row and snapshot, so a live server that had been used could never confirm its own commit. The receipt alone now proves it; full equality stays inside the commit. - A receipt that ages out of the 64-entry list keeps a compact record, so the commit never reads as absent. - A stage no client returns to within a week stops holding the server awake and is dropped at the next stage. * fix(migration): freeze a host's session from the fence on, ignore UI churn in the frozen-source check, and cache parsed journals - Renderer writes to a fenced host's ssh: partition are skipped from fence time, not only after commit, so tab work mid-conversion cannot change the source. - The frozen-source check leaves out workspace session and client routing state, which the UI rewrites as the user works (including through the local partition and UI state); the server gets them as journaled. - Parsed journals are cached per directory, keyed by each file's inode, size and mtime and dropped on every write and remove, so list and session calls stop re-parsing every manifest. --------- Co-authored-by: m4air <m4air@Mac.localdomain> * fix(ssh): an older relay a census cannot rule out stays held, and a reconnect resumes held PTYs through it (#25331) - A census that does not know the host platform is unverifiable, never "no older relay". On Windows it now probes each older version directory's pipe for the target, the way relay GC does, so a live older Windows relay keeps its PTYs held instead of respawning their panes; those endpoints are held, never bridged. - On reconnect, a PTY the current relay disowned while an older relay holds it is reattached through that relay's own bridge, with its runtime restored and its replay forwarded. When no route serves it, it is left for recovery like an exhausted reattach. - A superseded or disposed deploy no longer starts the census, so it cannot replace the current attempt's. - A route stops holding an unserved PTY that exits. - Drop the unused censusPreviousRelays export. Co-authored-by: m4air <m4air@Mac.localdomain> * fix(phase3): round-2 ui-infra review fixes (#25325) - An unverifiable move refusal with no count says so instead of "0 terminals". - One move per host: the dialog stays mounted while open, joins a run already in flight on remount, and the toast shares the same guard. - A managed server's start, wake or update no longer reloads every host's catalog; only a new environment for the host loads, scoped to that host and the local catalog. - A failed host-partition session write is no longer acknowledged as written, so the writer re-queues those fields. - The deploy picker lists only hosts with no managed server or pending move. - Harness: orcad and the released relay daemon are stopped when startup fails. - Windows host CI: the convert cell always runs last and fails on a failed native switch or build; ssh-host-server unit tests no longer trigger it. Co-authored-by: m4air <m4air@Mac.localdomain> * fix(relay,cli): never report an unreachable terminal as exited; keep PTYs through a deferred shutdown (#25328) * fix(relay,cli): never read an unreachable relay or terminal as exited; keep PTYs on a deferred shutdown * fix: stop an unrecorded breakaway child, route orcad serve through the spawn chokepoint, and drop review-flagged leftovers --------- Co-authored-by: m4air <m4air@Mac.localdomain> * fix(orcad): reclaim a crashed desktop lock, close the stop-cancel race, drop dead daemon and RPC code (#25338) * fix(orcad): reclaim a crashed desktop lock, close the stop-cancel race, drop dead daemon and RPC code - A desktop reclaims a stale desktop lock record (Electron's lock proves it), and a real orcad hold shows a dialog instead of exiting silently. - A failing quit handler no longer skips closing observability. - Completion withdraws its request when a cancel lands mid-write; the listener removes a cancelled leftover. - An idle stop's clean record is retracted when the shutdown fails or overruns. - The managed-stop request tolerates unknown fields from a newer client. - Shared error-code checks, one win32 coverage rule, no redundant isAlive filters. - Remove the recovery-only daemon provider, requirePinnedWsPort/strictPort and the superseded orcad.migration.importCatalog RPC. * fix(startup): keep main-process-preflight under the line cap --------- Co-authored-by: m4air <m4air@Mac.localdomain> * fix(ssh): fail fast on a held fence, restore untouched rollbacks, atomic Windows host script, honest tunnel ensure (#25332) * fix(ssh): fail fast on a held activation fence, restore a rollback the target never touched, stage the Windows host script atomically - withOrcadActivationLock no longer waits up to 15 minutes inside the target lifecycle: a held fence answers at once (orcad_activation_recovery_required); deploy probes it before uploading. - A rejected rollback target that left the restored snapshot untouched puts the newer build back unasked; only a real change waits for the operator. Crash recovery applies the same rule. - The Windows host script is written only when missing, through a partial file and a rename; a bridge exit without a sentinel is unverifiable unless the shell could not find the command. - The managed tunnel's ensure() throws when superseded, and a caller arriving after close() builds a fresh run instead of joining the doomed one. - An unparseable stop answer after a stop was sent keeps the fence (new 'unconfirmed' outcome). - stopRemote starts over instead of reporting live when another run settled the journal. - Releasing an unreachable setup stops the orcad it activated and clears active on proven exit. - A failed startup is torn down quietly so its error stays published; port-forward listeners keep an error handler; a linked SSH access connect is cancelled when its window closes. - Shared errorMessage, one SFTP transfer helper, getConnectGeneration only, test-cell names. * fix(ssh): stage the Windows host script through the pinned node.exe, which cmd.exe and PowerShell both run * fix(ssh): host-script staging runs as host-script ops; a joiner of an overtaken tunnel run builds its own - The presence check and the install are ops in the uploaded host script (script-present, and script-install run from the partial upload itself), invoked as node.exe <script> <op> <args> like every other Windows host op: no inline code. - ensure() callers that joined an in-flight run no longer inherit its 'superseded' end; they start a fresh run, which also covers a close() in between. --------- Co-authored-by: m4air <m4air@Mac.localdomain> * fix(ssh): a connect re-checks unverifiable relay terminals once its relay session can answer (#25385) The server decision runs before any relay session exists, so a terminal this desktop left detached could never be asked about and read unverifiable for as long as it ran. On Windows no endpoint census can fill that gap. Once the session is up, the relay that holds the PTY answers and a terminal it still runs is reported live. Co-authored-by: m4air <m4air@Mac.localdomain> * fix(settings): a managed server whose status never loaded reads unknown, not "Not running" (#25389) Co-authored-by: m4air <m4air@Mac.localdomain> * fix(relay,runtime-env,cli): keep a deferred relay's AI Vault and skill uploads, make managed re-pair crash-safe, show unverifiable in worktree ps text (#25393) Co-authored-by: m4air <m4air@Mac.localdomain> * fix(orcad): keep a surviving daemon's slot through the cache prune, and drop an idle record a signal stop took over (#25387) - The slot prune also protects any slot a live terminal daemon's PID record points into, and evicts nothing while a daemon record is unreadable. - The shutdown trigger reports whether it took ownership; an idle stop that another source took over discards its clean-idle record. Co-authored-by: m4air <m4air@Mac.localdomain> * fix(ssh): edit a managed host's connection, show rows no migration owns, drop the unused quit-drain predicate (#25403) - SSH settings may edit a managed host's connection fields; its fence and generation are never written from the renderer, and its tunnel is closed so the next use redials. Removing it is refused with a pointer to Stop under Managed servers, which stops and removes the server; the renderer no longer ends the host's terminals before that refusal. - A fenced host with no journal (an empty host's deploy, or one whose journal compacted after retirement) no longer hides its rows: no committed move owns them, so projects an older build added stay visible. An unreadable journal still hides them. - The quit drain's mayDetach predicate and disconnectAll's filter had no production caller and are removed. Co-authored-by: m4air <m4air@Mac.localdomain> * fix(ssh): input to a terminal an older relay holds but no route serves is refused, not dropped (#25404) Co-authored-by: m4air <m4air@Mac.localdomain> * fix(ssh): refresh a changed host's status once its delta move or keep-server choice lands (#25416) The status line kept saying the host was changed on an older Orca until a manual reconnect. Main now publishes the host as managed by its server after a successful move or keep, with a disconnected state when the move released the relay session. Co-authored-by: m4air <m4air@Mac.localdomain> * fix(ssh): a stopped managed server stops showing on its SSH host without a reconnect (#25409) * fix(ssh): a stopped managed server stops showing on its SSH host without a reconnect On unlink, forget the host's managed-server decision and republish its connection state. The republish also drops the host's cached worktree scans, so worktree.ps stops naming the removed server. * fix(runtime-environments): a removed server's workspace session partition goes with it Host-scoped listings enumerate session partitions as known hosts, so worktree ps kept naming a stopped or remov…

ELI5
This adds tests that run the real app against real SSH hosts and check the new connect-time conversion (#24979). A host Orca used over the old relay must move onto a managed Orca server the next time it connects. Its projects, folders and terminal tabs must come along, and its old records must stay until source retirement is switched on. It runs on a Linux Docker host and on a Windows OpenSSH host. A third test checks that the last relay-only release (v1.4.218) can still see a converted host and its project, in case someone downgrades.
What Changed
tests/e2e/ssh-orcad-auto-convert.spec.ts, one spec for both host kinds:ORCA_ORCAD_TEMPLATE_PATH) is empty, so the connect keeps the relay (orcad_unavailable). The spec registers the repo, adds a remote project group and folder workspace, adds a session tab that never spawns a shell, and runs then exits a shell (its exit record lets the terminal gate prove nothing is running).managed. On the new environment,repo.list,folderWorkspace.listandsession.tabs.listAllmust contain the repo, the folder and the tab.destination-committedwithsourceRetainedAt. With the retirement flag switched on, the next connect must move it tosource-retired, and the server must still serve the repo.tests/e2e/helpers/orcad-convert-host.ts: the host is the Docker fixture (ORCA_E2E_ORCAD_CONVERT_HOST=docker) or a Windows host-cell descriptor.orcad-auto-convert-dockerjob ine2e.ymlbuilds thelinux-x64-glibctemplate outside the app's default lookup and runs the spec. It's registered as a dedicated spec (job selection, sharding expectation, source route, release permissions).orcad-convertcell inssh-windows-hosts, run on the x64 inbox host only and last in the run.out/orcad-templateaside.tests/e2e/cross-version-wire/managed-orcad-convert-downgrade.unit.test.tsloads v1.4.218's ownnormalizeSshTargetandSshConnectionStorefrom a tagged checkout.orcadFence, noowner) must still be listed with its relay endpoint.visibleReposhides the retained project, which that release's unfilteredrepos:liststill returns.ORCA_E2E_ROLLOUT_FLAGS_FILE, read on every rollout flag check and honored only whenORCA_E2E_USER_DATA_DIRis set (the gate the otherORCA_E2E_*variables use). The retirement flag otherwise only arrives through the hardcoded campaign fetch, so a test couldn't switch it on between two connects.Why
The conversion reads the app's global SSH connection manager and target registry, so only the real app can drive it end to end. Running the same spec against Docker and a Windows host shows the conversion the same way on both kinds of host. The downgrade check uses the cross-version harness so the old build's own code decides visibility, not a re-implementation of it.
Linked Issue
Part of the Node-runtime convergence (#16741), Phase 3. Stacked on #24979.
Visual Proof
N/A: tests and CI.
Testing
Locally: the downgrade cross-version test (2 tests against a v1.4.218 checkout), the rollout override tests, the host-cell tests, and the workflow and routing contract suites (42 files, 388 tests). Lint, the gate and the e2e typecheck are clean.
The Docker and Windows cells run in this PR's CI.
I manually tested these changes locally
Automated tests added/updated, or explained why not below
Agent skill upstream boundary
docs/reference/agent-skill-sharing-upstream-boundary.mdand copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.