higher_graphen_core/extension/
derivation.rs1use super::common::LifecycleStatus;
2use crate::text::normalize_required_text;
3use crate::{CoreError, Id, Provenance, Result, ReviewStatus};
4use serde::{Deserialize, Serialize};
5
6#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
8#[serde(rename_all = "snake_case")]
9pub enum VerifierKind {
10 HumanReview,
12 SchemaValidator,
14 ProofChecker,
16 TestRun,
18 StaticAnalysis,
20 CustomEngine,
22}
23
24#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
26#[serde(deny_unknown_fields)]
27pub struct Verifier {
28 pub kind: VerifierKind,
30 pub reference: String,
32}
33
34impl Verifier {
35 pub fn new(kind: VerifierKind, reference: impl Into<String>) -> Result<Self> {
37 Ok(Self {
38 kind,
39 reference: normalize_required_text("verifier.reference", reference)?,
40 })
41 }
42}
43
44#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
46#[serde(rename_all = "snake_case")]
47pub enum VerificationStatus {
48 Unverified,
50 MachineChecked,
52 HumanReviewed,
54 Failed,
56 Superseded,
58}
59
60#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
62#[serde(rename_all = "snake_case")]
63pub enum DerivationFailureMode {
64 MissingPremise,
66 InvalidRule,
68 OutOfScopeRule,
70 ContradictedByWitness,
72 CircularDerivation,
74 UnsupportedJump,
76 VerifierUnavailable,
78 None,
80}
81
82#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
84#[serde(deny_unknown_fields)]
85pub struct InferenceRule {
86 pub id: Id,
88 pub name: String,
90 #[serde(default, skip_serializing_if = "Vec::is_empty")]
92 pub rule_scope_contexts: Vec<Id>,
93 #[serde(skip_serializing_if = "Option::is_none")]
95 pub interpretation_package: Option<Id>,
96}
97
98impl InferenceRule {
99 pub fn new(id: Id, name: impl Into<String>) -> Result<Self> {
101 Ok(Self {
102 id,
103 name: normalize_required_text("inference_rule.name", name)?,
104 rule_scope_contexts: Vec::new(),
105 interpretation_package: None,
106 })
107 }
108
109 fn has_scope(&self) -> bool {
110 !self.rule_scope_contexts.is_empty() || self.interpretation_package.is_some()
111 }
112}
113
114#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
116#[serde(deny_unknown_fields)]
117pub struct Derivation {
118 pub id: Id,
120 pub conclusion: Id,
122 #[serde(default, skip_serializing_if = "Vec::is_empty")]
124 pub premises: Vec<Id>,
125 pub inference_rule: InferenceRule,
127 #[serde(default, skip_serializing_if = "Vec::is_empty")]
129 pub warrants: Vec<Id>,
130 #[serde(default, skip_serializing_if = "Vec::is_empty")]
132 pub excluded_premises: Vec<Id>,
133 #[serde(default, skip_serializing_if = "Vec::is_empty")]
135 pub counterexamples: Vec<Id>,
136 #[serde(skip_serializing_if = "Option::is_none")]
138 pub verifier: Option<Verifier>,
139 pub verification_status: VerificationStatus,
141 pub failure_mode: DerivationFailureMode,
143 pub provenance: Provenance,
145 pub review_status: LifecycleStatus,
147}
148
149impl Derivation {
150 pub fn candidate(
152 id: Id,
153 conclusion: Id,
154 premises: Vec<Id>,
155 inference_rule: InferenceRule,
156 provenance: Provenance,
157 ) -> Self {
158 Self {
159 id,
160 conclusion,
161 premises,
162 inference_rule,
163 warrants: Vec::new(),
164 excluded_premises: Vec::new(),
165 counterexamples: Vec::new(),
166 verifier: None,
167 verification_status: VerificationStatus::Unverified,
168 failure_mode: DerivationFailureMode::None,
169 provenance,
170 review_status: LifecycleStatus::Candidate,
171 }
172 }
173
174 pub fn validate_acceptance(&self) -> Result<()> {
176 if self
177 .premises
178 .iter()
179 .all(|premise| premise == &self.conclusion)
180 {
181 return Err(CoreError::malformed_field(
182 "premises",
183 "derivation cannot be circular over only the conclusion",
184 ));
185 }
186 if !self.inference_rule.has_scope() {
187 return Err(CoreError::malformed_field(
188 "inference_rule.rule_scope",
189 "accepted derivation requires a scoped inference rule",
190 ));
191 }
192 if self.failure_mode != DerivationFailureMode::None {
193 return Err(CoreError::malformed_field(
194 "failure_mode",
195 "accepted derivation requires no unresolved failure mode",
196 ));
197 }
198 if !self.counterexamples.is_empty() {
199 return Err(CoreError::malformed_field(
200 "counterexamples",
201 "accepted derivation cannot have unresolved counterexamples",
202 ));
203 }
204 match self.verification_status {
205 VerificationStatus::MachineChecked | VerificationStatus::HumanReviewed => {}
206 _ => {
207 return Err(CoreError::malformed_field(
208 "verification_status",
209 "accepted derivation requires machine_checked or human_reviewed status",
210 ));
211 }
212 }
213 if self.verifier.is_none() && self.provenance.review_status != ReviewStatus::Accepted {
214 return Err(CoreError::malformed_field(
215 "verifier",
216 "accepted derivation requires verifier or explicit accepted review",
217 ));
218 }
219 Ok(())
220 }
221}