use std::fmt;
use std::str::FromStr;
fn normalized_preflight_token(input: &str) -> String {
let trimmed = input.trim();
let mut normalized = String::with_capacity(trimmed.len());
let mut previous_was_lowercase = false;
let mut previous_was_separator = false;
for character in trimmed.chars() {
match character {
'-' | '_' => {
if !normalized.is_empty() && !previous_was_separator {
normalized.push('_');
}
previous_was_lowercase = false;
previous_was_separator = true;
}
character if character.is_ascii_uppercase() => {
if previous_was_lowercase && !previous_was_separator {
normalized.push('_');
}
normalized.push(character.to_ascii_lowercase());
previous_was_lowercase = false;
previous_was_separator = false;
}
character => {
normalized.push(character.to_ascii_lowercase());
previous_was_lowercase = character.is_ascii_lowercase();
previous_was_separator = false;
}
}
}
normalized
}
pub const PREFLIGHT_RUN_SCHEMA_V1: &str = "ee.preflight_run.v1";
pub const RISK_BRIEF_SCHEMA_V1: &str = "ee.risk_brief.v1";
pub const TRIPWIRE_SCHEMA_V1: &str = "ee.tripwire.v1";
pub const TRIPWIRE_EVENT_SCHEMA_V1: &str = "ee.tripwire_event.v1";
pub const PREFLIGHT_RUN_ID_PREFIX: &str = "pf_";
pub const RISK_BRIEF_ID_PREFIX: &str = "rb_";
pub const TRIPWIRE_ID_PREFIX: &str = "tw_";
pub const TRIPWIRE_EVENT_ID_PREFIX: &str = "twe_";
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct PreflightRun {
pub schema: &'static str,
pub id: String,
pub workspace_id: Option<String>,
pub task_input: String,
pub status: PreflightStatus,
pub risk_brief_id: Option<String>,
pub tripwire_ids: Vec<String>,
pub risk_level: RiskLevel,
pub cleared: bool,
pub block_reason: Option<String>,
pub started_at: String,
pub completed_at: Option<String>,
pub duration_ms: Option<u64>,
}
impl PreflightRun {
#[must_use]
pub fn new(
id: impl Into<String>,
task_input: impl Into<String>,
started_at: impl Into<String>,
) -> Self {
Self {
schema: PREFLIGHT_RUN_SCHEMA_V1,
id: id.into(),
task_input: task_input.into(),
started_at: started_at.into(),
status: PreflightStatus::Running,
risk_level: RiskLevel::Unknown,
cleared: false,
..Default::default()
}
}
#[must_use]
pub fn with_workspace_id(mut self, id: impl Into<String>) -> Self {
self.workspace_id = Some(id.into());
self
}
#[must_use]
pub fn with_status(mut self, status: PreflightStatus) -> Self {
self.status = status;
self
}
#[must_use]
pub fn with_risk_brief_id(mut self, id: impl Into<String>) -> Self {
self.risk_brief_id = Some(id.into());
self
}
pub fn add_tripwire(&mut self, id: impl Into<String>) {
self.tripwire_ids.push(id.into());
}
#[must_use]
pub fn with_risk_level(mut self, level: RiskLevel) -> Self {
self.risk_level = level;
self
}
#[must_use]
pub fn cleared(mut self) -> Self {
self.cleared = true;
self.block_reason = None;
self
}
#[must_use]
pub fn blocked(mut self, reason: impl Into<String>) -> Self {
self.cleared = false;
self.block_reason = Some(reason.into());
self
}
#[must_use]
pub fn with_completed_at(mut self, ts: impl Into<String>) -> Self {
self.completed_at = Some(ts.into());
self
}
#[must_use]
pub fn with_duration_ms(mut self, ms: u64) -> Self {
self.duration_ms = Some(ms);
self
}
}
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
pub enum PreflightStatus {
#[default]
Running,
Completed,
Failed,
Cancelled,
Timeout,
}
impl PreflightStatus {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Running => "running",
Self::Completed => "completed",
Self::Failed => "failed",
Self::Cancelled => "cancelled",
Self::Timeout => "timeout",
}
}
}
impl fmt::Display for PreflightStatus {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl FromStr for PreflightStatus {
type Err = ParsePreflightStatusError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match normalized_preflight_token(s).as_str() {
"running" => Ok(Self::Running),
"completed" => Ok(Self::Completed),
"failed" => Ok(Self::Failed),
"cancelled" => Ok(Self::Cancelled),
"timeout" => Ok(Self::Timeout),
_ => Err(ParsePreflightStatusError {
input: s.to_owned(),
}),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ParsePreflightStatusError {
input: String,
}
impl fmt::Display for ParsePreflightStatusError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"unknown preflight status `{}`; expected running, completed, failed, cancelled, or timeout",
self.input
)
}
}
impl std::error::Error for ParsePreflightStatusError {}
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum RiskLevel {
None,
Low,
Medium,
High,
Critical,
#[default]
Unknown,
}
impl RiskLevel {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::None => "none",
Self::Low => "low",
Self::Medium => "medium",
Self::High => "high",
Self::Critical => "critical",
Self::Unknown => "unknown",
}
}
#[must_use]
pub const fn should_block(self) -> bool {
matches!(self, Self::Critical)
}
#[must_use]
pub const fn requires_confirmation(self) -> bool {
matches!(self, Self::High | Self::Critical)
}
}
impl fmt::Display for RiskLevel {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl FromStr for RiskLevel {
type Err = ParseRiskLevelError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match normalized_preflight_token(s).as_str() {
"none" => Ok(Self::None),
"low" => Ok(Self::Low),
"medium" => Ok(Self::Medium),
"high" => Ok(Self::High),
"critical" => Ok(Self::Critical),
"unknown" => Ok(Self::Unknown),
_ => Err(ParseRiskLevelError {
input: s.to_owned(),
}),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ParseRiskLevelError {
input: String,
}
impl fmt::Display for ParseRiskLevelError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"unknown risk level `{}`; expected none, low, medium, high, critical, or unknown",
self.input
)
}
}
impl std::error::Error for ParseRiskLevelError {}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct RiskBrief {
pub schema: &'static str,
pub id: String,
pub preflight_run_id: String,
pub risk_level: RiskLevel,
pub risks: Vec<RiskItem>,
pub recommendations: Vec<String>,
pub required_permissions: Vec<String>,
pub side_effects: Vec<String>,
pub summary: Option<String>,
pub generated_at: String,
}
impl RiskBrief {
#[must_use]
pub fn new(
id: impl Into<String>,
preflight_run_id: impl Into<String>,
risk_level: RiskLevel,
generated_at: impl Into<String>,
) -> Self {
Self {
schema: RISK_BRIEF_SCHEMA_V1,
id: id.into(),
preflight_run_id: preflight_run_id.into(),
risk_level,
generated_at: generated_at.into(),
..Default::default()
}
}
pub fn add_risk(&mut self, risk: RiskItem) {
self.risks.push(risk);
}
pub fn add_recommendation(&mut self, rec: impl Into<String>) {
self.recommendations.push(rec.into());
}
pub fn add_required_permission(&mut self, perm: impl Into<String>) {
self.required_permissions.push(perm.into());
}
pub fn add_side_effect(&mut self, effect: impl Into<String>) {
self.side_effects.push(effect.into());
}
#[must_use]
pub fn with_summary(mut self, summary: impl Into<String>) -> Self {
self.summary = Some(summary.into());
self
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct RiskItem {
pub category: RiskCategory,
pub level: RiskLevel,
pub description: String,
pub mitigation: Option<String>,
pub source: Option<String>,
}
impl RiskItem {
#[must_use]
pub fn new(category: RiskCategory, level: RiskLevel, description: impl Into<String>) -> Self {
Self {
category,
level,
description: description.into(),
mitigation: None,
source: None,
}
}
#[must_use]
pub fn with_mitigation(mut self, mitigation: impl Into<String>) -> Self {
self.mitigation = Some(mitigation.into());
self
}
#[must_use]
pub fn with_source(mut self, source: impl Into<String>) -> Self {
self.source = Some(source.into());
self
}
}
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
pub enum RiskCategory {
DataLoss,
Security,
Stability,
Performance,
ExternalService,
Compliance,
Reversibility,
ResourceExhaustion,
#[default]
Other,
}
impl RiskCategory {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::DataLoss => "data_loss",
Self::Security => "security",
Self::Stability => "stability",
Self::Performance => "performance",
Self::ExternalService => "external_service",
Self::Compliance => "compliance",
Self::Reversibility => "reversibility",
Self::ResourceExhaustion => "resource_exhaustion",
Self::Other => "other",
}
}
}
impl fmt::Display for RiskCategory {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl FromStr for RiskCategory {
type Err = ParseRiskCategoryError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match normalized_preflight_token(s).as_str() {
"data_loss" => Ok(Self::DataLoss),
"security" => Ok(Self::Security),
"stability" => Ok(Self::Stability),
"performance" => Ok(Self::Performance),
"external_service" => Ok(Self::ExternalService),
"compliance" => Ok(Self::Compliance),
"reversibility" => Ok(Self::Reversibility),
"resource_exhaustion" => Ok(Self::ResourceExhaustion),
"other" => Ok(Self::Other),
_ => Err(ParseRiskCategoryError {
input: s.to_owned(),
}),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ParseRiskCategoryError {
input: String,
}
impl fmt::Display for ParseRiskCategoryError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"unknown risk category `{}`; expected data_loss, security, stability, performance, external_service, compliance, reversibility, resource_exhaustion, or other",
self.input
)
}
}
impl std::error::Error for ParseRiskCategoryError {}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Tripwire {
pub schema: &'static str,
pub id: String,
pub preflight_run_id: String,
pub tripwire_type: TripwireType,
pub condition: String,
pub action: TripwireAction,
pub state: TripwireState,
pub message: Option<String>,
pub created_at: String,
pub last_checked_at: Option<String>,
pub triggered_at: Option<String>,
}
impl Tripwire {
#[must_use]
pub fn new(
id: impl Into<String>,
preflight_run_id: impl Into<String>,
tripwire_type: TripwireType,
condition: impl Into<String>,
action: TripwireAction,
created_at: impl Into<String>,
) -> Self {
Self {
schema: TRIPWIRE_SCHEMA_V1,
id: id.into(),
preflight_run_id: preflight_run_id.into(),
tripwire_type,
condition: condition.into(),
action,
state: TripwireState::Armed,
message: None,
created_at: created_at.into(),
last_checked_at: None,
triggered_at: None,
}
}
#[must_use]
pub fn with_message(mut self, msg: impl Into<String>) -> Self {
self.message = Some(msg.into());
self
}
#[must_use]
pub fn triggered(mut self, at: impl Into<String>) -> Self {
self.state = TripwireState::Triggered;
self.triggered_at = Some(at.into());
self
}
#[must_use]
pub fn checked(mut self, at: impl Into<String>) -> Self {
self.last_checked_at = Some(at.into());
self
}
#[must_use]
pub fn disarmed(mut self) -> Self {
self.state = TripwireState::Disarmed;
self
}
}
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
pub enum TripwireType {
FileChange,
ResourceThreshold,
TimeLimit,
ErrorThreshold,
ServiceHealth,
#[default]
Custom,
}
impl TripwireType {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::FileChange => "file_change",
Self::ResourceThreshold => "resource_threshold",
Self::TimeLimit => "time_limit",
Self::ErrorThreshold => "error_threshold",
Self::ServiceHealth => "service_health",
Self::Custom => "custom",
}
}
}
impl fmt::Display for TripwireType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl FromStr for TripwireType {
type Err = ParseTripwireTypeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match normalized_preflight_token(s).as_str() {
"file_change" => Ok(Self::FileChange),
"resource_threshold" => Ok(Self::ResourceThreshold),
"time_limit" => Ok(Self::TimeLimit),
"error_threshold" => Ok(Self::ErrorThreshold),
"service_health" => Ok(Self::ServiceHealth),
"custom" => Ok(Self::Custom),
_ => Err(ParseTripwireTypeError {
input: s.to_owned(),
}),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ParseTripwireTypeError {
input: String,
}
impl fmt::Display for ParseTripwireTypeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"unknown tripwire type `{}`; expected file_change, resource_threshold, time_limit, error_threshold, service_health, or custom",
self.input
)
}
}
impl std::error::Error for ParseTripwireTypeError {}
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
pub enum TripwireAction {
Halt,
Pause,
#[default]
Warn,
Audit,
}
impl TripwireAction {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Halt => "halt",
Self::Pause => "pause",
Self::Warn => "warn",
Self::Audit => "audit",
}
}
#[must_use]
pub const fn stops_execution(self) -> bool {
matches!(self, Self::Halt | Self::Pause)
}
}
impl fmt::Display for TripwireAction {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl FromStr for TripwireAction {
type Err = ParseTripwireActionError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match normalized_preflight_token(s).as_str() {
"halt" => Ok(Self::Halt),
"pause" => Ok(Self::Pause),
"warn" => Ok(Self::Warn),
"audit" => Ok(Self::Audit),
_ => Err(ParseTripwireActionError {
input: s.to_owned(),
}),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ParseTripwireActionError {
input: String,
}
impl fmt::Display for ParseTripwireActionError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"unknown tripwire action `{}`; expected halt, pause, warn, or audit",
self.input
)
}
}
impl std::error::Error for ParseTripwireActionError {}
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
pub enum TripwireState {
#[default]
Armed,
Triggered,
Disarmed,
Error,
}
impl TripwireState {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Armed => "armed",
Self::Triggered => "triggered",
Self::Disarmed => "disarmed",
Self::Error => "error",
}
}
}
impl fmt::Display for TripwireState {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl FromStr for TripwireState {
type Err = ParseTripwireStateError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match normalized_preflight_token(s).as_str() {
"armed" => Ok(Self::Armed),
"triggered" => Ok(Self::Triggered),
"disarmed" => Ok(Self::Disarmed),
"error" => Ok(Self::Error),
_ => Err(ParseTripwireStateError {
input: s.to_owned(),
}),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ParseTripwireStateError {
input: String,
}
impl fmt::Display for ParseTripwireStateError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"unknown tripwire state `{}`; expected armed, triggered, disarmed, or error",
self.input
)
}
}
impl std::error::Error for ParseTripwireStateError {}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct TripwireEvent {
pub schema: &'static str,
pub id: String,
pub tripwire_id: String,
pub event_type: TripwireEventType,
pub previous_state: TripwireState,
pub new_state: TripwireState,
pub details: Option<String>,
pub timestamp: String,
}
impl TripwireEvent {
#[must_use]
pub fn new(
id: impl Into<String>,
tripwire_id: impl Into<String>,
event_type: TripwireEventType,
previous_state: TripwireState,
new_state: TripwireState,
timestamp: impl Into<String>,
) -> Self {
Self {
schema: TRIPWIRE_EVENT_SCHEMA_V1,
id: id.into(),
tripwire_id: tripwire_id.into(),
event_type,
previous_state,
new_state,
details: None,
timestamp: timestamp.into(),
}
}
#[must_use]
pub fn with_details(mut self, details: impl Into<String>) -> Self {
self.details = Some(details.into());
self
}
}
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
pub enum TripwireEventType {
Armed,
Checked,
#[default]
Triggered,
Disarmed,
Error,
Reset,
}
impl TripwireEventType {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Armed => "armed",
Self::Checked => "checked",
Self::Triggered => "triggered",
Self::Disarmed => "disarmed",
Self::Error => "error",
Self::Reset => "reset",
}
}
}
impl fmt::Display for TripwireEventType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl FromStr for TripwireEventType {
type Err = ParseTripwireEventTypeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match normalized_preflight_token(s).as_str() {
"armed" => Ok(Self::Armed),
"checked" => Ok(Self::Checked),
"triggered" => Ok(Self::Triggered),
"disarmed" => Ok(Self::Disarmed),
"error" => Ok(Self::Error),
"reset" => Ok(Self::Reset),
_ => Err(ParseTripwireEventTypeError {
input: s.to_owned(),
}),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ParseTripwireEventTypeError {
input: String,
}
impl fmt::Display for ParseTripwireEventTypeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"unknown tripwire event type `{}`; expected armed, checked, triggered, disarmed, error, or reset",
self.input
)
}
}
impl std::error::Error for ParseTripwireEventTypeError {}
#[cfg(test)]
mod tests {
use super::*;
type TestResult = Result<(), String>;
fn ensure<T: std::fmt::Debug + PartialEq>(actual: T, expected: T, ctx: &str) -> TestResult {
if actual == expected {
Ok(())
} else {
Err(format!("{ctx}: expected {expected:?}, got {actual:?}"))
}
}
#[test]
fn preflight_schema_versions_are_stable() -> TestResult {
ensure(PREFLIGHT_RUN_SCHEMA_V1, "ee.preflight_run.v1", "run")?;
ensure(RISK_BRIEF_SCHEMA_V1, "ee.risk_brief.v1", "brief")?;
ensure(TRIPWIRE_SCHEMA_V1, "ee.tripwire.v1", "tripwire")?;
ensure(TRIPWIRE_EVENT_SCHEMA_V1, "ee.tripwire_event.v1", "event")
}
#[test]
fn preflight_run_builder() -> TestResult {
let mut run = PreflightRun::new("pf_001", "Deploy to production", "2026-04-30T12:00:00Z")
.with_workspace_id("ws_001")
.with_status(PreflightStatus::Completed)
.with_risk_brief_id("rb_001")
.with_risk_level(RiskLevel::High)
.cleared()
.with_completed_at("2026-04-30T12:01:00Z")
.with_duration_ms(60000);
run.add_tripwire("tw_001");
ensure(run.schema, PREFLIGHT_RUN_SCHEMA_V1, "schema")?;
ensure(run.status, PreflightStatus::Completed, "status")?;
ensure(run.risk_level, RiskLevel::High, "risk")?;
ensure(run.cleared, true, "cleared")?;
ensure(run.tripwire_ids.len(), 1, "tripwires")
}
#[test]
fn preflight_run_blocked() -> TestResult {
let run = PreflightRun::new("pf_002", "Delete database", "2026-04-30T12:00:00Z")
.with_risk_level(RiskLevel::Critical)
.blocked("Critical risk: data loss");
ensure(run.cleared, false, "cleared")?;
ensure(
run.block_reason,
Some("Critical risk: data loss".to_string()),
"reason",
)
}
#[test]
fn preflight_run_cleared_removes_prior_block_reason() -> TestResult {
let run = PreflightRun::new("pf_003", "Retry task", "2026-04-30T12:00:00Z")
.blocked("Waiting on explicit confirmation")
.cleared();
ensure(run.cleared, true, "cleared")?;
ensure(run.block_reason, None, "block reason")
}
#[test]
fn preflight_status_strings_are_stable() -> TestResult {
ensure(PreflightStatus::Running.as_str(), "running", "running")?;
ensure(
PreflightStatus::Completed.as_str(),
"completed",
"completed",
)?;
ensure(PreflightStatus::Failed.as_str(), "failed", "failed")?;
ensure(
PreflightStatus::Cancelled.as_str(),
"cancelled",
"cancelled",
)?;
ensure(PreflightStatus::Timeout.as_str(), "timeout", "timeout")
}
#[test]
fn preflight_status_round_trip() -> TestResult {
for s in [
PreflightStatus::Running,
PreflightStatus::Completed,
PreflightStatus::Failed,
PreflightStatus::Cancelled,
PreflightStatus::Timeout,
] {
let parsed = PreflightStatus::from_str(s.as_str());
ensure(parsed, Ok(s), s.as_str())?;
}
Ok(())
}
#[test]
fn risk_level_strings_are_stable() -> TestResult {
ensure(RiskLevel::None.as_str(), "none", "none")?;
ensure(RiskLevel::Low.as_str(), "low", "low")?;
ensure(RiskLevel::Medium.as_str(), "medium", "medium")?;
ensure(RiskLevel::High.as_str(), "high", "high")?;
ensure(RiskLevel::Critical.as_str(), "critical", "critical")?;
ensure(RiskLevel::Unknown.as_str(), "unknown", "unknown")
}
#[test]
fn risk_level_round_trip() -> TestResult {
for r in [
RiskLevel::None,
RiskLevel::Low,
RiskLevel::Medium,
RiskLevel::High,
RiskLevel::Critical,
RiskLevel::Unknown,
] {
let parsed = RiskLevel::from_str(r.as_str());
ensure(parsed, Ok(r), r.as_str())?;
}
Ok(())
}
#[test]
fn risk_level_blocking_behavior() -> TestResult {
ensure(RiskLevel::None.should_block(), false, "none")?;
ensure(RiskLevel::Low.should_block(), false, "low")?;
ensure(RiskLevel::Medium.should_block(), false, "medium")?;
ensure(RiskLevel::High.should_block(), false, "high")?;
ensure(RiskLevel::Critical.should_block(), true, "critical")
}
#[test]
fn risk_level_confirmation_behavior() -> TestResult {
ensure(RiskLevel::None.requires_confirmation(), false, "none")?;
ensure(RiskLevel::Low.requires_confirmation(), false, "low")?;
ensure(RiskLevel::Medium.requires_confirmation(), false, "medium")?;
ensure(RiskLevel::High.requires_confirmation(), true, "high")?;
ensure(
RiskLevel::Critical.requires_confirmation(),
true,
"critical",
)
}
#[test]
fn risk_brief_builder() -> TestResult {
let mut brief = RiskBrief::new(
"rb_001",
"pf_001",
RiskLevel::Medium,
"2026-04-30T12:00:00Z",
)
.with_summary("Moderate deployment risk");
brief.add_risk(RiskItem::new(
RiskCategory::DataLoss,
RiskLevel::Low,
"Potential data loss in migration",
));
brief.add_recommendation("Run backup first");
brief.add_required_permission("write:database");
brief.add_side_effect("Database downtime");
ensure(brief.schema, RISK_BRIEF_SCHEMA_V1, "schema")?;
ensure(brief.risks.len(), 1, "risks")?;
ensure(brief.recommendations.len(), 1, "recommendations")?;
ensure(brief.required_permissions.len(), 1, "permissions")?;
ensure(brief.side_effects.len(), 1, "effects")
}
#[test]
fn risk_item_builder() -> TestResult {
let item = RiskItem::new(
RiskCategory::Security,
RiskLevel::High,
"SQL injection risk",
)
.with_mitigation("Use parameterized queries")
.with_source("static analysis");
ensure(item.category, RiskCategory::Security, "category")?;
ensure(item.level, RiskLevel::High, "level")?;
ensure(
item.mitigation,
Some("Use parameterized queries".to_string()),
"mitigation",
)
}
#[test]
fn risk_category_strings_are_stable() -> TestResult {
ensure(RiskCategory::DataLoss.as_str(), "data_loss", "data_loss")?;
ensure(RiskCategory::Security.as_str(), "security", "security")?;
ensure(RiskCategory::Stability.as_str(), "stability", "stability")?;
ensure(
RiskCategory::Performance.as_str(),
"performance",
"performance",
)?;
ensure(
RiskCategory::ExternalService.as_str(),
"external_service",
"external",
)?;
ensure(
RiskCategory::Compliance.as_str(),
"compliance",
"compliance",
)?;
ensure(
RiskCategory::Reversibility.as_str(),
"reversibility",
"reversibility",
)?;
ensure(
RiskCategory::ResourceExhaustion.as_str(),
"resource_exhaustion",
"resource",
)?;
ensure(RiskCategory::Other.as_str(), "other", "other")
}
#[test]
fn risk_category_round_trip() -> TestResult {
for c in [
RiskCategory::DataLoss,
RiskCategory::Security,
RiskCategory::Stability,
RiskCategory::Performance,
RiskCategory::ExternalService,
RiskCategory::Compliance,
RiskCategory::Reversibility,
RiskCategory::ResourceExhaustion,
RiskCategory::Other,
] {
let parsed = RiskCategory::from_str(c.as_str());
ensure(parsed, Ok(c), c.as_str())?;
}
Ok(())
}
#[test]
fn tripwire_builder() -> TestResult {
let tw = Tripwire::new(
"tw_001",
"pf_001",
TripwireType::ResourceThreshold,
"memory_mb < 1000",
TripwireAction::Halt,
"2026-04-30T12:00:00Z",
)
.with_message("Memory threshold exceeded")
.checked("2026-04-30T12:05:00Z");
ensure(tw.schema, TRIPWIRE_SCHEMA_V1, "schema")?;
ensure(tw.tripwire_type, TripwireType::ResourceThreshold, "type")?;
ensure(tw.action, TripwireAction::Halt, "action")?;
ensure(tw.state, TripwireState::Armed, "state")
}
#[test]
fn tripwire_triggered() -> TestResult {
let tw = Tripwire::new(
"tw_002",
"pf_001",
TripwireType::TimeLimit,
"elapsed_ms < 60000",
TripwireAction::Warn,
"2026-04-30T12:00:00Z",
)
.triggered("2026-04-30T12:01:00Z");
ensure(tw.state, TripwireState::Triggered, "state")?;
ensure(
tw.triggered_at,
Some("2026-04-30T12:01:00Z".to_string()),
"triggered_at",
)
}
#[test]
fn tripwire_type_strings_are_stable() -> TestResult {
ensure(TripwireType::FileChange.as_str(), "file_change", "file")?;
ensure(
TripwireType::ResourceThreshold.as_str(),
"resource_threshold",
"resource",
)?;
ensure(TripwireType::TimeLimit.as_str(), "time_limit", "time")?;
ensure(
TripwireType::ErrorThreshold.as_str(),
"error_threshold",
"error",
)?;
ensure(
TripwireType::ServiceHealth.as_str(),
"service_health",
"service",
)?;
ensure(TripwireType::Custom.as_str(), "custom", "custom")
}
#[test]
fn tripwire_type_round_trip() -> TestResult {
for t in [
TripwireType::FileChange,
TripwireType::ResourceThreshold,
TripwireType::TimeLimit,
TripwireType::ErrorThreshold,
TripwireType::ServiceHealth,
TripwireType::Custom,
] {
let parsed = TripwireType::from_str(t.as_str());
ensure(parsed, Ok(t), t.as_str())?;
}
Ok(())
}
#[test]
fn tripwire_action_strings_are_stable() -> TestResult {
ensure(TripwireAction::Halt.as_str(), "halt", "halt")?;
ensure(TripwireAction::Pause.as_str(), "pause", "pause")?;
ensure(TripwireAction::Warn.as_str(), "warn", "warn")?;
ensure(TripwireAction::Audit.as_str(), "audit", "audit")
}
#[test]
fn tripwire_action_round_trip() -> TestResult {
for a in [
TripwireAction::Halt,
TripwireAction::Pause,
TripwireAction::Warn,
TripwireAction::Audit,
] {
let parsed = TripwireAction::from_str(a.as_str());
ensure(parsed, Ok(a), a.as_str())?;
}
Ok(())
}
#[test]
fn tripwire_action_stops_execution() -> TestResult {
ensure(TripwireAction::Halt.stops_execution(), true, "halt")?;
ensure(TripwireAction::Pause.stops_execution(), true, "pause")?;
ensure(TripwireAction::Warn.stops_execution(), false, "warn")?;
ensure(TripwireAction::Audit.stops_execution(), false, "audit")
}
#[test]
fn tripwire_state_strings_are_stable() -> TestResult {
ensure(TripwireState::Armed.as_str(), "armed", "armed")?;
ensure(TripwireState::Triggered.as_str(), "triggered", "triggered")?;
ensure(TripwireState::Disarmed.as_str(), "disarmed", "disarmed")?;
ensure(TripwireState::Error.as_str(), "error", "error")
}
#[test]
fn tripwire_state_round_trip() -> TestResult {
for s in [
TripwireState::Armed,
TripwireState::Triggered,
TripwireState::Disarmed,
TripwireState::Error,
] {
let parsed = TripwireState::from_str(s.as_str());
ensure(parsed, Ok(s), s.as_str())?;
}
Ok(())
}
#[test]
fn tripwire_event_builder() -> TestResult {
let event = TripwireEvent::new(
"twe_001",
"tw_001",
TripwireEventType::Triggered,
TripwireState::Armed,
TripwireState::Triggered,
"2026-04-30T12:01:00Z",
)
.with_details("Memory usage exceeded 1GB");
ensure(event.schema, TRIPWIRE_EVENT_SCHEMA_V1, "schema")?;
ensure(event.event_type, TripwireEventType::Triggered, "type")?;
ensure(event.previous_state, TripwireState::Armed, "prev")?;
ensure(event.new_state, TripwireState::Triggered, "new")
}
#[test]
fn tripwire_event_type_strings_are_stable() -> TestResult {
ensure(TripwireEventType::Armed.as_str(), "armed", "armed")?;
ensure(TripwireEventType::Checked.as_str(), "checked", "checked")?;
ensure(
TripwireEventType::Triggered.as_str(),
"triggered",
"triggered",
)?;
ensure(TripwireEventType::Disarmed.as_str(), "disarmed", "disarmed")?;
ensure(TripwireEventType::Error.as_str(), "error", "error")?;
ensure(TripwireEventType::Reset.as_str(), "reset", "reset")
}
#[test]
fn tripwire_event_type_round_trip() -> TestResult {
for e in [
TripwireEventType::Armed,
TripwireEventType::Checked,
TripwireEventType::Triggered,
TripwireEventType::Disarmed,
TripwireEventType::Error,
TripwireEventType::Reset,
] {
let parsed = TripwireEventType::from_str(e.as_str());
ensure(parsed, Ok(e), e.as_str())?;
}
Ok(())
}
#[test]
fn preflight_enums_accept_operator_spelling_variants() -> TestResult {
ensure(
PreflightStatus::from_str(" Completed "),
Ok(PreflightStatus::Completed),
"status alias",
)?;
ensure(
RiskLevel::from_str("CRITICAL"),
Ok(RiskLevel::Critical),
"risk level alias",
)?;
ensure(
RiskCategory::from_str("data-loss"),
Ok(RiskCategory::DataLoss),
"risk category alias",
)?;
ensure(
RiskCategory::from_str("ExternalService"),
Ok(RiskCategory::ExternalService),
"risk category PascalCase alias",
)?;
ensure(
RiskCategory::from_str("resourceExhaustion"),
Ok(RiskCategory::ResourceExhaustion),
"risk category camelCase alias",
)?;
ensure(
TripwireType::from_str("RESOURCE-THRESHOLD"),
Ok(TripwireType::ResourceThreshold),
"tripwire type alias",
)?;
ensure(
TripwireType::from_str("fileChange"),
Ok(TripwireType::FileChange),
"tripwire type camelCase alias",
)?;
ensure(
TripwireType::from_str("ServiceHealth"),
Ok(TripwireType::ServiceHealth),
"tripwire type PascalCase alias",
)?;
ensure(
TripwireAction::from_str(" Warn "),
Ok(TripwireAction::Warn),
"tripwire action alias",
)?;
ensure(
TripwireState::from_str("DISARMED"),
Ok(TripwireState::Disarmed),
"tripwire state alias",
)?;
ensure(
TripwireEventType::from_str(" Triggered "),
Ok(TripwireEventType::Triggered),
"tripwire event alias",
)
}
}