refactor(phase0): source-agnostic formatter/gating scaffold + harness (inert) (#28)

Foundation for making all hazard formatting+gating source-agnostic. ZERO
behavior change — the formatter/decider registries are empty (get_formatter/
get_decider return None → existing precomposed/Mode-B path preserved), and the
shadow comparator is off unless MESHAI_SHADOW_CATEGORIES is set.

- notifications/formatters/ (registry+dispatch with family fallback), gating/
  (GateResult + deferred-commit contract), both empty registries.
- notifications/clock.py determinism seam; route wfigs/quake/nws gating time
  reads through it (identical values) so goldens can freeze time.
- formatters/_budget.py = copy of central/budget.py; central/budget.py is now a
  re-export shim (import-smoke test guards it).
- compose_mesh_message consults the registry first (verbatim, no Mode-B re-cap),
  falls back to legacy; _resolve_budget injects per-category budget.
- notifications/shadow.py + two DRY-RUN hooks (consumer._normalize, dispatcher
  render): compute the new result and diff-log SHADOW_MISMATCH JSONL, but NEVER
  commit/emit/write tables and always broadcast the OLD result. Inert by default.
- tests/harness (pinned_time/pinned_tz, byte-golden, gate-sequence) +
  scripts/capture_fixtures.py (ephemeral read-only NATS capture); tzdata pinned.

Tests: +60 (18 scaffold + 42 harness/shadow); 0 new failures (34 baseline).

Co-authored-by: Matt Johnson <mj@k7zvx.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
malice 2026-07-04 12:55:48 -06:00 committed by GitHub
commit f06bf3ce92
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
26 changed files with 1909 additions and 32 deletions

View file

@ -12,13 +12,47 @@ and clears the persistence-layer threading.local caches around each test.
Existing tests that don't reference any fixture get isolation for free;
tests that explicitly use a `db_path` (or similar) fixture can still
override the env var inside their own fixture body -- last setenv wins.
Phase-0 addition: a session-scoped TZ fixture validates that
America/Boise resolves via zoneinfo, exercising the tzdata guard needed
in CI environments. The fixture saves and restores the original TZ so
that existing tests running with naive datetimes are unaffected.
"""
import os
import time
import zoneinfo
import pytest
from meshai.persistence import close_thread_connection
from meshai.persistence import db as _persistence_db
@pytest.fixture(scope="session", autouse=True)
def _tz_boise():
"""Set TZ=America/Boise, call tzset(), assert zoneinfo resolves, then restore.
This validates that the tzdata package (or system zoneinfo) is available
in CI before any formatter/renderer test runs. The original TZ is restored
so that existing tests using naive datetimes are not affected by the
Mountain Time offset.
"""
original_tz = os.environ.get("TZ")
os.environ["TZ"] = "America/Boise"
time.tzset()
# Assert that zoneinfo can resolve the timezone (requires tzdata package
# or /usr/share/zoneinfo/America/Boise on the system).
zoneinfo.ZoneInfo("America/Boise")
# Restore the original TZ so that tests using naive datetime / time.localtime()
# behave the same as they did before this session fixture was added.
if original_tz is None:
os.environ.pop("TZ", None)
else:
os.environ["TZ"] = original_tz
time.tzset()
yield
@pytest.fixture(autouse=True)
def _isolate_meshai_db(tmp_path, monkeypatch):
"""Point MESHAI_DB_PATH at a tmp file per test + run init_db so the