Every other defstruct in the app already has a @type t declaration;
these two GenServer states were the last holdouts. Declaring t here
means the whole codebase is ready for the typed-struct milestone of
Elixir's set-theoretic type system (the next phase after the inference
pass already shipping in 1.19).
Turned on :error_handling, :underspecs, and :unmatched_returns in
mix.exs dialyzer config. The 97 warnings this surfaced were fixed in
place rather than suppressed:
- unmatched_return (79): explicit discard with `_ = ...` for
fire-and-forget side effects (Process.cancel_timer, :ets.new,
send/2), and pattern-matched `:ok = ...` for control-plane
Phoenix.PubSub subscribe/unsubscribe/broadcast calls so a future
return-shape change fails loud.
- contract_supertype (18): tightened @spec arg and return types on
data_builder, historical_loader, url_params, packet_utils,
encoding_utils, aprs_symbol, weather_controller, packet_replay to
match each function's actual success typing.
No behavioural change. mix compile clean, 1008 tests pass, dialyzer
count is now 0.
ConnectionMonitor.gather_cluster_stats was calling :rpc.call(Node.self())
which GenServer.call'd back into itself while blocked in handle_info,
causing a 5-second deadlock timeout every 30 seconds. Fixed by computing
local stats directly from state.
LeaderElection never verified that a leader still held the :global
registration. After :global conflict resolution (two isolated nodes
merging), the loser was silently unregistered but stayed is_leader: true
forever, causing both pods to connect to APRS-IS with the same callsign
in a reconnect loop. Fixed by adding verify_leadership/1 to
check_leadership which detects lost registrations and steps down.
- Add missing env vars to migration init container (PHX_HOST, PORT,
APRS_PORT, APRS_CALLSIGN, APRS_PASSCODE) so runtime.exs config
provider doesn't fail due to Kubernetes service discovery injecting
APRS_PORT as a full URL
- Guard ConnectionMonitor.accepting_connections?/0 with Process.whereis
to avoid crashing when the GenServer isn't started (e.g. cluster
enabled but no libcluster topologies configured)
Extract nested logic into helper functions to improve readability:
- regex_cache.ex: Extract compile_and_cache/1 for regex compilation and caching
- connection_monitor.ex: Extract calculate_scheduler_utilization/1 for CPU stat calculation
- device_identification.ex: Extract fetch_devices_from_url/0 for HTTP request handling
Co-Authored-By: Graham <noreply@anthropic.com>
- Fix leader election guard test for :undefined pid
- Fix cpu_sup.util() pattern matching to handle numeric return
- Fix encoding_utils to always return explicit nil when needed
- Remove unused finite_float? function
- Fix signal handler to match :ok return from :os.set_signal/2
- Remove redundant catch-all pattern in database metrics
- Fix Gridsquare pattern matching in info_live template
- Remove unnecessary nil check for calculate_course result
- Fix get_packet_received_at to not check for nil (always returns DateTime)
- Remove redundant catch-all pattern in weather format_weather_value
- Fix query builder to use from(p in Packet) instead of bare Packet atom
Reduced dialyzer errors from 49 to 6. Remaining warnings are mostly
false positives from template compilation and overloaded function specs.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
Implements automatic connection draining to balance load across cluster nodes:
- ConnectionMonitor tracks CPU usage and connection counts across nodes
- Triggers draining when node is overloaded (>70% CPU or 2x avg connections)
- LiveViews gracefully disconnect and reconnect to less loaded nodes
- Helps prevent single pod from handling all WebSocket connections
Also fixes Mic-E packet parsing to properly extract altitude data and
remove telemetry suffixes from comments.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>