fix(gateway): harden multiplex primary token gate — canonical platform map + unserved-platform warning

Follow-ups to @SAMBAS123's #64986 salvage:

- Replace the hardcoded token-platform set in _platform_has_bot_credential
  with PLATFORM_TOKEN_ENV_NAMES, a shared canonical map in gateway/config.py
  also used by the empty-token validation warning — one source of truth, so
  future token platforms can't silently bypass the gate or drift between
  the two sites.
- After secondary-profile startup, warn loudly for any platform skipped on
  the primary that no secondary profile ended up serving: an enabled
  platform with no credential anywhere is a config error, not a silent
  no-op.
- AUTHOR_MAP entry for the salvaged commit's author email.
This commit is contained in:
Teknium 2026-07-16 01:55:02 -07:00
parent 86e7917ba7
commit b4e4b5a43e
3 changed files with 42 additions and 18 deletions

View file

@ -440,6 +440,22 @@ class ChannelOverride:
)
# Canonical map of platforms whose primary credential is ``PlatformConfig.token``
# and the env var it loads from. Used for empty-token warnings at config
# validation and by the multiplex primary-startup credential gate in
# ``gateway.run`` (#64674). Platforms absent from this map authenticate some
# other way (session files, port-bound webhooks, api_key-only) and must never
# be skipped for a missing token.
PLATFORM_TOKEN_ENV_NAMES: dict["Platform", str] = {
Platform.TELEGRAM: "TELEGRAM_BOT_TOKEN",
Platform.DISCORD: "DISCORD_BOT_TOKEN",
Platform.SLACK: "SLACK_BOT_TOKEN",
Platform.MATTERMOST: "MATTERMOST_TOKEN",
Platform.MATRIX: "MATRIX_ACCESS_TOKEN",
Platform.WEIXIN: "WEIXIN_TOKEN",
}
@dataclass
class PlatformConfig:
"""Configuration for a single messaging platform."""
@ -1424,14 +1440,7 @@ def _validate_gateway_config(config: "GatewayConfig") -> None:
# Warn about empty bot tokens — platforms that loaded an empty string
# won't connect and the cause can be confusing without a log line.
_token_env_names = {
Platform.TELEGRAM: "TELEGRAM_BOT_TOKEN",
Platform.DISCORD: "DISCORD_BOT_TOKEN",
Platform.SLACK: "SLACK_BOT_TOKEN",
Platform.MATTERMOST: "MATTERMOST_TOKEN",
Platform.MATRIX: "MATRIX_ACCESS_TOKEN",
Platform.WEIXIN: "WEIXIN_TOKEN",
}
_token_env_names = PLATFORM_TOKEN_ENV_NAMES
for platform, pconfig in config.platforms.items():
if not pconfig.enabled:
continue

View file

@ -1513,16 +1513,9 @@ def _platform_has_bot_credential(platform: "Platform", platform_config: "Platfor
Platforms that do not use ``PlatformConfig.token`` always return True so we
never skip them here (Signal session paths, port-binding HTTP adapters, etc.).
"""
# Keep in sync with gateway.config token env map used for empty-token warnings.
token_platforms = {
Platform.TELEGRAM,
Platform.DISCORD,
Platform.SLACK,
Platform.MATTERMOST,
Platform.MATRIX,
Platform.WEIXIN,
}
if platform not in token_platforms:
from gateway.config import PLATFORM_TOKEN_ENV_NAMES
if platform not in PLATFORM_TOKEN_ENV_NAMES:
return True
token = getattr(platform_config, "token", None) or ""
if isinstance(token, str) and token.strip():
@ -7232,6 +7225,7 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew
# Initialize and connect each configured platform
_multiplex_on = bool(getattr(self.config, "multiplex_profiles", False))
_multiplex_skipped_platforms: list[Platform] = []
for platform, platform_config in self.config.platforms.items():
if await self._abort_startup_if_shutdown_requested():
return True
@ -7251,6 +7245,7 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew
"provide the token will still connect.",
platform.value,
)
_multiplex_skipped_platforms.append(platform)
continue
enabled_platform_count += 1
@ -7396,6 +7391,25 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew
except Exception as e:
logger.error("Secondary-profile adapter startup failed: %s", e, exc_info=True)
# A platform we skipped on the primary for a missing credential was
# supposed to be picked up by a secondary profile that owns the token.
# If none did, the platform is enabled in config.yaml yet silently
# unserved — surface it loudly so the operator sees a config problem
# instead of a quiet dead channel (#64674 follow-up).
for _skipped in _multiplex_skipped_platforms:
_served_by_secondary = any(
_skipped in _profile_map
for _profile_map in self._profile_adapters.values()
)
if not _served_by_secondary:
logger.warning(
"%s is enabled but no profile (default or secondary) "
"provided a bot credential for it — the platform is not "
"being served. Add its token to the profile that should "
"own it, or disable the platform.",
_skipped.value,
)
if connected_count == 0:
if startup_nonretryable_errors:
reason = "; ".join(startup_nonretryable_errors)

View file

@ -45,6 +45,7 @@ ACP_REGISTRY_MANIFEST = REPO_ROOT / "acp_registry" / "agent.json"
# Auto-extracted from noreply emails + manual overrides
AUTHOR_MAP = {
"doogie@spark.local": "SAMBAS123", # PR #64986 salvage (gateway: multiplex primary bot token scope)
"41409874+2751738943@users.noreply.github.com": "2751738943", # PR #54785 salvage (tui: post-turn completion ownership routing)
"Burgunthy@users.noreply.github.com": "Burgunthy", # PR #20096 salvage (gateway: profile-based routing for inbound messages)
"75556242+webtecnica@users.noreply.github.com": "webtecnica", # PR #63360 salvage (nous: restore inference-api base_url)