Remove the lab-witness exemption so every hotspot obeys one group QSO per RF
slot. Expire same-TG zombie peer_voice_slots after STREAM_TO, allow orphan
VTERM through the slot gate, and route ingress slot tracking via
track_peer_group_dmrd for consistent session lifecycle.
Enforce one QSO per RF slot for normal hotspots, clear peer_voice_slots
on disconnect, allow matching VTERM through slot gates, exempt lab
witnesses with many static TGs, and log ingress TG-busy drops once per stream.
Introduce downlink.py as the single authority for hotspot eligibility (OPTIONS,
slot busy, post-TX GROUP_HANGTIME). send_peer and BRDG fan-out share the same
gate; monitor events carry stream id from BRDG so new QSOs after hangtime
display without replaying calls blocked during the hangtime window.
Planned release: 2.2.0
* feat: persist peer dynamic TGs in MariaDB across reconnects
Add DATABASE config, async DynamicTgStore, and restore on RPTC so
SINGLE=0/1 dynamics survive hotspot disconnects and server restarts
without blocking the DMRD voice path.
* fix: ensure peer_dynamic_tgs table on server startup
Apply migration 004 idempotently at boot so the server does not depend
on adn-monitor db_bootstrap when peer_dynamic_tgs is missing.
* fix: import DynamicTgEntry for ruff F821 in subscription_table
* fix: log clear MariaDB startup failures to file and stderr
Validate DATABASE at config load and on connect; map common MySQL
errors to actionable messages so the server does not fail silently.
* fix: TG 4000 clears STATUS and bridge legs after dynamic reset
Clear RX slot state to stop RPTO re-seeding cleared sessions, run
in-band 4000 deactivation on inject-only paths, and mark downlink dirty.
* fix: complete TG 4000 reset for monitor and dynamic TG persistence
Emit INGRESS BRDG_EVENT so SINGLE=0 UA chips clear without stuck TX;
wipe all peer dynamic rows from memory and MariaDB on reset. Never store
TG 4000 as a UA session. Require DATABASE only for full peer-server configs.
When voice arrives on TS2 but the hotspot lists the TG on TS1 (or vice
versa), flip the slot bit on send_peer so duplex radios RX on the
configured timeslot.
Normalize all Python sources to the standard ADN copyright block with
complete GPLv3 notice. Add legacy attribution on routing and dmr_utils
ports; drop SemVer wording from changelog and fix an unused test import.
Replace internal bridge terminology with routing (RoutingUseCases, AclRouter,
routing_table export). SubscriptionStore remains runtime authority with O(1)
indexes for router and downlink filters. Fix STATIC TG parity on OPTIONS/RPTO,
parrot in-band edge cases, and remove per-packet routing_table export from the
hot path that caused high CPU under multi-hotspot OBP load.
Require subscription_store in BridgeUseCases and route timer, OPTIONS,
static TG, and OBP mutations through store ops with export-only BRIDGES shim.
Fix dashboard YAML static TG fallback and sole-hotspot monitor remap for
dynamic UA when a bridge leg is active.
Move stat_trimmer, bridge_debug, bridge_reset, bridge_table (static TG,
reflectors, UA), and OBP source ensure to mutate SubscriptionStore first
and export to the BRIDGES shim; batch OPTIONS loops export once at the end.
Add InbandTriggers to Subscription with ON/OFF/RESET import/export parity.
apply_in_band_signalling mutates the store directly and exports to the
BRIDGES shim (SINGLE_MODE, TG 4000, reflector # rules unchanged).
Mutate SubscriptionStore in rule_timer_loop and export to the BRIDGES shim
without re-importing stale router state. Voice forward uses resolved ForwardLeg
list instead of scanning dict rows when the store is wired.
Subscription store and router are always active when wired at bootstrap;
drop YAML toggles and flag-off test paths. Sync router into store before
get_bridges() export and after make_static_tg mutations.
Sync subscription store after timer and in-band mutations, add optional
store authority with BRIDGES export shim, wire MeshCodecRegistry into
OBP udp_hbp paths, and extend harness parity tests.
Push STATE_SND when peers send RPTO so monitor chips update without reload.
Fetch and inject MySQL OPTIONS immediately on PASS= instead of a 10s delay.
Track per-peer dynamic TG sets for SINGLE=0 so keyed hotspots hear each other.
Enforce strict SINGLE=1 RX exclusivity, always filter inject-only downlink by
each peer's own OPTIONS, and push self-service DB options only after PASS=.
Stop merged SYSTEM static TG lists from leaking across hotspots in topology.
Add real-stack REPEAT and proxy fan-in E2E tests so regressions outside
DeterministicScenario are caught. Remove duplicate or fragile tests,
consolidate monitor remap cases, and document harness vs integration policy.
Match bridge_tables_with_active_source and to_target forward selection on
subscription rows; add parity tests vs legacy BRIDGES scan including OBP dedup.
Add opt-in REPORTS.MQTT with retained shared state and live voice_event
topics, topology static TG in v2 payloads, dashboard_state builder, and
SIGHUP reload.
Decouple bridge events from TCP send via a reactor-drained queue (50ms, 2048
events). Export peer connected_at in topology so monitor timers survive refresh.
Introduce RuntimeContextHolder with ConfigProxy so main wiring keeps a stable
config view while SIGHUP reload builds a deep copy, applies reload_server_config,
then swaps the pointer only on success.