fix(gateway): the free-tier bootstrap runs inside the launch profile's scope under multiplex
Same bug class as the warm-up: `_start_free_tier_bootstrap` ran on a bare executor thread, and `anon_auth.ensure_portal_identity` resolves the Portal through `_nous_portal_env_override`, so under multiplex a non-production HERMES_PORTAL_BASE_URL read as absent and the identity would be minted against the production Portal. Route the hop through `_run_boot_probe_in_launch_scope`. Gated behind guest onboarding, so latent on today's fleet; one test pins the binding.
This commit is contained in:
@@ -110,7 +110,8 @@ class GatewayStartupMixin:
|
||||
|
||||
async def _run_boot_probe_in_launch_scope(self, fn):
|
||||
"""Run a boot-time probe on the default executor under the LAUNCH profile's scope when
|
||||
multiplexing (the ``_discover_gateway_mcp_tools`` shape). Boot probes (``check_fn``s) read profile-scoped secrets; an unscoped read under multiplex fails
|
||||
multiplexing (the ``_discover_gateway_mcp_tools`` shape). Boot probes (``check_fn``s, the
|
||||
free-tier bootstrap) read profile-scoped secrets; an unscoped read under multiplex fails
|
||||
closed, so routing overrides resolve as absent and default routing applies. ``copy_context``
|
||||
carries the contextvars across the executor hop; single-profile keeps environ semantics."""
|
||||
loop = asyncio.get_running_loop()
|
||||
@@ -127,6 +128,11 @@ class GatewayStartupMixin:
|
||||
getattr(fn, "__name__", fn), exc_info=True)
|
||||
return await loop.run_in_executor(None, copy_context().run, fn)
|
||||
|
||||
async def _run_free_tier_bootstrap(self) -> None:
|
||||
"""The free-tier bootstrap mints the Portal identity through the same profile-scoped routing
|
||||
overrides the warm-up resolves, so it takes the same launch-profile binding."""
|
||||
await self._run_boot_probe_in_launch_scope(self._start_free_tier_bootstrap)
|
||||
|
||||
async def _warm_turn_prerequisites(self) -> None:
|
||||
"""Initialize turn machinery on an executor thread before the gate opens. Never raises: a
|
||||
failed warm-up degrades to lazy init and must not block startup."""
|
||||
@@ -1394,7 +1400,7 @@ class GatewayStartupMixin:
|
||||
# identity to already exist. Blocking here, before any adapter connects, is what keeps a fast
|
||||
# first DM from arriving with nothing to resolve. With the launch gate unset this is a local
|
||||
# inventory and no network.
|
||||
await asyncio.get_running_loop().run_in_executor(None, self._start_free_tier_bootstrap)
|
||||
await self._run_free_tier_bootstrap()
|
||||
# Serialize startup restore against inbound: adapters receive as soon as they connect, so inbound
|
||||
# queues until every synthetic resume turn has finished.
|
||||
self._startup_restore_in_progress = True
|
||||
|
||||
@@ -88,3 +88,13 @@ def test_single_profile_warmup_keeps_environ_semantics(tmp_path, monkeypatch):
|
||||
assert seen["scope_installed"] is False
|
||||
assert seen["portal_override"] == PORTAL
|
||||
|
||||
|
||||
def test_multiplex_free_tier_bootstrap_runs_inside_the_launch_profile_scope(multiplex_home, monkeypatch):
|
||||
"""The free-tier bootstrap mints the Portal identity at boot through the same override; it is the
|
||||
sibling unscoped executor hop and gets the same binding."""
|
||||
seen: dict = {}
|
||||
runner = _Runner(multiplex=True)
|
||||
runner._start_free_tier_bootstrap = _probe(seen)
|
||||
asyncio.run(runner._run_free_tier_bootstrap())
|
||||
assert seen["scope_installed"] is True
|
||||
assert seen["portal_override"] == PORTAL
|
||||
|
||||
Reference in New Issue
Block a user