mirror of
https://github.com/stablyai/orca.git
synced 2026-10-22 08:03:37 +00:00
wip(ssh): hoist migration for SSH session partitions — NOT YET WORKING
Adds hoistSshPartitionsIntoLocalSession and wires it into load. The migration is reached (verified: it sees the ssh:<target> partition on reopen) but the hoisted tabs are not visible on the reloaded store, so the oracle is still red. The oracle itself is now correct: it writes the partition, flushes, and REOPENS the profile so the load-time migration is what is under test. Its anti-vacuity checks pass — the partition survives the reopen and local starts empty. Do not ship. Next step is in the commit body of the follow-up.
This commit is contained in:
@@ -2849,6 +2849,70 @@ export type PtyBindingSourceExpectation = {
|
||||
incarnationId?: string
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Fold SSH pane membership left in `workspaceSessionsByHostId["ssh:<target>"]` into the local
|
||||
* session, which is the only plane the binding writer maintains.
|
||||
*
|
||||
* Why this has to exist: consolidating the two planes removed the writer but not the data. A pane
|
||||
* the local session has never heard of looks new to `persistPtyBinding`, and reattach passes
|
||||
* `mayCreate: false`, so the binding is refused and rolled back — for every pane the upgrading
|
||||
* user had open. Local always wins; the partition only supplies panes local does not already know.
|
||||
*/
|
||||
export function hoistSshPartitionsIntoLocalSession(state: PersistedState): boolean {
|
||||
const partitions = state.workspaceSessionsByHostId
|
||||
if (!partitions) {
|
||||
return false
|
||||
}
|
||||
const local = state.workspaceSession
|
||||
if (!local) {
|
||||
return false
|
||||
}
|
||||
let changed = false
|
||||
for (const [hostId, partition] of Object.entries(partitions)) {
|
||||
if (!hostId.startsWith('ssh:') || !partition) {
|
||||
continue
|
||||
}
|
||||
for (const [worktreeId, tabs] of Object.entries(partition.tabsByWorktree ?? {})) {
|
||||
const existing = local.tabsByWorktree?.[worktreeId] ?? []
|
||||
const known = new Set(existing.map((tab) => tab.id))
|
||||
const missing = (tabs ?? []).filter((tab) => !known.has(tab.id))
|
||||
if (missing.length === 0) {
|
||||
continue
|
||||
}
|
||||
local.tabsByWorktree = { ...local.tabsByWorktree, [worktreeId]: [...existing, ...missing] }
|
||||
changed = true
|
||||
}
|
||||
for (const [tabId, layout] of Object.entries(partition.terminalLayoutsByTabId ?? {})) {
|
||||
if (!layout || local.terminalLayoutsByTabId?.[tabId]) {
|
||||
continue
|
||||
}
|
||||
local.terminalLayoutsByTabId = { ...local.terminalLayoutsByTabId, [tabId]: layout }
|
||||
changed = true
|
||||
}
|
||||
for (const [paneKey, incarnationId] of Object.entries(
|
||||
partition.terminalPtyIncarnationsByPaneKey ?? {}
|
||||
)) {
|
||||
if (local.terminalPtyIncarnationsByPaneKey?.[paneKey] !== undefined) {
|
||||
continue
|
||||
}
|
||||
local.terminalPtyIncarnationsByPaneKey = {
|
||||
...local.terminalPtyIncarnationsByPaneKey,
|
||||
[paneKey]: incarnationId
|
||||
}
|
||||
changed = true
|
||||
}
|
||||
for (const [worktreeId, tabId] of Object.entries(partition.activeTabIdByWorktree ?? {})) {
|
||||
if (local.activeTabIdByWorktree?.[worktreeId] !== undefined) {
|
||||
continue
|
||||
}
|
||||
local.activeTabIdByWorktree = { ...local.activeTabIdByWorktree, [worktreeId]: tabId }
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
return changed
|
||||
}
|
||||
|
||||
export class Store {
|
||||
private state: PersistedState
|
||||
private readonly dataFile: string
|
||||
@@ -3938,6 +4002,14 @@ export class Store {
|
||||
migrated.githubCache = readGithubCacheSnapshot(this.dataFile) ?? migrated.githubCache
|
||||
}
|
||||
|
||||
// Why: earlier builds wrote SSH pane membership into `ssh:<target>` partitions; this build's
|
||||
// binding writer is local-only and its reattach refuses to create, so an unhoisted partition
|
||||
// means every pane is refused and the user's tabs are discarded. Runs on the final state so a
|
||||
// later migration cannot replace the session object out from under it.
|
||||
if (hoistSshPartitionsIntoLocalSession(migrated)) {
|
||||
this.loadNeedsSave = true
|
||||
}
|
||||
|
||||
logPersistenceStartupMilestone('persistence-load-done', {
|
||||
repos: migrated.repos.length,
|
||||
workspaceSessionBytes: Buffer.byteLength(JSON.stringify(migrated.workspaceSession))
|
||||
|
||||
@@ -44,6 +44,15 @@ async function createStore(state: Record<string, unknown> = {}) {
|
||||
return new Store()
|
||||
}
|
||||
|
||||
/** Reopen the SAME profile — the upgrade launch. The hoist migration runs at load, so a test that
|
||||
* writes the partition into an already-loaded store would never exercise it. */
|
||||
async function reopenStore() {
|
||||
vi.resetModules()
|
||||
const { Store, initDataPath } = await import('./persistence')
|
||||
initDataPath()
|
||||
return new Store()
|
||||
}
|
||||
|
||||
/** One SSH pane, exactly as the partitioned build persisted it. */
|
||||
function paneSession() {
|
||||
return {
|
||||
@@ -71,14 +80,18 @@ describe('an SSH pane whose membership was persisted into the ssh partition', ()
|
||||
})
|
||||
|
||||
it('is still bound on reattach after upgrading to a build that reads local', async () => {
|
||||
// Only the ssh partition holds the pane — the shape an upgrading user actually carries.
|
||||
// Written through the store, the way the partitioned build wrote it.
|
||||
const store = await createStore()
|
||||
store.setWorkspaceSession(paneSession() as never, SSH_PARTITION)
|
||||
// The old build: pane membership written into the ssh partition, nothing in local.
|
||||
const before = await createStore()
|
||||
before.setWorkspaceSession(paneSession() as never, SSH_PARTITION)
|
||||
expect(
|
||||
before.getWorkspaceSession(SSH_PARTITION).tabsByWorktree?.[WORKTREE],
|
||||
'the ssh partition never received the pane, so this proves nothing'
|
||||
).toHaveLength(1)
|
||||
expect(before.getWorkspaceSession().tabsByWorktree?.[WORKTREE] ?? []).toHaveLength(0)
|
||||
before.flushOrThrow()
|
||||
|
||||
// Anti-vacuity: the pane really is in the ssh partition and really is absent from local.
|
||||
expect(store.getWorkspaceSession(SSH_PARTITION).tabsByWorktree?.[WORKTREE]).toHaveLength(1)
|
||||
expect(store.getWorkspaceSession().tabsByWorktree?.[WORKTREE] ?? []).toHaveLength(0)
|
||||
// The upgrade launch.
|
||||
const store = await reopenStore()
|
||||
|
||||
// The reattach bind: no hostId, refuses to create.
|
||||
const bound = store.persistPtyBinding({
|
||||
@@ -99,8 +112,10 @@ describe('an SSH pane whose membership was persisted into the ssh partition', ()
|
||||
// `mayCreate` existed, the same state re-created the tab instead of discarding it. That is what
|
||||
// "before, many of the tabs were retained" described — they were being recreated.
|
||||
it('was recreated rather than discarded before the refusal was introduced', async () => {
|
||||
const store = await createStore()
|
||||
store.setWorkspaceSession(paneSession() as never, SSH_PARTITION)
|
||||
const before = await createStore()
|
||||
before.setWorkspaceSession(paneSession() as never, SSH_PARTITION)
|
||||
before.flushOrThrow()
|
||||
const store = await reopenStore()
|
||||
|
||||
const bound = store.persistPtyBinding({
|
||||
worktreeId: WORKTREE,
|
||||
|
||||
Reference in New Issue
Block a user