meshai/work/meshai
Matt Johnson 4dabbabaab fix(reminders): pace the reminder roll-call so N fires don't burst
The ReminderScheduler is the third fire-broadcast exit and the only one
that does not pass through the EventBus, so FirePacer (which paces the
Central and native fire-event exits to <=1/60s) never sees it. Its tick
is a roll-call: every eligible row is its own broadcast, dispatched in a
plain `for` loop with no gap. Unpaced, N eligible fires produce N
back-to-back mesh transmissions; the only downstream protection is
RadioSendQueue's ~2.2-2.6s per-transport inter-packet jitter, which
prevents packet collision but still lets a roll-call monopolise the mesh.

Not currently firing in production (every overdue fire is filtered by
terminate_when, so the eligible set is 0) -- this is fire-season
hardening against a latent burst, not a live incident.

Adds `spacing_seconds` (adapter_config, default 60 to match FirePacer)
enforcing a minimum gap between consecutive SUCCESSFUL reminder
deliveries. Deliberately a pure spacing change:

  * WHAT gets broadcast is untouched; nothing is dropped.
  * The ok-gated last_broadcast_at stamp still uses the tick's `now`.
  * A failed dispatch sent no packet, so it does not arm the gap.
  * Rows filtered by terminate_when/render never burn a spacing slot.
  * A lone eligible fire has nothing to pace against -> zero added latency.
  * The wait is interruptible by stop(): a 15-fire roll-call holds
    tick_once() for ~14 min and stop() awaits the tick task, so a plain
    sleep would stall shutdown.

Chose in-loop spacing over routing reminders through FirePacer itself:
reminders re-derive their targets from live DB state every tick and only
clear a row via last_broadcast_at after a confirmed send, so enqueuing
into a 60s-drain FIFO would re-enqueue the same fire on every intervening
tick -- the queue would grow faster than it drains. pacer.py, consumer.py,
store.py and main.py are untouched.

Tests fake the clock end-to-end, so 60s spacing costs the suite nothing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 05:43:10 +00:00
..
adapter_config fix(reminders): pace the reminder roll-call so N fires don't burst 2026-07-12 05:43:10 +00:00
backends refactor: move source tree into work/, multi-stage Docker build, fix satpass 2026-06-16 03:40:31 +00:00
central fix(firms): repair the FIRMS fire-fusion Event contract (issues #117-#119) (#120) 2026-07-11 17:41:40 -06:00
cli chore: remove legacy TUI configurator; surface last config gap in GUI (#45) 2026-07-05 16:21:32 -06:00
commands feat(phase4a): peak_compass end-to-end + repair stale satpass test suite (#35) 2026-07-05 01:06:29 -06:00
dashboard MeshCore: channel provisioning, key sharing, location-based recommendations & transport-aware identity (#113) 2026-07-10 23:30:57 -06:00
data refactor: move source tree into work/, multi-stage Docker build, fix satpass 2026-06-16 03:40:31 +00:00
env fix(fires): wire FirePacer into the native fire broadcast path (#123) 2026-07-11 21:46:13 -06:00
notifications fix(reminders): pace the reminder roll-call so N fires don't burst 2026-07-12 05:43:10 +00:00
persistence feat(context): durable SQLite-backed retention for MeshContext observations (#114) 2026-07-11 02:20:16 -06:00
scripts refactor: move source tree into work/, multi-stage Docker build, fix satpass 2026-06-16 03:40:31 +00:00
sources refactor: move source tree into work/, multi-stage Docker build, fix satpass 2026-06-16 03:40:31 +00:00
transport MeshCore: channel provisioning, key sharing, location-based recommendations & transport-aware identity (#113) 2026-07-10 23:30:57 -06:00
__init__.py refactor: move source tree into work/, multi-stage Docker build, fix satpass 2026-06-16 03:40:31 +00:00
__main__.py refactor: move source tree into work/, multi-stage Docker build, fix satpass 2026-06-16 03:40:31 +00:00
alert_engine.py Phase A — 4-section nav; move Scheduled Broadcasts + Danger Zones off Routing (#15) 2026-07-03 14:57:24 -06:00
central_normalizer.py refactor: move source tree into work/, multi-stage Docker build, fix satpass 2026-06-16 03:40:31 +00:00
chunker.py feat(reply): cap interactive LLM replies to 3 mesh packets (#7) 2026-07-02 16:49:10 -06:00
config.py MeshCore: channel provisioning, key sharing, location-based recommendations & transport-aware identity (#113) 2026-07-10 23:30:57 -06:00
config_loader.py feat(generic): config-driven REST/GeoJSON source adapter (ported from Central) (#78) 2026-07-07 00:20:02 -06:00
connector.py feat(send-queue): jittered pacing (default 2.2-2.6s) per radio instead of fixed 2.0s (#95) 2026-07-08 11:12:28 -06:00
context.py feat(context): durable SQLite-backed retention for MeshContext observations (#114) 2026-07-11 02:20:16 -06:00
coverage.py feat(coverage): widen adapter fetch scope to the enclosing box of coverage areas (#71) 2026-07-06 12:51:54 -06:00
coverage_area.py feat(region-routing): tag satpass events by observer coordinates so they region-route; add satpass to VALID_TOGGLES (#90) 2026-07-07 21:43:49 -06:00
geo.py refactor: move source tree into work/, multi-stage Docker build, fix satpass 2026-06-16 03:40:31 +00:00
history.py refactor: move source tree into work/, multi-stage Docker build, fix satpass 2026-06-16 03:40:31 +00:00
knowledge.py refactor: move source tree into work/, multi-stage Docker build, fix satpass 2026-06-16 03:40:31 +00:00
main.py fix(fires): wire FirePacer into the native fire broadcast path (#123) 2026-07-11 21:46:13 -06:00
memory.py refactor: move source tree into work/, multi-stage Docker build, fix satpass 2026-06-16 03:40:31 +00:00
mesh_data_store.py refactor(danger-zones): drop min_severity + position_max_age; table snow (grayed/inert) 2026-06-21 06:56:22 +00:00
mesh_health.py refactor: move source tree into work/, multi-stage Docker build, fix satpass 2026-06-16 03:40:31 +00:00
mesh_models.py refactor: move source tree into work/, multi-stage Docker build, fix satpass 2026-06-16 03:40:31 +00:00
mesh_reporter.py refactor: move source tree into work/, multi-stage Docker build, fix satpass 2026-06-16 03:40:31 +00:00
mesh_sources.py refactor: move source tree into work/, multi-stage Docker build, fix satpass 2026-06-16 03:40:31 +00:00
meshmonitor.py refactor: move source tree into work/, multi-stage Docker build, fix satpass 2026-06-16 03:40:31 +00:00
responder.py feat(transport): per-radio serialized+paced outbound send queue (#93) 2026-07-08 09:38:08 -06:00
router.py MeshCore: channel provisioning, key sharing, location-based recommendations & transport-aware identity (#113) 2026-07-10 23:30:57 -06:00
secrets_store.py feat(meshcore): route MeshCore cells to room servers (open rooms) (#110) 2026-07-09 22:14:42 -06:00
serial_ports.py fix(serial): detect passed-through /dev USB-serial nodes (container GUI detect) (#53) 2026-07-05 23:01:08 -06:00