yah-tower 0.8.20

yah-tower — rule-driven monitor on scryer. Rule parser/compiler, ScryerFilter, and dispatch engine.
Documentation
//! Minimal event type for tower-internal use.
//!
//! Will be replaced by `observation::Event` when crates/yah/observation/ ships
//! (R093-F1). The shape here mirrors what scryer will produce so that
//! `ScryerFilter::matches` and the tests are faithful to the substrate contract.

use std::collections::HashMap;

use serde_json::Value;
use tower_rules::{ForgeId, HealthSignal, MeshIdent};

/// A single structured event produced by a scryer ingestion adapter.
///
/// The `(scope, seq)` pair is unique and stable across the event's lifetime,
/// per the scryer substrate contract. Tower uses it for dedup in the supervisor.
#[derive(Debug, Clone)]
pub struct Event {
    /// Identifies which service, task-run, forge-run, or health stream this
    /// event belongs to.
    pub scope: EventScope,
    /// Log level. Defaults to `Info` for unstructured lines.
    pub level: Level,
    /// Event category, e.g. `"raft.term_change"` or `"database.query_slow"`.
    pub target: String,
    /// Human-readable message.
    pub msg: String,
    /// Structured key-value payload, JSON-typed.
    pub fields: HashMap<String, Value>,
    /// Per-scope monotonically increasing event number. Unique with `scope`.
    pub seq: u64,
}

/// Identifies which event stream an event belongs to.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EventScope {
    /// An agent or tool subprocess run.
    TaskRun(String),
    /// A long-running containerd workload identified by its mesh identity.
    Service(MeshIdent),
    /// A transient forge run.
    Forge(ForgeId),
    /// Scryer's own health emissions (produced by the scryer substrate itself,
    /// not by a workload). Used by `Predicate::ScryerHealth`.
    Health,
}

/// Structured log level, ordered Debug < Info < Warn < Error.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Level {
    Debug = 0,
    Info = 1,
    Warn = 2,
    Error = 3,
}

impl Event {
    /// Construct a health-signal event. These come from scryer's own substrate
    /// and are matched by `Predicate::ScryerHealth` rules.
    pub fn health(signal: HealthSignal, seq: u64) -> Self {
        Event {
            scope: EventScope::Health,
            level: Level::Warn,
            target: format!("scryer.health.{}", health_signal_name(signal)),
            msg: String::new(),
            fields: HashMap::new(),
            seq,
        }
    }
}

pub(crate) fn health_signal_name(signal: HealthSignal) -> &'static str {
    match signal {
        HealthSignal::IngestionLag => "ingestion_lag",
        HealthSignal::RingOverflow => "ring_overflow",
        HealthSignal::FederationTimeout => "federation_timeout",
        HealthSignal::YubabaRaftQuorumLost => "yubaba_raft_quorum_lost",
    }
}