meshai/tests/test_adapter_config_foundation.py

347 lines
11 KiB
Python
Raw Normal View History

feat(v0.6-3a.1): trim adapter_config registry to CONFIG-only per Matt config-vs-code rule + log-on-delete safety net for orphan cleanup Drops 35 of the v0.6-3a-draft 77 keys + adds 1 net-new key (firms.dedup_distance_m) for a final count of 43. The trim rules: CONFIG (lives in adapter_config, surfaces in the GUI): where we send (channels), how often (cadences/schedules), thresholds (magnitude floors, severity gates, distance radius, cooldown durations, freshness windows), curation data (which sites/states/codes), toggles (enabled, include_in_llm_context, drop_zero_magnitude). CODE (stays in handlers, never reaches the GUI): sentence templates, emoji choices, mapping/translation functions (TomTom icon_map, ITD sub_type_map, Central adapter_map and category_map), rendering logic (anchor priority order, expires-bucket formatting, threshold-state labels), heuristic logic (band_conditions Kp/SFI -> Good/Fair/Poor function). Per-adapter outcome (kept | killed): wfigs 4 | 4 (cooldown_seconds, anchor_max_mi, two re-broadcast toggles) nws 3 | 4 (broadcast_severities, tombstone_msgtypes, warning_suffix_promotes) usgs_quake 6 | 3 (centroid, radius, PAGER list, 3 mag floors) swpc 3 | 7 (three storm-tier floors) usgs_nwis 2 | 4 (parameter_codes, broadcast_on_recede) incident 2 | 0 (freshness_seconds, broadcast_on_update) tomtom_incidents 2 | 1 (drop_zero_magnitude, drop_non_present) state_511_atis 1 | 0 (skipped_states) itd_511 0 | 3 (all sub_type maps/emoji/phrase = CODE) central 1 | 2 (severity_thresholds) dispatcher 4 | 0 (LRU cap, prune params, retention days) band_conditions 3 | 6 (SWPC freshness + HamQSL endpoint config) geocoder 6 | 1 (Photon endpoint + town-OSM curation + cache cap) firms 4 | 1* (confidence_floor, frp_floor, bbox, dedup_distance_m) pipeline 2 | 0 (inhibitor TTL, grouper window) * firms: dedup_lat_lon_decimals is replaced by dedup_distance_m=5 per Matt s call (user-facing unit is meters, not decimal places; the handler will internally translate to quantization step in v0.6-3b). adapter_meta stays at 15 rows -- itd_511 keeps its include_in_llm_context toggle even with zero config keys. Live-DB cleanup: meshai/adapter_config/__init__.py:prune_orphans(conn) DELETEs every adapter_config row whose (adapter, key) is no longer in REGISTRY. Each delete is INFO-logged with the prefix "adapter_config orphan removed:" so docker logs carry the paper trail. Called from init_db() after seed_defaults; idempotent (zero deletes on every subsequent boot). Cache is invalidated when any orphan is removed. adapter_meta is NOT pruned -- meta rows are cheap and useful even for adapters that ended up with zero config keys. Tests (34 cases, replaces v0.6-3a 24-case set): - Registry count is 43; ADAPTER_META is 15 - Seed lands every REGISTRY + ADAPTER_META row; idempotent; never overwrites user edits - prune_orphans removes a synthetic legacy row, logs at INFO with the exact prefix, leaves known keys untouched, leaves adapter_meta untouched, invalidates the accessor cache - Accessor returns correctly-typed values incl new firms.dedup_distance_m - Guard tests: no key in REGISTRY contains "emoji", ends with "_map", or contains "template" / "prefix" (catches CODE leaking back in) Test count: 721 -> 731 (+10 net: +5 prune cases, +1 firms.dedup_distance_m, +3 CODE-guard cases, +1 registry-count assertion). Refs Matt s locked CONFIG-vs-CODE rule.
2026-06-05 18:09:49 +00:00
"""v0.6-3a foundation tests: migration, seed, accessor, orphan prune.
v0.6-3a.1: trimmed registry to 43 keys per Matt's CONFIG-vs-CODE rule.
prune_orphans cleans up rows that were in the v0.6-3a draft but no longer
in the trimmed REGISTRY. Tests now assert the 43-key count and exercise
the prune path.
feat(v0.6-3a): adapter_config foundation -- migration + defaults registry + typed accessor Closes the foundation slice of audit doc Section A (Rule 17). Lands two SQLite tables, the seed routine that populates them from a Python defaults registry, and a typed accessor that handler code will read in v0.6-3b. No handler changes in this commit -- ZERO behavior risk, every existing test still passes (721 / 69 skipped / 0 failed). v6.sql tables: - adapter_config(adapter, key, value_json, default_json, type, description, updated_at) PRIMARY KEY(adapter, key) -- JSON-encoded values flow through a single column uniformly. CHECK constraint on `type` closes the vocab (int/float/str/ bool/json). - adapter_meta(adapter PK, display_name, include_in_llm_context, description, updated_at) -- per-adapter metadata + the user-scopable LLM-context toggle (Matt refinement #5). meshai/adapter_config/ package: - defaults.py: REGISTRY dict mapping (adapter, key) -> {default, type, description}. Covers audit doc sections A.1-A.12: wfigs, nws, usgs_quake, swpc, usgs_nwis, incident family (tomtom_incidents, state_511_atis, itd_511, shared "incident"), central consumer, dispatcher, band_conditions, geocoder, firms, pipeline (Inhibitor + Grouper). ~85 keys total. ADAPTER_META covers 15 adapters with display_name + include_in_llm_context defaulting to True. Per Matt refinement #3, every default matches the current handler constant EXACTLY -- first deploy behavior is unchanged. - _accessor.py: AdapterConfig class with `adapter_config.<adapter>.<key>` syntax. Read pipeline: in-memory cache hit -> SQL -> registry fallback (with WARNING) -> AttributeError. Process-wide cache; PUT via v0.6-3c REST API calls invalidate_cache() to drop the cache. GIL-atomic dict reads on the fast path (handlers call this hot). - __init__.py: seed_defaults(conn) -- INSERT OR IGNOREs one row per registry entry. Idempotent, never overwrites user edits. Wiring: - meshai/persistence/db.py: SCHEMA_VERSION 5 -> 6, and init_db() now calls seed_defaults() after migrations apply. - meshai/main.py: _init_components() now calls init_db() FIRST (per commit #1 lessons-learned: a startup-time migration is required when handlers will rely on the new schema; lazy-on-first-handler is fine for v4/v5 but not for v6 where handler reads start in v0.6-3b). - tests/conftest.py: autouse fixture now calls init_db() + clears the accessor cache around each test, so every test gets the v6 seed AND a clean cache without per-test boilerplate. Tests (tests/test_adapter_config_foundation.py, 24 cases): - v6 tables exist + schema_meta at 6 + type-vocabulary CHECK enforced - seed populates every REGISTRY + ADAPTER_META row, value_json == default_json on first seed, type matches - seed is idempotent + does not overwrite user edits - accessor returns correctly typed values for int/float/str/bool/ json list/json dict/json None - cache hit: second read does not touch the DB (patched _load_from_db raises, accessor still succeeds) - invalidate_cache forces a re-read; mutated DB value wins - registry fallback path triggers when a row is missing (with WARNING) - unknown key raises AttributeError - setattr blocked (writes go via the REST API in 3c) - every default JSON round-trips cleanly; every type is in vocabulary - ADAPTER_META covers every adapter in REGISTRY Test count: 697 -> 721 (+24 new, 0 regressions). v0.6-3b will wire handlers one at a time (wfigs, nws, quake, swpc, nwis, incident, central, dispatcher, band_conditions, geocoder, firms). Per the audit lock, defaults match exactly so each wiring step is a pure refactor -- bisect-safe. v0.6-3c lands the /api/adapter-config CRUD + the AdapterConfig.tsx dashboard editor + cache invalidation on PUT. Refs audit doc v0.6-phase1-audit.md Section A + finding #4.
2026-06-05 17:06:51 +00:00
"""
from __future__ import annotations
import json
feat(v0.6-3a.1): trim adapter_config registry to CONFIG-only per Matt config-vs-code rule + log-on-delete safety net for orphan cleanup Drops 35 of the v0.6-3a-draft 77 keys + adds 1 net-new key (firms.dedup_distance_m) for a final count of 43. The trim rules: CONFIG (lives in adapter_config, surfaces in the GUI): where we send (channels), how often (cadences/schedules), thresholds (magnitude floors, severity gates, distance radius, cooldown durations, freshness windows), curation data (which sites/states/codes), toggles (enabled, include_in_llm_context, drop_zero_magnitude). CODE (stays in handlers, never reaches the GUI): sentence templates, emoji choices, mapping/translation functions (TomTom icon_map, ITD sub_type_map, Central adapter_map and category_map), rendering logic (anchor priority order, expires-bucket formatting, threshold-state labels), heuristic logic (band_conditions Kp/SFI -> Good/Fair/Poor function). Per-adapter outcome (kept | killed): wfigs 4 | 4 (cooldown_seconds, anchor_max_mi, two re-broadcast toggles) nws 3 | 4 (broadcast_severities, tombstone_msgtypes, warning_suffix_promotes) usgs_quake 6 | 3 (centroid, radius, PAGER list, 3 mag floors) swpc 3 | 7 (three storm-tier floors) usgs_nwis 2 | 4 (parameter_codes, broadcast_on_recede) incident 2 | 0 (freshness_seconds, broadcast_on_update) tomtom_incidents 2 | 1 (drop_zero_magnitude, drop_non_present) state_511_atis 1 | 0 (skipped_states) itd_511 0 | 3 (all sub_type maps/emoji/phrase = CODE) central 1 | 2 (severity_thresholds) dispatcher 4 | 0 (LRU cap, prune params, retention days) band_conditions 3 | 6 (SWPC freshness + HamQSL endpoint config) geocoder 6 | 1 (Photon endpoint + town-OSM curation + cache cap) firms 4 | 1* (confidence_floor, frp_floor, bbox, dedup_distance_m) pipeline 2 | 0 (inhibitor TTL, grouper window) * firms: dedup_lat_lon_decimals is replaced by dedup_distance_m=5 per Matt s call (user-facing unit is meters, not decimal places; the handler will internally translate to quantization step in v0.6-3b). adapter_meta stays at 15 rows -- itd_511 keeps its include_in_llm_context toggle even with zero config keys. Live-DB cleanup: meshai/adapter_config/__init__.py:prune_orphans(conn) DELETEs every adapter_config row whose (adapter, key) is no longer in REGISTRY. Each delete is INFO-logged with the prefix "adapter_config orphan removed:" so docker logs carry the paper trail. Called from init_db() after seed_defaults; idempotent (zero deletes on every subsequent boot). Cache is invalidated when any orphan is removed. adapter_meta is NOT pruned -- meta rows are cheap and useful even for adapters that ended up with zero config keys. Tests (34 cases, replaces v0.6-3a 24-case set): - Registry count is 43; ADAPTER_META is 15 - Seed lands every REGISTRY + ADAPTER_META row; idempotent; never overwrites user edits - prune_orphans removes a synthetic legacy row, logs at INFO with the exact prefix, leaves known keys untouched, leaves adapter_meta untouched, invalidates the accessor cache - Accessor returns correctly-typed values incl new firms.dedup_distance_m - Guard tests: no key in REGISTRY contains "emoji", ends with "_map", or contains "template" / "prefix" (catches CODE leaking back in) Test count: 721 -> 731 (+10 net: +5 prune cases, +1 firms.dedup_distance_m, +3 CODE-guard cases, +1 registry-count assertion). Refs Matt s locked CONFIG-vs-CODE rule.
2026-06-05 18:09:49 +00:00
import logging
feat(v0.6-3a): adapter_config foundation -- migration + defaults registry + typed accessor Closes the foundation slice of audit doc Section A (Rule 17). Lands two SQLite tables, the seed routine that populates them from a Python defaults registry, and a typed accessor that handler code will read in v0.6-3b. No handler changes in this commit -- ZERO behavior risk, every existing test still passes (721 / 69 skipped / 0 failed). v6.sql tables: - adapter_config(adapter, key, value_json, default_json, type, description, updated_at) PRIMARY KEY(adapter, key) -- JSON-encoded values flow through a single column uniformly. CHECK constraint on `type` closes the vocab (int/float/str/ bool/json). - adapter_meta(adapter PK, display_name, include_in_llm_context, description, updated_at) -- per-adapter metadata + the user-scopable LLM-context toggle (Matt refinement #5). meshai/adapter_config/ package: - defaults.py: REGISTRY dict mapping (adapter, key) -> {default, type, description}. Covers audit doc sections A.1-A.12: wfigs, nws, usgs_quake, swpc, usgs_nwis, incident family (tomtom_incidents, state_511_atis, itd_511, shared "incident"), central consumer, dispatcher, band_conditions, geocoder, firms, pipeline (Inhibitor + Grouper). ~85 keys total. ADAPTER_META covers 15 adapters with display_name + include_in_llm_context defaulting to True. Per Matt refinement #3, every default matches the current handler constant EXACTLY -- first deploy behavior is unchanged. - _accessor.py: AdapterConfig class with `adapter_config.<adapter>.<key>` syntax. Read pipeline: in-memory cache hit -> SQL -> registry fallback (with WARNING) -> AttributeError. Process-wide cache; PUT via v0.6-3c REST API calls invalidate_cache() to drop the cache. GIL-atomic dict reads on the fast path (handlers call this hot). - __init__.py: seed_defaults(conn) -- INSERT OR IGNOREs one row per registry entry. Idempotent, never overwrites user edits. Wiring: - meshai/persistence/db.py: SCHEMA_VERSION 5 -> 6, and init_db() now calls seed_defaults() after migrations apply. - meshai/main.py: _init_components() now calls init_db() FIRST (per commit #1 lessons-learned: a startup-time migration is required when handlers will rely on the new schema; lazy-on-first-handler is fine for v4/v5 but not for v6 where handler reads start in v0.6-3b). - tests/conftest.py: autouse fixture now calls init_db() + clears the accessor cache around each test, so every test gets the v6 seed AND a clean cache without per-test boilerplate. Tests (tests/test_adapter_config_foundation.py, 24 cases): - v6 tables exist + schema_meta at 6 + type-vocabulary CHECK enforced - seed populates every REGISTRY + ADAPTER_META row, value_json == default_json on first seed, type matches - seed is idempotent + does not overwrite user edits - accessor returns correctly typed values for int/float/str/bool/ json list/json dict/json None - cache hit: second read does not touch the DB (patched _load_from_db raises, accessor still succeeds) - invalidate_cache forces a re-read; mutated DB value wins - registry fallback path triggers when a row is missing (with WARNING) - unknown key raises AttributeError - setattr blocked (writes go via the REST API in 3c) - every default JSON round-trips cleanly; every type is in vocabulary - ADAPTER_META covers every adapter in REGISTRY Test count: 697 -> 721 (+24 new, 0 regressions). v0.6-3b will wire handlers one at a time (wfigs, nws, quake, swpc, nwis, incident, central, dispatcher, band_conditions, geocoder, firms). Per the audit lock, defaults match exactly so each wiring step is a pure refactor -- bisect-safe. v0.6-3c lands the /api/adapter-config CRUD + the AdapterConfig.tsx dashboard editor + cache invalidation on PUT. Refs audit doc v0.6-phase1-audit.md Section A + finding #4.
2026-06-05 17:06:51 +00:00
from unittest.mock import patch
import pytest
from meshai.adapter_config import (
adapter_config,
invalidate_cache,
seed_defaults,
feat(v0.6-3a.1): trim adapter_config registry to CONFIG-only per Matt config-vs-code rule + log-on-delete safety net for orphan cleanup Drops 35 of the v0.6-3a-draft 77 keys + adds 1 net-new key (firms.dedup_distance_m) for a final count of 43. The trim rules: CONFIG (lives in adapter_config, surfaces in the GUI): where we send (channels), how often (cadences/schedules), thresholds (magnitude floors, severity gates, distance radius, cooldown durations, freshness windows), curation data (which sites/states/codes), toggles (enabled, include_in_llm_context, drop_zero_magnitude). CODE (stays in handlers, never reaches the GUI): sentence templates, emoji choices, mapping/translation functions (TomTom icon_map, ITD sub_type_map, Central adapter_map and category_map), rendering logic (anchor priority order, expires-bucket formatting, threshold-state labels), heuristic logic (band_conditions Kp/SFI -> Good/Fair/Poor function). Per-adapter outcome (kept | killed): wfigs 4 | 4 (cooldown_seconds, anchor_max_mi, two re-broadcast toggles) nws 3 | 4 (broadcast_severities, tombstone_msgtypes, warning_suffix_promotes) usgs_quake 6 | 3 (centroid, radius, PAGER list, 3 mag floors) swpc 3 | 7 (three storm-tier floors) usgs_nwis 2 | 4 (parameter_codes, broadcast_on_recede) incident 2 | 0 (freshness_seconds, broadcast_on_update) tomtom_incidents 2 | 1 (drop_zero_magnitude, drop_non_present) state_511_atis 1 | 0 (skipped_states) itd_511 0 | 3 (all sub_type maps/emoji/phrase = CODE) central 1 | 2 (severity_thresholds) dispatcher 4 | 0 (LRU cap, prune params, retention days) band_conditions 3 | 6 (SWPC freshness + HamQSL endpoint config) geocoder 6 | 1 (Photon endpoint + town-OSM curation + cache cap) firms 4 | 1* (confidence_floor, frp_floor, bbox, dedup_distance_m) pipeline 2 | 0 (inhibitor TTL, grouper window) * firms: dedup_lat_lon_decimals is replaced by dedup_distance_m=5 per Matt s call (user-facing unit is meters, not decimal places; the handler will internally translate to quantization step in v0.6-3b). adapter_meta stays at 15 rows -- itd_511 keeps its include_in_llm_context toggle even with zero config keys. Live-DB cleanup: meshai/adapter_config/__init__.py:prune_orphans(conn) DELETEs every adapter_config row whose (adapter, key) is no longer in REGISTRY. Each delete is INFO-logged with the prefix "adapter_config orphan removed:" so docker logs carry the paper trail. Called from init_db() after seed_defaults; idempotent (zero deletes on every subsequent boot). Cache is invalidated when any orphan is removed. adapter_meta is NOT pruned -- meta rows are cheap and useful even for adapters that ended up with zero config keys. Tests (34 cases, replaces v0.6-3a 24-case set): - Registry count is 43; ADAPTER_META is 15 - Seed lands every REGISTRY + ADAPTER_META row; idempotent; never overwrites user edits - prune_orphans removes a synthetic legacy row, logs at INFO with the exact prefix, leaves known keys untouched, leaves adapter_meta untouched, invalidates the accessor cache - Accessor returns correctly-typed values incl new firms.dedup_distance_m - Guard tests: no key in REGISTRY contains "emoji", ends with "_map", or contains "template" / "prefix" (catches CODE leaking back in) Test count: 721 -> 731 (+10 net: +5 prune cases, +1 firms.dedup_distance_m, +3 CODE-guard cases, +1 registry-count assertion). Refs Matt s locked CONFIG-vs-CODE rule.
2026-06-05 18:09:49 +00:00
prune_orphans,
feat(v0.6-3a): adapter_config foundation -- migration + defaults registry + typed accessor Closes the foundation slice of audit doc Section A (Rule 17). Lands two SQLite tables, the seed routine that populates them from a Python defaults registry, and a typed accessor that handler code will read in v0.6-3b. No handler changes in this commit -- ZERO behavior risk, every existing test still passes (721 / 69 skipped / 0 failed). v6.sql tables: - adapter_config(adapter, key, value_json, default_json, type, description, updated_at) PRIMARY KEY(adapter, key) -- JSON-encoded values flow through a single column uniformly. CHECK constraint on `type` closes the vocab (int/float/str/ bool/json). - adapter_meta(adapter PK, display_name, include_in_llm_context, description, updated_at) -- per-adapter metadata + the user-scopable LLM-context toggle (Matt refinement #5). meshai/adapter_config/ package: - defaults.py: REGISTRY dict mapping (adapter, key) -> {default, type, description}. Covers audit doc sections A.1-A.12: wfigs, nws, usgs_quake, swpc, usgs_nwis, incident family (tomtom_incidents, state_511_atis, itd_511, shared "incident"), central consumer, dispatcher, band_conditions, geocoder, firms, pipeline (Inhibitor + Grouper). ~85 keys total. ADAPTER_META covers 15 adapters with display_name + include_in_llm_context defaulting to True. Per Matt refinement #3, every default matches the current handler constant EXACTLY -- first deploy behavior is unchanged. - _accessor.py: AdapterConfig class with `adapter_config.<adapter>.<key>` syntax. Read pipeline: in-memory cache hit -> SQL -> registry fallback (with WARNING) -> AttributeError. Process-wide cache; PUT via v0.6-3c REST API calls invalidate_cache() to drop the cache. GIL-atomic dict reads on the fast path (handlers call this hot). - __init__.py: seed_defaults(conn) -- INSERT OR IGNOREs one row per registry entry. Idempotent, never overwrites user edits. Wiring: - meshai/persistence/db.py: SCHEMA_VERSION 5 -> 6, and init_db() now calls seed_defaults() after migrations apply. - meshai/main.py: _init_components() now calls init_db() FIRST (per commit #1 lessons-learned: a startup-time migration is required when handlers will rely on the new schema; lazy-on-first-handler is fine for v4/v5 but not for v6 where handler reads start in v0.6-3b). - tests/conftest.py: autouse fixture now calls init_db() + clears the accessor cache around each test, so every test gets the v6 seed AND a clean cache without per-test boilerplate. Tests (tests/test_adapter_config_foundation.py, 24 cases): - v6 tables exist + schema_meta at 6 + type-vocabulary CHECK enforced - seed populates every REGISTRY + ADAPTER_META row, value_json == default_json on first seed, type matches - seed is idempotent + does not overwrite user edits - accessor returns correctly typed values for int/float/str/bool/ json list/json dict/json None - cache hit: second read does not touch the DB (patched _load_from_db raises, accessor still succeeds) - invalidate_cache forces a re-read; mutated DB value wins - registry fallback path triggers when a row is missing (with WARNING) - unknown key raises AttributeError - setattr blocked (writes go via the REST API in 3c) - every default JSON round-trips cleanly; every type is in vocabulary - ADAPTER_META covers every adapter in REGISTRY Test count: 697 -> 721 (+24 new, 0 regressions). v0.6-3b will wire handlers one at a time (wfigs, nws, quake, swpc, nwis, incident, central, dispatcher, band_conditions, geocoder, firms). Per the audit lock, defaults match exactly so each wiring step is a pure refactor -- bisect-safe. v0.6-3c lands the /api/adapter-config CRUD + the AdapterConfig.tsx dashboard editor + cache invalidation on PUT. Refs audit doc v0.6-phase1-audit.md Section A + finding #4.
2026-06-05 17:06:51 +00:00
REGISTRY,
ADAPTER_META,
)
from meshai.adapter_config import _accessor as accessor_mod
from meshai.persistence import close_thread_connection, init_db
from meshai.persistence import db as persistence_db
# ---------- fixtures ------------------------------------------------------
@pytest.fixture
def fresh_db(tmp_path, monkeypatch):
p = str(tmp_path / "ac-test.sqlite")
monkeypatch.setenv("MESHAI_DB_PATH", p)
persistence_db._initialised.clear()
close_thread_connection()
invalidate_cache()
conn = init_db()
yield conn
close_thread_connection()
persistence_db._initialised.discard(p)
invalidate_cache()
# ---------- schema --------------------------------------------------------
def test_v6_tables_exist(fresh_db):
tables = {r["name"] for r in fresh_db.execute(
"SELECT name FROM sqlite_master WHERE type='table'"
).fetchall()}
assert "adapter_config" in tables
assert "adapter_meta" in tables
feat(v0.6-3b): wire every handler to adapter_config + v7.sql firms dedup_key column Replaces module-level magic numbers in 12 handlers with reads via the v0.6-3a.1 typed accessor. Every default matches the prior hardcoded value exactly, so first-deploy behavior is unchanged. Handlers wired (43 keys across the 43-row registry): wfigs cooldown_seconds, anchor_max_mi, broadcast_on_acres, broadcast_on_contained nws broadcast_severities, tombstone_msgtypes, warning_suffix_promotes usgs_quake regional_centroid, regional_radius_mi, broadcast_pager_alerts, global_mag_floor, regional_mag_floor, escalate_mag_floor swpc geomag_kp_floor (extends G-scale down to Kp 5 when lowered), flare_class_floor (R-scale extended to M-class when lowered), proton_pfu_floor usgs_nwis parameter_codes, broadcast_on_recede incident freshness_seconds, broadcast_on_update (Update path re-implemented when toggled True: magnitude step-up / delay doubling / icon_category change) tomtom_incidents drop_zero_magnitude, drop_non_present state_511_atis skipped_states (case-insensitive match against both state_code and primary_region suffix) central severity_thresholds (immediate_min check ordered before priority_max so the +inf clamp still works) dispatcher dedup_lru_max, cooldown_prune_size, cooldown_prune_multiplier, dedup_db_retention_days band_conditions swpc_freshness_seconds, hamqsl_url, hamqsl_timeout_s geocoder (photon_url/timeout/radius/limit/town_osm_values/ h3_cache_max -- module-level constants kept as backward-compat aliases; runtime reads via accessor) pipeline Inhibitor.ttl_seconds + Grouper.window_seconds now default to None, falling back to adapter_config.pipeline.{inhibitor_ttl_seconds, grouper_window_seconds}. Explicit constructor values still win (test fixtures unchanged). firms confidence_floor, frp_floor, bbox, dedup_distance_m Schema: v7.sql adds firms_pixels.dedup_key column + drops the old hardcoded round(lat,5) UNIQUE INDEX, replaces with UNIQUE (dedup_key, acq_time, satellite). The firms_handler quantizes lat/lon to (dedup_distance_m / 111000) degrees at INSERT time -- meters-based precision per Matt s spec, tunable via the GUI without schema changes. SCHEMA_VERSION 6 -> 7. firms_pixels has 0 rows in production so no backfill needed. CODE preserved (Matt s rule): sentence templates, emoji literals, the TomTom icon_map / ITD sub_type_map / Central adapter_map / category_map translation tables, the band_conditions Kp/SFI -> Good/Fair/Poor heuristic, anchor-priority ordering, expires-bucket boundaries, the NOAA G/R/S scale tables. None of these reach the GUI. Hot-path performance: every accessor read hits the in-memory cache after the first call; cache hit is one dict get. Per-event reads (e.g. WFIGS cooldown_seconds on every WFIGS poll-cycle) add a single dict lookup to existing pipelines. Backward-compat aliases retained for module-level imports that exist in test code: WFIGS_BROADCAST_COOLDOWN_S, FIRMS_CONFIDENCE_FLOOR, FIRMS_FRP_FLOOR, FIRMS_BBOX_OPTIONAL, INCIDENT_FRESHNESS_MAX_S, PHOTON_BASE_URL/TIMEOUT_S/RADIUS_KM/LIMIT. Handler code reads via adapter_config; tests can either monkeypatch the module attribute (firms) or mutate adapter_config DB values. Test count: 731 -> 731 (no new tests in 3b -- handler wiring is a pure refactor; coverage comes from the existing handler test suites passing unchanged). Refs audit doc v0.6-phase1-audit.md Section A + Matt s CONFIG-vs-CODE rule.
2026-06-05 18:38:21 +00:00
def test_schema_meta_at_v7(fresh_db):
feat(v0.6-3a): adapter_config foundation -- migration + defaults registry + typed accessor Closes the foundation slice of audit doc Section A (Rule 17). Lands two SQLite tables, the seed routine that populates them from a Python defaults registry, and a typed accessor that handler code will read in v0.6-3b. No handler changes in this commit -- ZERO behavior risk, every existing test still passes (721 / 69 skipped / 0 failed). v6.sql tables: - adapter_config(adapter, key, value_json, default_json, type, description, updated_at) PRIMARY KEY(adapter, key) -- JSON-encoded values flow through a single column uniformly. CHECK constraint on `type` closes the vocab (int/float/str/ bool/json). - adapter_meta(adapter PK, display_name, include_in_llm_context, description, updated_at) -- per-adapter metadata + the user-scopable LLM-context toggle (Matt refinement #5). meshai/adapter_config/ package: - defaults.py: REGISTRY dict mapping (adapter, key) -> {default, type, description}. Covers audit doc sections A.1-A.12: wfigs, nws, usgs_quake, swpc, usgs_nwis, incident family (tomtom_incidents, state_511_atis, itd_511, shared "incident"), central consumer, dispatcher, band_conditions, geocoder, firms, pipeline (Inhibitor + Grouper). ~85 keys total. ADAPTER_META covers 15 adapters with display_name + include_in_llm_context defaulting to True. Per Matt refinement #3, every default matches the current handler constant EXACTLY -- first deploy behavior is unchanged. - _accessor.py: AdapterConfig class with `adapter_config.<adapter>.<key>` syntax. Read pipeline: in-memory cache hit -> SQL -> registry fallback (with WARNING) -> AttributeError. Process-wide cache; PUT via v0.6-3c REST API calls invalidate_cache() to drop the cache. GIL-atomic dict reads on the fast path (handlers call this hot). - __init__.py: seed_defaults(conn) -- INSERT OR IGNOREs one row per registry entry. Idempotent, never overwrites user edits. Wiring: - meshai/persistence/db.py: SCHEMA_VERSION 5 -> 6, and init_db() now calls seed_defaults() after migrations apply. - meshai/main.py: _init_components() now calls init_db() FIRST (per commit #1 lessons-learned: a startup-time migration is required when handlers will rely on the new schema; lazy-on-first-handler is fine for v4/v5 but not for v6 where handler reads start in v0.6-3b). - tests/conftest.py: autouse fixture now calls init_db() + clears the accessor cache around each test, so every test gets the v6 seed AND a clean cache without per-test boilerplate. Tests (tests/test_adapter_config_foundation.py, 24 cases): - v6 tables exist + schema_meta at 6 + type-vocabulary CHECK enforced - seed populates every REGISTRY + ADAPTER_META row, value_json == default_json on first seed, type matches - seed is idempotent + does not overwrite user edits - accessor returns correctly typed values for int/float/str/bool/ json list/json dict/json None - cache hit: second read does not touch the DB (patched _load_from_db raises, accessor still succeeds) - invalidate_cache forces a re-read; mutated DB value wins - registry fallback path triggers when a row is missing (with WARNING) - unknown key raises AttributeError - setattr blocked (writes go via the REST API in 3c) - every default JSON round-trips cleanly; every type is in vocabulary - ADAPTER_META covers every adapter in REGISTRY Test count: 697 -> 721 (+24 new, 0 regressions). v0.6-3b will wire handlers one at a time (wfigs, nws, quake, swpc, nwis, incident, central, dispatcher, band_conditions, geocoder, firms). Per the audit lock, defaults match exactly so each wiring step is a pure refactor -- bisect-safe. v0.6-3c lands the /api/adapter-config CRUD + the AdapterConfig.tsx dashboard editor + cache invalidation on PUT. Refs audit doc v0.6-phase1-audit.md Section A + finding #4.
2026-06-05 17:06:51 +00:00
v = fresh_db.execute(
"SELECT value FROM schema_meta WHERE key='version'"
).fetchone()["value"]
feat(v0.6-3b): wire every handler to adapter_config + v7.sql firms dedup_key column Replaces module-level magic numbers in 12 handlers with reads via the v0.6-3a.1 typed accessor. Every default matches the prior hardcoded value exactly, so first-deploy behavior is unchanged. Handlers wired (43 keys across the 43-row registry): wfigs cooldown_seconds, anchor_max_mi, broadcast_on_acres, broadcast_on_contained nws broadcast_severities, tombstone_msgtypes, warning_suffix_promotes usgs_quake regional_centroid, regional_radius_mi, broadcast_pager_alerts, global_mag_floor, regional_mag_floor, escalate_mag_floor swpc geomag_kp_floor (extends G-scale down to Kp 5 when lowered), flare_class_floor (R-scale extended to M-class when lowered), proton_pfu_floor usgs_nwis parameter_codes, broadcast_on_recede incident freshness_seconds, broadcast_on_update (Update path re-implemented when toggled True: magnitude step-up / delay doubling / icon_category change) tomtom_incidents drop_zero_magnitude, drop_non_present state_511_atis skipped_states (case-insensitive match against both state_code and primary_region suffix) central severity_thresholds (immediate_min check ordered before priority_max so the +inf clamp still works) dispatcher dedup_lru_max, cooldown_prune_size, cooldown_prune_multiplier, dedup_db_retention_days band_conditions swpc_freshness_seconds, hamqsl_url, hamqsl_timeout_s geocoder (photon_url/timeout/radius/limit/town_osm_values/ h3_cache_max -- module-level constants kept as backward-compat aliases; runtime reads via accessor) pipeline Inhibitor.ttl_seconds + Grouper.window_seconds now default to None, falling back to adapter_config.pipeline.{inhibitor_ttl_seconds, grouper_window_seconds}. Explicit constructor values still win (test fixtures unchanged). firms confidence_floor, frp_floor, bbox, dedup_distance_m Schema: v7.sql adds firms_pixels.dedup_key column + drops the old hardcoded round(lat,5) UNIQUE INDEX, replaces with UNIQUE (dedup_key, acq_time, satellite). The firms_handler quantizes lat/lon to (dedup_distance_m / 111000) degrees at INSERT time -- meters-based precision per Matt s spec, tunable via the GUI without schema changes. SCHEMA_VERSION 6 -> 7. firms_pixels has 0 rows in production so no backfill needed. CODE preserved (Matt s rule): sentence templates, emoji literals, the TomTom icon_map / ITD sub_type_map / Central adapter_map / category_map translation tables, the band_conditions Kp/SFI -> Good/Fair/Poor heuristic, anchor-priority ordering, expires-bucket boundaries, the NOAA G/R/S scale tables. None of these reach the GUI. Hot-path performance: every accessor read hits the in-memory cache after the first call; cache hit is one dict get. Per-event reads (e.g. WFIGS cooldown_seconds on every WFIGS poll-cycle) add a single dict lookup to existing pipelines. Backward-compat aliases retained for module-level imports that exist in test code: WFIGS_BROADCAST_COOLDOWN_S, FIRMS_CONFIDENCE_FLOOR, FIRMS_FRP_FLOOR, FIRMS_BBOX_OPTIONAL, INCIDENT_FRESHNESS_MAX_S, PHOTON_BASE_URL/TIMEOUT_S/RADIUS_KM/LIMIT. Handler code reads via adapter_config; tests can either monkeypatch the module attribute (firms) or mutate adapter_config DB values. Test count: 731 -> 731 (no new tests in 3b -- handler wiring is a pure refactor; coverage comes from the existing handler test suites passing unchanged). Refs audit doc v0.6-phase1-audit.md Section A + Matt s CONFIG-vs-CODE rule.
2026-06-05 18:38:21 +00:00
assert int(v) == 7
feat(v0.6-3a): adapter_config foundation -- migration + defaults registry + typed accessor Closes the foundation slice of audit doc Section A (Rule 17). Lands two SQLite tables, the seed routine that populates them from a Python defaults registry, and a typed accessor that handler code will read in v0.6-3b. No handler changes in this commit -- ZERO behavior risk, every existing test still passes (721 / 69 skipped / 0 failed). v6.sql tables: - adapter_config(adapter, key, value_json, default_json, type, description, updated_at) PRIMARY KEY(adapter, key) -- JSON-encoded values flow through a single column uniformly. CHECK constraint on `type` closes the vocab (int/float/str/ bool/json). - adapter_meta(adapter PK, display_name, include_in_llm_context, description, updated_at) -- per-adapter metadata + the user-scopable LLM-context toggle (Matt refinement #5). meshai/adapter_config/ package: - defaults.py: REGISTRY dict mapping (adapter, key) -> {default, type, description}. Covers audit doc sections A.1-A.12: wfigs, nws, usgs_quake, swpc, usgs_nwis, incident family (tomtom_incidents, state_511_atis, itd_511, shared "incident"), central consumer, dispatcher, band_conditions, geocoder, firms, pipeline (Inhibitor + Grouper). ~85 keys total. ADAPTER_META covers 15 adapters with display_name + include_in_llm_context defaulting to True. Per Matt refinement #3, every default matches the current handler constant EXACTLY -- first deploy behavior is unchanged. - _accessor.py: AdapterConfig class with `adapter_config.<adapter>.<key>` syntax. Read pipeline: in-memory cache hit -> SQL -> registry fallback (with WARNING) -> AttributeError. Process-wide cache; PUT via v0.6-3c REST API calls invalidate_cache() to drop the cache. GIL-atomic dict reads on the fast path (handlers call this hot). - __init__.py: seed_defaults(conn) -- INSERT OR IGNOREs one row per registry entry. Idempotent, never overwrites user edits. Wiring: - meshai/persistence/db.py: SCHEMA_VERSION 5 -> 6, and init_db() now calls seed_defaults() after migrations apply. - meshai/main.py: _init_components() now calls init_db() FIRST (per commit #1 lessons-learned: a startup-time migration is required when handlers will rely on the new schema; lazy-on-first-handler is fine for v4/v5 but not for v6 where handler reads start in v0.6-3b). - tests/conftest.py: autouse fixture now calls init_db() + clears the accessor cache around each test, so every test gets the v6 seed AND a clean cache without per-test boilerplate. Tests (tests/test_adapter_config_foundation.py, 24 cases): - v6 tables exist + schema_meta at 6 + type-vocabulary CHECK enforced - seed populates every REGISTRY + ADAPTER_META row, value_json == default_json on first seed, type matches - seed is idempotent + does not overwrite user edits - accessor returns correctly typed values for int/float/str/bool/ json list/json dict/json None - cache hit: second read does not touch the DB (patched _load_from_db raises, accessor still succeeds) - invalidate_cache forces a re-read; mutated DB value wins - registry fallback path triggers when a row is missing (with WARNING) - unknown key raises AttributeError - setattr blocked (writes go via the REST API in 3c) - every default JSON round-trips cleanly; every type is in vocabulary - ADAPTER_META covers every adapter in REGISTRY Test count: 697 -> 721 (+24 new, 0 regressions). v0.6-3b will wire handlers one at a time (wfigs, nws, quake, swpc, nwis, incident, central, dispatcher, band_conditions, geocoder, firms). Per the audit lock, defaults match exactly so each wiring step is a pure refactor -- bisect-safe. v0.6-3c lands the /api/adapter-config CRUD + the AdapterConfig.tsx dashboard editor + cache invalidation on PUT. Refs audit doc v0.6-phase1-audit.md Section A + finding #4.
2026-06-05 17:06:51 +00:00
def test_adapter_config_type_check_constrains_vocabulary(fresh_db):
with pytest.raises(Exception):
fresh_db.execute(
"INSERT INTO adapter_config(adapter, key, value_json, default_json, "
"type, description, updated_at) VALUES (?,?,?,?,?,?,?)",
feat(v0.6-3a.1): trim adapter_config registry to CONFIG-only per Matt config-vs-code rule + log-on-delete safety net for orphan cleanup Drops 35 of the v0.6-3a-draft 77 keys + adds 1 net-new key (firms.dedup_distance_m) for a final count of 43. The trim rules: CONFIG (lives in adapter_config, surfaces in the GUI): where we send (channels), how often (cadences/schedules), thresholds (magnitude floors, severity gates, distance radius, cooldown durations, freshness windows), curation data (which sites/states/codes), toggles (enabled, include_in_llm_context, drop_zero_magnitude). CODE (stays in handlers, never reaches the GUI): sentence templates, emoji choices, mapping/translation functions (TomTom icon_map, ITD sub_type_map, Central adapter_map and category_map), rendering logic (anchor priority order, expires-bucket formatting, threshold-state labels), heuristic logic (band_conditions Kp/SFI -> Good/Fair/Poor function). Per-adapter outcome (kept | killed): wfigs 4 | 4 (cooldown_seconds, anchor_max_mi, two re-broadcast toggles) nws 3 | 4 (broadcast_severities, tombstone_msgtypes, warning_suffix_promotes) usgs_quake 6 | 3 (centroid, radius, PAGER list, 3 mag floors) swpc 3 | 7 (three storm-tier floors) usgs_nwis 2 | 4 (parameter_codes, broadcast_on_recede) incident 2 | 0 (freshness_seconds, broadcast_on_update) tomtom_incidents 2 | 1 (drop_zero_magnitude, drop_non_present) state_511_atis 1 | 0 (skipped_states) itd_511 0 | 3 (all sub_type maps/emoji/phrase = CODE) central 1 | 2 (severity_thresholds) dispatcher 4 | 0 (LRU cap, prune params, retention days) band_conditions 3 | 6 (SWPC freshness + HamQSL endpoint config) geocoder 6 | 1 (Photon endpoint + town-OSM curation + cache cap) firms 4 | 1* (confidence_floor, frp_floor, bbox, dedup_distance_m) pipeline 2 | 0 (inhibitor TTL, grouper window) * firms: dedup_lat_lon_decimals is replaced by dedup_distance_m=5 per Matt s call (user-facing unit is meters, not decimal places; the handler will internally translate to quantization step in v0.6-3b). adapter_meta stays at 15 rows -- itd_511 keeps its include_in_llm_context toggle even with zero config keys. Live-DB cleanup: meshai/adapter_config/__init__.py:prune_orphans(conn) DELETEs every adapter_config row whose (adapter, key) is no longer in REGISTRY. Each delete is INFO-logged with the prefix "adapter_config orphan removed:" so docker logs carry the paper trail. Called from init_db() after seed_defaults; idempotent (zero deletes on every subsequent boot). Cache is invalidated when any orphan is removed. adapter_meta is NOT pruned -- meta rows are cheap and useful even for adapters that ended up with zero config keys. Tests (34 cases, replaces v0.6-3a 24-case set): - Registry count is 43; ADAPTER_META is 15 - Seed lands every REGISTRY + ADAPTER_META row; idempotent; never overwrites user edits - prune_orphans removes a synthetic legacy row, logs at INFO with the exact prefix, leaves known keys untouched, leaves adapter_meta untouched, invalidates the accessor cache - Accessor returns correctly-typed values incl new firms.dedup_distance_m - Guard tests: no key in REGISTRY contains "emoji", ends with "_map", or contains "template" / "prefix" (catches CODE leaking back in) Test count: 721 -> 731 (+10 net: +5 prune cases, +1 firms.dedup_distance_m, +3 CODE-guard cases, +1 registry-count assertion). Refs Matt s locked CONFIG-vs-CODE rule.
2026-06-05 18:09:49 +00:00
("x", "y", "1", "1", "integer", "", 0.0),
feat(v0.6-3a): adapter_config foundation -- migration + defaults registry + typed accessor Closes the foundation slice of audit doc Section A (Rule 17). Lands two SQLite tables, the seed routine that populates them from a Python defaults registry, and a typed accessor that handler code will read in v0.6-3b. No handler changes in this commit -- ZERO behavior risk, every existing test still passes (721 / 69 skipped / 0 failed). v6.sql tables: - adapter_config(adapter, key, value_json, default_json, type, description, updated_at) PRIMARY KEY(adapter, key) -- JSON-encoded values flow through a single column uniformly. CHECK constraint on `type` closes the vocab (int/float/str/ bool/json). - adapter_meta(adapter PK, display_name, include_in_llm_context, description, updated_at) -- per-adapter metadata + the user-scopable LLM-context toggle (Matt refinement #5). meshai/adapter_config/ package: - defaults.py: REGISTRY dict mapping (adapter, key) -> {default, type, description}. Covers audit doc sections A.1-A.12: wfigs, nws, usgs_quake, swpc, usgs_nwis, incident family (tomtom_incidents, state_511_atis, itd_511, shared "incident"), central consumer, dispatcher, band_conditions, geocoder, firms, pipeline (Inhibitor + Grouper). ~85 keys total. ADAPTER_META covers 15 adapters with display_name + include_in_llm_context defaulting to True. Per Matt refinement #3, every default matches the current handler constant EXACTLY -- first deploy behavior is unchanged. - _accessor.py: AdapterConfig class with `adapter_config.<adapter>.<key>` syntax. Read pipeline: in-memory cache hit -> SQL -> registry fallback (with WARNING) -> AttributeError. Process-wide cache; PUT via v0.6-3c REST API calls invalidate_cache() to drop the cache. GIL-atomic dict reads on the fast path (handlers call this hot). - __init__.py: seed_defaults(conn) -- INSERT OR IGNOREs one row per registry entry. Idempotent, never overwrites user edits. Wiring: - meshai/persistence/db.py: SCHEMA_VERSION 5 -> 6, and init_db() now calls seed_defaults() after migrations apply. - meshai/main.py: _init_components() now calls init_db() FIRST (per commit #1 lessons-learned: a startup-time migration is required when handlers will rely on the new schema; lazy-on-first-handler is fine for v4/v5 but not for v6 where handler reads start in v0.6-3b). - tests/conftest.py: autouse fixture now calls init_db() + clears the accessor cache around each test, so every test gets the v6 seed AND a clean cache without per-test boilerplate. Tests (tests/test_adapter_config_foundation.py, 24 cases): - v6 tables exist + schema_meta at 6 + type-vocabulary CHECK enforced - seed populates every REGISTRY + ADAPTER_META row, value_json == default_json on first seed, type matches - seed is idempotent + does not overwrite user edits - accessor returns correctly typed values for int/float/str/bool/ json list/json dict/json None - cache hit: second read does not touch the DB (patched _load_from_db raises, accessor still succeeds) - invalidate_cache forces a re-read; mutated DB value wins - registry fallback path triggers when a row is missing (with WARNING) - unknown key raises AttributeError - setattr blocked (writes go via the REST API in 3c) - every default JSON round-trips cleanly; every type is in vocabulary - ADAPTER_META covers every adapter in REGISTRY Test count: 697 -> 721 (+24 new, 0 regressions). v0.6-3b will wire handlers one at a time (wfigs, nws, quake, swpc, nwis, incident, central, dispatcher, band_conditions, geocoder, firms). Per the audit lock, defaults match exactly so each wiring step is a pure refactor -- bisect-safe. v0.6-3c lands the /api/adapter-config CRUD + the AdapterConfig.tsx dashboard editor + cache invalidation on PUT. Refs audit doc v0.6-phase1-audit.md Section A + finding #4.
2026-06-05 17:06:51 +00:00
)
feat(v0.6-3a.1): trim adapter_config registry to CONFIG-only per Matt config-vs-code rule + log-on-delete safety net for orphan cleanup Drops 35 of the v0.6-3a-draft 77 keys + adds 1 net-new key (firms.dedup_distance_m) for a final count of 43. The trim rules: CONFIG (lives in adapter_config, surfaces in the GUI): where we send (channels), how often (cadences/schedules), thresholds (magnitude floors, severity gates, distance radius, cooldown durations, freshness windows), curation data (which sites/states/codes), toggles (enabled, include_in_llm_context, drop_zero_magnitude). CODE (stays in handlers, never reaches the GUI): sentence templates, emoji choices, mapping/translation functions (TomTom icon_map, ITD sub_type_map, Central adapter_map and category_map), rendering logic (anchor priority order, expires-bucket formatting, threshold-state labels), heuristic logic (band_conditions Kp/SFI -> Good/Fair/Poor function). Per-adapter outcome (kept | killed): wfigs 4 | 4 (cooldown_seconds, anchor_max_mi, two re-broadcast toggles) nws 3 | 4 (broadcast_severities, tombstone_msgtypes, warning_suffix_promotes) usgs_quake 6 | 3 (centroid, radius, PAGER list, 3 mag floors) swpc 3 | 7 (three storm-tier floors) usgs_nwis 2 | 4 (parameter_codes, broadcast_on_recede) incident 2 | 0 (freshness_seconds, broadcast_on_update) tomtom_incidents 2 | 1 (drop_zero_magnitude, drop_non_present) state_511_atis 1 | 0 (skipped_states) itd_511 0 | 3 (all sub_type maps/emoji/phrase = CODE) central 1 | 2 (severity_thresholds) dispatcher 4 | 0 (LRU cap, prune params, retention days) band_conditions 3 | 6 (SWPC freshness + HamQSL endpoint config) geocoder 6 | 1 (Photon endpoint + town-OSM curation + cache cap) firms 4 | 1* (confidence_floor, frp_floor, bbox, dedup_distance_m) pipeline 2 | 0 (inhibitor TTL, grouper window) * firms: dedup_lat_lon_decimals is replaced by dedup_distance_m=5 per Matt s call (user-facing unit is meters, not decimal places; the handler will internally translate to quantization step in v0.6-3b). adapter_meta stays at 15 rows -- itd_511 keeps its include_in_llm_context toggle even with zero config keys. Live-DB cleanup: meshai/adapter_config/__init__.py:prune_orphans(conn) DELETEs every adapter_config row whose (adapter, key) is no longer in REGISTRY. Each delete is INFO-logged with the prefix "adapter_config orphan removed:" so docker logs carry the paper trail. Called from init_db() after seed_defaults; idempotent (zero deletes on every subsequent boot). Cache is invalidated when any orphan is removed. adapter_meta is NOT pruned -- meta rows are cheap and useful even for adapters that ended up with zero config keys. Tests (34 cases, replaces v0.6-3a 24-case set): - Registry count is 43; ADAPTER_META is 15 - Seed lands every REGISTRY + ADAPTER_META row; idempotent; never overwrites user edits - prune_orphans removes a synthetic legacy row, logs at INFO with the exact prefix, leaves known keys untouched, leaves adapter_meta untouched, invalidates the accessor cache - Accessor returns correctly-typed values incl new firms.dedup_distance_m - Guard tests: no key in REGISTRY contains "emoji", ends with "_map", or contains "template" / "prefix" (catches CODE leaking back in) Test count: 721 -> 731 (+10 net: +5 prune cases, +1 firms.dedup_distance_m, +3 CODE-guard cases, +1 registry-count assertion). Refs Matt s locked CONFIG-vs-CODE rule.
2026-06-05 18:09:49 +00:00
# ---------- registry shape -----------------------------------------------
def test_registry_at_43_entries():
"""v0.6-3a.1 trim: 43 CONFIG-only keys (was 77 in v0.6-3a draft)."""
assert len(REGISTRY) == 43, (
f"REGISTRY should have 43 entries after CONFIG-vs-CODE trim; got {len(REGISTRY)}. "
f"If a sentence template / emoji / heuristic snuck in, it belongs in CODE not config."
)
def test_adapter_meta_at_15(fresh_db):
assert len(ADAPTER_META) == 15
feat(v0.6-3a): adapter_config foundation -- migration + defaults registry + typed accessor Closes the foundation slice of audit doc Section A (Rule 17). Lands two SQLite tables, the seed routine that populates them from a Python defaults registry, and a typed accessor that handler code will read in v0.6-3b. No handler changes in this commit -- ZERO behavior risk, every existing test still passes (721 / 69 skipped / 0 failed). v6.sql tables: - adapter_config(adapter, key, value_json, default_json, type, description, updated_at) PRIMARY KEY(adapter, key) -- JSON-encoded values flow through a single column uniformly. CHECK constraint on `type` closes the vocab (int/float/str/ bool/json). - adapter_meta(adapter PK, display_name, include_in_llm_context, description, updated_at) -- per-adapter metadata + the user-scopable LLM-context toggle (Matt refinement #5). meshai/adapter_config/ package: - defaults.py: REGISTRY dict mapping (adapter, key) -> {default, type, description}. Covers audit doc sections A.1-A.12: wfigs, nws, usgs_quake, swpc, usgs_nwis, incident family (tomtom_incidents, state_511_atis, itd_511, shared "incident"), central consumer, dispatcher, band_conditions, geocoder, firms, pipeline (Inhibitor + Grouper). ~85 keys total. ADAPTER_META covers 15 adapters with display_name + include_in_llm_context defaulting to True. Per Matt refinement #3, every default matches the current handler constant EXACTLY -- first deploy behavior is unchanged. - _accessor.py: AdapterConfig class with `adapter_config.<adapter>.<key>` syntax. Read pipeline: in-memory cache hit -> SQL -> registry fallback (with WARNING) -> AttributeError. Process-wide cache; PUT via v0.6-3c REST API calls invalidate_cache() to drop the cache. GIL-atomic dict reads on the fast path (handlers call this hot). - __init__.py: seed_defaults(conn) -- INSERT OR IGNOREs one row per registry entry. Idempotent, never overwrites user edits. Wiring: - meshai/persistence/db.py: SCHEMA_VERSION 5 -> 6, and init_db() now calls seed_defaults() after migrations apply. - meshai/main.py: _init_components() now calls init_db() FIRST (per commit #1 lessons-learned: a startup-time migration is required when handlers will rely on the new schema; lazy-on-first-handler is fine for v4/v5 but not for v6 where handler reads start in v0.6-3b). - tests/conftest.py: autouse fixture now calls init_db() + clears the accessor cache around each test, so every test gets the v6 seed AND a clean cache without per-test boilerplate. Tests (tests/test_adapter_config_foundation.py, 24 cases): - v6 tables exist + schema_meta at 6 + type-vocabulary CHECK enforced - seed populates every REGISTRY + ADAPTER_META row, value_json == default_json on first seed, type matches - seed is idempotent + does not overwrite user edits - accessor returns correctly typed values for int/float/str/bool/ json list/json dict/json None - cache hit: second read does not touch the DB (patched _load_from_db raises, accessor still succeeds) - invalidate_cache forces a re-read; mutated DB value wins - registry fallback path triggers when a row is missing (with WARNING) - unknown key raises AttributeError - setattr blocked (writes go via the REST API in 3c) - every default JSON round-trips cleanly; every type is in vocabulary - ADAPTER_META covers every adapter in REGISTRY Test count: 697 -> 721 (+24 new, 0 regressions). v0.6-3b will wire handlers one at a time (wfigs, nws, quake, swpc, nwis, incident, central, dispatcher, band_conditions, geocoder, firms). Per the audit lock, defaults match exactly so each wiring step is a pure refactor -- bisect-safe. v0.6-3c lands the /api/adapter-config CRUD + the AdapterConfig.tsx dashboard editor + cache invalidation on PUT. Refs audit doc v0.6-phase1-audit.md Section A + finding #4.
2026-06-05 17:06:51 +00:00
# ---------- seed ----------------------------------------------------------
def test_seed_populates_every_registry_row(fresh_db):
rows = fresh_db.execute("SELECT adapter, key FROM adapter_config").fetchall()
db_keys = {(r["adapter"], r["key"]) for r in rows}
feat(v0.6-3a.1): trim adapter_config registry to CONFIG-only per Matt config-vs-code rule + log-on-delete safety net for orphan cleanup Drops 35 of the v0.6-3a-draft 77 keys + adds 1 net-new key (firms.dedup_distance_m) for a final count of 43. The trim rules: CONFIG (lives in adapter_config, surfaces in the GUI): where we send (channels), how often (cadences/schedules), thresholds (magnitude floors, severity gates, distance radius, cooldown durations, freshness windows), curation data (which sites/states/codes), toggles (enabled, include_in_llm_context, drop_zero_magnitude). CODE (stays in handlers, never reaches the GUI): sentence templates, emoji choices, mapping/translation functions (TomTom icon_map, ITD sub_type_map, Central adapter_map and category_map), rendering logic (anchor priority order, expires-bucket formatting, threshold-state labels), heuristic logic (band_conditions Kp/SFI -> Good/Fair/Poor function). Per-adapter outcome (kept | killed): wfigs 4 | 4 (cooldown_seconds, anchor_max_mi, two re-broadcast toggles) nws 3 | 4 (broadcast_severities, tombstone_msgtypes, warning_suffix_promotes) usgs_quake 6 | 3 (centroid, radius, PAGER list, 3 mag floors) swpc 3 | 7 (three storm-tier floors) usgs_nwis 2 | 4 (parameter_codes, broadcast_on_recede) incident 2 | 0 (freshness_seconds, broadcast_on_update) tomtom_incidents 2 | 1 (drop_zero_magnitude, drop_non_present) state_511_atis 1 | 0 (skipped_states) itd_511 0 | 3 (all sub_type maps/emoji/phrase = CODE) central 1 | 2 (severity_thresholds) dispatcher 4 | 0 (LRU cap, prune params, retention days) band_conditions 3 | 6 (SWPC freshness + HamQSL endpoint config) geocoder 6 | 1 (Photon endpoint + town-OSM curation + cache cap) firms 4 | 1* (confidence_floor, frp_floor, bbox, dedup_distance_m) pipeline 2 | 0 (inhibitor TTL, grouper window) * firms: dedup_lat_lon_decimals is replaced by dedup_distance_m=5 per Matt s call (user-facing unit is meters, not decimal places; the handler will internally translate to quantization step in v0.6-3b). adapter_meta stays at 15 rows -- itd_511 keeps its include_in_llm_context toggle even with zero config keys. Live-DB cleanup: meshai/adapter_config/__init__.py:prune_orphans(conn) DELETEs every adapter_config row whose (adapter, key) is no longer in REGISTRY. Each delete is INFO-logged with the prefix "adapter_config orphan removed:" so docker logs carry the paper trail. Called from init_db() after seed_defaults; idempotent (zero deletes on every subsequent boot). Cache is invalidated when any orphan is removed. adapter_meta is NOT pruned -- meta rows are cheap and useful even for adapters that ended up with zero config keys. Tests (34 cases, replaces v0.6-3a 24-case set): - Registry count is 43; ADAPTER_META is 15 - Seed lands every REGISTRY + ADAPTER_META row; idempotent; never overwrites user edits - prune_orphans removes a synthetic legacy row, logs at INFO with the exact prefix, leaves known keys untouched, leaves adapter_meta untouched, invalidates the accessor cache - Accessor returns correctly-typed values incl new firms.dedup_distance_m - Guard tests: no key in REGISTRY contains "emoji", ends with "_map", or contains "template" / "prefix" (catches CODE leaking back in) Test count: 721 -> 731 (+10 net: +5 prune cases, +1 firms.dedup_distance_m, +3 CODE-guard cases, +1 registry-count assertion). Refs Matt s locked CONFIG-vs-CODE rule.
2026-06-05 18:09:49 +00:00
assert db_keys == set(REGISTRY.keys())
feat(v0.6-3a): adapter_config foundation -- migration + defaults registry + typed accessor Closes the foundation slice of audit doc Section A (Rule 17). Lands two SQLite tables, the seed routine that populates them from a Python defaults registry, and a typed accessor that handler code will read in v0.6-3b. No handler changes in this commit -- ZERO behavior risk, every existing test still passes (721 / 69 skipped / 0 failed). v6.sql tables: - adapter_config(adapter, key, value_json, default_json, type, description, updated_at) PRIMARY KEY(adapter, key) -- JSON-encoded values flow through a single column uniformly. CHECK constraint on `type` closes the vocab (int/float/str/ bool/json). - adapter_meta(adapter PK, display_name, include_in_llm_context, description, updated_at) -- per-adapter metadata + the user-scopable LLM-context toggle (Matt refinement #5). meshai/adapter_config/ package: - defaults.py: REGISTRY dict mapping (adapter, key) -> {default, type, description}. Covers audit doc sections A.1-A.12: wfigs, nws, usgs_quake, swpc, usgs_nwis, incident family (tomtom_incidents, state_511_atis, itd_511, shared "incident"), central consumer, dispatcher, band_conditions, geocoder, firms, pipeline (Inhibitor + Grouper). ~85 keys total. ADAPTER_META covers 15 adapters with display_name + include_in_llm_context defaulting to True. Per Matt refinement #3, every default matches the current handler constant EXACTLY -- first deploy behavior is unchanged. - _accessor.py: AdapterConfig class with `adapter_config.<adapter>.<key>` syntax. Read pipeline: in-memory cache hit -> SQL -> registry fallback (with WARNING) -> AttributeError. Process-wide cache; PUT via v0.6-3c REST API calls invalidate_cache() to drop the cache. GIL-atomic dict reads on the fast path (handlers call this hot). - __init__.py: seed_defaults(conn) -- INSERT OR IGNOREs one row per registry entry. Idempotent, never overwrites user edits. Wiring: - meshai/persistence/db.py: SCHEMA_VERSION 5 -> 6, and init_db() now calls seed_defaults() after migrations apply. - meshai/main.py: _init_components() now calls init_db() FIRST (per commit #1 lessons-learned: a startup-time migration is required when handlers will rely on the new schema; lazy-on-first-handler is fine for v4/v5 but not for v6 where handler reads start in v0.6-3b). - tests/conftest.py: autouse fixture now calls init_db() + clears the accessor cache around each test, so every test gets the v6 seed AND a clean cache without per-test boilerplate. Tests (tests/test_adapter_config_foundation.py, 24 cases): - v6 tables exist + schema_meta at 6 + type-vocabulary CHECK enforced - seed populates every REGISTRY + ADAPTER_META row, value_json == default_json on first seed, type matches - seed is idempotent + does not overwrite user edits - accessor returns correctly typed values for int/float/str/bool/ json list/json dict/json None - cache hit: second read does not touch the DB (patched _load_from_db raises, accessor still succeeds) - invalidate_cache forces a re-read; mutated DB value wins - registry fallback path triggers when a row is missing (with WARNING) - unknown key raises AttributeError - setattr blocked (writes go via the REST API in 3c) - every default JSON round-trips cleanly; every type is in vocabulary - ADAPTER_META covers every adapter in REGISTRY Test count: 697 -> 721 (+24 new, 0 regressions). v0.6-3b will wire handlers one at a time (wfigs, nws, quake, swpc, nwis, incident, central, dispatcher, band_conditions, geocoder, firms). Per the audit lock, defaults match exactly so each wiring step is a pure refactor -- bisect-safe. v0.6-3c lands the /api/adapter-config CRUD + the AdapterConfig.tsx dashboard editor + cache invalidation on PUT. Refs audit doc v0.6-phase1-audit.md Section A + finding #4.
2026-06-05 17:06:51 +00:00
def test_seed_value_matches_registry_default(fresh_db):
for (adapter, key), spec in REGISTRY.items():
row = fresh_db.execute(
"SELECT value_json, default_json, type FROM adapter_config "
"WHERE adapter=? AND key=?",
(adapter, key),
).fetchone()
expected = json.dumps(spec["default"])
assert row["value_json"] == expected, f"{adapter}.{key} value drift"
assert row["default_json"] == expected, f"{adapter}.{key} default drift"
assert row["type"] == spec["type"]
def test_seed_populates_every_adapter_meta_row(fresh_db):
rows = fresh_db.execute("SELECT adapter, include_in_llm_context FROM adapter_meta").fetchall()
db_adapters = {r["adapter"] for r in rows}
assert db_adapters == set(ADAPTER_META.keys())
def test_seed_is_idempotent(fresh_db):
a, b = seed_defaults(fresh_db)
assert a == 0 and b == 0
def test_seed_does_not_overwrite_user_edits(fresh_db):
fresh_db.execute(
"UPDATE adapter_config SET value_json=? WHERE adapter=? AND key=?",
("999", "wfigs", "cooldown_seconds"),
)
seed_defaults(fresh_db)
row = fresh_db.execute(
"SELECT value_json FROM adapter_config "
"WHERE adapter='wfigs' AND key='cooldown_seconds'"
).fetchone()
feat(v0.6-3a.1): trim adapter_config registry to CONFIG-only per Matt config-vs-code rule + log-on-delete safety net for orphan cleanup Drops 35 of the v0.6-3a-draft 77 keys + adds 1 net-new key (firms.dedup_distance_m) for a final count of 43. The trim rules: CONFIG (lives in adapter_config, surfaces in the GUI): where we send (channels), how often (cadences/schedules), thresholds (magnitude floors, severity gates, distance radius, cooldown durations, freshness windows), curation data (which sites/states/codes), toggles (enabled, include_in_llm_context, drop_zero_magnitude). CODE (stays in handlers, never reaches the GUI): sentence templates, emoji choices, mapping/translation functions (TomTom icon_map, ITD sub_type_map, Central adapter_map and category_map), rendering logic (anchor priority order, expires-bucket formatting, threshold-state labels), heuristic logic (band_conditions Kp/SFI -> Good/Fair/Poor function). Per-adapter outcome (kept | killed): wfigs 4 | 4 (cooldown_seconds, anchor_max_mi, two re-broadcast toggles) nws 3 | 4 (broadcast_severities, tombstone_msgtypes, warning_suffix_promotes) usgs_quake 6 | 3 (centroid, radius, PAGER list, 3 mag floors) swpc 3 | 7 (three storm-tier floors) usgs_nwis 2 | 4 (parameter_codes, broadcast_on_recede) incident 2 | 0 (freshness_seconds, broadcast_on_update) tomtom_incidents 2 | 1 (drop_zero_magnitude, drop_non_present) state_511_atis 1 | 0 (skipped_states) itd_511 0 | 3 (all sub_type maps/emoji/phrase = CODE) central 1 | 2 (severity_thresholds) dispatcher 4 | 0 (LRU cap, prune params, retention days) band_conditions 3 | 6 (SWPC freshness + HamQSL endpoint config) geocoder 6 | 1 (Photon endpoint + town-OSM curation + cache cap) firms 4 | 1* (confidence_floor, frp_floor, bbox, dedup_distance_m) pipeline 2 | 0 (inhibitor TTL, grouper window) * firms: dedup_lat_lon_decimals is replaced by dedup_distance_m=5 per Matt s call (user-facing unit is meters, not decimal places; the handler will internally translate to quantization step in v0.6-3b). adapter_meta stays at 15 rows -- itd_511 keeps its include_in_llm_context toggle even with zero config keys. Live-DB cleanup: meshai/adapter_config/__init__.py:prune_orphans(conn) DELETEs every adapter_config row whose (adapter, key) is no longer in REGISTRY. Each delete is INFO-logged with the prefix "adapter_config orphan removed:" so docker logs carry the paper trail. Called from init_db() after seed_defaults; idempotent (zero deletes on every subsequent boot). Cache is invalidated when any orphan is removed. adapter_meta is NOT pruned -- meta rows are cheap and useful even for adapters that ended up with zero config keys. Tests (34 cases, replaces v0.6-3a 24-case set): - Registry count is 43; ADAPTER_META is 15 - Seed lands every REGISTRY + ADAPTER_META row; idempotent; never overwrites user edits - prune_orphans removes a synthetic legacy row, logs at INFO with the exact prefix, leaves known keys untouched, leaves adapter_meta untouched, invalidates the accessor cache - Accessor returns correctly-typed values incl new firms.dedup_distance_m - Guard tests: no key in REGISTRY contains "emoji", ends with "_map", or contains "template" / "prefix" (catches CODE leaking back in) Test count: 721 -> 731 (+10 net: +5 prune cases, +1 firms.dedup_distance_m, +3 CODE-guard cases, +1 registry-count assertion). Refs Matt s locked CONFIG-vs-CODE rule.
2026-06-05 18:09:49 +00:00
assert row["value_json"] == "999"
# ---------- prune_orphans -------------------------------------------------
feat(v0.6-3a): adapter_config foundation -- migration + defaults registry + typed accessor Closes the foundation slice of audit doc Section A (Rule 17). Lands two SQLite tables, the seed routine that populates them from a Python defaults registry, and a typed accessor that handler code will read in v0.6-3b. No handler changes in this commit -- ZERO behavior risk, every existing test still passes (721 / 69 skipped / 0 failed). v6.sql tables: - adapter_config(adapter, key, value_json, default_json, type, description, updated_at) PRIMARY KEY(adapter, key) -- JSON-encoded values flow through a single column uniformly. CHECK constraint on `type` closes the vocab (int/float/str/ bool/json). - adapter_meta(adapter PK, display_name, include_in_llm_context, description, updated_at) -- per-adapter metadata + the user-scopable LLM-context toggle (Matt refinement #5). meshai/adapter_config/ package: - defaults.py: REGISTRY dict mapping (adapter, key) -> {default, type, description}. Covers audit doc sections A.1-A.12: wfigs, nws, usgs_quake, swpc, usgs_nwis, incident family (tomtom_incidents, state_511_atis, itd_511, shared "incident"), central consumer, dispatcher, band_conditions, geocoder, firms, pipeline (Inhibitor + Grouper). ~85 keys total. ADAPTER_META covers 15 adapters with display_name + include_in_llm_context defaulting to True. Per Matt refinement #3, every default matches the current handler constant EXACTLY -- first deploy behavior is unchanged. - _accessor.py: AdapterConfig class with `adapter_config.<adapter>.<key>` syntax. Read pipeline: in-memory cache hit -> SQL -> registry fallback (with WARNING) -> AttributeError. Process-wide cache; PUT via v0.6-3c REST API calls invalidate_cache() to drop the cache. GIL-atomic dict reads on the fast path (handlers call this hot). - __init__.py: seed_defaults(conn) -- INSERT OR IGNOREs one row per registry entry. Idempotent, never overwrites user edits. Wiring: - meshai/persistence/db.py: SCHEMA_VERSION 5 -> 6, and init_db() now calls seed_defaults() after migrations apply. - meshai/main.py: _init_components() now calls init_db() FIRST (per commit #1 lessons-learned: a startup-time migration is required when handlers will rely on the new schema; lazy-on-first-handler is fine for v4/v5 but not for v6 where handler reads start in v0.6-3b). - tests/conftest.py: autouse fixture now calls init_db() + clears the accessor cache around each test, so every test gets the v6 seed AND a clean cache without per-test boilerplate. Tests (tests/test_adapter_config_foundation.py, 24 cases): - v6 tables exist + schema_meta at 6 + type-vocabulary CHECK enforced - seed populates every REGISTRY + ADAPTER_META row, value_json == default_json on first seed, type matches - seed is idempotent + does not overwrite user edits - accessor returns correctly typed values for int/float/str/bool/ json list/json dict/json None - cache hit: second read does not touch the DB (patched _load_from_db raises, accessor still succeeds) - invalidate_cache forces a re-read; mutated DB value wins - registry fallback path triggers when a row is missing (with WARNING) - unknown key raises AttributeError - setattr blocked (writes go via the REST API in 3c) - every default JSON round-trips cleanly; every type is in vocabulary - ADAPTER_META covers every adapter in REGISTRY Test count: 697 -> 721 (+24 new, 0 regressions). v0.6-3b will wire handlers one at a time (wfigs, nws, quake, swpc, nwis, incident, central, dispatcher, band_conditions, geocoder, firms). Per the audit lock, defaults match exactly so each wiring step is a pure refactor -- bisect-safe. v0.6-3c lands the /api/adapter-config CRUD + the AdapterConfig.tsx dashboard editor + cache invalidation on PUT. Refs audit doc v0.6-phase1-audit.md Section A + finding #4.
2026-06-05 17:06:51 +00:00
feat(v0.6-3a.1): trim adapter_config registry to CONFIG-only per Matt config-vs-code rule + log-on-delete safety net for orphan cleanup Drops 35 of the v0.6-3a-draft 77 keys + adds 1 net-new key (firms.dedup_distance_m) for a final count of 43. The trim rules: CONFIG (lives in adapter_config, surfaces in the GUI): where we send (channels), how often (cadences/schedules), thresholds (magnitude floors, severity gates, distance radius, cooldown durations, freshness windows), curation data (which sites/states/codes), toggles (enabled, include_in_llm_context, drop_zero_magnitude). CODE (stays in handlers, never reaches the GUI): sentence templates, emoji choices, mapping/translation functions (TomTom icon_map, ITD sub_type_map, Central adapter_map and category_map), rendering logic (anchor priority order, expires-bucket formatting, threshold-state labels), heuristic logic (band_conditions Kp/SFI -> Good/Fair/Poor function). Per-adapter outcome (kept | killed): wfigs 4 | 4 (cooldown_seconds, anchor_max_mi, two re-broadcast toggles) nws 3 | 4 (broadcast_severities, tombstone_msgtypes, warning_suffix_promotes) usgs_quake 6 | 3 (centroid, radius, PAGER list, 3 mag floors) swpc 3 | 7 (three storm-tier floors) usgs_nwis 2 | 4 (parameter_codes, broadcast_on_recede) incident 2 | 0 (freshness_seconds, broadcast_on_update) tomtom_incidents 2 | 1 (drop_zero_magnitude, drop_non_present) state_511_atis 1 | 0 (skipped_states) itd_511 0 | 3 (all sub_type maps/emoji/phrase = CODE) central 1 | 2 (severity_thresholds) dispatcher 4 | 0 (LRU cap, prune params, retention days) band_conditions 3 | 6 (SWPC freshness + HamQSL endpoint config) geocoder 6 | 1 (Photon endpoint + town-OSM curation + cache cap) firms 4 | 1* (confidence_floor, frp_floor, bbox, dedup_distance_m) pipeline 2 | 0 (inhibitor TTL, grouper window) * firms: dedup_lat_lon_decimals is replaced by dedup_distance_m=5 per Matt s call (user-facing unit is meters, not decimal places; the handler will internally translate to quantization step in v0.6-3b). adapter_meta stays at 15 rows -- itd_511 keeps its include_in_llm_context toggle even with zero config keys. Live-DB cleanup: meshai/adapter_config/__init__.py:prune_orphans(conn) DELETEs every adapter_config row whose (adapter, key) is no longer in REGISTRY. Each delete is INFO-logged with the prefix "adapter_config orphan removed:" so docker logs carry the paper trail. Called from init_db() after seed_defaults; idempotent (zero deletes on every subsequent boot). Cache is invalidated when any orphan is removed. adapter_meta is NOT pruned -- meta rows are cheap and useful even for adapters that ended up with zero config keys. Tests (34 cases, replaces v0.6-3a 24-case set): - Registry count is 43; ADAPTER_META is 15 - Seed lands every REGISTRY + ADAPTER_META row; idempotent; never overwrites user edits - prune_orphans removes a synthetic legacy row, logs at INFO with the exact prefix, leaves known keys untouched, leaves adapter_meta untouched, invalidates the accessor cache - Accessor returns correctly-typed values incl new firms.dedup_distance_m - Guard tests: no key in REGISTRY contains "emoji", ends with "_map", or contains "template" / "prefix" (catches CODE leaking back in) Test count: 721 -> 731 (+10 net: +5 prune cases, +1 firms.dedup_distance_m, +3 CODE-guard cases, +1 registry-count assertion). Refs Matt s locked CONFIG-vs-CODE rule.
2026-06-05 18:09:49 +00:00
def test_prune_orphans_removes_unknown_keys(fresh_db, caplog):
"""A row whose (adapter, key) is no longer in REGISTRY is deleted on
the next prune_orphans, and the delete is logged at INFO."""
fresh_db.execute(
"INSERT INTO adapter_config(adapter, key, value_json, default_json, "
"type, description, updated_at) VALUES (?,?,?,?,?,?,?)",
("wfigs", "deprecated_legacy_key", "\"old\"", "\"old\"",
"str", "", 0.0),
)
caplog.set_level(logging.INFO, logger="meshai.adapter_config")
removed = prune_orphans(fresh_db)
assert removed == 1
msgs = [r.getMessage() for r in caplog.records
if r.name.startswith("meshai.adapter_config")]
assert any(
"adapter_config orphan removed: wfigs.deprecated_legacy_key" in m
for m in msgs
), f"expected orphan-removed log line; got: {msgs}"
# Row gone.
assert fresh_db.execute(
"SELECT 1 FROM adapter_config WHERE adapter=? AND key=?",
("wfigs", "deprecated_legacy_key"),
).fetchone() is None
def test_prune_orphans_idempotent(fresh_db):
assert prune_orphans(fresh_db) == 0
assert prune_orphans(fresh_db) == 0
def test_prune_orphans_does_not_touch_known_keys(fresh_db):
"""Every REGISTRY row survives the prune."""
before = {(r["adapter"], r["key"]) for r in fresh_db.execute(
"SELECT adapter, key FROM adapter_config"
).fetchall()}
prune_orphans(fresh_db)
after = {(r["adapter"], r["key"]) for r in fresh_db.execute(
"SELECT adapter, key FROM adapter_config"
).fetchall()}
assert before == after == set(REGISTRY.keys())
feat(v0.6-3a): adapter_config foundation -- migration + defaults registry + typed accessor Closes the foundation slice of audit doc Section A (Rule 17). Lands two SQLite tables, the seed routine that populates them from a Python defaults registry, and a typed accessor that handler code will read in v0.6-3b. No handler changes in this commit -- ZERO behavior risk, every existing test still passes (721 / 69 skipped / 0 failed). v6.sql tables: - adapter_config(adapter, key, value_json, default_json, type, description, updated_at) PRIMARY KEY(adapter, key) -- JSON-encoded values flow through a single column uniformly. CHECK constraint on `type` closes the vocab (int/float/str/ bool/json). - adapter_meta(adapter PK, display_name, include_in_llm_context, description, updated_at) -- per-adapter metadata + the user-scopable LLM-context toggle (Matt refinement #5). meshai/adapter_config/ package: - defaults.py: REGISTRY dict mapping (adapter, key) -> {default, type, description}. Covers audit doc sections A.1-A.12: wfigs, nws, usgs_quake, swpc, usgs_nwis, incident family (tomtom_incidents, state_511_atis, itd_511, shared "incident"), central consumer, dispatcher, band_conditions, geocoder, firms, pipeline (Inhibitor + Grouper). ~85 keys total. ADAPTER_META covers 15 adapters with display_name + include_in_llm_context defaulting to True. Per Matt refinement #3, every default matches the current handler constant EXACTLY -- first deploy behavior is unchanged. - _accessor.py: AdapterConfig class with `adapter_config.<adapter>.<key>` syntax. Read pipeline: in-memory cache hit -> SQL -> registry fallback (with WARNING) -> AttributeError. Process-wide cache; PUT via v0.6-3c REST API calls invalidate_cache() to drop the cache. GIL-atomic dict reads on the fast path (handlers call this hot). - __init__.py: seed_defaults(conn) -- INSERT OR IGNOREs one row per registry entry. Idempotent, never overwrites user edits. Wiring: - meshai/persistence/db.py: SCHEMA_VERSION 5 -> 6, and init_db() now calls seed_defaults() after migrations apply. - meshai/main.py: _init_components() now calls init_db() FIRST (per commit #1 lessons-learned: a startup-time migration is required when handlers will rely on the new schema; lazy-on-first-handler is fine for v4/v5 but not for v6 where handler reads start in v0.6-3b). - tests/conftest.py: autouse fixture now calls init_db() + clears the accessor cache around each test, so every test gets the v6 seed AND a clean cache without per-test boilerplate. Tests (tests/test_adapter_config_foundation.py, 24 cases): - v6 tables exist + schema_meta at 6 + type-vocabulary CHECK enforced - seed populates every REGISTRY + ADAPTER_META row, value_json == default_json on first seed, type matches - seed is idempotent + does not overwrite user edits - accessor returns correctly typed values for int/float/str/bool/ json list/json dict/json None - cache hit: second read does not touch the DB (patched _load_from_db raises, accessor still succeeds) - invalidate_cache forces a re-read; mutated DB value wins - registry fallback path triggers when a row is missing (with WARNING) - unknown key raises AttributeError - setattr blocked (writes go via the REST API in 3c) - every default JSON round-trips cleanly; every type is in vocabulary - ADAPTER_META covers every adapter in REGISTRY Test count: 697 -> 721 (+24 new, 0 regressions). v0.6-3b will wire handlers one at a time (wfigs, nws, quake, swpc, nwis, incident, central, dispatcher, band_conditions, geocoder, firms). Per the audit lock, defaults match exactly so each wiring step is a pure refactor -- bisect-safe. v0.6-3c lands the /api/adapter-config CRUD + the AdapterConfig.tsx dashboard editor + cache invalidation on PUT. Refs audit doc v0.6-phase1-audit.md Section A + finding #4.
2026-06-05 17:06:51 +00:00
feat(v0.6-3a.1): trim adapter_config registry to CONFIG-only per Matt config-vs-code rule + log-on-delete safety net for orphan cleanup Drops 35 of the v0.6-3a-draft 77 keys + adds 1 net-new key (firms.dedup_distance_m) for a final count of 43. The trim rules: CONFIG (lives in adapter_config, surfaces in the GUI): where we send (channels), how often (cadences/schedules), thresholds (magnitude floors, severity gates, distance radius, cooldown durations, freshness windows), curation data (which sites/states/codes), toggles (enabled, include_in_llm_context, drop_zero_magnitude). CODE (stays in handlers, never reaches the GUI): sentence templates, emoji choices, mapping/translation functions (TomTom icon_map, ITD sub_type_map, Central adapter_map and category_map), rendering logic (anchor priority order, expires-bucket formatting, threshold-state labels), heuristic logic (band_conditions Kp/SFI -> Good/Fair/Poor function). Per-adapter outcome (kept | killed): wfigs 4 | 4 (cooldown_seconds, anchor_max_mi, two re-broadcast toggles) nws 3 | 4 (broadcast_severities, tombstone_msgtypes, warning_suffix_promotes) usgs_quake 6 | 3 (centroid, radius, PAGER list, 3 mag floors) swpc 3 | 7 (three storm-tier floors) usgs_nwis 2 | 4 (parameter_codes, broadcast_on_recede) incident 2 | 0 (freshness_seconds, broadcast_on_update) tomtom_incidents 2 | 1 (drop_zero_magnitude, drop_non_present) state_511_atis 1 | 0 (skipped_states) itd_511 0 | 3 (all sub_type maps/emoji/phrase = CODE) central 1 | 2 (severity_thresholds) dispatcher 4 | 0 (LRU cap, prune params, retention days) band_conditions 3 | 6 (SWPC freshness + HamQSL endpoint config) geocoder 6 | 1 (Photon endpoint + town-OSM curation + cache cap) firms 4 | 1* (confidence_floor, frp_floor, bbox, dedup_distance_m) pipeline 2 | 0 (inhibitor TTL, grouper window) * firms: dedup_lat_lon_decimals is replaced by dedup_distance_m=5 per Matt s call (user-facing unit is meters, not decimal places; the handler will internally translate to quantization step in v0.6-3b). adapter_meta stays at 15 rows -- itd_511 keeps its include_in_llm_context toggle even with zero config keys. Live-DB cleanup: meshai/adapter_config/__init__.py:prune_orphans(conn) DELETEs every adapter_config row whose (adapter, key) is no longer in REGISTRY. Each delete is INFO-logged with the prefix "adapter_config orphan removed:" so docker logs carry the paper trail. Called from init_db() after seed_defaults; idempotent (zero deletes on every subsequent boot). Cache is invalidated when any orphan is removed. adapter_meta is NOT pruned -- meta rows are cheap and useful even for adapters that ended up with zero config keys. Tests (34 cases, replaces v0.6-3a 24-case set): - Registry count is 43; ADAPTER_META is 15 - Seed lands every REGISTRY + ADAPTER_META row; idempotent; never overwrites user edits - prune_orphans removes a synthetic legacy row, logs at INFO with the exact prefix, leaves known keys untouched, leaves adapter_meta untouched, invalidates the accessor cache - Accessor returns correctly-typed values incl new firms.dedup_distance_m - Guard tests: no key in REGISTRY contains "emoji", ends with "_map", or contains "template" / "prefix" (catches CODE leaking back in) Test count: 721 -> 731 (+10 net: +5 prune cases, +1 firms.dedup_distance_m, +3 CODE-guard cases, +1 registry-count assertion). Refs Matt s locked CONFIG-vs-CODE rule.
2026-06-05 18:09:49 +00:00
def test_prune_orphans_does_not_touch_adapter_meta(fresh_db):
"""A previously-known adapter whose config keys all moved to CODE
keeps its adapter_meta row (for the include_in_llm_context toggle)."""
before = fresh_db.execute("SELECT COUNT(*) FROM adapter_meta").fetchone()[0]
prune_orphans(fresh_db)
after = fresh_db.execute("SELECT COUNT(*) FROM adapter_meta").fetchone()[0]
assert before == after == len(ADAPTER_META)
def test_prune_orphans_invalidates_cache(fresh_db):
"""If a key disappears, any cached read of it should NOT linger."""
invalidate_cache()
# Prime cache with a key that will become orphan.
fresh_db.execute(
"INSERT INTO adapter_config(adapter, key, value_json, default_json, "
"type, description, updated_at) VALUES (?,?,?,?,?,?,?)",
("wfigs", "ghost", "42", "42", "int", "", 0.0),
)
# We can't read it via accessor (not in REGISTRY -> no fallback) so
# we just verify the cache is empty after prune.
prune_orphans(fresh_db)
assert accessor_mod._cache == {}, "cache should be cleared after orphan prune"
# ---------- accessor ------------------------------------------------------
feat(v0.6-3a): adapter_config foundation -- migration + defaults registry + typed accessor Closes the foundation slice of audit doc Section A (Rule 17). Lands two SQLite tables, the seed routine that populates them from a Python defaults registry, and a typed accessor that handler code will read in v0.6-3b. No handler changes in this commit -- ZERO behavior risk, every existing test still passes (721 / 69 skipped / 0 failed). v6.sql tables: - adapter_config(adapter, key, value_json, default_json, type, description, updated_at) PRIMARY KEY(adapter, key) -- JSON-encoded values flow through a single column uniformly. CHECK constraint on `type` closes the vocab (int/float/str/ bool/json). - adapter_meta(adapter PK, display_name, include_in_llm_context, description, updated_at) -- per-adapter metadata + the user-scopable LLM-context toggle (Matt refinement #5). meshai/adapter_config/ package: - defaults.py: REGISTRY dict mapping (adapter, key) -> {default, type, description}. Covers audit doc sections A.1-A.12: wfigs, nws, usgs_quake, swpc, usgs_nwis, incident family (tomtom_incidents, state_511_atis, itd_511, shared "incident"), central consumer, dispatcher, band_conditions, geocoder, firms, pipeline (Inhibitor + Grouper). ~85 keys total. ADAPTER_META covers 15 adapters with display_name + include_in_llm_context defaulting to True. Per Matt refinement #3, every default matches the current handler constant EXACTLY -- first deploy behavior is unchanged. - _accessor.py: AdapterConfig class with `adapter_config.<adapter>.<key>` syntax. Read pipeline: in-memory cache hit -> SQL -> registry fallback (with WARNING) -> AttributeError. Process-wide cache; PUT via v0.6-3c REST API calls invalidate_cache() to drop the cache. GIL-atomic dict reads on the fast path (handlers call this hot). - __init__.py: seed_defaults(conn) -- INSERT OR IGNOREs one row per registry entry. Idempotent, never overwrites user edits. Wiring: - meshai/persistence/db.py: SCHEMA_VERSION 5 -> 6, and init_db() now calls seed_defaults() after migrations apply. - meshai/main.py: _init_components() now calls init_db() FIRST (per commit #1 lessons-learned: a startup-time migration is required when handlers will rely on the new schema; lazy-on-first-handler is fine for v4/v5 but not for v6 where handler reads start in v0.6-3b). - tests/conftest.py: autouse fixture now calls init_db() + clears the accessor cache around each test, so every test gets the v6 seed AND a clean cache without per-test boilerplate. Tests (tests/test_adapter_config_foundation.py, 24 cases): - v6 tables exist + schema_meta at 6 + type-vocabulary CHECK enforced - seed populates every REGISTRY + ADAPTER_META row, value_json == default_json on first seed, type matches - seed is idempotent + does not overwrite user edits - accessor returns correctly typed values for int/float/str/bool/ json list/json dict/json None - cache hit: second read does not touch the DB (patched _load_from_db raises, accessor still succeeds) - invalidate_cache forces a re-read; mutated DB value wins - registry fallback path triggers when a row is missing (with WARNING) - unknown key raises AttributeError - setattr blocked (writes go via the REST API in 3c) - every default JSON round-trips cleanly; every type is in vocabulary - ADAPTER_META covers every adapter in REGISTRY Test count: 697 -> 721 (+24 new, 0 regressions). v0.6-3b will wire handlers one at a time (wfigs, nws, quake, swpc, nwis, incident, central, dispatcher, band_conditions, geocoder, firms). Per the audit lock, defaults match exactly so each wiring step is a pure refactor -- bisect-safe. v0.6-3c lands the /api/adapter-config CRUD + the AdapterConfig.tsx dashboard editor + cache invalidation on PUT. Refs audit doc v0.6-phase1-audit.md Section A + finding #4.
2026-06-05 17:06:51 +00:00
def test_accessor_returns_int(fresh_db):
invalidate_cache()
feat(v0.6-3a.1): trim adapter_config registry to CONFIG-only per Matt config-vs-code rule + log-on-delete safety net for orphan cleanup Drops 35 of the v0.6-3a-draft 77 keys + adds 1 net-new key (firms.dedup_distance_m) for a final count of 43. The trim rules: CONFIG (lives in adapter_config, surfaces in the GUI): where we send (channels), how often (cadences/schedules), thresholds (magnitude floors, severity gates, distance radius, cooldown durations, freshness windows), curation data (which sites/states/codes), toggles (enabled, include_in_llm_context, drop_zero_magnitude). CODE (stays in handlers, never reaches the GUI): sentence templates, emoji choices, mapping/translation functions (TomTom icon_map, ITD sub_type_map, Central adapter_map and category_map), rendering logic (anchor priority order, expires-bucket formatting, threshold-state labels), heuristic logic (band_conditions Kp/SFI -> Good/Fair/Poor function). Per-adapter outcome (kept | killed): wfigs 4 | 4 (cooldown_seconds, anchor_max_mi, two re-broadcast toggles) nws 3 | 4 (broadcast_severities, tombstone_msgtypes, warning_suffix_promotes) usgs_quake 6 | 3 (centroid, radius, PAGER list, 3 mag floors) swpc 3 | 7 (three storm-tier floors) usgs_nwis 2 | 4 (parameter_codes, broadcast_on_recede) incident 2 | 0 (freshness_seconds, broadcast_on_update) tomtom_incidents 2 | 1 (drop_zero_magnitude, drop_non_present) state_511_atis 1 | 0 (skipped_states) itd_511 0 | 3 (all sub_type maps/emoji/phrase = CODE) central 1 | 2 (severity_thresholds) dispatcher 4 | 0 (LRU cap, prune params, retention days) band_conditions 3 | 6 (SWPC freshness + HamQSL endpoint config) geocoder 6 | 1 (Photon endpoint + town-OSM curation + cache cap) firms 4 | 1* (confidence_floor, frp_floor, bbox, dedup_distance_m) pipeline 2 | 0 (inhibitor TTL, grouper window) * firms: dedup_lat_lon_decimals is replaced by dedup_distance_m=5 per Matt s call (user-facing unit is meters, not decimal places; the handler will internally translate to quantization step in v0.6-3b). adapter_meta stays at 15 rows -- itd_511 keeps its include_in_llm_context toggle even with zero config keys. Live-DB cleanup: meshai/adapter_config/__init__.py:prune_orphans(conn) DELETEs every adapter_config row whose (adapter, key) is no longer in REGISTRY. Each delete is INFO-logged with the prefix "adapter_config orphan removed:" so docker logs carry the paper trail. Called from init_db() after seed_defaults; idempotent (zero deletes on every subsequent boot). Cache is invalidated when any orphan is removed. adapter_meta is NOT pruned -- meta rows are cheap and useful even for adapters that ended up with zero config keys. Tests (34 cases, replaces v0.6-3a 24-case set): - Registry count is 43; ADAPTER_META is 15 - Seed lands every REGISTRY + ADAPTER_META row; idempotent; never overwrites user edits - prune_orphans removes a synthetic legacy row, logs at INFO with the exact prefix, leaves known keys untouched, leaves adapter_meta untouched, invalidates the accessor cache - Accessor returns correctly-typed values incl new firms.dedup_distance_m - Guard tests: no key in REGISTRY contains "emoji", ends with "_map", or contains "template" / "prefix" (catches CODE leaking back in) Test count: 721 -> 731 (+10 net: +5 prune cases, +1 firms.dedup_distance_m, +3 CODE-guard cases, +1 registry-count assertion). Refs Matt s locked CONFIG-vs-CODE rule.
2026-06-05 18:09:49 +00:00
assert adapter_config.wfigs.cooldown_seconds == 28800
feat(v0.6-3a): adapter_config foundation -- migration + defaults registry + typed accessor Closes the foundation slice of audit doc Section A (Rule 17). Lands two SQLite tables, the seed routine that populates them from a Python defaults registry, and a typed accessor that handler code will read in v0.6-3b. No handler changes in this commit -- ZERO behavior risk, every existing test still passes (721 / 69 skipped / 0 failed). v6.sql tables: - adapter_config(adapter, key, value_json, default_json, type, description, updated_at) PRIMARY KEY(adapter, key) -- JSON-encoded values flow through a single column uniformly. CHECK constraint on `type` closes the vocab (int/float/str/ bool/json). - adapter_meta(adapter PK, display_name, include_in_llm_context, description, updated_at) -- per-adapter metadata + the user-scopable LLM-context toggle (Matt refinement #5). meshai/adapter_config/ package: - defaults.py: REGISTRY dict mapping (adapter, key) -> {default, type, description}. Covers audit doc sections A.1-A.12: wfigs, nws, usgs_quake, swpc, usgs_nwis, incident family (tomtom_incidents, state_511_atis, itd_511, shared "incident"), central consumer, dispatcher, band_conditions, geocoder, firms, pipeline (Inhibitor + Grouper). ~85 keys total. ADAPTER_META covers 15 adapters with display_name + include_in_llm_context defaulting to True. Per Matt refinement #3, every default matches the current handler constant EXACTLY -- first deploy behavior is unchanged. - _accessor.py: AdapterConfig class with `adapter_config.<adapter>.<key>` syntax. Read pipeline: in-memory cache hit -> SQL -> registry fallback (with WARNING) -> AttributeError. Process-wide cache; PUT via v0.6-3c REST API calls invalidate_cache() to drop the cache. GIL-atomic dict reads on the fast path (handlers call this hot). - __init__.py: seed_defaults(conn) -- INSERT OR IGNOREs one row per registry entry. Idempotent, never overwrites user edits. Wiring: - meshai/persistence/db.py: SCHEMA_VERSION 5 -> 6, and init_db() now calls seed_defaults() after migrations apply. - meshai/main.py: _init_components() now calls init_db() FIRST (per commit #1 lessons-learned: a startup-time migration is required when handlers will rely on the new schema; lazy-on-first-handler is fine for v4/v5 but not for v6 where handler reads start in v0.6-3b). - tests/conftest.py: autouse fixture now calls init_db() + clears the accessor cache around each test, so every test gets the v6 seed AND a clean cache without per-test boilerplate. Tests (tests/test_adapter_config_foundation.py, 24 cases): - v6 tables exist + schema_meta at 6 + type-vocabulary CHECK enforced - seed populates every REGISTRY + ADAPTER_META row, value_json == default_json on first seed, type matches - seed is idempotent + does not overwrite user edits - accessor returns correctly typed values for int/float/str/bool/ json list/json dict/json None - cache hit: second read does not touch the DB (patched _load_from_db raises, accessor still succeeds) - invalidate_cache forces a re-read; mutated DB value wins - registry fallback path triggers when a row is missing (with WARNING) - unknown key raises AttributeError - setattr blocked (writes go via the REST API in 3c) - every default JSON round-trips cleanly; every type is in vocabulary - ADAPTER_META covers every adapter in REGISTRY Test count: 697 -> 721 (+24 new, 0 regressions). v0.6-3b will wire handlers one at a time (wfigs, nws, quake, swpc, nwis, incident, central, dispatcher, band_conditions, geocoder, firms). Per the audit lock, defaults match exactly so each wiring step is a pure refactor -- bisect-safe. v0.6-3c lands the /api/adapter-config CRUD + the AdapterConfig.tsx dashboard editor + cache invalidation on PUT. Refs audit doc v0.6-phase1-audit.md Section A + finding #4.
2026-06-05 17:06:51 +00:00
def test_accessor_returns_float(fresh_db):
invalidate_cache()
feat(v0.6-3a.1): trim adapter_config registry to CONFIG-only per Matt config-vs-code rule + log-on-delete safety net for orphan cleanup Drops 35 of the v0.6-3a-draft 77 keys + adds 1 net-new key (firms.dedup_distance_m) for a final count of 43. The trim rules: CONFIG (lives in adapter_config, surfaces in the GUI): where we send (channels), how often (cadences/schedules), thresholds (magnitude floors, severity gates, distance radius, cooldown durations, freshness windows), curation data (which sites/states/codes), toggles (enabled, include_in_llm_context, drop_zero_magnitude). CODE (stays in handlers, never reaches the GUI): sentence templates, emoji choices, mapping/translation functions (TomTom icon_map, ITD sub_type_map, Central adapter_map and category_map), rendering logic (anchor priority order, expires-bucket formatting, threshold-state labels), heuristic logic (band_conditions Kp/SFI -> Good/Fair/Poor function). Per-adapter outcome (kept | killed): wfigs 4 | 4 (cooldown_seconds, anchor_max_mi, two re-broadcast toggles) nws 3 | 4 (broadcast_severities, tombstone_msgtypes, warning_suffix_promotes) usgs_quake 6 | 3 (centroid, radius, PAGER list, 3 mag floors) swpc 3 | 7 (three storm-tier floors) usgs_nwis 2 | 4 (parameter_codes, broadcast_on_recede) incident 2 | 0 (freshness_seconds, broadcast_on_update) tomtom_incidents 2 | 1 (drop_zero_magnitude, drop_non_present) state_511_atis 1 | 0 (skipped_states) itd_511 0 | 3 (all sub_type maps/emoji/phrase = CODE) central 1 | 2 (severity_thresholds) dispatcher 4 | 0 (LRU cap, prune params, retention days) band_conditions 3 | 6 (SWPC freshness + HamQSL endpoint config) geocoder 6 | 1 (Photon endpoint + town-OSM curation + cache cap) firms 4 | 1* (confidence_floor, frp_floor, bbox, dedup_distance_m) pipeline 2 | 0 (inhibitor TTL, grouper window) * firms: dedup_lat_lon_decimals is replaced by dedup_distance_m=5 per Matt s call (user-facing unit is meters, not decimal places; the handler will internally translate to quantization step in v0.6-3b). adapter_meta stays at 15 rows -- itd_511 keeps its include_in_llm_context toggle even with zero config keys. Live-DB cleanup: meshai/adapter_config/__init__.py:prune_orphans(conn) DELETEs every adapter_config row whose (adapter, key) is no longer in REGISTRY. Each delete is INFO-logged with the prefix "adapter_config orphan removed:" so docker logs carry the paper trail. Called from init_db() after seed_defaults; idempotent (zero deletes on every subsequent boot). Cache is invalidated when any orphan is removed. adapter_meta is NOT pruned -- meta rows are cheap and useful even for adapters that ended up with zero config keys. Tests (34 cases, replaces v0.6-3a 24-case set): - Registry count is 43; ADAPTER_META is 15 - Seed lands every REGISTRY + ADAPTER_META row; idempotent; never overwrites user edits - prune_orphans removes a synthetic legacy row, logs at INFO with the exact prefix, leaves known keys untouched, leaves adapter_meta untouched, invalidates the accessor cache - Accessor returns correctly-typed values incl new firms.dedup_distance_m - Guard tests: no key in REGISTRY contains "emoji", ends with "_map", or contains "template" / "prefix" (catches CODE leaking back in) Test count: 721 -> 731 (+10 net: +5 prune cases, +1 firms.dedup_distance_m, +3 CODE-guard cases, +1 registry-count assertion). Refs Matt s locked CONFIG-vs-CODE rule.
2026-06-05 18:09:49 +00:00
assert adapter_config.usgs_quake.global_mag_floor == 3.0
feat(v0.6-3a): adapter_config foundation -- migration + defaults registry + typed accessor Closes the foundation slice of audit doc Section A (Rule 17). Lands two SQLite tables, the seed routine that populates them from a Python defaults registry, and a typed accessor that handler code will read in v0.6-3b. No handler changes in this commit -- ZERO behavior risk, every existing test still passes (721 / 69 skipped / 0 failed). v6.sql tables: - adapter_config(adapter, key, value_json, default_json, type, description, updated_at) PRIMARY KEY(adapter, key) -- JSON-encoded values flow through a single column uniformly. CHECK constraint on `type` closes the vocab (int/float/str/ bool/json). - adapter_meta(adapter PK, display_name, include_in_llm_context, description, updated_at) -- per-adapter metadata + the user-scopable LLM-context toggle (Matt refinement #5). meshai/adapter_config/ package: - defaults.py: REGISTRY dict mapping (adapter, key) -> {default, type, description}. Covers audit doc sections A.1-A.12: wfigs, nws, usgs_quake, swpc, usgs_nwis, incident family (tomtom_incidents, state_511_atis, itd_511, shared "incident"), central consumer, dispatcher, band_conditions, geocoder, firms, pipeline (Inhibitor + Grouper). ~85 keys total. ADAPTER_META covers 15 adapters with display_name + include_in_llm_context defaulting to True. Per Matt refinement #3, every default matches the current handler constant EXACTLY -- first deploy behavior is unchanged. - _accessor.py: AdapterConfig class with `adapter_config.<adapter>.<key>` syntax. Read pipeline: in-memory cache hit -> SQL -> registry fallback (with WARNING) -> AttributeError. Process-wide cache; PUT via v0.6-3c REST API calls invalidate_cache() to drop the cache. GIL-atomic dict reads on the fast path (handlers call this hot). - __init__.py: seed_defaults(conn) -- INSERT OR IGNOREs one row per registry entry. Idempotent, never overwrites user edits. Wiring: - meshai/persistence/db.py: SCHEMA_VERSION 5 -> 6, and init_db() now calls seed_defaults() after migrations apply. - meshai/main.py: _init_components() now calls init_db() FIRST (per commit #1 lessons-learned: a startup-time migration is required when handlers will rely on the new schema; lazy-on-first-handler is fine for v4/v5 but not for v6 where handler reads start in v0.6-3b). - tests/conftest.py: autouse fixture now calls init_db() + clears the accessor cache around each test, so every test gets the v6 seed AND a clean cache without per-test boilerplate. Tests (tests/test_adapter_config_foundation.py, 24 cases): - v6 tables exist + schema_meta at 6 + type-vocabulary CHECK enforced - seed populates every REGISTRY + ADAPTER_META row, value_json == default_json on first seed, type matches - seed is idempotent + does not overwrite user edits - accessor returns correctly typed values for int/float/str/bool/ json list/json dict/json None - cache hit: second read does not touch the DB (patched _load_from_db raises, accessor still succeeds) - invalidate_cache forces a re-read; mutated DB value wins - registry fallback path triggers when a row is missing (with WARNING) - unknown key raises AttributeError - setattr blocked (writes go via the REST API in 3c) - every default JSON round-trips cleanly; every type is in vocabulary - ADAPTER_META covers every adapter in REGISTRY Test count: 697 -> 721 (+24 new, 0 regressions). v0.6-3b will wire handlers one at a time (wfigs, nws, quake, swpc, nwis, incident, central, dispatcher, band_conditions, geocoder, firms). Per the audit lock, defaults match exactly so each wiring step is a pure refactor -- bisect-safe. v0.6-3c lands the /api/adapter-config CRUD + the AdapterConfig.tsx dashboard editor + cache invalidation on PUT. Refs audit doc v0.6-phase1-audit.md Section A + finding #4.
2026-06-05 17:06:51 +00:00
def test_accessor_returns_str(fresh_db):
invalidate_cache()
feat(v0.6-3a.1): trim adapter_config registry to CONFIG-only per Matt config-vs-code rule + log-on-delete safety net for orphan cleanup Drops 35 of the v0.6-3a-draft 77 keys + adds 1 net-new key (firms.dedup_distance_m) for a final count of 43. The trim rules: CONFIG (lives in adapter_config, surfaces in the GUI): where we send (channels), how often (cadences/schedules), thresholds (magnitude floors, severity gates, distance radius, cooldown durations, freshness windows), curation data (which sites/states/codes), toggles (enabled, include_in_llm_context, drop_zero_magnitude). CODE (stays in handlers, never reaches the GUI): sentence templates, emoji choices, mapping/translation functions (TomTom icon_map, ITD sub_type_map, Central adapter_map and category_map), rendering logic (anchor priority order, expires-bucket formatting, threshold-state labels), heuristic logic (band_conditions Kp/SFI -> Good/Fair/Poor function). Per-adapter outcome (kept | killed): wfigs 4 | 4 (cooldown_seconds, anchor_max_mi, two re-broadcast toggles) nws 3 | 4 (broadcast_severities, tombstone_msgtypes, warning_suffix_promotes) usgs_quake 6 | 3 (centroid, radius, PAGER list, 3 mag floors) swpc 3 | 7 (three storm-tier floors) usgs_nwis 2 | 4 (parameter_codes, broadcast_on_recede) incident 2 | 0 (freshness_seconds, broadcast_on_update) tomtom_incidents 2 | 1 (drop_zero_magnitude, drop_non_present) state_511_atis 1 | 0 (skipped_states) itd_511 0 | 3 (all sub_type maps/emoji/phrase = CODE) central 1 | 2 (severity_thresholds) dispatcher 4 | 0 (LRU cap, prune params, retention days) band_conditions 3 | 6 (SWPC freshness + HamQSL endpoint config) geocoder 6 | 1 (Photon endpoint + town-OSM curation + cache cap) firms 4 | 1* (confidence_floor, frp_floor, bbox, dedup_distance_m) pipeline 2 | 0 (inhibitor TTL, grouper window) * firms: dedup_lat_lon_decimals is replaced by dedup_distance_m=5 per Matt s call (user-facing unit is meters, not decimal places; the handler will internally translate to quantization step in v0.6-3b). adapter_meta stays at 15 rows -- itd_511 keeps its include_in_llm_context toggle even with zero config keys. Live-DB cleanup: meshai/adapter_config/__init__.py:prune_orphans(conn) DELETEs every adapter_config row whose (adapter, key) is no longer in REGISTRY. Each delete is INFO-logged with the prefix "adapter_config orphan removed:" so docker logs carry the paper trail. Called from init_db() after seed_defaults; idempotent (zero deletes on every subsequent boot). Cache is invalidated when any orphan is removed. adapter_meta is NOT pruned -- meta rows are cheap and useful even for adapters that ended up with zero config keys. Tests (34 cases, replaces v0.6-3a 24-case set): - Registry count is 43; ADAPTER_META is 15 - Seed lands every REGISTRY + ADAPTER_META row; idempotent; never overwrites user edits - prune_orphans removes a synthetic legacy row, logs at INFO with the exact prefix, leaves known keys untouched, leaves adapter_meta untouched, invalidates the accessor cache - Accessor returns correctly-typed values incl new firms.dedup_distance_m - Guard tests: no key in REGISTRY contains "emoji", ends with "_map", or contains "template" / "prefix" (catches CODE leaking back in) Test count: 721 -> 731 (+10 net: +5 prune cases, +1 firms.dedup_distance_m, +3 CODE-guard cases, +1 registry-count assertion). Refs Matt s locked CONFIG-vs-CODE rule.
2026-06-05 18:09:49 +00:00
assert adapter_config.geocoder.photon_url == "http://100.64.0.24:2322"
feat(v0.6-3a): adapter_config foundation -- migration + defaults registry + typed accessor Closes the foundation slice of audit doc Section A (Rule 17). Lands two SQLite tables, the seed routine that populates them from a Python defaults registry, and a typed accessor that handler code will read in v0.6-3b. No handler changes in this commit -- ZERO behavior risk, every existing test still passes (721 / 69 skipped / 0 failed). v6.sql tables: - adapter_config(adapter, key, value_json, default_json, type, description, updated_at) PRIMARY KEY(adapter, key) -- JSON-encoded values flow through a single column uniformly. CHECK constraint on `type` closes the vocab (int/float/str/ bool/json). - adapter_meta(adapter PK, display_name, include_in_llm_context, description, updated_at) -- per-adapter metadata + the user-scopable LLM-context toggle (Matt refinement #5). meshai/adapter_config/ package: - defaults.py: REGISTRY dict mapping (adapter, key) -> {default, type, description}. Covers audit doc sections A.1-A.12: wfigs, nws, usgs_quake, swpc, usgs_nwis, incident family (tomtom_incidents, state_511_atis, itd_511, shared "incident"), central consumer, dispatcher, band_conditions, geocoder, firms, pipeline (Inhibitor + Grouper). ~85 keys total. ADAPTER_META covers 15 adapters with display_name + include_in_llm_context defaulting to True. Per Matt refinement #3, every default matches the current handler constant EXACTLY -- first deploy behavior is unchanged. - _accessor.py: AdapterConfig class with `adapter_config.<adapter>.<key>` syntax. Read pipeline: in-memory cache hit -> SQL -> registry fallback (with WARNING) -> AttributeError. Process-wide cache; PUT via v0.6-3c REST API calls invalidate_cache() to drop the cache. GIL-atomic dict reads on the fast path (handlers call this hot). - __init__.py: seed_defaults(conn) -- INSERT OR IGNOREs one row per registry entry. Idempotent, never overwrites user edits. Wiring: - meshai/persistence/db.py: SCHEMA_VERSION 5 -> 6, and init_db() now calls seed_defaults() after migrations apply. - meshai/main.py: _init_components() now calls init_db() FIRST (per commit #1 lessons-learned: a startup-time migration is required when handlers will rely on the new schema; lazy-on-first-handler is fine for v4/v5 but not for v6 where handler reads start in v0.6-3b). - tests/conftest.py: autouse fixture now calls init_db() + clears the accessor cache around each test, so every test gets the v6 seed AND a clean cache without per-test boilerplate. Tests (tests/test_adapter_config_foundation.py, 24 cases): - v6 tables exist + schema_meta at 6 + type-vocabulary CHECK enforced - seed populates every REGISTRY + ADAPTER_META row, value_json == default_json on first seed, type matches - seed is idempotent + does not overwrite user edits - accessor returns correctly typed values for int/float/str/bool/ json list/json dict/json None - cache hit: second read does not touch the DB (patched _load_from_db raises, accessor still succeeds) - invalidate_cache forces a re-read; mutated DB value wins - registry fallback path triggers when a row is missing (with WARNING) - unknown key raises AttributeError - setattr blocked (writes go via the REST API in 3c) - every default JSON round-trips cleanly; every type is in vocabulary - ADAPTER_META covers every adapter in REGISTRY Test count: 697 -> 721 (+24 new, 0 regressions). v0.6-3b will wire handlers one at a time (wfigs, nws, quake, swpc, nwis, incident, central, dispatcher, band_conditions, geocoder, firms). Per the audit lock, defaults match exactly so each wiring step is a pure refactor -- bisect-safe. v0.6-3c lands the /api/adapter-config CRUD + the AdapterConfig.tsx dashboard editor + cache invalidation on PUT. Refs audit doc v0.6-phase1-audit.md Section A + finding #4.
2026-06-05 17:06:51 +00:00
def test_accessor_returns_bool(fresh_db):
invalidate_cache()
feat(v0.6-3a.1): trim adapter_config registry to CONFIG-only per Matt config-vs-code rule + log-on-delete safety net for orphan cleanup Drops 35 of the v0.6-3a-draft 77 keys + adds 1 net-new key (firms.dedup_distance_m) for a final count of 43. The trim rules: CONFIG (lives in adapter_config, surfaces in the GUI): where we send (channels), how often (cadences/schedules), thresholds (magnitude floors, severity gates, distance radius, cooldown durations, freshness windows), curation data (which sites/states/codes), toggles (enabled, include_in_llm_context, drop_zero_magnitude). CODE (stays in handlers, never reaches the GUI): sentence templates, emoji choices, mapping/translation functions (TomTom icon_map, ITD sub_type_map, Central adapter_map and category_map), rendering logic (anchor priority order, expires-bucket formatting, threshold-state labels), heuristic logic (band_conditions Kp/SFI -> Good/Fair/Poor function). Per-adapter outcome (kept | killed): wfigs 4 | 4 (cooldown_seconds, anchor_max_mi, two re-broadcast toggles) nws 3 | 4 (broadcast_severities, tombstone_msgtypes, warning_suffix_promotes) usgs_quake 6 | 3 (centroid, radius, PAGER list, 3 mag floors) swpc 3 | 7 (three storm-tier floors) usgs_nwis 2 | 4 (parameter_codes, broadcast_on_recede) incident 2 | 0 (freshness_seconds, broadcast_on_update) tomtom_incidents 2 | 1 (drop_zero_magnitude, drop_non_present) state_511_atis 1 | 0 (skipped_states) itd_511 0 | 3 (all sub_type maps/emoji/phrase = CODE) central 1 | 2 (severity_thresholds) dispatcher 4 | 0 (LRU cap, prune params, retention days) band_conditions 3 | 6 (SWPC freshness + HamQSL endpoint config) geocoder 6 | 1 (Photon endpoint + town-OSM curation + cache cap) firms 4 | 1* (confidence_floor, frp_floor, bbox, dedup_distance_m) pipeline 2 | 0 (inhibitor TTL, grouper window) * firms: dedup_lat_lon_decimals is replaced by dedup_distance_m=5 per Matt s call (user-facing unit is meters, not decimal places; the handler will internally translate to quantization step in v0.6-3b). adapter_meta stays at 15 rows -- itd_511 keeps its include_in_llm_context toggle even with zero config keys. Live-DB cleanup: meshai/adapter_config/__init__.py:prune_orphans(conn) DELETEs every adapter_config row whose (adapter, key) is no longer in REGISTRY. Each delete is INFO-logged with the prefix "adapter_config orphan removed:" so docker logs carry the paper trail. Called from init_db() after seed_defaults; idempotent (zero deletes on every subsequent boot). Cache is invalidated when any orphan is removed. adapter_meta is NOT pruned -- meta rows are cheap and useful even for adapters that ended up with zero config keys. Tests (34 cases, replaces v0.6-3a 24-case set): - Registry count is 43; ADAPTER_META is 15 - Seed lands every REGISTRY + ADAPTER_META row; idempotent; never overwrites user edits - prune_orphans removes a synthetic legacy row, logs at INFO with the exact prefix, leaves known keys untouched, leaves adapter_meta untouched, invalidates the accessor cache - Accessor returns correctly-typed values incl new firms.dedup_distance_m - Guard tests: no key in REGISTRY contains "emoji", ends with "_map", or contains "template" / "prefix" (catches CODE leaking back in) Test count: 721 -> 731 (+10 net: +5 prune cases, +1 firms.dedup_distance_m, +3 CODE-guard cases, +1 registry-count assertion). Refs Matt s locked CONFIG-vs-CODE rule.
2026-06-05 18:09:49 +00:00
assert adapter_config.tomtom_incidents.drop_zero_magnitude is True
feat(v0.6-3a): adapter_config foundation -- migration + defaults registry + typed accessor Closes the foundation slice of audit doc Section A (Rule 17). Lands two SQLite tables, the seed routine that populates them from a Python defaults registry, and a typed accessor that handler code will read in v0.6-3b. No handler changes in this commit -- ZERO behavior risk, every existing test still passes (721 / 69 skipped / 0 failed). v6.sql tables: - adapter_config(adapter, key, value_json, default_json, type, description, updated_at) PRIMARY KEY(adapter, key) -- JSON-encoded values flow through a single column uniformly. CHECK constraint on `type` closes the vocab (int/float/str/ bool/json). - adapter_meta(adapter PK, display_name, include_in_llm_context, description, updated_at) -- per-adapter metadata + the user-scopable LLM-context toggle (Matt refinement #5). meshai/adapter_config/ package: - defaults.py: REGISTRY dict mapping (adapter, key) -> {default, type, description}. Covers audit doc sections A.1-A.12: wfigs, nws, usgs_quake, swpc, usgs_nwis, incident family (tomtom_incidents, state_511_atis, itd_511, shared "incident"), central consumer, dispatcher, band_conditions, geocoder, firms, pipeline (Inhibitor + Grouper). ~85 keys total. ADAPTER_META covers 15 adapters with display_name + include_in_llm_context defaulting to True. Per Matt refinement #3, every default matches the current handler constant EXACTLY -- first deploy behavior is unchanged. - _accessor.py: AdapterConfig class with `adapter_config.<adapter>.<key>` syntax. Read pipeline: in-memory cache hit -> SQL -> registry fallback (with WARNING) -> AttributeError. Process-wide cache; PUT via v0.6-3c REST API calls invalidate_cache() to drop the cache. GIL-atomic dict reads on the fast path (handlers call this hot). - __init__.py: seed_defaults(conn) -- INSERT OR IGNOREs one row per registry entry. Idempotent, never overwrites user edits. Wiring: - meshai/persistence/db.py: SCHEMA_VERSION 5 -> 6, and init_db() now calls seed_defaults() after migrations apply. - meshai/main.py: _init_components() now calls init_db() FIRST (per commit #1 lessons-learned: a startup-time migration is required when handlers will rely on the new schema; lazy-on-first-handler is fine for v4/v5 but not for v6 where handler reads start in v0.6-3b). - tests/conftest.py: autouse fixture now calls init_db() + clears the accessor cache around each test, so every test gets the v6 seed AND a clean cache without per-test boilerplate. Tests (tests/test_adapter_config_foundation.py, 24 cases): - v6 tables exist + schema_meta at 6 + type-vocabulary CHECK enforced - seed populates every REGISTRY + ADAPTER_META row, value_json == default_json on first seed, type matches - seed is idempotent + does not overwrite user edits - accessor returns correctly typed values for int/float/str/bool/ json list/json dict/json None - cache hit: second read does not touch the DB (patched _load_from_db raises, accessor still succeeds) - invalidate_cache forces a re-read; mutated DB value wins - registry fallback path triggers when a row is missing (with WARNING) - unknown key raises AttributeError - setattr blocked (writes go via the REST API in 3c) - every default JSON round-trips cleanly; every type is in vocabulary - ADAPTER_META covers every adapter in REGISTRY Test count: 697 -> 721 (+24 new, 0 regressions). v0.6-3b will wire handlers one at a time (wfigs, nws, quake, swpc, nwis, incident, central, dispatcher, band_conditions, geocoder, firms). Per the audit lock, defaults match exactly so each wiring step is a pure refactor -- bisect-safe. v0.6-3c lands the /api/adapter-config CRUD + the AdapterConfig.tsx dashboard editor + cache invalidation on PUT. Refs audit doc v0.6-phase1-audit.md Section A + finding #4.
2026-06-05 17:06:51 +00:00
def test_accessor_returns_json_list(fresh_db):
invalidate_cache()
feat(v0.6-3a.1): trim adapter_config registry to CONFIG-only per Matt config-vs-code rule + log-on-delete safety net for orphan cleanup Drops 35 of the v0.6-3a-draft 77 keys + adds 1 net-new key (firms.dedup_distance_m) for a final count of 43. The trim rules: CONFIG (lives in adapter_config, surfaces in the GUI): where we send (channels), how often (cadences/schedules), thresholds (magnitude floors, severity gates, distance radius, cooldown durations, freshness windows), curation data (which sites/states/codes), toggles (enabled, include_in_llm_context, drop_zero_magnitude). CODE (stays in handlers, never reaches the GUI): sentence templates, emoji choices, mapping/translation functions (TomTom icon_map, ITD sub_type_map, Central adapter_map and category_map), rendering logic (anchor priority order, expires-bucket formatting, threshold-state labels), heuristic logic (band_conditions Kp/SFI -> Good/Fair/Poor function). Per-adapter outcome (kept | killed): wfigs 4 | 4 (cooldown_seconds, anchor_max_mi, two re-broadcast toggles) nws 3 | 4 (broadcast_severities, tombstone_msgtypes, warning_suffix_promotes) usgs_quake 6 | 3 (centroid, radius, PAGER list, 3 mag floors) swpc 3 | 7 (three storm-tier floors) usgs_nwis 2 | 4 (parameter_codes, broadcast_on_recede) incident 2 | 0 (freshness_seconds, broadcast_on_update) tomtom_incidents 2 | 1 (drop_zero_magnitude, drop_non_present) state_511_atis 1 | 0 (skipped_states) itd_511 0 | 3 (all sub_type maps/emoji/phrase = CODE) central 1 | 2 (severity_thresholds) dispatcher 4 | 0 (LRU cap, prune params, retention days) band_conditions 3 | 6 (SWPC freshness + HamQSL endpoint config) geocoder 6 | 1 (Photon endpoint + town-OSM curation + cache cap) firms 4 | 1* (confidence_floor, frp_floor, bbox, dedup_distance_m) pipeline 2 | 0 (inhibitor TTL, grouper window) * firms: dedup_lat_lon_decimals is replaced by dedup_distance_m=5 per Matt s call (user-facing unit is meters, not decimal places; the handler will internally translate to quantization step in v0.6-3b). adapter_meta stays at 15 rows -- itd_511 keeps its include_in_llm_context toggle even with zero config keys. Live-DB cleanup: meshai/adapter_config/__init__.py:prune_orphans(conn) DELETEs every adapter_config row whose (adapter, key) is no longer in REGISTRY. Each delete is INFO-logged with the prefix "adapter_config orphan removed:" so docker logs carry the paper trail. Called from init_db() after seed_defaults; idempotent (zero deletes on every subsequent boot). Cache is invalidated when any orphan is removed. adapter_meta is NOT pruned -- meta rows are cheap and useful even for adapters that ended up with zero config keys. Tests (34 cases, replaces v0.6-3a 24-case set): - Registry count is 43; ADAPTER_META is 15 - Seed lands every REGISTRY + ADAPTER_META row; idempotent; never overwrites user edits - prune_orphans removes a synthetic legacy row, logs at INFO with the exact prefix, leaves known keys untouched, leaves adapter_meta untouched, invalidates the accessor cache - Accessor returns correctly-typed values incl new firms.dedup_distance_m - Guard tests: no key in REGISTRY contains "emoji", ends with "_map", or contains "template" / "prefix" (catches CODE leaking back in) Test count: 721 -> 731 (+10 net: +5 prune cases, +1 firms.dedup_distance_m, +3 CODE-guard cases, +1 registry-count assertion). Refs Matt s locked CONFIG-vs-CODE rule.
2026-06-05 18:09:49 +00:00
assert adapter_config.nws.broadcast_severities == ["Extreme", "Severe"]
feat(v0.6-3a): adapter_config foundation -- migration + defaults registry + typed accessor Closes the foundation slice of audit doc Section A (Rule 17). Lands two SQLite tables, the seed routine that populates them from a Python defaults registry, and a typed accessor that handler code will read in v0.6-3b. No handler changes in this commit -- ZERO behavior risk, every existing test still passes (721 / 69 skipped / 0 failed). v6.sql tables: - adapter_config(adapter, key, value_json, default_json, type, description, updated_at) PRIMARY KEY(adapter, key) -- JSON-encoded values flow through a single column uniformly. CHECK constraint on `type` closes the vocab (int/float/str/ bool/json). - adapter_meta(adapter PK, display_name, include_in_llm_context, description, updated_at) -- per-adapter metadata + the user-scopable LLM-context toggle (Matt refinement #5). meshai/adapter_config/ package: - defaults.py: REGISTRY dict mapping (adapter, key) -> {default, type, description}. Covers audit doc sections A.1-A.12: wfigs, nws, usgs_quake, swpc, usgs_nwis, incident family (tomtom_incidents, state_511_atis, itd_511, shared "incident"), central consumer, dispatcher, band_conditions, geocoder, firms, pipeline (Inhibitor + Grouper). ~85 keys total. ADAPTER_META covers 15 adapters with display_name + include_in_llm_context defaulting to True. Per Matt refinement #3, every default matches the current handler constant EXACTLY -- first deploy behavior is unchanged. - _accessor.py: AdapterConfig class with `adapter_config.<adapter>.<key>` syntax. Read pipeline: in-memory cache hit -> SQL -> registry fallback (with WARNING) -> AttributeError. Process-wide cache; PUT via v0.6-3c REST API calls invalidate_cache() to drop the cache. GIL-atomic dict reads on the fast path (handlers call this hot). - __init__.py: seed_defaults(conn) -- INSERT OR IGNOREs one row per registry entry. Idempotent, never overwrites user edits. Wiring: - meshai/persistence/db.py: SCHEMA_VERSION 5 -> 6, and init_db() now calls seed_defaults() after migrations apply. - meshai/main.py: _init_components() now calls init_db() FIRST (per commit #1 lessons-learned: a startup-time migration is required when handlers will rely on the new schema; lazy-on-first-handler is fine for v4/v5 but not for v6 where handler reads start in v0.6-3b). - tests/conftest.py: autouse fixture now calls init_db() + clears the accessor cache around each test, so every test gets the v6 seed AND a clean cache without per-test boilerplate. Tests (tests/test_adapter_config_foundation.py, 24 cases): - v6 tables exist + schema_meta at 6 + type-vocabulary CHECK enforced - seed populates every REGISTRY + ADAPTER_META row, value_json == default_json on first seed, type matches - seed is idempotent + does not overwrite user edits - accessor returns correctly typed values for int/float/str/bool/ json list/json dict/json None - cache hit: second read does not touch the DB (patched _load_from_db raises, accessor still succeeds) - invalidate_cache forces a re-read; mutated DB value wins - registry fallback path triggers when a row is missing (with WARNING) - unknown key raises AttributeError - setattr blocked (writes go via the REST API in 3c) - every default JSON round-trips cleanly; every type is in vocabulary - ADAPTER_META covers every adapter in REGISTRY Test count: 697 -> 721 (+24 new, 0 regressions). v0.6-3b will wire handlers one at a time (wfigs, nws, quake, swpc, nwis, incident, central, dispatcher, band_conditions, geocoder, firms). Per the audit lock, defaults match exactly so each wiring step is a pure refactor -- bisect-safe. v0.6-3c lands the /api/adapter-config CRUD + the AdapterConfig.tsx dashboard editor + cache invalidation on PUT. Refs audit doc v0.6-phase1-audit.md Section A + finding #4.
2026-06-05 17:06:51 +00:00
def test_accessor_returns_json_dict(fresh_db):
invalidate_cache()
feat(v0.6-3a.1): trim adapter_config registry to CONFIG-only per Matt config-vs-code rule + log-on-delete safety net for orphan cleanup Drops 35 of the v0.6-3a-draft 77 keys + adds 1 net-new key (firms.dedup_distance_m) for a final count of 43. The trim rules: CONFIG (lives in adapter_config, surfaces in the GUI): where we send (channels), how often (cadences/schedules), thresholds (magnitude floors, severity gates, distance radius, cooldown durations, freshness windows), curation data (which sites/states/codes), toggles (enabled, include_in_llm_context, drop_zero_magnitude). CODE (stays in handlers, never reaches the GUI): sentence templates, emoji choices, mapping/translation functions (TomTom icon_map, ITD sub_type_map, Central adapter_map and category_map), rendering logic (anchor priority order, expires-bucket formatting, threshold-state labels), heuristic logic (band_conditions Kp/SFI -> Good/Fair/Poor function). Per-adapter outcome (kept | killed): wfigs 4 | 4 (cooldown_seconds, anchor_max_mi, two re-broadcast toggles) nws 3 | 4 (broadcast_severities, tombstone_msgtypes, warning_suffix_promotes) usgs_quake 6 | 3 (centroid, radius, PAGER list, 3 mag floors) swpc 3 | 7 (three storm-tier floors) usgs_nwis 2 | 4 (parameter_codes, broadcast_on_recede) incident 2 | 0 (freshness_seconds, broadcast_on_update) tomtom_incidents 2 | 1 (drop_zero_magnitude, drop_non_present) state_511_atis 1 | 0 (skipped_states) itd_511 0 | 3 (all sub_type maps/emoji/phrase = CODE) central 1 | 2 (severity_thresholds) dispatcher 4 | 0 (LRU cap, prune params, retention days) band_conditions 3 | 6 (SWPC freshness + HamQSL endpoint config) geocoder 6 | 1 (Photon endpoint + town-OSM curation + cache cap) firms 4 | 1* (confidence_floor, frp_floor, bbox, dedup_distance_m) pipeline 2 | 0 (inhibitor TTL, grouper window) * firms: dedup_lat_lon_decimals is replaced by dedup_distance_m=5 per Matt s call (user-facing unit is meters, not decimal places; the handler will internally translate to quantization step in v0.6-3b). adapter_meta stays at 15 rows -- itd_511 keeps its include_in_llm_context toggle even with zero config keys. Live-DB cleanup: meshai/adapter_config/__init__.py:prune_orphans(conn) DELETEs every adapter_config row whose (adapter, key) is no longer in REGISTRY. Each delete is INFO-logged with the prefix "adapter_config orphan removed:" so docker logs carry the paper trail. Called from init_db() after seed_defaults; idempotent (zero deletes on every subsequent boot). Cache is invalidated when any orphan is removed. adapter_meta is NOT pruned -- meta rows are cheap and useful even for adapters that ended up with zero config keys. Tests (34 cases, replaces v0.6-3a 24-case set): - Registry count is 43; ADAPTER_META is 15 - Seed lands every REGISTRY + ADAPTER_META row; idempotent; never overwrites user edits - prune_orphans removes a synthetic legacy row, logs at INFO with the exact prefix, leaves known keys untouched, leaves adapter_meta untouched, invalidates the accessor cache - Accessor returns correctly-typed values incl new firms.dedup_distance_m - Guard tests: no key in REGISTRY contains "emoji", ends with "_map", or contains "template" / "prefix" (catches CODE leaking back in) Test count: 721 -> 731 (+10 net: +5 prune cases, +1 firms.dedup_distance_m, +3 CODE-guard cases, +1 registry-count assertion). Refs Matt s locked CONFIG-vs-CODE rule.
2026-06-05 18:09:49 +00:00
v = adapter_config.central.severity_thresholds
assert v == {"routine_max": 1, "priority_max": 2, "immediate_min": 3}
feat(v0.6-3a): adapter_config foundation -- migration + defaults registry + typed accessor Closes the foundation slice of audit doc Section A (Rule 17). Lands two SQLite tables, the seed routine that populates them from a Python defaults registry, and a typed accessor that handler code will read in v0.6-3b. No handler changes in this commit -- ZERO behavior risk, every existing test still passes (721 / 69 skipped / 0 failed). v6.sql tables: - adapter_config(adapter, key, value_json, default_json, type, description, updated_at) PRIMARY KEY(adapter, key) -- JSON-encoded values flow through a single column uniformly. CHECK constraint on `type` closes the vocab (int/float/str/ bool/json). - adapter_meta(adapter PK, display_name, include_in_llm_context, description, updated_at) -- per-adapter metadata + the user-scopable LLM-context toggle (Matt refinement #5). meshai/adapter_config/ package: - defaults.py: REGISTRY dict mapping (adapter, key) -> {default, type, description}. Covers audit doc sections A.1-A.12: wfigs, nws, usgs_quake, swpc, usgs_nwis, incident family (tomtom_incidents, state_511_atis, itd_511, shared "incident"), central consumer, dispatcher, band_conditions, geocoder, firms, pipeline (Inhibitor + Grouper). ~85 keys total. ADAPTER_META covers 15 adapters with display_name + include_in_llm_context defaulting to True. Per Matt refinement #3, every default matches the current handler constant EXACTLY -- first deploy behavior is unchanged. - _accessor.py: AdapterConfig class with `adapter_config.<adapter>.<key>` syntax. Read pipeline: in-memory cache hit -> SQL -> registry fallback (with WARNING) -> AttributeError. Process-wide cache; PUT via v0.6-3c REST API calls invalidate_cache() to drop the cache. GIL-atomic dict reads on the fast path (handlers call this hot). - __init__.py: seed_defaults(conn) -- INSERT OR IGNOREs one row per registry entry. Idempotent, never overwrites user edits. Wiring: - meshai/persistence/db.py: SCHEMA_VERSION 5 -> 6, and init_db() now calls seed_defaults() after migrations apply. - meshai/main.py: _init_components() now calls init_db() FIRST (per commit #1 lessons-learned: a startup-time migration is required when handlers will rely on the new schema; lazy-on-first-handler is fine for v4/v5 but not for v6 where handler reads start in v0.6-3b). - tests/conftest.py: autouse fixture now calls init_db() + clears the accessor cache around each test, so every test gets the v6 seed AND a clean cache without per-test boilerplate. Tests (tests/test_adapter_config_foundation.py, 24 cases): - v6 tables exist + schema_meta at 6 + type-vocabulary CHECK enforced - seed populates every REGISTRY + ADAPTER_META row, value_json == default_json on first seed, type matches - seed is idempotent + does not overwrite user edits - accessor returns correctly typed values for int/float/str/bool/ json list/json dict/json None - cache hit: second read does not touch the DB (patched _load_from_db raises, accessor still succeeds) - invalidate_cache forces a re-read; mutated DB value wins - registry fallback path triggers when a row is missing (with WARNING) - unknown key raises AttributeError - setattr blocked (writes go via the REST API in 3c) - every default JSON round-trips cleanly; every type is in vocabulary - ADAPTER_META covers every adapter in REGISTRY Test count: 697 -> 721 (+24 new, 0 regressions). v0.6-3b will wire handlers one at a time (wfigs, nws, quake, swpc, nwis, incident, central, dispatcher, band_conditions, geocoder, firms). Per the audit lock, defaults match exactly so each wiring step is a pure refactor -- bisect-safe. v0.6-3c lands the /api/adapter-config CRUD + the AdapterConfig.tsx dashboard editor + cache invalidation on PUT. Refs audit doc v0.6-phase1-audit.md Section A + finding #4.
2026-06-05 17:06:51 +00:00
def test_accessor_returns_json_none(fresh_db):
invalidate_cache()
feat(v0.6-3a.1): trim adapter_config registry to CONFIG-only per Matt config-vs-code rule + log-on-delete safety net for orphan cleanup Drops 35 of the v0.6-3a-draft 77 keys + adds 1 net-new key (firms.dedup_distance_m) for a final count of 43. The trim rules: CONFIG (lives in adapter_config, surfaces in the GUI): where we send (channels), how often (cadences/schedules), thresholds (magnitude floors, severity gates, distance radius, cooldown durations, freshness windows), curation data (which sites/states/codes), toggles (enabled, include_in_llm_context, drop_zero_magnitude). CODE (stays in handlers, never reaches the GUI): sentence templates, emoji choices, mapping/translation functions (TomTom icon_map, ITD sub_type_map, Central adapter_map and category_map), rendering logic (anchor priority order, expires-bucket formatting, threshold-state labels), heuristic logic (band_conditions Kp/SFI -> Good/Fair/Poor function). Per-adapter outcome (kept | killed): wfigs 4 | 4 (cooldown_seconds, anchor_max_mi, two re-broadcast toggles) nws 3 | 4 (broadcast_severities, tombstone_msgtypes, warning_suffix_promotes) usgs_quake 6 | 3 (centroid, radius, PAGER list, 3 mag floors) swpc 3 | 7 (three storm-tier floors) usgs_nwis 2 | 4 (parameter_codes, broadcast_on_recede) incident 2 | 0 (freshness_seconds, broadcast_on_update) tomtom_incidents 2 | 1 (drop_zero_magnitude, drop_non_present) state_511_atis 1 | 0 (skipped_states) itd_511 0 | 3 (all sub_type maps/emoji/phrase = CODE) central 1 | 2 (severity_thresholds) dispatcher 4 | 0 (LRU cap, prune params, retention days) band_conditions 3 | 6 (SWPC freshness + HamQSL endpoint config) geocoder 6 | 1 (Photon endpoint + town-OSM curation + cache cap) firms 4 | 1* (confidence_floor, frp_floor, bbox, dedup_distance_m) pipeline 2 | 0 (inhibitor TTL, grouper window) * firms: dedup_lat_lon_decimals is replaced by dedup_distance_m=5 per Matt s call (user-facing unit is meters, not decimal places; the handler will internally translate to quantization step in v0.6-3b). adapter_meta stays at 15 rows -- itd_511 keeps its include_in_llm_context toggle even with zero config keys. Live-DB cleanup: meshai/adapter_config/__init__.py:prune_orphans(conn) DELETEs every adapter_config row whose (adapter, key) is no longer in REGISTRY. Each delete is INFO-logged with the prefix "adapter_config orphan removed:" so docker logs carry the paper trail. Called from init_db() after seed_defaults; idempotent (zero deletes on every subsequent boot). Cache is invalidated when any orphan is removed. adapter_meta is NOT pruned -- meta rows are cheap and useful even for adapters that ended up with zero config keys. Tests (34 cases, replaces v0.6-3a 24-case set): - Registry count is 43; ADAPTER_META is 15 - Seed lands every REGISTRY + ADAPTER_META row; idempotent; never overwrites user edits - prune_orphans removes a synthetic legacy row, logs at INFO with the exact prefix, leaves known keys untouched, leaves adapter_meta untouched, invalidates the accessor cache - Accessor returns correctly-typed values incl new firms.dedup_distance_m - Guard tests: no key in REGISTRY contains "emoji", ends with "_map", or contains "template" / "prefix" (catches CODE leaking back in) Test count: 721 -> 731 (+10 net: +5 prune cases, +1 firms.dedup_distance_m, +3 CODE-guard cases, +1 registry-count assertion). Refs Matt s locked CONFIG-vs-CODE rule.
2026-06-05 18:09:49 +00:00
assert adapter_config.firms.bbox is None
def test_firms_dedup_distance_m_default(fresh_db):
"""v0.6-3a.1 Matt's call: user-facing unit is meters, default 5."""
invalidate_cache()
v = adapter_config.firms.dedup_distance_m
assert isinstance(v, int)
assert v == 5
feat(v0.6-3a): adapter_config foundation -- migration + defaults registry + typed accessor Closes the foundation slice of audit doc Section A (Rule 17). Lands two SQLite tables, the seed routine that populates them from a Python defaults registry, and a typed accessor that handler code will read in v0.6-3b. No handler changes in this commit -- ZERO behavior risk, every existing test still passes (721 / 69 skipped / 0 failed). v6.sql tables: - adapter_config(adapter, key, value_json, default_json, type, description, updated_at) PRIMARY KEY(adapter, key) -- JSON-encoded values flow through a single column uniformly. CHECK constraint on `type` closes the vocab (int/float/str/ bool/json). - adapter_meta(adapter PK, display_name, include_in_llm_context, description, updated_at) -- per-adapter metadata + the user-scopable LLM-context toggle (Matt refinement #5). meshai/adapter_config/ package: - defaults.py: REGISTRY dict mapping (adapter, key) -> {default, type, description}. Covers audit doc sections A.1-A.12: wfigs, nws, usgs_quake, swpc, usgs_nwis, incident family (tomtom_incidents, state_511_atis, itd_511, shared "incident"), central consumer, dispatcher, band_conditions, geocoder, firms, pipeline (Inhibitor + Grouper). ~85 keys total. ADAPTER_META covers 15 adapters with display_name + include_in_llm_context defaulting to True. Per Matt refinement #3, every default matches the current handler constant EXACTLY -- first deploy behavior is unchanged. - _accessor.py: AdapterConfig class with `adapter_config.<adapter>.<key>` syntax. Read pipeline: in-memory cache hit -> SQL -> registry fallback (with WARNING) -> AttributeError. Process-wide cache; PUT via v0.6-3c REST API calls invalidate_cache() to drop the cache. GIL-atomic dict reads on the fast path (handlers call this hot). - __init__.py: seed_defaults(conn) -- INSERT OR IGNOREs one row per registry entry. Idempotent, never overwrites user edits. Wiring: - meshai/persistence/db.py: SCHEMA_VERSION 5 -> 6, and init_db() now calls seed_defaults() after migrations apply. - meshai/main.py: _init_components() now calls init_db() FIRST (per commit #1 lessons-learned: a startup-time migration is required when handlers will rely on the new schema; lazy-on-first-handler is fine for v4/v5 but not for v6 where handler reads start in v0.6-3b). - tests/conftest.py: autouse fixture now calls init_db() + clears the accessor cache around each test, so every test gets the v6 seed AND a clean cache without per-test boilerplate. Tests (tests/test_adapter_config_foundation.py, 24 cases): - v6 tables exist + schema_meta at 6 + type-vocabulary CHECK enforced - seed populates every REGISTRY + ADAPTER_META row, value_json == default_json on first seed, type matches - seed is idempotent + does not overwrite user edits - accessor returns correctly typed values for int/float/str/bool/ json list/json dict/json None - cache hit: second read does not touch the DB (patched _load_from_db raises, accessor still succeeds) - invalidate_cache forces a re-read; mutated DB value wins - registry fallback path triggers when a row is missing (with WARNING) - unknown key raises AttributeError - setattr blocked (writes go via the REST API in 3c) - every default JSON round-trips cleanly; every type is in vocabulary - ADAPTER_META covers every adapter in REGISTRY Test count: 697 -> 721 (+24 new, 0 regressions). v0.6-3b will wire handlers one at a time (wfigs, nws, quake, swpc, nwis, incident, central, dispatcher, band_conditions, geocoder, firms). Per the audit lock, defaults match exactly so each wiring step is a pure refactor -- bisect-safe. v0.6-3c lands the /api/adapter-config CRUD + the AdapterConfig.tsx dashboard editor + cache invalidation on PUT. Refs audit doc v0.6-phase1-audit.md Section A + finding #4.
2026-06-05 17:06:51 +00:00
# ---------- cache --------------------------------------------------------
def test_cache_hits_second_read(fresh_db):
invalidate_cache()
_ = adapter_config.wfigs.cooldown_seconds
with patch.object(accessor_mod, "_load_from_db",
feat(v0.6-3a.1): trim adapter_config registry to CONFIG-only per Matt config-vs-code rule + log-on-delete safety net for orphan cleanup Drops 35 of the v0.6-3a-draft 77 keys + adds 1 net-new key (firms.dedup_distance_m) for a final count of 43. The trim rules: CONFIG (lives in adapter_config, surfaces in the GUI): where we send (channels), how often (cadences/schedules), thresholds (magnitude floors, severity gates, distance radius, cooldown durations, freshness windows), curation data (which sites/states/codes), toggles (enabled, include_in_llm_context, drop_zero_magnitude). CODE (stays in handlers, never reaches the GUI): sentence templates, emoji choices, mapping/translation functions (TomTom icon_map, ITD sub_type_map, Central adapter_map and category_map), rendering logic (anchor priority order, expires-bucket formatting, threshold-state labels), heuristic logic (band_conditions Kp/SFI -> Good/Fair/Poor function). Per-adapter outcome (kept | killed): wfigs 4 | 4 (cooldown_seconds, anchor_max_mi, two re-broadcast toggles) nws 3 | 4 (broadcast_severities, tombstone_msgtypes, warning_suffix_promotes) usgs_quake 6 | 3 (centroid, radius, PAGER list, 3 mag floors) swpc 3 | 7 (three storm-tier floors) usgs_nwis 2 | 4 (parameter_codes, broadcast_on_recede) incident 2 | 0 (freshness_seconds, broadcast_on_update) tomtom_incidents 2 | 1 (drop_zero_magnitude, drop_non_present) state_511_atis 1 | 0 (skipped_states) itd_511 0 | 3 (all sub_type maps/emoji/phrase = CODE) central 1 | 2 (severity_thresholds) dispatcher 4 | 0 (LRU cap, prune params, retention days) band_conditions 3 | 6 (SWPC freshness + HamQSL endpoint config) geocoder 6 | 1 (Photon endpoint + town-OSM curation + cache cap) firms 4 | 1* (confidence_floor, frp_floor, bbox, dedup_distance_m) pipeline 2 | 0 (inhibitor TTL, grouper window) * firms: dedup_lat_lon_decimals is replaced by dedup_distance_m=5 per Matt s call (user-facing unit is meters, not decimal places; the handler will internally translate to quantization step in v0.6-3b). adapter_meta stays at 15 rows -- itd_511 keeps its include_in_llm_context toggle even with zero config keys. Live-DB cleanup: meshai/adapter_config/__init__.py:prune_orphans(conn) DELETEs every adapter_config row whose (adapter, key) is no longer in REGISTRY. Each delete is INFO-logged with the prefix "adapter_config orphan removed:" so docker logs carry the paper trail. Called from init_db() after seed_defaults; idempotent (zero deletes on every subsequent boot). Cache is invalidated when any orphan is removed. adapter_meta is NOT pruned -- meta rows are cheap and useful even for adapters that ended up with zero config keys. Tests (34 cases, replaces v0.6-3a 24-case set): - Registry count is 43; ADAPTER_META is 15 - Seed lands every REGISTRY + ADAPTER_META row; idempotent; never overwrites user edits - prune_orphans removes a synthetic legacy row, logs at INFO with the exact prefix, leaves known keys untouched, leaves adapter_meta untouched, invalidates the accessor cache - Accessor returns correctly-typed values incl new firms.dedup_distance_m - Guard tests: no key in REGISTRY contains "emoji", ends with "_map", or contains "template" / "prefix" (catches CODE leaking back in) Test count: 721 -> 731 (+10 net: +5 prune cases, +1 firms.dedup_distance_m, +3 CODE-guard cases, +1 registry-count assertion). Refs Matt s locked CONFIG-vs-CODE rule.
2026-06-05 18:09:49 +00:00
side_effect=AssertionError("cache miss")):
feat(v0.6-3a): adapter_config foundation -- migration + defaults registry + typed accessor Closes the foundation slice of audit doc Section A (Rule 17). Lands two SQLite tables, the seed routine that populates them from a Python defaults registry, and a typed accessor that handler code will read in v0.6-3b. No handler changes in this commit -- ZERO behavior risk, every existing test still passes (721 / 69 skipped / 0 failed). v6.sql tables: - adapter_config(adapter, key, value_json, default_json, type, description, updated_at) PRIMARY KEY(adapter, key) -- JSON-encoded values flow through a single column uniformly. CHECK constraint on `type` closes the vocab (int/float/str/ bool/json). - adapter_meta(adapter PK, display_name, include_in_llm_context, description, updated_at) -- per-adapter metadata + the user-scopable LLM-context toggle (Matt refinement #5). meshai/adapter_config/ package: - defaults.py: REGISTRY dict mapping (adapter, key) -> {default, type, description}. Covers audit doc sections A.1-A.12: wfigs, nws, usgs_quake, swpc, usgs_nwis, incident family (tomtom_incidents, state_511_atis, itd_511, shared "incident"), central consumer, dispatcher, band_conditions, geocoder, firms, pipeline (Inhibitor + Grouper). ~85 keys total. ADAPTER_META covers 15 adapters with display_name + include_in_llm_context defaulting to True. Per Matt refinement #3, every default matches the current handler constant EXACTLY -- first deploy behavior is unchanged. - _accessor.py: AdapterConfig class with `adapter_config.<adapter>.<key>` syntax. Read pipeline: in-memory cache hit -> SQL -> registry fallback (with WARNING) -> AttributeError. Process-wide cache; PUT via v0.6-3c REST API calls invalidate_cache() to drop the cache. GIL-atomic dict reads on the fast path (handlers call this hot). - __init__.py: seed_defaults(conn) -- INSERT OR IGNOREs one row per registry entry. Idempotent, never overwrites user edits. Wiring: - meshai/persistence/db.py: SCHEMA_VERSION 5 -> 6, and init_db() now calls seed_defaults() after migrations apply. - meshai/main.py: _init_components() now calls init_db() FIRST (per commit #1 lessons-learned: a startup-time migration is required when handlers will rely on the new schema; lazy-on-first-handler is fine for v4/v5 but not for v6 where handler reads start in v0.6-3b). - tests/conftest.py: autouse fixture now calls init_db() + clears the accessor cache around each test, so every test gets the v6 seed AND a clean cache without per-test boilerplate. Tests (tests/test_adapter_config_foundation.py, 24 cases): - v6 tables exist + schema_meta at 6 + type-vocabulary CHECK enforced - seed populates every REGISTRY + ADAPTER_META row, value_json == default_json on first seed, type matches - seed is idempotent + does not overwrite user edits - accessor returns correctly typed values for int/float/str/bool/ json list/json dict/json None - cache hit: second read does not touch the DB (patched _load_from_db raises, accessor still succeeds) - invalidate_cache forces a re-read; mutated DB value wins - registry fallback path triggers when a row is missing (with WARNING) - unknown key raises AttributeError - setattr blocked (writes go via the REST API in 3c) - every default JSON round-trips cleanly; every type is in vocabulary - ADAPTER_META covers every adapter in REGISTRY Test count: 697 -> 721 (+24 new, 0 regressions). v0.6-3b will wire handlers one at a time (wfigs, nws, quake, swpc, nwis, incident, central, dispatcher, band_conditions, geocoder, firms). Per the audit lock, defaults match exactly so each wiring step is a pure refactor -- bisect-safe. v0.6-3c lands the /api/adapter-config CRUD + the AdapterConfig.tsx dashboard editor + cache invalidation on PUT. Refs audit doc v0.6-phase1-audit.md Section A + finding #4.
2026-06-05 17:06:51 +00:00
v = adapter_config.wfigs.cooldown_seconds
assert v == 28800
def test_invalidate_forces_reload(fresh_db):
invalidate_cache()
feat(v0.6-3a.1): trim adapter_config registry to CONFIG-only per Matt config-vs-code rule + log-on-delete safety net for orphan cleanup Drops 35 of the v0.6-3a-draft 77 keys + adds 1 net-new key (firms.dedup_distance_m) for a final count of 43. The trim rules: CONFIG (lives in adapter_config, surfaces in the GUI): where we send (channels), how often (cadences/schedules), thresholds (magnitude floors, severity gates, distance radius, cooldown durations, freshness windows), curation data (which sites/states/codes), toggles (enabled, include_in_llm_context, drop_zero_magnitude). CODE (stays in handlers, never reaches the GUI): sentence templates, emoji choices, mapping/translation functions (TomTom icon_map, ITD sub_type_map, Central adapter_map and category_map), rendering logic (anchor priority order, expires-bucket formatting, threshold-state labels), heuristic logic (band_conditions Kp/SFI -> Good/Fair/Poor function). Per-adapter outcome (kept | killed): wfigs 4 | 4 (cooldown_seconds, anchor_max_mi, two re-broadcast toggles) nws 3 | 4 (broadcast_severities, tombstone_msgtypes, warning_suffix_promotes) usgs_quake 6 | 3 (centroid, radius, PAGER list, 3 mag floors) swpc 3 | 7 (three storm-tier floors) usgs_nwis 2 | 4 (parameter_codes, broadcast_on_recede) incident 2 | 0 (freshness_seconds, broadcast_on_update) tomtom_incidents 2 | 1 (drop_zero_magnitude, drop_non_present) state_511_atis 1 | 0 (skipped_states) itd_511 0 | 3 (all sub_type maps/emoji/phrase = CODE) central 1 | 2 (severity_thresholds) dispatcher 4 | 0 (LRU cap, prune params, retention days) band_conditions 3 | 6 (SWPC freshness + HamQSL endpoint config) geocoder 6 | 1 (Photon endpoint + town-OSM curation + cache cap) firms 4 | 1* (confidence_floor, frp_floor, bbox, dedup_distance_m) pipeline 2 | 0 (inhibitor TTL, grouper window) * firms: dedup_lat_lon_decimals is replaced by dedup_distance_m=5 per Matt s call (user-facing unit is meters, not decimal places; the handler will internally translate to quantization step in v0.6-3b). adapter_meta stays at 15 rows -- itd_511 keeps its include_in_llm_context toggle even with zero config keys. Live-DB cleanup: meshai/adapter_config/__init__.py:prune_orphans(conn) DELETEs every adapter_config row whose (adapter, key) is no longer in REGISTRY. Each delete is INFO-logged with the prefix "adapter_config orphan removed:" so docker logs carry the paper trail. Called from init_db() after seed_defaults; idempotent (zero deletes on every subsequent boot). Cache is invalidated when any orphan is removed. adapter_meta is NOT pruned -- meta rows are cheap and useful even for adapters that ended up with zero config keys. Tests (34 cases, replaces v0.6-3a 24-case set): - Registry count is 43; ADAPTER_META is 15 - Seed lands every REGISTRY + ADAPTER_META row; idempotent; never overwrites user edits - prune_orphans removes a synthetic legacy row, logs at INFO with the exact prefix, leaves known keys untouched, leaves adapter_meta untouched, invalidates the accessor cache - Accessor returns correctly-typed values incl new firms.dedup_distance_m - Guard tests: no key in REGISTRY contains "emoji", ends with "_map", or contains "template" / "prefix" (catches CODE leaking back in) Test count: 721 -> 731 (+10 net: +5 prune cases, +1 firms.dedup_distance_m, +3 CODE-guard cases, +1 registry-count assertion). Refs Matt s locked CONFIG-vs-CODE rule.
2026-06-05 18:09:49 +00:00
_ = adapter_config.wfigs.cooldown_seconds
feat(v0.6-3a): adapter_config foundation -- migration + defaults registry + typed accessor Closes the foundation slice of audit doc Section A (Rule 17). Lands two SQLite tables, the seed routine that populates them from a Python defaults registry, and a typed accessor that handler code will read in v0.6-3b. No handler changes in this commit -- ZERO behavior risk, every existing test still passes (721 / 69 skipped / 0 failed). v6.sql tables: - adapter_config(adapter, key, value_json, default_json, type, description, updated_at) PRIMARY KEY(adapter, key) -- JSON-encoded values flow through a single column uniformly. CHECK constraint on `type` closes the vocab (int/float/str/ bool/json). - adapter_meta(adapter PK, display_name, include_in_llm_context, description, updated_at) -- per-adapter metadata + the user-scopable LLM-context toggle (Matt refinement #5). meshai/adapter_config/ package: - defaults.py: REGISTRY dict mapping (adapter, key) -> {default, type, description}. Covers audit doc sections A.1-A.12: wfigs, nws, usgs_quake, swpc, usgs_nwis, incident family (tomtom_incidents, state_511_atis, itd_511, shared "incident"), central consumer, dispatcher, band_conditions, geocoder, firms, pipeline (Inhibitor + Grouper). ~85 keys total. ADAPTER_META covers 15 adapters with display_name + include_in_llm_context defaulting to True. Per Matt refinement #3, every default matches the current handler constant EXACTLY -- first deploy behavior is unchanged. - _accessor.py: AdapterConfig class with `adapter_config.<adapter>.<key>` syntax. Read pipeline: in-memory cache hit -> SQL -> registry fallback (with WARNING) -> AttributeError. Process-wide cache; PUT via v0.6-3c REST API calls invalidate_cache() to drop the cache. GIL-atomic dict reads on the fast path (handlers call this hot). - __init__.py: seed_defaults(conn) -- INSERT OR IGNOREs one row per registry entry. Idempotent, never overwrites user edits. Wiring: - meshai/persistence/db.py: SCHEMA_VERSION 5 -> 6, and init_db() now calls seed_defaults() after migrations apply. - meshai/main.py: _init_components() now calls init_db() FIRST (per commit #1 lessons-learned: a startup-time migration is required when handlers will rely on the new schema; lazy-on-first-handler is fine for v4/v5 but not for v6 where handler reads start in v0.6-3b). - tests/conftest.py: autouse fixture now calls init_db() + clears the accessor cache around each test, so every test gets the v6 seed AND a clean cache without per-test boilerplate. Tests (tests/test_adapter_config_foundation.py, 24 cases): - v6 tables exist + schema_meta at 6 + type-vocabulary CHECK enforced - seed populates every REGISTRY + ADAPTER_META row, value_json == default_json on first seed, type matches - seed is idempotent + does not overwrite user edits - accessor returns correctly typed values for int/float/str/bool/ json list/json dict/json None - cache hit: second read does not touch the DB (patched _load_from_db raises, accessor still succeeds) - invalidate_cache forces a re-read; mutated DB value wins - registry fallback path triggers when a row is missing (with WARNING) - unknown key raises AttributeError - setattr blocked (writes go via the REST API in 3c) - every default JSON round-trips cleanly; every type is in vocabulary - ADAPTER_META covers every adapter in REGISTRY Test count: 697 -> 721 (+24 new, 0 regressions). v0.6-3b will wire handlers one at a time (wfigs, nws, quake, swpc, nwis, incident, central, dispatcher, band_conditions, geocoder, firms). Per the audit lock, defaults match exactly so each wiring step is a pure refactor -- bisect-safe. v0.6-3c lands the /api/adapter-config CRUD + the AdapterConfig.tsx dashboard editor + cache invalidation on PUT. Refs audit doc v0.6-phase1-audit.md Section A + finding #4.
2026-06-05 17:06:51 +00:00
fresh_db.execute(
"UPDATE adapter_config SET value_json=? WHERE adapter='wfigs' AND key='cooldown_seconds'",
("3600",),
)
feat(v0.6-3a.1): trim adapter_config registry to CONFIG-only per Matt config-vs-code rule + log-on-delete safety net for orphan cleanup Drops 35 of the v0.6-3a-draft 77 keys + adds 1 net-new key (firms.dedup_distance_m) for a final count of 43. The trim rules: CONFIG (lives in adapter_config, surfaces in the GUI): where we send (channels), how often (cadences/schedules), thresholds (magnitude floors, severity gates, distance radius, cooldown durations, freshness windows), curation data (which sites/states/codes), toggles (enabled, include_in_llm_context, drop_zero_magnitude). CODE (stays in handlers, never reaches the GUI): sentence templates, emoji choices, mapping/translation functions (TomTom icon_map, ITD sub_type_map, Central adapter_map and category_map), rendering logic (anchor priority order, expires-bucket formatting, threshold-state labels), heuristic logic (band_conditions Kp/SFI -> Good/Fair/Poor function). Per-adapter outcome (kept | killed): wfigs 4 | 4 (cooldown_seconds, anchor_max_mi, two re-broadcast toggles) nws 3 | 4 (broadcast_severities, tombstone_msgtypes, warning_suffix_promotes) usgs_quake 6 | 3 (centroid, radius, PAGER list, 3 mag floors) swpc 3 | 7 (three storm-tier floors) usgs_nwis 2 | 4 (parameter_codes, broadcast_on_recede) incident 2 | 0 (freshness_seconds, broadcast_on_update) tomtom_incidents 2 | 1 (drop_zero_magnitude, drop_non_present) state_511_atis 1 | 0 (skipped_states) itd_511 0 | 3 (all sub_type maps/emoji/phrase = CODE) central 1 | 2 (severity_thresholds) dispatcher 4 | 0 (LRU cap, prune params, retention days) band_conditions 3 | 6 (SWPC freshness + HamQSL endpoint config) geocoder 6 | 1 (Photon endpoint + town-OSM curation + cache cap) firms 4 | 1* (confidence_floor, frp_floor, bbox, dedup_distance_m) pipeline 2 | 0 (inhibitor TTL, grouper window) * firms: dedup_lat_lon_decimals is replaced by dedup_distance_m=5 per Matt s call (user-facing unit is meters, not decimal places; the handler will internally translate to quantization step in v0.6-3b). adapter_meta stays at 15 rows -- itd_511 keeps its include_in_llm_context toggle even with zero config keys. Live-DB cleanup: meshai/adapter_config/__init__.py:prune_orphans(conn) DELETEs every adapter_config row whose (adapter, key) is no longer in REGISTRY. Each delete is INFO-logged with the prefix "adapter_config orphan removed:" so docker logs carry the paper trail. Called from init_db() after seed_defaults; idempotent (zero deletes on every subsequent boot). Cache is invalidated when any orphan is removed. adapter_meta is NOT pruned -- meta rows are cheap and useful even for adapters that ended up with zero config keys. Tests (34 cases, replaces v0.6-3a 24-case set): - Registry count is 43; ADAPTER_META is 15 - Seed lands every REGISTRY + ADAPTER_META row; idempotent; never overwrites user edits - prune_orphans removes a synthetic legacy row, logs at INFO with the exact prefix, leaves known keys untouched, leaves adapter_meta untouched, invalidates the accessor cache - Accessor returns correctly-typed values incl new firms.dedup_distance_m - Guard tests: no key in REGISTRY contains "emoji", ends with "_map", or contains "template" / "prefix" (catches CODE leaking back in) Test count: 721 -> 731 (+10 net: +5 prune cases, +1 firms.dedup_distance_m, +3 CODE-guard cases, +1 registry-count assertion). Refs Matt s locked CONFIG-vs-CODE rule.
2026-06-05 18:09:49 +00:00
assert adapter_config.wfigs.cooldown_seconds == 28800 # still cached
feat(v0.6-3a): adapter_config foundation -- migration + defaults registry + typed accessor Closes the foundation slice of audit doc Section A (Rule 17). Lands two SQLite tables, the seed routine that populates them from a Python defaults registry, and a typed accessor that handler code will read in v0.6-3b. No handler changes in this commit -- ZERO behavior risk, every existing test still passes (721 / 69 skipped / 0 failed). v6.sql tables: - adapter_config(adapter, key, value_json, default_json, type, description, updated_at) PRIMARY KEY(adapter, key) -- JSON-encoded values flow through a single column uniformly. CHECK constraint on `type` closes the vocab (int/float/str/ bool/json). - adapter_meta(adapter PK, display_name, include_in_llm_context, description, updated_at) -- per-adapter metadata + the user-scopable LLM-context toggle (Matt refinement #5). meshai/adapter_config/ package: - defaults.py: REGISTRY dict mapping (adapter, key) -> {default, type, description}. Covers audit doc sections A.1-A.12: wfigs, nws, usgs_quake, swpc, usgs_nwis, incident family (tomtom_incidents, state_511_atis, itd_511, shared "incident"), central consumer, dispatcher, band_conditions, geocoder, firms, pipeline (Inhibitor + Grouper). ~85 keys total. ADAPTER_META covers 15 adapters with display_name + include_in_llm_context defaulting to True. Per Matt refinement #3, every default matches the current handler constant EXACTLY -- first deploy behavior is unchanged. - _accessor.py: AdapterConfig class with `adapter_config.<adapter>.<key>` syntax. Read pipeline: in-memory cache hit -> SQL -> registry fallback (with WARNING) -> AttributeError. Process-wide cache; PUT via v0.6-3c REST API calls invalidate_cache() to drop the cache. GIL-atomic dict reads on the fast path (handlers call this hot). - __init__.py: seed_defaults(conn) -- INSERT OR IGNOREs one row per registry entry. Idempotent, never overwrites user edits. Wiring: - meshai/persistence/db.py: SCHEMA_VERSION 5 -> 6, and init_db() now calls seed_defaults() after migrations apply. - meshai/main.py: _init_components() now calls init_db() FIRST (per commit #1 lessons-learned: a startup-time migration is required when handlers will rely on the new schema; lazy-on-first-handler is fine for v4/v5 but not for v6 where handler reads start in v0.6-3b). - tests/conftest.py: autouse fixture now calls init_db() + clears the accessor cache around each test, so every test gets the v6 seed AND a clean cache without per-test boilerplate. Tests (tests/test_adapter_config_foundation.py, 24 cases): - v6 tables exist + schema_meta at 6 + type-vocabulary CHECK enforced - seed populates every REGISTRY + ADAPTER_META row, value_json == default_json on first seed, type matches - seed is idempotent + does not overwrite user edits - accessor returns correctly typed values for int/float/str/bool/ json list/json dict/json None - cache hit: second read does not touch the DB (patched _load_from_db raises, accessor still succeeds) - invalidate_cache forces a re-read; mutated DB value wins - registry fallback path triggers when a row is missing (with WARNING) - unknown key raises AttributeError - setattr blocked (writes go via the REST API in 3c) - every default JSON round-trips cleanly; every type is in vocabulary - ADAPTER_META covers every adapter in REGISTRY Test count: 697 -> 721 (+24 new, 0 regressions). v0.6-3b will wire handlers one at a time (wfigs, nws, quake, swpc, nwis, incident, central, dispatcher, band_conditions, geocoder, firms). Per the audit lock, defaults match exactly so each wiring step is a pure refactor -- bisect-safe. v0.6-3c lands the /api/adapter-config CRUD + the AdapterConfig.tsx dashboard editor + cache invalidation on PUT. Refs audit doc v0.6-phase1-audit.md Section A + finding #4.
2026-06-05 17:06:51 +00:00
invalidate_cache()
assert adapter_config.wfigs.cooldown_seconds == 3600
# ---------- defensive fallback paths -------------------------------------
def test_registry_fallback_when_db_row_missing(fresh_db, caplog):
invalidate_cache()
fresh_db.execute(
"DELETE FROM adapter_config WHERE adapter='wfigs' AND key='cooldown_seconds'"
)
caplog.set_level(logging.WARNING, logger="meshai.adapter_config._accessor")
v = adapter_config.wfigs.cooldown_seconds
feat(v0.6-3a.1): trim adapter_config registry to CONFIG-only per Matt config-vs-code rule + log-on-delete safety net for orphan cleanup Drops 35 of the v0.6-3a-draft 77 keys + adds 1 net-new key (firms.dedup_distance_m) for a final count of 43. The trim rules: CONFIG (lives in adapter_config, surfaces in the GUI): where we send (channels), how often (cadences/schedules), thresholds (magnitude floors, severity gates, distance radius, cooldown durations, freshness windows), curation data (which sites/states/codes), toggles (enabled, include_in_llm_context, drop_zero_magnitude). CODE (stays in handlers, never reaches the GUI): sentence templates, emoji choices, mapping/translation functions (TomTom icon_map, ITD sub_type_map, Central adapter_map and category_map), rendering logic (anchor priority order, expires-bucket formatting, threshold-state labels), heuristic logic (band_conditions Kp/SFI -> Good/Fair/Poor function). Per-adapter outcome (kept | killed): wfigs 4 | 4 (cooldown_seconds, anchor_max_mi, two re-broadcast toggles) nws 3 | 4 (broadcast_severities, tombstone_msgtypes, warning_suffix_promotes) usgs_quake 6 | 3 (centroid, radius, PAGER list, 3 mag floors) swpc 3 | 7 (three storm-tier floors) usgs_nwis 2 | 4 (parameter_codes, broadcast_on_recede) incident 2 | 0 (freshness_seconds, broadcast_on_update) tomtom_incidents 2 | 1 (drop_zero_magnitude, drop_non_present) state_511_atis 1 | 0 (skipped_states) itd_511 0 | 3 (all sub_type maps/emoji/phrase = CODE) central 1 | 2 (severity_thresholds) dispatcher 4 | 0 (LRU cap, prune params, retention days) band_conditions 3 | 6 (SWPC freshness + HamQSL endpoint config) geocoder 6 | 1 (Photon endpoint + town-OSM curation + cache cap) firms 4 | 1* (confidence_floor, frp_floor, bbox, dedup_distance_m) pipeline 2 | 0 (inhibitor TTL, grouper window) * firms: dedup_lat_lon_decimals is replaced by dedup_distance_m=5 per Matt s call (user-facing unit is meters, not decimal places; the handler will internally translate to quantization step in v0.6-3b). adapter_meta stays at 15 rows -- itd_511 keeps its include_in_llm_context toggle even with zero config keys. Live-DB cleanup: meshai/adapter_config/__init__.py:prune_orphans(conn) DELETEs every adapter_config row whose (adapter, key) is no longer in REGISTRY. Each delete is INFO-logged with the prefix "adapter_config orphan removed:" so docker logs carry the paper trail. Called from init_db() after seed_defaults; idempotent (zero deletes on every subsequent boot). Cache is invalidated when any orphan is removed. adapter_meta is NOT pruned -- meta rows are cheap and useful even for adapters that ended up with zero config keys. Tests (34 cases, replaces v0.6-3a 24-case set): - Registry count is 43; ADAPTER_META is 15 - Seed lands every REGISTRY + ADAPTER_META row; idempotent; never overwrites user edits - prune_orphans removes a synthetic legacy row, logs at INFO with the exact prefix, leaves known keys untouched, leaves adapter_meta untouched, invalidates the accessor cache - Accessor returns correctly-typed values incl new firms.dedup_distance_m - Guard tests: no key in REGISTRY contains "emoji", ends with "_map", or contains "template" / "prefix" (catches CODE leaking back in) Test count: 721 -> 731 (+10 net: +5 prune cases, +1 firms.dedup_distance_m, +3 CODE-guard cases, +1 registry-count assertion). Refs Matt s locked CONFIG-vs-CODE rule.
2026-06-05 18:09:49 +00:00
assert v == 28800
feat(v0.6-3a): adapter_config foundation -- migration + defaults registry + typed accessor Closes the foundation slice of audit doc Section A (Rule 17). Lands two SQLite tables, the seed routine that populates them from a Python defaults registry, and a typed accessor that handler code will read in v0.6-3b. No handler changes in this commit -- ZERO behavior risk, every existing test still passes (721 / 69 skipped / 0 failed). v6.sql tables: - adapter_config(adapter, key, value_json, default_json, type, description, updated_at) PRIMARY KEY(adapter, key) -- JSON-encoded values flow through a single column uniformly. CHECK constraint on `type` closes the vocab (int/float/str/ bool/json). - adapter_meta(adapter PK, display_name, include_in_llm_context, description, updated_at) -- per-adapter metadata + the user-scopable LLM-context toggle (Matt refinement #5). meshai/adapter_config/ package: - defaults.py: REGISTRY dict mapping (adapter, key) -> {default, type, description}. Covers audit doc sections A.1-A.12: wfigs, nws, usgs_quake, swpc, usgs_nwis, incident family (tomtom_incidents, state_511_atis, itd_511, shared "incident"), central consumer, dispatcher, band_conditions, geocoder, firms, pipeline (Inhibitor + Grouper). ~85 keys total. ADAPTER_META covers 15 adapters with display_name + include_in_llm_context defaulting to True. Per Matt refinement #3, every default matches the current handler constant EXACTLY -- first deploy behavior is unchanged. - _accessor.py: AdapterConfig class with `adapter_config.<adapter>.<key>` syntax. Read pipeline: in-memory cache hit -> SQL -> registry fallback (with WARNING) -> AttributeError. Process-wide cache; PUT via v0.6-3c REST API calls invalidate_cache() to drop the cache. GIL-atomic dict reads on the fast path (handlers call this hot). - __init__.py: seed_defaults(conn) -- INSERT OR IGNOREs one row per registry entry. Idempotent, never overwrites user edits. Wiring: - meshai/persistence/db.py: SCHEMA_VERSION 5 -> 6, and init_db() now calls seed_defaults() after migrations apply. - meshai/main.py: _init_components() now calls init_db() FIRST (per commit #1 lessons-learned: a startup-time migration is required when handlers will rely on the new schema; lazy-on-first-handler is fine for v4/v5 but not for v6 where handler reads start in v0.6-3b). - tests/conftest.py: autouse fixture now calls init_db() + clears the accessor cache around each test, so every test gets the v6 seed AND a clean cache without per-test boilerplate. Tests (tests/test_adapter_config_foundation.py, 24 cases): - v6 tables exist + schema_meta at 6 + type-vocabulary CHECK enforced - seed populates every REGISTRY + ADAPTER_META row, value_json == default_json on first seed, type matches - seed is idempotent + does not overwrite user edits - accessor returns correctly typed values for int/float/str/bool/ json list/json dict/json None - cache hit: second read does not touch the DB (patched _load_from_db raises, accessor still succeeds) - invalidate_cache forces a re-read; mutated DB value wins - registry fallback path triggers when a row is missing (with WARNING) - unknown key raises AttributeError - setattr blocked (writes go via the REST API in 3c) - every default JSON round-trips cleanly; every type is in vocabulary - ADAPTER_META covers every adapter in REGISTRY Test count: 697 -> 721 (+24 new, 0 regressions). v0.6-3b will wire handlers one at a time (wfigs, nws, quake, swpc, nwis, incident, central, dispatcher, band_conditions, geocoder, firms). Per the audit lock, defaults match exactly so each wiring step is a pure refactor -- bisect-safe. v0.6-3c lands the /api/adapter-config CRUD + the AdapterConfig.tsx dashboard editor + cache invalidation on PUT. Refs audit doc v0.6-phase1-audit.md Section A + finding #4.
2026-06-05 17:06:51 +00:00
assert any("missing from DB" in r.message for r in caplog.records)
def test_unknown_key_raises(fresh_db):
invalidate_cache()
with pytest.raises(AttributeError):
_ = adapter_config.wfigs.no_such_key
def test_setattr_blocked(fresh_db):
invalidate_cache()
with pytest.raises(AttributeError):
adapter_config.wfigs.cooldown_seconds = 999
# ---------- registry sanity ----------------------------------------------
def test_every_registry_default_round_trips_through_json():
for (adapter, key), spec in REGISTRY.items():
encoded = json.dumps(spec["default"])
decoded = json.loads(encoded)
assert decoded == spec["default"], f"{adapter}.{key}: JSON round-trip drift"
def test_every_registry_type_is_in_vocabulary():
valid = {"int", "float", "str", "bool", "json"}
for (adapter, key), spec in REGISTRY.items():
assert spec["type"] in valid, f"{adapter}.{key}: invalid type {spec['type']!r}"
def test_adapter_meta_includes_every_registry_adapter():
reg_adapters = {a for a, _ in REGISTRY}
meta_adapters = set(ADAPTER_META)
missing = reg_adapters - meta_adapters
assert not missing, f"adapters in REGISTRY but missing ADAPTER_META: {missing}"
feat(v0.6-3a.1): trim adapter_config registry to CONFIG-only per Matt config-vs-code rule + log-on-delete safety net for orphan cleanup Drops 35 of the v0.6-3a-draft 77 keys + adds 1 net-new key (firms.dedup_distance_m) for a final count of 43. The trim rules: CONFIG (lives in adapter_config, surfaces in the GUI): where we send (channels), how often (cadences/schedules), thresholds (magnitude floors, severity gates, distance radius, cooldown durations, freshness windows), curation data (which sites/states/codes), toggles (enabled, include_in_llm_context, drop_zero_magnitude). CODE (stays in handlers, never reaches the GUI): sentence templates, emoji choices, mapping/translation functions (TomTom icon_map, ITD sub_type_map, Central adapter_map and category_map), rendering logic (anchor priority order, expires-bucket formatting, threshold-state labels), heuristic logic (band_conditions Kp/SFI -> Good/Fair/Poor function). Per-adapter outcome (kept | killed): wfigs 4 | 4 (cooldown_seconds, anchor_max_mi, two re-broadcast toggles) nws 3 | 4 (broadcast_severities, tombstone_msgtypes, warning_suffix_promotes) usgs_quake 6 | 3 (centroid, radius, PAGER list, 3 mag floors) swpc 3 | 7 (three storm-tier floors) usgs_nwis 2 | 4 (parameter_codes, broadcast_on_recede) incident 2 | 0 (freshness_seconds, broadcast_on_update) tomtom_incidents 2 | 1 (drop_zero_magnitude, drop_non_present) state_511_atis 1 | 0 (skipped_states) itd_511 0 | 3 (all sub_type maps/emoji/phrase = CODE) central 1 | 2 (severity_thresholds) dispatcher 4 | 0 (LRU cap, prune params, retention days) band_conditions 3 | 6 (SWPC freshness + HamQSL endpoint config) geocoder 6 | 1 (Photon endpoint + town-OSM curation + cache cap) firms 4 | 1* (confidence_floor, frp_floor, bbox, dedup_distance_m) pipeline 2 | 0 (inhibitor TTL, grouper window) * firms: dedup_lat_lon_decimals is replaced by dedup_distance_m=5 per Matt s call (user-facing unit is meters, not decimal places; the handler will internally translate to quantization step in v0.6-3b). adapter_meta stays at 15 rows -- itd_511 keeps its include_in_llm_context toggle even with zero config keys. Live-DB cleanup: meshai/adapter_config/__init__.py:prune_orphans(conn) DELETEs every adapter_config row whose (adapter, key) is no longer in REGISTRY. Each delete is INFO-logged with the prefix "adapter_config orphan removed:" so docker logs carry the paper trail. Called from init_db() after seed_defaults; idempotent (zero deletes on every subsequent boot). Cache is invalidated when any orphan is removed. adapter_meta is NOT pruned -- meta rows are cheap and useful even for adapters that ended up with zero config keys. Tests (34 cases, replaces v0.6-3a 24-case set): - Registry count is 43; ADAPTER_META is 15 - Seed lands every REGISTRY + ADAPTER_META row; idempotent; never overwrites user edits - prune_orphans removes a synthetic legacy row, logs at INFO with the exact prefix, leaves known keys untouched, leaves adapter_meta untouched, invalidates the accessor cache - Accessor returns correctly-typed values incl new firms.dedup_distance_m - Guard tests: no key in REGISTRY contains "emoji", ends with "_map", or contains "template" / "prefix" (catches CODE leaking back in) Test count: 721 -> 731 (+10 net: +5 prune cases, +1 firms.dedup_distance_m, +3 CODE-guard cases, +1 registry-count assertion). Refs Matt s locked CONFIG-vs-CODE rule.
2026-06-05 18:09:49 +00:00
# ---------- guard against CODE leaking back into the registry -----------
def test_no_emoji_keys_in_registry():
"""Emoji choices are CODE, not config (Matt's locked rule)."""
for (adapter, key) in REGISTRY:
assert "emoji" not in key, (
f"{adapter}.{key} looks like an emoji setting; emojis are CODE"
)
def test_no_template_keys_in_registry():
"""Sentence templates are CODE."""
for (adapter, key) in REGISTRY:
assert "template" not in key and "prefix" not in key, (
f"{adapter}.{key} looks like a sentence template / prefix; sentences are CODE"
)
def test_no_map_keys_in_registry():
"""Translation maps are CODE (TomTom icon_map, ITD sub_type_map, etc.)."""
for (adapter, key) in REGISTRY:
assert not key.endswith("_map"), (
f"{adapter}.{key} looks like a translation map; mapping functions are CODE"
)