logicaffeine_proof/
certifier.rs

1//! The Certifier: Converts DerivationTrees to Kernel Terms.
2//!
3//! This is the Great Bridge between the Proof Engine and the Kernel.
4//! Via Curry-Howard, each proof rule maps to a term constructor:
5//!
6//! | DerivationTree Rule     | Kernel Term                           |
7//! |-------------------------|---------------------------------------|
8//! | Axiom / PremiseMatch    | Term::Global(name)                    |
9//! | ModusPonens [impl, arg] | Term::App(certify(impl), certify(arg))|
10//! | ConjunctionIntro [p, q] | conj P Q p_proof q_proof              |
11
12use logicaffeine_kernel::{Context, KernelError, KernelResult, Term};
13
14use crate::{DerivationTree, InferenceRule, ProofExpr, ProofTerm};
15
16// =============================================================================
17// INDUCTION STATE - Tracks context during induction certification
18// =============================================================================
19
20/// State for tracking induction during certification.
21/// When certifying the step case, IH references become recursive calls.
22struct InductionState {
23    /// Name for self-reference in the fixpoint (e.g., "rec_n")
24    fix_name: String,
25    /// The predecessor variable in the step case (e.g., "k")
26    step_var: String,
27    /// What the IH looks like in the derivation tree
28    ih_conclusion: ProofExpr,
29}
30
31// =============================================================================
32// CERTIFICATION CONTEXT
33// =============================================================================
34
35/// Context for certifying derivation trees into kernel terms.
36///
37/// Wraps a kernel [`Context`] and tracks additional state
38/// needed during certification:
39///
40/// - **Local variables**: Variables bound by lambda abstractions during traversal
41/// - **Induction state**: For resolving IH (inductive hypothesis) references in step cases
42///
43/// # Lifetime
44///
45/// The `'a` lifetime ties this context to the kernel context it wraps.
46/// The certification context borrows the kernel context immutably.
47///
48/// # Example
49///
50/// ```ignore
51/// use logicaffeine_proof::certifier::CertificationContext;
52/// use logicaffeine_kernel::Context;
53///
54/// let kernel_ctx = Context::new();
55/// let cert_ctx = CertificationContext::new(&kernel_ctx);
56/// ```
57///
58/// # See Also
59///
60/// * [`certify`] - The main function that uses this context
61/// * [`Context`] - The underlying kernel context
62pub struct CertificationContext<'a> {
63    kernel_ctx: &'a Context,
64    /// Local variables in scope (from lambda abstractions)
65    locals: Vec<String>,
66    /// Induction state for IH resolution (only set during step case)
67    induction_state: Option<InductionState>,
68}
69
70impl<'a> CertificationContext<'a> {
71    pub fn new(kernel_ctx: &'a Context) -> Self {
72        Self {
73            kernel_ctx,
74            locals: Vec::new(),
75            induction_state: None,
76        }
77    }
78
79    /// Create a new context with an additional local variable.
80    fn with_local(&self, name: &str) -> Self {
81        let mut new_locals = self.locals.clone();
82        new_locals.push(name.to_string());
83        Self {
84            kernel_ctx: self.kernel_ctx,
85            locals: new_locals,
86            induction_state: self.induction_state.clone(),
87        }
88    }
89
90    /// Create a new context with induction state for step case certification.
91    fn with_induction(&self, fix_name: &str, step_var: &str, ih: ProofExpr) -> Self {
92        Self {
93            kernel_ctx: self.kernel_ctx,
94            locals: self.locals.clone(),
95            induction_state: Some(InductionState {
96                fix_name: fix_name.to_string(),
97                step_var: step_var.to_string(),
98                ih_conclusion: ih,
99            }),
100        }
101    }
102
103    /// Check if a name is a local variable.
104    fn is_local(&self, name: &str) -> bool {
105        self.locals.iter().any(|n| n == name)
106    }
107
108    /// Check if this conclusion matches the IH in current induction context.
109    /// Returns the recursive call term if it matches.
110    fn get_ih_term(&self, conclusion: &ProofExpr) -> Option<Term> {
111        if let Some(state) = &self.induction_state {
112            if conclusions_match(conclusion, &state.ih_conclusion) {
113                // IH becomes: rec k (recursive call)
114                return Some(Term::App(
115                    Box::new(Term::Var(state.fix_name.clone())),
116                    Box::new(Term::Var(state.step_var.clone())),
117                ));
118            }
119        }
120        None
121    }
122}
123
124impl Clone for InductionState {
125    fn clone(&self) -> Self {
126        Self {
127            fix_name: self.fix_name.clone(),
128            step_var: self.step_var.clone(),
129            ih_conclusion: self.ih_conclusion.clone(),
130        }
131    }
132}
133
134/// Check if two ProofExprs are structurally equivalent (for IH matching).
135/// Uses PartialEq derivation.
136fn conclusions_match(a: &ProofExpr, b: &ProofExpr) -> bool {
137    a == b
138}
139
140/// Certify a derivation tree, producing a kernel term.
141///
142/// Converts a proof (derivation tree) into a typed lambda calculus term
143/// via the Curry-Howard correspondence. The resulting term, when type-checked
144/// by the kernel, should have the type corresponding to the tree's conclusion.
145///
146/// # Arguments
147///
148/// * `tree` - The derivation tree to certify
149/// * `ctx` - The certification context (wraps kernel context)
150///
151/// # Returns
152///
153/// * `Ok(term)` - The kernel term representing this proof
154/// * `Err(_)` - If certification fails (missing hypothesis, wrong premises, etc.)
155///
156/// # Curry-Howard Mapping
157///
158/// | Inference Rule | Kernel Term |
159/// |----------------|-------------|
160/// | `Axiom` / `PremiseMatch` | `Term::Global(name)` (hypothesis lookup) |
161/// | `ModusPonens` | `Term::App(certify(impl), certify(arg))` |
162/// | `ConjunctionIntro` | `conj P Q p_proof q_proof` |
163/// | `UniversalInst(w)` | `Term::App(forall_proof, w)` |
164/// | `UniversalIntro { var, type }` | `Term::Lambda { param: var, ... }` |
165/// | `StructuralInduction` | `Term::Fix { name, body: λn. match n ... }` |
166/// | `Rewrite { from, to }` | `Eq_rec A from P proof to eq_proof` |
167/// | `ExistentialIntro` | `witness A P w proof` |
168///
169/// # Induction Handling
170///
171/// Structural induction produces fixpoint terms. The step case certification
172/// tracks the induction variable so that IH references become recursive calls.
173///
174/// # See Also
175///
176/// * [`CertificationContext`] - The context passed to this function
177/// * [`DerivationTree`] - The input proof structure
178/// * [`InferenceRule`] - The rules being certified
179pub fn certify(tree: &DerivationTree, ctx: &CertificationContext) -> KernelResult<Term> {
180    match &tree.rule {
181        // Axiom or direct hypothesis reference
182        InferenceRule::Axiom | InferenceRule::PremiseMatch => {
183            certify_hypothesis(&tree.conclusion, ctx)
184        }
185
186        // Modus Ponens: App(impl_proof, arg_proof)
187        // P → Q, P ⊢ Q becomes (h1 h2) : Q
188        InferenceRule::ModusPonens => {
189            if tree.premises.len() != 2 {
190                return Err(KernelError::CertificationError(
191                    "ModusPonens requires exactly 2 premises".to_string(),
192                ));
193            }
194            let impl_term = certify(&tree.premises[0], ctx)?;
195            let arg_term = certify(&tree.premises[1], ctx)?;
196            Ok(Term::App(Box::new(impl_term), Box::new(arg_term)))
197        }
198
199        // Conjunction Introduction: conj P Q p_proof q_proof
200        // P, Q ⊢ P ∧ Q becomes (conj P Q p q) : And P Q
201        InferenceRule::ConjunctionIntro => {
202            if tree.premises.len() != 2 {
203                return Err(KernelError::CertificationError(
204                    "ConjunctionIntro requires exactly 2 premises".to_string(),
205                ));
206            }
207            let (p_type, q_type) = extract_and_types(&tree.conclusion)?;
208            let p_term = certify(&tree.premises[0], ctx)?;
209            let q_term = certify(&tree.premises[1], ctx)?;
210
211            // Build: conj P Q p_proof q_proof (fully curried)
212            let conj = Term::Global("conj".to_string());
213            let applied = Term::App(
214                Box::new(Term::App(
215                    Box::new(Term::App(
216                        Box::new(Term::App(Box::new(conj), Box::new(p_type))),
217                        Box::new(q_type),
218                    )),
219                    Box::new(p_term),
220                )),
221                Box::new(q_term),
222            );
223            Ok(applied)
224        }
225
226        // Universal Instantiation: forall x.P(x) |- P(t)
227        // Curry-Howard: Apply the forall-proof (a function) to the witness term
228        InferenceRule::UniversalInst(witness) => {
229            if tree.premises.len() != 1 {
230                return Err(KernelError::CertificationError(
231                    "UniversalInst requires exactly 1 premise".to_string(),
232                ));
233            }
234            let forall_proof = certify(&tree.premises[0], ctx)?;
235            // Check if witness is a local variable (inside a lambda body)
236            let witness_term = if ctx.is_local(witness) {
237                Term::Var(witness.clone())
238            } else {
239                Term::Global(witness.clone())
240            };
241            Ok(Term::App(Box::new(forall_proof), Box::new(witness_term)))
242        }
243
244        // Universal Introduction: Γ, x:T ⊢ P(x) implies Γ ⊢ ∀x:T. P(x)
245        // Curry-Howard: Wrap the body proof in a Lambda
246        InferenceRule::UniversalIntro { variable, var_type } => {
247            if tree.premises.len() != 1 {
248                return Err(KernelError::CertificationError(
249                    "UniversalIntro requires exactly 1 premise".to_string(),
250                ));
251            }
252
253            // Build the type term
254            let type_term = Term::Global(var_type.clone());
255
256            // Certify body with variable in scope
257            let extended_ctx = ctx.with_local(variable);
258            let body_term = certify(&tree.premises[0], &extended_ctx)?;
259
260            // Wrap in Lambda
261            Ok(Term::Lambda {
262                param: variable.clone(),
263                param_type: Box::new(type_term),
264                body: Box::new(body_term),
265            })
266        }
267
268        // Structural Induction: P(0), ∀k(P(k) → P(S(k))) ⊢ ∀n P(n)
269        // Curry-Howard: fix rec. λn. match n { Zero => base, Succ k => step }
270        InferenceRule::StructuralInduction {
271            variable: var_name,
272            ind_type,
273            step_var,
274        } => {
275            if tree.premises.len() != 2 {
276                return Err(KernelError::CertificationError(
277                    "StructuralInduction requires exactly 2 premises (base, step)".to_string(),
278                ));
279            }
280
281            // Extract motive body from ForAll conclusion
282            let motive_body = extract_motive_body(&tree.conclusion, var_name)?;
283
284            // Generate fix name
285            let fix_name = format!("rec_{}", var_name);
286
287            // === Certify Base Case ===
288            // Base case is certified in the current context (no IH)
289            let base_term = certify(&tree.premises[0], ctx)?;
290
291            // === Certify Step Case ===
292            // IH: P(k) - compute what IH looks like by substituting n -> k
293            let ih_conclusion = compute_ih_conclusion(&tree.conclusion, var_name, step_var)?;
294
295            // Create context with:
296            // 1. step_var as a local (bound by the Succ case lambda)
297            // 2. induction state (for IH resolution)
298            let step_ctx = ctx
299                .with_local(step_var)
300                .with_induction(&fix_name, step_var, ih_conclusion);
301
302            let step_body = certify(&tree.premises[1], &step_ctx)?;
303
304            // Wrap step body in lambda: λk. step_body
305            let step_term = Term::Lambda {
306                param: step_var.clone(),
307                param_type: Box::new(Term::Global(ind_type.clone())),
308                body: Box::new(step_body),
309            };
310
311            // === Build Match ===
312            // match n return (λn:Nat. P(n)) with { Zero => base, Succ k => step }
313            // The motive uses var_name so body references are properly bound
314            let match_term = Term::Match {
315                discriminant: Box::new(Term::Var(var_name.clone())),
316                motive: Box::new(build_motive(ind_type, &motive_body, var_name)),
317                cases: vec![base_term, step_term],
318            };
319
320            // === Build Lambda ===
321            // λn:Nat. match n ...
322            let lambda_term = Term::Lambda {
323                param: var_name.clone(),
324                param_type: Box::new(Term::Global(ind_type.clone())),
325                body: Box::new(match_term),
326            };
327
328            // === Build Fixpoint ===
329            // fix rec_n. λn. match n ...
330            Ok(Term::Fix {
331                name: fix_name,
332                body: Box::new(lambda_term),
333            })
334        }
335
336        // Existential Introduction: P(w) ⊢ ∃x.P(x)
337        // Curry-Howard: witness A P w proof
338        InferenceRule::ExistentialIntro {
339            witness: witness_str,
340            witness_type,
341        } => {
342            if tree.premises.len() != 1 {
343                return Err(KernelError::CertificationError(
344                    "ExistentialIntro requires exactly 1 premise".to_string(),
345                ));
346            }
347
348            // Extract variable and body from Exists conclusion
349            let (variable, body) = match &tree.conclusion {
350                ProofExpr::Exists { variable, body } => (variable.clone(), body.as_ref().clone()),
351                _ => {
352                    return Err(KernelError::CertificationError(
353                        "ExistentialIntro conclusion must be Exists".to_string(),
354                    ))
355                }
356            };
357
358            // Determine witness term (local or global)
359            let witness_term = if ctx.is_local(witness_str) {
360                Term::Var(witness_str.clone())
361            } else {
362                Term::Global(witness_str.clone())
363            };
364
365            // Certify the premise (proof of P(witness))
366            let proof_term = certify(&tree.premises[0], ctx)?;
367
368            // The type A comes from the InferenceRule (EXPLICIT INTENT)
369            let type_a = Term::Global(witness_type.clone());
370
371            // Build predicate P
372            // If body is just "P(x)" where x is the bound variable, use P directly
373            // Otherwise, build λvar. body_type
374            let predicate = match &body {
375                // Simple case: P(x) where x is the existential variable
376                ProofExpr::Predicate { name, args, .. }
377                    if args.len() == 1
378                        && matches!(&args[0], ProofTerm::Variable(v) if v == &variable) =>
379                {
380                    Term::Global(name.clone())
381                }
382                // General case: wrap in lambda
383                _ => {
384                    let body_type = proof_expr_to_type(&body)?;
385                    Term::Lambda {
386                        param: variable.clone(),
387                        param_type: Box::new(type_a.clone()),
388                        body: Box::new(body_type),
389                    }
390                }
391            };
392
393            // Build: witness A P w proof
394            // witness : Π(A:Type). Π(P:A→Prop). Π(x:A). P(x) → Ex A P
395            let witness_ctor = Term::Global("witness".to_string());
396
397            let applied = Term::App(
398                Box::new(Term::App(
399                    Box::new(Term::App(
400                        Box::new(Term::App(Box::new(witness_ctor), Box::new(type_a))),
401                        Box::new(predicate),
402                    )),
403                    Box::new(witness_term),
404                )),
405                Box::new(proof_term),
406            );
407
408            Ok(applied)
409        }
410
411        // Equality Rewriting (Leibniz's Law)
412        // a = b, P(a) ⊢ P(b)
413        // Uses: Eq_rec A x P proof y eq_proof
414        InferenceRule::Rewrite { from, to } => {
415            if tree.premises.len() != 2 {
416                return Err(KernelError::CertificationError(
417                    "Rewrite requires exactly 2 premises (equality, source)".to_string(),
418                ));
419            }
420
421            // Certify both premises
422            let eq_proof = certify(&tree.premises[0], ctx)?;
423            let source_proof = certify(&tree.premises[1], ctx)?;
424
425            // Convert terms
426            let from_term = proof_term_to_kernel_term(from)?;
427            let to_term = proof_term_to_kernel_term(to)?;
428
429            // Build the predicate P as a lambda that wraps the goal
430            // P = λz. (goal with 'to' replaced by z)
431            let predicate = build_equality_predicate(&tree.conclusion, to)?;
432
433            // Eq_rec : Π(A:Type). Π(x:A). Π(P:A→Prop). P x → Π(y:A). Eq A x y → P y
434            // Build: Eq_rec Entity from P source_proof to eq_proof
435            let eq_rec = Term::Global("Eq_rec".to_string());
436            let entity = Term::Global("Entity".to_string());
437
438            let applied = Term::App(
439                Box::new(Term::App(
440                    Box::new(Term::App(
441                        Box::new(Term::App(
442                            Box::new(Term::App(
443                                Box::new(Term::App(Box::new(eq_rec), Box::new(entity))),
444                                Box::new(from_term),
445                            )),
446                            Box::new(predicate),
447                        )),
448                        Box::new(source_proof),
449                    )),
450                    Box::new(to_term),
451                )),
452                Box::new(eq_proof),
453            );
454
455            Ok(applied)
456        }
457
458        // Equality Symmetry: a = b ⊢ b = a
459        // Uses: Eq_sym A x y proof
460        InferenceRule::EqualitySymmetry => {
461            if tree.premises.len() != 1 {
462                return Err(KernelError::CertificationError(
463                    "EqualitySymmetry requires exactly 1 premise".to_string(),
464                ));
465            }
466
467            let premise_proof = certify(&tree.premises[0], ctx)?;
468
469            // Extract x and y from the premise conclusion (x = y)
470            let (x, y) = match &tree.premises[0].conclusion {
471                ProofExpr::Identity(l, r) => {
472                    (proof_term_to_kernel_term(l)?, proof_term_to_kernel_term(r)?)
473                }
474                _ => {
475                    return Err(KernelError::CertificationError(
476                        "EqualitySymmetry premise must be an Identity".to_string(),
477                    ))
478                }
479            };
480
481            // Eq_sym : Π(A:Type). Π(x:A). Π(y:A). Eq A x y → Eq A y x
482            // Build: Eq_sym Entity x y proof
483            let eq_sym = Term::Global("Eq_sym".to_string());
484            let entity = Term::Global("Entity".to_string());
485
486            Ok(Term::App(
487                Box::new(Term::App(
488                    Box::new(Term::App(
489                        Box::new(Term::App(Box::new(eq_sym), Box::new(entity))),
490                        Box::new(x),
491                    )),
492                    Box::new(y),
493                )),
494                Box::new(premise_proof),
495            ))
496        }
497
498        // Equality Transitivity: a = b, b = c ⊢ a = c
499        // Uses: Eq_trans A x y z proof1 proof2
500        InferenceRule::EqualityTransitivity => {
501            if tree.premises.len() != 2 {
502                return Err(KernelError::CertificationError(
503                    "EqualityTransitivity requires exactly 2 premises".to_string(),
504                ));
505            }
506
507            let proof1 = certify(&tree.premises[0], ctx)?;
508            let proof2 = certify(&tree.premises[1], ctx)?;
509
510            // Extract x, y from first premise (x = y)
511            let (x, y) = match &tree.premises[0].conclusion {
512                ProofExpr::Identity(l, r) => {
513                    (proof_term_to_kernel_term(l)?, proof_term_to_kernel_term(r)?)
514                }
515                _ => {
516                    return Err(KernelError::CertificationError(
517                        "EqualityTransitivity first premise must be Identity".to_string(),
518                    ))
519                }
520            };
521
522            // Extract z from second premise (y = z)
523            let z = match &tree.premises[1].conclusion {
524                ProofExpr::Identity(_, r) => proof_term_to_kernel_term(r)?,
525                _ => {
526                    return Err(KernelError::CertificationError(
527                        "EqualityTransitivity second premise must be Identity".to_string(),
528                    ))
529                }
530            };
531
532            // Eq_trans : Π(A:Type). Π(x:A). Π(y:A). Π(z:A). Eq A x y → Eq A y z → Eq A x z
533            // Build: Eq_trans Entity x y z proof1 proof2
534            let eq_trans = Term::Global("Eq_trans".to_string());
535            let entity = Term::Global("Entity".to_string());
536
537            Ok(Term::App(
538                Box::new(Term::App(
539                    Box::new(Term::App(
540                        Box::new(Term::App(
541                            Box::new(Term::App(
542                                Box::new(Term::App(Box::new(eq_trans), Box::new(entity))),
543                                Box::new(x),
544                            )),
545                            Box::new(y),
546                        )),
547                        Box::new(z),
548                    )),
549                    Box::new(proof1),
550                )),
551                Box::new(proof2),
552            ))
553        }
554
555        // Fallback for unimplemented rules
556        rule => Err(KernelError::CertificationError(format!(
557            "Certification not implemented for {:?}",
558            rule
559        ))),
560    }
561}
562
563/// Build the equality predicate for rewriting.
564/// Given goal P(y) and term y, extracts P.
565///
566/// For simple predicates like `mortal(Superman)`, returns `mortal` directly.
567/// For complex goals, builds `λz. (goal with y replaced by z)`.
568fn build_equality_predicate(goal: &ProofExpr, replace_term: &ProofTerm) -> KernelResult<Term> {
569    // Simple case: if the goal is P(y), just use P as the predicate
570    // This avoids the beta-reduction issue with (λz. P(z)) x
571    if let ProofExpr::Predicate { name, args, .. } = goal {
572        if args.len() == 1 && &args[0] == replace_term {
573            return Ok(Term::Global(name.clone()));
574        }
575    }
576
577    // General case: build λz. (goal with replace_term replaced by z)
578    let goal_type = proof_expr_to_type(goal)?;
579    let param_name = "_eq_var".to_string();
580    let substituted = substitute_term_in_kernel(
581        &goal_type,
582        &proof_term_to_kernel_term(replace_term)?,
583        &Term::Var(param_name.clone()),
584    );
585
586    Ok(Term::Lambda {
587        param: param_name,
588        param_type: Box::new(Term::Global("Entity".to_string())),
589        body: Box::new(substituted),
590    })
591}
592
593/// Substitute a kernel term for another in a kernel Term.
594fn substitute_term_in_kernel(term: &Term, from: &Term, to: &Term) -> Term {
595    if term == from {
596        return to.clone();
597    }
598    match term {
599        Term::App(f, a) => Term::App(
600            Box::new(substitute_term_in_kernel(f, from, to)),
601            Box::new(substitute_term_in_kernel(a, from, to)),
602        ),
603        Term::Pi {
604            param,
605            param_type,
606            body_type,
607        } => Term::Pi {
608            param: param.clone(),
609            param_type: Box::new(substitute_term_in_kernel(param_type, from, to)),
610            body_type: Box::new(substitute_term_in_kernel(body_type, from, to)),
611        },
612        Term::Lambda {
613            param,
614            param_type,
615            body,
616        } => Term::Lambda {
617            param: param.clone(),
618            param_type: Box::new(substitute_term_in_kernel(param_type, from, to)),
619            body: Box::new(substitute_term_in_kernel(body, from, to)),
620        },
621        Term::Match {
622            discriminant,
623            motive,
624            cases,
625        } => Term::Match {
626            discriminant: Box::new(substitute_term_in_kernel(discriminant, from, to)),
627            motive: Box::new(substitute_term_in_kernel(motive, from, to)),
628            cases: cases
629                .iter()
630                .map(|c| substitute_term_in_kernel(c, from, to))
631                .collect(),
632        },
633        Term::Fix { name, body } => Term::Fix {
634            name: name.clone(),
635            body: Box::new(substitute_term_in_kernel(body, from, to)),
636        },
637        other => other.clone(),
638    }
639}
640
641/// Certify a hypothesis reference (Axiom or PremiseMatch).
642fn certify_hypothesis(conclusion: &ProofExpr, ctx: &CertificationContext) -> KernelResult<Term> {
643    // Check if this is an IH reference (MUST check first!)
644    if let Some(ih_term) = ctx.get_ih_term(conclusion) {
645        return Ok(ih_term);
646    }
647
648    match conclusion {
649        ProofExpr::Atom(name) => {
650            // Check locals first (for lambda-bound variables)
651            if ctx.is_local(name) {
652                return Ok(Term::Var(name.clone()));
653            }
654            // Then check globals
655            if ctx.kernel_ctx.get_global(name).is_some() {
656                Ok(Term::Global(name.clone()))
657            } else {
658                Err(KernelError::CertificationError(format!(
659                    "Unknown hypothesis: {}",
660                    name
661                )))
662            }
663        }
664        // For predicate hypotheses, look up by type in kernel context
665        ProofExpr::Predicate { name, args, .. } => {
666            // Build the target type: P(a, b, ...) as nested application
667            let mut target_type = Term::Global(name.clone());
668            for arg in args {
669                let arg_term = proof_term_to_kernel_term(arg)?;
670                target_type = Term::App(Box::new(target_type), Box::new(arg_term));
671            }
672
673            // Search declarations for one with matching type
674            for (decl_name, decl_type) in ctx.kernel_ctx.iter_declarations() {
675                if types_structurally_match(&target_type, decl_type) {
676                    return Ok(Term::Global(decl_name.to_string()));
677                }
678            }
679
680            Err(KernelError::CertificationError(format!(
681                "Cannot find hypothesis with type: {:?}",
682                conclusion
683            )))
684        }
685        // For ForAll, Implies, and Identity hypotheses, look up by type in kernel context
686        ProofExpr::ForAll { .. } | ProofExpr::Implies(_, _) | ProofExpr::Identity(_, _) => {
687            // Convert the ProofExpr to a kernel type
688            let target_type = proof_expr_to_type(conclusion)?;
689
690            // Search declarations for one with matching type
691            for (name, decl_type) in ctx.kernel_ctx.iter_declarations() {
692                if types_structurally_match(&target_type, decl_type) {
693                    return Ok(Term::Global(name.to_string()));
694                }
695            }
696
697            Err(KernelError::CertificationError(format!(
698                "Cannot find hypothesis with type matching: {:?}",
699                conclusion
700            )))
701        }
702        _ => Err(KernelError::CertificationError(format!(
703            "Cannot certify hypothesis: {:?}",
704            conclusion
705        ))),
706    }
707}
708
709/// Check if two kernel terms are alpha-equivalent.
710/// Two terms are alpha-equivalent if they are the same up to renaming of bound variables.
711fn types_structurally_match(a: &Term, b: &Term) -> bool {
712    // Use a helper with a mapping from bound vars in a to bound vars in b
713    types_alpha_equiv(a, b, &mut Vec::new())
714}
715
716/// Check alpha-equivalence with a mapping of bound variable pairs.
717/// `bindings` tracks pairs of corresponding bound variable names.
718fn types_alpha_equiv(a: &Term, b: &Term, bindings: &mut Vec<(String, String)>) -> bool {
719    match (a, b) {
720        (Term::Sort(u1), Term::Sort(u2)) => u1 == u2,
721        (Term::Var(v1), Term::Var(v2)) => {
722            // Check if these are corresponding bound variables
723            for (bound_a, bound_b) in bindings.iter().rev() {
724                if v1 == bound_a {
725                    return v2 == bound_b;
726                }
727                if v2 == bound_b {
728                    return false; // v2 is bound but v1 doesn't match
729                }
730            }
731            // Both are free variables - must have same name
732            v1 == v2
733        }
734        (Term::Global(g1), Term::Global(g2)) => g1 == g2,
735        (Term::App(f1, a1), Term::App(f2, a2)) => {
736            types_alpha_equiv(f1, f2, bindings) && types_alpha_equiv(a1, a2, bindings)
737        }
738        (
739            Term::Pi {
740                param: p1,
741                param_type: pt1,
742                body_type: bt1,
743            },
744            Term::Pi {
745                param: p2,
746                param_type: pt2,
747                body_type: bt2,
748            },
749        ) => {
750            // Parameter types must match in current scope
751            if !types_alpha_equiv(pt1, pt2, bindings) {
752                return false;
753            }
754            // Body types must match with the new binding
755            bindings.push((p1.clone(), p2.clone()));
756            let result = types_alpha_equiv(bt1, bt2, bindings);
757            bindings.pop();
758            result
759        }
760        (
761            Term::Lambda {
762                param: p1,
763                param_type: pt1,
764                body: b1,
765            },
766            Term::Lambda {
767                param: p2,
768                param_type: pt2,
769                body: b2,
770            },
771        ) => {
772            // Parameter types must match in current scope
773            if !types_alpha_equiv(pt1, pt2, bindings) {
774                return false;
775            }
776            // Bodies must match with the new binding
777            bindings.push((p1.clone(), p2.clone()));
778            let result = types_alpha_equiv(b1, b2, bindings);
779            bindings.pop();
780            result
781        }
782        _ => false,
783    }
784}
785
786/// Extract P and Q types from an And(P, Q) conclusion.
787fn extract_and_types(conclusion: &ProofExpr) -> KernelResult<(Term, Term)> {
788    match conclusion {
789        ProofExpr::And(p, q) => {
790            let p_term = proof_expr_to_type(p)?;
791            let q_term = proof_expr_to_type(q)?;
792            Ok((p_term, q_term))
793        }
794        _ => Err(KernelError::CertificationError(format!(
795            "Expected And, got {:?}",
796            conclusion
797        ))),
798    }
799}
800
801/// Convert a ProofExpr (proposition) to a kernel Term (type).
802fn proof_expr_to_type(expr: &ProofExpr) -> KernelResult<Term> {
803    match expr {
804        ProofExpr::Atom(name) => Ok(Term::Global(name.clone())),
805        ProofExpr::And(p, q) => Ok(Term::App(
806            Box::new(Term::App(
807                Box::new(Term::Global("And".to_string())),
808                Box::new(proof_expr_to_type(p)?),
809            )),
810            Box::new(proof_expr_to_type(q)?),
811        )),
812        ProofExpr::Or(p, q) => Ok(Term::App(
813            Box::new(Term::App(
814                Box::new(Term::Global("Or".to_string())),
815                Box::new(proof_expr_to_type(p)?),
816            )),
817            Box::new(proof_expr_to_type(q)?),
818        )),
819        ProofExpr::Implies(p, q) => Ok(Term::Pi {
820            param: "_".to_string(),
821            param_type: Box::new(proof_expr_to_type(p)?),
822            body_type: Box::new(proof_expr_to_type(q)?),
823        }),
824        // ForAll ∀x.P(x) becomes Π(x:Entity). P(x)
825        ProofExpr::ForAll { variable, body } => {
826            let body_type = proof_expr_to_type(body)?;
827            Ok(Term::Pi {
828                param: variable.clone(),
829                param_type: Box::new(Term::Global("Entity".to_string())),
830                body_type: Box::new(body_type),
831            })
832        }
833        // Predicate P(x, y, ...) becomes (P x y ...)
834        ProofExpr::Predicate { name, args, .. } => {
835            let mut result = Term::Global(name.clone());
836            for arg in args {
837                let arg_term = proof_term_to_kernel_term(arg)?;
838                result = Term::App(Box::new(result), Box::new(arg_term));
839            }
840            Ok(result)
841        }
842        // Identity t1 = t2 becomes (Eq Entity t1 t2)
843        ProofExpr::Identity(l, r) => {
844            let l_term = proof_term_to_kernel_term(l)?;
845            let r_term = proof_term_to_kernel_term(r)?;
846            Ok(Term::App(
847                Box::new(Term::App(
848                    Box::new(Term::App(
849                        Box::new(Term::Global("Eq".to_string())),
850                        Box::new(Term::Global("Entity".to_string())),
851                    )),
852                    Box::new(l_term),
853                )),
854                Box::new(r_term),
855            ))
856        }
857        // Exists ∃x.P(x) becomes Ex ? (λx.P(x))
858        // Note: Variable type defaults to Nat - use ExistentialIntro handler for explicit types
859        ProofExpr::Exists { variable, body } => {
860            let var_type = Term::Global("Nat".to_string()); // Default type
861            let body_type = proof_expr_to_type(body)?;
862
863            // Build: Ex VarType (λvar. body_type)
864            let predicate = Term::Lambda {
865                param: variable.clone(),
866                param_type: Box::new(var_type.clone()),
867                body: Box::new(body_type),
868            };
869
870            Ok(Term::App(
871                Box::new(Term::App(
872                    Box::new(Term::Global("Ex".to_string())),
873                    Box::new(var_type),
874                )),
875                Box::new(predicate),
876            ))
877        }
878        _ => Err(KernelError::CertificationError(format!(
879            "Cannot convert {:?} to kernel type",
880            expr
881        ))),
882    }
883}
884
885/// Convert a ProofTerm to a kernel Term.
886///
887/// This bridges the proof engine's term representation to the kernel's.
888/// Used for converting witness terms in quantifier instantiation.
889fn proof_term_to_kernel_term(term: &ProofTerm) -> KernelResult<Term> {
890    match term {
891        ProofTerm::Constant(name) => Ok(Term::Global(name.clone())),
892        ProofTerm::Variable(name) => Ok(Term::Var(name.clone())),
893        ProofTerm::BoundVarRef(name) => Ok(Term::Var(name.clone())),
894        ProofTerm::Function(name, args) => {
895            // Build nested applications: f(a, b) -> ((f a) b)
896            let mut result = Term::Global(name.clone());
897            for arg in args {
898                let arg_term = proof_term_to_kernel_term(arg)?;
899                result = Term::App(Box::new(result), Box::new(arg_term));
900            }
901            Ok(result)
902        }
903        ProofTerm::Group(_) => Err(KernelError::CertificationError(
904            "Cannot convert Group to kernel term".to_string(),
905        )),
906    }
907}
908
909// =============================================================================
910// INDUCTION HELPER FUNCTIONS
911// =============================================================================
912
913/// Extract motive body from ForAll conclusion.
914/// The motive is the predicate body of the ForAll that we're proving by induction.
915fn extract_motive_body(conclusion: &ProofExpr, var_name: &str) -> KernelResult<Term> {
916    match conclusion {
917        ProofExpr::ForAll { variable, body } if variable == var_name => {
918            // Convert body to kernel type (the motive body)
919            proof_expr_to_type(body)
920        }
921        _ => Err(KernelError::CertificationError(format!(
922            "StructuralInduction conclusion must be ForAll over {}, got {:?}",
923            var_name, conclusion
924        ))),
925    }
926}
927
928/// Compute what the IH conclusion looks like: P(k) when proving P(n).
929/// Substitutes the induction variable with the step variable in the ForAll body.
930fn compute_ih_conclusion(
931    conclusion: &ProofExpr,
932    orig_var: &str,
933    step_var: &str,
934) -> KernelResult<ProofExpr> {
935    match conclusion {
936        ProofExpr::ForAll { body, .. } => Ok(substitute_var_in_expr(body, orig_var, step_var)),
937        _ => Err(KernelError::CertificationError(
938            "Expected ForAll for IH computation".to_string(),
939        )),
940    }
941}
942
943/// Build the match motive: λn:IndType. ResultType
944/// The motive_param should be the same variable name used in the body
945/// so that the body's references are properly captured.
946fn build_motive(ind_type: &str, result_type: &Term, motive_param: &str) -> Term {
947    Term::Lambda {
948        param: motive_param.to_string(),
949        param_type: Box::new(Term::Global(ind_type.to_string())),
950        body: Box::new(result_type.clone()),
951    }
952}
953
954/// Substitute variable name in ProofExpr (recursive).
955fn substitute_var_in_expr(expr: &ProofExpr, from: &str, to: &str) -> ProofExpr {
956    match expr {
957        ProofExpr::Atom(s) if s == from => ProofExpr::Atom(to.to_string()),
958        ProofExpr::Atom(s) => ProofExpr::Atom(s.clone()),
959
960        ProofExpr::TypedVar { name, typename } => ProofExpr::TypedVar {
961            name: if name == from {
962                to.to_string()
963            } else {
964                name.clone()
965            },
966            typename: typename.clone(),
967        },
968
969        ProofExpr::Predicate { name, args, world } => ProofExpr::Predicate {
970            name: name.clone(),
971            args: args
972                .iter()
973                .map(|a| substitute_var_in_term(a, from, to))
974                .collect(),
975            world: world.clone(),
976        },
977
978        ProofExpr::Identity(l, r) => ProofExpr::Identity(
979            substitute_var_in_term(l, from, to),
980            substitute_var_in_term(r, from, to),
981        ),
982
983        ProofExpr::And(l, r) => ProofExpr::And(
984            Box::new(substitute_var_in_expr(l, from, to)),
985            Box::new(substitute_var_in_expr(r, from, to)),
986        ),
987
988        ProofExpr::Or(l, r) => ProofExpr::Or(
989            Box::new(substitute_var_in_expr(l, from, to)),
990            Box::new(substitute_var_in_expr(r, from, to)),
991        ),
992
993        ProofExpr::Implies(l, r) => ProofExpr::Implies(
994            Box::new(substitute_var_in_expr(l, from, to)),
995            Box::new(substitute_var_in_expr(r, from, to)),
996        ),
997
998        ProofExpr::Iff(l, r) => ProofExpr::Iff(
999            Box::new(substitute_var_in_expr(l, from, to)),
1000            Box::new(substitute_var_in_expr(r, from, to)),
1001        ),
1002
1003        ProofExpr::Not(inner) => {
1004            ProofExpr::Not(Box::new(substitute_var_in_expr(inner, from, to)))
1005        }
1006
1007        // Don't substitute inside binding that shadows
1008        ProofExpr::ForAll { variable, body } if variable != from => ProofExpr::ForAll {
1009            variable: variable.clone(),
1010            body: Box::new(substitute_var_in_expr(body, from, to)),
1011        },
1012
1013        ProofExpr::Exists { variable, body } if variable != from => ProofExpr::Exists {
1014            variable: variable.clone(),
1015            body: Box::new(substitute_var_in_expr(body, from, to)),
1016        },
1017
1018        ProofExpr::Lambda { variable, body } if variable != from => ProofExpr::Lambda {
1019            variable: variable.clone(),
1020            body: Box::new(substitute_var_in_expr(body, from, to)),
1021        },
1022
1023        ProofExpr::App(f, a) => ProofExpr::App(
1024            Box::new(substitute_var_in_expr(f, from, to)),
1025            Box::new(substitute_var_in_expr(a, from, to)),
1026        ),
1027
1028        ProofExpr::Term(t) => ProofExpr::Term(substitute_var_in_term(t, from, to)),
1029
1030        ProofExpr::Ctor { name, args } => ProofExpr::Ctor {
1031            name: name.clone(),
1032            args: args
1033                .iter()
1034                .map(|a| substitute_var_in_expr(a, from, to))
1035                .collect(),
1036        },
1037
1038        // For anything else (shadowed bindings, modals, temporals, etc.), clone as-is
1039        other => other.clone(),
1040    }
1041}
1042
1043/// Substitute variable name in ProofTerm.
1044fn substitute_var_in_term(term: &ProofTerm, from: &str, to: &str) -> ProofTerm {
1045    match term {
1046        ProofTerm::Variable(s) if s == from => ProofTerm::Variable(to.to_string()),
1047        ProofTerm::Variable(s) => ProofTerm::Variable(s.clone()),
1048        ProofTerm::Constant(s) => ProofTerm::Constant(s.clone()),
1049        ProofTerm::BoundVarRef(s) if s == from => ProofTerm::BoundVarRef(to.to_string()),
1050        ProofTerm::BoundVarRef(s) => ProofTerm::BoundVarRef(s.clone()),
1051        ProofTerm::Function(name, args) => ProofTerm::Function(
1052            name.clone(),
1053            args.iter()
1054                .map(|a| substitute_var_in_term(a, from, to))
1055                .collect(),
1056        ),
1057        ProofTerm::Group(terms) => ProofTerm::Group(
1058            terms
1059                .iter()
1060                .map(|t| substitute_var_in_term(t, from, to))
1061                .collect(),
1062        ),
1063    }
1064}