Break ticket 102's actor split into reviewable sub-tickets (103-108)

Ticket 102 (splitting the engine into reader/primary/profile actors)
was a single multi-thousand-line ticket covering a new actor
topology, an EngineHandle API redesign, a query fan-out layer, moving
alerts, and a full test-harness rewrite, with some design decisions
deliberately left open. Splitting it into six sequenced tickets
(profile actor skeleton + signaling, move metric computation, move
alert evaluation, split the handle API, query fan-out, test harness
rework) lets each land as its own reviewable commit instead of one
large speculative rewrite.

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:33:01 +01:00
co-authored by Claude Sonnet 5
parent 756e054527
commit 2c1aaa6e9c
7 changed files with 369 additions and 107 deletions
@@ -0,0 +1,73 @@
# 104 — Move metric declarations and bucket computation into the profile actor
## Summary
Second slice of the split: metric declarations, `apply_all_metrics`,
and the declare/update/remove/backfill machinery move off the primary
`EngineSnapshot` and onto the profile actor's `ProfileSnapshot`
(stood up in [[103-profile-actor-skeleton-and-primary-signaling]]).
The primary keeps raw log storage and param declarations only, per
[[0001-actor-split]]'s resolved decision that the primary "extracts
declared + auto params only... no metrics, no profile logic." The
profile actor consumes newly-visible primary lines (via the
watch-wake loop from ticket 103) and runs metric regexes against them
itself, building its own bucket data independent of the primary.
## Where
- `src/engine/snapshot/mod.rs:364-434` (`validate_metric_request`,
`declare_metric`, `resolve_backfill_and_store_metric`) and
`:434-487` (`validate_update_metric`, `update_metric`,
`remove_metric_declaration`) — the declare/update/remove machinery
to move onto `ProfileSnapshot`.
- `src/engine/snapshot/mod.rs:587-617` (`backfill_metric`,
`apply_all_metrics`) — backfill replays `self.logs_range()` (a
primary-side accessor) through `apply_metric_to_log`; once metrics
live on the profile actor, backfill needs to read logs through the
profile actor's cloned primary `IndexReader` instead of `self`.
- `src/engine/handle.rs` — `EngineMessage::DeclareMetric`/
`UpdateMetric`/`RemoveMetricDeclaration` arms (see the message list
at `src/engine/message.rs:30-63`) currently route through the
single actor's `write.mutate` against `EngineSnapshot`; these need
to route to the profile actor's `ProfileSnapshot` instead.
- `src/engine/handle.rs`'s `Ingest` arm — `snap.apply_all_metrics(...)`
is called directly after `ingest_params` today
(see ticket 101's change); this call moves out of the primary's
ingest closure entirely once the profile actor computes metrics
itself from the lines it observes via the watch/read loop, rather
than the primary computing them inline.
## Scope
- Move `MetricDeclaration`/bucket-related `IndexKey`/`DataValue`
variants' *usage* (not necessarily their type definitions, which are
shared) onto `ProfileSnapshot`'s own index/map, separate from the
primary's.
- Move `declare_metric`/`update_metric`/`remove_metric_declaration`/
`backfill_metric`/`apply_all_metrics` (and their validation
counterparts) to operate on `ProfileSnapshot`, reading raw log text
via the profile actor's cloned primary `IndexReader` wherever they
need `logs_range`-style access, instead of `self.index` on the
primary.
- Remove the `apply_all_metrics` call from the primary's `Ingest` arm
in `handle.rs` — the primary no longer computes metrics inline; the
profile actor's watch-wake loop (from ticket 103) now calls
`apply_all_metrics`-equivalent logic itself per newly-visible line.
- `DeclareMetric`/`UpdateMetric`/`RemoveMetricDeclaration` messages
need a new route to the profile actor (a second `mpsc` channel, or
folding into the profile actor's own message enum) — decide the
concrete mechanism during implementation; `EngineHandle`'s public
API doesn't need to change yet if this ticket keeps routing internal
to `handle.rs` (full API surface changes are
[[106-split-enginehandle-api-for-primary-and-profile]]'s scope).
- Out of scope: alert evaluation (still reads the primary's snapshot
directly today — [[105-move-alert-evaluation-to-profile-actor]]
moves it), and the profile-switch (`activate_profile`) reload path
(still targets whatever this ticket lands as the metric-owning
actor, but retargeting `src/api/mod.rs:812-852` explicitly is
[[106-split-enginehandle-api-for-primary-and-profile]]'s scope).
- Backfill cost and blocking behavior: per [[0001-actor-split]]'s
performance rationale, this move is what stops a slow backfill from
blocking the primary's ingest/query-serving — confirm with a test
that ingesting new lines on the primary keeps working while the
profile actor is mid-backfill on a newly declared metric.