fix(desktop): preserve implicit default local route
This commit is contained in:
@@ -745,18 +745,21 @@ test('registry local route: per-profile override wins when global remote is also
|
|||||||
assert.deepEqual(route, { delegate: true, poolKey: 'research' })
|
assert.deepEqual(route, { delegate: true, poolKey: 'research' })
|
||||||
})
|
})
|
||||||
|
|
||||||
test('registry local route: a concrete remote-only profile is refused on the forced-local branch, including default', () => {
|
test('registry local route: a concrete remote-only profile is refused on the forced-local branch', () => {
|
||||||
// globalRemote still force-locals a profile that exists on this machine
|
// globalRemote still force-locals a profile that exists on this machine
|
||||||
// (This device must not dial the remote). A profile that exists only on the
|
// (This device must not dial the remote). A named profile that exists only
|
||||||
// remote must not spawn a local child — including default, which the
|
// on the remote must not spawn a local child.
|
||||||
// ordinary existence guard exempts and would otherwise start silently.
|
|
||||||
const named = resolveRegistryLocalRoute('inbox', { globalRemote: true, localProfileExists: false })
|
const named = resolveRegistryLocalRoute('inbox', { globalRemote: true, localProfileExists: false })
|
||||||
const fallback = resolveRegistryLocalRoute('default', { globalRemote: true, localProfileExists: false })
|
|
||||||
|
|
||||||
assert.match(String(named.refuse ?? ''), /Profile "inbox" no longer exists/)
|
assert.match(String(named.refuse ?? ''), /Profile "inbox" no longer exists/)
|
||||||
assert.equal(named.delegate, false)
|
assert.equal(named.delegate, false)
|
||||||
assert.match(String(fallback.refuse ?? ''), /Profile "default" no longer exists/)
|
|
||||||
assert.equal(fallback.delegate, false)
|
// default is $HERMES_HOME, not profiles/default: a profiles/default probe
|
||||||
|
// reports absent, but This device -> default must still open locally.
|
||||||
|
assert.deepEqual(resolveRegistryLocalRoute('default', { globalRemote: true, localProfileExists: false }), {
|
||||||
|
delegate: false,
|
||||||
|
poolKey: 'conn:local::default'
|
||||||
|
})
|
||||||
|
|
||||||
const present = resolveRegistryLocalRoute('research', { globalRemote: true, localProfileExists: true })
|
const present = resolveRegistryLocalRoute('research', { globalRemote: true, localProfileExists: true })
|
||||||
|
|
||||||
|
|||||||
@@ -220,9 +220,7 @@ export interface RegistryLocalRoute {
|
|||||||
delegate: boolean
|
delegate: boolean
|
||||||
/** Pool key for the forced-local child when not delegating. */
|
/** Pool key for the forced-local child when not delegating. */
|
||||||
poolKey: string
|
poolKey: string
|
||||||
/** Set when a concrete remote-only profile must not spawn a local child.
|
/** Set when a concrete remote-only profile must not spawn a local child. */
|
||||||
* Includes `default`: the ordinary existence guard exempts it, so a
|
|
||||||
* forced-local default spawn would otherwise succeed with no error. */
|
|
||||||
refuse?: string
|
refuse?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -526,8 +524,9 @@ function normalizedSshTarget(route: { host?: unknown; port?: unknown; user?: unk
|
|||||||
* the BARE profile key by design, and that slot may already hold the v1
|
* the BARE profile key by design, and that slot may already hold the v1
|
||||||
* route's REMOTE descriptor — so the forced-local child pools under the
|
* route's REMOTE descriptor — so the forced-local child pools under the
|
||||||
* `conn:local::<profile>` form instead (colons are invalid in profile names,
|
* `conn:local::<profile>` form instead (colons are invalid in profile names,
|
||||||
* so it cannot collide). A concrete profile that does not exist on this
|
* so it cannot collide). A concrete named profile that does not exist on
|
||||||
* machine, including default, is refused instead of spawned. A per-profile
|
* this machine is refused instead of spawned. `default` is `$HERMES_HOME`
|
||||||
|
* itself, so it always exists here and is never refused. A per-profile
|
||||||
* remote override still delegates to the legacy profile route.
|
* remote override still delegates to the legacy profile route.
|
||||||
*/
|
*/
|
||||||
export function resolveRegistryLocalRoute(
|
export function resolveRegistryLocalRoute(
|
||||||
@@ -551,12 +550,13 @@ export function resolveRegistryLocalRoute(
|
|||||||
if (opts.globalRemote) {
|
if (opts.globalRemote) {
|
||||||
const poolKey = `${backendScopePrefix(LOCAL_CONNECTION_ID)}${profileKey}`
|
const poolKey = `${backendScopePrefix(LOCAL_CONNECTION_ID)}${profileKey}`
|
||||||
|
|
||||||
// A concrete profile that does not exist on this machine is remote-only.
|
// A concrete named profile that does not exist on this machine is
|
||||||
// Spawning it locally is the #90477 loop (and, for default, a silent
|
// remote-only. Spawning it locally is the #90477 loop, so refuse. An
|
||||||
// success — the ordinary guard exempts default). Refuse instead. An
|
// unprofiled call is enumeration, not a dial. `default` lives at
|
||||||
// unprofiled call is enumeration, not a dial, and a profile that exists
|
// $HERMES_HOME, not profiles/default, so This device -> default always
|
||||||
// locally still force-locals so "This device" does not dial the remote.
|
// force-locals. A profile that exists locally still force-locals so
|
||||||
if (concrete && opts.localProfileExists === false) {
|
// "This device" does not dial the remote.
|
||||||
|
if (concrete && profileKey !== 'default' && opts.localProfileExists === false) {
|
||||||
return { delegate: false, poolKey, refuse: `Profile "${profileKey}" no longer exists.` }
|
return { delegate: false, poolKey, refuse: `Profile "${profileKey}" no longer exists.` }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11676,11 +11676,8 @@ async function spawnPoolBackend(
|
|||||||
// here, and logging "Starting" first left an orphaned line with no READY
|
// here, and logging "Starting" first left an orphaned line with no READY
|
||||||
// and no exit — the exact undiagnosable burst signature in remote-gateway
|
// and no exit — the exact undiagnosable burst signature in remote-gateway
|
||||||
// user bundles (Aug 2026, Dash's report).
|
// user bundles (Aug 2026, Dash's report).
|
||||||
assertLocalProfileCanStart(
|
assertLocalProfileCanStart(profile, profileDeletionGate, key =>
|
||||||
profile,
|
directoryExists(path.join(HERMES_HOME, 'profiles', key))
|
||||||
profileDeletionGate,
|
|
||||||
key => directoryExists(path.join(HERMES_HOME, 'profiles', key)),
|
|
||||||
{ allowImplicitDefault: !opts.forceLocal }
|
|
||||||
)
|
)
|
||||||
rememberLog(`Starting Hermes backend for profile "${profile}" via ${backend.label}`)
|
rememberLog(`Starting Hermes backend for profile "${profile}" via ${backend.label}`)
|
||||||
|
|
||||||
|
|||||||
@@ -140,16 +140,6 @@ test('assertLocalProfileCanStart rejects a delayed retry after the profile direc
|
|||||||
assert.doesNotThrow(() => assertLocalProfileCanStart('selena', gate, profile => profile === 'selena'))
|
assert.doesNotThrow(() => assertLocalProfileCanStart('selena', gate, profile => profile === 'selena'))
|
||||||
})
|
})
|
||||||
|
|
||||||
test('assertLocalProfileCanStart does not exempt default when a forced-local spawn asks', () => {
|
|
||||||
const gate = new ProfileDeletionGate()
|
|
||||||
|
|
||||||
assert.throws(
|
|
||||||
() => assertLocalProfileCanStart('default', gate, () => false, { allowImplicitDefault: false }),
|
|
||||||
/Profile "default" no longer exists/
|
|
||||||
)
|
|
||||||
assert.doesNotThrow(() => assertLocalProfileCanStart('default', gate, () => true, { allowImplicitDefault: false }))
|
|
||||||
})
|
|
||||||
|
|
||||||
test('localProfilePoolKeys returns every local process scope for one profile', () => {
|
test('localProfilePoolKeys returns every local process scope for one profile', () => {
|
||||||
assert.deepEqual(localProfilePoolKeys('Selena'), ['selena', 'conn:local::selena'])
|
assert.deepEqual(localProfilePoolKeys('Selena'), ['selena', 'conn:local::selena'])
|
||||||
assert.deepEqual(localProfilePoolKeys(''), [])
|
assert.deepEqual(localProfilePoolKeys(''), [])
|
||||||
|
|||||||
@@ -184,8 +184,7 @@ export class ProfileDeletionGate {
|
|||||||
export function assertLocalProfileCanStart(
|
export function assertLocalProfileCanStart(
|
||||||
profile: unknown,
|
profile: unknown,
|
||||||
gate: ProfileDeletionGate,
|
gate: ProfileDeletionGate,
|
||||||
profileDirectoryExists: (profile: string) => boolean,
|
profileDirectoryExists: (profile: string) => boolean
|
||||||
opts: { allowImplicitDefault?: boolean } = {}
|
|
||||||
): void {
|
): void {
|
||||||
const key = String(profile ?? '')
|
const key = String(profile ?? '')
|
||||||
.trim()
|
.trim()
|
||||||
@@ -193,12 +192,7 @@ export function assertLocalProfileCanStart(
|
|||||||
|
|
||||||
gate.assertCanStart(key)
|
gate.assertCanStart(key)
|
||||||
|
|
||||||
// `default` is `$HERMES_HOME`, not `profiles/default`, so the ordinary local
|
if (key && key !== 'default' && !profileDirectoryExists(key)) {
|
||||||
// start exempts it. A forced-local spawn must not: that exemption is how a
|
|
||||||
// remote-only default starts a local agent with no error (#90477).
|
|
||||||
const implicitDefault = opts.allowImplicitDefault !== false && key === 'default'
|
|
||||||
|
|
||||||
if (key && !implicitDefault && !profileDirectoryExists(key)) {
|
|
||||||
throw new Error(`Profile "${key}" no longer exists.`)
|
throw new Error(`Profile "${key}" no longer exists.`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user