use crate::types::TenantId;
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use thiserror::Error;
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum Severity {
Critical,
High,
Medium,
Low,
}
impl Severity {
#[must_use]
pub fn escalate_one_level(self) -> Self {
match self {
Self::Low => Self::Medium,
Self::Medium => Self::High,
Self::High => Self::Critical,
Self::Critical => Self::Critical,
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum EscalationState {
Raised,
Acknowledged,
Resolved,
AutoDenied,
Halted,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[non_exhaustive]
pub struct ProvenanceRef {
pub run_id: String,
pub merkle_root: String,
pub chain_signature: Option<String>,
}
impl ProvenanceRef {
pub fn new(
run_id: impl Into<String>,
merkle_root: impl Into<String>,
chain_signature: Option<String>,
) -> Self {
Self {
run_id: run_id.into(),
merkle_root: merkle_root.into(),
chain_signature,
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[non_exhaustive]
pub struct EscalationTicket {
pub tenant: Option<TenantId>,
pub severity: Severity,
pub reason: String,
pub provenance: Option<ProvenanceRef>,
}
impl EscalationTicket {
pub fn new(
severity: Severity,
reason: impl Into<String>,
tenant: Option<TenantId>,
provenance: Option<ProvenanceRef>,
) -> Self {
Self {
tenant,
severity,
reason: reason.into(),
provenance,
}
}
}
#[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)]
pub struct EscalationId(pub String);
impl std::fmt::Display for EscalationId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.0)
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[non_exhaustive]
pub struct Resolution {
pub outcome: ResolutionOutcome,
pub reason: Option<String>,
pub approvers: Vec<String>,
pub signatures: Vec<Vec<u8>>,
}
impl Resolution {
pub fn new(
outcome: ResolutionOutcome,
reason: Option<String>,
approvers: Vec<String>,
signatures: Vec<Vec<u8>>,
) -> Self {
Self {
outcome,
reason,
approvers,
signatures,
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum ResolutionOutcome {
Approved,
Denied,
TimedOut,
Halted,
}
#[derive(Clone, Debug, Default)]
#[non_exhaustive]
pub struct EscalationFilter {
pub states: Option<Vec<EscalationState>>,
pub min_severity: Option<Severity>,
pub tenant: Option<TenantId>,
}
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum EscalationError {
#[error("escalation channel unavailable: {0}")]
Unavailable(String),
#[error("unknown escalation ticket: {0}")]
UnknownTicket(EscalationId),
#[error("quorum not met: {provided}/{required}")]
QuorumNotMet {
provided: u8,
required: u8,
},
#[error("internal: {0}")]
Internal(String),
}
#[async_trait]
pub trait Escalation: Send + Sync {
async fn raise(&self, ticket: EscalationTicket) -> Result<EscalationId, EscalationError>;
async fn resolve(
&self,
id: EscalationId,
resolution: Resolution,
) -> Result<(), EscalationError>;
async fn list(&self, filter: EscalationFilter) -> Vec<EscalationTicket>;
}