Skip to main content

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}