Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/monetize

crates/monetize-guardian/src/case.rs

//! **A case**: one thing wrong with one estate, from detection to done, with who pressed and
//! what happened — the receipt.

use monetize_product::guardian::{Finding, Remedy, Step};

/// detected → investigating → approved → running → done | failed; `resolved` when the
/// estate recovered on its own before anyone pressed.
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum CaseState {
    #[default]
    Detected,
    Investigating,
    Approved,
    Running,
    Done,
    Failed,
    Resolved,
}

impl CaseState {
    /// Still being worked on: at most one per estate.
    pub fn open(self) -> bool {
        matches!(self, CaseState::Detected | CaseState::Investigating | CaseState::Approved | CaseState::Running)
    }

    pub fn as_str(self) -> &'static str {
        match self {
            CaseState::Detected => "detected",
            CaseState::Investigating => "investigating",
            CaseState::Approved => "approved",
            CaseState::Running => "running",
            CaseState::Done => "done",
            CaseState::Failed => "failed",
            CaseState::Resolved => "resolved",
        }
    }
}

/// Where a step of the plan stands.
pub mod step_state {
    pub const PENDING: &str = "pending";
    pub const RUNNING: &str = "running";
    pub const MET: &str = "met";
    pub const FAILED: &str = "failed";
}

/// One step of the case's plan, and how it went.
#[derive(Clone, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
pub struct CaseStep {
    pub step: Step,
    /// [`step_state`].
    pub state: String,
    #[serde(default)]
    pub started_unix_ms: u64,
    #[serde(default)]
    pub done_unix_ms: u64,
    #[serde(default)]
    pub words: String,
    /// The order's seq, for an order step that was queued; 0 otherwise.
    #[serde(default)]
    pub seq: u64,
    /// What a fence step asked the provider to give back (its ids), once; empty before.
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub asked: Vec<String>,
    /// **A fence step's proof** — the old box the provider's listing read back gone, and
    /// when; empty while unconfirmed, and for a fence that had no laid record to give back
    /// ("nothing to fence" is no fence). A promotion needs it ([`crate::runner`]).
    #[serde(default, skip_serializing_if = "String::is_empty")]
    pub fenced: String,
}

/// One line of the case's log.
#[derive(Clone, PartialEq, Eq, Debug, Default, serde::Serialize, serde::Deserialize)]
pub struct Line {
    pub at_unix_ms: u64,
    pub words: String,
}

/// **The case** — stored whole, and its JSON is the receipt.
#[derive(Clone, PartialEq, Eq, Debug, Default, serde::Serialize, serde::Deserialize)]
#[serde(default)]
pub struct Case {
    /// `<instance>-<n>`.
    pub id: String,
    pub instance: String,
    pub product: String,
    pub state: CaseState,
    pub opened_unix_ms: u64,
    pub updated_unix_ms: u64,
    pub finding: Finding,
    /// The remedy pressed (or auto-fix took).
    pub remedy: Option<Remedy>,
    /// WHO approved: the operator's principal, or [`crate::AUTO_PRINCIPAL`].
    pub approved_by: String,
    pub approved_unix_ms: u64,
    /// Approved by the estate's auto-fix opt-in, not a human.
    pub auto: bool,
    /// The fence epoch this case's orders carry.
    pub epoch: u64,
    pub steps: Vec<CaseStep>,
    /// Evidence and progress, in order.
    pub log: Vec<Line>,
    pub outcome: String,
}

impl Case {
    pub fn say(&mut self, now_unix_ms: u64, words: impl Into<String>) {
        self.updated_unix_ms = now_unix_ms;
        self.log.push(Line { at_unix_ms: now_unix_ms, words: words.into() });
    }

    /// The buttons this case offers now: its finding's remedies while nobody pressed.
    pub fn buttons(&self) -> Vec<Remedy> {
        if matches!(self.state, CaseState::Detected | CaseState::Investigating) {
            self.finding.remedies.clone()
        } else {
            Vec::new()
        }
    }

    /// The receipt: the case, whole, as JSON.
    pub fn receipt_json(&self) -> String {
        serde_json::to_string_pretty(self).unwrap_or_default()
    }
}