1use serde::{Deserialize, Serialize};
8
9#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
13pub struct EntityRef {
14 pub id: String,
16 #[serde(default, skip_serializing_if = "Option::is_none")]
18 pub label: Option<String>,
19}
20
21impl EntityRef {
22 pub fn new(id: impl Into<String>) -> Self {
24 Self {
25 id: id.into(),
26 label: None,
27 }
28 }
29}
30
31#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
34#[serde(tag = "kind", rename_all = "snake_case")]
35pub enum Object {
36 Entity(EntityRef),
38 Value { value: String },
40}
41
42#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
45pub struct EvidenceRef {
46 pub turn_id: String,
48}
49
50impl EvidenceRef {
51 pub fn new(turn_id: impl Into<String>) -> Self {
53 Self {
54 turn_id: turn_id.into(),
55 }
56 }
57}
58
59#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
61#[serde(rename_all = "snake_case")]
62pub enum Polarity {
63 Positive,
64 Negative,
65}
66
67#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
69#[serde(rename_all = "snake_case")]
70pub enum Category {
71 Requirement,
72 Constraint,
73 Risk,
74 Context,
75 Decision,
76}
77
78impl Category {
79 pub fn is_intent(self) -> bool {
89 matches!(
90 self,
91 Category::Requirement | Category::Constraint | Category::Decision
92 )
93 }
94}
95
96#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
98#[serde(rename_all = "snake_case")]
99pub enum Modality {
100 Fact,
101 Goal,
102 Constraint,
103 Assumption,
104 Decision,
105 Risk,
106 Preference,
107}
108
109pub type DimensionTag = String;
112
113#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
117pub struct TimeScope {
118 pub condition: String,
120}
121
122#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
125pub struct Claim {
126 pub id: String,
127 pub session_id: String,
128 pub subject: EntityRef,
129 pub predicate: String,
131 pub object: Object,
132 pub polarity: Polarity,
133 pub category: Category,
134 pub modality: Modality,
135 #[serde(default)]
136 pub dimensions: Vec<DimensionTag>,
137 pub source: EvidenceRef,
138 #[serde(default, skip_serializing_if = "Option::is_none")]
139 pub time_scope: Option<TimeScope>,
140 #[serde(default, skip_serializing_if = "Option::is_none")]
144 pub condition: Option<String>,
145}
146
147#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
149#[serde(rename_all = "snake_case")]
150pub enum HypothesisStanding {
151 Open,
153 Challenged,
155 Withstood,
157 Disproven,
159}
160
161impl HypothesisStanding {
162 pub fn rank(self) -> u8 {
165 match self {
166 HypothesisStanding::Disproven => 0,
167 HypothesisStanding::Challenged => 1,
168 HypothesisStanding::Open => 2,
169 HypothesisStanding::Withstood => 3,
170 }
171 }
172
173 pub fn is_withstood_or_better(self) -> bool {
175 self.rank() >= HypothesisStanding::Withstood.rank()
176 }
177}
178
179#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
182#[serde(rename_all = "snake_case")]
183pub enum EvidenceSourceType {
184 UserAssent,
186 UserRefutation,
188 Corroboration,
190 AppResearchSupport,
192 AppResearchRefutation,
194}
195
196impl EvidenceSourceType {
197 pub fn is_support(self) -> bool {
199 matches!(
200 self,
201 EvidenceSourceType::UserAssent
202 | EvidenceSourceType::Corroboration
203 | EvidenceSourceType::AppResearchSupport
204 )
205 }
206
207 pub fn is_refutation(self) -> bool {
209 matches!(
210 self,
211 EvidenceSourceType::UserRefutation | EvidenceSourceType::AppResearchRefutation
212 )
213 }
214
215 pub fn is_user_sourced(self) -> bool {
218 matches!(
219 self,
220 EvidenceSourceType::UserAssent | EvidenceSourceType::UserRefutation
221 )
222 }
223}
224
225#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
227#[serde(rename_all = "snake_case")]
228pub enum Severity {
229 Low,
230 Medium,
231 High,
232}
233
234#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
236#[serde(rename_all = "snake_case")]
237pub enum ContradictionKind {
238 IntraSpec,
240 SpecVsReality,
242}
243
244#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
247pub struct Contradiction {
248 pub id: String,
249 pub kind: ContradictionKind,
250 pub claims: Vec<String>,
251 pub severity: Severity,
252 pub explanation: String,
253 pub suggested_question: String,
254}
255
256#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
258#[serde(tag = "move", rename_all = "snake_case")]
259pub enum RemediationMove {
260 Supersede { old: String, new: String },
262 Retract { claim: String },
264 Qualify {
266 claim: String,
267 #[serde(default, skip_serializing_if = "Option::is_none")]
268 condition: Option<String>,
269 #[serde(default, skip_serializing_if = "Option::is_none")]
270 time_scope: Option<TimeScope>,
271 },
272 ReconcileByEvidence { contradiction: String },
276 Accept { target: String, rationale: String },
280}
281
282#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
284pub struct RemediationEvent {
285 pub id: String,
286 pub session_id: String,
287 #[serde(flatten)]
288 pub r#move: RemediationMove,
289 pub source: EvidenceRef,
290 #[serde(default, skip_serializing_if = "Option::is_none")]
291 pub detail: Option<String>,
292}
293
294#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
296pub struct RequirementQualityCheck {
297 pub name: String,
299 pub passed: bool,
300 #[serde(default, skip_serializing_if = "Option::is_none")]
301 pub detail: Option<String>,
302}
303
304#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
306#[serde(rename_all = "snake_case")]
307pub enum Priority {
308 Must,
309 Should,
310 Could,
311}
312
313#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
317pub struct RequirementCandidate {
318 pub id: String,
319 pub statement: String,
320 pub subject: EntityRef,
321 pub capability: String,
322 #[serde(default, skip_serializing_if = "Option::is_none")]
323 pub condition: Option<String>,
324 #[serde(default)]
325 pub acceptance_criteria: Vec<String>,
326 #[serde(default, skip_serializing_if = "Option::is_none")]
327 pub priority: Option<Priority>,
328 #[serde(default, skip_serializing_if = "Option::is_none")]
329 pub owner: Option<String>,
330 #[serde(default, skip_serializing_if = "Option::is_none")]
331 pub rationale: Option<String>,
332 #[serde(default)]
333 pub source_claims: Vec<String>,
334 #[serde(default)]
335 pub quality_checks: Vec<RequirementQualityCheck>,
336}
337
338#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
342pub struct RequirementMeta {
343 #[serde(default, skip_serializing_if = "Option::is_none")]
344 pub statement: Option<String>,
345 #[serde(default, skip_serializing_if = "Option::is_none")]
346 pub capability: Option<String>,
347 #[serde(default)]
348 pub acceptance_criteria: Vec<String>,
349 #[serde(default, skip_serializing_if = "Option::is_none")]
350 pub priority: Option<Priority>,
351 #[serde(default, skip_serializing_if = "Option::is_none")]
352 pub owner: Option<String>,
353 #[serde(default, skip_serializing_if = "Option::is_none")]
354 pub rationale: Option<String>,
355}