Commit graph

1 commit

Author SHA1 Message Date
90783376e8 feat(v0.6-6): inhibit_state + grouper_held persistence + ToggleFilter live-reload + Inhibitor/Grouper config knobs
Closes audit doc section A.9 + finding #5. The last Phase-1 pipeline
state that lived only in instance memory now writes through to SQLite,
and ToggleFilter changes propagate without a container restart.

Schema:
  v10.sql adds inhibit_state(key PK, rank, expires_at, updated_at) and
  grouper_held(group_key PK, event_json, hold_until_at, updated_at).
  Indexes on expires_at / hold_until_at support the prune sweeps.
  SCHEMA_VERSION 9 -> 10.

Migration runner:
  Fixed the alphabetical-vs-numeric sort bug v10 surfaced -- the runner
  now sorts pending migrations by their integer version, not by
  filename, so v10.sql correctly applies AFTER v9.sql (was applying
  after v1 alphabetically, which made schema_meta stick at 9).

Inhibitor (meshai/notifications/pipeline/inhibitor.py):
  - __init__ restores non-expired keys from inhibit_state on construct.
  - handle() write-throughs every (key, rank, expires_at) tuple.
  - _prune_expired DELETEs the same expired keys from disk.
  - clear() (test path) drops the table.

Grouper (meshai/notifications/pipeline/grouper.py):
  - __init__ restores non-expired held events from grouper_held; the
    Event is rebuilt via Event.from_dict(json.loads(event_json)).
  - handle() write-throughs (group_key, event_json, hold_until_at).
  - tick() and flush_all() DELETE on emit.

ToggleFilter (meshai/notifications/pipeline/toggle_filter.py):
  - new refresh(config) method re-reads config.notifications.toggles and
    rebuilds the enabled set.

Live wiring:
  - meshai/dashboard/api/config_routes.py adds a POST
    /api/notifications/refresh-toggles endpoint that reaches into
    app.state.bus._pipeline_components["toggle_filter"] and calls
    refresh(app.state.config). The frontend pings this after PUT
    /api/config/notifications so toggles take effect on the next event.
  - meshai/main.py stashes self.event_bus on the dashboard FastAPI
    app.state after build_pipeline so the route can reach it.
  - Inhibitor.ttl_seconds and Grouper.window_seconds already read from
    adapter_config.pipeline.{inhibitor_ttl_seconds, grouper_window_seconds}
    via the v0.6-3b None-default wiring (rows seeded in v0.6-3a.1).

Tests (tests/test_pipeline_persistence.py, 11 cases):
  - v10 tables present
  - Inhibitor: state persists across simulated restart; expired rows
    not restored; prune removes from disk; clear() wipes both.
  - Grouper: state persists across restart; tick() clears disk;
    expired rows not restored.
  - ToggleFilter: refresh() picks up new enabled set; refresh(None)
    is a no-op; disabling a family in config + refresh drops it.

Test count: 819 -> 830 (+11 pipeline persistence cases + schema test
bump).
2026-06-05 20:23:34 +00:00