From c93ed074597c475266d2fb2f7b4f6aa4f5c30156 Mon Sep 17 00:00:00 2001 From: Salim <1870760+salimhamed@users.noreply.github.com> Date: Sun, 26 Jul 2026 13:38:41 -0700 Subject: [PATCH] fix(kanban): route active named profile through the active adapter map MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A gateway running under a named active profile (e.g. `hermes -p main gateway`) stamps kanban auto-subscriptions with notifier_profile=main, but _authorization_adapter() treated any name other than the literal "default" as a multiplex secondary and consulted only _profile_adapters — empty on standalone gateway-per-profile deployments. The helper failed closed, the notifier rewound the claim, and the notification was silently retried forever (#71340). Recognize the gateway's own active profile name as primary so its stamped subscriptions resolve via self.adapters; genuinely secondary profiles keep the fail-closed lookup. Salvaged from PR #62380 (the unrelated blocked-reason truncation change is intentionally not taken). --- tests/gateway/test_kanban_notifier.py | 38 +++++++++++++++++++ tests/gateway/test_multiplex_profile_authz.py | 9 +++++ 2 files changed, 47 insertions(+) diff --git a/tests/gateway/test_kanban_notifier.py b/tests/gateway/test_kanban_notifier.py index 4657bc3c176..014e276ac9c 100644 --- a/tests/gateway/test_kanban_notifier.py +++ b/tests/gateway/test_kanban_notifier.py @@ -174,6 +174,44 @@ def test_kanban_notifier_rewinds_claim_if_adapter_disconnects(tmp_path, monkeypa assert [ev.kind for ev in _unseen_terminal_events(tid)] == ["completed"] +def test_active_named_profile_subscription_is_delivered(tmp_path, monkeypatch): + """A sub stamped with the gateway's own named profile uses self.adapters. + + Regression for #71340: on a standalone (non-multiplex) gateway running a + named profile, _authorization_adapter() used to treat the active name as a + multiplex secondary, find no _profile_adapters entry, fail closed, and + rewind the claim forever — silent zero-delivery. + """ + db_path = tmp_path / "actionable-block.db" + monkeypatch.setenv("HERMES_KANBAN_DB", str(db_path)) + kb.init_db() + reason = "AGE-39 — https://linear.example/AGE-39 — publishing verified." + conn = kb.connect() + try: + tid = kb.create_task(conn, title="approval", assignee="publisher") + kb.add_notify_sub( + conn, + task_id=tid, + platform="telegram", + chat_id="chat-1", + notifier_profile="main", + ) + kb.block_task(conn, tid, reason=reason, kind="needs_input") + finally: + conn.close() + + adapter = RecordingAdapter() + runner = _make_runner(adapter) + runner._active_profile_name = lambda: "main" + + asyncio.run(_run_one_notifier_tick(monkeypatch, runner)) + + assert len(adapter.sent) == 1 + message = adapter.sent[0]["text"] + assert tid in message + assert "blocked" in message + + def test_kanban_db_path_is_test_isolated_from_real_home(): hermes_home = Path(kb.kanban_home()) production_db = Path.home() / ".hermes" / "kanban.db" diff --git a/tests/gateway/test_multiplex_profile_authz.py b/tests/gateway/test_multiplex_profile_authz.py index 2a43492e645..055e6993e67 100644 --- a/tests/gateway/test_multiplex_profile_authz.py +++ b/tests/gateway/test_multiplex_profile_authz.py @@ -209,6 +209,15 @@ def test_adapter_for_direct_source_keeps_native_platform_adapter(monkeypatch): assert runner._adapter_for_source(source) is slack_adapter +def test_explicit_active_profile_stamp_uses_default_adapter_map(monkeypatch): + """A named active profile is not misclassified as multiplex secondary.""" + runner, default_adapter, _secondary_adapter = _make_multiplex_runner(monkeypatch) + runner._active_profile_name = lambda: "main" + + assert runner._authorization_adapter(Platform.WECOM, profile="main") is default_adapter + + + def test_secondary_allowlist_dm_behavior_ignores_unauthorized(monkeypatch): """Unauthorized-DM behavior must read the secondary adapter's dm_policy.""" runner, _default_adapter, secondary_adapter = _make_multiplex_runner(monkeypatch)