fix(relay): stamp logical platform + relay trust on Discord interaction events (#84318)

The relay interactions passthrough lane (_discord_interaction_to_event)
built its SessionSource with platform=Platform.RELAY and no
delivered_via_upstream_relay marker — unlike the relay text lane
(ws_transport._event_from_wire), which maps the connector's platform to
the logical enum and stamps the authenticated-upstream flag.

Consequences of the mismatch:

- /sethome sent as a Discord slash command persisted the home channel
  under platforms.relay.home_channel (invisible to cron delivery, which
  looks up the logical platform) and mirrored it into the dead
  RELAY_HOME_CHANNEL env var — so cron jobs with deliver='discord' kept
  falling back to local-only even after the resolution/delivery fixes.
  The absent trust marker also meant via_relay=False, so the handler's
  'Relay does not authenticate this logical home target' guard —
  designed to reject exactly this misfiled shape — never engaged.
- Session keys forked: the connector binds the interaction's follow-up
  capability under buildSessionKey with platform 'discord' and
  chat_type 'group' (interactionSessionSource), while the gateway keyed
  the same interaction as relay/channel.
- _capture_scope skipped recording _platform_by_chat (it ignores the
  generic 'relay'), losing the egress sender hint for the chat.

Stamp Platform.DISCORD (the lane statically parses Discord interaction
wire payloads), chat_type 'group' for guild channels (native-adapter and
connector parity), and delivered_via_upstream_relay=True (parity with
the text lane; set locally, never read off the wire).

With this, slash-command /sethome files under platforms.discord and
passes the via_relay guard legitimately, and cron delivery over relay
works end to end with the #84300 resolution fixes.
This commit is contained in:
Ben Barclay
2026-08-12 17:04:02 +10:00
committed by GitHub
parent 5fffe56066
commit 333536e7c9
2 changed files with 79 additions and 4 deletions
+33 -2
View File
@@ -780,9 +780,31 @@ class RelayAdapter(BasePlatformAdapter):
channel_id = str(payload.get("channel_id") or "")
guild_id = payload.get("guild_id") # real Discord interaction wire field
source = SessionSource(
platform=Platform.RELAY,
# The LOGICAL platform, not Platform.RELAY. This lane parses a
# Discord interaction wire payload, so the underlying platform is
# known statically — and it must be stamped for three consumers:
# 1. Session keys: the connector binds the interaction's
# follow-up capability under buildSessionKey with
# platform="discord" (interactionSessionSource); the relay
# TEXT lane (ws_transport._event_from_wire) also maps to the
# logical platform. RELAY here forked interaction sessions
# away from both.
# 2. /sethome: with platform=RELAY the handler filed the home
# channel under platforms.relay.home_channel (invisible to
# cron delivery, which looks up the logical platform) and
# mirrored it into the dead RELAY_HOME_CHANNEL env var.
# 3. Egress: _capture_scope records _platform_by_chat from this
# value and deliberately skips the generic "relay".
platform=Platform.DISCORD,
chat_id=channel_id,
chat_type="channel" if guild_id else "dm",
# "group", not "channel": the session key embeds chat_type, and
# BOTH the connector's capability binding (interactionSessionSource
# → buildSessionKey, chat_type "group") and the native Discord
# adapter's channel events key guild channels as "group". A
# "channel" slot here forked the interaction session from the
# chat's message session AND from the vault key the connector
# bound the follow-up capability under.
chat_type="group" if guild_id else "dm",
user_id=str(user.get("id"))
if isinstance(user, dict) and user.get("id")
else None,
@@ -793,6 +815,15 @@ class RelayAdapter(BasePlatformAdapter):
if guild_id
else None, # Discord guild → generic scope slot
message_id=str(payload.get("id")) if payload.get("id") else None,
# Same upstream-trust marker the relay text lane stamps
# (ws_transport._event_from_wire): this interaction arrived over
# the per-instance-authenticated relay WS after the connector
# verified Discord's edge signature and resolved the tenant.
# Without it, authz treated the event as unauthenticated relay
# traffic — and /sethome's via_relay guard never engaged, which is
# how platform=RELAY home channels slipped through in the first
# place. Set locally, never read off the wire.
delivered_via_upstream_relay=True,
)
event = MessageEvent(text=text, message_type=message_type, source=source)
if itype == 3: