meshai/work/tests/test_v064_guard_commit.py
malice de91751bbc
chore(central-ripout 2d-i): relocate the fire engine + renderer out of central/ (#164)
Moves the last two live files out of the retired-Central folder. central/ is
now EMPTY and deleted entirely (incl. __init__.py).

- fire-fusion engine (ingest_hotspot_pixel + the growth/cluster/spotting
  engine) → env/fire_fusion.py, next to its sole consumer env/firms.py.
- wildfire text renderer (_render + its live helpers) → env/fire_render.py.

THE COUPLING RESOLVED: firms_handler._handle_pass_boundary had a LAZY import
inside a function body — `from meshai.central.wfigs_handler import _render` —
sitting on the live FIRMS fire-growth path. It is now a normal top-of-file
import (`from meshai.env.fire_render import _render`), visible and greppable.

PURE MOVE — no behavior change:
- The parity oracle (test_fire_refactor.py) PASSES UNCHANGED (only its import
  paths updated) — proving the fire wire output is byte-for-byte identical
  before and after. Fire alerts say exactly what they said.
- handle_firms / handle_wfigs (dead entrypoints, only caller was the deleted
  consumer.py) were KEPT and moved rather than dropped — the wording-cleanup
  PR removes them deliberately. "When unsure, keep."
- fire_render.py's geo-helper imports still point at central_normalizer — that
  file's split is a SEPARATE PR; carried the imports along, did not touch it.

Consumers + test import paths rewired. Full suite: 2059 passed, 0 failed.

Co-authored-by: Matt Johnson <mj@k7zvx.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 16:31:48 -06:00

177 lines
6.4 KiB
Python

"""v0.6-4 — B13 guard-commit ordering + dedup suffix.
Regression tests for two silent-suppression defects:
1. B13 ordering: cooldown arming and dedup recording used to happen
BEFORE the region filter / severity floor / matrix resolution and
BEFORE delivery. A non-deliverable or failed event therefore burned
guard state and suppressed later deliverable events for up to the
dedup retention window. Now both commit only after >=1 successful
delivery.
2. Dedup suffix: feeds like WFIGS publish the SAME upstream id
(IrwinID) on every sweep, so the bare (source, id) dedup key
permanently swallowed every post-"New" lifecycle broadcast. The
handler now stamps the broadcast-justifying state into
event.data["_dedup_suffix"]; unchanged repeats dedup, updates pass.
"""
import asyncio
import pytest
from meshai.config import Config
from meshai.notifications.pipeline.dispatcher import Dispatcher
from meshai.notifications.events import make_event
class RecChannel:
def __init__(self, rec, succeed=True):
self.rec = rec
self.succeed = succeed
async def deliver(self, payload, rule):
self.rec.append({
"delivery_type": rule.delivery_type,
"message": payload.message,
})
return self.succeed
def _cfg(**kw):
cfg = Config()
cfg.notifications.rules = []
cfg.notifications.cold_start_grace_seconds = 0
t = cfg.notifications.toggles["weather"]
t.enabled = True
t.min_severity = kw.get("min_severity", "routine")
t.regions = kw.get("regions", [])
t.severity_channels = kw.get("severity_channels", {
"routine": ["mesh_broadcast"],
"priority": ["mesh_broadcast"],
"immediate": ["mesh_broadcast"],
})
t.freshness_seconds = 600
t.cooldown_seconds = kw.get("cooldown_seconds", 300)
t.broadcast_channel = 1
return cfg
def _ev(eid="ev-1", severity="priority", **data):
ev = make_event(source="nws", category="weather.alert.severe",
severity=severity, title="t")
ev.id = eid
if data:
ev.data.update(data)
return ev
def _disp(cfg, succeed=True):
rec: list = []
d = Dispatcher(cfg, lambda rule, conn: RecChannel(rec, succeed), connector=None)
return d, rec
# ---------------------------------------------------------------- B13
def test_empty_matrix_row_burns_no_guard_state():
"""Event whose severity row is empty must leave no cooldown/dedup."""
cfg = _cfg(severity_channels={"routine": [], "priority": [],
"immediate": ["mesh_broadcast"]})
d, rec = _disp(cfg)
asyncio.run(d.dispatch(_ev(eid="fire-x", severity="priority")))
assert rec == []
assert len(d._toggle_cooldown) == 0
assert len(d._dedup_lru) == 0
# Operator fixes the matrix -> the SAME event id must now deliver.
cfg.notifications.toggles["weather"].severity_channels["priority"] = \
["mesh_broadcast"]
asyncio.run(d.dispatch(_ev(eid="fire-x", severity="priority")))
assert len(rec) == 1
def test_below_severity_floor_burns_no_guard_state():
cfg = _cfg(min_severity="immediate")
d, rec = _disp(cfg)
asyncio.run(d.dispatch(_ev(eid="ev-floor", severity="priority")))
assert rec == [] and not d._toggle_cooldown and not d._dedup_lru
def test_wrong_region_burns_no_guard_state():
cfg = _cfg(regions=["US-ID"])
ev = _ev(eid="ev-region", severity="priority")
ev.region = "US-MT"
d, rec = _disp(cfg)
asyncio.run(d.dispatch(ev))
assert rec == [] and not d._toggle_cooldown and not d._dedup_lru
def test_failed_delivery_leaves_no_dedup_and_retries():
"""All-channels-failed delivery must not dedup-record: the next
sweep's redelivery of the same id must reach the channel again."""
cfg = _cfg()
d, rec = _disp(cfg, succeed=False)
asyncio.run(d.dispatch(_ev(eid="retry-me", severity="priority")))
assert len(rec) == 1 # attempted
assert not d._dedup_lru # but not recorded
assert not d._toggle_cooldown # and no cooldown armed
# Channel recovers -> same event id delivers.
d._channel_factory = lambda rule, conn: RecChannel(rec, succeed=True)
asyncio.run(d.dispatch(_ev(eid="retry-me", severity="priority")))
assert len(rec) == 2
assert d._dedup_lru # recorded only on success
def test_successful_delivery_commits_cooldown_and_dedup():
cfg = _cfg()
d, rec = _disp(cfg)
asyncio.run(d.dispatch(_ev(eid="ok-1", severity="priority")))
assert len(rec) == 1
assert len(d._toggle_cooldown) == 1
assert ("nws", "ok-1") in d._dedup_lru
# Exact repeat is throttled (cooldown fires first at this severity).
asyncio.run(d.dispatch(_ev(eid="ok-1", severity="priority")))
assert len(rec) == 1
assert d._cooldown_dropped == 1
# With cooldown out of the way, the repeat is dedup-dropped.
cfg.notifications.toggles["weather"].cooldown_seconds = 0
asyncio.run(d.dispatch(_ev(eid="ok-1", severity="priority")))
assert len(rec) == 1
assert d._dedup_dropped == 1
# ---------------------------------------------------------------- suffix
def test_dedup_suffix_lets_updates_pass_and_repeats_dedup():
"""Same upstream id: identical suffix dedups, changed suffix passes —
the WFIGS lifecycle-update fix."""
cfg = _cfg(cooldown_seconds=0)
d, rec = _disp(cfg)
# "New" broadcast: 0.1 acres.
asyncio.run(d.dispatch(
_ev(eid="{IRWIN-1}", severity="immediate", _dedup_suffix="0.1|0")))
assert len(rec) == 1
# Feed re-sweeps, nothing changed: same id + same suffix -> dedup.
asyncio.run(d.dispatch(
_ev(eid="{IRWIN-1}", severity="immediate", _dedup_suffix="0.1|0")))
assert len(rec) == 1
assert d._dedup_dropped == 1
# The fire blows up: same id, NEW suffix -> must broadcast.
asyncio.run(d.dispatch(
_ev(eid="{IRWIN-1}", severity="immediate", _dedup_suffix="9400.0|10")))
assert len(rec) == 2
# And that update's repeat dedups too.
asyncio.run(d.dispatch(
_ev(eid="{IRWIN-1}", severity="immediate", _dedup_suffix="9400.0|10")))
assert len(rec) == 2
def test_wfigs_handler_stamps_dedup_suffix():
from meshai.env.fire_render import _attach_commit_handles
data = {}
_attach_commit_handles(data, irwin_id="{X}", acres=42.0,
contained_pct=15, event_log_row_id=None)
assert data["_dedup_suffix"] == "42.0|15"
assert data["_cooldown_suffix"] == "{X}"