Move alert evaluation to the profile actor (ticket 105)

Alert rules are profile-scoped and read metric buckets, which now live
on the profile actor (ticket 104) rather than the primary. alert_states
and the per-line evaluate_alerts/deliver_for_rule calls move from the
primary's Ingest arm to the profile actor's per-line loop, evaluated
against its own snapshot right after that line's apply_all_metrics
publishes -- matching the primary's original per-message publish-then-
evaluate ordering. The primary's run() no longer needs a SettingsHandle
at all now that alerts are gone from its Ingest arm; settings (for
alert rules and ntfy config, read fresh per line) moves to
engine::profile::spawn instead.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TeyhRfMYjMditek6geDRoQ
This commit is contained in:
Hugh Kaznowski
2026-09-07 13:55:51 +01:00
co-authored by Claude Sonnet 5
parent ce3e741cb0
commit 3751508de8
3 changed files with 73 additions and 109 deletions
@@ -1,52 +0,0 @@
# 105 — Move alert evaluation onto the profile actor
## Summary
Alert rules are profile-scoped and read metric buckets
(`alert_states: HashMap<String, AlertRuntimeState>` lives on the
single actor's stack frame today), so per [[0001-actor-split]]'s
resolved decision, alert evaluation moves with metric computation.
Depends on [[104-move-metric-computation-to-profile-actor]] landing
first — alert evaluation reads the metric buckets that ticket moves
onto `ProfileSnapshot`.
## Where
- `src/engine/handle.rs:141` — `let mut alert_states:
HashMap<String, AlertRuntimeState> = HashMap::new();`, currently a
local in the single actor's `run()` loop.
- `src/engine/handle.rs`'s `Ingest` arm — today, right after
`write.mutate(...)` publishes, the actor does `write.peek()` and
calls `alerts::evaluate_alerts(&rules, &mut alert_states, &guard,
sequence, timestamp, &text)`, then spawns
`alerts::deliver_for_rule` for each fired alert. This whole block
moves to wherever the profile actor's per-line processing lives
(the watch-wake loop from ticket 103/104).
- `src/engine/alerts.rs:139` (`AlertRuntimeState`), `:241`
(`evaluate_alerts`), `:384` (`deliver_for_rule`) — signatures take
the rules list and a `&EngineSnapshot`-shaped guard; check whether
`evaluate_alerts` needs to change its snapshot parameter type to
`ProfileSnapshot` (likely yes, since it reads bucket data that now
lives there) or whether it can stay snapshot-shape-agnostic via
whatever trait/accessor bucket-reading already uses.
- Settings access for alert rules/ntfy config
(`src/engine/handle.rs:170-177`, `settings.lock().unwrap()`) — this
stays available to the profile actor the same way (it's a
`SettingsHandle` clone, not primary-actor state), so no primary
dependency here.
## Scope
- Move `alert_states` ownership to the profile actor's task-local
state.
- Move the post-publish `evaluate_alerts` call and `deliver_for_rule`
spawn to fire from the profile actor's per-line processing, reading
`ProfileSnapshot` (post-metric-computation) instead of the primary's
`EngineSnapshot`.
- Update `evaluate_alerts`'s signature in `src/engine/alerts.rs` if it
needs to accept a different snapshot type; keep its actual
evaluation logic unchanged — this ticket is a relocation, not a
rewrite of alert semantics.
- Out of scope: any change to alert rule semantics, ntfy delivery, or
the `AlertRuntimeState` model itself — purely which actor owns and
drives evaluation.