Skip to main content

cbtop/double_blind/
types.rs

1//! Types, enums, and data models for the double-blind verification framework.
2
3use std::collections::HashMap;
4use std::time::SystemTime;
5
6/// Role in the double-blind verification process
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
8pub enum Role {
9    /// Developer - implements features, makes claims
10    Dev,
11    /// Quality Assurance - verifies claims black-box
12    Qa,
13    /// System - makes release decisions
14    System,
15}
16
17impl Role {
18    /// Get role name
19    pub fn name(&self) -> &'static str {
20        match self {
21            Role::Dev => "Developer",
22            Role::Qa => "QA",
23            Role::System => "System",
24        }
25    }
26
27    /// Check if role can make claims
28    pub fn can_claim(&self) -> bool {
29        matches!(self, Role::Dev)
30    }
31
32    /// Check if role can verify
33    pub fn can_verify(&self) -> bool {
34        matches!(self, Role::Qa)
35    }
36
37    /// Check if role can approve releases
38    pub fn can_approve(&self) -> bool {
39        matches!(self, Role::System)
40    }
41}
42
43/// Verification result
44#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45pub enum VerificationResult {
46    /// Claim was successfully falsified (QA found a bug)
47    Falsified,
48    /// Unable to falsify the claim (QA failed to find bugs)
49    Unfalsified,
50    /// Verification was inconclusive
51    Inconclusive,
52}
53
54impl VerificationResult {
55    /// Check if release should be approved based on this result
56    pub fn should_approve(&self) -> bool {
57        matches!(self, VerificationResult::Unfalsified)
58    }
59}
60
61/// A falsification criterion (F-criteria)
62#[derive(Debug, Clone)]
63pub struct FalsificationCriterion {
64    /// Criterion ID (e.g., "F1001")
65    pub id: String,
66    /// Description of what to test
67    pub description: String,
68    /// Pass condition
69    pub pass_condition: String,
70}
71
72impl FalsificationCriterion {
73    /// Create a new criterion
74    pub fn new(id: &str, description: &str, pass_condition: &str) -> Self {
75        Self {
76            id: id.to_string(),
77            description: description.to_string(),
78            pass_condition: pass_condition.to_string(),
79        }
80    }
81
82    /// Compute hash of criterion for integrity verification
83    pub fn hash(&self) -> u64 {
84        // Simple hash combining id, description, and pass_condition
85        let mut hash: u64 = 0;
86        for byte in self.id.bytes() {
87            hash = hash.wrapping_mul(31).wrapping_add(u64::from(byte));
88        }
89        for byte in self.description.bytes() {
90            hash = hash.wrapping_mul(31).wrapping_add(u64::from(byte));
91        }
92        for byte in self.pass_condition.bytes() {
93            hash = hash.wrapping_mul(31).wrapping_add(u64::from(byte));
94        }
95        hash
96    }
97}
98
99/// Developer's claim that falsification passed
100#[derive(Debug, Clone)]
101pub struct FalsificationClaim {
102    /// Claim ID
103    pub id: String,
104    /// Feature or component being claimed
105    pub feature: String,
106    /// F-criteria that were tested
107    pub criteria: Vec<FalsificationCriterion>,
108    /// Hash of all criteria for integrity
109    pub criteria_hash: u64,
110    /// Timestamp of claim
111    pub timestamp: SystemTime,
112    /// Developer making the claim
113    pub claimant: String,
114    /// Version of the software
115    pub version: String,
116    /// Evidence supporting the claim (test results, logs)
117    pub evidence: Vec<String>,
118}
119
120impl FalsificationClaim {
121    /// Create a new claim
122    pub fn new(id: &str, feature: &str, claimant: &str, version: &str) -> Self {
123        Self {
124            id: id.to_string(),
125            feature: feature.to_string(),
126            criteria: Vec::new(),
127            criteria_hash: 0,
128            timestamp: SystemTime::now(),
129            claimant: claimant.to_string(),
130            version: version.to_string(),
131            evidence: Vec::new(),
132        }
133    }
134
135    /// Add a criterion to the claim
136    pub fn add_criterion(&mut self, criterion: FalsificationCriterion) {
137        self.criteria.push(criterion);
138        self.update_hash();
139    }
140
141    /// Add evidence to the claim
142    pub fn add_evidence(&mut self, evidence: &str) {
143        self.evidence.push(evidence.to_string());
144    }
145
146    /// Update the criteria hash
147    fn update_hash(&mut self) {
148        let mut hash: u64 = 0;
149        for criterion in &self.criteria {
150            hash = hash.wrapping_add(criterion.hash());
151        }
152        self.criteria_hash = hash;
153    }
154
155    /// Verify the criteria hash matches
156    pub fn verify_hash(&self) -> bool {
157        let mut expected: u64 = 0;
158        for criterion in &self.criteria {
159            expected = expected.wrapping_add(criterion.hash());
160        }
161        expected == self.criteria_hash
162    }
163
164    /// Check if claim is valid (has required fields)
165    pub fn is_valid(&self) -> bool {
166        !self.id.is_empty()
167            && !self.feature.is_empty()
168            && !self.claimant.is_empty()
169            && !self.version.is_empty()
170            && !self.criteria.is_empty()
171            && self.verify_hash()
172    }
173}
174
175/// Black-box artifact for QA (no source code)
176#[derive(Debug, Clone)]
177pub struct BlackBoxArtifact {
178    /// Artifact ID
179    pub id: String,
180    /// Binary hash (SHA256 hex)
181    pub binary_hash: String,
182    /// F-criteria to test against
183    pub criteria: Vec<FalsificationCriterion>,
184    /// Criteria hash for integrity
185    pub criteria_hash: u64,
186    /// Version being tested
187    pub version: String,
188    /// Deadline for verification
189    pub deadline: Option<SystemTime>,
190}
191
192impl BlackBoxArtifact {
193    /// Create from a claim (strips source-related info)
194    pub fn from_claim(claim: &FalsificationClaim, binary_hash: &str) -> Self {
195        Self {
196            id: format!("BB-{}", claim.id),
197            binary_hash: binary_hash.to_string(),
198            criteria: claim.criteria.clone(),
199            criteria_hash: claim.criteria_hash,
200            version: claim.version.clone(),
201            deadline: None,
202        }
203    }
204
205    /// Set verification deadline
206    pub fn with_deadline(mut self, deadline: SystemTime) -> Self {
207        self.deadline = Some(deadline);
208        self
209    }
210
211    /// Check if deadline has passed
212    pub fn is_expired(&self) -> bool {
213        if let Some(deadline) = self.deadline {
214            SystemTime::now() > deadline
215        } else {
216            false
217        }
218    }
219
220    /// Verify criteria hash matches claim
221    pub fn verify_criteria_integrity(&self, claim: &FalsificationClaim) -> bool {
222        self.criteria_hash == claim.criteria_hash
223    }
224}
225
226/// A single verification attempt by QA
227#[derive(Debug, Clone)]
228pub struct VerificationAttempt {
229    /// Attempt ID
230    pub id: String,
231    /// Artifact being tested
232    pub artifact_id: String,
233    /// QA engineer making the attempt
234    pub verifier: String,
235    /// Result of verification
236    pub result: VerificationResult,
237    /// Timestamp
238    pub timestamp: SystemTime,
239    /// Evidence collected (logs, traces)
240    pub evidence: Vec<String>,
241    /// Individual criterion results
242    pub criterion_results: HashMap<String, bool>,
243}
244
245impl VerificationAttempt {
246    /// Create a new attempt
247    pub fn new(id: &str, artifact_id: &str, verifier: &str) -> Self {
248        Self {
249            id: id.to_string(),
250            artifact_id: artifact_id.to_string(),
251            verifier: verifier.to_string(),
252            result: VerificationResult::Inconclusive,
253            timestamp: SystemTime::now(),
254            evidence: Vec::new(),
255            criterion_results: HashMap::new(),
256        }
257    }
258
259    /// Record result for a criterion
260    pub fn record_criterion(&mut self, criterion_id: &str, passed: bool) {
261        self.criterion_results
262            .insert(criterion_id.to_string(), passed);
263    }
264
265    /// Add evidence
266    pub fn add_evidence(&mut self, evidence: &str) {
267        self.evidence.push(evidence.to_string());
268    }
269
270    /// Finalize the attempt with result
271    pub fn finalize(&mut self, result: VerificationResult) {
272        self.result = result;
273        self.timestamp = SystemTime::now();
274    }
275
276    /// Check if any criterion was falsified
277    pub fn has_falsification(&self) -> bool {
278        self.criterion_results.values().any(|&passed| !passed)
279    }
280
281    /// Get count of passed criteria
282    pub fn passed_count(&self) -> usize {
283        self.criterion_results.values().filter(|&&p| p).count()
284    }
285
286    /// Get count of failed criteria
287    pub fn failed_count(&self) -> usize {
288        self.criterion_results.values().filter(|&&p| !p).count()
289    }
290}
291
292/// Scorecard component with weight
293#[derive(Debug, Clone)]
294pub struct ScorecardComponent {
295    /// Component name
296    pub name: String,
297    /// Weight (0.0 to 1.0, must sum to 1.0 across all components)
298    pub weight: f64,
299    /// Score (0 to 100)
300    pub score: u32,
301}
302
303impl ScorecardComponent {
304    /// Create a new component
305    pub fn new(name: &str, weight: f64, score: u32) -> Self {
306        Self {
307            name: name.to_string(),
308            weight,
309            score: score.min(100),
310        }
311    }
312
313    /// Calculate weighted score
314    pub fn weighted_score(&self) -> f64 {
315        self.weight * f64::from(self.score)
316    }
317}
318
319/// Falsification Scorecard v2 per section 36.3
320#[derive(Debug, Clone)]
321pub struct ScorecardV2 {
322    /// Components with weights
323    pub components: Vec<ScorecardComponent>,
324    /// Version (v1 or v2)
325    pub version: u8,
326}
327
328impl Default for ScorecardV2 {
329    fn default() -> Self {
330        Self::new()
331    }
332}
333
334impl ScorecardV2 {
335    /// Create a new v2 scorecard with default components
336    pub fn new() -> Self {
337        Self {
338            components: vec![
339                ScorecardComponent::new("Core Correctness", 0.30, 0),
340                ScorecardComponent::new("Performance", 0.30, 0),
341                ScorecardComponent::new("Resilience", 0.20, 0),
342                ScorecardComponent::new("Usability", 0.20, 0),
343            ],
344            version: 2,
345        }
346    }
347
348    /// Set score for a component by name
349    pub fn set_score(&mut self, name: &str, score: u32) -> bool {
350        for component in &mut self.components {
351            if component.name == name {
352                component.score = score.min(100);
353                return true;
354            }
355        }
356        false
357    }
358
359    /// Calculate total weighted score
360    pub fn total_score(&self) -> f64 {
361        self.components.iter().map(|c| c.weighted_score()).sum()
362    }
363
364    /// Check if weights sum to 1.0
365    pub fn weights_valid(&self) -> bool {
366        let sum: f64 = self.components.iter().map(|c| c.weight).sum();
367        (sum - 1.0).abs() < 1e-10
368    }
369
370    /// Check if scorecard passes (>= 70)
371    pub fn passes(&self) -> bool {
372        self.total_score() >= 70.0
373    }
374
375    /// Get grade based on score
376    pub fn grade(&self) -> &'static str {
377        let score = self.total_score();
378        if score >= 90.0 {
379            "A"
380        } else if score >= 80.0 {
381            "B"
382        } else if score >= 70.0 {
383            "C"
384        } else if score >= 60.0 {
385            "D"
386        } else {
387            "F"
388        }
389    }
390}
391
392/// Release decision
393#[derive(Debug, Clone, PartialEq, Eq)]
394pub enum ReleaseDecision {
395    /// Approved for release
396    Approved { reason: String },
397    /// Rejected
398    Rejected { reason: String },
399    /// Pending more verification
400    Pending { reason: String },
401}
402
403impl ReleaseDecision {
404    /// Check if approved
405    pub fn is_approved(&self) -> bool {
406        matches!(self, ReleaseDecision::Approved { .. })
407    }
408
409    /// Get reason
410    pub fn reason(&self) -> &str {
411        match self {
412            ReleaseDecision::Approved { reason }
413            | ReleaseDecision::Rejected { reason }
414            | ReleaseDecision::Pending { reason } => reason,
415        }
416    }
417}
418
419/// Audit trail entry
420#[derive(Debug, Clone)]
421pub struct AuditEntry {
422    /// Entry ID
423    pub id: String,
424    /// Timestamp
425    pub timestamp: SystemTime,
426    /// Role performing action
427    pub role: Role,
428    /// Actor name
429    pub actor: String,
430    /// Action description
431    pub action: String,
432    /// Related artifact IDs
433    pub artifacts: Vec<String>,
434}
435
436impl AuditEntry {
437    /// Create a new audit entry
438    pub fn new(id: &str, role: Role, actor: &str, action: &str) -> Self {
439        Self {
440            id: id.to_string(),
441            timestamp: SystemTime::now(),
442            role,
443            actor: actor.to_string(),
444            action: action.to_string(),
445            artifacts: Vec::new(),
446        }
447    }
448
449    /// Add related artifact
450    pub fn with_artifact(mut self, artifact_id: &str) -> Self {
451        self.artifacts.push(artifact_id.to_string());
452        self
453    }
454}
455
456/// Session state
457#[derive(Debug, Clone, Copy, PartialEq, Eq)]
458pub enum SessionState {
459    /// Awaiting claims from Dev
460    AwaitingClaims,
461    /// Claims received, awaiting verification
462    AwaitingVerification,
463    /// Verification complete, awaiting decision
464    AwaitingDecision,
465    /// Session completed
466    Completed,
467}
468
469/// Summary report of verification session
470#[derive(Debug, Clone)]
471pub struct VerificationReport {
472    /// Session ID
473    pub session_id: String,
474    /// Total claims submitted
475    pub total_claims: usize,
476    /// Total artifacts generated
477    pub total_artifacts: usize,
478    /// Total verification attempts
479    pub total_attempts: usize,
480    /// Number of falsified claims
481    pub falsified_count: usize,
482    /// Number of unfalsified claims
483    pub unfalsified_count: usize,
484    /// Number of inconclusive attempts
485    pub inconclusive_count: usize,
486    /// Scorecard total
487    pub scorecard_total: f64,
488    /// Scorecard grade
489    pub scorecard_grade: String,
490    /// Audit trail entries
491    pub audit_entries: usize,
492}
493
494impl VerificationReport {
495    /// Check if report indicates success
496    pub fn is_success(&self) -> bool {
497        self.falsified_count == 0 && self.scorecard_total >= 70.0
498    }
499}