fix(desktop): let a per-profile remote override win over the forced-local route (#90477)

resolveRegistryLocalRoute collapsed globalRemote and profileRemoteOverride
into one forced-local branch. The two cases are different:

- globalRemote: forcing "This device" to spawn genuinely-local children is
  the intended migration behavior — unchanged.
- profileRemoteOverride: the per-profile SSH/remote override is an explicit,
  authoritative routing decision for that profile. Forcing local made the
  roster enumerate the profile via its override but open the thread in a
  forced-local child, which dies with 'Profile "x" no longer exists' when
  the profile only exists on the remote — reproduced on a macOS Desktop in
  global SSH mode where mythony-agent/q-agent exist only on the NAS.

The registry 'local' entry now delegates to the legacy profile route when a
per-profile override is present, so the override stays authoritative.

Tests: the override case now pins delegation, and a new witness pins that
globalRemote alone still forces local; both contracts are asserted together.
88 connection-registry + 91 remote-lifecycle + 73 routing tests pass;
tsc --build clean.
This commit is contained in:
SZWzz
2026-08-28 17:53:05 +08:00
committed by Teknium
parent 3fcfa647ed
commit ae81786580
2 changed files with 25 additions and 2 deletions

View File

@@ -697,9 +697,22 @@ test('registry local route: v1 REMOTE global mode forces a genuinely-local backe
assert.notEqual(route.poolKey, backendScopeKey(LOCAL_CONNECTION_ID, 'default'))
})
test('registry local route: a per-profile remote override also forces local', () => {
test('registry local route: a per-profile remote override delegates to the override (#90477)', () => {
// The per-profile SSH/remote override is the authoritative route for that
// profile. Forcing local here made the roster list the profile via its
// override but open the thread in a local child — which fails when the
// profile exists only on the remote. The override must win.
const route = resolveRegistryLocalRoute('research', { profileRemoteOverride: true })
assert.deepEqual(route, { delegate: true, poolKey: 'research' })
})
test('registry local route: global remote keeps forced-local even when a profile override is absent', () => {
// Witness for the other half of the split: app-global remote mode still
// forces "This device" to spawn genuinely-local children (migration
// scenario above) — only the per-profile override delegates.
const route = resolveRegistryLocalRoute('research', { globalRemote: true })
assert.deepEqual(route, { delegate: false, poolKey: 'conn:local::research' })
})

View File

@@ -493,7 +493,17 @@ export function resolveRegistryLocalRoute(
): RegistryLocalRoute {
const profileKey = String(profile ?? '').trim() || 'default'
if (opts.globalRemote || opts.profileRemoteOverride) {
// A per-profile SSH/remote override is an explicit per-profile routing
// decision: the override owns this profile's backend, so the 'local' entry
// must delegate to the legacy profile route (which resolves the override),
// not spawn a forced-local child. Forcing local here is the #90477 split:
// the roster lists the profile via its override, but opening the thread
// spawned a local backend that fails when the profile doesn't exist locally.
if (opts.profileRemoteOverride) {
return { delegate: true, poolKey: profileKey }
}
if (opts.globalRemote) {
return { delegate: false, poolKey: `${backendScopePrefix(LOCAL_CONNECTION_ID)}${profileKey}` }
}