meshai/work/tests/test_tombstone_broadcast.py

225 lines
9.9 KiB
Python
Raw Permalink Normal View History

"""Tests for tombstone broadcast path fix.
Validates:
test: fix 13 stale tests in the red suite, leave 6 real-bug failures (#124) (#129) Triaged all 20 known-red tests. 13 were stale tests asserting rotted expectations against deliberate, documented behavior changes; fixed by deriving expected values instead of hard-coding, or updating the expectation to match a documented policy change: - test_adapter_config_foundation.py / test_adapter_config_api.py: REGISTRY/API key-count and key-set guards hard-coded magic numbers (59/94/17) that rotted repeatedly. Now derive expectations from REGISTRY itself and, for the schema version, from the migrations directory, so they can't rot the same way again. - test_fire_tracker_phase4.py: two tests hardcoded a nonexistent deployment path (/opt/meshai/meshai/router.py) that matches no Dockerfile WORKDIR in this repo; resolve the module path via importlib.util.find_spec instead. - test_tombstone_broadcast.py: asserted fire severity == "immediate", which commit 2f677e85 deliberately downgraded to "priority" (to stop fire broadcasts bypassing the Grouper/cooldown during NATS backlog replay) without updating this test. - test_pipeline_grouper.py: test_immediate_severity_bypasses_grouper asserted an immediate-severity bypass that commit 85d48ce3 ("fix(fire): remove immediate-severity exemption from grouper + cooldown") DELETED on purpose -- fire events carry _severity_override="immediate", and the exemption left fire with no rate control at all in normal live operation. Re-adding the bypass would re-open that fire-spam hole on a public-safety mesh, so the test moves, not the source. Renamed + inverted to assert the real contract (all severities coalesce; only a missing group_key passes through). - test_tail_followups.py: dispatcher mock was missing dispatch_scheduled_fire_broadcast (a method added alongside the generic dispatch_scheduled_broadcast; test_reminders.py already mocks both). - test_tracking_v057.py: guard required an empty tracking-family adapter list in Environment.tsx, but the frontend has long grouped the pre-existing native satpass adapter under the "Tracking" display section (its own "satpass" backend toggle, unrelated to the Phase-7 tracking family every other guard in this file confirms is still unimplemented). Narrowed the guard to allow only that known entry. - test_v052_dispatcher.py: two tests used category="wildfire_incident", which the phase3b fire migration (#33) forced onto a dedicated formatter via NATIVE_ALWAYS_DECIDE; swapped to wildfire_hotspot (same emoji/label, not in NATIVE_ALWAYS_DECIDE) to keep exercising the generic composer logic under test. Also fixes one stale COMMENT (comment-only, no logic change) in meshai/notifications/pipeline/__init__.py's start_pipeline(): it still claimed "Immediate events bypass the grouper and don't need this [periodic flush]", which has been false since 85d48ce3 and is precisely what makes the deleted bypass look like a missing feature. The comment now records that the removal was deliberate and must not be reverted. The remaining 6 failures are left untouched -- 2 confirmed real bugs, to be fixed deliberately in their own changes: - meshcore_transport.py defines `_resolve_contact` TWICE on MeshCoreTransport (line 171 from PR #56, line 1227 from PR #92). The second silently shadows the first, so the DM contact-resolution refetch-on-miss that #56 added is dead code in production. (3 tests) - SCHEMA_VERSION (persistence/db.py:33) is stale at 26 vs. the actual highest migration v28; v27 and v28 shipped without bumping it. (3 tests) Plus 1 environment gap, not a code defect: test_natural_language_fire_question_routes_to_llm needs the `openai` package, which is declared in requirements.txt but not installed here. Suite: 20 failed, 2240 passed, 72 skipped -> 7 failed, 2254 passed, 72 skipped. All 7 remaining failures are ones classified above; no new failures introduced elsewhere in the suite. Co-authored-by: Matt Johnson <mj@k7zvx.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 10:36:19 -06:00
T1: tombstone yields _severity_override="priority" + commit handles
T2: closure wire dispatches when New was broadcast >=10min earlier
T3: build_env_summary excludes tombstoned and 100%-contained fires
test: fix 13 stale tests in the red suite, leave 6 real-bug failures (#124) (#129) Triaged all 20 known-red tests. 13 were stale tests asserting rotted expectations against deliberate, documented behavior changes; fixed by deriving expected values instead of hard-coding, or updating the expectation to match a documented policy change: - test_adapter_config_foundation.py / test_adapter_config_api.py: REGISTRY/API key-count and key-set guards hard-coded magic numbers (59/94/17) that rotted repeatedly. Now derive expectations from REGISTRY itself and, for the schema version, from the migrations directory, so they can't rot the same way again. - test_fire_tracker_phase4.py: two tests hardcoded a nonexistent deployment path (/opt/meshai/meshai/router.py) that matches no Dockerfile WORKDIR in this repo; resolve the module path via importlib.util.find_spec instead. - test_tombstone_broadcast.py: asserted fire severity == "immediate", which commit 2f677e85 deliberately downgraded to "priority" (to stop fire broadcasts bypassing the Grouper/cooldown during NATS backlog replay) without updating this test. - test_pipeline_grouper.py: test_immediate_severity_bypasses_grouper asserted an immediate-severity bypass that commit 85d48ce3 ("fix(fire): remove immediate-severity exemption from grouper + cooldown") DELETED on purpose -- fire events carry _severity_override="immediate", and the exemption left fire with no rate control at all in normal live operation. Re-adding the bypass would re-open that fire-spam hole on a public-safety mesh, so the test moves, not the source. Renamed + inverted to assert the real contract (all severities coalesce; only a missing group_key passes through). - test_tail_followups.py: dispatcher mock was missing dispatch_scheduled_fire_broadcast (a method added alongside the generic dispatch_scheduled_broadcast; test_reminders.py already mocks both). - test_tracking_v057.py: guard required an empty tracking-family adapter list in Environment.tsx, but the frontend has long grouped the pre-existing native satpass adapter under the "Tracking" display section (its own "satpass" backend toggle, unrelated to the Phase-7 tracking family every other guard in this file confirms is still unimplemented). Narrowed the guard to allow only that known entry. - test_v052_dispatcher.py: two tests used category="wildfire_incident", which the phase3b fire migration (#33) forced onto a dedicated formatter via NATIVE_ALWAYS_DECIDE; swapped to wildfire_hotspot (same emoji/label, not in NATIVE_ALWAYS_DECIDE) to keep exercising the generic composer logic under test. Also fixes one stale COMMENT (comment-only, no logic change) in meshai/notifications/pipeline/__init__.py's start_pipeline(): it still claimed "Immediate events bypass the grouper and don't need this [periodic flush]", which has been false since 85d48ce3 and is precisely what makes the deleted bypass look like a missing feature. The comment now records that the removal was deliberate and must not be reverted. The remaining 6 failures are left untouched -- 2 confirmed real bugs, to be fixed deliberately in their own changes: - meshcore_transport.py defines `_resolve_contact` TWICE on MeshCoreTransport (line 171 from PR #56, line 1227 from PR #92). The second silently shadows the first, so the DM contact-resolution refetch-on-miss that #56 added is dead code in production. (3 tests) - SCHEMA_VERSION (persistence/db.py:33) is stale at 26 vs. the actual highest migration v28; v27 and v28 shipped without bumping it. (3 tests) Plus 1 environment gap, not a code defect: test_natural_language_fire_question_routes_to_llm needs the `openai` package, which is declared in requirements.txt but not installed here. Suite: 20 failed, 2240 passed, 72 skipped -> 7 failed, 2254 passed, 72 skipped. All 7 remaining failures are ones classified above; no new failures introduced elsewhere in the suite. Co-authored-by: Matt Johnson <mj@k7zvx.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 10:36:19 -06:00
Severity note: fire broadcasts (new/update/tombstone-closure alike) were
downgraded from "immediate" to "priority" by commit 2f677e85
("fix(fire): drain-mode pacer to prevent post-reconnect broadcast spam").
After a NATS consumer outage, LAST_PER_SUBJECT delivery could flood
thousands of backlogged events at once; "immediate" severity bypassed the
Grouper and zeroed dispatcher cooldowns, so a backlog replay produced
duplicate "New" broadcasts for the same fire. "priority" routes fire
broadcasts back through the normal pipeline guards (Grouper, cooldown).
This file's expectations were written before that downgrade and never
updated -- "immediate" here would be reverting a deliberate, documented
incident fix.
chore(central-ripout 2d-ii): remove dead fire scraps (NOT the 'rewrite' — there wasn't one) (#166) * chore(fire-ripout 2dii-a): remove dead wildfire_growth cutover path + handle_firms wildfire_growth events are always fully precomposed (env/firms.py sets _meshai_precomposed=True + title=<wire from _render()>) and the category is not in cutover.NATIVE_ALWAYS_DECIDE, so the registered fire formatter could never be reached live via compose_mesh_message for this category -- the precomposed-title bypass always won first. Removes the dead is_cutover("wildfire_growth") NEW-PATH branch in fire_fusion._handle_pass_boundary (kept the live legacy leg that calls _render unconditionally) and the now-unreachable wildfire_growth formatter registration in notifications/formatters/__init__.py. Also removes handle_firms, the dead Central NATS-envelope entrypoint (zero live production callers -- Central's consumer that drove it is gone), plus its envelope-specific filtering helpers (_confidence_passes, _in_bbox, _coerce_severity, _log_event, FIRMS_CONFIDENCE_FLOOR/FRP_FLOOR/BBOX_OPTIONAL) that had no live callers left. The shared, LIVE fusion core (_ingest_pixel_core, attribution, clustering, growth/spotting/halt) and _parse_acq_epoch are unaffected -- still the engine behind the native ingest_hotspot_pixel entrypoint. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(fire-ripout 2dii-b): remove dead handle_wfigs entrypoint handle_wfigs (the dead Central NATS-envelope entrypoint) had zero live production callers -- Central's consumer that drove it is gone. The LIVE WFIGS path is env/fires.py (native adapter) -> env/store.py::_emit_event, forced onto gating.fire.decide + the shared fire formatter via cutover.NATIVE_ALWAYS_DECIDE, independent of this dead handler. Removes handle_wfigs and its private-only helpers (_coerce_severity, _log_event, _log_event_returning_id) that had no callers left. _render remains -- it is called directly by env.fire_fusion._handle_pass_boundary on the FIRMS wildfire_growth path, and is used as a byte-identity oracle by tests against the shared, live fire formatter. _build_canonical, _attach_commit_handles, _fire_too_old_to_announce, _now, _cleanup_stale_fires and WFIGS_BROADCAST_COOLDOWN_S are kept -- each has its own direct test coverage independent of handle_wfigs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(fire-ripout 2dii-c): rewire fire tests off the deleted dead entrypoints handle_firms and handle_wfigs (both removed in the prior two commits) were used throughout this test suite purely as convenient DRIVERS for live logic (pixel attribution/clustering/growth/spotting/halt fusion; the WFIGS New/Update/cooldown/closed decider). Per-file disposition: Rewired to drive the LIVE native entrypoint directly (no behavior change -- same underlying _ingest_pixel_core / gating.fire.decide engines): - test_fire_tracker_phase1/2/3.py: handle_firms -> ingest_hotspot_pixel - test_firms_refactor.py: handle_firms driver helpers -> ingest_hotspot_pixel; dropped the dead wildfire_growth cutover test + the fully-redundant TestNotCutoverLegacyVerbatim class (already covered by test_firms_native_fusion.py's TestIngestGrowth/Spotting/Halt against the live entrypoint); wildfire_growth formatter registration test now asserts None (matches source change). - test_wfigs_handler.py: handle_wfigs -> _render (the live WFIGS renderer) called directly, for the anchor-priority + missing-acres cases that exercise shared/live code (_location_anchor). - test_fire_refactor.py: handle_wfigs -> gating.fire.decide() driven directly, with state written the same unconditional shape the live native path uses; TestGateSequenceParity trimmed to focus on the tombstone/closed lifecycle step (not covered by test_fire_native_growth.py's native-adapter New/Update/cooldown coverage). - test_tombstone_broadcast.py, test_fire_age_gate.py: handle_wfigs -> gating.fire.decide() driven directly; same assertions, off the dead path. Deleted as pure dead-entrypoint contract testing with no live equivalent: - test_firms_native_fusion.py::TestCentralPathUnchanged (2 tests) -- existed only to guard handle_firms's own envelope parsing. - test_firms_handler.py: confidence/FRP/bbox filtering, missing-coords, non-firms-adapter guard, event_log accounting (all handle_firms-specific; the native adapter filters upstream in a different code path). Kept + rewired: the shared _ingest_pixel_core dedup behavior and _parse_acq_epoch's int/short acq_time parsing (both genuinely live/shared). - test_wfigs_handler.py: envelope field-extraction (acres-fallback chain, IA-placeholder-as-name), tombstone/perimeter subject -> event_log, New/Update/cooldown decision + audit-row wiring (all redundant with tests/test_fire_native_growth.py's coverage of gating.fire.decide() through the real native adapter). - test_tombstone_broadcast.py::test_commit_callback_flips_handled -- asserted handle_wfigs's own event_log-row flip on commit, a Central-only concept the native path never used. - test_tail_followups.py::test_wfigs_tombstone_stamps_column -- asserted handle_wfigs's own inline tombstoned_at UPDATE; no live producer emits _kind=wfigs_tombstone today, so nothing in the live system stamps it. Both live renderers stay covered: fire_format (wildfire_declared/incident, via test_fire_refactor.py + test_fire_native_growth.py, untouched) and _render (wildfire_growth via FIRMS, via test_fire_tracker_phase2.py + test_firms_native_fusion.py's TestIngestGrowth, both driving the live ingest_hotspot_pixel entrypoint). Collection: 2010 -> 1974 tests (net -36: dead-only tests deleted, live behavior rewired 1:1 or consolidated onto already-existing native-path coverage). Full suite: 1974 passed, 0 failed. 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-17 22:11:00 -06:00
chore/ripout-2dii: `handle_wfigs` (the dead Central NATS-envelope entrypoint
T1/T2 used to drive) has been REMOVED from `meshai.env.fire_render` -- zero
live production callers. T1/T2 now drive `gating.fire.decide` directly (the
LIVE, shared decider -- `_kind="wfigs_tombstone"` is its all-clear branch,
reused by the shared fire formatter). `test_commit_callback_flips_handled`
(which asserted handle_wfigs's OWN event_log-row flip on commit -- a
Central-only concept the native path never used) was deleted with it.
"""
from __future__ import annotations
import time
import pytest
chore(central-ripout 2d-ii): remove dead fire scraps (NOT the 'rewrite' — there wasn't one) (#166) * chore(fire-ripout 2dii-a): remove dead wildfire_growth cutover path + handle_firms wildfire_growth events are always fully precomposed (env/firms.py sets _meshai_precomposed=True + title=<wire from _render()>) and the category is not in cutover.NATIVE_ALWAYS_DECIDE, so the registered fire formatter could never be reached live via compose_mesh_message for this category -- the precomposed-title bypass always won first. Removes the dead is_cutover("wildfire_growth") NEW-PATH branch in fire_fusion._handle_pass_boundary (kept the live legacy leg that calls _render unconditionally) and the now-unreachable wildfire_growth formatter registration in notifications/formatters/__init__.py. Also removes handle_firms, the dead Central NATS-envelope entrypoint (zero live production callers -- Central's consumer that drove it is gone), plus its envelope-specific filtering helpers (_confidence_passes, _in_bbox, _coerce_severity, _log_event, FIRMS_CONFIDENCE_FLOOR/FRP_FLOOR/BBOX_OPTIONAL) that had no live callers left. The shared, LIVE fusion core (_ingest_pixel_core, attribution, clustering, growth/spotting/halt) and _parse_acq_epoch are unaffected -- still the engine behind the native ingest_hotspot_pixel entrypoint. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(fire-ripout 2dii-b): remove dead handle_wfigs entrypoint handle_wfigs (the dead Central NATS-envelope entrypoint) had zero live production callers -- Central's consumer that drove it is gone. The LIVE WFIGS path is env/fires.py (native adapter) -> env/store.py::_emit_event, forced onto gating.fire.decide + the shared fire formatter via cutover.NATIVE_ALWAYS_DECIDE, independent of this dead handler. Removes handle_wfigs and its private-only helpers (_coerce_severity, _log_event, _log_event_returning_id) that had no callers left. _render remains -- it is called directly by env.fire_fusion._handle_pass_boundary on the FIRMS wildfire_growth path, and is used as a byte-identity oracle by tests against the shared, live fire formatter. _build_canonical, _attach_commit_handles, _fire_too_old_to_announce, _now, _cleanup_stale_fires and WFIGS_BROADCAST_COOLDOWN_S are kept -- each has its own direct test coverage independent of handle_wfigs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(fire-ripout 2dii-c): rewire fire tests off the deleted dead entrypoints handle_firms and handle_wfigs (both removed in the prior two commits) were used throughout this test suite purely as convenient DRIVERS for live logic (pixel attribution/clustering/growth/spotting/halt fusion; the WFIGS New/Update/cooldown/closed decider). Per-file disposition: Rewired to drive the LIVE native entrypoint directly (no behavior change -- same underlying _ingest_pixel_core / gating.fire.decide engines): - test_fire_tracker_phase1/2/3.py: handle_firms -> ingest_hotspot_pixel - test_firms_refactor.py: handle_firms driver helpers -> ingest_hotspot_pixel; dropped the dead wildfire_growth cutover test + the fully-redundant TestNotCutoverLegacyVerbatim class (already covered by test_firms_native_fusion.py's TestIngestGrowth/Spotting/Halt against the live entrypoint); wildfire_growth formatter registration test now asserts None (matches source change). - test_wfigs_handler.py: handle_wfigs -> _render (the live WFIGS renderer) called directly, for the anchor-priority + missing-acres cases that exercise shared/live code (_location_anchor). - test_fire_refactor.py: handle_wfigs -> gating.fire.decide() driven directly, with state written the same unconditional shape the live native path uses; TestGateSequenceParity trimmed to focus on the tombstone/closed lifecycle step (not covered by test_fire_native_growth.py's native-adapter New/Update/cooldown coverage). - test_tombstone_broadcast.py, test_fire_age_gate.py: handle_wfigs -> gating.fire.decide() driven directly; same assertions, off the dead path. Deleted as pure dead-entrypoint contract testing with no live equivalent: - test_firms_native_fusion.py::TestCentralPathUnchanged (2 tests) -- existed only to guard handle_firms's own envelope parsing. - test_firms_handler.py: confidence/FRP/bbox filtering, missing-coords, non-firms-adapter guard, event_log accounting (all handle_firms-specific; the native adapter filters upstream in a different code path). Kept + rewired: the shared _ingest_pixel_core dedup behavior and _parse_acq_epoch's int/short acq_time parsing (both genuinely live/shared). - test_wfigs_handler.py: envelope field-extraction (acres-fallback chain, IA-placeholder-as-name), tombstone/perimeter subject -> event_log, New/Update/cooldown decision + audit-row wiring (all redundant with tests/test_fire_native_growth.py's coverage of gating.fire.decide() through the real native adapter). - test_tombstone_broadcast.py::test_commit_callback_flips_handled -- asserted handle_wfigs's own event_log-row flip on commit, a Central-only concept the native path never used. - test_tail_followups.py::test_wfigs_tombstone_stamps_column -- asserted handle_wfigs's own inline tombstoned_at UPDATE; no live producer emits _kind=wfigs_tombstone today, so nothing in the live system stamps it. Both live renderers stay covered: fire_format (wildfire_declared/incident, via test_fire_refactor.py + test_fire_native_growth.py, untouched) and _render (wildfire_growth via FIRMS, via test_fire_tracker_phase2.py + test_firms_native_fusion.py's TestIngestGrowth, both driving the live ingest_hotspot_pixel entrypoint). Collection: 2010 -> 1974 tests (net -36: dead-only tests deleted, live behavior rewired 1:1 or consolidated onto already-existing native-path coverage). Full suite: 1974 passed, 0 failed. 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-17 22:11:00 -06:00
from meshai.notifications.gating.fire import decide as fire_decide
from meshai.notifications.env_reporter import EnvReporter
from meshai.persistence import get_db
@pytest.fixture
def reporter():
return EnvReporter()
def _seed_fire(conn, *, irwin_id, name, acres, contained=None,
last_broadcast_at=None, last_event_at=None,
tombstoned_at=None, county="Ada", state="ID"):
now = int(time.time())
conn.execute(
"INSERT OR REPLACE INTO fires(irwin_id, incident_name, incident_type, "
"current_acres, current_contained_pct, lat, lon, county, state, "
"declared_at, last_event_at, last_broadcast_at, tombstoned_at) "
"VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)",
(irwin_id, name, "WF", acres, contained, 43.6, -116.2,
county, state, now, last_event_at or now,
last_broadcast_at, tombstoned_at),
)
class TestTombstoneSeverityAndCommitHandles:
chore(central-ripout 2d-ii): remove dead fire scraps (NOT the 'rewrite' — there wasn't one) (#166) * chore(fire-ripout 2dii-a): remove dead wildfire_growth cutover path + handle_firms wildfire_growth events are always fully precomposed (env/firms.py sets _meshai_precomposed=True + title=<wire from _render()>) and the category is not in cutover.NATIVE_ALWAYS_DECIDE, so the registered fire formatter could never be reached live via compose_mesh_message for this category -- the precomposed-title bypass always won first. Removes the dead is_cutover("wildfire_growth") NEW-PATH branch in fire_fusion._handle_pass_boundary (kept the live legacy leg that calls _render unconditionally) and the now-unreachable wildfire_growth formatter registration in notifications/formatters/__init__.py. Also removes handle_firms, the dead Central NATS-envelope entrypoint (zero live production callers -- Central's consumer that drove it is gone), plus its envelope-specific filtering helpers (_confidence_passes, _in_bbox, _coerce_severity, _log_event, FIRMS_CONFIDENCE_FLOOR/FRP_FLOOR/BBOX_OPTIONAL) that had no live callers left. The shared, LIVE fusion core (_ingest_pixel_core, attribution, clustering, growth/spotting/halt) and _parse_acq_epoch are unaffected -- still the engine behind the native ingest_hotspot_pixel entrypoint. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(fire-ripout 2dii-b): remove dead handle_wfigs entrypoint handle_wfigs (the dead Central NATS-envelope entrypoint) had zero live production callers -- Central's consumer that drove it is gone. The LIVE WFIGS path is env/fires.py (native adapter) -> env/store.py::_emit_event, forced onto gating.fire.decide + the shared fire formatter via cutover.NATIVE_ALWAYS_DECIDE, independent of this dead handler. Removes handle_wfigs and its private-only helpers (_coerce_severity, _log_event, _log_event_returning_id) that had no callers left. _render remains -- it is called directly by env.fire_fusion._handle_pass_boundary on the FIRMS wildfire_growth path, and is used as a byte-identity oracle by tests against the shared, live fire formatter. _build_canonical, _attach_commit_handles, _fire_too_old_to_announce, _now, _cleanup_stale_fires and WFIGS_BROADCAST_COOLDOWN_S are kept -- each has its own direct test coverage independent of handle_wfigs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(fire-ripout 2dii-c): rewire fire tests off the deleted dead entrypoints handle_firms and handle_wfigs (both removed in the prior two commits) were used throughout this test suite purely as convenient DRIVERS for live logic (pixel attribution/clustering/growth/spotting/halt fusion; the WFIGS New/Update/cooldown/closed decider). Per-file disposition: Rewired to drive the LIVE native entrypoint directly (no behavior change -- same underlying _ingest_pixel_core / gating.fire.decide engines): - test_fire_tracker_phase1/2/3.py: handle_firms -> ingest_hotspot_pixel - test_firms_refactor.py: handle_firms driver helpers -> ingest_hotspot_pixel; dropped the dead wildfire_growth cutover test + the fully-redundant TestNotCutoverLegacyVerbatim class (already covered by test_firms_native_fusion.py's TestIngestGrowth/Spotting/Halt against the live entrypoint); wildfire_growth formatter registration test now asserts None (matches source change). - test_wfigs_handler.py: handle_wfigs -> _render (the live WFIGS renderer) called directly, for the anchor-priority + missing-acres cases that exercise shared/live code (_location_anchor). - test_fire_refactor.py: handle_wfigs -> gating.fire.decide() driven directly, with state written the same unconditional shape the live native path uses; TestGateSequenceParity trimmed to focus on the tombstone/closed lifecycle step (not covered by test_fire_native_growth.py's native-adapter New/Update/cooldown coverage). - test_tombstone_broadcast.py, test_fire_age_gate.py: handle_wfigs -> gating.fire.decide() driven directly; same assertions, off the dead path. Deleted as pure dead-entrypoint contract testing with no live equivalent: - test_firms_native_fusion.py::TestCentralPathUnchanged (2 tests) -- existed only to guard handle_firms's own envelope parsing. - test_firms_handler.py: confidence/FRP/bbox filtering, missing-coords, non-firms-adapter guard, event_log accounting (all handle_firms-specific; the native adapter filters upstream in a different code path). Kept + rewired: the shared _ingest_pixel_core dedup behavior and _parse_acq_epoch's int/short acq_time parsing (both genuinely live/shared). - test_wfigs_handler.py: envelope field-extraction (acres-fallback chain, IA-placeholder-as-name), tombstone/perimeter subject -> event_log, New/Update/cooldown decision + audit-row wiring (all redundant with tests/test_fire_native_growth.py's coverage of gating.fire.decide() through the real native adapter). - test_tombstone_broadcast.py::test_commit_callback_flips_handled -- asserted handle_wfigs's own event_log-row flip on commit, a Central-only concept the native path never used. - test_tail_followups.py::test_wfigs_tombstone_stamps_column -- asserted handle_wfigs's own inline tombstoned_at UPDATE; no live producer emits _kind=wfigs_tombstone today, so nothing in the live system stamps it. Both live renderers stay covered: fire_format (wildfire_declared/incident, via test_fire_refactor.py + test_fire_native_growth.py, untouched) and _render (wildfire_growth via FIRMS, via test_fire_tracker_phase2.py + test_firms_native_fusion.py's TestIngestGrowth, both driving the live ingest_hotspot_pixel entrypoint). Collection: 2010 -> 1974 tests (net -36: dead-only tests deleted, live behavior rewired 1:1 or consolidated onto already-existing native-path coverage). Full suite: 1974 passed, 0 failed. 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-17 22:11:00 -06:00
"""T1: tombstone branch sets priority severity and attaches commit handles.
Drives gating.fire.decide() directly (chore/ripout-2dii: handle_wfigs, the
dead entrypoint this used to wrap, is gone). decide()'s data_patch carries
the same stamps the dispatcher reads off event.data.
"""
test: fix 13 stale tests in the red suite, leave 6 real-bug failures (#124) (#129) Triaged all 20 known-red tests. 13 were stale tests asserting rotted expectations against deliberate, documented behavior changes; fixed by deriving expected values instead of hard-coding, or updating the expectation to match a documented policy change: - test_adapter_config_foundation.py / test_adapter_config_api.py: REGISTRY/API key-count and key-set guards hard-coded magic numbers (59/94/17) that rotted repeatedly. Now derive expectations from REGISTRY itself and, for the schema version, from the migrations directory, so they can't rot the same way again. - test_fire_tracker_phase4.py: two tests hardcoded a nonexistent deployment path (/opt/meshai/meshai/router.py) that matches no Dockerfile WORKDIR in this repo; resolve the module path via importlib.util.find_spec instead. - test_tombstone_broadcast.py: asserted fire severity == "immediate", which commit 2f677e85 deliberately downgraded to "priority" (to stop fire broadcasts bypassing the Grouper/cooldown during NATS backlog replay) without updating this test. - test_pipeline_grouper.py: test_immediate_severity_bypasses_grouper asserted an immediate-severity bypass that commit 85d48ce3 ("fix(fire): remove immediate-severity exemption from grouper + cooldown") DELETED on purpose -- fire events carry _severity_override="immediate", and the exemption left fire with no rate control at all in normal live operation. Re-adding the bypass would re-open that fire-spam hole on a public-safety mesh, so the test moves, not the source. Renamed + inverted to assert the real contract (all severities coalesce; only a missing group_key passes through). - test_tail_followups.py: dispatcher mock was missing dispatch_scheduled_fire_broadcast (a method added alongside the generic dispatch_scheduled_broadcast; test_reminders.py already mocks both). - test_tracking_v057.py: guard required an empty tracking-family adapter list in Environment.tsx, but the frontend has long grouped the pre-existing native satpass adapter under the "Tracking" display section (its own "satpass" backend toggle, unrelated to the Phase-7 tracking family every other guard in this file confirms is still unimplemented). Narrowed the guard to allow only that known entry. - test_v052_dispatcher.py: two tests used category="wildfire_incident", which the phase3b fire migration (#33) forced onto a dedicated formatter via NATIVE_ALWAYS_DECIDE; swapped to wildfire_hotspot (same emoji/label, not in NATIVE_ALWAYS_DECIDE) to keep exercising the generic composer logic under test. Also fixes one stale COMMENT (comment-only, no logic change) in meshai/notifications/pipeline/__init__.py's start_pipeline(): it still claimed "Immediate events bypass the grouper and don't need this [periodic flush]", which has been false since 85d48ce3 and is precisely what makes the deleted bypass look like a missing feature. The comment now records that the removal was deliberate and must not be reverted. The remaining 6 failures are left untouched -- 2 confirmed real bugs, to be fixed deliberately in their own changes: - meshcore_transport.py defines `_resolve_contact` TWICE on MeshCoreTransport (line 171 from PR #56, line 1227 from PR #92). The second silently shadows the first, so the DM contact-resolution refetch-on-miss that #56 added is dead code in production. (3 tests) - SCHEMA_VERSION (persistence/db.py:33) is stale at 26 vs. the actual highest migration v28; v27 and v28 shipped without bumping it. (3 tests) Plus 1 environment gap, not a code defect: test_natural_language_fire_question_routes_to_llm needs the `openai` package, which is declared in requirements.txt but not installed here. Suite: 20 failed, 2240 passed, 72 skipped -> 7 failed, 2254 passed, 72 skipped. All 7 remaining failures are ones classified above; no new failures introduced elsewhere in the suite. Co-authored-by: Matt Johnson <mj@k7zvx.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 10:36:19 -06:00
def test_severity_is_priority(self):
conn = get_db()
now = int(time.time())
_seed_fire(conn, irwin_id="FIRE-001", name="Test Fire",
acres=500, contained=80,
last_broadcast_at=now - 3600)
chore(central-ripout 2d-ii): remove dead fire scraps (NOT the 'rewrite' — there wasn't one) (#166) * chore(fire-ripout 2dii-a): remove dead wildfire_growth cutover path + handle_firms wildfire_growth events are always fully precomposed (env/firms.py sets _meshai_precomposed=True + title=<wire from _render()>) and the category is not in cutover.NATIVE_ALWAYS_DECIDE, so the registered fire formatter could never be reached live via compose_mesh_message for this category -- the precomposed-title bypass always won first. Removes the dead is_cutover("wildfire_growth") NEW-PATH branch in fire_fusion._handle_pass_boundary (kept the live legacy leg that calls _render unconditionally) and the now-unreachable wildfire_growth formatter registration in notifications/formatters/__init__.py. Also removes handle_firms, the dead Central NATS-envelope entrypoint (zero live production callers -- Central's consumer that drove it is gone), plus its envelope-specific filtering helpers (_confidence_passes, _in_bbox, _coerce_severity, _log_event, FIRMS_CONFIDENCE_FLOOR/FRP_FLOOR/BBOX_OPTIONAL) that had no live callers left. The shared, LIVE fusion core (_ingest_pixel_core, attribution, clustering, growth/spotting/halt) and _parse_acq_epoch are unaffected -- still the engine behind the native ingest_hotspot_pixel entrypoint. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(fire-ripout 2dii-b): remove dead handle_wfigs entrypoint handle_wfigs (the dead Central NATS-envelope entrypoint) had zero live production callers -- Central's consumer that drove it is gone. The LIVE WFIGS path is env/fires.py (native adapter) -> env/store.py::_emit_event, forced onto gating.fire.decide + the shared fire formatter via cutover.NATIVE_ALWAYS_DECIDE, independent of this dead handler. Removes handle_wfigs and its private-only helpers (_coerce_severity, _log_event, _log_event_returning_id) that had no callers left. _render remains -- it is called directly by env.fire_fusion._handle_pass_boundary on the FIRMS wildfire_growth path, and is used as a byte-identity oracle by tests against the shared, live fire formatter. _build_canonical, _attach_commit_handles, _fire_too_old_to_announce, _now, _cleanup_stale_fires and WFIGS_BROADCAST_COOLDOWN_S are kept -- each has its own direct test coverage independent of handle_wfigs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(fire-ripout 2dii-c): rewire fire tests off the deleted dead entrypoints handle_firms and handle_wfigs (both removed in the prior two commits) were used throughout this test suite purely as convenient DRIVERS for live logic (pixel attribution/clustering/growth/spotting/halt fusion; the WFIGS New/Update/cooldown/closed decider). Per-file disposition: Rewired to drive the LIVE native entrypoint directly (no behavior change -- same underlying _ingest_pixel_core / gating.fire.decide engines): - test_fire_tracker_phase1/2/3.py: handle_firms -> ingest_hotspot_pixel - test_firms_refactor.py: handle_firms driver helpers -> ingest_hotspot_pixel; dropped the dead wildfire_growth cutover test + the fully-redundant TestNotCutoverLegacyVerbatim class (already covered by test_firms_native_fusion.py's TestIngestGrowth/Spotting/Halt against the live entrypoint); wildfire_growth formatter registration test now asserts None (matches source change). - test_wfigs_handler.py: handle_wfigs -> _render (the live WFIGS renderer) called directly, for the anchor-priority + missing-acres cases that exercise shared/live code (_location_anchor). - test_fire_refactor.py: handle_wfigs -> gating.fire.decide() driven directly, with state written the same unconditional shape the live native path uses; TestGateSequenceParity trimmed to focus on the tombstone/closed lifecycle step (not covered by test_fire_native_growth.py's native-adapter New/Update/cooldown coverage). - test_tombstone_broadcast.py, test_fire_age_gate.py: handle_wfigs -> gating.fire.decide() driven directly; same assertions, off the dead path. Deleted as pure dead-entrypoint contract testing with no live equivalent: - test_firms_native_fusion.py::TestCentralPathUnchanged (2 tests) -- existed only to guard handle_firms's own envelope parsing. - test_firms_handler.py: confidence/FRP/bbox filtering, missing-coords, non-firms-adapter guard, event_log accounting (all handle_firms-specific; the native adapter filters upstream in a different code path). Kept + rewired: the shared _ingest_pixel_core dedup behavior and _parse_acq_epoch's int/short acq_time parsing (both genuinely live/shared). - test_wfigs_handler.py: envelope field-extraction (acres-fallback chain, IA-placeholder-as-name), tombstone/perimeter subject -> event_log, New/Update/cooldown decision + audit-row wiring (all redundant with tests/test_fire_native_growth.py's coverage of gating.fire.decide() through the real native adapter). - test_tombstone_broadcast.py::test_commit_callback_flips_handled -- asserted handle_wfigs's own event_log-row flip on commit, a Central-only concept the native path never used. - test_tail_followups.py::test_wfigs_tombstone_stamps_column -- asserted handle_wfigs's own inline tombstoned_at UPDATE; no live producer emits _kind=wfigs_tombstone today, so nothing in the live system stamps it. Both live renderers stay covered: fire_format (wildfire_declared/incident, via test_fire_refactor.py + test_fire_native_growth.py, untouched) and _render (wildfire_growth via FIRMS, via test_fire_tracker_phase2.py + test_firms_native_fusion.py's TestIngestGrowth, both driving the live ingest_hotspot_pixel entrypoint). Collection: 2010 -> 1974 tests (net -36: dead-only tests deleted, live behavior rewired 1:1 or consolidated onto already-existing native-path coverage). Full suite: 1974 passed, 0 failed. 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-17 22:11:00 -06:00
gate = fire_decide({"_kind": "wfigs_tombstone", "irwin_id": "FIRE-001"},
source="wfigs", now=float(now))
assert gate.broadcast is True, "tombstone should broadcast for previously-broadcast fire"
test: fix 13 stale tests in the red suite, leave 6 real-bug failures (#124) (#129) Triaged all 20 known-red tests. 13 were stale tests asserting rotted expectations against deliberate, documented behavior changes; fixed by deriving expected values instead of hard-coding, or updating the expectation to match a documented policy change: - test_adapter_config_foundation.py / test_adapter_config_api.py: REGISTRY/API key-count and key-set guards hard-coded magic numbers (59/94/17) that rotted repeatedly. Now derive expectations from REGISTRY itself and, for the schema version, from the migrations directory, so they can't rot the same way again. - test_fire_tracker_phase4.py: two tests hardcoded a nonexistent deployment path (/opt/meshai/meshai/router.py) that matches no Dockerfile WORKDIR in this repo; resolve the module path via importlib.util.find_spec instead. - test_tombstone_broadcast.py: asserted fire severity == "immediate", which commit 2f677e85 deliberately downgraded to "priority" (to stop fire broadcasts bypassing the Grouper/cooldown during NATS backlog replay) without updating this test. - test_pipeline_grouper.py: test_immediate_severity_bypasses_grouper asserted an immediate-severity bypass that commit 85d48ce3 ("fix(fire): remove immediate-severity exemption from grouper + cooldown") DELETED on purpose -- fire events carry _severity_override="immediate", and the exemption left fire with no rate control at all in normal live operation. Re-adding the bypass would re-open that fire-spam hole on a public-safety mesh, so the test moves, not the source. Renamed + inverted to assert the real contract (all severities coalesce; only a missing group_key passes through). - test_tail_followups.py: dispatcher mock was missing dispatch_scheduled_fire_broadcast (a method added alongside the generic dispatch_scheduled_broadcast; test_reminders.py already mocks both). - test_tracking_v057.py: guard required an empty tracking-family adapter list in Environment.tsx, but the frontend has long grouped the pre-existing native satpass adapter under the "Tracking" display section (its own "satpass" backend toggle, unrelated to the Phase-7 tracking family every other guard in this file confirms is still unimplemented). Narrowed the guard to allow only that known entry. - test_v052_dispatcher.py: two tests used category="wildfire_incident", which the phase3b fire migration (#33) forced onto a dedicated formatter via NATIVE_ALWAYS_DECIDE; swapped to wildfire_hotspot (same emoji/label, not in NATIVE_ALWAYS_DECIDE) to keep exercising the generic composer logic under test. Also fixes one stale COMMENT (comment-only, no logic change) in meshai/notifications/pipeline/__init__.py's start_pipeline(): it still claimed "Immediate events bypass the grouper and don't need this [periodic flush]", which has been false since 85d48ce3 and is precisely what makes the deleted bypass look like a missing feature. The comment now records that the removal was deliberate and must not be reverted. The remaining 6 failures are left untouched -- 2 confirmed real bugs, to be fixed deliberately in their own changes: - meshcore_transport.py defines `_resolve_contact` TWICE on MeshCoreTransport (line 171 from PR #56, line 1227 from PR #92). The second silently shadows the first, so the DM contact-resolution refetch-on-miss that #56 added is dead code in production. (3 tests) - SCHEMA_VERSION (persistence/db.py:33) is stale at 26 vs. the actual highest migration v28; v27 and v28 shipped without bumping it. (3 tests) Plus 1 environment gap, not a code defect: test_natural_language_fire_question_routes_to_llm needs the `openai` package, which is declared in requirements.txt but not installed here. Suite: 20 failed, 2240 passed, 72 skipped -> 7 failed, 2254 passed, 72 skipped. All 7 remaining failures are ones classified above; no new failures introduced elsewhere in the suite. Co-authored-by: Matt Johnson <mj@k7zvx.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 10:36:19 -06:00
# See module docstring: fire severity was deliberately downgraded
# from "immediate" to "priority" (commit 2f677e85) so fire
# broadcasts flow through the normal Grouper/cooldown guards.
chore(central-ripout 2d-ii): remove dead fire scraps (NOT the 'rewrite' — there wasn't one) (#166) * chore(fire-ripout 2dii-a): remove dead wildfire_growth cutover path + handle_firms wildfire_growth events are always fully precomposed (env/firms.py sets _meshai_precomposed=True + title=<wire from _render()>) and the category is not in cutover.NATIVE_ALWAYS_DECIDE, so the registered fire formatter could never be reached live via compose_mesh_message for this category -- the precomposed-title bypass always won first. Removes the dead is_cutover("wildfire_growth") NEW-PATH branch in fire_fusion._handle_pass_boundary (kept the live legacy leg that calls _render unconditionally) and the now-unreachable wildfire_growth formatter registration in notifications/formatters/__init__.py. Also removes handle_firms, the dead Central NATS-envelope entrypoint (zero live production callers -- Central's consumer that drove it is gone), plus its envelope-specific filtering helpers (_confidence_passes, _in_bbox, _coerce_severity, _log_event, FIRMS_CONFIDENCE_FLOOR/FRP_FLOOR/BBOX_OPTIONAL) that had no live callers left. The shared, LIVE fusion core (_ingest_pixel_core, attribution, clustering, growth/spotting/halt) and _parse_acq_epoch are unaffected -- still the engine behind the native ingest_hotspot_pixel entrypoint. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(fire-ripout 2dii-b): remove dead handle_wfigs entrypoint handle_wfigs (the dead Central NATS-envelope entrypoint) had zero live production callers -- Central's consumer that drove it is gone. The LIVE WFIGS path is env/fires.py (native adapter) -> env/store.py::_emit_event, forced onto gating.fire.decide + the shared fire formatter via cutover.NATIVE_ALWAYS_DECIDE, independent of this dead handler. Removes handle_wfigs and its private-only helpers (_coerce_severity, _log_event, _log_event_returning_id) that had no callers left. _render remains -- it is called directly by env.fire_fusion._handle_pass_boundary on the FIRMS wildfire_growth path, and is used as a byte-identity oracle by tests against the shared, live fire formatter. _build_canonical, _attach_commit_handles, _fire_too_old_to_announce, _now, _cleanup_stale_fires and WFIGS_BROADCAST_COOLDOWN_S are kept -- each has its own direct test coverage independent of handle_wfigs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(fire-ripout 2dii-c): rewire fire tests off the deleted dead entrypoints handle_firms and handle_wfigs (both removed in the prior two commits) were used throughout this test suite purely as convenient DRIVERS for live logic (pixel attribution/clustering/growth/spotting/halt fusion; the WFIGS New/Update/cooldown/closed decider). Per-file disposition: Rewired to drive the LIVE native entrypoint directly (no behavior change -- same underlying _ingest_pixel_core / gating.fire.decide engines): - test_fire_tracker_phase1/2/3.py: handle_firms -> ingest_hotspot_pixel - test_firms_refactor.py: handle_firms driver helpers -> ingest_hotspot_pixel; dropped the dead wildfire_growth cutover test + the fully-redundant TestNotCutoverLegacyVerbatim class (already covered by test_firms_native_fusion.py's TestIngestGrowth/Spotting/Halt against the live entrypoint); wildfire_growth formatter registration test now asserts None (matches source change). - test_wfigs_handler.py: handle_wfigs -> _render (the live WFIGS renderer) called directly, for the anchor-priority + missing-acres cases that exercise shared/live code (_location_anchor). - test_fire_refactor.py: handle_wfigs -> gating.fire.decide() driven directly, with state written the same unconditional shape the live native path uses; TestGateSequenceParity trimmed to focus on the tombstone/closed lifecycle step (not covered by test_fire_native_growth.py's native-adapter New/Update/cooldown coverage). - test_tombstone_broadcast.py, test_fire_age_gate.py: handle_wfigs -> gating.fire.decide() driven directly; same assertions, off the dead path. Deleted as pure dead-entrypoint contract testing with no live equivalent: - test_firms_native_fusion.py::TestCentralPathUnchanged (2 tests) -- existed only to guard handle_firms's own envelope parsing. - test_firms_handler.py: confidence/FRP/bbox filtering, missing-coords, non-firms-adapter guard, event_log accounting (all handle_firms-specific; the native adapter filters upstream in a different code path). Kept + rewired: the shared _ingest_pixel_core dedup behavior and _parse_acq_epoch's int/short acq_time parsing (both genuinely live/shared). - test_wfigs_handler.py: envelope field-extraction (acres-fallback chain, IA-placeholder-as-name), tombstone/perimeter subject -> event_log, New/Update/cooldown decision + audit-row wiring (all redundant with tests/test_fire_native_growth.py's coverage of gating.fire.decide() through the real native adapter). - test_tombstone_broadcast.py::test_commit_callback_flips_handled -- asserted handle_wfigs's own event_log-row flip on commit, a Central-only concept the native path never used. - test_tail_followups.py::test_wfigs_tombstone_stamps_column -- asserted handle_wfigs's own inline tombstoned_at UPDATE; no live producer emits _kind=wfigs_tombstone today, so nothing in the live system stamps it. Both live renderers stay covered: fire_format (wildfire_declared/incident, via test_fire_refactor.py + test_fire_native_growth.py, untouched) and _render (wildfire_growth via FIRMS, via test_fire_tracker_phase2.py + test_firms_native_fusion.py's TestIngestGrowth, both driving the live ingest_hotspot_pixel entrypoint). Collection: 2010 -> 1974 tests (net -36: dead-only tests deleted, live behavior rewired 1:1 or consolidated onto already-existing native-path coverage). Full suite: 1974 passed, 0 failed. 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-17 22:11:00 -06:00
assert gate.data_patch.get("_severity_override") == "priority", (
f"expected priority, got {gate.data_patch.get('_severity_override')}")
def test_commit_handles_attached(self):
conn = get_db()
now = int(time.time())
_seed_fire(conn, irwin_id="FIRE-002", name="Handled Fire",
acres=1000, contained=95,
last_broadcast_at=now - 7200)
chore(central-ripout 2d-ii): remove dead fire scraps (NOT the 'rewrite' — there wasn't one) (#166) * chore(fire-ripout 2dii-a): remove dead wildfire_growth cutover path + handle_firms wildfire_growth events are always fully precomposed (env/firms.py sets _meshai_precomposed=True + title=<wire from _render()>) and the category is not in cutover.NATIVE_ALWAYS_DECIDE, so the registered fire formatter could never be reached live via compose_mesh_message for this category -- the precomposed-title bypass always won first. Removes the dead is_cutover("wildfire_growth") NEW-PATH branch in fire_fusion._handle_pass_boundary (kept the live legacy leg that calls _render unconditionally) and the now-unreachable wildfire_growth formatter registration in notifications/formatters/__init__.py. Also removes handle_firms, the dead Central NATS-envelope entrypoint (zero live production callers -- Central's consumer that drove it is gone), plus its envelope-specific filtering helpers (_confidence_passes, _in_bbox, _coerce_severity, _log_event, FIRMS_CONFIDENCE_FLOOR/FRP_FLOOR/BBOX_OPTIONAL) that had no live callers left. The shared, LIVE fusion core (_ingest_pixel_core, attribution, clustering, growth/spotting/halt) and _parse_acq_epoch are unaffected -- still the engine behind the native ingest_hotspot_pixel entrypoint. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(fire-ripout 2dii-b): remove dead handle_wfigs entrypoint handle_wfigs (the dead Central NATS-envelope entrypoint) had zero live production callers -- Central's consumer that drove it is gone. The LIVE WFIGS path is env/fires.py (native adapter) -> env/store.py::_emit_event, forced onto gating.fire.decide + the shared fire formatter via cutover.NATIVE_ALWAYS_DECIDE, independent of this dead handler. Removes handle_wfigs and its private-only helpers (_coerce_severity, _log_event, _log_event_returning_id) that had no callers left. _render remains -- it is called directly by env.fire_fusion._handle_pass_boundary on the FIRMS wildfire_growth path, and is used as a byte-identity oracle by tests against the shared, live fire formatter. _build_canonical, _attach_commit_handles, _fire_too_old_to_announce, _now, _cleanup_stale_fires and WFIGS_BROADCAST_COOLDOWN_S are kept -- each has its own direct test coverage independent of handle_wfigs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(fire-ripout 2dii-c): rewire fire tests off the deleted dead entrypoints handle_firms and handle_wfigs (both removed in the prior two commits) were used throughout this test suite purely as convenient DRIVERS for live logic (pixel attribution/clustering/growth/spotting/halt fusion; the WFIGS New/Update/cooldown/closed decider). Per-file disposition: Rewired to drive the LIVE native entrypoint directly (no behavior change -- same underlying _ingest_pixel_core / gating.fire.decide engines): - test_fire_tracker_phase1/2/3.py: handle_firms -> ingest_hotspot_pixel - test_firms_refactor.py: handle_firms driver helpers -> ingest_hotspot_pixel; dropped the dead wildfire_growth cutover test + the fully-redundant TestNotCutoverLegacyVerbatim class (already covered by test_firms_native_fusion.py's TestIngestGrowth/Spotting/Halt against the live entrypoint); wildfire_growth formatter registration test now asserts None (matches source change). - test_wfigs_handler.py: handle_wfigs -> _render (the live WFIGS renderer) called directly, for the anchor-priority + missing-acres cases that exercise shared/live code (_location_anchor). - test_fire_refactor.py: handle_wfigs -> gating.fire.decide() driven directly, with state written the same unconditional shape the live native path uses; TestGateSequenceParity trimmed to focus on the tombstone/closed lifecycle step (not covered by test_fire_native_growth.py's native-adapter New/Update/cooldown coverage). - test_tombstone_broadcast.py, test_fire_age_gate.py: handle_wfigs -> gating.fire.decide() driven directly; same assertions, off the dead path. Deleted as pure dead-entrypoint contract testing with no live equivalent: - test_firms_native_fusion.py::TestCentralPathUnchanged (2 tests) -- existed only to guard handle_firms's own envelope parsing. - test_firms_handler.py: confidence/FRP/bbox filtering, missing-coords, non-firms-adapter guard, event_log accounting (all handle_firms-specific; the native adapter filters upstream in a different code path). Kept + rewired: the shared _ingest_pixel_core dedup behavior and _parse_acq_epoch's int/short acq_time parsing (both genuinely live/shared). - test_wfigs_handler.py: envelope field-extraction (acres-fallback chain, IA-placeholder-as-name), tombstone/perimeter subject -> event_log, New/Update/cooldown decision + audit-row wiring (all redundant with tests/test_fire_native_growth.py's coverage of gating.fire.decide() through the real native adapter). - test_tombstone_broadcast.py::test_commit_callback_flips_handled -- asserted handle_wfigs's own event_log-row flip on commit, a Central-only concept the native path never used. - test_tail_followups.py::test_wfigs_tombstone_stamps_column -- asserted handle_wfigs's own inline tombstoned_at UPDATE; no live producer emits _kind=wfigs_tombstone today, so nothing in the live system stamps it. Both live renderers stay covered: fire_format (wildfire_declared/incident, via test_fire_refactor.py + test_fire_native_growth.py, untouched) and _render (wildfire_growth via FIRMS, via test_fire_tracker_phase2.py + test_firms_native_fusion.py's TestIngestGrowth, both driving the live ingest_hotspot_pixel entrypoint). Collection: 2010 -> 1974 tests (net -36: dead-only tests deleted, live behavior rewired 1:1 or consolidated onto already-existing native-path coverage). Full suite: 1974 passed, 0 failed. 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-17 22:11:00 -06:00
gate = fire_decide({"_kind": "wfigs_tombstone", "irwin_id": "FIRE-002"},
source="wfigs", now=float(now))
assert gate.broadcast is True
assert callable(gate.commit), "commit callback missing"
assert gate.data_patch.get("_cooldown_suffix") == "FIRE-002"
def test_dedup_suffix_is_closed(self):
conn = get_db()
now = int(time.time())
_seed_fire(conn, irwin_id="FIRE-003", name="Dedup Fire",
acres=200, contained=100,
last_broadcast_at=now - 600)
chore(central-ripout 2d-ii): remove dead fire scraps (NOT the 'rewrite' — there wasn't one) (#166) * chore(fire-ripout 2dii-a): remove dead wildfire_growth cutover path + handle_firms wildfire_growth events are always fully precomposed (env/firms.py sets _meshai_precomposed=True + title=<wire from _render()>) and the category is not in cutover.NATIVE_ALWAYS_DECIDE, so the registered fire formatter could never be reached live via compose_mesh_message for this category -- the precomposed-title bypass always won first. Removes the dead is_cutover("wildfire_growth") NEW-PATH branch in fire_fusion._handle_pass_boundary (kept the live legacy leg that calls _render unconditionally) and the now-unreachable wildfire_growth formatter registration in notifications/formatters/__init__.py. Also removes handle_firms, the dead Central NATS-envelope entrypoint (zero live production callers -- Central's consumer that drove it is gone), plus its envelope-specific filtering helpers (_confidence_passes, _in_bbox, _coerce_severity, _log_event, FIRMS_CONFIDENCE_FLOOR/FRP_FLOOR/BBOX_OPTIONAL) that had no live callers left. The shared, LIVE fusion core (_ingest_pixel_core, attribution, clustering, growth/spotting/halt) and _parse_acq_epoch are unaffected -- still the engine behind the native ingest_hotspot_pixel entrypoint. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(fire-ripout 2dii-b): remove dead handle_wfigs entrypoint handle_wfigs (the dead Central NATS-envelope entrypoint) had zero live production callers -- Central's consumer that drove it is gone. The LIVE WFIGS path is env/fires.py (native adapter) -> env/store.py::_emit_event, forced onto gating.fire.decide + the shared fire formatter via cutover.NATIVE_ALWAYS_DECIDE, independent of this dead handler. Removes handle_wfigs and its private-only helpers (_coerce_severity, _log_event, _log_event_returning_id) that had no callers left. _render remains -- it is called directly by env.fire_fusion._handle_pass_boundary on the FIRMS wildfire_growth path, and is used as a byte-identity oracle by tests against the shared, live fire formatter. _build_canonical, _attach_commit_handles, _fire_too_old_to_announce, _now, _cleanup_stale_fires and WFIGS_BROADCAST_COOLDOWN_S are kept -- each has its own direct test coverage independent of handle_wfigs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(fire-ripout 2dii-c): rewire fire tests off the deleted dead entrypoints handle_firms and handle_wfigs (both removed in the prior two commits) were used throughout this test suite purely as convenient DRIVERS for live logic (pixel attribution/clustering/growth/spotting/halt fusion; the WFIGS New/Update/cooldown/closed decider). Per-file disposition: Rewired to drive the LIVE native entrypoint directly (no behavior change -- same underlying _ingest_pixel_core / gating.fire.decide engines): - test_fire_tracker_phase1/2/3.py: handle_firms -> ingest_hotspot_pixel - test_firms_refactor.py: handle_firms driver helpers -> ingest_hotspot_pixel; dropped the dead wildfire_growth cutover test + the fully-redundant TestNotCutoverLegacyVerbatim class (already covered by test_firms_native_fusion.py's TestIngestGrowth/Spotting/Halt against the live entrypoint); wildfire_growth formatter registration test now asserts None (matches source change). - test_wfigs_handler.py: handle_wfigs -> _render (the live WFIGS renderer) called directly, for the anchor-priority + missing-acres cases that exercise shared/live code (_location_anchor). - test_fire_refactor.py: handle_wfigs -> gating.fire.decide() driven directly, with state written the same unconditional shape the live native path uses; TestGateSequenceParity trimmed to focus on the tombstone/closed lifecycle step (not covered by test_fire_native_growth.py's native-adapter New/Update/cooldown coverage). - test_tombstone_broadcast.py, test_fire_age_gate.py: handle_wfigs -> gating.fire.decide() driven directly; same assertions, off the dead path. Deleted as pure dead-entrypoint contract testing with no live equivalent: - test_firms_native_fusion.py::TestCentralPathUnchanged (2 tests) -- existed only to guard handle_firms's own envelope parsing. - test_firms_handler.py: confidence/FRP/bbox filtering, missing-coords, non-firms-adapter guard, event_log accounting (all handle_firms-specific; the native adapter filters upstream in a different code path). Kept + rewired: the shared _ingest_pixel_core dedup behavior and _parse_acq_epoch's int/short acq_time parsing (both genuinely live/shared). - test_wfigs_handler.py: envelope field-extraction (acres-fallback chain, IA-placeholder-as-name), tombstone/perimeter subject -> event_log, New/Update/cooldown decision + audit-row wiring (all redundant with tests/test_fire_native_growth.py's coverage of gating.fire.decide() through the real native adapter). - test_tombstone_broadcast.py::test_commit_callback_flips_handled -- asserted handle_wfigs's own event_log-row flip on commit, a Central-only concept the native path never used. - test_tail_followups.py::test_wfigs_tombstone_stamps_column -- asserted handle_wfigs's own inline tombstoned_at UPDATE; no live producer emits _kind=wfigs_tombstone today, so nothing in the live system stamps it. Both live renderers stay covered: fire_format (wildfire_declared/incident, via test_fire_refactor.py + test_fire_native_growth.py, untouched) and _render (wildfire_growth via FIRMS, via test_fire_tracker_phase2.py + test_firms_native_fusion.py's TestIngestGrowth, both driving the live ingest_hotspot_pixel entrypoint). Collection: 2010 -> 1974 tests (net -36: dead-only tests deleted, live behavior rewired 1:1 or consolidated onto already-existing native-path coverage). Full suite: 1974 passed, 0 failed. 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-17 22:11:00 -06:00
gate = fire_decide({"_kind": "wfigs_tombstone", "irwin_id": "FIRE-003"},
source="wfigs", now=float(now))
assert gate.broadcast is True
assert gate.data_patch.get("_dedup_suffix") == "closed", (
f"expected 'closed', got {gate.data_patch.get('_dedup_suffix')}")
class TestTombstoneAfterNewBroadcast:
chore(central-ripout 2d-ii): remove dead fire scraps (NOT the 'rewrite' — there wasn't one) (#166) * chore(fire-ripout 2dii-a): remove dead wildfire_growth cutover path + handle_firms wildfire_growth events are always fully precomposed (env/firms.py sets _meshai_precomposed=True + title=<wire from _render()>) and the category is not in cutover.NATIVE_ALWAYS_DECIDE, so the registered fire formatter could never be reached live via compose_mesh_message for this category -- the precomposed-title bypass always won first. Removes the dead is_cutover("wildfire_growth") NEW-PATH branch in fire_fusion._handle_pass_boundary (kept the live legacy leg that calls _render unconditionally) and the now-unreachable wildfire_growth formatter registration in notifications/formatters/__init__.py. Also removes handle_firms, the dead Central NATS-envelope entrypoint (zero live production callers -- Central's consumer that drove it is gone), plus its envelope-specific filtering helpers (_confidence_passes, _in_bbox, _coerce_severity, _log_event, FIRMS_CONFIDENCE_FLOOR/FRP_FLOOR/BBOX_OPTIONAL) that had no live callers left. The shared, LIVE fusion core (_ingest_pixel_core, attribution, clustering, growth/spotting/halt) and _parse_acq_epoch are unaffected -- still the engine behind the native ingest_hotspot_pixel entrypoint. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(fire-ripout 2dii-b): remove dead handle_wfigs entrypoint handle_wfigs (the dead Central NATS-envelope entrypoint) had zero live production callers -- Central's consumer that drove it is gone. The LIVE WFIGS path is env/fires.py (native adapter) -> env/store.py::_emit_event, forced onto gating.fire.decide + the shared fire formatter via cutover.NATIVE_ALWAYS_DECIDE, independent of this dead handler. Removes handle_wfigs and its private-only helpers (_coerce_severity, _log_event, _log_event_returning_id) that had no callers left. _render remains -- it is called directly by env.fire_fusion._handle_pass_boundary on the FIRMS wildfire_growth path, and is used as a byte-identity oracle by tests against the shared, live fire formatter. _build_canonical, _attach_commit_handles, _fire_too_old_to_announce, _now, _cleanup_stale_fires and WFIGS_BROADCAST_COOLDOWN_S are kept -- each has its own direct test coverage independent of handle_wfigs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(fire-ripout 2dii-c): rewire fire tests off the deleted dead entrypoints handle_firms and handle_wfigs (both removed in the prior two commits) were used throughout this test suite purely as convenient DRIVERS for live logic (pixel attribution/clustering/growth/spotting/halt fusion; the WFIGS New/Update/cooldown/closed decider). Per-file disposition: Rewired to drive the LIVE native entrypoint directly (no behavior change -- same underlying _ingest_pixel_core / gating.fire.decide engines): - test_fire_tracker_phase1/2/3.py: handle_firms -> ingest_hotspot_pixel - test_firms_refactor.py: handle_firms driver helpers -> ingest_hotspot_pixel; dropped the dead wildfire_growth cutover test + the fully-redundant TestNotCutoverLegacyVerbatim class (already covered by test_firms_native_fusion.py's TestIngestGrowth/Spotting/Halt against the live entrypoint); wildfire_growth formatter registration test now asserts None (matches source change). - test_wfigs_handler.py: handle_wfigs -> _render (the live WFIGS renderer) called directly, for the anchor-priority + missing-acres cases that exercise shared/live code (_location_anchor). - test_fire_refactor.py: handle_wfigs -> gating.fire.decide() driven directly, with state written the same unconditional shape the live native path uses; TestGateSequenceParity trimmed to focus on the tombstone/closed lifecycle step (not covered by test_fire_native_growth.py's native-adapter New/Update/cooldown coverage). - test_tombstone_broadcast.py, test_fire_age_gate.py: handle_wfigs -> gating.fire.decide() driven directly; same assertions, off the dead path. Deleted as pure dead-entrypoint contract testing with no live equivalent: - test_firms_native_fusion.py::TestCentralPathUnchanged (2 tests) -- existed only to guard handle_firms's own envelope parsing. - test_firms_handler.py: confidence/FRP/bbox filtering, missing-coords, non-firms-adapter guard, event_log accounting (all handle_firms-specific; the native adapter filters upstream in a different code path). Kept + rewired: the shared _ingest_pixel_core dedup behavior and _parse_acq_epoch's int/short acq_time parsing (both genuinely live/shared). - test_wfigs_handler.py: envelope field-extraction (acres-fallback chain, IA-placeholder-as-name), tombstone/perimeter subject -> event_log, New/Update/cooldown decision + audit-row wiring (all redundant with tests/test_fire_native_growth.py's coverage of gating.fire.decide() through the real native adapter). - test_tombstone_broadcast.py::test_commit_callback_flips_handled -- asserted handle_wfigs's own event_log-row flip on commit, a Central-only concept the native path never used. - test_tail_followups.py::test_wfigs_tombstone_stamps_column -- asserted handle_wfigs's own inline tombstoned_at UPDATE; no live producer emits _kind=wfigs_tombstone today, so nothing in the live system stamps it. Both live renderers stay covered: fire_format (wildfire_declared/incident, via test_fire_refactor.py + test_fire_native_growth.py, untouched) and _render (wildfire_growth via FIRMS, via test_fire_tracker_phase2.py + test_firms_native_fusion.py's TestIngestGrowth, both driving the live ingest_hotspot_pixel entrypoint). Collection: 2010 -> 1974 tests (net -36: dead-only tests deleted, live behavior rewired 1:1 or consolidated onto already-existing native-path coverage). Full suite: 1974 passed, 0 failed. 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-17 22:11:00 -06:00
"""T2: closure dispatches when a New broadcast went out earlier.
Drives gating.fire.decide() directly (chore/ripout-2dii: handle_wfigs is
gone). The wire itself is rendered by the shared, LIVE fire formatter
(notifications/formatters/fire.py::format) from decide()'s data_patch --
the same contract the native WFIGS path uses.
"""
def test_closure_wire_after_prior_broadcast(self):
"""Fire that was broadcast 10 min ago gets a closure wire on tombstone."""
chore(central-ripout 2d-ii): remove dead fire scraps (NOT the 'rewrite' — there wasn't one) (#166) * chore(fire-ripout 2dii-a): remove dead wildfire_growth cutover path + handle_firms wildfire_growth events are always fully precomposed (env/firms.py sets _meshai_precomposed=True + title=<wire from _render()>) and the category is not in cutover.NATIVE_ALWAYS_DECIDE, so the registered fire formatter could never be reached live via compose_mesh_message for this category -- the precomposed-title bypass always won first. Removes the dead is_cutover("wildfire_growth") NEW-PATH branch in fire_fusion._handle_pass_boundary (kept the live legacy leg that calls _render unconditionally) and the now-unreachable wildfire_growth formatter registration in notifications/formatters/__init__.py. Also removes handle_firms, the dead Central NATS-envelope entrypoint (zero live production callers -- Central's consumer that drove it is gone), plus its envelope-specific filtering helpers (_confidence_passes, _in_bbox, _coerce_severity, _log_event, FIRMS_CONFIDENCE_FLOOR/FRP_FLOOR/BBOX_OPTIONAL) that had no live callers left. The shared, LIVE fusion core (_ingest_pixel_core, attribution, clustering, growth/spotting/halt) and _parse_acq_epoch are unaffected -- still the engine behind the native ingest_hotspot_pixel entrypoint. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(fire-ripout 2dii-b): remove dead handle_wfigs entrypoint handle_wfigs (the dead Central NATS-envelope entrypoint) had zero live production callers -- Central's consumer that drove it is gone. The LIVE WFIGS path is env/fires.py (native adapter) -> env/store.py::_emit_event, forced onto gating.fire.decide + the shared fire formatter via cutover.NATIVE_ALWAYS_DECIDE, independent of this dead handler. Removes handle_wfigs and its private-only helpers (_coerce_severity, _log_event, _log_event_returning_id) that had no callers left. _render remains -- it is called directly by env.fire_fusion._handle_pass_boundary on the FIRMS wildfire_growth path, and is used as a byte-identity oracle by tests against the shared, live fire formatter. _build_canonical, _attach_commit_handles, _fire_too_old_to_announce, _now, _cleanup_stale_fires and WFIGS_BROADCAST_COOLDOWN_S are kept -- each has its own direct test coverage independent of handle_wfigs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(fire-ripout 2dii-c): rewire fire tests off the deleted dead entrypoints handle_firms and handle_wfigs (both removed in the prior two commits) were used throughout this test suite purely as convenient DRIVERS for live logic (pixel attribution/clustering/growth/spotting/halt fusion; the WFIGS New/Update/cooldown/closed decider). Per-file disposition: Rewired to drive the LIVE native entrypoint directly (no behavior change -- same underlying _ingest_pixel_core / gating.fire.decide engines): - test_fire_tracker_phase1/2/3.py: handle_firms -> ingest_hotspot_pixel - test_firms_refactor.py: handle_firms driver helpers -> ingest_hotspot_pixel; dropped the dead wildfire_growth cutover test + the fully-redundant TestNotCutoverLegacyVerbatim class (already covered by test_firms_native_fusion.py's TestIngestGrowth/Spotting/Halt against the live entrypoint); wildfire_growth formatter registration test now asserts None (matches source change). - test_wfigs_handler.py: handle_wfigs -> _render (the live WFIGS renderer) called directly, for the anchor-priority + missing-acres cases that exercise shared/live code (_location_anchor). - test_fire_refactor.py: handle_wfigs -> gating.fire.decide() driven directly, with state written the same unconditional shape the live native path uses; TestGateSequenceParity trimmed to focus on the tombstone/closed lifecycle step (not covered by test_fire_native_growth.py's native-adapter New/Update/cooldown coverage). - test_tombstone_broadcast.py, test_fire_age_gate.py: handle_wfigs -> gating.fire.decide() driven directly; same assertions, off the dead path. Deleted as pure dead-entrypoint contract testing with no live equivalent: - test_firms_native_fusion.py::TestCentralPathUnchanged (2 tests) -- existed only to guard handle_firms's own envelope parsing. - test_firms_handler.py: confidence/FRP/bbox filtering, missing-coords, non-firms-adapter guard, event_log accounting (all handle_firms-specific; the native adapter filters upstream in a different code path). Kept + rewired: the shared _ingest_pixel_core dedup behavior and _parse_acq_epoch's int/short acq_time parsing (both genuinely live/shared). - test_wfigs_handler.py: envelope field-extraction (acres-fallback chain, IA-placeholder-as-name), tombstone/perimeter subject -> event_log, New/Update/cooldown decision + audit-row wiring (all redundant with tests/test_fire_native_growth.py's coverage of gating.fire.decide() through the real native adapter). - test_tombstone_broadcast.py::test_commit_callback_flips_handled -- asserted handle_wfigs's own event_log-row flip on commit, a Central-only concept the native path never used. - test_tail_followups.py::test_wfigs_tombstone_stamps_column -- asserted handle_wfigs's own inline tombstoned_at UPDATE; no live producer emits _kind=wfigs_tombstone today, so nothing in the live system stamps it. Both live renderers stay covered: fire_format (wildfire_declared/incident, via test_fire_refactor.py + test_fire_native_growth.py, untouched) and _render (wildfire_growth via FIRMS, via test_fire_tracker_phase2.py + test_firms_native_fusion.py's TestIngestGrowth, both driving the live ingest_hotspot_pixel entrypoint). Collection: 2010 -> 1974 tests (net -36: dead-only tests deleted, live behavior rewired 1:1 or consolidated onto already-existing native-path coverage). Full suite: 1974 passed, 0 failed. 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-17 22:11:00 -06:00
from meshai.notifications.formatters.fire import format as fire_format
from meshai.notifications.formatters._budget import budget_for
class _FakeEvent:
def __init__(self, data, category=None):
self.data = data
self.category = category
conn = get_db()
now = int(time.time())
_seed_fire(conn, irwin_id="IA-1", name="IA 1",
acres=0.1, contained=None,
last_broadcast_at=now - 600, # 10 min ago
last_event_at=now - 600)
chore(central-ripout 2d-ii): remove dead fire scraps (NOT the 'rewrite' — there wasn't one) (#166) * chore(fire-ripout 2dii-a): remove dead wildfire_growth cutover path + handle_firms wildfire_growth events are always fully precomposed (env/firms.py sets _meshai_precomposed=True + title=<wire from _render()>) and the category is not in cutover.NATIVE_ALWAYS_DECIDE, so the registered fire formatter could never be reached live via compose_mesh_message for this category -- the precomposed-title bypass always won first. Removes the dead is_cutover("wildfire_growth") NEW-PATH branch in fire_fusion._handle_pass_boundary (kept the live legacy leg that calls _render unconditionally) and the now-unreachable wildfire_growth formatter registration in notifications/formatters/__init__.py. Also removes handle_firms, the dead Central NATS-envelope entrypoint (zero live production callers -- Central's consumer that drove it is gone), plus its envelope-specific filtering helpers (_confidence_passes, _in_bbox, _coerce_severity, _log_event, FIRMS_CONFIDENCE_FLOOR/FRP_FLOOR/BBOX_OPTIONAL) that had no live callers left. The shared, LIVE fusion core (_ingest_pixel_core, attribution, clustering, growth/spotting/halt) and _parse_acq_epoch are unaffected -- still the engine behind the native ingest_hotspot_pixel entrypoint. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(fire-ripout 2dii-b): remove dead handle_wfigs entrypoint handle_wfigs (the dead Central NATS-envelope entrypoint) had zero live production callers -- Central's consumer that drove it is gone. The LIVE WFIGS path is env/fires.py (native adapter) -> env/store.py::_emit_event, forced onto gating.fire.decide + the shared fire formatter via cutover.NATIVE_ALWAYS_DECIDE, independent of this dead handler. Removes handle_wfigs and its private-only helpers (_coerce_severity, _log_event, _log_event_returning_id) that had no callers left. _render remains -- it is called directly by env.fire_fusion._handle_pass_boundary on the FIRMS wildfire_growth path, and is used as a byte-identity oracle by tests against the shared, live fire formatter. _build_canonical, _attach_commit_handles, _fire_too_old_to_announce, _now, _cleanup_stale_fires and WFIGS_BROADCAST_COOLDOWN_S are kept -- each has its own direct test coverage independent of handle_wfigs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(fire-ripout 2dii-c): rewire fire tests off the deleted dead entrypoints handle_firms and handle_wfigs (both removed in the prior two commits) were used throughout this test suite purely as convenient DRIVERS for live logic (pixel attribution/clustering/growth/spotting/halt fusion; the WFIGS New/Update/cooldown/closed decider). Per-file disposition: Rewired to drive the LIVE native entrypoint directly (no behavior change -- same underlying _ingest_pixel_core / gating.fire.decide engines): - test_fire_tracker_phase1/2/3.py: handle_firms -> ingest_hotspot_pixel - test_firms_refactor.py: handle_firms driver helpers -> ingest_hotspot_pixel; dropped the dead wildfire_growth cutover test + the fully-redundant TestNotCutoverLegacyVerbatim class (already covered by test_firms_native_fusion.py's TestIngestGrowth/Spotting/Halt against the live entrypoint); wildfire_growth formatter registration test now asserts None (matches source change). - test_wfigs_handler.py: handle_wfigs -> _render (the live WFIGS renderer) called directly, for the anchor-priority + missing-acres cases that exercise shared/live code (_location_anchor). - test_fire_refactor.py: handle_wfigs -> gating.fire.decide() driven directly, with state written the same unconditional shape the live native path uses; TestGateSequenceParity trimmed to focus on the tombstone/closed lifecycle step (not covered by test_fire_native_growth.py's native-adapter New/Update/cooldown coverage). - test_tombstone_broadcast.py, test_fire_age_gate.py: handle_wfigs -> gating.fire.decide() driven directly; same assertions, off the dead path. Deleted as pure dead-entrypoint contract testing with no live equivalent: - test_firms_native_fusion.py::TestCentralPathUnchanged (2 tests) -- existed only to guard handle_firms's own envelope parsing. - test_firms_handler.py: confidence/FRP/bbox filtering, missing-coords, non-firms-adapter guard, event_log accounting (all handle_firms-specific; the native adapter filters upstream in a different code path). Kept + rewired: the shared _ingest_pixel_core dedup behavior and _parse_acq_epoch's int/short acq_time parsing (both genuinely live/shared). - test_wfigs_handler.py: envelope field-extraction (acres-fallback chain, IA-placeholder-as-name), tombstone/perimeter subject -> event_log, New/Update/cooldown decision + audit-row wiring (all redundant with tests/test_fire_native_growth.py's coverage of gating.fire.decide() through the real native adapter). - test_tombstone_broadcast.py::test_commit_callback_flips_handled -- asserted handle_wfigs's own event_log-row flip on commit, a Central-only concept the native path never used. - test_tail_followups.py::test_wfigs_tombstone_stamps_column -- asserted handle_wfigs's own inline tombstoned_at UPDATE; no live producer emits _kind=wfigs_tombstone today, so nothing in the live system stamps it. Both live renderers stay covered: fire_format (wildfire_declared/incident, via test_fire_refactor.py + test_fire_native_growth.py, untouched) and _render (wildfire_growth via FIRMS, via test_fire_tracker_phase2.py + test_firms_native_fusion.py's TestIngestGrowth, both driving the live ingest_hotspot_pixel entrypoint). Collection: 2010 -> 1974 tests (net -36: dead-only tests deleted, live behavior rewired 1:1 or consolidated onto already-existing native-path coverage). Full suite: 1974 passed, 0 failed. 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-17 22:11:00 -06:00
gate = fire_decide({"_kind": "wfigs_tombstone", "irwin_id": "IA-1"},
source="wfigs", now=float(now))
assert gate.broadcast is True, "tombstone should broadcast"
assert gate.data_patch["category"] == "wildfire_closed"
test: fix 13 stale tests in the red suite, leave 6 real-bug failures (#124) (#129) Triaged all 20 known-red tests. 13 were stale tests asserting rotted expectations against deliberate, documented behavior changes; fixed by deriving expected values instead of hard-coding, or updating the expectation to match a documented policy change: - test_adapter_config_foundation.py / test_adapter_config_api.py: REGISTRY/API key-count and key-set guards hard-coded magic numbers (59/94/17) that rotted repeatedly. Now derive expectations from REGISTRY itself and, for the schema version, from the migrations directory, so they can't rot the same way again. - test_fire_tracker_phase4.py: two tests hardcoded a nonexistent deployment path (/opt/meshai/meshai/router.py) that matches no Dockerfile WORKDIR in this repo; resolve the module path via importlib.util.find_spec instead. - test_tombstone_broadcast.py: asserted fire severity == "immediate", which commit 2f677e85 deliberately downgraded to "priority" (to stop fire broadcasts bypassing the Grouper/cooldown during NATS backlog replay) without updating this test. - test_pipeline_grouper.py: test_immediate_severity_bypasses_grouper asserted an immediate-severity bypass that commit 85d48ce3 ("fix(fire): remove immediate-severity exemption from grouper + cooldown") DELETED on purpose -- fire events carry _severity_override="immediate", and the exemption left fire with no rate control at all in normal live operation. Re-adding the bypass would re-open that fire-spam hole on a public-safety mesh, so the test moves, not the source. Renamed + inverted to assert the real contract (all severities coalesce; only a missing group_key passes through). - test_tail_followups.py: dispatcher mock was missing dispatch_scheduled_fire_broadcast (a method added alongside the generic dispatch_scheduled_broadcast; test_reminders.py already mocks both). - test_tracking_v057.py: guard required an empty tracking-family adapter list in Environment.tsx, but the frontend has long grouped the pre-existing native satpass adapter under the "Tracking" display section (its own "satpass" backend toggle, unrelated to the Phase-7 tracking family every other guard in this file confirms is still unimplemented). Narrowed the guard to allow only that known entry. - test_v052_dispatcher.py: two tests used category="wildfire_incident", which the phase3b fire migration (#33) forced onto a dedicated formatter via NATIVE_ALWAYS_DECIDE; swapped to wildfire_hotspot (same emoji/label, not in NATIVE_ALWAYS_DECIDE) to keep exercising the generic composer logic under test. Also fixes one stale COMMENT (comment-only, no logic change) in meshai/notifications/pipeline/__init__.py's start_pipeline(): it still claimed "Immediate events bypass the grouper and don't need this [periodic flush]", which has been false since 85d48ce3 and is precisely what makes the deleted bypass look like a missing feature. The comment now records that the removal was deliberate and must not be reverted. The remaining 6 failures are left untouched -- 2 confirmed real bugs, to be fixed deliberately in their own changes: - meshcore_transport.py defines `_resolve_contact` TWICE on MeshCoreTransport (line 171 from PR #56, line 1227 from PR #92). The second silently shadows the first, so the DM contact-resolution refetch-on-miss that #56 added is dead code in production. (3 tests) - SCHEMA_VERSION (persistence/db.py:33) is stale at 26 vs. the actual highest migration v28; v27 and v28 shipped without bumping it. (3 tests) Plus 1 environment gap, not a code defect: test_natural_language_fire_question_routes_to_llm needs the `openai` package, which is declared in requirements.txt but not installed here. Suite: 20 failed, 2240 passed, 72 skipped -> 7 failed, 2254 passed, 72 skipped. All 7 remaining failures are ones classified above; no new failures introduced elsewhere in the suite. Co-authored-by: Matt Johnson <mj@k7zvx.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 10:36:19 -06:00
# See module docstring: downgraded from "immediate" to "priority"
# by commit 2f677e85 to prevent Grouper/cooldown bypass.
chore(central-ripout 2d-ii): remove dead fire scraps (NOT the 'rewrite' — there wasn't one) (#166) * chore(fire-ripout 2dii-a): remove dead wildfire_growth cutover path + handle_firms wildfire_growth events are always fully precomposed (env/firms.py sets _meshai_precomposed=True + title=<wire from _render()>) and the category is not in cutover.NATIVE_ALWAYS_DECIDE, so the registered fire formatter could never be reached live via compose_mesh_message for this category -- the precomposed-title bypass always won first. Removes the dead is_cutover("wildfire_growth") NEW-PATH branch in fire_fusion._handle_pass_boundary (kept the live legacy leg that calls _render unconditionally) and the now-unreachable wildfire_growth formatter registration in notifications/formatters/__init__.py. Also removes handle_firms, the dead Central NATS-envelope entrypoint (zero live production callers -- Central's consumer that drove it is gone), plus its envelope-specific filtering helpers (_confidence_passes, _in_bbox, _coerce_severity, _log_event, FIRMS_CONFIDENCE_FLOOR/FRP_FLOOR/BBOX_OPTIONAL) that had no live callers left. The shared, LIVE fusion core (_ingest_pixel_core, attribution, clustering, growth/spotting/halt) and _parse_acq_epoch are unaffected -- still the engine behind the native ingest_hotspot_pixel entrypoint. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(fire-ripout 2dii-b): remove dead handle_wfigs entrypoint handle_wfigs (the dead Central NATS-envelope entrypoint) had zero live production callers -- Central's consumer that drove it is gone. The LIVE WFIGS path is env/fires.py (native adapter) -> env/store.py::_emit_event, forced onto gating.fire.decide + the shared fire formatter via cutover.NATIVE_ALWAYS_DECIDE, independent of this dead handler. Removes handle_wfigs and its private-only helpers (_coerce_severity, _log_event, _log_event_returning_id) that had no callers left. _render remains -- it is called directly by env.fire_fusion._handle_pass_boundary on the FIRMS wildfire_growth path, and is used as a byte-identity oracle by tests against the shared, live fire formatter. _build_canonical, _attach_commit_handles, _fire_too_old_to_announce, _now, _cleanup_stale_fires and WFIGS_BROADCAST_COOLDOWN_S are kept -- each has its own direct test coverage independent of handle_wfigs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(fire-ripout 2dii-c): rewire fire tests off the deleted dead entrypoints handle_firms and handle_wfigs (both removed in the prior two commits) were used throughout this test suite purely as convenient DRIVERS for live logic (pixel attribution/clustering/growth/spotting/halt fusion; the WFIGS New/Update/cooldown/closed decider). Per-file disposition: Rewired to drive the LIVE native entrypoint directly (no behavior change -- same underlying _ingest_pixel_core / gating.fire.decide engines): - test_fire_tracker_phase1/2/3.py: handle_firms -> ingest_hotspot_pixel - test_firms_refactor.py: handle_firms driver helpers -> ingest_hotspot_pixel; dropped the dead wildfire_growth cutover test + the fully-redundant TestNotCutoverLegacyVerbatim class (already covered by test_firms_native_fusion.py's TestIngestGrowth/Spotting/Halt against the live entrypoint); wildfire_growth formatter registration test now asserts None (matches source change). - test_wfigs_handler.py: handle_wfigs -> _render (the live WFIGS renderer) called directly, for the anchor-priority + missing-acres cases that exercise shared/live code (_location_anchor). - test_fire_refactor.py: handle_wfigs -> gating.fire.decide() driven directly, with state written the same unconditional shape the live native path uses; TestGateSequenceParity trimmed to focus on the tombstone/closed lifecycle step (not covered by test_fire_native_growth.py's native-adapter New/Update/cooldown coverage). - test_tombstone_broadcast.py, test_fire_age_gate.py: handle_wfigs -> gating.fire.decide() driven directly; same assertions, off the dead path. Deleted as pure dead-entrypoint contract testing with no live equivalent: - test_firms_native_fusion.py::TestCentralPathUnchanged (2 tests) -- existed only to guard handle_firms's own envelope parsing. - test_firms_handler.py: confidence/FRP/bbox filtering, missing-coords, non-firms-adapter guard, event_log accounting (all handle_firms-specific; the native adapter filters upstream in a different code path). Kept + rewired: the shared _ingest_pixel_core dedup behavior and _parse_acq_epoch's int/short acq_time parsing (both genuinely live/shared). - test_wfigs_handler.py: envelope field-extraction (acres-fallback chain, IA-placeholder-as-name), tombstone/perimeter subject -> event_log, New/Update/cooldown decision + audit-row wiring (all redundant with tests/test_fire_native_growth.py's coverage of gating.fire.decide() through the real native adapter). - test_tombstone_broadcast.py::test_commit_callback_flips_handled -- asserted handle_wfigs's own event_log-row flip on commit, a Central-only concept the native path never used. - test_tail_followups.py::test_wfigs_tombstone_stamps_column -- asserted handle_wfigs's own inline tombstoned_at UPDATE; no live producer emits _kind=wfigs_tombstone today, so nothing in the live system stamps it. Both live renderers stay covered: fire_format (wildfire_declared/incident, via test_fire_refactor.py + test_fire_native_growth.py, untouched) and _render (wildfire_growth via FIRMS, via test_fire_tracker_phase2.py + test_firms_native_fusion.py's TestIngestGrowth, both driving the live ingest_hotspot_pixel entrypoint). Collection: 2010 -> 1974 tests (net -36: dead-only tests deleted, live behavior rewired 1:1 or consolidated onto already-existing native-path coverage). Full suite: 1974 passed, 0 failed. 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-17 22:11:00 -06:00
assert gate.data_patch["_severity_override"] == "priority"
assert callable(gate.commit)
wire = fire_format(_FakeEvent(gate.data_patch, category="wildfire_closed"),
now=0.0, budget=budget_for("wfigs"))
assert "" in wire, "closure wire should contain checkmark"
assert "IA 1" in wire, "closure wire should name the fire"
def test_no_wire_when_never_broadcast(self):
"""Fire that was never broadcast should NOT get a closure wire."""
conn = get_db()
now = int(time.time())
_seed_fire(conn, irwin_id="SILENT-1", name="Silent Fire",
acres=5, contained=None,
last_broadcast_at=None, # never broadcast
last_event_at=now - 600)
chore(central-ripout 2d-ii): remove dead fire scraps (NOT the 'rewrite' — there wasn't one) (#166) * chore(fire-ripout 2dii-a): remove dead wildfire_growth cutover path + handle_firms wildfire_growth events are always fully precomposed (env/firms.py sets _meshai_precomposed=True + title=<wire from _render()>) and the category is not in cutover.NATIVE_ALWAYS_DECIDE, so the registered fire formatter could never be reached live via compose_mesh_message for this category -- the precomposed-title bypass always won first. Removes the dead is_cutover("wildfire_growth") NEW-PATH branch in fire_fusion._handle_pass_boundary (kept the live legacy leg that calls _render unconditionally) and the now-unreachable wildfire_growth formatter registration in notifications/formatters/__init__.py. Also removes handle_firms, the dead Central NATS-envelope entrypoint (zero live production callers -- Central's consumer that drove it is gone), plus its envelope-specific filtering helpers (_confidence_passes, _in_bbox, _coerce_severity, _log_event, FIRMS_CONFIDENCE_FLOOR/FRP_FLOOR/BBOX_OPTIONAL) that had no live callers left. The shared, LIVE fusion core (_ingest_pixel_core, attribution, clustering, growth/spotting/halt) and _parse_acq_epoch are unaffected -- still the engine behind the native ingest_hotspot_pixel entrypoint. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(fire-ripout 2dii-b): remove dead handle_wfigs entrypoint handle_wfigs (the dead Central NATS-envelope entrypoint) had zero live production callers -- Central's consumer that drove it is gone. The LIVE WFIGS path is env/fires.py (native adapter) -> env/store.py::_emit_event, forced onto gating.fire.decide + the shared fire formatter via cutover.NATIVE_ALWAYS_DECIDE, independent of this dead handler. Removes handle_wfigs and its private-only helpers (_coerce_severity, _log_event, _log_event_returning_id) that had no callers left. _render remains -- it is called directly by env.fire_fusion._handle_pass_boundary on the FIRMS wildfire_growth path, and is used as a byte-identity oracle by tests against the shared, live fire formatter. _build_canonical, _attach_commit_handles, _fire_too_old_to_announce, _now, _cleanup_stale_fires and WFIGS_BROADCAST_COOLDOWN_S are kept -- each has its own direct test coverage independent of handle_wfigs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(fire-ripout 2dii-c): rewire fire tests off the deleted dead entrypoints handle_firms and handle_wfigs (both removed in the prior two commits) were used throughout this test suite purely as convenient DRIVERS for live logic (pixel attribution/clustering/growth/spotting/halt fusion; the WFIGS New/Update/cooldown/closed decider). Per-file disposition: Rewired to drive the LIVE native entrypoint directly (no behavior change -- same underlying _ingest_pixel_core / gating.fire.decide engines): - test_fire_tracker_phase1/2/3.py: handle_firms -> ingest_hotspot_pixel - test_firms_refactor.py: handle_firms driver helpers -> ingest_hotspot_pixel; dropped the dead wildfire_growth cutover test + the fully-redundant TestNotCutoverLegacyVerbatim class (already covered by test_firms_native_fusion.py's TestIngestGrowth/Spotting/Halt against the live entrypoint); wildfire_growth formatter registration test now asserts None (matches source change). - test_wfigs_handler.py: handle_wfigs -> _render (the live WFIGS renderer) called directly, for the anchor-priority + missing-acres cases that exercise shared/live code (_location_anchor). - test_fire_refactor.py: handle_wfigs -> gating.fire.decide() driven directly, with state written the same unconditional shape the live native path uses; TestGateSequenceParity trimmed to focus on the tombstone/closed lifecycle step (not covered by test_fire_native_growth.py's native-adapter New/Update/cooldown coverage). - test_tombstone_broadcast.py, test_fire_age_gate.py: handle_wfigs -> gating.fire.decide() driven directly; same assertions, off the dead path. Deleted as pure dead-entrypoint contract testing with no live equivalent: - test_firms_native_fusion.py::TestCentralPathUnchanged (2 tests) -- existed only to guard handle_firms's own envelope parsing. - test_firms_handler.py: confidence/FRP/bbox filtering, missing-coords, non-firms-adapter guard, event_log accounting (all handle_firms-specific; the native adapter filters upstream in a different code path). Kept + rewired: the shared _ingest_pixel_core dedup behavior and _parse_acq_epoch's int/short acq_time parsing (both genuinely live/shared). - test_wfigs_handler.py: envelope field-extraction (acres-fallback chain, IA-placeholder-as-name), tombstone/perimeter subject -> event_log, New/Update/cooldown decision + audit-row wiring (all redundant with tests/test_fire_native_growth.py's coverage of gating.fire.decide() through the real native adapter). - test_tombstone_broadcast.py::test_commit_callback_flips_handled -- asserted handle_wfigs's own event_log-row flip on commit, a Central-only concept the native path never used. - test_tail_followups.py::test_wfigs_tombstone_stamps_column -- asserted handle_wfigs's own inline tombstoned_at UPDATE; no live producer emits _kind=wfigs_tombstone today, so nothing in the live system stamps it. Both live renderers stay covered: fire_format (wildfire_declared/incident, via test_fire_refactor.py + test_fire_native_growth.py, untouched) and _render (wildfire_growth via FIRMS, via test_fire_tracker_phase2.py + test_firms_native_fusion.py's TestIngestGrowth, both driving the live ingest_hotspot_pixel entrypoint). Collection: 2010 -> 1974 tests (net -36: dead-only tests deleted, live behavior rewired 1:1 or consolidated onto already-existing native-path coverage). Full suite: 1974 passed, 0 failed. 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-17 22:11:00 -06:00
gate = fire_decide({"_kind": "wfigs_tombstone", "irwin_id": "SILENT-1"},
source="wfigs", now=float(now))
assert gate.broadcast is False, "no closure wire for never-broadcast fire"
class TestEnvSummaryExcludesContainedTombstoned:
"""T3: build_env_summary fire count excludes tombstoned and 100%-contained."""
def test_summary_excludes_tombstoned(self, reporter):
conn = get_db()
now = int(time.time())
# Active fire
_seed_fire(conn, irwin_id="ACTIVE-1", name="Active Fire",
acres=500, contained=20,
last_event_at=now - 3600)
# Tombstoned fire
_seed_fire(conn, irwin_id="DEAD-1", name="Dead Fire",
acres=1000, contained=100,
last_event_at=now - 7200,
tombstoned_at=now - 3600)
text = reporter.build_env_summary(now=now)
assert "Active fires (WFIGS, last 7d): 1" in text, (
f"should count 1 active fire, got: {text}")
def test_summary_excludes_100_contained(self, reporter):
conn = get_db()
now = int(time.time())
# Active fire, 50% contained
_seed_fire(conn, irwin_id="HALF-1", name="Half Contained",
acres=300, contained=50,
last_event_at=now - 3600)
# 100% contained, NOT tombstoned
_seed_fire(conn, irwin_id="FULL-1", name="Fully Contained",
acres=800, contained=100,
last_event_at=now - 7200)
text = reporter.build_env_summary(now=now)
assert "Active fires (WFIGS, last 7d): 1" in text, (
f"should count 1 active fire, got: {text}")
def test_summary_includes_null_containment(self, reporter):
conn = get_db()
now = int(time.time())
# Two fires with NULL containment
_seed_fire(conn, irwin_id="NULL-1", name="No Containment 1",
acres=10, contained=None,
last_event_at=now - 3600)
_seed_fire(conn, irwin_id="NULL-2", name="No Containment 2",
acres=20, contained=None,
last_event_at=now - 7200)
text = reporter.build_env_summary(now=now)
assert "Active fires (WFIGS, last 7d): 2" in text, (
f"should count 2 active fires, got: {text}")
def test_summary_empty_when_all_excluded(self, reporter):
conn = get_db()
now = int(time.time())
# Only tombstoned and 100%-contained fires
_seed_fire(conn, irwin_id="EX-1", name="Ex 1",
acres=100, contained=100,
last_event_at=now - 3600,
tombstoned_at=now - 1800)
_seed_fire(conn, irwin_id="EX-2", name="Ex 2",
acres=200, contained=100,
last_event_at=now - 7200)
text = reporter.build_env_summary(now=now)
assert "Active fires" not in text, (
f"should not mention fires when all excluded, got: {text}")