fix(gateway): resolve silence narration filter from config only (#116886)

drops process env override in delivery router so profile config is authoritative under multiplexing
closes #116886
This commit is contained in:
Mohamad Kanso
2026-09-20 12:14:15 +01:00
committed by Teknium
parent 2ddfec3203
commit 166ec17747
2 changed files with 47 additions and 12 deletions

View File

@@ -2,7 +2,6 @@
platform home channel ("telegram"), origin (back to where the job was created), or local (files).""" platform home channel ("telegram"), origin (back to where the job was created), or local (files)."""
import logging import logging
import os
import re import re
from pathlib import Path from pathlib import Path
from datetime import datetime from datetime import datetime
@@ -221,10 +220,8 @@ class DeliveryRouter:
return path return path
def _filter_silence_narration_enabled(self) -> bool: def _filter_silence_narration_enabled(self) -> bool:
"""``HERMES_FILTER_SILENCE_NARRATION`` env overrides the ``gateway.filter_silence_narration`` flag.""" """filter silence narration based on gateway config without checking process env"""
env = os.getenv("HERMES_FILTER_SILENCE_NARRATION") return bool(getattr(self.config, "filter_silence_narration", True))
return (bool(getattr(self.config, "filter_silence_narration", True)) if env is None
else env.strip().lower() in ("1", "true", "yes", "on"))
def _cap_oversized_output(self, adapter: Any, content: str, job_id: str) -> str: def _cap_oversized_output(self, adapter: Any, content: str, job_id: str) -> str:
"""Audit-save oversized cron output; truncate it for non-chunking adapters. Above MAX_PLATFORM_OUTPUT """Audit-save oversized cron output; truncate it for non-chunking adapters. Above MAX_PLATFORM_OUTPUT

View File

@@ -77,7 +77,6 @@ class RecordingAdapter:
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_silence_narration_dropped_pre_send(tmp_path, monkeypatch): async def test_silence_narration_dropped_pre_send(tmp_path, monkeypatch):
monkeypatch.setattr("gateway.delivery.get_hermes_home", lambda: tmp_path) monkeypatch.setattr("gateway.delivery.get_hermes_home", lambda: tmp_path)
monkeypatch.delenv("HERMES_FILTER_SILENCE_NARRATION", raising=False)
adapter = RecordingAdapter() adapter = RecordingAdapter()
router = DeliveryRouter(GatewayConfig(), adapters={Platform.DISCORD: adapter}) router = DeliveryRouter(GatewayConfig(), adapters={Platform.DISCORD: adapter})
target = DeliveryTarget.parse("discord:99887766") target = DeliveryTarget.parse("discord:99887766")
@@ -95,7 +94,6 @@ async def test_silence_narration_dropped_pre_send(tmp_path, monkeypatch):
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_config_opt_out_lets_silence_through(tmp_path, monkeypatch): async def test_config_opt_out_lets_silence_through(tmp_path, monkeypatch):
monkeypatch.setattr("gateway.delivery.get_hermes_home", lambda: tmp_path) monkeypatch.setattr("gateway.delivery.get_hermes_home", lambda: tmp_path)
monkeypatch.delenv("HERMES_FILTER_SILENCE_NARRATION", raising=False)
adapter = RecordingAdapter() adapter = RecordingAdapter()
config = GatewayConfig(filter_silence_narration=False) config = GatewayConfig(filter_silence_narration=False)
router = DeliveryRouter(config, adapters={Platform.DISCORD: adapter}) router = DeliveryRouter(config, adapters={Platform.DISCORD: adapter})
@@ -109,19 +107,61 @@ async def test_config_opt_out_lets_silence_through(tmp_path, monkeypatch):
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_env_override_enables_filter_over_config(tmp_path, monkeypatch): async def test_env_does_not_override_config_opt_out(tmp_path, monkeypatch):
# env shouldn't override config when filter is turned off
monkeypatch.setattr("gateway.delivery.get_hermes_home", lambda: tmp_path) monkeypatch.setattr("gateway.delivery.get_hermes_home", lambda: tmp_path)
monkeypatch.setenv("HERMES_FILTER_SILENCE_NARRATION", "1") monkeypatch.setenv("HERMES_FILTER_SILENCE_NARRATION", "1")
adapter = RecordingAdapter() adapter = RecordingAdapter()
# Config says off, env override forces on.
config = GatewayConfig(filter_silence_narration=False) config = GatewayConfig(filter_silence_narration=False)
router = DeliveryRouter(config, adapters={Platform.DISCORD: adapter}) router = DeliveryRouter(config, adapters={Platform.DISCORD: adapter})
target = DeliveryTarget.parse("discord:99887766") target = DeliveryTarget.parse("discord:99887766")
result = await router._deliver_to_platform(target, "*(silent)*", metadata=None) result = await router._deliver_to_platform(target, "*(silent)*", metadata=None)
assert len(adapter.calls) == 1
assert adapter.calls[0]["content"] == "*(silent)*"
assert result == {"success": True}
@pytest.mark.asyncio
async def test_env_does_not_disable_filter_when_config_enabled(tmp_path, monkeypatch):
# env shouldn't disable filter when config has it on
monkeypatch.setattr("gateway.delivery.get_hermes_home", lambda: tmp_path)
monkeypatch.setenv("HERMES_FILTER_SILENCE_NARRATION", "0")
adapter = RecordingAdapter()
config = GatewayConfig(filter_silence_narration=True)
router = DeliveryRouter(config, adapters={Platform.DISCORD: adapter})
target = DeliveryTarget.parse("discord:99887766")
result = await router._deliver_to_platform(target, "*(silent)*", metadata=None)
assert adapter.calls == [] assert adapter.calls == []
assert result["filtered"] == "silence_narration" assert result == {
"success": True,
"filtered": "silence_narration",
"delivered": False,
}
@pytest.mark.asyncio
async def test_multiplex_profiles_independent_silence_narration_filtering(tmp_path, monkeypatch):
# secondary profile keeps its own setting under multiplex regardless of process env
monkeypatch.setattr("gateway.delivery.get_hermes_home", lambda: tmp_path)
monkeypatch.setenv("HERMES_FILTER_SILENCE_NARRATION", "1")
adapter_a = RecordingAdapter()
adapter_b = RecordingAdapter()
router_a = DeliveryRouter(GatewayConfig(filter_silence_narration=True), adapters={Platform.DISCORD: adapter_a})
router_b = DeliveryRouter(GatewayConfig(filter_silence_narration=False), adapters={Platform.DISCORD: adapter_b})
target = DeliveryTarget.parse("discord:99887766")
res_a = await router_a._deliver_to_platform(target, "*(silent)*", metadata=None)
res_b = await router_b._deliver_to_platform(target, "*(silent)*", metadata=None)
assert adapter_a.calls == []
assert res_a["filtered"] == "silence_narration"
assert len(adapter_b.calls) == 1
assert adapter_b.calls[0]["content"] == "*(silent)*"
assert res_b == {"success": True}
# --- Cron artifacts are exempt ---------------------------------------------- # --- Cron artifacts are exempt ----------------------------------------------
@@ -136,7 +176,6 @@ async def test_env_override_enables_filter_over_config(tmp_path, monkeypatch):
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_cron_job_id_metadata_bypasses_the_filter(tmp_path, monkeypatch): async def test_cron_job_id_metadata_bypasses_the_filter(tmp_path, monkeypatch):
monkeypatch.setattr("gateway.delivery.get_hermes_home", lambda: tmp_path) monkeypatch.setattr("gateway.delivery.get_hermes_home", lambda: tmp_path)
monkeypatch.delenv("HERMES_FILTER_SILENCE_NARRATION", raising=False)
adapter = RecordingAdapter() adapter = RecordingAdapter()
router = DeliveryRouter(GatewayConfig(), adapters={Platform.DISCORD: adapter}) router = DeliveryRouter(GatewayConfig(), adapters={Platform.DISCORD: adapter})
target = DeliveryTarget.parse("discord:99887766") target = DeliveryTarget.parse("discord:99887766")
@@ -155,7 +194,6 @@ async def test_cron_job_id_metadata_bypasses_the_filter(tmp_path, monkeypatch):
async def test_non_cron_metadata_still_filters(tmp_path, monkeypatch): async def test_non_cron_metadata_still_filters(tmp_path, monkeypatch):
"""The exemption keys on job_id alone — everything else is unchanged.""" """The exemption keys on job_id alone — everything else is unchanged."""
monkeypatch.setattr("gateway.delivery.get_hermes_home", lambda: tmp_path) monkeypatch.setattr("gateway.delivery.get_hermes_home", lambda: tmp_path)
monkeypatch.delenv("HERMES_FILTER_SILENCE_NARRATION", raising=False)
adapter = RecordingAdapter() adapter = RecordingAdapter()
router = DeliveryRouter(GatewayConfig(), adapters={Platform.DISCORD: adapter}) router = DeliveryRouter(GatewayConfig(), adapters={Platform.DISCORD: adapter})
target = DeliveryTarget.parse("discord:99887766") target = DeliveryTarget.parse("discord:99887766")