use serde::{Deserialize, Serialize};
use crate::response::Scope;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct EntityRef {
pub id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub label: Option<String>,
}
impl EntityRef {
pub fn new(id: impl Into<String>) -> Self {
Self {
id: id.into(),
label: None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct EvidenceRef {
pub turn_id: String,
}
impl EvidenceRef {
pub fn new(turn_id: impl Into<String>) -> Self {
Self {
turn_id: turn_id.into(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Level {
Low,
Medium,
High,
}
impl Level {
pub fn rank(self) -> u8 {
match self {
Level::Low => 0,
Level::Medium => 1,
Level::High => 2,
}
}
}
pub type Criticality = Level;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Domain {
Ux,
Runtime,
Architecture,
Delivery,
Security,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum MitigationKind {
Prevention,
Detection,
FailFast,
}
impl MitigationKind {
pub fn rank(self) -> u8 {
match self {
MitigationKind::Prevention => 0,
MitigationKind::Detection => 1,
MitigationKind::FailFast => 2,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct FailureMode {
pub id: String,
pub session_id: String,
pub component: EntityRef,
pub description: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cause: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub effect: Option<String>,
#[serde(default)]
pub severity_observations: Vec<String>,
#[serde(default)]
pub probability_observations: Vec<String>,
pub domain: Domain,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub scope: Option<Scope>,
pub source: EvidenceRef,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub parent: Option<ParentRef>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub refs: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub local_effect: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub next_effect: Option<String>,
#[serde(default, skip_serializing_if = "is_false")]
pub single_point: bool,
}
fn is_false(b: &bool) -> bool {
!*b
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AnalysisLevel {
Concept,
Conops,
TechnicalPlan,
ProjectPlan,
Implementation,
Verification,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct SessionMeta {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub level: Option<AnalysisLevel>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub aspect: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub scope_paths: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub parent: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ParentRef {
pub session_id: String,
pub failure_mode_id: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum EvidenceBasis {
Design,
Test,
Inspection,
FailureHistory,
OperationalUse,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum Disposition {
#[default]
Open,
Accepted,
Excluded,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct DispositionChange {
pub session_id: String,
pub failure_mode_id: String,
pub disposition: Disposition,
pub rationale: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub approver: Option<String>,
#[serde(default)]
pub evidence: Vec<ResolutionEvidence>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub at: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum MitigationStatus {
#[default]
Planned,
Implemented,
Verified,
Abandoned,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum EvidenceKind {
Commit,
PullRequest,
Test,
File,
Url,
Note,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ResolutionEvidence {
pub kind: EvidenceKind,
pub reference: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub basis: Option<EvidenceBasis>,
}
impl ResolutionEvidence {
pub fn is_substantive(&self) -> bool {
self.kind != EvidenceKind::Note
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct MitigationStatusChange {
pub session_id: String,
pub mitigation_id: String,
pub status: MitigationStatus,
#[serde(default)]
pub evidence: Vec<ResolutionEvidence>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub note: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub at: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Mitigation {
pub id: String,
pub session_id: String,
pub failure_mode_id: String,
pub kind: MitigationKind,
pub description: String,
#[serde(default)]
pub residual_severity_observations: Vec<String>,
#[serde(default)]
pub residual_probability_observations: Vec<String>,
pub source: EvidenceRef,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Rescore {
pub failure_mode_id: String,
pub session_id: String,
#[serde(default)]
pub severity_observations: Vec<String>,
#[serde(default)]
pub probability_observations: Vec<String>,
pub source: EvidenceRef,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum FailureModeStanding {
Unmitigated,
UnderMitigated,
Acceptable,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum IssueType {
UnmitigatedHigh,
UnmitigatedMedium,
MissingCause,
MissingEffect,
MissingScore,
WeakMitigationOrder,
ResidualStillHigh,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Issue {
pub id: String,
pub r#type: IssueType,
pub failure_mode_id: String,
pub severity: Level,
pub explanation: String,
pub suggested_action: String,
}
#[cfg(test)]
mod domain_tests {
use super::Domain;
#[test]
fn security_domain_round_trips_as_snake_case() {
let d: Domain = serde_json::from_str("\"security\"").expect("security parses");
assert_eq!(d, Domain::Security);
assert_eq!(serde_json::to_string(&d).unwrap(), "\"security\"");
}
}