# 101 — Metric computation is inlined into the single ingest actor's write closure, not a separately-callable stage ## Summary Per-line ingest currently does ingest-lag bookkeeping, log-stream header detection, raw-text storage, auto-param detection, *and* every declared metric's regex evaluation, all synchronously inside one `write.mutate(...)` closure in the actor's `run()` loop, followed by alert evaluation against the result in the same message handler. This is migration step 2 of [[0001-actor-split]]'s resolved plan: pull metric computation out into a stage that can be called independently of the ingest closure — still within the single actor for now — so that step 3 (splitting into reader/primary/profile actors) is a wiring change rather than also being the first time metric computation is separated from raw ingest. Depends on [[100-arc-ify-stored-log-and-metric-strings]] landing first (Arc'd text is what makes passing a line into a separate stage cheap). ## Where - `src/engine/handle.rs:132-224` (`run()`'s `EngineMessage::Ingest` arm) — the closure passed to `write.mutate` at :180-198 does header detection, storage, `auto_declare_params`, and `snap.apply_all_metrics(sequence, timestamp, &text_for_op)` (:196) all in one synchronous block; alert evaluation follows immediately after at :199-219, reading the snapshot metric computation just produced. - `src/engine/snapshot/mod.rs:616` (`apply_all_metrics`) — the entry point that iterates every declared metric and calls `apply_metric_to_log` (:499-586) per match; currently only reachable as a method on `EngineSnapshot` invoked from inside the ingest closure above. - `src/engine/alerts.rs:211` (`evaluate_alerts`) — currently called directly from the ingest arm (`handle.rs:199-219`) against the snapshot mutated by the same message; this is the alert-evaluation call site that migration step 3 will later move to a profile actor (per [[0001-actor-split]]'s "Resolved decisions" — not in scope for this ticket). ## Scope - Extract param-extraction (header detection, `auto_declare_params`, raw storage) and metric-computation (`apply_all_metrics`) into separate, independently-callable functions/methods with a clear boundary between them — e.g. a function that takes a line and returns/records derived params only, and a second that takes the declared-metrics set plus a param-resolved line and returns bucket updates — rather than one closure that does both inline. - Keep both stages running inside the same actor and the same `write.mutate` transaction for this ticket — the goal is a clean internal seam, not concurrency yet. Step 3 (a separate ticket, not this one) is what actually moves metric computation to a different task. - Do not change alert evaluation's behavior or its current call site relative to metric computation (still immediately after, same actor) — only prepare the metric-computation code so it's callable as its own unit; moving alerts to a profile actor is explicitly step 3's concern per [[0001-actor-split]]'s resolved decisions. - Out of scope: the reader/primary/profile actor split itself, any new `tokio::sync::watch` signaling, and any change to `EngineHandle`'s public API — this ticket is an internal refactor within the existing single-actor structure.