use serde::{Deserialize, Serialize};
use crate::crypto::PublicKey;
use crate::error::{Error, Result};
use crate::event::{EventId, ResourceId};
use super::capability::{CapabilityId, CapabilityKind};
use super::principal::PrincipalId;
use super::session::SessionId;
pub type DurationMs = i64;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum EmergencyAction {
SuspendAgent {
agent: PublicKey,
reason: String,
duration: Option<DurationMs>,
scope: SuspensionScope,
},
RevokeAgent {
agent: PublicKey,
reason: String,
},
TerminateSession {
session_id: SessionId,
reason: String,
},
RevokeCapability {
capability_id: CapabilityId,
reason: String,
},
BlockResource {
resource: ResourceId,
blocked_actors: Vec<PublicKey>,
reason: String,
duration: Option<DurationMs>,
},
GlobalPause {
reason: String,
duration: DurationMs,
exceptions: Vec<PublicKey>,
},
RollbackActions {
agent: PublicKey,
since: i64,
reason: String,
},
}
impl EmergencyAction {
pub fn suspend_agent(
agent: PublicKey,
reason: impl Into<String>,
duration: Option<DurationMs>,
scope: SuspensionScope,
) -> Self {
Self::SuspendAgent {
agent,
reason: reason.into(),
duration,
scope,
}
}
pub fn revoke_agent(agent: PublicKey, reason: impl Into<String>) -> Self {
Self::RevokeAgent {
agent,
reason: reason.into(),
}
}
pub fn terminate_session(session_id: SessionId, reason: impl Into<String>) -> Self {
Self::TerminateSession {
session_id,
reason: reason.into(),
}
}
pub fn revoke_capability(capability_id: CapabilityId, reason: impl Into<String>) -> Self {
Self::RevokeCapability {
capability_id,
reason: reason.into(),
}
}
pub fn block_resource(
resource: ResourceId,
blocked_actors: Vec<PublicKey>,
reason: impl Into<String>,
duration: Option<DurationMs>,
) -> Self {
Self::BlockResource {
resource,
blocked_actors,
reason: reason.into(),
duration,
}
}
pub fn global_pause(
reason: impl Into<String>,
duration: DurationMs,
exceptions: Vec<PublicKey>,
) -> Self {
Self::GlobalPause {
reason: reason.into(),
duration,
exceptions,
}
}
pub fn rollback_actions(agent: PublicKey, since: i64, reason: impl Into<String>) -> Self {
Self::RollbackActions {
agent,
since,
reason: reason.into(),
}
}
pub fn reason(&self) -> &str {
match self {
EmergencyAction::SuspendAgent { reason, .. } => reason,
EmergencyAction::RevokeAgent { reason, .. } => reason,
EmergencyAction::TerminateSession { reason, .. } => reason,
EmergencyAction::RevokeCapability { reason, .. } => reason,
EmergencyAction::BlockResource { reason, .. } => reason,
EmergencyAction::GlobalPause { reason, .. } => reason,
EmergencyAction::RollbackActions { reason, .. } => reason,
}
}
pub fn affects_agent(&self, agent: &PublicKey) -> bool {
match self {
EmergencyAction::SuspendAgent { agent: a, .. } => a == agent,
EmergencyAction::RevokeAgent { agent: a, .. } => a == agent,
EmergencyAction::BlockResource { blocked_actors, .. } => blocked_actors.contains(agent),
EmergencyAction::GlobalPause { exceptions, .. } => !exceptions.contains(agent),
EmergencyAction::RollbackActions { agent: a, .. } => a == agent,
_ => false,
}
}
pub fn is_permanent(&self) -> bool {
match self {
EmergencyAction::SuspendAgent { duration, .. } => duration.is_none(),
EmergencyAction::RevokeAgent { .. } => true,
EmergencyAction::TerminateSession { .. } => true,
EmergencyAction::RevokeCapability { .. } => true,
EmergencyAction::BlockResource { duration, .. } => duration.is_none(),
EmergencyAction::GlobalPause { .. } => false, EmergencyAction::RollbackActions { .. } => true,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum SuspensionScope {
Full,
Capabilities(Vec<CapabilityKind>),
Resources(Vec<ResourceId>),
}
impl Default for SuspensionScope {
fn default() -> Self {
Self::Full
}
}
impl SuspensionScope {
pub fn full() -> Self {
Self::Full
}
pub fn capabilities(capabilities: Vec<CapabilityKind>) -> Self {
Self::Capabilities(capabilities)
}
pub fn resources(resources: Vec<ResourceId>) -> Self {
Self::Resources(resources)
}
pub fn includes_capability(&self, capability: &CapabilityKind) -> bool {
match self {
SuspensionScope::Full => true,
SuspensionScope::Capabilities(caps) => caps.contains(capability),
SuspensionScope::Resources(_) => false,
}
}
pub fn includes_resource(&self, resource: &ResourceId) -> bool {
match self {
SuspensionScope::Full => true,
SuspensionScope::Capabilities(_) => false,
SuspensionScope::Resources(resources) => resources.contains(resource),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum EmergencyPriority {
Low,
Medium,
High,
Critical,
}
impl EmergencyPriority {
pub fn expected_response_ms(&self) -> i64 {
match self {
EmergencyPriority::Low => 60 * 60 * 1000, EmergencyPriority::Medium => 5 * 60 * 1000, EmergencyPriority::High => 60 * 1000, EmergencyPriority::Critical => 10 * 1000, }
}
}
impl std::fmt::Display for EmergencyPriority {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
EmergencyPriority::Low => write!(f, "low"),
EmergencyPriority::Medium => write!(f, "medium"),
EmergencyPriority::High => write!(f, "high"),
EmergencyPriority::Critical => write!(f, "critical"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EmergencyEvent {
action: EmergencyAction,
initiator: PrincipalId,
priority: EmergencyPriority,
trigger_evidence: Vec<EventId>,
declared_at: i64,
expected_resolution: Option<i64>,
notify: Vec<PrincipalId>,
}
impl EmergencyEvent {
pub fn builder() -> EmergencyEventBuilder {
EmergencyEventBuilder::new()
}
pub fn action(&self) -> &EmergencyAction {
&self.action
}
pub fn initiator(&self) -> &PrincipalId {
&self.initiator
}
pub fn priority(&self) -> EmergencyPriority {
self.priority
}
pub fn trigger_evidence(&self) -> &[EventId] {
&self.trigger_evidence
}
pub fn declared_at(&self) -> i64 {
self.declared_at
}
pub fn expected_resolution(&self) -> Option<i64> {
self.expected_resolution
}
pub fn notify(&self) -> &[PrincipalId] {
&self.notify
}
pub fn is_critical(&self) -> bool {
self.priority == EmergencyPriority::Critical
}
pub fn is_overdue(&self) -> bool {
let now = chrono::Utc::now().timestamp_millis();
let deadline = self.declared_at + self.priority.expected_response_ms();
now > deadline
}
}
#[derive(Debug, Default)]
pub struct EmergencyEventBuilder {
action: Option<EmergencyAction>,
initiator: Option<PrincipalId>,
priority: Option<EmergencyPriority>,
trigger_evidence: Vec<EventId>,
declared_at: Option<i64>,
expected_resolution: Option<i64>,
notify: Vec<PrincipalId>,
}
impl EmergencyEventBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn action(mut self, action: EmergencyAction) -> Self {
self.action = Some(action);
self
}
pub fn initiator(mut self, initiator: PrincipalId) -> Self {
self.initiator = Some(initiator);
self
}
pub fn priority(mut self, priority: EmergencyPriority) -> Self {
self.priority = Some(priority);
self
}
pub fn trigger_evidence(mut self, evidence: EventId) -> Self {
self.trigger_evidence.push(evidence);
self
}
pub fn declared_at(mut self, timestamp: i64) -> Self {
self.declared_at = Some(timestamp);
self
}
pub fn declared_now(mut self) -> Self {
self.declared_at = Some(chrono::Utc::now().timestamp_millis());
self
}
pub fn expected_resolution(mut self, timestamp: i64) -> Self {
self.expected_resolution = Some(timestamp);
self
}
pub fn notify(mut self, principal: PrincipalId) -> Self {
self.notify.push(principal);
self
}
pub fn build(self) -> Result<EmergencyEvent> {
let action = self
.action
.ok_or_else(|| Error::invalid_input("action is required"))?;
let initiator = self
.initiator
.ok_or_else(|| Error::invalid_input("initiator is required"))?;
let priority = self.priority.unwrap_or(EmergencyPriority::High);
let declared_at = self
.declared_at
.unwrap_or_else(|| chrono::Utc::now().timestamp_millis());
Ok(EmergencyEvent {
action,
initiator,
priority,
trigger_evidence: self.trigger_evidence,
declared_at,
expected_resolution: self.expected_resolution,
notify: self.notify,
})
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EmergencyResolution {
emergency_event_id: EventId,
resolution: Resolution,
resolver: PrincipalId,
resolved_at: i64,
post_mortem: Option<PostMortem>,
}
impl EmergencyResolution {
pub fn new(emergency_event_id: EventId, resolution: Resolution, resolver: PrincipalId) -> Self {
Self {
emergency_event_id,
resolution,
resolver,
resolved_at: chrono::Utc::now().timestamp_millis(),
post_mortem: None,
}
}
pub fn with_post_mortem(mut self, post_mortem: PostMortem) -> Self {
self.post_mortem = Some(post_mortem);
self
}
pub fn with_resolved_at(mut self, timestamp: i64) -> Self {
self.resolved_at = timestamp;
self
}
pub fn emergency_event_id(&self) -> EventId {
self.emergency_event_id
}
pub fn resolution(&self) -> &Resolution {
&self.resolution
}
pub fn resolver(&self) -> &PrincipalId {
&self.resolver
}
pub fn resolved_at(&self) -> i64 {
self.resolved_at
}
pub fn post_mortem(&self) -> Option<&PostMortem> {
self.post_mortem.as_ref()
}
pub fn is_false_alarm(&self) -> bool {
matches!(self.resolution, Resolution::FalseAlarm { .. })
}
pub fn has_active_restrictions(&self) -> bool {
matches!(self.resolution, Resolution::RestrictionsActive { .. })
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum Resolution {
FalseAlarm {
explanation: String,
},
Fixed {
fix_description: String,
},
AgentRemoved,
RestrictionsActive {
review_date: i64,
},
Escalated {
authority: String,
},
}
impl Resolution {
pub fn false_alarm(explanation: impl Into<String>) -> Self {
Self::FalseAlarm {
explanation: explanation.into(),
}
}
pub fn fixed(fix_description: impl Into<String>) -> Self {
Self::Fixed {
fix_description: fix_description.into(),
}
}
pub fn agent_removed() -> Self {
Self::AgentRemoved
}
pub fn restrictions_active(review_date: i64) -> Self {
Self::RestrictionsActive { review_date }
}
pub fn escalated(authority: impl Into<String>) -> Self {
Self::Escalated {
authority: authority.into(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PostMortem {
summary: String,
root_cause: String,
impact: String,
actions_taken: Vec<String>,
prevention: Vec<String>,
lessons: Vec<String>,
}
impl PostMortem {
pub fn new(
summary: impl Into<String>,
root_cause: impl Into<String>,
impact: impl Into<String>,
) -> Self {
Self {
summary: summary.into(),
root_cause: root_cause.into(),
impact: impact.into(),
actions_taken: Vec::new(),
prevention: Vec::new(),
lessons: Vec::new(),
}
}
pub fn with_action_taken(mut self, action: impl Into<String>) -> Self {
self.actions_taken.push(action.into());
self
}
pub fn with_prevention(mut self, measure: impl Into<String>) -> Self {
self.prevention.push(measure.into());
self
}
pub fn with_lesson(mut self, lesson: impl Into<String>) -> Self {
self.lessons.push(lesson.into());
self
}
pub fn summary(&self) -> &str {
&self.summary
}
pub fn root_cause(&self) -> &str {
&self.root_cause
}
pub fn impact(&self) -> &str {
&self.impact
}
pub fn actions_taken(&self) -> &[String] {
&self.actions_taken
}
pub fn prevention(&self) -> &[String] {
&self.prevention
}
pub fn lessons(&self) -> &[String] {
&self.lessons
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum EmergencyTrigger {
RateLimitViolation {
factor: f64,
},
AuthorizationViolation {
attempts: u32,
},
AttestationInvalid,
SessionViolation,
AnomalyDetected {
anomaly_type: String,
score: f64,
},
HumanReport {
reporter: PrincipalId,
},
ThreatIntelligence {
source: String,
threat_id: String,
},
}
impl EmergencyTrigger {
pub fn rate_limit_violation(factor: f64) -> Self {
Self::RateLimitViolation { factor }
}
pub fn authorization_violation(attempts: u32) -> Self {
Self::AuthorizationViolation { attempts }
}
pub fn attestation_invalid() -> Self {
Self::AttestationInvalid
}
pub fn session_violation() -> Self {
Self::SessionViolation
}
pub fn anomaly_detected(anomaly_type: impl Into<String>, score: f64) -> Self {
Self::AnomalyDetected {
anomaly_type: anomaly_type.into(),
score,
}
}
pub fn human_report(reporter: PrincipalId) -> Self {
Self::HumanReport { reporter }
}
pub fn threat_intelligence(source: impl Into<String>, threat_id: impl Into<String>) -> Self {
Self::ThreatIntelligence {
source: source.into(),
threat_id: threat_id.into(),
}
}
pub fn recommended_priority(&self) -> EmergencyPriority {
match self {
EmergencyTrigger::RateLimitViolation { factor } => {
if *factor >= 10.0 {
EmergencyPriority::Critical
} else if *factor >= 5.0 {
EmergencyPriority::High
} else {
EmergencyPriority::Medium
}
}
EmergencyTrigger::AuthorizationViolation { attempts } => {
if *attempts >= 10 {
EmergencyPriority::Critical
} else if *attempts >= 5 {
EmergencyPriority::High
} else {
EmergencyPriority::Medium
}
}
EmergencyTrigger::AttestationInvalid => EmergencyPriority::High,
EmergencyTrigger::SessionViolation => EmergencyPriority::High,
EmergencyTrigger::AnomalyDetected { score, .. } => {
if *score >= 0.9 {
EmergencyPriority::Critical
} else if *score >= 0.7 {
EmergencyPriority::High
} else {
EmergencyPriority::Medium
}
}
EmergencyTrigger::HumanReport { .. } => EmergencyPriority::High,
EmergencyTrigger::ThreatIntelligence { .. } => EmergencyPriority::Critical,
}
}
}
#[derive(Debug, Clone)]
pub struct SuspensionEntry {
pub agent: PublicKey,
pub scope: SuspensionScope,
pub suspended_at: i64,
pub expires_at: Option<i64>,
pub reason: String,
}
impl SuspensionEntry {
pub fn is_active(&self, now: i64) -> bool {
match self.expires_at {
None => true, Some(expires) => now < expires,
}
}
}
#[derive(Debug, Default)]
pub struct SuspensionRegistry {
suspensions: std::collections::HashMap<PublicKey, Vec<SuspensionEntry>>,
global_pause: Option<GlobalPauseState>,
}
#[derive(Debug, Clone)]
pub struct GlobalPauseState {
pub reason: String,
pub expires_at: i64,
pub exceptions: Vec<PublicKey>,
}
impl SuspensionRegistry {
pub fn new() -> Self {
Self::default()
}
pub fn suspend(
&mut self,
agent: PublicKey,
scope: SuspensionScope,
reason: String,
now: i64,
duration: Option<DurationMs>,
) {
let expires_at = duration.map(|d| now.saturating_add(d));
let entry = SuspensionEntry {
agent: agent.clone(),
scope,
suspended_at: now,
expires_at,
reason,
};
self.suspensions.entry(agent).or_default().push(entry);
}
pub fn global_pause(
&mut self,
reason: String,
duration_ms: DurationMs,
exceptions: Vec<PublicKey>,
now: i64,
) {
self.global_pause = Some(GlobalPauseState {
reason,
expires_at: now.saturating_add(duration_ms),
exceptions,
});
}
pub fn lift_suspension(&mut self, agent: &PublicKey) {
self.suspensions.remove(agent);
}
pub fn lift_global_pause(&mut self) {
self.global_pause = None;
}
pub fn check_agent(&self, agent: &PublicKey, now: i64) -> Result<()> {
if let Some(pause) = &self.global_pause {
if now < pause.expires_at && !pause.exceptions.contains(agent) {
return Err(Error::invalid_input(format!(
"global pause active: {}",
pause.reason
)));
}
}
if let Some(entries) = self.suspensions.get(agent) {
for entry in entries {
if entry.is_active(now) {
match &entry.scope {
SuspensionScope::Full => {
return Err(Error::invalid_input(format!(
"agent is fully suspended: {}",
entry.reason
)));
}
_ => {
}
}
}
}
}
Ok(())
}
pub fn check_capability(
&self,
agent: &PublicKey,
capability: &CapabilityKind,
now: i64,
) -> Result<()> {
if let Some(entries) = self.suspensions.get(agent) {
for entry in entries {
if entry.is_active(now) && entry.scope.includes_capability(capability) {
return Err(Error::invalid_input(format!(
"capability suspended for agent: {}",
entry.reason
)));
}
}
}
Ok(())
}
pub fn check_resource(&self, agent: &PublicKey, resource: &ResourceId, now: i64) -> Result<()> {
if let Some(entries) = self.suspensions.get(agent) {
for entry in entries {
if entry.is_active(now) && entry.scope.includes_resource(resource) {
return Err(Error::invalid_input(format!(
"resource access blocked for agent: {}",
entry.reason
)));
}
}
}
Ok(())
}
pub fn prune_expired(&mut self, now: i64) {
for entries in self.suspensions.values_mut() {
entries.retain(|e| e.is_active(now));
}
self.suspensions.retain(|_, entries| !entries.is_empty());
if let Some(pause) = &self.global_pause {
if now >= pause.expires_at {
self.global_pause = None;
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::crypto::{hash, SecretKey};
use crate::event::ResourceKind;
fn test_key() -> SecretKey {
SecretKey::generate()
}
fn test_event_id() -> EventId {
EventId(hash(b"test-event"))
}
fn test_principal() -> PrincipalId {
PrincipalId::user("admin@example.com").unwrap()
}
fn test_session_id() -> SessionId {
SessionId::random()
}
fn test_capability_id() -> CapabilityId {
CapabilityId::generate()
}
fn test_resource_id() -> ResourceId {
ResourceId::new(ResourceKind::File, "/tmp/test.txt")
}
#[test]
fn suspend_agent_action() {
let key = test_key();
let action = EmergencyAction::suspend_agent(
key.public_key(),
"Suspicious behavior",
Some(3600000),
SuspensionScope::Full,
);
assert_eq!(action.reason(), "Suspicious behavior");
assert!(action.affects_agent(&key.public_key()));
assert!(!action.is_permanent());
}
#[test]
fn revoke_agent_action() {
let key = test_key();
let action = EmergencyAction::revoke_agent(key.public_key(), "Malicious activity");
assert!(action.is_permanent());
}
#[test]
fn terminate_session_action() {
let action = EmergencyAction::terminate_session(test_session_id(), "Session compromised");
assert!(action.is_permanent());
}
#[test]
fn revoke_capability_action() {
let action = EmergencyAction::revoke_capability(test_capability_id(), "Capability abused");
assert!(action.is_permanent());
}
#[test]
fn block_resource_action() {
let key = test_key();
let action = EmergencyAction::block_resource(
test_resource_id(),
vec![key.public_key()],
"Resource at risk",
None,
);
assert!(action.is_permanent());
assert!(action.affects_agent(&key.public_key()));
}
#[test]
fn global_pause_action() {
let key = test_key();
let other_key = test_key();
let action = EmergencyAction::global_pause(
"System maintenance",
3600000,
vec![key.public_key()], );
assert!(!action.is_permanent());
assert!(!action.affects_agent(&key.public_key())); assert!(action.affects_agent(&other_key.public_key())); }
#[test]
fn rollback_actions_action() {
let key = test_key();
let action = EmergencyAction::rollback_actions(key.public_key(), 1000, "Undo damage");
assert!(action.is_permanent());
}
#[test]
fn suspension_scope_full() {
let scope = SuspensionScope::full();
assert!(scope.includes_capability(&CapabilityKind::Read));
assert!(scope.includes_resource(&test_resource_id()));
}
#[test]
fn suspension_scope_capabilities() {
let scope =
SuspensionScope::capabilities(vec![CapabilityKind::Read, CapabilityKind::Write]);
assert!(scope.includes_capability(&CapabilityKind::Read));
assert!(!scope.includes_capability(&CapabilityKind::Execute));
assert!(!scope.includes_resource(&test_resource_id()));
}
#[test]
fn suspension_scope_resources() {
let resource = test_resource_id();
let scope = SuspensionScope::resources(vec![resource.clone()]);
assert!(scope.includes_resource(&resource));
assert!(!scope.includes_capability(&CapabilityKind::Read));
}
#[test]
fn priority_ordering() {
assert!(EmergencyPriority::Low < EmergencyPriority::Medium);
assert!(EmergencyPriority::Medium < EmergencyPriority::High);
assert!(EmergencyPriority::High < EmergencyPriority::Critical);
}
#[test]
fn priority_response_times() {
assert!(
EmergencyPriority::Critical.expected_response_ms()
< EmergencyPriority::High.expected_response_ms()
);
assert!(
EmergencyPriority::High.expected_response_ms()
< EmergencyPriority::Medium.expected_response_ms()
);
assert!(
EmergencyPriority::Medium.expected_response_ms()
< EmergencyPriority::Low.expected_response_ms()
);
}
#[test]
fn emergency_event_build() {
let key = test_key();
let event = EmergencyEvent::builder()
.action(EmergencyAction::suspend_agent(
key.public_key(),
"Test",
None,
SuspensionScope::Full,
))
.initiator(test_principal())
.priority(EmergencyPriority::High)
.declared_now()
.build()
.unwrap();
assert_eq!(event.priority(), EmergencyPriority::High);
assert!(!event.is_critical());
}
#[test]
fn emergency_event_critical() {
let key = test_key();
let event = EmergencyEvent::builder()
.action(EmergencyAction::revoke_agent(key.public_key(), "Malicious"))
.initiator(test_principal())
.priority(EmergencyPriority::Critical)
.declared_now()
.build()
.unwrap();
assert!(event.is_critical());
}
#[test]
fn emergency_event_requires_action() {
let result = EmergencyEvent::builder()
.initiator(test_principal())
.build();
assert!(result.is_err());
}
#[test]
fn emergency_event_requires_initiator() {
let key = test_key();
let result = EmergencyEvent::builder()
.action(EmergencyAction::revoke_agent(key.public_key(), "Test"))
.build();
assert!(result.is_err());
}
#[test]
fn resolution_false_alarm() {
let resolution = EmergencyResolution::new(
test_event_id(),
Resolution::false_alarm("Misconfigured alert"),
test_principal(),
);
assert!(resolution.is_false_alarm());
assert!(!resolution.has_active_restrictions());
}
#[test]
fn resolution_fixed() {
let resolution = EmergencyResolution::new(
test_event_id(),
Resolution::fixed("Patched vulnerability"),
test_principal(),
);
assert!(!resolution.is_false_alarm());
}
#[test]
fn resolution_with_post_mortem() {
let post_mortem = PostMortem::new(
"Agent exceeded rate limits",
"Misconfigured retry logic",
"Minor service degradation",
)
.with_action_taken("Disabled agent")
.with_prevention("Add rate limiting at client level")
.with_lesson("Monitor retry patterns");
let resolution = EmergencyResolution::new(
test_event_id(),
Resolution::fixed("Fixed retry logic"),
test_principal(),
)
.with_post_mortem(post_mortem);
assert!(resolution.post_mortem().is_some());
let pm = resolution.post_mortem().unwrap();
assert_eq!(pm.actions_taken().len(), 1);
assert_eq!(pm.prevention().len(), 1);
assert_eq!(pm.lessons().len(), 1);
}
#[test]
fn resolution_restrictions_active() {
let review_date = chrono::Utc::now().timestamp_millis() + 86400000; let resolution = EmergencyResolution::new(
test_event_id(),
Resolution::restrictions_active(review_date),
test_principal(),
);
assert!(resolution.has_active_restrictions());
}
#[test]
fn trigger_rate_limit_priority() {
let low = EmergencyTrigger::rate_limit_violation(2.0);
assert_eq!(low.recommended_priority(), EmergencyPriority::Medium);
let high = EmergencyTrigger::rate_limit_violation(5.0);
assert_eq!(high.recommended_priority(), EmergencyPriority::High);
let critical = EmergencyTrigger::rate_limit_violation(10.0);
assert_eq!(critical.recommended_priority(), EmergencyPriority::Critical);
}
#[test]
fn trigger_authorization_violation_priority() {
let low = EmergencyTrigger::authorization_violation(2);
assert_eq!(low.recommended_priority(), EmergencyPriority::Medium);
let high = EmergencyTrigger::authorization_violation(5);
assert_eq!(high.recommended_priority(), EmergencyPriority::High);
let critical = EmergencyTrigger::authorization_violation(10);
assert_eq!(critical.recommended_priority(), EmergencyPriority::Critical);
}
#[test]
fn trigger_anomaly_priority() {
let medium = EmergencyTrigger::anomaly_detected("unusual_pattern", 0.5);
assert_eq!(medium.recommended_priority(), EmergencyPriority::Medium);
let high = EmergencyTrigger::anomaly_detected("unusual_pattern", 0.7);
assert_eq!(high.recommended_priority(), EmergencyPriority::High);
let critical = EmergencyTrigger::anomaly_detected("unusual_pattern", 0.9);
assert_eq!(critical.recommended_priority(), EmergencyPriority::Critical);
}
#[test]
fn trigger_threat_intelligence_always_critical() {
let trigger = EmergencyTrigger::threat_intelligence("threat-feed", "CVE-2024-1234");
assert_eq!(trigger.recommended_priority(), EmergencyPriority::Critical);
}
#[test]
fn trigger_human_report() {
let trigger = EmergencyTrigger::human_report(test_principal());
assert_eq!(trigger.recommended_priority(), EmergencyPriority::High);
}
#[test]
fn emergency_event_build_error_is_crate_error() {
let result = EmergencyEvent::builder()
.initiator(test_principal())
.build();
let err: crate::error::Error = result.unwrap_err();
assert!(err.to_string().contains("action"));
}
}