mod types;
pub use types::*;
#[derive(Debug)]
pub struct VerificationSession {
pub id: String,
claims: Vec<FalsificationClaim>,
artifacts: Vec<BlackBoxArtifact>,
attempts: Vec<VerificationAttempt>,
pub scorecard: ScorecardV2,
audit_trail: Vec<AuditEntry>,
state: SessionState,
}
impl Default for VerificationSession {
fn default() -> Self {
Self::new("session-1")
}
}
impl VerificationSession {
pub fn new(id: &str) -> Self {
Self {
id: id.to_string(),
claims: Vec::new(),
artifacts: Vec::new(),
attempts: Vec::new(),
scorecard: ScorecardV2::new(),
audit_trail: Vec::new(),
state: SessionState::AwaitingClaims,
}
}
pub fn state(&self) -> SessionState {
self.state
}
pub fn submit_claim(
&mut self,
role: Role,
claim: FalsificationClaim,
) -> Result<(), &'static str> {
if !role.can_claim() {
return Err("Only Dev role can submit claims");
}
if !claim.is_valid() {
return Err("Invalid claim structure");
}
let entry = AuditEntry::new(
&format!("audit-{}", self.audit_trail.len()),
role,
&claim.claimant,
&format!("Submitted claim for {}", claim.feature),
)
.with_artifact(&claim.id);
self.audit_trail.push(entry);
self.claims.push(claim);
self.state = SessionState::AwaitingVerification;
Ok(())
}
pub fn generate_artifact(
&mut self,
claim_id: &str,
binary_hash: &str,
) -> Option<BlackBoxArtifact> {
let claim = self.claims.iter().find(|c| c.id == claim_id)?;
let artifact = BlackBoxArtifact::from_claim(claim, binary_hash);
let entry = AuditEntry::new(
&format!("audit-{}", self.audit_trail.len()),
Role::System,
"System",
&format!("Generated black-box artifact from claim {}", claim_id),
)
.with_artifact(&artifact.id);
self.audit_trail.push(entry);
self.artifacts.push(artifact.clone());
Some(artifact)
}
pub fn submit_attempt(
&mut self,
role: Role,
attempt: VerificationAttempt,
) -> Result<(), &'static str> {
if !role.can_verify() {
return Err("Only QA role can submit verification attempts");
}
let entry = AuditEntry::new(
&format!("audit-{}", self.audit_trail.len()),
role,
&attempt.verifier,
&format!("Submitted verification attempt: {:?}", attempt.result),
)
.with_artifact(&attempt.artifact_id);
self.audit_trail.push(entry);
self.attempts.push(attempt);
Ok(())
}
pub fn get_attempts(&self, artifact_id: &str) -> Vec<&VerificationAttempt> {
self.attempts
.iter()
.filter(|a| a.artifact_id == artifact_id)
.collect()
}
pub fn make_decision(&mut self, role: Role) -> Result<ReleaseDecision, &'static str> {
if !role.can_approve() {
return Err("Only System role can make release decisions");
}
if self.attempts.is_empty() {
return Ok(ReleaseDecision::Pending {
reason: "No verification attempts yet".to_string(),
});
}
let falsified = self
.attempts
.iter()
.any(|a| a.result == VerificationResult::Falsified);
let all_inconclusive = self
.attempts
.iter()
.all(|a| a.result == VerificationResult::Inconclusive);
let decision = if falsified {
ReleaseDecision::Rejected {
reason: "QA successfully falsified the claim".to_string(),
}
} else if all_inconclusive {
ReleaseDecision::Pending {
reason: "All verification attempts were inconclusive".to_string(),
}
} else {
if self.scorecard.passes() {
ReleaseDecision::Approved {
reason: format!(
"QA failed to falsify and scorecard passes ({:.1}/100, grade {})",
self.scorecard.total_score(),
self.scorecard.grade()
),
}
} else {
ReleaseDecision::Rejected {
reason: format!(
"Scorecard fails ({:.1}/100 < 70, grade {})",
self.scorecard.total_score(),
self.scorecard.grade()
),
}
}
};
let entry = AuditEntry::new(
&format!("audit-{}", self.audit_trail.len()),
role,
"System",
&format!("Release decision: {:?}", decision),
);
self.audit_trail.push(entry);
self.state = SessionState::Completed;
Ok(decision)
}
pub fn audit_trail(&self) -> &[AuditEntry] {
&self.audit_trail
}
pub fn claim_count(&self) -> usize {
self.claims.len()
}
pub fn attempt_count(&self) -> usize {
self.attempts.len()
}
pub fn generate_report(&self) -> VerificationReport {
VerificationReport {
session_id: self.id.clone(),
total_claims: self.claims.len(),
total_artifacts: self.artifacts.len(),
total_attempts: self.attempts.len(),
falsified_count: self
.attempts
.iter()
.filter(|a| a.result == VerificationResult::Falsified)
.count(),
unfalsified_count: self
.attempts
.iter()
.filter(|a| a.result == VerificationResult::Unfalsified)
.count(),
inconclusive_count: self
.attempts
.iter()
.filter(|a| a.result == VerificationResult::Inconclusive)
.count(),
scorecard_total: self.scorecard.total_score(),
scorecard_grade: self.scorecard.grade().to_string(),
audit_entries: self.audit_trail.len(),
}
}
}
#[cfg(test)]
mod tests;