tower/event.rs
1//! Minimal event type for tower-internal use.
2//!
3//! Will be replaced by `observation::Event` when crates/yah/observation/ ships
4//! (R093-F1). The shape here mirrors what scryer will produce so that
5//! `ScryerFilter::matches` and the tests are faithful to the substrate contract.
6
7use std::collections::HashMap;
8
9use serde_json::Value;
10use tower_rules::{ForgeId, HealthSignal, MeshIdent};
11
12/// A single structured event produced by a scryer ingestion adapter.
13///
14/// The `(scope, seq)` pair is unique and stable across the event's lifetime,
15/// per the scryer substrate contract. Tower uses it for dedup in the supervisor.
16#[derive(Debug, Clone)]
17pub struct Event {
18 /// Identifies which service, task-run, forge-run, or health stream this
19 /// event belongs to.
20 pub scope: EventScope,
21 /// Log level. Defaults to `Info` for unstructured lines.
22 pub level: Level,
23 /// Event category, e.g. `"raft.term_change"` or `"database.query_slow"`.
24 pub target: String,
25 /// Human-readable message.
26 pub msg: String,
27 /// Structured key-value payload, JSON-typed.
28 pub fields: HashMap<String, Value>,
29 /// Per-scope monotonically increasing event number. Unique with `scope`.
30 pub seq: u64,
31}
32
33/// Identifies which event stream an event belongs to.
34#[derive(Debug, Clone, PartialEq, Eq)]
35pub enum EventScope {
36 /// An agent or tool subprocess run.
37 TaskRun(String),
38 /// A long-running containerd workload identified by its mesh identity.
39 Service(MeshIdent),
40 /// A transient forge run.
41 Forge(ForgeId),
42 /// Scryer's own health emissions (produced by the scryer substrate itself,
43 /// not by a workload). Used by `Predicate::ScryerHealth`.
44 Health,
45}
46
47/// Structured log level, ordered Debug < Info < Warn < Error.
48#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
49pub enum Level {
50 Debug = 0,
51 Info = 1,
52 Warn = 2,
53 Error = 3,
54}
55
56impl Event {
57 /// Construct a health-signal event. These come from scryer's own substrate
58 /// and are matched by `Predicate::ScryerHealth` rules.
59 pub fn health(signal: HealthSignal, seq: u64) -> Self {
60 Event {
61 scope: EventScope::Health,
62 level: Level::Warn,
63 target: format!("scryer.health.{}", health_signal_name(signal)),
64 msg: String::new(),
65 fields: HashMap::new(),
66 seq,
67 }
68 }
69}
70
71pub(crate) fn health_signal_name(signal: HealthSignal) -> &'static str {
72 match signal {
73 HealthSignal::IngestionLag => "ingestion_lag",
74 HealthSignal::RingOverflow => "ring_overflow",
75 HealthSignal::FederationTimeout => "federation_timeout",
76 HealthSignal::YubabaRaftQuorumLost => "yubaba_raft_quorum_lost",
77 }
78}