Skip to main content

aidens_contracts/
proof.rs

1//! Proof profile, debt, waiver, and promotion-eligibility DTOs.
2//!
3//! These records expose proof/debt state for AiDENs operators. Waiver remains
4//! governance evidence, not proof, and canonical proof authority is delegated.
5
6use super::*;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
9#[serde(rename_all = "kebab-case")]
10pub enum ProofUseRestrictionV1 {
11    MayPromote,
12    AdvisoryOnly,
13    QuarantineOnly,
14    BlockRelease,
15}
16
17#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
18pub struct ProofObligationV1 {
19    pub obligation_id: ArtifactId,
20    pub description: String,
21    pub required_evidence_family: String,
22    pub satisfied_by: Vec<ArtifactId>,
23    pub waived_by: Vec<ArtifactId>,
24}
25
26impl ProofObligationV1 {
27    pub fn new(
28        description: impl Into<String>,
29        required_evidence_family: impl Into<String>,
30    ) -> Self {
31        let description = description.into();
32        let required_evidence_family = required_evidence_family.into();
33        Self {
34            obligation_id: generated_artifact_id_from_material(
35                "proof-obligation",
36                &format!("{description}|{required_evidence_family}"),
37            ),
38            description,
39            required_evidence_family,
40            satisfied_by: Vec::new(),
41            waived_by: Vec::new(),
42        }
43    }
44
45    pub fn is_proven(&self) -> bool {
46        !self.satisfied_by.is_empty()
47    }
48
49    pub fn is_waived(&self) -> bool {
50        !self.waived_by.is_empty()
51    }
52}
53
54#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
55pub struct LocalProofProfileV1 {
56    pub profile_id: ArtifactId,
57    pub proof_class: String,
58    pub exactness_tier: String,
59    pub obligations: Vec<ProofObligationV1>,
60    pub promotion_requires_all_obligations: bool,
61    pub waiver_policy: String,
62    #[serde(default, skip_serializing_if = "Vec::is_empty")]
63    pub reason_codes: Vec<String>,
64}
65
66impl LocalProofProfileV1 {
67    pub fn local_exact(obligations: Vec<ProofObligationV1>) -> Self {
68        let material = obligations
69            .iter()
70            .map(|obligation| obligation.obligation_id.0.clone())
71            .collect::<Vec<_>>()
72            .join("|");
73        Self {
74            profile_id: generated_artifact_id_from_material("proof-profile", &material),
75            proof_class: "local-executable-evidence".into(),
76            exactness_tier: "exact-check-required".into(),
77            obligations,
78            promotion_requires_all_obligations: true,
79            waiver_policy: "waiver-is-governance-not-proof".into(),
80            reason_codes: vec!["proof-profile-declared".into()],
81        }
82    }
83
84    pub fn proof_satisfied(&self) -> bool {
85        self.obligations.iter().all(ProofObligationV1::is_proven)
86    }
87
88    pub fn has_waiver_without_proof(&self) -> bool {
89        self.obligations
90            .iter()
91            .any(|obligation| obligation.is_waived() && !obligation.is_proven())
92    }
93}
94
95#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
96pub struct ProofDebtItemV1 {
97    pub debt_id: ArtifactId,
98    pub obligation_id: ArtifactId,
99    pub restriction: ProofUseRestrictionV1,
100    #[serde(default, skip_serializing_if = "Vec::is_empty")]
101    pub allowed_uses: Vec<String>,
102    #[serde(default, skip_serializing_if = "Option::is_none")]
103    pub expires_at: Option<DateTime<Utc>>,
104    #[serde(default, skip_serializing_if = "Option::is_none")]
105    pub escalated_at: Option<DateTime<Utc>>,
106    #[serde(default, skip_serializing_if = "Option::is_none")]
107    pub escalation_reason: Option<String>,
108    pub reason: String,
109}
110
111impl ProofDebtItemV1 {
112    pub fn with_expiry(mut self, expires_at: DateTime<Utc>) -> Self {
113        self.expires_at = Some(expires_at);
114        self
115    }
116
117    pub fn escalated(mut self, escalated_at: DateTime<Utc>, reason: impl Into<String>) -> Self {
118        self.escalated_at = Some(escalated_at);
119        self.escalation_reason = Some(reason.into());
120        self.restriction = ProofUseRestrictionV1::BlockRelease;
121        self.allowed_uses = Vec::new();
122        self
123    }
124
125    pub fn is_expired_at(&self, now: DateTime<Utc>) -> bool {
126        self.expires_at
127            .map(|expires_at| now >= expires_at)
128            .unwrap_or(false)
129    }
130
131    pub fn is_escalated_at(&self, now: DateTime<Utc>) -> bool {
132        self.escalated_at
133            .map(|escalated_at| now >= escalated_at)
134            .unwrap_or(false)
135            || self.is_expired_at(now)
136    }
137
138    pub fn allows_use_at(&self, requested_use: &str, now: DateTime<Utc>) -> bool {
139        !self.is_escalated_at(now)
140            && matches!(self.restriction, ProofUseRestrictionV1::AdvisoryOnly)
141            && self
142                .allowed_uses
143                .iter()
144                .any(|allowed| allowed == requested_use)
145    }
146}
147
148#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
149pub struct ProofDebtLedgerV1 {
150    pub ledger_id: ArtifactId,
151    pub artifact_ref: ArtifactId,
152    pub items: Vec<ProofDebtItemV1>,
153    pub generated_at: DateTime<Utc>,
154}
155
156impl ProofDebtLedgerV1 {
157    pub fn from_profile(artifact_ref: ArtifactId, profile: &LocalProofProfileV1) -> Self {
158        let items = profile
159            .obligations
160            .iter()
161            .filter(|obligation| !obligation.is_proven())
162            .map(|obligation| ProofDebtItemV1 {
163                debt_id: generated_artifact_id_from_material(
164                    "proof-debt",
165                    &format!("{}|{}", artifact_ref.0, obligation.obligation_id.0),
166                ),
167                obligation_id: obligation.obligation_id.clone(),
168                restriction: if obligation.is_waived() {
169                    ProofUseRestrictionV1::AdvisoryOnly
170                } else {
171                    ProofUseRestrictionV1::BlockRelease
172                },
173                allowed_uses: if obligation.is_waived() {
174                    vec!["local-advisory-display".into(), "operator-triage".into()]
175                } else {
176                    Vec::new()
177                },
178                expires_at: None,
179                escalated_at: None,
180                escalation_reason: None,
181                reason: if obligation.is_waived() {
182                    "waiver-recorded-but-proof-missing".into()
183                } else {
184                    "proof-obligation-unsatisfied".into()
185                },
186            })
187            .collect::<Vec<_>>();
188        let material = items
189            .iter()
190            .map(|item| item.debt_id.0.clone())
191            .collect::<Vec<_>>()
192            .join("|");
193        Self {
194            ledger_id: generated_artifact_id_from_material("proof-debt-ledger", &material),
195            artifact_ref,
196            items,
197            generated_at: Utc::now(),
198        }
199    }
200
201    pub fn blocks_promotion(&self) -> bool {
202        self.items.iter().any(|item| {
203            matches!(
204                item.restriction,
205                ProofUseRestrictionV1::BlockRelease
206                    | ProofUseRestrictionV1::AdvisoryOnly
207                    | ProofUseRestrictionV1::QuarantineOnly
208            )
209        })
210    }
211
212    pub fn active_items_at(&self, now: DateTime<Utc>) -> Vec<&ProofDebtItemV1> {
213        self.items
214            .iter()
215            .filter(|item| !item.is_expired_at(now))
216            .collect()
217    }
218
219    pub fn expired_items_at(&self, now: DateTime<Utc>) -> Vec<&ProofDebtItemV1> {
220        self.items
221            .iter()
222            .filter(|item| item.is_expired_at(now))
223            .collect()
224    }
225
226    pub fn escalated_items_at(&self, now: DateTime<Utc>) -> Vec<&ProofDebtItemV1> {
227        self.items
228            .iter()
229            .filter(|item| item.is_escalated_at(now))
230            .collect()
231    }
232
233    pub fn allows_use_at(&self, requested_use: &str, now: DateTime<Utc>) -> bool {
234        !self.items.is_empty()
235            && self
236                .items
237                .iter()
238                .all(|item| item.allows_use_at(requested_use, now))
239    }
240}
241
242#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
243pub struct ProofWaiverReceiptV1 {
244    pub receipt_id: ArtifactId,
245    pub obligation_id: ArtifactId,
246    pub approver: String,
247    pub rationale: String,
248    pub waiver_is_not_proof: bool,
249    pub recorded_at: DateTime<Utc>,
250}
251
252impl ProofWaiverReceiptV1 {
253    pub fn new(
254        obligation_id: ArtifactId,
255        approver: impl Into<String>,
256        rationale: impl Into<String>,
257    ) -> Self {
258        let approver = approver.into();
259        let rationale = rationale.into();
260        Self {
261            receipt_id: generated_artifact_id_from_material(
262                "proof-waiver",
263                &format!("{}|{}|{}", obligation_id.0, approver, rationale),
264            ),
265            obligation_id,
266            approver,
267            rationale,
268            waiver_is_not_proof: true,
269            recorded_at: Utc::now(),
270        }
271    }
272}
273
274#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
275pub struct PromotionEligibilityReportV1 {
276    pub report_id: ArtifactId,
277    pub artifact_ref: ArtifactId,
278    pub proof_profile_id: ArtifactId,
279    pub proof_debt_ledger_id: ArtifactId,
280    pub eligible: bool,
281    #[serde(default, skip_serializing_if = "Vec::is_empty")]
282    pub reason_codes: Vec<String>,
283    pub generated_at: DateTime<Utc>,
284}
285
286impl PromotionEligibilityReportV1 {
287    pub fn new(
288        artifact_ref: ArtifactId,
289        profile: &LocalProofProfileV1,
290        debt: &ProofDebtLedgerV1,
291    ) -> Self {
292        let eligible = profile.proof_satisfied() && !debt.blocks_promotion();
293        Self {
294            report_id: generated_artifact_id_from_material(
295                "promotion-eligibility",
296                &format!(
297                    "{}|{}|{}",
298                    artifact_ref.0, profile.profile_id.0, debt.ledger_id.0
299                ),
300            ),
301            artifact_ref,
302            proof_profile_id: profile.profile_id.clone(),
303            proof_debt_ledger_id: debt.ledger_id.clone(),
304            eligible,
305            reason_codes: if eligible {
306                vec!["promotion-proof-satisfied".into()]
307            } else {
308                vec!["promotion-blocked-by-proof-debt".into()]
309            },
310            generated_at: Utc::now(),
311        }
312    }
313}