refactor(telegram): extract authorization cluster into TelegramAuthorizationMixin - #75742
Conversation
|
Thanks for the focused mechanical extraction. Current No concrete correctness, completeness, or design-fit problem was found in the reviewed diff. Automated hermes-sweeper review. |
SummaryTwo PRs address the same god-file decomposition pattern on separate platform adapters: #75735 extracts Discord authorization from four dispersed regions, while #75742 extracts Telegram authorization and scope readers from two dispersed regions. Both diffs target the reported auditability problem through platform-specific mixins while preserving mixin-first method resolution order. Related pull requests
Suggested consolidationKeep both #75735 and #75742 open with platform-specific salvage paths; they implement parallel Discord and Telegram cuts rather than duplicate changes. #75735 requires the contributor-identified relative-import, active-package logger, and namespaced-loader test corrections, while #75742 should remain the separate Telegram extraction in line with its maintainer-bot keep_open verdict; neither PR can be closed as a duplicate of the other. Complex graphflowchart LR
classDef open fill:#dbeafe,stroke:#1d4ed8,color:#1e3a8a
classDef merged fill:#dcfce7,stroke:#15803d,color:#14532d
classDef closed fill:#e5e7eb,stroke:#6b7280,color:#1f2937
classDef unverified fill:#f3f4f6,stroke:#9ca3af,color:#374151
classDef best stroke-width:3px,stroke:#b45309
classDef target stroke-width:3px,stroke:#4338ca
I75741(["issue #75741 (open)"])
P75742["PR #75742 (open)"]
P75742 -->|best fix| I75741
class I75741 open
class P75742 open
class P75742 best
class P75742 target
click I75741 "https://github.com/NousResearch/hermes-agent/issues/75741"
click P75742 "https://github.com/NousResearch/hermes-agent/pull/75742"
Graph: solid arrow = fixes / best fix, dashed arrow = partial or unverified (see edge label); boxed group = PRs duplicating each other; amber border = best fix; indigo border = target; gray node = closed (state tag in the node label). Cross-PR triage: Reviewed 2 pull requests and 2 issues in this complex. Each diff was read against this issue; Assessment working set: 81 kB of PR diffs, 13 kB of issue/PR text, 2 kB of discussion (3 comments), 6 verify verdicts. verdicts reflect diff content, not PR titles. Part of an automated triage batch. |
|
Coordination on the allowlist-getter overlap (cross-PR composition, from the adapter sharding wave, epic #78791): Your authz_mixin.py defines 5 allowlist getters (, , , , ) that also exist in the config/mention shard (TelegramConfigMentionMixin, wave-2 shard A5 — mixin is FIRST base of TelegramAdapter). Verified 2026-08-04:
Recommendation: when rebasing this PR, DROP the 5 duplicate getters from authz_mixin.py (keep the genuine authz methods — , , , etc.) and let them resolve via A5's mixin. Credit: the shard wave preserved the current semantics deliberately — @MaxFreedomPollard's authz slice is otherwise welcome, the duplication is the only overlap. |
|
Coordination on the allowlist-getter overlap (cross-PR composition, from the adapter sharding wave, epic #78791): Your authz_mixin.py defines 5 allowlist getters (_telegram_allowed_chats, _telegram_group_allowed_chats, _telegram_observe_allowed_chats, _telegram_allowed_topics, _telegram_ignored_threads) that also exist in the config/mention shard (TelegramConfigMentionMixin, wave-2 shard A5 — mixin is FIRST base of TelegramAdapter). Verified 2026-08-04:
Recommendation: when rebasing this PR, DROP the 5 duplicate getters from authz_mixin.py (keep the genuine authz methods — _is_callback_user_authorized, _telegram_auth_env_configured, _should_pass_unauthorized_dm_for_pairing, _is_user_authorized_from_message etc.) and let them resolve via A5's mixin. Credit: the shard wave preserved the current _scoped_gate_env semantics deliberately — @MaxFreedomPollard's authz slice is otherwise welcome; the duplication is the only overlap. |
|
Re: the auth-cluster overlap with the adapter sharding wave (epic #78791) — verified against pr-75742-head @ f05870c1 on 2026-08-04:
Once those three land, the shard wave will delete the adapter's residual 5-method auth cluster and inherit through |
…Research#78791) Verbatim extraction of 53 methods across rich_mixin, topics_mixin, init_guards, network_mixin, text_format, config_mixin, authz_mixin (4 authz methods skipped — covered by NousResearch#75742). Wave-2 VERIFIED by w2a/w2b, wave-3 VERIFIED by w3a/w3b. 101 regression tests, 509/509 baseline-identical on the full telegram suite.
…izationMixin (god-file Phase 3)
TelegramAdapter is 9,184 lines and 193 methods, the largest platform adapter
in the tree. This lifts its authorization cluster to
plugins/platforms/telegram/authz_mixin.py, the same mechanical mixin
extraction that produced gateway/authz_mixin.py.
Moved verbatim (263 lines):
the authorization decision (908-1103)
_is_callback_user_authorized _telegram_auth_env_configured
_source_from_message_for_auth _is_user_authorized_from_message
the chat / topic / thread scope readers (7757-7830)
_telegram_allowed_chats _telegram_allowed_topics
_telegram_group_allowed_chats _telegram_ignored_threads
_telegram_observe_allowed_chats
The boundary is deliberate: what moves answers 'is this permitted'. The
mention, guest-mode and free-response helpers answer 'should the bot reply'
and stay on the adapter, because they are routing policy rather than a trust
decision.
Behavior-neutral. Every body is byte-identical to what it replaced; the only
edits are the class declaration, one swapped import, and the new module
header. The mixin precedes BasePlatformAdapter in the bases so resolution
order is unchanged. logger is bound by explicit name, so records keep the name
plugins.platforms.telegram.adapter and getLogger returns the adapter's own
object. Message is imported under the adapter's own ImportError guard, and
this module deliberately does not enable postponed annotation evaluation,
matching the adapter, so the lifted signatures evaluate exactly as before.
_coerce_allow_set was imported for the sole use of
_is_user_authorized_from_message and moves with it, so the adapter's import of
it is replaced by the mixin import.
adapter.py 10,019 -> 9,747 lines; the class 9,184 -> 8,912 lines and 193 -> 184 methods.
f05870c to
b5b8154
Compare
|
@andrexibiza thank you for digging into this properly, both findings were correct and the first one was worse than it looked. Rebased onto current main at b5b8154. The pairing fallthrough. You were right that The allowlist getters. Dropped from the mixin, as you suggested, with one adjustment. Rather than deleting them, I left all five on the adapter. The extraction is now four methods, not nine: Two smaller things. The adapter's I also changed the mixin's logger from a hard-coded 543 tests pass across the telegram gateway files plus test_discord_gate_isolation. Before the fix that same set was 529 passing and 14 failing, including the three pairing cases you named. |
Closes #75741.
TelegramAdapteris 9,184 lines and 193 methods, the largest platform adapter in the tree. This lifts its authorization cluster intoplugins/platforms/telegram/authz_mixin.py, the same mechanical extraction that producedgateway/authz_mixin.pyforGatewayRunner, and the Telegram counterpart to #75735.adapter.pygoes 10,019 -> 9,747 lines. The class goes 9,184 -> 8,912 lines and 193 -> 184 methods.What moved
263 lines, verbatim, from two regions about 6,800 lines apart:
_is_callback_user_authorized_source_from_message_for_auth_telegram_auth_env_configured_is_user_authorized_from_message_telegram_allowed_chats_telegram_group_allowed_chats_telegram_observe_allowed_chats_telegram_allowed_topics_telegram_ignored_threadsWhere the boundary is, and why
The five scope readers sit in a run of ten similar-looking config readers. The other five did not move, deliberately.
_telegram_guest_mode,_telegram_exclusive_bot_mentions,_telegram_free_response_chats,_telegram_free_response_topicsand_telegram_is_free_response_topicanswer "should the bot reply to this", which is routing policy. The five that moved answer "is this permitted", which is a trust decision. They read alike and live side by side, but only one group belongs behind a name ending inAuthorizationMixin, and mixing them would make the module's name stop meaning anything precise._coerce_allow_setwas imported into the adapter for the sole use of_is_user_authorized_from_message, so it travels with it; the adapter's import of it is replaced by the mixin import rather than left dangling.Why it is safe
1. The bodies are byte-identical. A script parses the pre-refactor
adapter.pyout of git and the two post-refactor files, extracts each callable's exact source span, and compares strings. All 9 match. The same script confirms exactly those 9 methods leftTelegramAdapter, that nothing was added, and that no other method in the class changed by a single character.2. Method resolution is unchanged.
TelegramAuthorizationMixinprecedesBasePlatformAdapter, givingTelegramAdapter -> TelegramAuthorizationMixin -> BasePlatformAdapter -> ABC. Every method name defined on the old class still resolves, and all 9 resolve to the mixin.3. Module-level names still resolve to the same objects.
loggeris bound aslogging.getLogger("plugins.platforms.telegram.adapter")rather than__name__, so records emitted from the moved methods keep the name they had.getLoggeris a registry lookup, so this is the adapter's own logger object. Verified at runtime:authz_mixin.logger is adapter.logger.Messageis imported under the adapter's ownImportErrorguard with the sameAnyfallback. Verified at runtime:authz_mixin.Message is adapter.Message.Unlike
plugins/platforms/discord/adapter.py, this module does not enable postponed annotation evaluation, soMessagein a lifted signature is evaluated when the function is defined rather than left as a string. The new module deliberately omitsfrom __future__ import annotationsfor that reason: adding it would have silently turned the lifted annotations into strings. Verified by reading__annotations__off the composed class after the move.No import cycle:
authz_mixinimports nothing fromadapter.Verification
Differential run against clean
upstream/mainat the same base commit, in two isolated worktrees, overtests/gateway(53 Telegram test files),tests/pluginsandtests/tools:The failures are identical in both runs and pre-existing on
main. Set difference in both directions is empty: nothing new fails, and nothing that failed before now passes.No test file is modified by this PR.