hermes-agent/tests/gateway/relay
Ben Barclay 3f497e2b4f
fix(gateway): relay thread-rename must carry the parent-channel discriminator (#76465)
Live staging (2026-08-01, on a fresh instance where title generation
finally succeeded): the rename lane fired end to end, but the connector
declined the op with "discord egress declined: target not routed to an
onboarded tenant". The trace logs added earlier pinpointed it:

  discord auto-thread rename: thread=... lane=relay new_title='...'
  relay thread_rename declined ...: target not routed to an onboarded tenant
  discord auto-thread rename result: thread=... applied=False

Root cause: the connector's routedEgressGuard resolves the owning tenant
from the outbound metadata's scope_id (guild) or user_id (author). The
adapter builds those via _with_scope(chat_id), reading per-chat caches
keyed by the PARENT channel chat_id learned at inbound. The relay rename
lane called rename_thread WITHOUT parent_chat_id, so chat_id defaulted to
the THREAD id — a key the caches never held — and the op shipped with no
discriminator. resolveTenant returned undefined and egress was declined
before the op ever reached the (now-durable) no-clobber guard.

This was the true terminal blocker: every earlier fix (send-result
feedback, registration/poll ordering, connector-owned guard, durable
Redis store) was correct but sat DOWNSTREAM of this egress-routing
decline, so none of them could take effect.

Fix: the relay lane passes parent_chat_id=source.chat_id (the relay
source's chat_id IS the parent channel; the thread came from send-result
feedback). _with_scope then resolves scope_id/user_id from the
parent-channel caches and the connector routes the op to the tenant.
Scoped to the relay lane only (use_connector_guard); the native lane
renames via the direct Discord API and needs no discriminator.

Tests: adapter-level — a rename passing parent_chat_id carries the cached
scope_id, one keyed on the thread id alone does not (the regression
shape); lane-level — the late-feedback test now asserts parent_chat_id
flows through as the parent channel. Relay suite 150 passed; ruff +
footguns clean.

Connector-compatible with the deployed egress guard; no gateway-gateway
change needed.
2026-08-01 17:08:29 -07:00
..
__init__.py
…
stub_connector.py
…
test_auth.py
…
test_channel_context_consume.py
…
test_contract_doc_conformance.py
…
test_descriptor.py
…
test_descriptor_from_entry.py
…
test_handoff_relay_aliasing.py
…
test_identity_token_resolver.py
…
test_no_stub_leak.py
…
test_relay_adapter.py
…
test_relay_follow_up.py
…
test_relay_going_idle.py
…
test_relay_interactive.py
…
test_relay_interrupt.py
…
test_relay_media.py
…
test_relay_multiplatform.py
…
test_relay_passthrough.py
…
test_relay_per_platform_caps.py
…
test_relay_policy_send.py
…
test_relay_registration.py
…
test_relay_roundtrip.py
…
test_relay_roundtrip_telegram.py
…
test_relay_sheds_crypto.py
…
test_relay_slack_dm_streaming.py
…
test_relay_slack_prompt_dm_root.py
…
test_relay_threads.py
…
test_self_provision.py
…
test_wire_user_identity.py
…
test_ws_transport.py
…