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.fix/verification-admin-route-recovery
parent
86e7917ba7
commit
b4e4b5a43e
|
|
@ -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
|
@dataclass
|
||||||
class PlatformConfig:
|
class PlatformConfig:
|
||||||
"""Configuration for a single messaging platform."""
|
"""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
|
# Warn about empty bot tokens — platforms that loaded an empty string
|
||||||
# won't connect and the cause can be confusing without a log line.
|
# won't connect and the cause can be confusing without a log line.
|
||||||
_token_env_names = {
|
_token_env_names = PLATFORM_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",
|
|
||||||
}
|
|
||||||
for platform, pconfig in config.platforms.items():
|
for platform, pconfig in config.platforms.items():
|
||||||
if not pconfig.enabled:
|
if not pconfig.enabled:
|
||||||
continue
|
continue
|
||||||
|
|
|
||||||
|
|
@ -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
|
Platforms that do not use ``PlatformConfig.token`` always return True so we
|
||||||
never skip them here (Signal session paths, port-binding HTTP adapters, etc.).
|
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.
|
from gateway.config import PLATFORM_TOKEN_ENV_NAMES
|
||||||
token_platforms = {
|
|
||||||
Platform.TELEGRAM,
|
if platform not in PLATFORM_TOKEN_ENV_NAMES:
|
||||||
Platform.DISCORD,
|
|
||||||
Platform.SLACK,
|
|
||||||
Platform.MATTERMOST,
|
|
||||||
Platform.MATRIX,
|
|
||||||
Platform.WEIXIN,
|
|
||||||
}
|
|
||||||
if platform not in token_platforms:
|
|
||||||
return True
|
return True
|
||||||
token = getattr(platform_config, "token", None) or ""
|
token = getattr(platform_config, "token", None) or ""
|
||||||
if isinstance(token, str) and token.strip():
|
if isinstance(token, str) and token.strip():
|
||||||
|
|
@ -7232,6 +7225,7 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew
|
||||||
|
|
||||||
# Initialize and connect each configured platform
|
# Initialize and connect each configured platform
|
||||||
_multiplex_on = bool(getattr(self.config, "multiplex_profiles", False))
|
_multiplex_on = bool(getattr(self.config, "multiplex_profiles", False))
|
||||||
|
_multiplex_skipped_platforms: list[Platform] = []
|
||||||
for platform, platform_config in self.config.platforms.items():
|
for platform, platform_config in self.config.platforms.items():
|
||||||
if await self._abort_startup_if_shutdown_requested():
|
if await self._abort_startup_if_shutdown_requested():
|
||||||
return True
|
return True
|
||||||
|
|
@ -7251,6 +7245,7 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew
|
||||||
"provide the token will still connect.",
|
"provide the token will still connect.",
|
||||||
platform.value,
|
platform.value,
|
||||||
)
|
)
|
||||||
|
_multiplex_skipped_platforms.append(platform)
|
||||||
continue
|
continue
|
||||||
enabled_platform_count += 1
|
enabled_platform_count += 1
|
||||||
|
|
||||||
|
|
@ -7396,6 +7391,25 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("Secondary-profile adapter startup failed: %s", e, exc_info=True)
|
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 connected_count == 0:
|
||||||
if startup_nonretryable_errors:
|
if startup_nonretryable_errors:
|
||||||
reason = "; ".join(startup_nonretryable_errors)
|
reason = "; ".join(startup_nonretryable_errors)
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,7 @@ ACP_REGISTRY_MANIFEST = REPO_ROOT / "acp_registry" / "agent.json"
|
||||||
|
|
||||||
# Auto-extracted from noreply emails + manual overrides
|
# Auto-extracted from noreply emails + manual overrides
|
||||||
AUTHOR_MAP = {
|
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)
|
"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)
|
"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)
|
"75556242+webtecnica@users.noreply.github.com": "webtecnica", # PR #63360 salvage (nous: restore inference-api base_url)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue