use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Hash, 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)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum Object {
Entity(EntityRef),
Value { value: String },
}
#[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, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Polarity {
Positive,
Negative,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Category {
Requirement,
Constraint,
Risk,
Context,
Decision,
}
impl Category {
pub fn is_intent(self) -> bool {
matches!(
self,
Category::Requirement | Category::Constraint | Category::Decision
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Modality {
Fact,
Goal,
Constraint,
Assumption,
Decision,
Risk,
Preference,
}
pub type DimensionTag = String;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct TimeScope {
pub condition: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Claim {
pub id: String,
pub session_id: String,
pub subject: EntityRef,
pub predicate: String,
pub object: Object,
pub polarity: Polarity,
pub category: Category,
pub modality: Modality,
#[serde(default)]
pub dimensions: Vec<DimensionTag>,
pub source: EvidenceRef,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub time_scope: Option<TimeScope>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub condition: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum HypothesisStanding {
Open,
Challenged,
Withstood,
Disproven,
}
impl HypothesisStanding {
pub fn rank(self) -> u8 {
match self {
HypothesisStanding::Disproven => 0,
HypothesisStanding::Challenged => 1,
HypothesisStanding::Open => 2,
HypothesisStanding::Withstood => 3,
}
}
pub fn is_withstood_or_better(self) -> bool {
self.rank() >= HypothesisStanding::Withstood.rank()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum EvidenceSourceType {
UserAssent,
UserRefutation,
Corroboration,
AppResearchSupport,
AppResearchRefutation,
}
impl EvidenceSourceType {
pub fn is_support(self) -> bool {
matches!(
self,
EvidenceSourceType::UserAssent
| EvidenceSourceType::Corroboration
| EvidenceSourceType::AppResearchSupport
)
}
pub fn is_refutation(self) -> bool {
matches!(
self,
EvidenceSourceType::UserRefutation | EvidenceSourceType::AppResearchRefutation
)
}
pub fn is_user_sourced(self) -> bool {
matches!(
self,
EvidenceSourceType::UserAssent | EvidenceSourceType::UserRefutation
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Severity {
Low,
Medium,
High,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ContradictionKind {
IntraSpec,
SpecVsReality,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Contradiction {
pub id: String,
pub kind: ContradictionKind,
pub claims: Vec<String>,
pub severity: Severity,
pub explanation: String,
pub suggested_question: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "move", rename_all = "snake_case")]
pub enum RemediationMove {
Supersede { old: String, new: String },
Retract { claim: String },
Qualify {
claim: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
condition: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
time_scope: Option<TimeScope>,
},
ReconcileByEvidence { contradiction: String },
Accept { target: String, rationale: String },
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RemediationEvent {
pub id: String,
pub session_id: String,
#[serde(flatten)]
pub r#move: RemediationMove,
pub source: EvidenceRef,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub detail: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RequirementQualityCheck {
pub name: String,
pub passed: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub detail: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Priority {
Must,
Should,
Could,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RequirementCandidate {
pub id: String,
pub statement: String,
pub subject: EntityRef,
pub capability: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub condition: Option<String>,
#[serde(default)]
pub acceptance_criteria: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub priority: Option<Priority>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub owner: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub rationale: Option<String>,
#[serde(default)]
pub source_claims: Vec<String>,
#[serde(default)]
pub quality_checks: Vec<RequirementQualityCheck>,
}
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
pub struct RequirementMeta {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub statement: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub capability: Option<String>,
#[serde(default)]
pub acceptance_criteria: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub priority: Option<Priority>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub owner: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub rationale: Option<String>,
}