meshai/work/tests/test_channel_rendering.py

331 lines
11 KiB
Python
Raw Normal View History

"""Tests for channel-renderer integration (Phase 2.5b)."""
import asyncio
import time
from unittest.mock import MagicMock, AsyncMock, patch
import pytest
from meshai.notifications.events import NotificationPayload
from meshai.notifications.channels import (
MeshBroadcastChannel,
MeshDMChannel,
EmailChannel,
WebhookChannel,
)
# ============================================================
# MESH CHANNEL RENDERING TESTS
# ============================================================
def test_mesh_channel_uses_mesh_renderer():
"""MeshBroadcastChannel renders long messages to multiple chunks."""
mock_connector = MagicMock()
channel = MeshBroadcastChannel(
connector=mock_connector,
channel_index=0,
)
# Build a long message that will require chunking
long_message = "This is a very long alert message that exceeds the character limit. " * 5
payload = NotificationPayload(
message=long_message,
category="weather_warning",
severity="priority",
timestamp=time.time(),
event_type="weather_warning",
)
asyncio.run(channel.deliver(payload, None))
# Should have called send_message multiple times (once per chunk)
assert mock_connector.send_message.call_count >= 2
# Each call's text should be <= 200 chars
for call in mock_connector.send_message.call_args_list:
text = call.kwargs.get("text", call.args[0] if call.args else "")
assert len(text) <= 200
def test_mesh_channel_uses_payload_message_directly_when_chunk_metadata_set():
"""Pre-chunked payloads (from digest) skip re-rendering."""
mock_connector = MagicMock()
channel = MeshBroadcastChannel(
connector=mock_connector,
channel_index=0,
)
# Payload with chunk metadata set (from digest scheduler)
payload = NotificationPayload(
message="pre-chunked text",
category="digest",
severity="routine",
timestamp=time.time(),
chunk_index=1,
chunk_total=3,
)
asyncio.run(channel.deliver(payload, None))
# Should have called send_message exactly once
assert mock_connector.send_message.call_count == 1
# Should use the message directly
call = mock_connector.send_message.call_args
text = call.kwargs.get("text", call.args[0] if call.args else "")
assert text == "pre-chunked text"
def test_mesh_dm_channel_uses_mesh_renderer():
"""MeshDMChannel renders long messages to chunks for each recipient."""
mock_connector = MagicMock()
channel = MeshDMChannel(
connector=mock_connector,
node_ids=["!node1", "!node2"],
)
long_message = "This is a long DM message that should be chunked. " * 4
payload = NotificationPayload(
message=long_message,
category="test",
severity="routine",
timestamp=time.time(),
)
asyncio.run(channel.deliver(payload, None))
# Should have called send_message multiple times
# (chunks * nodes)
assert mock_connector.send_message.call_count >= 2
def test_mesh_dm_channel_uses_payload_message_directly_when_chunk_metadata_set():
"""Pre-chunked DM payloads skip re-rendering."""
mock_connector = MagicMock()
channel = MeshDMChannel(
connector=mock_connector,
node_ids=["!node1"],
)
payload = NotificationPayload(
message="pre-chunked DM",
category="digest",
severity="routine",
timestamp=time.time(),
chunk_index=2,
chunk_total=5,
)
asyncio.run(channel.deliver(payload, None))
# Should use message directly, once per node
assert mock_connector.send_message.call_count == 1
call = mock_connector.send_message.call_args
text = call.kwargs.get("text", call.args[0] if call.args else "")
assert text == "pre-chunked DM"
# ============================================================
# EMAIL CHANNEL RENDERING TESTS
# ============================================================
def test_email_channel_uses_email_renderer():
"""EmailChannel uses renderer for subject and body."""
channel = EmailChannel(
smtp_host="localhost",
smtp_port=25,
smtp_user="",
smtp_password="",
smtp_tls=False,
from_address="test@example.com",
recipients=["user@example.com"],
)
payload = NotificationPayload(
message="Test alert message",
category="weather_warning",
severity="immediate",
timestamp=time.time(),
event_type="weather_warning",
)
# Mock the _send_email method
with patch.object(channel, "_send_email") as mock_send:
asyncio.run(channel.deliver(payload, None))
# Should have been called with renderer output
mock_send.assert_called_once()
call_args = mock_send.call_args
subject = call_args.args[0]
body = call_args.args[1]
# Renderer format checks
assert "[MeshAI]" in subject
assert "IMMEDIATE" in subject
assert "Test alert message" in body
assert "Severity:" in body
# ============================================================
# WEBHOOK CHANNEL RENDERING TESTS
# ============================================================
def test_webhook_channel_uses_webhook_renderer():
"""WebhookChannel uses renderer for JSON payload."""
channel = WebhookChannel(
url="https://example.com/webhook",
headers={},
)
payload = NotificationPayload(
message="Test webhook message",
category="test",
severity="priority",
timestamp=time.time(),
event_type="battery_warning",
)
# Mock httpx
with patch("meshai.notifications.channels.httpx.AsyncClient") as mock_client_class:
mock_client = MagicMock()
mock_response = MagicMock()
mock_response.status_code = 200
mock_client.post = AsyncMock(return_value=mock_response)
mock_client.__aenter__ = AsyncMock(return_value=mock_client)
mock_client.__aexit__ = AsyncMock(return_value=None)
mock_client_class.return_value = mock_client
asyncio.run(channel.deliver(payload, None))
# Check the POST was called
mock_client.post.assert_called_once()
call_kwargs = mock_client.post.call_args.kwargs
# Should have JSON payload with schema_version
json_payload = call_kwargs.get("json", {})
assert "schema_version" in json_payload
assert json_payload["schema_version"] == "1.0"
assert json_payload["message"] == "Test webhook message"
feat(dashboard): MeshCore transport + per-family routing GUI controls (#10) * feat(dashboard): MeshCore transport + per-family routing GUI controls Add Transport mode selector (Meshtastic/MeshCore/Both) and MeshCore host/port fields to the Config Connection section, and an independent per-family "MeshCore channel" number input in Notifications (blank = not broadcast on MeshCore, sends null). Extends the ConnectionConfig and per-family toggle TS types. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(routing): MeshCore routing by channel name, not index MeshCore channels are {name,PSK} (up to 40+ slots, not Meshtastic's 0-7). The send index is a fragile slot position, so store the channel NAME per family and resolve name->slot against the companion's live channel table at send time; never blind-send to an unresolved slot. GUI field becomes a channel-name text box. meshtastic path unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(routing): thread per-family meshcore_channel through the broadcast send path MeshBroadcastChannel now carries the rule's meshcore_channel name and passes it to send_message, so per-family MeshCore routing actually fires end-to-end (dispatcher -> channel -> composite -> MeshCoreTransport). Meshtastic path unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Matt Johnson <mj@k7zvx.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 16:49:59 -06:00
# ============================================================
# PER-FAMILY MESHCORE ROUTING — end-to-end threading guard
feat(routing): MeshCore as first-class delivery types (meshcore_broadcast/dm) (#11) * feat(routing): MeshCore as first-class delivery types (meshcore_broadcast/dm) Replace the composite auto-fan with explicit per-mesh delivery types so each family independently controls broadcast/DM per severity on Meshtastic AND MeshCore. mesh_broadcast->Meshtastic only, meshcore_broadcast->MeshCore (by channel name), mesh_dm/meshcore_dm likewise; routing via the existing transport hint. Adds meshcore_dm_contacts. Meshtastic-only configs unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(routing): deliver meshcore_broadcast via meshcore_channel through CompositeTransport The hinted _broadcast path passed the channel NAME on the `channel` kwarg, which MeshCoreTransport ignores (it reads meshcore_channel), so meshcore_broadcast silently no-op'd on transport=both configs. Route the meshcore child via meshcore_channel and the meshtastic child via channel. Fix the test that asserted the broken kwarg layout. Add the new delivery types to the remaining enumeration/validation sites (channel-test endpoint, scheduler digest chunking, danger-zone valid set). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(dashboard): split Notifications into Meshtastic and MeshCore sections Delineate per-mesh routing: each family configures Meshtastic delivery (mesh_broadcast/mesh_dm, channel index, node IDs) and MeshCore delivery (meshcore_broadcast/meshcore_dm, channel name, contacts) in separate sections; shared settings (enable/severity/regions/email/webhook) once. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(dashboard): first-class MeshCore nav section + dedicated pages Group the sidebar into Meshtastic and MeshCore sections. Promote MeshCore routing and connection to their own pages; move MeshCore routing out of Notifications (which stays Meshtastic + shared family settings). Add placeholder Contacts and Companion pages for the follow-on companion data API. No backend change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(dashboard): parallel MT/MC nav order + symmetric connection links Order both nav groups Connection/Routing/Mesh(Contacts)/Sources(Companion). Replace the prominent MeshCore block on the Meshtastic Connection page with a single subtle cross-link, mirrored on the MeshCore Connection page. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(dashboard): focused Meshtastic Connection/Sources pages for MT/MC parity Meshtastic Connection and Sources are now their own focused pages (mirroring MeshCore), instead of deep-linking into the full Config page. Global settings move to a restored top-level Config item. No duplicate editors; connection cross-links are mirror-image between the two pages. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Matt Johnson <mj@k7zvx.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 22:42:14 -06:00
# Updated for the explicit-per-mesh model (meshcore_broadcast/mesh_broadcast)
feat(dashboard): MeshCore transport + per-family routing GUI controls (#10) * feat(dashboard): MeshCore transport + per-family routing GUI controls Add Transport mode selector (Meshtastic/MeshCore/Both) and MeshCore host/port fields to the Config Connection section, and an independent per-family "MeshCore channel" number input in Notifications (blank = not broadcast on MeshCore, sends null). Extends the ConnectionConfig and per-family toggle TS types. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(routing): MeshCore routing by channel name, not index MeshCore channels are {name,PSK} (up to 40+ slots, not Meshtastic's 0-7). The send index is a fragile slot position, so store the channel NAME per family and resolve name->slot against the companion's live channel table at send time; never blind-send to an unresolved slot. GUI field becomes a channel-name text box. meshtastic path unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(routing): thread per-family meshcore_channel through the broadcast send path MeshBroadcastChannel now carries the rule's meshcore_channel name and passes it to send_message, so per-family MeshCore routing actually fires end-to-end (dispatcher -> channel -> composite -> MeshCoreTransport). Meshtastic path unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Matt Johnson <mj@k7zvx.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 16:49:59 -06:00
# ============================================================
feat(routing): MeshCore as first-class delivery types (meshcore_broadcast/dm) (#11) * feat(routing): MeshCore as first-class delivery types (meshcore_broadcast/dm) Replace the composite auto-fan with explicit per-mesh delivery types so each family independently controls broadcast/DM per severity on Meshtastic AND MeshCore. mesh_broadcast->Meshtastic only, meshcore_broadcast->MeshCore (by channel name), mesh_dm/meshcore_dm likewise; routing via the existing transport hint. Adds meshcore_dm_contacts. Meshtastic-only configs unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(routing): deliver meshcore_broadcast via meshcore_channel through CompositeTransport The hinted _broadcast path passed the channel NAME on the `channel` kwarg, which MeshCoreTransport ignores (it reads meshcore_channel), so meshcore_broadcast silently no-op'd on transport=both configs. Route the meshcore child via meshcore_channel and the meshtastic child via channel. Fix the test that asserted the broken kwarg layout. Add the new delivery types to the remaining enumeration/validation sites (channel-test endpoint, scheduler digest chunking, danger-zone valid set). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(dashboard): split Notifications into Meshtastic and MeshCore sections Delineate per-mesh routing: each family configures Meshtastic delivery (mesh_broadcast/mesh_dm, channel index, node IDs) and MeshCore delivery (meshcore_broadcast/meshcore_dm, channel name, contacts) in separate sections; shared settings (enable/severity/regions/email/webhook) once. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(dashboard): first-class MeshCore nav section + dedicated pages Group the sidebar into Meshtastic and MeshCore sections. Promote MeshCore routing and connection to their own pages; move MeshCore routing out of Notifications (which stays Meshtastic + shared family settings). Add placeholder Contacts and Companion pages for the follow-on companion data API. No backend change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(dashboard): parallel MT/MC nav order + symmetric connection links Order both nav groups Connection/Routing/Mesh(Contacts)/Sources(Companion). Replace the prominent MeshCore block on the Meshtastic Connection page with a single subtle cross-link, mirrored on the MeshCore Connection page. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(dashboard): focused Meshtastic Connection/Sources pages for MT/MC parity Meshtastic Connection and Sources are now their own focused pages (mirroring MeshCore), instead of deep-linking into the full Config page. Global settings move to a restored top-level Config item. No duplicate editors; connection cross-links are mirror-image between the two pages. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Matt Johnson <mj@k7zvx.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 22:42:14 -06:00
def test_mesh_broadcast_routes_to_meshtastic_only():
"""mesh_broadcast passes transport='meshtastic' and channel index to
send_message. meshcore_channel is NOT passed (auto-fan removed).
feat(dashboard): MeshCore transport + per-family routing GUI controls (#10) * feat(dashboard): MeshCore transport + per-family routing GUI controls Add Transport mode selector (Meshtastic/MeshCore/Both) and MeshCore host/port fields to the Config Connection section, and an independent per-family "MeshCore channel" number input in Notifications (blank = not broadcast on MeshCore, sends null). Extends the ConnectionConfig and per-family toggle TS types. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(routing): MeshCore routing by channel name, not index MeshCore channels are {name,PSK} (up to 40+ slots, not Meshtastic's 0-7). The send index is a fragile slot position, so store the channel NAME per family and resolve name->slot against the companion's live channel table at send time; never blind-send to an unresolved slot. GUI field becomes a channel-name text box. meshtastic path unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(routing): thread per-family meshcore_channel through the broadcast send path MeshBroadcastChannel now carries the rule's meshcore_channel name and passes it to send_message, so per-family MeshCore routing actually fires end-to-end (dispatcher -> channel -> composite -> MeshCoreTransport). Meshtastic path unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Matt Johnson <mj@k7zvx.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 16:49:59 -06:00
feat(routing): MeshCore as first-class delivery types (meshcore_broadcast/dm) (#11) * feat(routing): MeshCore as first-class delivery types (meshcore_broadcast/dm) Replace the composite auto-fan with explicit per-mesh delivery types so each family independently controls broadcast/DM per severity on Meshtastic AND MeshCore. mesh_broadcast->Meshtastic only, meshcore_broadcast->MeshCore (by channel name), mesh_dm/meshcore_dm likewise; routing via the existing transport hint. Adds meshcore_dm_contacts. Meshtastic-only configs unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(routing): deliver meshcore_broadcast via meshcore_channel through CompositeTransport The hinted _broadcast path passed the channel NAME on the `channel` kwarg, which MeshCoreTransport ignores (it reads meshcore_channel), so meshcore_broadcast silently no-op'd on transport=both configs. Route the meshcore child via meshcore_channel and the meshtastic child via channel. Fix the test that asserted the broken kwarg layout. Add the new delivery types to the remaining enumeration/validation sites (channel-test endpoint, scheduler digest chunking, danger-zone valid set). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(dashboard): split Notifications into Meshtastic and MeshCore sections Delineate per-mesh routing: each family configures Meshtastic delivery (mesh_broadcast/mesh_dm, channel index, node IDs) and MeshCore delivery (meshcore_broadcast/meshcore_dm, channel name, contacts) in separate sections; shared settings (enable/severity/regions/email/webhook) once. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(dashboard): first-class MeshCore nav section + dedicated pages Group the sidebar into Meshtastic and MeshCore sections. Promote MeshCore routing and connection to their own pages; move MeshCore routing out of Notifications (which stays Meshtastic + shared family settings). Add placeholder Contacts and Companion pages for the follow-on companion data API. No backend change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(dashboard): parallel MT/MC nav order + symmetric connection links Order both nav groups Connection/Routing/Mesh(Contacts)/Sources(Companion). Replace the prominent MeshCore block on the Meshtastic Connection page with a single subtle cross-link, mirrored on the MeshCore Connection page. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(dashboard): focused Meshtastic Connection/Sources pages for MT/MC parity Meshtastic Connection and Sources are now their own focused pages (mirroring MeshCore), instead of deep-linking into the full Config page. Global settings move to a restored top-level Config item. No duplicate editors; connection cross-links are mirror-image between the two pages. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Matt Johnson <mj@k7zvx.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 22:42:14 -06:00
Regression guard: before this model, mesh_broadcast also threaded
meshcore_channel through; now it is Meshtastic-only.
feat(dashboard): MeshCore transport + per-family routing GUI controls (#10) * feat(dashboard): MeshCore transport + per-family routing GUI controls Add Transport mode selector (Meshtastic/MeshCore/Both) and MeshCore host/port fields to the Config Connection section, and an independent per-family "MeshCore channel" number input in Notifications (blank = not broadcast on MeshCore, sends null). Extends the ConnectionConfig and per-family toggle TS types. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(routing): MeshCore routing by channel name, not index MeshCore channels are {name,PSK} (up to 40+ slots, not Meshtastic's 0-7). The send index is a fragile slot position, so store the channel NAME per family and resolve name->slot against the companion's live channel table at send time; never blind-send to an unresolved slot. GUI field becomes a channel-name text box. meshtastic path unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(routing): thread per-family meshcore_channel through the broadcast send path MeshBroadcastChannel now carries the rule's meshcore_channel name and passes it to send_message, so per-family MeshCore routing actually fires end-to-end (dispatcher -> channel -> composite -> MeshCoreTransport). Meshtastic path unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Matt Johnson <mj@k7zvx.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 16:49:59 -06:00
"""
from meshai.config import NotificationRuleConfig
from meshai.notifications.channels import create_channel
mock_connector = MagicMock()
rule = NotificationRuleConfig(
name="toggle:fire",
delivery_type="mesh_broadcast",
broadcast_channel=1,
feat(routing): MeshCore as first-class delivery types (meshcore_broadcast/dm) (#11) * feat(routing): MeshCore as first-class delivery types (meshcore_broadcast/dm) Replace the composite auto-fan with explicit per-mesh delivery types so each family independently controls broadcast/DM per severity on Meshtastic AND MeshCore. mesh_broadcast->Meshtastic only, meshcore_broadcast->MeshCore (by channel name), mesh_dm/meshcore_dm likewise; routing via the existing transport hint. Adds meshcore_dm_contacts. Meshtastic-only configs unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(routing): deliver meshcore_broadcast via meshcore_channel through CompositeTransport The hinted _broadcast path passed the channel NAME on the `channel` kwarg, which MeshCoreTransport ignores (it reads meshcore_channel), so meshcore_broadcast silently no-op'd on transport=both configs. Route the meshcore child via meshcore_channel and the meshtastic child via channel. Fix the test that asserted the broken kwarg layout. Add the new delivery types to the remaining enumeration/validation sites (channel-test endpoint, scheduler digest chunking, danger-zone valid set). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(dashboard): split Notifications into Meshtastic and MeshCore sections Delineate per-mesh routing: each family configures Meshtastic delivery (mesh_broadcast/mesh_dm, channel index, node IDs) and MeshCore delivery (meshcore_broadcast/meshcore_dm, channel name, contacts) in separate sections; shared settings (enable/severity/regions/email/webhook) once. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(dashboard): first-class MeshCore nav section + dedicated pages Group the sidebar into Meshtastic and MeshCore sections. Promote MeshCore routing and connection to their own pages; move MeshCore routing out of Notifications (which stays Meshtastic + shared family settings). Add placeholder Contacts and Companion pages for the follow-on companion data API. No backend change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(dashboard): parallel MT/MC nav order + symmetric connection links Order both nav groups Connection/Routing/Mesh(Contacts)/Sources(Companion). Replace the prominent MeshCore block on the Meshtastic Connection page with a single subtle cross-link, mirrored on the MeshCore Connection page. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(dashboard): focused Meshtastic Connection/Sources pages for MT/MC parity Meshtastic Connection and Sources are now their own focused pages (mirroring MeshCore), instead of deep-linking into the full Config page. Global settings move to a restored top-level Config item. No duplicate editors; connection cross-links are mirror-image between the two pages. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Matt Johnson <mj@k7zvx.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 22:42:14 -06:00
meshcore_channel="AIDA", # present in config but must NOT flow to send_message
feat(dashboard): MeshCore transport + per-family routing GUI controls (#10) * feat(dashboard): MeshCore transport + per-family routing GUI controls Add Transport mode selector (Meshtastic/MeshCore/Both) and MeshCore host/port fields to the Config Connection section, and an independent per-family "MeshCore channel" number input in Notifications (blank = not broadcast on MeshCore, sends null). Extends the ConnectionConfig and per-family toggle TS types. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(routing): MeshCore routing by channel name, not index MeshCore channels are {name,PSK} (up to 40+ slots, not Meshtastic's 0-7). The send index is a fragile slot position, so store the channel NAME per family and resolve name->slot against the companion's live channel table at send time; never blind-send to an unresolved slot. GUI field becomes a channel-name text box. meshtastic path unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(routing): thread per-family meshcore_channel through the broadcast send path MeshBroadcastChannel now carries the rule's meshcore_channel name and passes it to send_message, so per-family MeshCore routing actually fires end-to-end (dispatcher -> channel -> composite -> MeshCoreTransport). Meshtastic path unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Matt Johnson <mj@k7zvx.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 16:49:59 -06:00
)
channel = create_channel(rule, mock_connector)
# Pre-chunked payload => exactly one deterministic send_message call.
payload = NotificationPayload(
message="fire alert",
category="fire",
severity="immediate",
timestamp=time.time(),
event_type="fire",
chunk_index=0,
)
assert asyncio.run(channel.deliver(payload, rule)) is True
mock_connector.send_message.assert_called_once()
kwargs = mock_connector.send_message.call_args.kwargs
assert kwargs.get("channel") == 1
feat(routing): MeshCore as first-class delivery types (meshcore_broadcast/dm) (#11) * feat(routing): MeshCore as first-class delivery types (meshcore_broadcast/dm) Replace the composite auto-fan with explicit per-mesh delivery types so each family independently controls broadcast/DM per severity on Meshtastic AND MeshCore. mesh_broadcast->Meshtastic only, meshcore_broadcast->MeshCore (by channel name), mesh_dm/meshcore_dm likewise; routing via the existing transport hint. Adds meshcore_dm_contacts. Meshtastic-only configs unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(routing): deliver meshcore_broadcast via meshcore_channel through CompositeTransport The hinted _broadcast path passed the channel NAME on the `channel` kwarg, which MeshCoreTransport ignores (it reads meshcore_channel), so meshcore_broadcast silently no-op'd on transport=both configs. Route the meshcore child via meshcore_channel and the meshtastic child via channel. Fix the test that asserted the broken kwarg layout. Add the new delivery types to the remaining enumeration/validation sites (channel-test endpoint, scheduler digest chunking, danger-zone valid set). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(dashboard): split Notifications into Meshtastic and MeshCore sections Delineate per-mesh routing: each family configures Meshtastic delivery (mesh_broadcast/mesh_dm, channel index, node IDs) and MeshCore delivery (meshcore_broadcast/meshcore_dm, channel name, contacts) in separate sections; shared settings (enable/severity/regions/email/webhook) once. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(dashboard): first-class MeshCore nav section + dedicated pages Group the sidebar into Meshtastic and MeshCore sections. Promote MeshCore routing and connection to their own pages; move MeshCore routing out of Notifications (which stays Meshtastic + shared family settings). Add placeholder Contacts and Companion pages for the follow-on companion data API. No backend change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(dashboard): parallel MT/MC nav order + symmetric connection links Order both nav groups Connection/Routing/Mesh(Contacts)/Sources(Companion). Replace the prominent MeshCore block on the Meshtastic Connection page with a single subtle cross-link, mirrored on the MeshCore Connection page. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(dashboard): focused Meshtastic Connection/Sources pages for MT/MC parity Meshtastic Connection and Sources are now their own focused pages (mirroring MeshCore), instead of deep-linking into the full Config page. Global settings move to a restored top-level Config item. No duplicate editors; connection cross-links are mirror-image between the two pages. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Matt Johnson <mj@k7zvx.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 22:42:14 -06:00
assert kwargs.get("transport") == "meshtastic"
# meshcore_channel must NOT be present (no auto-fan).
assert "meshcore_channel" not in kwargs or kwargs.get("meshcore_channel") is None
feat(dashboard): MeshCore transport + per-family routing GUI controls (#10) * feat(dashboard): MeshCore transport + per-family routing GUI controls Add Transport mode selector (Meshtastic/MeshCore/Both) and MeshCore host/port fields to the Config Connection section, and an independent per-family "MeshCore channel" number input in Notifications (blank = not broadcast on MeshCore, sends null). Extends the ConnectionConfig and per-family toggle TS types. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(routing): MeshCore routing by channel name, not index MeshCore channels are {name,PSK} (up to 40+ slots, not Meshtastic's 0-7). The send index is a fragile slot position, so store the channel NAME per family and resolve name->slot against the companion's live channel table at send time; never blind-send to an unresolved slot. GUI field becomes a channel-name text box. meshtastic path unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(routing): thread per-family meshcore_channel through the broadcast send path MeshBroadcastChannel now carries the rule's meshcore_channel name and passes it to send_message, so per-family MeshCore routing actually fires end-to-end (dispatcher -> channel -> composite -> MeshCoreTransport). Meshtastic path unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Matt Johnson <mj@k7zvx.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 16:49:59 -06:00
feat(routing): MeshCore as first-class delivery types (meshcore_broadcast/dm) (#11) * feat(routing): MeshCore as first-class delivery types (meshcore_broadcast/dm) Replace the composite auto-fan with explicit per-mesh delivery types so each family independently controls broadcast/DM per severity on Meshtastic AND MeshCore. mesh_broadcast->Meshtastic only, meshcore_broadcast->MeshCore (by channel name), mesh_dm/meshcore_dm likewise; routing via the existing transport hint. Adds meshcore_dm_contacts. Meshtastic-only configs unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(routing): deliver meshcore_broadcast via meshcore_channel through CompositeTransport The hinted _broadcast path passed the channel NAME on the `channel` kwarg, which MeshCoreTransport ignores (it reads meshcore_channel), so meshcore_broadcast silently no-op'd on transport=both configs. Route the meshcore child via meshcore_channel and the meshtastic child via channel. Fix the test that asserted the broken kwarg layout. Add the new delivery types to the remaining enumeration/validation sites (channel-test endpoint, scheduler digest chunking, danger-zone valid set). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(dashboard): split Notifications into Meshtastic and MeshCore sections Delineate per-mesh routing: each family configures Meshtastic delivery (mesh_broadcast/mesh_dm, channel index, node IDs) and MeshCore delivery (meshcore_broadcast/meshcore_dm, channel name, contacts) in separate sections; shared settings (enable/severity/regions/email/webhook) once. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(dashboard): first-class MeshCore nav section + dedicated pages Group the sidebar into Meshtastic and MeshCore sections. Promote MeshCore routing and connection to their own pages; move MeshCore routing out of Notifications (which stays Meshtastic + shared family settings). Add placeholder Contacts and Companion pages for the follow-on companion data API. No backend change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(dashboard): parallel MT/MC nav order + symmetric connection links Order both nav groups Connection/Routing/Mesh(Contacts)/Sources(Companion). Replace the prominent MeshCore block on the Meshtastic Connection page with a single subtle cross-link, mirrored on the MeshCore Connection page. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(dashboard): focused Meshtastic Connection/Sources pages for MT/MC parity Meshtastic Connection and Sources are now their own focused pages (mirroring MeshCore), instead of deep-linking into the full Config page. Global settings move to a restored top-level Config item. No duplicate editors; connection cross-links are mirror-image between the two pages. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Matt Johnson <mj@k7zvx.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 22:42:14 -06:00
def test_meshcore_broadcast_routes_to_meshcore_only():
"""meshcore_broadcast passes meshcore_channel=name and transport='meshcore'
to send_message. This is the explicit MeshCore-only delivery path."""
feat(dashboard): MeshCore transport + per-family routing GUI controls (#10) * feat(dashboard): MeshCore transport + per-family routing GUI controls Add Transport mode selector (Meshtastic/MeshCore/Both) and MeshCore host/port fields to the Config Connection section, and an independent per-family "MeshCore channel" number input in Notifications (blank = not broadcast on MeshCore, sends null). Extends the ConnectionConfig and per-family toggle TS types. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(routing): MeshCore routing by channel name, not index MeshCore channels are {name,PSK} (up to 40+ slots, not Meshtastic's 0-7). The send index is a fragile slot position, so store the channel NAME per family and resolve name->slot against the companion's live channel table at send time; never blind-send to an unresolved slot. GUI field becomes a channel-name text box. meshtastic path unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(routing): thread per-family meshcore_channel through the broadcast send path MeshBroadcastChannel now carries the rule's meshcore_channel name and passes it to send_message, so per-family MeshCore routing actually fires end-to-end (dispatcher -> channel -> composite -> MeshCoreTransport). Meshtastic path unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Matt Johnson <mj@k7zvx.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 16:49:59 -06:00
from meshai.config import NotificationRuleConfig
feat(routing): MeshCore as first-class delivery types (meshcore_broadcast/dm) (#11) * feat(routing): MeshCore as first-class delivery types (meshcore_broadcast/dm) Replace the composite auto-fan with explicit per-mesh delivery types so each family independently controls broadcast/DM per severity on Meshtastic AND MeshCore. mesh_broadcast->Meshtastic only, meshcore_broadcast->MeshCore (by channel name), mesh_dm/meshcore_dm likewise; routing via the existing transport hint. Adds meshcore_dm_contacts. Meshtastic-only configs unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(routing): deliver meshcore_broadcast via meshcore_channel through CompositeTransport The hinted _broadcast path passed the channel NAME on the `channel` kwarg, which MeshCoreTransport ignores (it reads meshcore_channel), so meshcore_broadcast silently no-op'd on transport=both configs. Route the meshcore child via meshcore_channel and the meshtastic child via channel. Fix the test that asserted the broken kwarg layout. Add the new delivery types to the remaining enumeration/validation sites (channel-test endpoint, scheduler digest chunking, danger-zone valid set). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(dashboard): split Notifications into Meshtastic and MeshCore sections Delineate per-mesh routing: each family configures Meshtastic delivery (mesh_broadcast/mesh_dm, channel index, node IDs) and MeshCore delivery (meshcore_broadcast/meshcore_dm, channel name, contacts) in separate sections; shared settings (enable/severity/regions/email/webhook) once. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(dashboard): first-class MeshCore nav section + dedicated pages Group the sidebar into Meshtastic and MeshCore sections. Promote MeshCore routing and connection to their own pages; move MeshCore routing out of Notifications (which stays Meshtastic + shared family settings). Add placeholder Contacts and Companion pages for the follow-on companion data API. No backend change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(dashboard): parallel MT/MC nav order + symmetric connection links Order both nav groups Connection/Routing/Mesh(Contacts)/Sources(Companion). Replace the prominent MeshCore block on the Meshtastic Connection page with a single subtle cross-link, mirrored on the MeshCore Connection page. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(dashboard): focused Meshtastic Connection/Sources pages for MT/MC parity Meshtastic Connection and Sources are now their own focused pages (mirroring MeshCore), instead of deep-linking into the full Config page. Global settings move to a restored top-level Config item. No duplicate editors; connection cross-links are mirror-image between the two pages. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Matt Johnson <mj@k7zvx.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 22:42:14 -06:00
from meshai.notifications.channels import MeshCoreBroadcastChannel, create_channel
feat(dashboard): MeshCore transport + per-family routing GUI controls (#10) * feat(dashboard): MeshCore transport + per-family routing GUI controls Add Transport mode selector (Meshtastic/MeshCore/Both) and MeshCore host/port fields to the Config Connection section, and an independent per-family "MeshCore channel" number input in Notifications (blank = not broadcast on MeshCore, sends null). Extends the ConnectionConfig and per-family toggle TS types. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(routing): MeshCore routing by channel name, not index MeshCore channels are {name,PSK} (up to 40+ slots, not Meshtastic's 0-7). The send index is a fragile slot position, so store the channel NAME per family and resolve name->slot against the companion's live channel table at send time; never blind-send to an unresolved slot. GUI field becomes a channel-name text box. meshtastic path unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(routing): thread per-family meshcore_channel through the broadcast send path MeshBroadcastChannel now carries the rule's meshcore_channel name and passes it to send_message, so per-family MeshCore routing actually fires end-to-end (dispatcher -> channel -> composite -> MeshCoreTransport). Meshtastic path unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Matt Johnson <mj@k7zvx.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 16:49:59 -06:00
feat(routing): MeshCore as first-class delivery types (meshcore_broadcast/dm) (#11) * feat(routing): MeshCore as first-class delivery types (meshcore_broadcast/dm) Replace the composite auto-fan with explicit per-mesh delivery types so each family independently controls broadcast/DM per severity on Meshtastic AND MeshCore. mesh_broadcast->Meshtastic only, meshcore_broadcast->MeshCore (by channel name), mesh_dm/meshcore_dm likewise; routing via the existing transport hint. Adds meshcore_dm_contacts. Meshtastic-only configs unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(routing): deliver meshcore_broadcast via meshcore_channel through CompositeTransport The hinted _broadcast path passed the channel NAME on the `channel` kwarg, which MeshCoreTransport ignores (it reads meshcore_channel), so meshcore_broadcast silently no-op'd on transport=both configs. Route the meshcore child via meshcore_channel and the meshtastic child via channel. Fix the test that asserted the broken kwarg layout. Add the new delivery types to the remaining enumeration/validation sites (channel-test endpoint, scheduler digest chunking, danger-zone valid set). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(dashboard): split Notifications into Meshtastic and MeshCore sections Delineate per-mesh routing: each family configures Meshtastic delivery (mesh_broadcast/mesh_dm, channel index, node IDs) and MeshCore delivery (meshcore_broadcast/meshcore_dm, channel name, contacts) in separate sections; shared settings (enable/severity/regions/email/webhook) once. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(dashboard): first-class MeshCore nav section + dedicated pages Group the sidebar into Meshtastic and MeshCore sections. Promote MeshCore routing and connection to their own pages; move MeshCore routing out of Notifications (which stays Meshtastic + shared family settings). Add placeholder Contacts and Companion pages for the follow-on companion data API. No backend change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(dashboard): parallel MT/MC nav order + symmetric connection links Order both nav groups Connection/Routing/Mesh(Contacts)/Sources(Companion). Replace the prominent MeshCore block on the Meshtastic Connection page with a single subtle cross-link, mirrored on the MeshCore Connection page. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(dashboard): focused Meshtastic Connection/Sources pages for MT/MC parity Meshtastic Connection and Sources are now their own focused pages (mirroring MeshCore), instead of deep-linking into the full Config page. Global settings move to a restored top-level Config item. No duplicate editors; connection cross-links are mirror-image between the two pages. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Matt Johnson <mj@k7zvx.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 22:42:14 -06:00
# Simulate a CompositeTransport connector with a meshcore child.
feat(dashboard): MeshCore transport + per-family routing GUI controls (#10) * feat(dashboard): MeshCore transport + per-family routing GUI controls Add Transport mode selector (Meshtastic/MeshCore/Both) and MeshCore host/port fields to the Config Connection section, and an independent per-family "MeshCore channel" number input in Notifications (blank = not broadcast on MeshCore, sends null). Extends the ConnectionConfig and per-family toggle TS types. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(routing): MeshCore routing by channel name, not index MeshCore channels are {name,PSK} (up to 40+ slots, not Meshtastic's 0-7). The send index is a fragile slot position, so store the channel NAME per family and resolve name->slot against the companion's live channel table at send time; never blind-send to an unresolved slot. GUI field becomes a channel-name text box. meshtastic path unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(routing): thread per-family meshcore_channel through the broadcast send path MeshBroadcastChannel now carries the rule's meshcore_channel name and passes it to send_message, so per-family MeshCore routing actually fires end-to-end (dispatcher -> channel -> composite -> MeshCoreTransport). Meshtastic path unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Matt Johnson <mj@k7zvx.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 16:49:59 -06:00
mock_connector = MagicMock()
feat(routing): MeshCore as first-class delivery types (meshcore_broadcast/dm) (#11) * feat(routing): MeshCore as first-class delivery types (meshcore_broadcast/dm) Replace the composite auto-fan with explicit per-mesh delivery types so each family independently controls broadcast/DM per severity on Meshtastic AND MeshCore. mesh_broadcast->Meshtastic only, meshcore_broadcast->MeshCore (by channel name), mesh_dm/meshcore_dm likewise; routing via the existing transport hint. Adds meshcore_dm_contacts. Meshtastic-only configs unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(routing): deliver meshcore_broadcast via meshcore_channel through CompositeTransport The hinted _broadcast path passed the channel NAME on the `channel` kwarg, which MeshCoreTransport ignores (it reads meshcore_channel), so meshcore_broadcast silently no-op'd on transport=both configs. Route the meshcore child via meshcore_channel and the meshtastic child via channel. Fix the test that asserted the broken kwarg layout. Add the new delivery types to the remaining enumeration/validation sites (channel-test endpoint, scheduler digest chunking, danger-zone valid set). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(dashboard): split Notifications into Meshtastic and MeshCore sections Delineate per-mesh routing: each family configures Meshtastic delivery (mesh_broadcast/mesh_dm, channel index, node IDs) and MeshCore delivery (meshcore_broadcast/meshcore_dm, channel name, contacts) in separate sections; shared settings (enable/severity/regions/email/webhook) once. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(dashboard): first-class MeshCore nav section + dedicated pages Group the sidebar into Meshtastic and MeshCore sections. Promote MeshCore routing and connection to their own pages; move MeshCore routing out of Notifications (which stays Meshtastic + shared family settings). Add placeholder Contacts and Companion pages for the follow-on companion data API. No backend change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(dashboard): parallel MT/MC nav order + symmetric connection links Order both nav groups Connection/Routing/Mesh(Contacts)/Sources(Companion). Replace the prominent MeshCore block on the Meshtastic Connection page with a single subtle cross-link, mirrored on the MeshCore Connection page. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(dashboard): focused Meshtastic Connection/Sources pages for MT/MC parity Meshtastic Connection and Sources are now their own focused pages (mirroring MeshCore), instead of deep-linking into the full Config page. Global settings move to a restored top-level Config item. No duplicate editors; connection cross-links are mirror-image between the two pages. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Matt Johnson <mj@k7zvx.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 22:42:14 -06:00
mock_connector._by_name = {"meshcore": MagicMock(), "meshtastic": MagicMock()}
mock_connector.send_message.return_value = True
feat(dashboard): MeshCore transport + per-family routing GUI controls (#10) * feat(dashboard): MeshCore transport + per-family routing GUI controls Add Transport mode selector (Meshtastic/MeshCore/Both) and MeshCore host/port fields to the Config Connection section, and an independent per-family "MeshCore channel" number input in Notifications (blank = not broadcast on MeshCore, sends null). Extends the ConnectionConfig and per-family toggle TS types. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(routing): MeshCore routing by channel name, not index MeshCore channels are {name,PSK} (up to 40+ slots, not Meshtastic's 0-7). The send index is a fragile slot position, so store the channel NAME per family and resolve name->slot against the companion's live channel table at send time; never blind-send to an unresolved slot. GUI field becomes a channel-name text box. meshtastic path unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(routing): thread per-family meshcore_channel through the broadcast send path MeshBroadcastChannel now carries the rule's meshcore_channel name and passes it to send_message, so per-family MeshCore routing actually fires end-to-end (dispatcher -> channel -> composite -> MeshCoreTransport). Meshtastic path unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Matt Johnson <mj@k7zvx.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 16:49:59 -06:00
rule = NotificationRuleConfig(
feat(routing): MeshCore as first-class delivery types (meshcore_broadcast/dm) (#11) * feat(routing): MeshCore as first-class delivery types (meshcore_broadcast/dm) Replace the composite auto-fan with explicit per-mesh delivery types so each family independently controls broadcast/DM per severity on Meshtastic AND MeshCore. mesh_broadcast->Meshtastic only, meshcore_broadcast->MeshCore (by channel name), mesh_dm/meshcore_dm likewise; routing via the existing transport hint. Adds meshcore_dm_contacts. Meshtastic-only configs unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(routing): deliver meshcore_broadcast via meshcore_channel through CompositeTransport The hinted _broadcast path passed the channel NAME on the `channel` kwarg, which MeshCoreTransport ignores (it reads meshcore_channel), so meshcore_broadcast silently no-op'd on transport=both configs. Route the meshcore child via meshcore_channel and the meshtastic child via channel. Fix the test that asserted the broken kwarg layout. Add the new delivery types to the remaining enumeration/validation sites (channel-test endpoint, scheduler digest chunking, danger-zone valid set). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(dashboard): split Notifications into Meshtastic and MeshCore sections Delineate per-mesh routing: each family configures Meshtastic delivery (mesh_broadcast/mesh_dm, channel index, node IDs) and MeshCore delivery (meshcore_broadcast/meshcore_dm, channel name, contacts) in separate sections; shared settings (enable/severity/regions/email/webhook) once. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(dashboard): first-class MeshCore nav section + dedicated pages Group the sidebar into Meshtastic and MeshCore sections. Promote MeshCore routing and connection to their own pages; move MeshCore routing out of Notifications (which stays Meshtastic + shared family settings). Add placeholder Contacts and Companion pages for the follow-on companion data API. No backend change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(dashboard): parallel MT/MC nav order + symmetric connection links Order both nav groups Connection/Routing/Mesh(Contacts)/Sources(Companion). Replace the prominent MeshCore block on the Meshtastic Connection page with a single subtle cross-link, mirrored on the MeshCore Connection page. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(dashboard): focused Meshtastic Connection/Sources pages for MT/MC parity Meshtastic Connection and Sources are now their own focused pages (mirroring MeshCore), instead of deep-linking into the full Config page. Global settings move to a restored top-level Config item. No duplicate editors; connection cross-links are mirror-image between the two pages. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Matt Johnson <mj@k7zvx.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 22:42:14 -06:00
name="toggle:fire",
delivery_type="meshcore_broadcast",
meshcore_channel="AIDA",
feat(dashboard): MeshCore transport + per-family routing GUI controls (#10) * feat(dashboard): MeshCore transport + per-family routing GUI controls Add Transport mode selector (Meshtastic/MeshCore/Both) and MeshCore host/port fields to the Config Connection section, and an independent per-family "MeshCore channel" number input in Notifications (blank = not broadcast on MeshCore, sends null). Extends the ConnectionConfig and per-family toggle TS types. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(routing): MeshCore routing by channel name, not index MeshCore channels are {name,PSK} (up to 40+ slots, not Meshtastic's 0-7). The send index is a fragile slot position, so store the channel NAME per family and resolve name->slot against the companion's live channel table at send time; never blind-send to an unresolved slot. GUI field becomes a channel-name text box. meshtastic path unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(routing): thread per-family meshcore_channel through the broadcast send path MeshBroadcastChannel now carries the rule's meshcore_channel name and passes it to send_message, so per-family MeshCore routing actually fires end-to-end (dispatcher -> channel -> composite -> MeshCoreTransport). Meshtastic path unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Matt Johnson <mj@k7zvx.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 16:49:59 -06:00
)
channel = create_channel(rule, mock_connector)
feat(routing): MeshCore as first-class delivery types (meshcore_broadcast/dm) (#11) * feat(routing): MeshCore as first-class delivery types (meshcore_broadcast/dm) Replace the composite auto-fan with explicit per-mesh delivery types so each family independently controls broadcast/DM per severity on Meshtastic AND MeshCore. mesh_broadcast->Meshtastic only, meshcore_broadcast->MeshCore (by channel name), mesh_dm/meshcore_dm likewise; routing via the existing transport hint. Adds meshcore_dm_contacts. Meshtastic-only configs unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(routing): deliver meshcore_broadcast via meshcore_channel through CompositeTransport The hinted _broadcast path passed the channel NAME on the `channel` kwarg, which MeshCoreTransport ignores (it reads meshcore_channel), so meshcore_broadcast silently no-op'd on transport=both configs. Route the meshcore child via meshcore_channel and the meshtastic child via channel. Fix the test that asserted the broken kwarg layout. Add the new delivery types to the remaining enumeration/validation sites (channel-test endpoint, scheduler digest chunking, danger-zone valid set). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(dashboard): split Notifications into Meshtastic and MeshCore sections Delineate per-mesh routing: each family configures Meshtastic delivery (mesh_broadcast/mesh_dm, channel index, node IDs) and MeshCore delivery (meshcore_broadcast/meshcore_dm, channel name, contacts) in separate sections; shared settings (enable/severity/regions/email/webhook) once. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(dashboard): first-class MeshCore nav section + dedicated pages Group the sidebar into Meshtastic and MeshCore sections. Promote MeshCore routing and connection to their own pages; move MeshCore routing out of Notifications (which stays Meshtastic + shared family settings). Add placeholder Contacts and Companion pages for the follow-on companion data API. No backend change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(dashboard): parallel MT/MC nav order + symmetric connection links Order both nav groups Connection/Routing/Mesh(Contacts)/Sources(Companion). Replace the prominent MeshCore block on the Meshtastic Connection page with a single subtle cross-link, mirrored on the MeshCore Connection page. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(dashboard): focused Meshtastic Connection/Sources pages for MT/MC parity Meshtastic Connection and Sources are now their own focused pages (mirroring MeshCore), instead of deep-linking into the full Config page. Global settings move to a restored top-level Config item. No duplicate editors; connection cross-links are mirror-image between the two pages. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Matt Johnson <mj@k7zvx.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 22:42:14 -06:00
assert isinstance(channel, MeshCoreBroadcastChannel)
feat(dashboard): MeshCore transport + per-family routing GUI controls (#10) * feat(dashboard): MeshCore transport + per-family routing GUI controls Add Transport mode selector (Meshtastic/MeshCore/Both) and MeshCore host/port fields to the Config Connection section, and an independent per-family "MeshCore channel" number input in Notifications (blank = not broadcast on MeshCore, sends null). Extends the ConnectionConfig and per-family toggle TS types. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(routing): MeshCore routing by channel name, not index MeshCore channels are {name,PSK} (up to 40+ slots, not Meshtastic's 0-7). The send index is a fragile slot position, so store the channel NAME per family and resolve name->slot against the companion's live channel table at send time; never blind-send to an unresolved slot. GUI field becomes a channel-name text box. meshtastic path unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(routing): thread per-family meshcore_channel through the broadcast send path MeshBroadcastChannel now carries the rule's meshcore_channel name and passes it to send_message, so per-family MeshCore routing actually fires end-to-end (dispatcher -> channel -> composite -> MeshCoreTransport). Meshtastic path unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Matt Johnson <mj@k7zvx.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 16:49:59 -06:00
payload = NotificationPayload(
feat(routing): MeshCore as first-class delivery types (meshcore_broadcast/dm) (#11) * feat(routing): MeshCore as first-class delivery types (meshcore_broadcast/dm) Replace the composite auto-fan with explicit per-mesh delivery types so each family independently controls broadcast/DM per severity on Meshtastic AND MeshCore. mesh_broadcast->Meshtastic only, meshcore_broadcast->MeshCore (by channel name), mesh_dm/meshcore_dm likewise; routing via the existing transport hint. Adds meshcore_dm_contacts. Meshtastic-only configs unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(routing): deliver meshcore_broadcast via meshcore_channel through CompositeTransport The hinted _broadcast path passed the channel NAME on the `channel` kwarg, which MeshCoreTransport ignores (it reads meshcore_channel), so meshcore_broadcast silently no-op'd on transport=both configs. Route the meshcore child via meshcore_channel and the meshtastic child via channel. Fix the test that asserted the broken kwarg layout. Add the new delivery types to the remaining enumeration/validation sites (channel-test endpoint, scheduler digest chunking, danger-zone valid set). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(dashboard): split Notifications into Meshtastic and MeshCore sections Delineate per-mesh routing: each family configures Meshtastic delivery (mesh_broadcast/mesh_dm, channel index, node IDs) and MeshCore delivery (meshcore_broadcast/meshcore_dm, channel name, contacts) in separate sections; shared settings (enable/severity/regions/email/webhook) once. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(dashboard): first-class MeshCore nav section + dedicated pages Group the sidebar into Meshtastic and MeshCore sections. Promote MeshCore routing and connection to their own pages; move MeshCore routing out of Notifications (which stays Meshtastic + shared family settings). Add placeholder Contacts and Companion pages for the follow-on companion data API. No backend change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(dashboard): parallel MT/MC nav order + symmetric connection links Order both nav groups Connection/Routing/Mesh(Contacts)/Sources(Companion). Replace the prominent MeshCore block on the Meshtastic Connection page with a single subtle cross-link, mirrored on the MeshCore Connection page. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(dashboard): focused Meshtastic Connection/Sources pages for MT/MC parity Meshtastic Connection and Sources are now their own focused pages (mirroring MeshCore), instead of deep-linking into the full Config page. Global settings move to a restored top-level Config item. No duplicate editors; connection cross-links are mirror-image between the two pages. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Matt Johnson <mj@k7zvx.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 22:42:14 -06:00
message="fire alert",
category="fire",
severity="immediate",
feat(dashboard): MeshCore transport + per-family routing GUI controls (#10) * feat(dashboard): MeshCore transport + per-family routing GUI controls Add Transport mode selector (Meshtastic/MeshCore/Both) and MeshCore host/port fields to the Config Connection section, and an independent per-family "MeshCore channel" number input in Notifications (blank = not broadcast on MeshCore, sends null). Extends the ConnectionConfig and per-family toggle TS types. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(routing): MeshCore routing by channel name, not index MeshCore channels are {name,PSK} (up to 40+ slots, not Meshtastic's 0-7). The send index is a fragile slot position, so store the channel NAME per family and resolve name->slot against the companion's live channel table at send time; never blind-send to an unresolved slot. GUI field becomes a channel-name text box. meshtastic path unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(routing): thread per-family meshcore_channel through the broadcast send path MeshBroadcastChannel now carries the rule's meshcore_channel name and passes it to send_message, so per-family MeshCore routing actually fires end-to-end (dispatcher -> channel -> composite -> MeshCoreTransport). Meshtastic path unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Matt Johnson <mj@k7zvx.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 16:49:59 -06:00
timestamp=time.time(),
feat(routing): MeshCore as first-class delivery types (meshcore_broadcast/dm) (#11) * feat(routing): MeshCore as first-class delivery types (meshcore_broadcast/dm) Replace the composite auto-fan with explicit per-mesh delivery types so each family independently controls broadcast/DM per severity on Meshtastic AND MeshCore. mesh_broadcast->Meshtastic only, meshcore_broadcast->MeshCore (by channel name), mesh_dm/meshcore_dm likewise; routing via the existing transport hint. Adds meshcore_dm_contacts. Meshtastic-only configs unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(routing): deliver meshcore_broadcast via meshcore_channel through CompositeTransport The hinted _broadcast path passed the channel NAME on the `channel` kwarg, which MeshCoreTransport ignores (it reads meshcore_channel), so meshcore_broadcast silently no-op'd on transport=both configs. Route the meshcore child via meshcore_channel and the meshtastic child via channel. Fix the test that asserted the broken kwarg layout. Add the new delivery types to the remaining enumeration/validation sites (channel-test endpoint, scheduler digest chunking, danger-zone valid set). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(dashboard): split Notifications into Meshtastic and MeshCore sections Delineate per-mesh routing: each family configures Meshtastic delivery (mesh_broadcast/mesh_dm, channel index, node IDs) and MeshCore delivery (meshcore_broadcast/meshcore_dm, channel name, contacts) in separate sections; shared settings (enable/severity/regions/email/webhook) once. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(dashboard): first-class MeshCore nav section + dedicated pages Group the sidebar into Meshtastic and MeshCore sections. Promote MeshCore routing and connection to their own pages; move MeshCore routing out of Notifications (which stays Meshtastic + shared family settings). Add placeholder Contacts and Companion pages for the follow-on companion data API. No backend change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(dashboard): parallel MT/MC nav order + symmetric connection links Order both nav groups Connection/Routing/Mesh(Contacts)/Sources(Companion). Replace the prominent MeshCore block on the Meshtastic Connection page with a single subtle cross-link, mirrored on the MeshCore Connection page. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(dashboard): focused Meshtastic Connection/Sources pages for MT/MC parity Meshtastic Connection and Sources are now their own focused pages (mirroring MeshCore), instead of deep-linking into the full Config page. Global settings move to a restored top-level Config item. No duplicate editors; connection cross-links are mirror-image between the two pages. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Matt Johnson <mj@k7zvx.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 22:42:14 -06:00
event_type="fire",
feat(dashboard): MeshCore transport + per-family routing GUI controls (#10) * feat(dashboard): MeshCore transport + per-family routing GUI controls Add Transport mode selector (Meshtastic/MeshCore/Both) and MeshCore host/port fields to the Config Connection section, and an independent per-family "MeshCore channel" number input in Notifications (blank = not broadcast on MeshCore, sends null). Extends the ConnectionConfig and per-family toggle TS types. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(routing): MeshCore routing by channel name, not index MeshCore channels are {name,PSK} (up to 40+ slots, not Meshtastic's 0-7). The send index is a fragile slot position, so store the channel NAME per family and resolve name->slot against the companion's live channel table at send time; never blind-send to an unresolved slot. GUI field becomes a channel-name text box. meshtastic path unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(routing): thread per-family meshcore_channel through the broadcast send path MeshBroadcastChannel now carries the rule's meshcore_channel name and passes it to send_message, so per-family MeshCore routing actually fires end-to-end (dispatcher -> channel -> composite -> MeshCoreTransport). Meshtastic path unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Matt Johnson <mj@k7zvx.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 16:49:59 -06:00
chunk_index=0,
)
assert asyncio.run(channel.deliver(payload, rule)) is True
feat(routing): MeshCore as first-class delivery types (meshcore_broadcast/dm) (#11) * feat(routing): MeshCore as first-class delivery types (meshcore_broadcast/dm) Replace the composite auto-fan with explicit per-mesh delivery types so each family independently controls broadcast/DM per severity on Meshtastic AND MeshCore. mesh_broadcast->Meshtastic only, meshcore_broadcast->MeshCore (by channel name), mesh_dm/meshcore_dm likewise; routing via the existing transport hint. Adds meshcore_dm_contacts. Meshtastic-only configs unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(routing): deliver meshcore_broadcast via meshcore_channel through CompositeTransport The hinted _broadcast path passed the channel NAME on the `channel` kwarg, which MeshCoreTransport ignores (it reads meshcore_channel), so meshcore_broadcast silently no-op'd on transport=both configs. Route the meshcore child via meshcore_channel and the meshtastic child via channel. Fix the test that asserted the broken kwarg layout. Add the new delivery types to the remaining enumeration/validation sites (channel-test endpoint, scheduler digest chunking, danger-zone valid set). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(dashboard): split Notifications into Meshtastic and MeshCore sections Delineate per-mesh routing: each family configures Meshtastic delivery (mesh_broadcast/mesh_dm, channel index, node IDs) and MeshCore delivery (meshcore_broadcast/meshcore_dm, channel name, contacts) in separate sections; shared settings (enable/severity/regions/email/webhook) once. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(dashboard): first-class MeshCore nav section + dedicated pages Group the sidebar into Meshtastic and MeshCore sections. Promote MeshCore routing and connection to their own pages; move MeshCore routing out of Notifications (which stays Meshtastic + shared family settings). Add placeholder Contacts and Companion pages for the follow-on companion data API. No backend change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(dashboard): parallel MT/MC nav order + symmetric connection links Order both nav groups Connection/Routing/Mesh(Contacts)/Sources(Companion). Replace the prominent MeshCore block on the Meshtastic Connection page with a single subtle cross-link, mirrored on the MeshCore Connection page. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(dashboard): focused Meshtastic Connection/Sources pages for MT/MC parity Meshtastic Connection and Sources are now their own focused pages (mirroring MeshCore), instead of deep-linking into the full Config page. Global settings move to a restored top-level Config item. No duplicate editors; connection cross-links are mirror-image between the two pages. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Matt Johnson <mj@k7zvx.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 22:42:14 -06:00
mock_connector.send_message.assert_called_once()
feat(dashboard): MeshCore transport + per-family routing GUI controls (#10) * feat(dashboard): MeshCore transport + per-family routing GUI controls Add Transport mode selector (Meshtastic/MeshCore/Both) and MeshCore host/port fields to the Config Connection section, and an independent per-family "MeshCore channel" number input in Notifications (blank = not broadcast on MeshCore, sends null). Extends the ConnectionConfig and per-family toggle TS types. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(routing): MeshCore routing by channel name, not index MeshCore channels are {name,PSK} (up to 40+ slots, not Meshtastic's 0-7). The send index is a fragile slot position, so store the channel NAME per family and resolve name->slot against the companion's live channel table at send time; never blind-send to an unresolved slot. GUI field becomes a channel-name text box. meshtastic path unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(routing): thread per-family meshcore_channel through the broadcast send path MeshBroadcastChannel now carries the rule's meshcore_channel name and passes it to send_message, so per-family MeshCore routing actually fires end-to-end (dispatcher -> channel -> composite -> MeshCoreTransport). Meshtastic path unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Matt Johnson <mj@k7zvx.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 16:49:59 -06:00
kwargs = mock_connector.send_message.call_args.kwargs
feat(routing): MeshCore as first-class delivery types (meshcore_broadcast/dm) (#11) * feat(routing): MeshCore as first-class delivery types (meshcore_broadcast/dm) Replace the composite auto-fan with explicit per-mesh delivery types so each family independently controls broadcast/DM per severity on Meshtastic AND MeshCore. mesh_broadcast->Meshtastic only, meshcore_broadcast->MeshCore (by channel name), mesh_dm/meshcore_dm likewise; routing via the existing transport hint. Adds meshcore_dm_contacts. Meshtastic-only configs unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(routing): deliver meshcore_broadcast via meshcore_channel through CompositeTransport The hinted _broadcast path passed the channel NAME on the `channel` kwarg, which MeshCoreTransport ignores (it reads meshcore_channel), so meshcore_broadcast silently no-op'd on transport=both configs. Route the meshcore child via meshcore_channel and the meshtastic child via channel. Fix the test that asserted the broken kwarg layout. Add the new delivery types to the remaining enumeration/validation sites (channel-test endpoint, scheduler digest chunking, danger-zone valid set). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(dashboard): split Notifications into Meshtastic and MeshCore sections Delineate per-mesh routing: each family configures Meshtastic delivery (mesh_broadcast/mesh_dm, channel index, node IDs) and MeshCore delivery (meshcore_broadcast/meshcore_dm, channel name, contacts) in separate sections; shared settings (enable/severity/regions/email/webhook) once. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(dashboard): first-class MeshCore nav section + dedicated pages Group the sidebar into Meshtastic and MeshCore sections. Promote MeshCore routing and connection to their own pages; move MeshCore routing out of Notifications (which stays Meshtastic + shared family settings). Add placeholder Contacts and Companion pages for the follow-on companion data API. No backend change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(dashboard): parallel MT/MC nav order + symmetric connection links Order both nav groups Connection/Routing/Mesh(Contacts)/Sources(Companion). Replace the prominent MeshCore block on the Meshtastic Connection page with a single subtle cross-link, mirrored on the MeshCore Connection page. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(dashboard): focused Meshtastic Connection/Sources pages for MT/MC parity Meshtastic Connection and Sources are now their own focused pages (mirroring MeshCore), instead of deep-linking into the full Config page. Global settings move to a restored top-level Config item. No duplicate editors; connection cross-links are mirror-image between the two pages. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Matt Johnson <mj@k7zvx.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 22:42:14 -06:00
assert kwargs.get("meshcore_channel") == "AIDA"
assert kwargs.get("transport") == "meshcore"
assert kwargs.get("destination") is None
feat(dashboard): MeshCore transport + per-family routing GUI controls (#10) * feat(dashboard): MeshCore transport + per-family routing GUI controls Add Transport mode selector (Meshtastic/MeshCore/Both) and MeshCore host/port fields to the Config Connection section, and an independent per-family "MeshCore channel" number input in Notifications (blank = not broadcast on MeshCore, sends null). Extends the ConnectionConfig and per-family toggle TS types. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(routing): MeshCore routing by channel name, not index MeshCore channels are {name,PSK} (up to 40+ slots, not Meshtastic's 0-7). The send index is a fragile slot position, so store the channel NAME per family and resolve name->slot against the companion's live channel table at send time; never blind-send to an unresolved slot. GUI field becomes a channel-name text box. meshtastic path unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(routing): thread per-family meshcore_channel through the broadcast send path MeshBroadcastChannel now carries the rule's meshcore_channel name and passes it to send_message, so per-family MeshCore routing actually fires end-to-end (dispatcher -> channel -> composite -> MeshCoreTransport). Meshtastic path unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Matt Johnson <mj@k7zvx.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 16:49:59 -06:00
def test_broadcast_render_loop_threads_meshcore_channel():
feat(routing): MeshCore as first-class delivery types (meshcore_broadcast/dm) (#11) * feat(routing): MeshCore as first-class delivery types (meshcore_broadcast/dm) Replace the composite auto-fan with explicit per-mesh delivery types so each family independently controls broadcast/DM per severity on Meshtastic AND MeshCore. mesh_broadcast->Meshtastic only, meshcore_broadcast->MeshCore (by channel name), mesh_dm/meshcore_dm likewise; routing via the existing transport hint. Adds meshcore_dm_contacts. Meshtastic-only configs unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(routing): deliver meshcore_broadcast via meshcore_channel through CompositeTransport The hinted _broadcast path passed the channel NAME on the `channel` kwarg, which MeshCoreTransport ignores (it reads meshcore_channel), so meshcore_broadcast silently no-op'd on transport=both configs. Route the meshcore child via meshcore_channel and the meshtastic child via channel. Fix the test that asserted the broken kwarg layout. Add the new delivery types to the remaining enumeration/validation sites (channel-test endpoint, scheduler digest chunking, danger-zone valid set). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(dashboard): split Notifications into Meshtastic and MeshCore sections Delineate per-mesh routing: each family configures Meshtastic delivery (mesh_broadcast/mesh_dm, channel index, node IDs) and MeshCore delivery (meshcore_broadcast/meshcore_dm, channel name, contacts) in separate sections; shared settings (enable/severity/regions/email/webhook) once. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(dashboard): first-class MeshCore nav section + dedicated pages Group the sidebar into Meshtastic and MeshCore sections. Promote MeshCore routing and connection to their own pages; move MeshCore routing out of Notifications (which stays Meshtastic + shared family settings). Add placeholder Contacts and Companion pages for the follow-on companion data API. No backend change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(dashboard): parallel MT/MC nav order + symmetric connection links Order both nav groups Connection/Routing/Mesh(Contacts)/Sources(Companion). Replace the prominent MeshCore block on the Meshtastic Connection page with a single subtle cross-link, mirrored on the MeshCore Connection page. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(dashboard): focused Meshtastic Connection/Sources pages for MT/MC parity Meshtastic Connection and Sources are now their own focused pages (mirroring MeshCore), instead of deep-linking into the full Config page. Global settings move to a restored top-level Config item. No duplicate editors; connection cross-links are mirror-image between the two pages. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Matt Johnson <mj@k7zvx.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 22:42:14 -06:00
"""Non-prechunked path (renderer loop) for meshcore_broadcast threads
meshcore_channel on every chunk send."""
feat(dashboard): MeshCore transport + per-family routing GUI controls (#10) * feat(dashboard): MeshCore transport + per-family routing GUI controls Add Transport mode selector (Meshtastic/MeshCore/Both) and MeshCore host/port fields to the Config Connection section, and an independent per-family "MeshCore channel" number input in Notifications (blank = not broadcast on MeshCore, sends null). Extends the ConnectionConfig and per-family toggle TS types. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(routing): MeshCore routing by channel name, not index MeshCore channels are {name,PSK} (up to 40+ slots, not Meshtastic's 0-7). The send index is a fragile slot position, so store the channel NAME per family and resolve name->slot against the companion's live channel table at send time; never blind-send to an unresolved slot. GUI field becomes a channel-name text box. meshtastic path unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(routing): thread per-family meshcore_channel through the broadcast send path MeshBroadcastChannel now carries the rule's meshcore_channel name and passes it to send_message, so per-family MeshCore routing actually fires end-to-end (dispatcher -> channel -> composite -> MeshCoreTransport). Meshtastic path unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Matt Johnson <mj@k7zvx.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 16:49:59 -06:00
from meshai.config import NotificationRuleConfig
from meshai.notifications.channels import create_channel
mock_connector = MagicMock()
feat(routing): MeshCore as first-class delivery types (meshcore_broadcast/dm) (#11) * feat(routing): MeshCore as first-class delivery types (meshcore_broadcast/dm) Replace the composite auto-fan with explicit per-mesh delivery types so each family independently controls broadcast/DM per severity on Meshtastic AND MeshCore. mesh_broadcast->Meshtastic only, meshcore_broadcast->MeshCore (by channel name), mesh_dm/meshcore_dm likewise; routing via the existing transport hint. Adds meshcore_dm_contacts. Meshtastic-only configs unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(routing): deliver meshcore_broadcast via meshcore_channel through CompositeTransport The hinted _broadcast path passed the channel NAME on the `channel` kwarg, which MeshCoreTransport ignores (it reads meshcore_channel), so meshcore_broadcast silently no-op'd on transport=both configs. Route the meshcore child via meshcore_channel and the meshtastic child via channel. Fix the test that asserted the broken kwarg layout. Add the new delivery types to the remaining enumeration/validation sites (channel-test endpoint, scheduler digest chunking, danger-zone valid set). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(dashboard): split Notifications into Meshtastic and MeshCore sections Delineate per-mesh routing: each family configures Meshtastic delivery (mesh_broadcast/mesh_dm, channel index, node IDs) and MeshCore delivery (meshcore_broadcast/meshcore_dm, channel name, contacts) in separate sections; shared settings (enable/severity/regions/email/webhook) once. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(dashboard): first-class MeshCore nav section + dedicated pages Group the sidebar into Meshtastic and MeshCore sections. Promote MeshCore routing and connection to their own pages; move MeshCore routing out of Notifications (which stays Meshtastic + shared family settings). Add placeholder Contacts and Companion pages for the follow-on companion data API. No backend change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(dashboard): parallel MT/MC nav order + symmetric connection links Order both nav groups Connection/Routing/Mesh(Contacts)/Sources(Companion). Replace the prominent MeshCore block on the Meshtastic Connection page with a single subtle cross-link, mirrored on the MeshCore Connection page. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(dashboard): focused Meshtastic Connection/Sources pages for MT/MC parity Meshtastic Connection and Sources are now their own focused pages (mirroring MeshCore), instead of deep-linking into the full Config page. Global settings move to a restored top-level Config item. No duplicate editors; connection cross-links are mirror-image between the two pages. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Matt Johnson <mj@k7zvx.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 22:42:14 -06:00
# Connector has a meshcore child so the no-op guard passes.
mock_connector._by_name = {"meshcore": MagicMock(), "meshtastic": MagicMock()}
mock_connector.send_message.return_value = True
feat(dashboard): MeshCore transport + per-family routing GUI controls (#10) * feat(dashboard): MeshCore transport + per-family routing GUI controls Add Transport mode selector (Meshtastic/MeshCore/Both) and MeshCore host/port fields to the Config Connection section, and an independent per-family "MeshCore channel" number input in Notifications (blank = not broadcast on MeshCore, sends null). Extends the ConnectionConfig and per-family toggle TS types. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(routing): MeshCore routing by channel name, not index MeshCore channels are {name,PSK} (up to 40+ slots, not Meshtastic's 0-7). The send index is a fragile slot position, so store the channel NAME per family and resolve name->slot against the companion's live channel table at send time; never blind-send to an unresolved slot. GUI field becomes a channel-name text box. meshtastic path unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(routing): thread per-family meshcore_channel through the broadcast send path MeshBroadcastChannel now carries the rule's meshcore_channel name and passes it to send_message, so per-family MeshCore routing actually fires end-to-end (dispatcher -> channel -> composite -> MeshCoreTransport). Meshtastic path unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Matt Johnson <mj@k7zvx.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 16:49:59 -06:00
rule = NotificationRuleConfig(
name="toggle:fire",
feat(routing): MeshCore as first-class delivery types (meshcore_broadcast/dm) (#11) * feat(routing): MeshCore as first-class delivery types (meshcore_broadcast/dm) Replace the composite auto-fan with explicit per-mesh delivery types so each family independently controls broadcast/DM per severity on Meshtastic AND MeshCore. mesh_broadcast->Meshtastic only, meshcore_broadcast->MeshCore (by channel name), mesh_dm/meshcore_dm likewise; routing via the existing transport hint. Adds meshcore_dm_contacts. Meshtastic-only configs unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(routing): deliver meshcore_broadcast via meshcore_channel through CompositeTransport The hinted _broadcast path passed the channel NAME on the `channel` kwarg, which MeshCoreTransport ignores (it reads meshcore_channel), so meshcore_broadcast silently no-op'd on transport=both configs. Route the meshcore child via meshcore_channel and the meshtastic child via channel. Fix the test that asserted the broken kwarg layout. Add the new delivery types to the remaining enumeration/validation sites (channel-test endpoint, scheduler digest chunking, danger-zone valid set). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(dashboard): split Notifications into Meshtastic and MeshCore sections Delineate per-mesh routing: each family configures Meshtastic delivery (mesh_broadcast/mesh_dm, channel index, node IDs) and MeshCore delivery (meshcore_broadcast/meshcore_dm, channel name, contacts) in separate sections; shared settings (enable/severity/regions/email/webhook) once. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(dashboard): first-class MeshCore nav section + dedicated pages Group the sidebar into Meshtastic and MeshCore sections. Promote MeshCore routing and connection to their own pages; move MeshCore routing out of Notifications (which stays Meshtastic + shared family settings). Add placeholder Contacts and Companion pages for the follow-on companion data API. No backend change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(dashboard): parallel MT/MC nav order + symmetric connection links Order both nav groups Connection/Routing/Mesh(Contacts)/Sources(Companion). Replace the prominent MeshCore block on the Meshtastic Connection page with a single subtle cross-link, mirrored on the MeshCore Connection page. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(dashboard): focused Meshtastic Connection/Sources pages for MT/MC parity Meshtastic Connection and Sources are now their own focused pages (mirroring MeshCore), instead of deep-linking into the full Config page. Global settings move to a restored top-level Config item. No duplicate editors; connection cross-links are mirror-image between the two pages. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Matt Johnson <mj@k7zvx.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 22:42:14 -06:00
delivery_type="meshcore_broadcast",
feat(dashboard): MeshCore transport + per-family routing GUI controls (#10) * feat(dashboard): MeshCore transport + per-family routing GUI controls Add Transport mode selector (Meshtastic/MeshCore/Both) and MeshCore host/port fields to the Config Connection section, and an independent per-family "MeshCore channel" number input in Notifications (blank = not broadcast on MeshCore, sends null). Extends the ConnectionConfig and per-family toggle TS types. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(routing): MeshCore routing by channel name, not index MeshCore channels are {name,PSK} (up to 40+ slots, not Meshtastic's 0-7). The send index is a fragile slot position, so store the channel NAME per family and resolve name->slot against the companion's live channel table at send time; never blind-send to an unresolved slot. GUI field becomes a channel-name text box. meshtastic path unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(routing): thread per-family meshcore_channel through the broadcast send path MeshBroadcastChannel now carries the rule's meshcore_channel name and passes it to send_message, so per-family MeshCore routing actually fires end-to-end (dispatcher -> channel -> composite -> MeshCoreTransport). Meshtastic path unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Matt Johnson <mj@k7zvx.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 16:49:59 -06:00
meshcore_channel="AIDA",
)
channel = create_channel(rule, mock_connector)
long_message = "This is a very long alert message that exceeds the limit. " * 5
payload = NotificationPayload(
message=long_message,
category="fire",
severity="immediate",
timestamp=time.time(),
event_type="fire",
)
assert asyncio.run(channel.deliver(payload, rule)) is True
assert mock_connector.send_message.call_count >= 2
for call in mock_connector.send_message.call_args_list:
assert call.kwargs.get("meshcore_channel") == "AIDA"
feat(routing): MeshCore as first-class delivery types (meshcore_broadcast/dm) (#11) * feat(routing): MeshCore as first-class delivery types (meshcore_broadcast/dm) Replace the composite auto-fan with explicit per-mesh delivery types so each family independently controls broadcast/DM per severity on Meshtastic AND MeshCore. mesh_broadcast->Meshtastic only, meshcore_broadcast->MeshCore (by channel name), mesh_dm/meshcore_dm likewise; routing via the existing transport hint. Adds meshcore_dm_contacts. Meshtastic-only configs unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(routing): deliver meshcore_broadcast via meshcore_channel through CompositeTransport The hinted _broadcast path passed the channel NAME on the `channel` kwarg, which MeshCoreTransport ignores (it reads meshcore_channel), so meshcore_broadcast silently no-op'd on transport=both configs. Route the meshcore child via meshcore_channel and the meshtastic child via channel. Fix the test that asserted the broken kwarg layout. Add the new delivery types to the remaining enumeration/validation sites (channel-test endpoint, scheduler digest chunking, danger-zone valid set). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(dashboard): split Notifications into Meshtastic and MeshCore sections Delineate per-mesh routing: each family configures Meshtastic delivery (mesh_broadcast/mesh_dm, channel index, node IDs) and MeshCore delivery (meshcore_broadcast/meshcore_dm, channel name, contacts) in separate sections; shared settings (enable/severity/regions/email/webhook) once. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(dashboard): first-class MeshCore nav section + dedicated pages Group the sidebar into Meshtastic and MeshCore sections. Promote MeshCore routing and connection to their own pages; move MeshCore routing out of Notifications (which stays Meshtastic + shared family settings). Add placeholder Contacts and Companion pages for the follow-on companion data API. No backend change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(dashboard): parallel MT/MC nav order + symmetric connection links Order both nav groups Connection/Routing/Mesh(Contacts)/Sources(Companion). Replace the prominent MeshCore block on the Meshtastic Connection page with a single subtle cross-link, mirrored on the MeshCore Connection page. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(dashboard): focused Meshtastic Connection/Sources pages for MT/MC parity Meshtastic Connection and Sources are now their own focused pages (mirroring MeshCore), instead of deep-linking into the full Config page. Global settings move to a restored top-level Config item. No duplicate editors; connection cross-links are mirror-image between the two pages. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Matt Johnson <mj@k7zvx.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 22:42:14 -06:00
assert call.kwargs.get("transport") == "meshcore"