Commit graph

2 commits

Author SHA1 Message Date
68dcbc74d0 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
cb3c5aec7e 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