Skip to main content

logicaffeine_kernel/
certificate.rs

1//! Independently re-checkable proof certificates — the De Bruijn criterion.
2//!
3//! A proof in LOGOS is a [`Term`]. A *certificate* bundles that term with the
4//! proposition it claims to prove, so anyone can re-validate it against the
5//! prelude axioms — with **no** access to the parser, the proof-search engine,
6//! the certifier, or any oracle. The entire trusted surface of a re-check is
7//! this crate (`logicaffeine_kernel`) plus its single dependency
8//! (`logicaffeine_base`).
9//!
10//! # The integrity rule
11//!
12//! A certificate carries **only** the proof term and the claimed type — never a
13//! context. `recheck` rebuilds the trusted axiom context *itself* via
14//! [`StandardLibrary::register`], so a malicious certificate cannot smuggle in a
15//! bogus axiom (e.g. a free proof of `False`). You trust the prelude's primitives
16//! and the type-checker; you do not trust the certificate's provenance.
17//!
18//! # Three kinds, one family
19//!
20//! * [`Certificate`] — a proof in the bare prelude.
21//! * [`TheoremCertificate`] — a proof from a disclosed environment delta over the prelude.
22//! * [`EnvironmentCertificate`] (`logos-environment-certificate/1` or `/2`) — a proof together with every
23//!   declaration it needs (inductives, constructors, recursors, universe-polymorphic definitions,
24//!   opaque theorems carried with their proofs, structure η, quotient primitives, assumptions),
25//!   rebuilt from a declared base with every entry kernel-validated. This is what a Lean import is
26//!   packaged as.
27//!
28//! [`read_certificate`] reads any of them by one door. Every struct refuses unknown fields, and a
29//! certificate minted against a retired prelude (`logos-coc-1` …) is refused as incompatible — the
30//! tag is never rewritten to fit.
31//!
32//! This module is available under the `serde` feature.
33
34use serde::{Deserialize, Serialize};
35
36use std::collections::{BTreeMap, BTreeSet, HashMap};
37
38use crate::prelude::StandardLibrary;
39use crate::{
40    double_check, infer_type, is_subtype, Context, DoubleCheck, KernelError, Term, Universe,
41};
42
43/// The prelude/axiom-set version a certificate was produced against. A
44/// re-checker may refuse certificates minted against an axiom set it does not
45/// recognise. Bump this whenever the trusted prelude changes.
46///
47/// `logos-coc-6` DEFINES `Quot_rec`. The dependent quotient eliminator is a checked
48/// definition — `Quot_lift` into Lean's `PSigma`, `Quot_ind`, and a transport — so it
49/// leaves the trusted base, and its axiom closure is `Quot_sound`, read through its
50/// body. `Quot_lift`'s target is `Sort v`, as Lean's is, which that definition needs.
51/// `Quot`, `Quot_mk`, `Quot_lift` and `Quot_ind` keep the registered primitive
52/// identities tied to their computation rules and are not reported as assumption
53/// leaves while that identity is live. `Quot_sound`, `propext`, `Classical_choice`
54/// and the opaque `Float` operation surface remain assumptions.
55///
56/// Earlier tags, including `logos-coc-5`, are refused even when a particular
57/// proof would still check: a certificate claims to have been checked against
58/// its stated assumptions, and no validated migration rewrites that claim.
59/// The wire schemas and entry encodings are unchanged; only the trusted prelude
60/// version changes with its primitive computation and assumption accounting.
61pub const PRELUDE_VERSION: &str = "logos-coc-6";
62
63/// A self-contained, re-checkable proof certificate.
64///
65/// `proof_term` is claimed to have type `claimed_type` in the standard prelude
66/// context. Nothing else is trusted: the re-checker supplies its own axioms.
67#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
68#[serde(deny_unknown_fields)]
69pub struct Certificate {
70    /// The proof (a lambda-calculus term).
71    pub proof_term: Term,
72    /// The proposition the proof claims to establish (a type).
73    pub claimed_type: Term,
74    /// The axiom-set version this certificate was produced against.
75    pub prelude_version: String,
76}
77
78impl Certificate {
79    /// Bundle a proof term with the proposition it claims to prove.
80    pub fn new(proof_term: Term, claimed_type: Term) -> Self {
81        Self {
82            proof_term: proof_term.into_stripped(),
83            claimed_type: claimed_type.into_stripped(),
84            prelude_version: PRELUDE_VERSION.to_string(),
85        }
86    }
87
88    /// The axioms this certificate's proof actually rests on — our `#print axioms`.
89    ///
90    /// Computed against the trusted base the certificate would be RE-CHECKED in, which is the
91    /// only context whose answer is worth anything: a closure taken against the minting side's
92    /// environment reports what the prover believed, not what the verifier is being asked to
93    /// accept.
94    ///
95    /// Over the proof term AND the claimed type, because a certificate is both. An axiom named
96    /// in the theorem's own STATEMENT is a real dependency — `Π(h : Nonempty Nat). Eq Nat
97    /// (Classical_choice Nat h) Zero` rests on choice whatever the proof term looks like — and
98    /// Lean reports it: `CollectAxioms` visits a constant's `type` as well as its `value`.
99    pub fn axioms_used(&self) -> AxiomUsage {
100        let ctx = StandardLibrary::context();
101        let mut usage = axiom_dependencies(ctx, &self.proof_term);
102        usage.absorb(axiom_dependencies(ctx, &self.claimed_type));
103        usage
104    }
105}
106
107/// Independently re-validate a certificate.
108///
109/// Builds a *fresh* trusted context from the standard library, infers the type
110/// of the certificate's proof term, and requires it to match the claimed type
111/// (up to definitional equality). Returns `Ok(())` only if the proof genuinely
112/// establishes the claimed proposition.
113///
114/// This function is the whole trusted core of an independent check. It does not
115/// look at where the certificate came from.
116pub fn recheck(cert: &Certificate) -> Result<(), KernelError> {
117    if cert.prelude_version != PRELUDE_VERSION {
118        return Err(KernelError::CertificationError(format!(
119            "certificate was minted against prelude '{}', but this checker is '{}'",
120            cert.prelude_version, PRELUDE_VERSION
121        )));
122    }
123
124    // The trusted axiom context is this checker's own standard library registration — never a
125    // context the certificate might carry.
126    let ctx = StandardLibrary::context();
127
128    // BEFORE type-checking: what does this proof rest on? An ADMITTED step — `sorryAx` and its
129    // relatives — type-checks perfectly and proves anything, so "it type-checks" is precisely the
130    // wrong question to ask first. The closure is transitive, so a `sorry` three lemmas upstream
131    // is caught by the same check as one written in the proof term itself.
132    let mut usage = axiom_dependencies(ctx, &cert.proof_term);
133    usage.absorb(axiom_dependencies(ctx, &cert.claimed_type));
134    if usage.is_admitted() {
135        return Err(KernelError::CertificationError(
136            usage.render("this certificate"),
137        ));
138    }
139
140    // The term must type-check at all...
141    let inferred = infer_type(ctx, &cert.proof_term)?;
142
143    // ...and its type must be the claimed proposition.
144    if is_subtype(ctx, &inferred, &cert.claimed_type) {
145        Ok(())
146    } else {
147        Err(KernelError::CertificationError(format!(
148            "certificate term proves a different proposition: has type {:?}, claims {:?}",
149            inferred, cert.claimed_type
150        )))
151    }
152}
153
154// ---------------------------------------------------------------------------
155// Theorem certificates — a proof discharged FROM disclosed premises.
156// ---------------------------------------------------------------------------
157//
158// The prelude-only [`Certificate`] above proves `⊢ goal` in the bare axiom set:
159// perfect for a purely logical theorem like `∀(P:Prop). P → P`. But a real
160// `## Theorem` block is proved `Γ ⊢ goal`, where `Γ` names the theorem's own
161// vocabulary (its predicates and constants) and its stated premises as
162// hypotheses — e.g. `Socrates is a man` becomes a declaration `h : man Socrates`,
163// and the proof term `h2 Socrates h1` cites those hypotheses. Such a proof cannot
164// re-check against the bare prelude: its free names are unbound there.
165//
166// A [`TheoremCertificate`] therefore carries the theorem's ENVIRONMENT DELTA — the
167// declarations (premise hypotheses + declared predicates/constants) and
168// transparent definitions the theorem introduced beyond the standard prelude —
169// EXPLICITLY, so an independent checker can rebuild the exact context and type-check
170// the proof term against the claimed goal WITHOUT ever running proof search.
171//
172// # Honesty
173//
174// The delta is DISCLOSED, not trusted-blind: it IS the theorem's statement (its
175// signature and premises), which a reader inspects. The trusted core stays the
176// standard prelude plus the type-checker; the checker rebuilds the prelude ITSELF
177// and only then replays the disclosed delta. The certificate proves the honest
178// judgement `premises ⊢ goal`, never the stronger `⊢ goal` — the hypotheses it
179// assumes are laid out in the open. A verifier who does not accept a stated premise
180// simply does not accept the theorem; they can never be tricked about WHICH premises
181// were assumed, because every one is enumerated in `hypotheses`.
182
183/// A declaration in a theorem's disclosed environment: a `name : type` assumption
184/// (a premise hypothesis, or a declared predicate/constant) that the proof cites.
185#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
186#[serde(deny_unknown_fields)]
187pub struct CertDeclaration {
188    pub name: String,
189    pub ty: Term,
190}
191
192/// A transparent definition in a theorem's disclosed environment: `name : type := body`,
193/// δ-unfoldable during re-checking exactly as the prover registered it.
194#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
195#[serde(deny_unknown_fields)]
196pub struct CertDefinition {
197    pub name: String,
198    pub ty: Term,
199    pub body: Term,
200}
201
202/// A self-contained, re-checkable certificate for a theorem proved FROM premises.
203///
204/// `proof_term` is claimed to have type `claimed_type` in the standard prelude
205/// EXTENDED by the disclosed `declarations` (assumptions) and `definitions`
206/// (transparent). The re-checker rebuilds the prelude itself and replays only this
207/// disclosed delta, then type-checks — it never trusts, or even sees, how the proof
208/// was found.
209#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
210#[serde(deny_unknown_fields)]
211pub struct TheoremCertificate {
212    /// The theorem's identity (its `## Theorem: Name`), for provenance in reports.
213    pub theorem: String,
214    /// The proof (a lambda-calculus term).
215    pub proof_term: Term,
216    /// The proposition the proof claims to establish (a type) — the theorem's goal.
217    pub claimed_type: Term,
218    /// The disclosed premise hypotheses + declared vocabulary the proof assumes.
219    /// Every assumption the theorem rests on is enumerated here, in the open.
220    pub declarations: Vec<CertDeclaration>,
221    /// The disclosed transparent definitions the proof unfolds.
222    pub definitions: Vec<CertDefinition>,
223    /// The axiom-set version this certificate was produced against.
224    pub prelude_version: String,
225}
226
227/// Every AXIOM a term actually depends on — the `#print axioms` analogue.
228///
229/// Lean's single most important trust tool is the ability to ask a finished theorem which
230/// assumptions it really rests on. `TheoremCertificate` discloses only the delta a caller chose to
231/// pass, never the prelude axioms the proof cites, so "which theorems depend on `Classical_choice`?"
232/// — or on a smuggled `sorry` — was unanswerable at any surface.
233///
234/// The implementation lives in [`crate::prelude`], NOT here, and is re-exported: this module is
235/// behind the `serde` feature, and an audit trail that disappears when certificates are not being
236/// serialised is not an audit trail. The REPL's `Print Axioms`, the kernel's own trusted-base
237/// diagnostics and `logicaffeine_proof`'s verdicts all need it in the default build, and all three
238/// must read the SAME closure — a second copy is a second answer.
239pub use crate::prelude::{
240    axiom_dependencies, axiom_dependencies_of, is_admitted_axiom, AxiomUsage, ADMITTED_AXIOMS,
241};
242
243impl TheoremCertificate {
244    /// The axioms this certificate's proof actually rests on, re-computed against the trusted base
245    /// it would be re-checked in — NOT against whatever the minting side chose to disclose.
246    ///
247    /// Over the proof term AND the claimed goal, for the same reason [`Certificate::axioms_used`]
248    /// is: the statement is half of what a certificate asserts, and an assumption it names is one
249    /// the reader is being asked to accept. A report that walked only the proof term would say a
250    /// theorem ABOUT `Classical_choice` rests on nothing, and `axiom_dependencies_of` — the door
251    /// the REPL comes in by — already answers the same question the other way, so the two doors
252    /// would disagree about one graph.
253    pub fn axioms_used(&self) -> AxiomUsage {
254        let ctx = self.rebuild_context().0;
255        theorem_axioms(&ctx, &self.proof_term, &self.claimed_type)
256    }
257}
258
259/// The closure of a theorem: its proof term and its statement, merged. One function, so
260/// [`TheoremCertificate::axioms_used`] and [`recheck_theorem`] can never come to report different
261/// axiom sets for the same certificate — which is the failure that makes an audit trail worthless,
262/// because the reader has no way to tell which door they came in by.
263fn theorem_axioms(ctx: &Context, proof_term: &Term, claimed_type: &Term) -> AxiomUsage {
264    let mut usage = axiom_dependencies(ctx, proof_term);
265    usage.absorb(axiom_dependencies(ctx, claimed_type));
266    usage
267}
268
269/// The outcome of independently re-checking a [`TheoremCertificate`].
270#[derive(Debug, Clone, PartialEq)]
271pub struct TheoremRecheck {
272    /// Whether the proof term type-checks to the claimed goal in the rebuilt context **and rests
273    /// on nothing ADMITTED**. This is the load-bearing verdict — established by TYPE-CHECKING the
274    /// term, never by re-running proof search.
275    ///
276    /// The second conjunct is the part Lean leaves to the reader. A `sorry`-tainted proof
277    /// type-checks, both kernels agree on it, and a caller that asks only "did it check?" is
278    /// told yes. So the taint is folded into THIS field rather than offered beside it: there is
279    /// no reading of `rechecks == true` under which the proof rests on an admission, and no
280    /// command anyone has to remember to run.
281    pub rechecks: bool,
282    /// The strongest agreement reached: `true` when the SECOND, independent de Bruijn
283    /// kernel ([`double_check`]) also verified the term to a definitionally-equal type
284    /// (the full de Bruijn criterion). `false` when the term type-checks under the main
285    /// kernel but sits outside the re-checker's covered fragment (still a valid proof,
286    /// honestly single-checked) or fails to re-check at all.
287    pub two_kernel_agreement: bool,
288    /// The reason a re-check failed, when it did.
289    pub error: Option<String>,
290    /// Every axiom the proof rests on, TRANSITIVELY — our `#print axioms`, re-derived against the
291    /// trusted base this check rebuilt rather than against whatever the minting side disclosed.
292    ///
293    /// Populated even when the re-check FAILS, and deliberately: "which assumptions did this
294    /// thing want?" is exactly the question a rejected certificate raises, and answering it is
295    /// most of the diagnosis.
296    ///
297    /// EMPTY on exactly two refusals, and in both the honest answer is "no report": a
298    /// version mismatch means the names were not checked against THIS base at all, and a delta
299    /// that REDEFINES a trusted constant means a report naming `dne` would name the reader's
300    /// `dne` and not the certificate's — which is the misreading that refusal exists to prevent.
301    pub axioms: AxiomUsage,
302}
303
304impl TheoremCertificate {
305    /// Bundle a proof term, its goal, and the disclosed environment delta.
306    pub fn new(
307        theorem: impl Into<String>,
308        proof_term: Term,
309        claimed_type: Term,
310        declarations: Vec<CertDeclaration>,
311        definitions: Vec<CertDefinition>,
312    ) -> Self {
313        Self {
314            theorem: theorem.into(),
315            proof_term: proof_term.into_stripped(),
316            claimed_type: claimed_type.into_stripped(),
317            declarations: declarations
318                .into_iter()
319                .map(|d| CertDeclaration {
320                    name: d.name,
321                    ty: d.ty.into_stripped(),
322                })
323                .collect(),
324            definitions: definitions
325                .into_iter()
326                .map(|d| CertDefinition {
327                    name: d.name,
328                    ty: d.ty.into_stripped(),
329                    body: d.body.into_stripped(),
330                })
331                .collect(),
332            prelude_version: PRELUDE_VERSION.to_string(),
333        }
334    }
335
336    /// Rebuild the trusted context: a fresh standard prelude, then the disclosed delta — and the
337    /// delta may only EXTEND that prelude, never replace any part of it.
338    ///
339    /// Returns the context together with every delta name the trusted base already owned. Those
340    /// entries are NOT applied, and [`recheck_theorem`] turns a non-empty list into a refusal.
341    ///
342    /// # The hole this closes
343    ///
344    /// `Context::add_declaration` and `Context::add_definition` both begin by DISPLACING the name
345    /// — one name means one thing, which is right — so replaying a delta entry named `dne` or
346    /// `Classical_choice` does not sit alongside the prelude's, it REPLACES it. A certificate
347    /// could therefore disclose `dne : Π(P:Prop). P`, prove `False` from it, and re-check GREEN
348    /// with both kernels agreeing; the axiom report would name `dne`, a constant that IS in the
349    /// trusted base, and a reader auditing the certificate would see a proof resting on
350    /// double-negation elimination. It rests on something else entirely. Disclosure is only
351    /// honest if the names in it mean what the reader's own prelude says they mean.
352    ///
353    /// **Declarations first, definitions second, and the order is load-bearing.** A declaration is
354    /// a `name : type` assumption with no body, so nothing about registering one can depend on a
355    /// definition; a definition's BODY routinely cites a declared premise — `h1` in
356    /// `h2 Socrates h1` is the whole point of the delta. `add_definition` is a CHECKED gateway: it
357    /// infers the body's type and refuses a body it can neither check nor defer, so replaying a
358    /// definition before the premise it cites can drop the definition on the floor (recorded in
359    /// `refused_declarations`, invisible to a caller reading only `rechecks`). The minting side's
360    /// emission order is not something a re-checker may depend on, so the re-checker imposes the
361    /// one order that always works.
362    fn rebuild_context(&self) -> (Context, Vec<String>) {
363        let mut ctx = StandardLibrary::context().clone();
364        let mut shadowed: Vec<String> = Vec::new();
365        for decl in &self.declarations {
366            if ctx.get_global(&decl.name).is_some() {
367                shadowed.push(decl.name.clone());
368                continue;
369            }
370            ctx.add_declaration(&decl.name, decl.ty.clone());
371        }
372        for def in &self.definitions {
373            if ctx.get_global(&def.name).is_some() {
374                shadowed.push(def.name.clone());
375                continue;
376            }
377            ctx.add_definition(def.name.clone(), def.ty.clone(), def.body.clone());
378        }
379        (ctx, shadowed)
380    }
381}
382
383/// Independently re-validate a [`TheoremCertificate`].
384///
385/// Rebuilds the prelude + disclosed delta, infers the proof term's type, and requires
386/// it to be the claimed goal (up to definitional equality / cumulativity). The verdict
387/// comes from TYPE-CHECKING, not from re-running the prover's backward-chaining search
388/// — so the checker is a genuinely independent witness. Where the term lies inside the
389/// second kernel's fragment, [`double_check`] corroborates it, giving the full de Bruijn
390/// two-kernel guarantee (`two_kernel_agreement`).
391///
392/// # The admission gate — and why it is a gate rather than a note
393///
394/// The transitive axiom closure is computed FIRST, against the rebuilt context, and a proof that
395/// rests on anything [`is_admitted_axiom`] recognises does not re-check at all. This is the one
396/// place LOGOS deliberately does more than Lean, and the reason is the shape of the failure:
397/// `sorryAx` proves anything, so a tainted proof type-checks, BOTH kernels agree on it, and
398/// `verified == true` really does hold. Lean warns on the declaration that CONTAINS the `sorry`
399/// and says nothing about its dependants, so a theorem three lemmas downstream looks finished and
400/// `#print axioms` is a command you must remember to run. Here the closure is transitive and the
401/// verdict carries it, so the taint cannot be lost by renaming, re-exporting, citing at a distance
402/// — or by a caller reading only `rechecks`.
403///
404/// Tracking is not banning: a proof resting on `Classical_choice`, `propext` or `Quot_sound` is a
405/// perfectly good proof and re-checks, with those names reported in `axioms`. The gate is for the
406/// names that mark a proof as NOT FINISHED.
407pub fn recheck_theorem(cert: &TheoremCertificate) -> TheoremRecheck {
408    if cert.prelude_version != PRELUDE_VERSION {
409        return TheoremRecheck {
410            rechecks: false,
411            two_kernel_agreement: false,
412            error: Some(format!(
413                "certificate was minted against prelude '{}', but this checker is '{}'",
414                cert.prelude_version, PRELUDE_VERSION
415            )),
416            axioms: AxiomUsage::default(),
417        };
418    }
419
420    let (ctx, shadowed) = cert.rebuild_context();
421    if !shadowed.is_empty() {
422        return TheoremRecheck {
423            rechecks: false,
424            two_kernel_agreement: false,
425            error: Some(format!(
426                "the disclosed delta REDEFINES constants the trusted base already owns: {}. A \
427                 certificate may extend the prelude with its own vocabulary and premises; it may \
428                 never replace an axiom or definition the prelude already has, because every name \
429                 it discloses has to mean what the reader's own prelude says it means",
430                shadowed.join(", ")
431            )),
432            axioms: AxiomUsage::default(),
433        };
434    }
435
436    // What does this proof rest on? Asked BEFORE "does it type-check", because an admitted step
437    // type-checks and the answer would be reassuring and wrong. Over the STATEMENT as well as the
438    // proof term: a goal that cites `sorryAx` is not a proposition anyone proved either, and a
439    // gate that looked only at the proof term could be walked straight past by moving the
440    // admission into the type.
441    let axioms = theorem_axioms(&ctx, &cert.proof_term, &cert.claimed_type);
442    if axioms.is_admitted() {
443        return TheoremRecheck {
444            rechecks: false,
445            two_kernel_agreement: false,
446            error: Some(axioms.render(&cert.theorem)),
447            axioms,
448        };
449    }
450
451    // The term must type-check at all, and to the claimed goal.
452    let inferred = match infer_type(&ctx, &cert.proof_term) {
453        Ok(t) => t,
454        Err(e) => {
455            return TheoremRecheck {
456                rechecks: false,
457                two_kernel_agreement: false,
458                error: Some(format!("proof term did not type-check: {:?}", e)),
459                axioms,
460            };
461        }
462    };
463    if !is_subtype(&ctx, &inferred, &cert.claimed_type) {
464        return TheoremRecheck {
465            rechecks: false,
466            two_kernel_agreement: false,
467            error: Some(format!(
468                "certificate term proves a different proposition: has type {:?}, claims {:?}",
469                inferred, cert.claimed_type
470            )),
471            axioms,
472        };
473    }
474
475    // Corroborate with the SECOND, independently-written de Bruijn kernel. Agreement is
476    // the full guarantee; an incomplete re-check (a `Fix`/`Hole` fragment) is honestly
477    // reported as single-checked, NOT dressed up as agreement. A `Disagree` — one kernel
478    // accepting what the other rejects — is a soundness alarm and fails the re-check.
479    match double_check(&ctx, &cert.proof_term) {
480        DoubleCheck::Agreed => TheoremRecheck {
481            rechecks: true,
482            two_kernel_agreement: true,
483            error: None,
484            axioms,
485        },
486        DoubleCheck::MainOnlyReCheckerIncomplete(_) => TheoremRecheck {
487            rechecks: true,
488            two_kernel_agreement: false,
489            error: None,
490            axioms,
491        },
492        DoubleCheck::Disagree(why) => TheoremRecheck {
493            rechecks: false,
494            two_kernel_agreement: false,
495            error: Some(format!(
496                "the two kernels DISAGREED on the proof term: {why}"
497            )),
498            axioms,
499        },
500    }
501}
502
503// ---------------------------------------------------------------------------
504// Reading any certificate — one format family, dispatched by shape and schema.
505// ---------------------------------------------------------------------------
506
507/// Every certificate this module defines, read from JSON by one door.
508#[derive(Debug, Clone, PartialEq)]
509pub enum AnyCertificate {
510    /// A [`Certificate`]: a proof in the bare prelude.
511    Proof(Certificate),
512    /// A [`TheoremCertificate`]: a proof from a disclosed environment delta.
513    Theorem(TheoremCertificate),
514    /// An [`EnvironmentCertificate`]: a proof together with every declaration it needs.
515    Environment(EnvironmentCertificate),
516}
517
518/// Prelude tags this checker knows it is NOT. A certificate carrying one of them is refused with
519/// an explicit incompatibility — the axiom sets differ and no migration exists — rather than the
520/// generic "unknown version" a typo would get. The tag is never rewritten to make it fit.
521pub const RETIRED_PRELUDE_VERSIONS: &[&str] = &[
522    "logos-coc-1",
523    "logos-coc-2",
524    "logos-coc-3",
525    "logos-coc-4",
526    "logos-coc-5",
527];
528
529/// Read a certificate of any kind from JSON.
530///
531/// The kind is decided by the document's own shape: an object with a `schema` field is a versioned
532/// certificate and must name a schema this checker implements; otherwise a `theorem` field marks a
533/// [`TheoremCertificate`] and its absence a bare [`Certificate`]. Every struct refuses unknown
534/// fields, so a theorem certificate can no longer be silently read as a bare one with its
535/// environment dropped. The prelude tag is checked here, before anything is re-checked.
536pub fn read_certificate(json: &str) -> Result<AnyCertificate, EnvironmentError> {
537    let value: serde_json::Value = serde_json::from_str(json).map_err(|e| {
538        EnvironmentError::new(
539            EnvironmentErrorCode::Malformed,
540            format!("not a JSON certificate: {e}"),
541        )
542    })?;
543    let object = value.as_object().ok_or_else(|| {
544        EnvironmentError::new(
545            EnvironmentErrorCode::Malformed,
546            "a certificate is a JSON object",
547        )
548    })?;
549    let prelude = object
550        .get("prelude_version")
551        .and_then(|v| v.as_str())
552        .map(str::to_string);
553    let certificate = if let Some(schema) = object.get("schema") {
554        match schema.as_str() {
555            Some(OPAQUE_ENVIRONMENT_SCHEMA) | Some(ENVIRONMENT_SCHEMA) => {
556                let environment: EnvironmentCertificate = serde_json::from_value(value.clone()).map_err(|e| {
557                    EnvironmentError::new(EnvironmentErrorCode::Malformed, format!("{OPAQUE_ENVIRONMENT_SCHEMA}: {e}"))
558                })?;
559                environment.check_schema()?;
560                AnyCertificate::Environment(environment)
561            }
562            other => {
563                return Err(EnvironmentError::new(
564                    EnvironmentErrorCode::UnsupportedSchema,
565                    format!("unsupported certificate schema {other:?}; this checker reads {OPAQUE_ENVIRONMENT_SCHEMA}"),
566                ))
567            }
568        }
569    } else if object.contains_key("theorem") {
570        AnyCertificate::Theorem(serde_json::from_value(value.clone()).map_err(|e| {
571            EnvironmentError::new(
572                EnvironmentErrorCode::Malformed,
573                format!("theorem certificate: {e}"),
574            )
575        })?)
576    } else {
577        AnyCertificate::Proof(serde_json::from_value(value.clone()).map_err(|e| {
578            EnvironmentError::new(
579                EnvironmentErrorCode::Malformed,
580                format!("proof certificate: {e}"),
581            )
582        })?)
583    };
584    check_prelude_version(prelude.as_deref().unwrap_or(""))?;
585    Ok(certificate)
586}
587
588/// Refuse any prelude tag but this checker's own, explicitly for a retired one.
589pub fn check_prelude_version(found: &str) -> Result<(), EnvironmentError> {
590    if found == PRELUDE_VERSION {
591        return Ok(());
592    }
593    let message = if RETIRED_PRELUDE_VERSIONS.contains(&found) {
594        format!(
595            "certificate was minted against the retired prelude '{found}'; this checker is \
596             '{PRELUDE_VERSION}'. The two axiom sets differ and no validated migration exists, so the \
597             certificate is incompatible: re-mint it against '{PRELUDE_VERSION}'"
598        )
599    } else {
600        format!("certificate was minted against prelude '{found}', but this checker is '{PRELUDE_VERSION}'")
601    };
602    Err(EnvironmentError::new(
603        EnvironmentErrorCode::IncompatiblePrelude,
604        message,
605    ))
606}
607
608// ---------------------------------------------------------------------------
609// Environment certificates — a proof together with every declaration it needs.
610// ---------------------------------------------------------------------------
611//
612// A [`TheoremCertificate`] discloses premises and transparent definitions over the standard
613// prelude. A proof imported from another system needs more: its own inductive types with their
614// constructors and recursors, universe-polymorphic declarations, opaque theorems whose proofs are
615// evidence but never unfold, certified quotient primitives, structure η, and assumptions under an
616// explicit policy. An [`EnvironmentCertificate`] carries all of it, over a declared base.
617//
618// # Deserializing is not validating
619//
620// Nothing a certificate says is registered on its word. [`EnvironmentCertificate::rebuild`] starts
621// from the declared base (built by the checker itself), and hands every entry to the kernel's
622// CHECKED gateways in order: an inductive block goes through positivity and universe checking, a
623// definition's body is type-checked against its type, an opaque theorem is checked by both kernels
624// before its header is published, a structure's projections are proved to compute, a quotient
625// primitive must carry its canonical signature. A name the base already owns is never replaced, a
626// reference to a name not yet declared is refused rather than deferred, and a rebuild that ends
627// with an owed obligation fails.
628//
629// # Terms are a table
630//
631// Terms are written once each, children first, in `nodes`; a node refers to earlier nodes by
632// index. A forward or self reference is therefore a malformed certificate rather than a cycle to
633// chase, the node count, depth and unfolded size of every root are known BEFORE any term is built,
634// and a shared subterm costs one row however often it occurs.
635
636/// The latest environment-certificate format. Version 2 adds checked opaque declarations.
637/// Capture writes the oldest supported format that can represent its entries: version 1 for
638/// original entry kinds, version 2 when an opaque occurs. Readers never retag existing evidence.
639pub const OPAQUE_ENVIRONMENT_SCHEMA: &str = "logos-environment-certificate/2";
640/// The original format, supported unchanged for certificates containing its original entry kinds.
641pub const ENVIRONMENT_SCHEMA: &str = "logos-environment-certificate/1";
642
643/// A reference to an earlier row of a certificate's term table.
644pub type NodeRef = u32;
645
646/// One row of a certificate's term table: a [`Term`] constructor whose children are earlier rows.
647#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
648#[serde(rename_all = "snake_case", deny_unknown_fields)]
649pub enum TermNode {
650    Sort(Universe),
651    Var(String),
652    Global(String),
653    Const(String, Vec<Universe>),
654    Pi(String, NodeRef, NodeRef),
655    Lambda(String, NodeRef, NodeRef),
656    App(NodeRef, NodeRef),
657    Match(NodeRef, NodeRef, Vec<NodeRef>),
658    Fix(String, NodeRef),
659    MutualFix(Vec<(String, NodeRef)>, usize),
660    Let(String, NodeRef, NodeRef, NodeRef),
661    Lit(crate::Literal),
662    Hole,
663}
664
665/// The trusted base an environment is rebuilt on. The checker builds it; the certificate only
666/// names it.
667#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
668#[serde(rename_all = "snake_case")]
669pub enum EnvironmentBase {
670    /// The bare kernel: no declarations at all. An imported environment brings everything it uses,
671    /// and may not take a name the standard library gives native meaning to.
672    Kernel,
673    /// The standard LOGOS prelude, `PRELUDE_VERSION`.
674    StandardLibrary,
675}
676
677/// How declared types are compared with inferred ones.
678#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
679#[serde(rename_all = "snake_case")]
680pub enum ConversionDiscipline {
681    /// LOGOS's own judgement: cumulative universes.
682    Cumulative,
683    /// Exact conversion throughout — every application, let and declaration compared by
684    /// definitional equality, never by cumulative subtyping. The discipline of a Lean import.
685    Exact,
686}
687
688/// Lean's four quotient primitives, as the kernel certifies them.
689#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
690#[serde(rename_all = "snake_case")]
691pub enum QuotientPrimitive {
692    Type,
693    Constructor,
694    Lift,
695    Induction,
696}
697
698impl QuotientPrimitive {
699    fn kind(self) -> crate::quotient::Kind {
700        use crate::quotient::Kind;
701        match self {
702            QuotientPrimitive::Type => Kind::Type,
703            QuotientPrimitive::Constructor => Kind::Constructor,
704            QuotientPrimitive::Lift => Kind::Lift,
705            QuotientPrimitive::Induction => Kind::Induction,
706        }
707    }
708    fn of(kind: crate::quotient::Kind) -> Self {
709        use crate::quotient::Kind;
710        match kind {
711            Kind::Type => QuotientPrimitive::Type,
712            Kind::Constructor => QuotientPrimitive::Constructor,
713            Kind::Lift => QuotientPrimitive::Lift,
714            Kind::Induction => QuotientPrimitive::Induction,
715        }
716    }
717}
718
719/// One constructor of an inductive type: its name and full type (parameters included).
720#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
721#[serde(deny_unknown_fields)]
722pub struct ConstructorClaim {
723    pub name: String,
724    pub ty: NodeRef,
725}
726
727/// One member of an inductive block.
728#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
729#[serde(deny_unknown_fields)]
730pub struct InductiveType {
731    pub name: String,
732    /// The arity: parameters, then indices, then the sort.
733    pub arity: NodeRef,
734    /// How many leading arity binders are uniform parameters.
735    pub num_params: usize,
736    pub constructors: Vec<ConstructorClaim>,
737}
738
739/// One declaration of an environment, in dependency order.
740#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
741#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
742pub enum EnvironmentEntry {
743    /// An assumption: a name and a type, no proof. Subject to the reader's assumption policy.
744    Axiom {
745        name: String,
746        level_params: Vec<String>,
747        ty: NodeRef,
748    },
749    /// A transparent definition, δ-unfoldable. Recursors and structure projections are
750    /// definitions: their bodies are type-checked like any other.
751    Definition {
752        name: String,
753        level_params: Vec<String>,
754        ty: NodeRef,
755        body: NodeRef,
756    },
757    /// A theorem. Its proof is carried and checked by both kernels, and the declaration is
758    /// published as Lean publishes a theorem: conversion may unfold it at the lowest priority.
759    Theorem {
760        name: String,
761        level_params: Vec<String>,
762        ty: NodeRef,
763        proof: NodeRef,
764    },
765    /// A checked opaque value or proof. Its body is evidence and never unfolds in conversion.
766    Opaque {
767        name: String,
768        level_params: Vec<String>,
769        ty: NodeRef,
770        body: NodeRef,
771    },
772    /// An inductive block (one member, or a mutual block) and its constructors.
773    Inductive {
774        level_params: Vec<String>,
775        types: Vec<InductiveType>,
776    },
777    /// Structure η for an earlier single-constructor inductive, through earlier projection
778    /// definitions. Registered only after every projection is proved to compute.
779    Structure {
780        inductive: String,
781        projections: Vec<String>,
782    },
783    /// A certified quotient primitive. `equality` names the equality inductive `lift` respects.
784    Quotient {
785        primitive: QuotientPrimitive,
786        level_params: Vec<String>,
787        ty: NodeRef,
788        equality: Option<String>,
789    },
790}
791
792impl EnvironmentEntry {
793    /// The names this entry introduces, in order.
794    pub fn names(&self) -> Vec<&str> {
795        match self {
796            EnvironmentEntry::Axiom { name, .. }
797            | EnvironmentEntry::Definition { name, .. }
798            | EnvironmentEntry::Theorem { name, .. }
799            | EnvironmentEntry::Opaque { name, .. } => vec![name.as_str()],
800            EnvironmentEntry::Inductive { types, .. } => types
801                .iter()
802                .flat_map(|t| {
803                    std::iter::once(t.name.as_str())
804                        .chain(t.constructors.iter().map(|c| c.name.as_str()))
805                })
806                .collect(),
807            EnvironmentEntry::Structure { .. } => Vec::new(),
808            EnvironmentEntry::Quotient { primitive, .. } => vec![primitive.kind().name()],
809        }
810    }
811
812    /// Every term row this entry cites directly.
813    pub fn roots(&self) -> Vec<NodeRef> {
814        match self {
815            EnvironmentEntry::Axiom { ty, .. } | EnvironmentEntry::Quotient { ty, .. } => vec![*ty],
816            EnvironmentEntry::Definition { ty, body, .. }
817            | EnvironmentEntry::Opaque { ty, body, .. } => vec![*ty, *body],
818            EnvironmentEntry::Theorem { ty, proof, .. } => vec![*ty, *proof],
819            EnvironmentEntry::Inductive { types, .. } => types
820                .iter()
821                .flat_map(|t| std::iter::once(t.arity).chain(t.constructors.iter().map(|c| c.ty)))
822                .collect(),
823            EnvironmentEntry::Structure { .. } => Vec::new(),
824        }
825    }
826
827    fn kind_name(&self) -> &'static str {
828        match self {
829            EnvironmentEntry::Axiom { .. } => "axiom",
830            EnvironmentEntry::Definition { .. } => "definition",
831            EnvironmentEntry::Theorem { .. } => "theorem",
832            EnvironmentEntry::Opaque { .. } => "opaque",
833            EnvironmentEntry::Inductive { .. } => "inductive",
834            EnvironmentEntry::Structure { .. } => "structure",
835            EnvironmentEntry::Quotient { .. } => "quotient",
836        }
837    }
838}
839
840/// A proof together with the complete environment it needs, rebuilt and re-checked from nothing
841/// but the declared base.
842#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
843#[serde(deny_unknown_fields)]
844pub struct EnvironmentCertificate {
845    /// [`ENVIRONMENT_SCHEMA`] for original entry kinds, or [`OPAQUE_ENVIRONMENT_SCHEMA`] with opaque declarations.
846    pub schema: String,
847    /// The prelude/kernel version this certificate was minted against.
848    pub prelude_version: String,
849    pub base: EnvironmentBase,
850    pub conversion: ConversionDiscipline,
851    /// The term table, children before parents.
852    pub nodes: Vec<TermNode>,
853    /// The declarations, in dependency order: every name an entry cites is the base's or an
854    /// earlier entry's.
855    pub entries: Vec<EnvironmentEntry>,
856    /// The name of the definition or theorem this certificate proves.
857    pub target: String,
858    /// Source binder annotations of declarations' leading telescopes, by name. Presentation
859    /// metadata: it changes no judgement, and a reader of the LOGOS document is told which
860    /// annotations the fully explicit kernel terms do not write.
861    pub binder_kinds: BTreeMap<String, Vec<BinderKind>>,
862}
863
864/// A binder annotation, as Lean's `BinderInfo` records it.
865#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
866#[serde(rename_all = "snake_case")]
867pub enum BinderKind {
868    Explicit,
869    Implicit,
870    StrictImplicit,
871    Instance,
872}
873
874impl BinderKind {
875    fn of(kind: crate::elaborate::ParamKind) -> Self {
876        use crate::elaborate::ParamKind;
877        match kind {
878            ParamKind::Explicit => BinderKind::Explicit,
879            ParamKind::Implicit => BinderKind::Implicit,
880            ParamKind::StrictImplicit => BinderKind::StrictImplicit,
881            ParamKind::Instance => BinderKind::Instance,
882        }
883    }
884    fn param_kind(self) -> crate::elaborate::ParamKind {
885        use crate::elaborate::ParamKind;
886        match self {
887            BinderKind::Explicit => ParamKind::Explicit,
888            BinderKind::Implicit => ParamKind::Implicit,
889            BinderKind::StrictImplicit => ParamKind::StrictImplicit,
890            BinderKind::Instance => ParamKind::Instance,
891        }
892    }
893}
894
895/// Why an environment certificate was refused. Each code is one kind of refusal, so a caller — and
896/// a tamper test — can say not only THAT a certificate failed but which check refused it.
897#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
898pub enum EnvironmentErrorCode {
899    /// The document names a schema this checker does not implement.
900    UnsupportedSchema,
901    /// The certificate was minted against another prelude.
902    IncompatiblePrelude,
903    /// The document is not a well-formed certificate (bad JSON, a dangling or forward row).
904    Malformed,
905    /// A declared limit was exceeded before any checking work began.
906    ResourceLimit,
907    /// Declarations cite each other in a cycle.
908    DependencyCycle,
909    /// A declaration cites a name that is not declared before it, or checking left an obligation.
910    UnresolvedObligation,
911    /// A declaration takes a name the base owns, a reserved name, or an earlier entry's name.
912    TrustedNameReplacement,
913    /// The kernel refuses a declaration.
914    IllTyped,
915    /// A declaration checks only under cumulative subtyping, where exact conversion was declared.
916    InexactConversion,
917    /// The two kernels disagree.
918    IndependentDisagreement,
919    /// The target is missing or has no proof.
920    TargetNotProved,
921    /// Evidence the certificate must carry is missing (an opaque theorem's proof).
922    MissingEvidence,
923    /// A construct this checker does not rebuild.
924    Unsupported,
925}
926
927impl EnvironmentErrorCode {
928    pub fn as_str(self) -> &'static str {
929        match self {
930            EnvironmentErrorCode::UnsupportedSchema => "unsupported_schema",
931            EnvironmentErrorCode::IncompatiblePrelude => "incompatible_prelude",
932            EnvironmentErrorCode::Malformed => "malformed",
933            EnvironmentErrorCode::ResourceLimit => "resource_limit",
934            EnvironmentErrorCode::DependencyCycle => "dependency_cycle",
935            EnvironmentErrorCode::UnresolvedObligation => "unresolved_obligation",
936            EnvironmentErrorCode::TrustedNameReplacement => "trusted_name_replacement",
937            EnvironmentErrorCode::IllTyped => "ill_typed",
938            EnvironmentErrorCode::InexactConversion => "inexact_conversion",
939            EnvironmentErrorCode::IndependentDisagreement => "independent_disagreement",
940            EnvironmentErrorCode::TargetNotProved => "target_not_proved",
941            EnvironmentErrorCode::MissingEvidence => "missing_evidence",
942            EnvironmentErrorCode::Unsupported => "unsupported",
943        }
944    }
945}
946
947/// A refusal: its code and a sentence saying what was refused.
948#[derive(Debug, Clone, PartialEq, Eq)]
949pub struct EnvironmentError {
950    pub code: EnvironmentErrorCode,
951    pub message: String,
952}
953
954impl EnvironmentError {
955    pub fn new(code: EnvironmentErrorCode, message: impl Into<String>) -> Self {
956        EnvironmentError {
957            code,
958            message: message.into(),
959        }
960    }
961}
962
963impl std::fmt::Display for EnvironmentError {
964    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
965        write!(f, "{}: {}", self.code.as_str(), self.message)
966    }
967}
968
969/// Limits enforced on a certificate BEFORE any term is built or checked.
970#[derive(Debug, Clone, Copy, PartialEq, Eq)]
971pub struct EnvironmentLimits {
972    /// Rows in the term table.
973    pub max_nodes: usize,
974    /// Declarations.
975    pub max_entries: usize,
976    /// Depth of any term. The kernel refuses deeper terms anyway; refusing here refuses before
977    /// work.
978    pub max_depth: usize,
979    /// The unfolded (tree) size of all roots together.
980    pub max_tree_nodes: u64,
981    /// Bytes in any one name.
982    pub max_name_bytes: usize,
983}
984
985impl Default for EnvironmentLimits {
986    fn default() -> Self {
987        EnvironmentLimits {
988            max_nodes: 2_000_000,
989            max_entries: 100_000,
990            max_depth: crate::type_checker::MAX_TERM_DEPTH,
991            max_tree_nodes: 20_000_000,
992            max_name_bytes: 4096,
993        }
994    }
995}
996
997/// How one declaration fared under the second, independent kernel ([`crate::recheck`](mod@crate::recheck)).
998#[derive(Debug, Clone, PartialEq, Eq)]
999pub enum IndependentVerdict {
1000    /// The re-checker inferred a type definitionally equal to the declared one.
1001    Agreed,
1002    /// The term lies outside the re-checker's covered fragment. Checked once, not twice.
1003    Unsupported(String),
1004}
1005
1006/// A rebuilt environment: the checked context and what the checks found.
1007pub struct RebuiltEnvironment {
1008    /// The context, rebuilt from the base with every entry kernel-checked.
1009    pub context: Context,
1010    pub target: String,
1011    /// The target's universe parameters, type and proof.
1012    pub target_level_params: Vec<String>,
1013    pub target_type: Term,
1014    pub target_proof: Term,
1015    /// Whether the target is a theorem whose proof may unfold during conversion.
1016    pub target_is_theorem: bool,
1017    /// Whether the target is a checked opaque value or proof whose body stays sealed.
1018    pub target_is_opaque: bool,
1019    /// Every checked declaration term under the second kernel, by declaration name.
1020    pub independent: Vec<(String, IndependentVerdict)>,
1021    /// The transitive assumptions of the target's statement AND proof — `#print axioms`.
1022    pub axioms: AxiomUsage,
1023    /// Each assumption the environment declares, with its universe parameters and type.
1024    pub assumptions: Vec<(String, Vec<String>, Term)>,
1025    /// The checked proofs of the environment's opaque theorems — evidence the context does not
1026    /// publish, kept for re-emission.
1027    pub proofs: BTreeMap<String, Term>,
1028    /// The maximum term depth in the certificate, which sized the stack the rebuild needs.
1029    pub depth: usize,
1030}
1031
1032impl RebuiltEnvironment {
1033    /// `true` when every checked term was corroborated by the independent kernel.
1034    pub fn independently_agreed(&self) -> bool {
1035        self.independent
1036            .iter()
1037            .all(|(_, v)| *v == IndependentVerdict::Agreed)
1038    }
1039}
1040
1041/// The terms of a certificate's table, admitted and interned, extracted root by root on demand.
1042struct DecodedTable {
1043    store: crate::term_store::TermStore,
1044    ids: Vec<crate::term_store::TermId>,
1045    depth: usize,
1046}
1047
1048impl DecodedTable {
1049    fn term(&self, row: NodeRef) -> Result<Term, EnvironmentError> {
1050        let id = *self.ids.get(row as usize).ok_or_else(|| {
1051            EnvironmentError::new(
1052                EnvironmentErrorCode::Malformed,
1053                format!("term row {row} does not exist"),
1054            )
1055        })?;
1056        Ok(self.store.extract(id))
1057    }
1058}
1059
1060fn malformed(message: impl Into<String>) -> EnvironmentError {
1061    EnvironmentError::new(EnvironmentErrorCode::Malformed, message)
1062}
1063
1064fn limit(message: impl Into<String>) -> EnvironmentError {
1065    EnvironmentError::new(EnvironmentErrorCode::ResourceLimit, message)
1066}
1067
1068fn ill_typed(message: impl Into<String>) -> EnvironmentError {
1069    EnvironmentError::new(EnvironmentErrorCode::IllTyped, message)
1070}
1071
1072/// The base context, built by the checker itself.
1073fn base_context(base: EnvironmentBase) -> Context {
1074    if base == EnvironmentBase::StandardLibrary {
1075        StandardLibrary::context().clone()
1076    } else {
1077        Context::new()
1078    }
1079}
1080
1081fn declared(ctx: &Context, name: &str) -> bool {
1082    ctx.get_global(name).is_some() || ctx.poly_declaration(name).is_some()
1083}
1084
1085/// Every global name a term cites, without native recursion.
1086fn cited_names(term: &Term) -> BTreeSet<String> {
1087    let mut out = BTreeSet::new();
1088    let mut work = vec![term];
1089    while let Some(t) = work.pop() {
1090        match t {
1091            Term::Global(n) => {
1092                out.insert(n.clone());
1093            }
1094            Term::Const { name, .. } => {
1095                out.insert(name.clone());
1096            }
1097            Term::Sort(_) | Term::Var(_) | Term::Lit(_) | Term::Hole => {}
1098            Term::Pi {
1099                param_type,
1100                body_type,
1101                ..
1102            } => {
1103                work.push(param_type);
1104                work.push(body_type);
1105            }
1106            Term::Lambda {
1107                param_type, body, ..
1108            } => {
1109                work.push(param_type);
1110                work.push(body);
1111            }
1112            Term::App(f, a) => {
1113                work.push(f);
1114                work.push(a);
1115            }
1116            Term::Match {
1117                discriminant,
1118                motive,
1119                cases,
1120            } => {
1121                work.push(discriminant);
1122                work.push(motive);
1123                work.extend(cases.iter());
1124            }
1125            Term::Fix { body, .. } => work.push(body),
1126            Term::MutualFix { defs, .. } => work.extend(defs.iter().map(|(_, b)| b)),
1127            Term::Let {
1128                ty, value, body, ..
1129            } => {
1130                work.push(ty);
1131                work.push(value);
1132                work.push(body);
1133            }
1134            Term::Annotated { term, .. } => work.push(term),
1135        }
1136    }
1137    out
1138}
1139
1140impl EnvironmentCertificate {
1141    /// Admit the term table under `limits` — row count, name sizes, backward-only references,
1142    /// depth and unfolded size — and intern it. No term is built before every limit has been
1143    /// checked.
1144    fn decode(&self, limits: &EnvironmentLimits) -> Result<DecodedTable, EnvironmentError> {
1145        if self.nodes.len() > limits.max_nodes {
1146            return Err(limit(format!(
1147                "{} term rows exceed the limit of {}",
1148                self.nodes.len(),
1149                limits.max_nodes
1150            )));
1151        }
1152        if self.entries.len() > limits.max_entries {
1153            return Err(limit(format!(
1154                "{} declarations exceed the limit of {}",
1155                self.entries.len(),
1156                limits.max_entries
1157            )));
1158        }
1159        let long_name = |n: &str| n.len() > limits.max_name_bytes;
1160        let mut depth: Vec<usize> = Vec::with_capacity(self.nodes.len());
1161        let mut size: Vec<u64> = Vec::with_capacity(self.nodes.len());
1162        for (row, node) in self.nodes.iter().enumerate() {
1163            let mut children: Vec<NodeRef> = Vec::new();
1164            let mut names: Vec<&str> = Vec::new();
1165            match node {
1166                TermNode::Sort(_) | TermNode::Lit(_) | TermNode::Hole => {}
1167                TermNode::Var(n) | TermNode::Global(n) | TermNode::Const(n, _) => names.push(n),
1168                TermNode::Pi(n, a, b) | TermNode::Lambda(n, a, b) => {
1169                    names.push(n);
1170                    children.extend([*a, *b]);
1171                }
1172                TermNode::App(a, b) => children.extend([*a, *b]),
1173                TermNode::Match(d, m, cases) => {
1174                    children.extend([*d, *m]);
1175                    children.extend(cases.iter().copied());
1176                }
1177                TermNode::Fix(n, b) => {
1178                    names.push(n);
1179                    children.push(*b);
1180                }
1181                TermNode::MutualFix(defs, index) => {
1182                    if *index >= defs.len() {
1183                        return Err(malformed(format!(
1184                            "term row {row}: mutual fixpoint index out of range"
1185                        )));
1186                    }
1187                    for (n, b) in defs {
1188                        names.push(n);
1189                        children.push(*b);
1190                    }
1191                }
1192                TermNode::Let(n, a, b, c) => {
1193                    names.push(n);
1194                    children.extend([*a, *b, *c]);
1195                }
1196            }
1197            if let Some(n) = names.iter().find(|n| long_name(n)) {
1198                return Err(limit(format!(
1199                    "term row {row}: a {}-byte name exceeds the limit",
1200                    n.len()
1201                )));
1202            }
1203            let mut d = 0usize;
1204            let mut s = 1u64;
1205            for child in children {
1206                if child as usize >= row {
1207                    return Err(malformed(format!(
1208                        "term row {row} cites row {child}: rows cite only EARLIER rows, so a forward or \
1209                         self reference is a cycle, never a term"
1210                    )));
1211                }
1212                d = d.max(depth[child as usize]);
1213                s = s.saturating_add(size[child as usize]);
1214            }
1215            let d = d + 1;
1216            if d > limits.max_depth {
1217                return Err(limit(format!(
1218                    "term row {row} has depth {d}, beyond the limit of {}",
1219                    limits.max_depth
1220                )));
1221            }
1222            depth.push(d);
1223            size.push(s);
1224        }
1225        let mut total = 0u64;
1226        for entry in &self.entries {
1227            for row in entry.roots() {
1228                let row_size = *size.get(row as usize).ok_or_else(|| {
1229                    malformed(format!(
1230                        "a {} cites term row {row}, which does not exist",
1231                        entry.kind_name()
1232                    ))
1233                })?;
1234                total = total.saturating_add(row_size);
1235            }
1236        }
1237        if total > limits.max_tree_nodes {
1238            return Err(limit(format!(
1239                "the declarations unfold to {total} term nodes, beyond the limit of {}",
1240                limits.max_tree_nodes
1241            )));
1242        }
1243        for entry in &self.entries {
1244            for name in entry.names() {
1245                if long_name(name) {
1246                    return Err(limit(format!(
1247                        "a {}-byte declaration name exceeds the limit",
1248                        name.len()
1249                    )));
1250                }
1251            }
1252        }
1253        let mut store = crate::term_store::TermStore::new();
1254        let mut ids = Vec::with_capacity(self.nodes.len());
1255        for node in &self.nodes {
1256            let at = |r: &NodeRef| ids[*r as usize];
1257            let id = match node {
1258                TermNode::Sort(u) => store.mk_sort(u),
1259                TermNode::Var(n) => store.mk_var(n),
1260                TermNode::Global(n) => store.mk_global(n),
1261                TermNode::Const(n, levels) => store.mk_const(n, levels),
1262                TermNode::Pi(n, a, b) => {
1263                    let (a, b) = (at(a), at(b));
1264                    store.mk_pi(n, a, b)
1265                }
1266                TermNode::Lambda(n, a, b) => {
1267                    let (a, b) = (at(a), at(b));
1268                    store.mk_lambda(n, a, b)
1269                }
1270                TermNode::App(a, b) => {
1271                    let (a, b) = (at(a), at(b));
1272                    store.mk_app(a, b)
1273                }
1274                TermNode::Match(d, m, cases) => {
1275                    let cases: Vec<_> = cases.iter().map(at).collect();
1276                    let (d, m) = (at(d), at(m));
1277                    store.mk_match(d, m, &cases)
1278                }
1279                TermNode::Fix(n, b) => {
1280                    let b = at(b);
1281                    store.mk_fix(n, b)
1282                }
1283                TermNode::MutualFix(defs, index) => {
1284                    let defs: Vec<(&str, _)> =
1285                        defs.iter().map(|(n, b)| (n.as_str(), at(b))).collect();
1286                    store.mk_mutual_fix(&defs, *index)
1287                }
1288                TermNode::Let(n, a, b, c) => {
1289                    let (a, b, c) = (at(a), at(b), at(c));
1290                    store.mk_let(n, a, b, c)
1291                }
1292                TermNode::Lit(l) => store.mk_lit(l),
1293                TermNode::Hole => store.mk_hole(),
1294            };
1295            ids.push(id);
1296        }
1297        Ok(DecodedTable {
1298            store,
1299            ids,
1300            depth: depth.iter().copied().max().unwrap_or(0),
1301        })
1302    }
1303
1304    /// The deepest term in the certificate, after admission. A caller sizes the stack of the
1305    /// thread that runs [`EnvironmentCertificate::rebuild`] from it.
1306    pub fn admitted_depth(&self, limits: &EnvironmentLimits) -> Result<usize, EnvironmentError> {
1307        self.decode(limits).map(|t| t.depth)
1308    }
1309
1310    /// The order of the entries' citations, refused when they form a cycle — checked before any
1311    /// kernel work, so a cycle is named as one rather than surfacing as a missing name.
1312    fn check_acyclic(&self, table: &DecodedTable) -> Result<(), EnvironmentError> {
1313        let mut owner: HashMap<String, usize> = HashMap::new();
1314        for (i, entry) in self.entries.iter().enumerate() {
1315            for name in entry.names() {
1316                owner.entry(name.to_string()).or_insert(i);
1317            }
1318        }
1319        let mut edges: Vec<BTreeSet<usize>> = Vec::with_capacity(self.entries.len());
1320        for (i, entry) in self.entries.iter().enumerate() {
1321            let mut out = BTreeSet::new();
1322            let mut cited = BTreeSet::new();
1323            for row in entry.roots() {
1324                cited.extend(cited_names(&table.term(row)?));
1325            }
1326            if let EnvironmentEntry::Structure {
1327                inductive,
1328                projections,
1329            } = entry
1330            {
1331                cited.insert(inductive.clone());
1332                cited.extend(projections.iter().cloned());
1333            }
1334            if let EnvironmentEntry::Quotient {
1335                equality: Some(eq), ..
1336            } = entry
1337            {
1338                cited.insert(eq.clone());
1339            }
1340            for name in cited {
1341                if let Some(&j) = owner.get(&name) {
1342                    if j != i {
1343                        out.insert(j);
1344                    }
1345                }
1346            }
1347            edges.push(out);
1348        }
1349        // Iterative three-colour depth-first search.
1350        let mut colour = vec![0u8; self.entries.len()];
1351        for start in 0..self.entries.len() {
1352            if colour[start] != 0 {
1353                continue;
1354            }
1355            let mut stack: Vec<(usize, Vec<usize>)> =
1356                vec![(start, edges[start].iter().copied().collect())];
1357            colour[start] = 1;
1358            while let Some((node, pending)) = stack.last_mut() {
1359                if let Some(next) = pending.pop() {
1360                    match colour[next] {
1361                        0 => {
1362                            colour[next] = 1;
1363                            let children = edges[next].iter().copied().collect();
1364                            stack.push((next, children));
1365                        }
1366                        1 => {
1367                            let cycle: Vec<String> = stack
1368                                .iter()
1369                                .skip_while(|(n, _)| *n != next)
1370                                .flat_map(|(n, _)| {
1371                                    self.entries[*n]
1372                                        .names()
1373                                        .into_iter()
1374                                        .take(1)
1375                                        .map(str::to_string)
1376                                })
1377                                .collect();
1378                            return Err(EnvironmentError::new(
1379                                EnvironmentErrorCode::DependencyCycle,
1380                                format!(
1381                                    "declarations cite each other in a cycle: {}",
1382                                    cycle.join(" → ")
1383                                ),
1384                            ));
1385                        }
1386                        _ => {}
1387                    }
1388                } else {
1389                    colour[*node] = 2;
1390                    stack.pop();
1391                }
1392            }
1393        }
1394        Ok(())
1395    }
1396
1397    /// Rebuild the environment from the declared base, kernel-checking every entry, and return the
1398    /// checked context with the target's statement and proof.
1399    ///
1400    /// Runs on the caller's thread; a caller holding a deep certificate sizes that thread's stack
1401    /// with [`EnvironmentCertificate::admitted_depth`] and
1402    /// [`crate::term_store::stack_bytes_for_depth`] first.
1403    pub fn rebuild(
1404        &self,
1405        limits: &EnvironmentLimits,
1406    ) -> Result<RebuiltEnvironment, EnvironmentError> {
1407        self.rebuild_observing_definitions(limits, &mut |_, _, _, _, _| {})
1408    }
1409
1410    /// Observe each checked transparent definition before registering it. The supplied context
1411    /// contains only preceding entries, so a source comparison cannot unfold the definition being
1412    /// compared or a later declaration. Observations never authorize kernel admission.
1413    pub(crate) fn rebuild_observing_definitions(
1414        &self,
1415        limits: &EnvironmentLimits,
1416        observe: &mut dyn FnMut(&Context, &str, &[String], &Term, &Term),
1417    ) -> Result<RebuiltEnvironment, EnvironmentError> {
1418        self.rebuild_observing_values_impl(limits, observe, false)
1419    }
1420
1421    /// Observe checked transparent and opaque values in their preceding contexts. Opaque
1422    /// declarations remain sealed; theorem proof bodies are intentionally not observed.
1423    pub(crate) fn rebuild_observing_values(
1424        &self,
1425        limits: &EnvironmentLimits,
1426        observe: &mut dyn FnMut(&Context, &str, &[String], &Term, &Term),
1427    ) -> Result<RebuiltEnvironment, EnvironmentError> {
1428        self.rebuild_observing_values_impl(limits, observe, true)
1429    }
1430
1431    fn rebuild_observing_values_impl(
1432        &self,
1433        limits: &EnvironmentLimits,
1434        observe: &mut dyn FnMut(&Context, &str, &[String], &Term, &Term),
1435        include_opaque: bool,
1436    ) -> Result<RebuiltEnvironment, EnvironmentError> {
1437        self.check_schema()?;
1438        check_prelude_version(&self.prelude_version)?;
1439        #[cfg(not(feature = "lean"))]
1440        if self.conversion == ConversionDiscipline::Exact {
1441            return Err(EnvironmentError::new(
1442                EnvironmentErrorCode::Unsupported,
1443                "exact conversion is checked by the `lean` feature's exact typing, which this build lacks",
1444            ));
1445        }
1446        let table = self.decode(limits)?;
1447        for row in 0..self.nodes.len() {
1448            if matches!(self.nodes[row], TermNode::Hole) {
1449                return Err(ill_typed(format!(
1450                    "term row {row} is a hole: a certificate carries no unelaborated term"
1451                )));
1452            }
1453        }
1454        self.check_acyclic(&table)?;
1455        let mut rebuild = Rebuild {
1456            certificate: self,
1457            table: &table,
1458            ctx: base_context(self.base),
1459            introduced: BTreeSet::new(),
1460            independent: Vec::new(),
1461            assumptions: Vec::new(),
1462            proofs: BTreeMap::new(),
1463        };
1464        for entry in &self.entries {
1465            rebuild.entry(entry, observe, include_opaque)?;
1466        }
1467        let mut ctx = rebuild.ctx;
1468        for (name, kinds) in &self.binder_kinds {
1469            if !rebuild.introduced.contains(name) {
1470                return Err(malformed(format!(
1471                    "binder annotations name {name}, which the certificate does not declare"
1472                )));
1473            }
1474            let ty = ctx
1475                .poly_declaration(name)
1476                .map(|(_, t)| t.clone())
1477                .or_else(|| ctx.get_global(name).cloned());
1478            let mut telescope = 0usize;
1479            let mut at = ty.as_ref();
1480            while let Some(Term::Pi { body_type, .. }) = at {
1481                telescope += 1;
1482                at = Some(body_type);
1483            }
1484            if kinds.len() > telescope {
1485                return Err(malformed(format!(
1486                    "{name} has {} binder annotations for a telescope of {telescope}",
1487                    kinds.len()
1488                )));
1489            }
1490            ctx.set_binder_kinds(name, kinds.iter().map(|k| k.param_kind()).collect());
1491        }
1492        let obligations = ctx.pending_obligations();
1493        if !obligations.is_empty() {
1494            return Err(EnvironmentError::new(
1495                EnvironmentErrorCode::UnresolvedObligation,
1496                format!(
1497                    "the rebuilt environment still owes {} obligation(s): {:?}",
1498                    obligations.len(),
1499                    obligations
1500                ),
1501            ));
1502        }
1503        if !ctx.refused_declarations().is_empty() {
1504            return Err(ill_typed(format!(
1505                "the kernel refused {:?}",
1506                ctx.refused_declarations()
1507            )));
1508        }
1509        let (target_level_params, target_type, target_proof, target_is_theorem, target_is_opaque) = self
1510            .entries
1511            .iter()
1512            .find_map(|entry| match entry {
1513                EnvironmentEntry::Definition { name, level_params, ty, body } if *name == self.target => {
1514                    Some((level_params.clone(), *ty, *body, false, false))
1515                }
1516                EnvironmentEntry::Theorem { name, level_params, ty, proof } if *name == self.target => {
1517                    Some((level_params.clone(), *ty, *proof, true, false))
1518                }
1519                EnvironmentEntry::Opaque { name, level_params, ty, body } if *name == self.target => {
1520                    Some((level_params.clone(), *ty, *body, false, true))
1521                }
1522                _ => None,
1523            })
1524            .ok_or_else(|| {
1525                let assumed = self.entries.iter().any(|e| matches!(e, EnvironmentEntry::Axiom { name, .. } if *name == self.target));
1526                EnvironmentError::new(
1527                    EnvironmentErrorCode::TargetNotProved,
1528                    if assumed {
1529                        format!("the target {} is an assumption: a certificate proves its target, it does not assume it", self.target)
1530                    } else {
1531                        format!("the certificate declares no proved target named {}", self.target)
1532                    },
1533                )
1534            })?;
1535        let target_type = table.term(target_type)?;
1536        let target_proof = table.term(target_proof)?;
1537        let mut axioms = axiom_dependencies(&ctx, &target_type);
1538        axioms.absorb(axiom_dependencies(&ctx, &target_proof));
1539        // An opaque target publishes only its header; its proof's closure is its checked evidence.
1540        if let Some(evidence) = axiom_dependencies_of(&ctx, &self.target) {
1541            axioms.absorb(evidence);
1542        }
1543        Ok(RebuiltEnvironment {
1544            context: ctx,
1545            target: self.target.clone(),
1546            target_level_params,
1547            target_type,
1548            target_proof,
1549            target_is_theorem,
1550            target_is_opaque,
1551            independent: rebuild.independent,
1552            axioms,
1553            assumptions: rebuild.assumptions,
1554            proofs: rebuild.proofs,
1555            depth: table.depth,
1556        })
1557    }
1558}
1559
1560/// The state of one rebuild.
1561struct Rebuild<'a> {
1562    certificate: &'a EnvironmentCertificate,
1563    table: &'a DecodedTable,
1564    ctx: Context,
1565    introduced: BTreeSet<String>,
1566    independent: Vec<(String, IndependentVerdict)>,
1567    assumptions: Vec<(String, Vec<String>, Term)>,
1568    proofs: BTreeMap<String, Term>,
1569}
1570
1571impl Rebuild<'_> {
1572    fn exact(&self) -> bool {
1573        self.certificate.conversion == ConversionDiscipline::Exact
1574    }
1575
1576    /// Refuse a name the base owns, a reserved name, or a name an earlier entry introduced.
1577    fn claim_name(
1578        &mut self,
1579        name: &str,
1580        canonical_nat: bool,
1581        quotient: bool,
1582    ) -> Result<(), EnvironmentError> {
1583        let replacement =
1584            |why: String| EnvironmentError::new(EnvironmentErrorCode::TrustedNameReplacement, why);
1585        if name.is_empty() {
1586            return Err(malformed("a declaration has an empty name"));
1587        }
1588        if self.introduced.contains(name) {
1589            return Err(replacement(format!("{name} is declared twice")));
1590        }
1591        if declared(&self.ctx, name) {
1592            return Err(replacement(format!(
1593                "{name} is already declared by the trusted base and may not be replaced"
1594            )));
1595        }
1596        let is_nat = matches!(name, "Nat" | "Zero" | "Succ");
1597        let is_quotient = name.starts_with("kernel.quot.");
1598        if is_quotient && !quotient {
1599            return Err(replacement(format!(
1600                "{name} is a certified quotient primitive and only a quotient entry may install it"
1601            )));
1602        }
1603        if is_nat && !canonical_nat {
1604            return Err(replacement(format!("{name} is the kernel's literal Nat and only its canonical inductive may declare it")));
1605        }
1606        // Every standard library name carries native meaning somewhere in the kernel (a reduction
1607        // keyed on `Quot_lift`, a decision procedure keyed on `le`, …), so a kernel-based
1608        // environment may not declare one itself — except the canonical `Nat`, `Zero` and `Succ`,
1609        // which the kernel's literals denote and which an imported environment must bring in their
1610        // canonical shape.
1611        if self.certificate.base == EnvironmentBase::Kernel
1612            && !is_nat
1613            && !is_quotient
1614            && declared(StandardLibrary::context(), name)
1615        {
1616            return Err(replacement(format!(
1617                "{name} carries native meaning in the standard library; an imported environment may not declare it"
1618            )));
1619        }
1620        self.introduced.insert(name.to_string());
1621        Ok(())
1622    }
1623
1624    /// Every name the terms cite must already be declared (or be one of `own`).
1625    fn require_declared(
1626        &self,
1627        subject: &str,
1628        terms: &[&Term],
1629        own: &BTreeSet<String>,
1630    ) -> Result<(), EnvironmentError> {
1631        for term in terms {
1632            for name in cited_names(term) {
1633                if !own.contains(&name) && !declared(&self.ctx, &name) {
1634                    return Err(EnvironmentError::new(
1635                        EnvironmentErrorCode::UnresolvedObligation,
1636                        format!("{subject} cites {name}, which is not declared before it"),
1637                    ));
1638                }
1639            }
1640        }
1641        Ok(())
1642    }
1643
1644    fn level_telescope(
1645        &self,
1646        subject: &str,
1647        params: &[String],
1648        terms: &[&Term],
1649    ) -> Result<(), EnvironmentError> {
1650        let bound: BTreeSet<&str> = params.iter().map(String::as_str).collect();
1651        if bound.len() != params.len() {
1652            return Err(ill_typed(format!("{subject} repeats a universe parameter")));
1653        }
1654        for term in terms {
1655            for level in crate::term::free_level_vars(term) {
1656                if !bound.contains(level.as_str()) {
1657                    return Err(ill_typed(format!(
1658                        "{subject} uses the unbound universe parameter {level}"
1659                    )));
1660                }
1661            }
1662        }
1663        Ok(())
1664    }
1665
1666    /// The type of `term` under the declared discipline.
1667    fn infer(&self, term: &Term) -> Result<Term, EnvironmentError> {
1668        if self.exact() {
1669            #[cfg(feature = "lean")]
1670            {
1671                return crate::interface::lean::lean_type(&self.ctx, term).map_err(|verdict| {
1672                    let cumulative = crate::infer_type(&self.ctx, term).is_ok();
1673                    EnvironmentError::new(
1674                        if cumulative {
1675                            EnvironmentErrorCode::InexactConversion
1676                        } else {
1677                            EnvironmentErrorCode::IllTyped
1678                        },
1679                        verdict.reason().message().to_string(),
1680                    )
1681                });
1682            }
1683        }
1684        crate::infer_type(&self.ctx, term).map_err(|e| ill_typed(e.to_string()))
1685    }
1686
1687    /// `inferred` must be the declared type: definitionally equal under the exact discipline, a
1688    /// subtype under the cumulative one.
1689    fn conforms(
1690        &self,
1691        subject: &str,
1692        inferred: &Term,
1693        declared_ty: &Term,
1694    ) -> Result<(), EnvironmentError> {
1695        let ok = if self.exact() {
1696            crate::type_checker::def_eq(&self.ctx, inferred, declared_ty)
1697        } else {
1698            is_subtype(&self.ctx, inferred, declared_ty)
1699        };
1700        if ok {
1701            return Ok(());
1702        }
1703        if self.exact() && is_subtype(&self.ctx, inferred, declared_ty) {
1704            return Err(EnvironmentError::new(
1705                EnvironmentErrorCode::InexactConversion,
1706                format!("{subject} has its declared type only by cumulative subtyping"),
1707            ));
1708        }
1709        Err(ill_typed(format!(
1710            "{subject} does not have its declared type"
1711        )))
1712    }
1713
1714    /// A declared type must itself be a type.
1715    fn check_type(&self, subject: &str, ty: &Term) -> Result<(), EnvironmentError> {
1716        let sort = self.infer(ty)?;
1717        let sort = crate::normalize_checked(&self.ctx, &sort).map_err(|e| {
1718            ill_typed(format!(
1719                "{subject}: the sort of its type did not normalize: {e:?}"
1720            ))
1721        })?;
1722        if matches!(sort, Term::Sort(_)) {
1723            Ok(())
1724        } else {
1725            Err(ill_typed(format!(
1726                "{subject}: its declared type is not a type"
1727            )))
1728        }
1729    }
1730
1731    /// Re-check `term` with the second kernel and record the verdict under `subject`.
1732    fn independently(
1733        &mut self,
1734        subject: &str,
1735        term: &Term,
1736        expected: &Term,
1737    ) -> Result<(), EnvironmentError> {
1738        match crate::recheck(&self.ctx, term) {
1739            Ok(inferred) => {
1740                if crate::type_checker::def_eq(&self.ctx, &inferred, expected) || is_subtype(&self.ctx, &inferred, expected) {
1741                    self.independent.push((subject.to_string(), IndependentVerdict::Agreed));
1742                    Ok(())
1743                } else {
1744                    Err(EnvironmentError::new(
1745                        EnvironmentErrorCode::IndependentDisagreement,
1746                        format!("{subject}: the independent kernel infers a different type"),
1747                    ))
1748                }
1749            }
1750            Err(crate::ReCheckError::Unsupported(why)) => {
1751                self.independent.push((subject.to_string(), IndependentVerdict::Unsupported(why.into())));
1752                Ok(())
1753            }
1754            Err(crate::ReCheckError::Ill(why)) => Err(EnvironmentError::new(
1755                EnvironmentErrorCode::IndependentDisagreement,
1756                format!("{subject}: the independent kernel refuses what the primary kernel accepted: {why}"),
1757            )),
1758        }
1759    }
1760
1761    fn entry(
1762        &mut self,
1763        entry: &EnvironmentEntry,
1764        observe: &mut dyn FnMut(&Context, &str, &[String], &Term, &Term),
1765        include_opaque: bool,
1766    ) -> Result<(), EnvironmentError> {
1767        match entry {
1768            EnvironmentEntry::Axiom {
1769                name,
1770                level_params,
1771                ty,
1772            } => {
1773                let ty = self.table.term(*ty)?;
1774                self.claim_name(name, false, false)?;
1775                self.require_declared(name, &[&ty], &BTreeSet::new())?;
1776                self.level_telescope(name, level_params, &[&ty])?;
1777                self.check_type(name, &ty)?;
1778                let sort =
1779                    crate::infer_type(&self.ctx, &ty).map_err(|e| ill_typed(e.to_string()))?;
1780                self.independently(name, &ty, &sort)?;
1781                self.ctx
1782                    .add_universe_poly_declaration(name, level_params.clone(), ty.clone())
1783                    .map_err(|e| ill_typed(format!("{name}: {e}")))?;
1784                self.assumptions
1785                    .push((name.clone(), level_params.clone(), ty));
1786                Ok(())
1787            }
1788            EnvironmentEntry::Definition {
1789                name,
1790                level_params,
1791                ty,
1792                body,
1793            } => {
1794                let (ty, body) = (self.table.term(*ty)?, self.table.term(*body)?);
1795                self.claim_name(name, false, false)?;
1796                self.require_declared(name, &[&ty, &body], &BTreeSet::new())?;
1797                self.level_telescope(name, level_params, &[&ty, &body])?;
1798                self.check_type(name, &ty)?;
1799                let inferred = self.infer(&body)?;
1800                self.conforms(name, &inferred, &ty)?;
1801                self.independently(name, &body, &ty)?;
1802                observe(&self.ctx, name, level_params, &ty, &body);
1803                let registered = if level_params.is_empty() {
1804                    self.ctx.declare(name, ty, body)
1805                } else {
1806                    self.ctx
1807                        .declare_universe_poly(name, level_params.clone(), ty, body)
1808                };
1809                registered.map_err(|e| ill_typed(format!("{name}: {e}")))
1810            }
1811            EnvironmentEntry::Theorem {
1812                name,
1813                level_params,
1814                ty,
1815                proof,
1816            } => {
1817                let (ty, proof) = (self.table.term(*ty)?, self.table.term(*proof)?);
1818                self.claim_name(name, false, false)?;
1819                self.require_declared(name, &[&ty, &proof], &BTreeSet::new())?;
1820                self.level_telescope(name, level_params, &[&ty, &proof])?;
1821                self.check_type(name, &ty)?;
1822                let inferred = self.infer(&proof)?;
1823                self.conforms(name, &inferred, &ty)?;
1824                self.independently(name, &proof, &ty)?;
1825                // A theorem is rebuilt as the importer publishes it: checked, and unfoldable
1826                // as Lean's kernel unfolds a theorem.
1827                self.ctx
1828                    .declare_theorem_checked(name, level_params.clone(), ty, proof.clone())
1829                    .map_err(|e| ill_typed(format!("{name}: {e}")))?;
1830                self.proofs.insert(name.clone(), proof);
1831                Ok(())
1832            }
1833            EnvironmentEntry::Opaque {
1834                name,
1835                level_params,
1836                ty,
1837                body,
1838            } => {
1839                let (ty, body) = (self.table.term(*ty)?, self.table.term(*body)?);
1840                self.claim_name(name, false, false)?;
1841                self.require_declared(name, &[&ty, &body], &BTreeSet::new())?;
1842                self.level_telescope(name, level_params, &[&ty, &body])?;
1843                self.check_type(name, &ty)?;
1844                let inferred = self.infer(&body)?;
1845                self.conforms(name, &inferred, &ty)?;
1846                self.independently(name, &body, &ty)?;
1847                if include_opaque {
1848                    observe(&self.ctx, name, level_params, &ty, &body);
1849                }
1850                self.ctx
1851                    .declare_opaque_checked(name, level_params.clone(), ty, body.clone())
1852                    .map_err(|e| ill_typed(format!("{name}: {e}")))?;
1853                self.proofs.insert(name.clone(), body);
1854                Ok(())
1855            }
1856            EnvironmentEntry::Inductive {
1857                level_params,
1858                types,
1859            } => self.inductive(level_params, types),
1860            EnvironmentEntry::Structure {
1861                inductive,
1862                projections,
1863            } => self.structure(inductive, projections),
1864            EnvironmentEntry::Quotient {
1865                primitive,
1866                level_params,
1867                ty,
1868                equality,
1869            } => {
1870                let kind = primitive.kind();
1871                let name = kind.name();
1872                let ty = self.table.term(*ty)?;
1873                self.claim_name(name, false, true)?;
1874                let equality_name = equality.clone().unwrap_or_default();
1875                if kind == crate::quotient::Kind::Lift && equality.is_none() {
1876                    return Err(malformed("the quotient lift names no equality"));
1877                }
1878                let levels: Vec<Universe> =
1879                    level_params.iter().cloned().map(Universe::Var).collect();
1880                let expected = crate::quotient::signature(kind, &levels, &equality_name)
1881                    .map_err(|e| ill_typed(format!("{name}: {e}")))?;
1882                if !crate::type_checker::def_eq(&self.ctx, &ty, &expected) {
1883                    return Err(ill_typed(format!(
1884                        "{name} does not carry the canonical quotient signature"
1885                    )));
1886                }
1887                crate::quotient::install(&mut self.ctx, kind, level_params.clone(), &equality_name)
1888                    .map_err(|e| ill_typed(format!("{name}: {e}")))
1889            }
1890        }
1891    }
1892
1893    fn inductive(
1894        &mut self,
1895        level_params: &[String],
1896        types: &[InductiveType],
1897    ) -> Result<(), EnvironmentError> {
1898        if types.is_empty() {
1899            return Err(malformed("an inductive entry declares no type"));
1900        }
1901        let mut own = BTreeSet::new();
1902        let mut decoded: Vec<(String, Term, usize, Vec<(String, Term)>)> = Vec::new();
1903        for member in types {
1904            let arity = self.table.term(member.arity)?;
1905            let constructors = member
1906                .constructors
1907                .iter()
1908                .map(|c| Ok((c.name.clone(), self.table.term(c.ty)?)))
1909                .collect::<Result<Vec<_>, EnvironmentError>>()?;
1910            decoded.push((member.name.clone(), arity, member.num_params, constructors));
1911        }
1912        let canonical_nat = self.canonical_nat(level_params, &decoded)?;
1913        for (name, _, _, constructors) in &decoded {
1914            self.claim_name(name, canonical_nat, false)?;
1915            own.insert(name.clone());
1916            for (c, _) in constructors {
1917                self.claim_name(c, canonical_nat, false)?;
1918                own.insert(c.clone());
1919            }
1920        }
1921        for (name, arity, _, constructors) in &decoded {
1922            let mut terms: Vec<&Term> = vec![arity];
1923            terms.extend(constructors.iter().map(|(_, t)| t));
1924            self.require_declared(name, &terms, &own)?;
1925            self.level_telescope(name, level_params, &terms)?;
1926        }
1927        // Types of arities and constructors are checked under the declared discipline in a scratch
1928        // context carrying the block's headers; the registration below re-decides the whole block.
1929        let mut headers = self.ctx.clone();
1930        for (name, arity, num_params, _) in &decoded {
1931            if level_params.is_empty() {
1932                headers.add_indexed_inductive(name, arity.clone(), *num_params);
1933            } else {
1934                headers.add_universe_poly_inductive(
1935                    name,
1936                    level_params.to_vec(),
1937                    arity.clone(),
1938                    *num_params,
1939                );
1940            }
1941        }
1942        let outer = std::mem::replace(&mut self.ctx, headers);
1943        let checked = decoded
1944            .iter()
1945            .try_for_each(|(name, arity, _, constructors)| {
1946                self.check_type(name, arity)?;
1947                constructors
1948                    .iter()
1949                    .try_for_each(|(c, ty)| self.check_type(c, ty))
1950            });
1951        self.ctx = outer;
1952        checked?;
1953        let block: Vec<crate::MutualInductive> = decoded
1954            .iter()
1955            .map(
1956                |(name, arity, num_params, constructors)| crate::MutualInductive {
1957                    name: name.clone(),
1958                    sort: arity.clone(),
1959                    num_params: *num_params,
1960                    constructors: constructors.clone(),
1961                },
1962            )
1963            .collect();
1964        let registered = if level_params.is_empty() {
1965            self.ctx.add_mutual_inductives(&block)
1966        } else {
1967            self.ctx.add_universe_poly_inductives(level_params, &block)
1968        };
1969        registered.map_err(|e| ill_typed(format!("inductive {}: {e}", decoded[0].0)))?;
1970        for (name, arity, _, constructors) in &decoded {
1971            let sort = crate::infer_type(&self.ctx, arity).map_err(|e| ill_typed(e.to_string()))?;
1972            self.independently(name, arity, &sort)?;
1973            for (c, ty) in constructors {
1974                let sort =
1975                    crate::infer_type(&self.ctx, ty).map_err(|e| ill_typed(e.to_string()))?;
1976                self.independently(c, ty, &sort)?;
1977            }
1978        }
1979        Ok(())
1980    }
1981
1982    /// Whether this block is the kernel's literal `Nat`. A block that uses any of the names `Nat`,
1983    /// `Zero`, `Succ` must be exactly `Nat : Type 0` with `Zero : Nat` and `Succ : Nat → Nat`, or it
1984    /// is refused: the kernel's literals and their arithmetic denote that datatype.
1985    fn canonical_nat(
1986        &self,
1987        level_params: &[String],
1988        decoded: &[(String, Term, usize, Vec<(String, Term)>)],
1989    ) -> Result<bool, EnvironmentError> {
1990        let reserved = |n: &str| matches!(n, "Nat" | "Zero" | "Succ");
1991        let uses = decoded
1992            .iter()
1993            .any(|(n, _, _, cs)| reserved(n) || cs.iter().any(|(c, _)| reserved(c)));
1994        if !uses {
1995            return Ok(false);
1996        }
1997        let nat = Term::Global("Nat".into());
1998        let canonical = level_params.is_empty()
1999            && decoded.len() == 1
2000            && decoded[0].0 == "Nat"
2001            && decoded[0].2 == 0
2002            && decoded[0].1 == Term::Sort(Universe::Type(0))
2003            && decoded[0].3.len() == 2
2004            && decoded[0].3[0].0 == "Zero"
2005            && decoded[0].3[0].1 == nat
2006            && decoded[0].3[1].0 == "Succ"
2007            && matches!(&decoded[0].3[1].1, Term::Pi { param_type, body_type, .. }
2008                if **param_type == nat && **body_type == nat);
2009        if canonical {
2010            Ok(true)
2011        } else {
2012            Err(EnvironmentError::new(
2013                EnvironmentErrorCode::TrustedNameReplacement,
2014                "a block using the names Nat, Zero or Succ must be the canonical Nat: Nat : Type 0, Zero : Nat, Succ : Nat → Nat",
2015            ))
2016        }
2017    }
2018
2019    /// Structure η for `inductive` through `projections`, through the kernel's one door
2020    /// ([`Context::register_structure_checked`]). The certificate adds only its own scoping rule:
2021    /// the inductive and every projection it names must have been introduced by this certificate,
2022    /// never borrowed from the base.
2023    fn structure(
2024        &mut self,
2025        inductive: &str,
2026        projections: &[String],
2027    ) -> Result<(), EnvironmentError> {
2028        if self.ctx.is_inductive(inductive) && !self.introduced.contains(inductive) {
2029            return Err(EnvironmentError::new(
2030                EnvironmentErrorCode::UnresolvedObligation,
2031                format!(
2032                    "structure η names {inductive}, which is not an inductive declared before it"
2033                ),
2034            ));
2035        }
2036        if let Some(borrowed) = projections
2037            .iter()
2038            .find(|p| declared(&self.ctx, p) && !self.introduced.contains(p.as_str()))
2039        {
2040            return Err(ill_typed(format!(
2041                "{borrowed} is not a definition declared before the structure"
2042            )));
2043        }
2044        self.ctx
2045            .register_structure_checked(inductive, projections)
2046            .map_err(structure_refusal)
2047    }
2048}
2049
2050/// A refused structure η, classified: naming no inductive leaves an obligation unresolved, a
2051/// second registration would replace trusted η, and every other refusal is ill-typed evidence.
2052fn structure_refusal(refusal: crate::StructureRefusal) -> EnvironmentError {
2053    use crate::StructureRefusal as R;
2054    let code = match refusal {
2055        R::NotAnInductive { .. } => EnvironmentErrorCode::UnresolvedObligation,
2056        R::AlreadyAStructure { .. } => EnvironmentErrorCode::TrustedNameReplacement,
2057        _ => EnvironmentErrorCode::IllTyped,
2058    };
2059    EnvironmentError::new(code, refusal.to_string())
2060}
2061
2062// ---------------------------------------------------------------------------
2063// Capturing an environment certificate from a checked context.
2064// ---------------------------------------------------------------------------
2065
2066/// What to capture: the target, the context it was checked in, and the evidence the context does
2067/// not keep.
2068/// A checked theorem published transparently (as Lean publishes one): its level telescope,
2069/// statement and proof. Ordinary definitions carry no certification record and are not theorems.
2070fn checked_theorem<'c>(ctx: &'c Context, name: &str) -> Option<(&'c [String], &'c Term, &'c Term)> {
2071    ctx.checked_opaque_dependencies(name)?;
2072    ctx.get_universe_poly(name)
2073        .map(|(params, ty, proof)| (params.as_slice(), ty, proof))
2074}
2075
2076pub struct CaptureRequest<'a> {
2077    pub context: &'a Context,
2078    pub base: EnvironmentBase,
2079    pub conversion: ConversionDiscipline,
2080    pub target: &'a str,
2081    /// The checked proofs of the opaque theorems in the target's closure (the target's own
2082    /// included, when it is one). A checked context publishes only an opaque theorem's header.
2083    pub opaque_proofs: &'a BTreeMap<String, Term>,
2084}
2085
2086/// One unit of the dependency order.
2087#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
2088enum Item {
2089    Declaration(String),
2090    Block(Vec<String>),
2091}
2092
2093/// The term table under construction: each distinct row written once, children first.
2094struct TableWriter {
2095    nodes: Vec<TermNode>,
2096    index: HashMap<String, NodeRef>,
2097    /// The first term the format cannot carry (a non-finite float has no JSON spelling).
2098    refusal: Option<String>,
2099}
2100
2101impl TableWriter {
2102    fn row(&mut self, node: TermNode) -> NodeRef {
2103        let key = match serde_json::to_string(&node) {
2104            Ok(key) => key,
2105            Err(e) => {
2106                self.refusal.get_or_insert_with(|| {
2107                    format!("a term the certificate format cannot carry: {e}")
2108                });
2109                format!("{node:?}")
2110            }
2111        };
2112        if let Some(&r) = self.index.get(&key) {
2113            return r;
2114        }
2115        let r = self.nodes.len() as NodeRef;
2116        self.nodes.push(node);
2117        self.index.insert(key, r);
2118        r
2119    }
2120
2121    /// Write `term`, post-order and without native recursion.
2122    fn write(&mut self, term: &Term) -> NodeRef {
2123        enum Task<'a> {
2124            Enter(&'a Term),
2125            Exit(&'a Term),
2126        }
2127        let mut work = vec![Task::Enter(term)];
2128        let mut out: Vec<NodeRef> = Vec::new();
2129        while let Some(task) = work.pop() {
2130            match task {
2131                Task::Enter(t) => {
2132                    work.push(Task::Exit(t));
2133                    let mut children: Vec<&Term> = Vec::new();
2134                    match t {
2135                        Term::Sort(_)
2136                        | Term::Var(_)
2137                        | Term::Global(_)
2138                        | Term::Const { .. }
2139                        | Term::Lit(_)
2140                        | Term::Hole => {}
2141                        Term::Pi {
2142                            param_type,
2143                            body_type,
2144                            ..
2145                        } => children.extend([&**param_type, &**body_type]),
2146                        Term::Lambda {
2147                            param_type, body, ..
2148                        } => children.extend([&**param_type, &**body]),
2149                        Term::App(f, a) => children.extend([&**f, &**a]),
2150                        Term::Match {
2151                            discriminant,
2152                            motive,
2153                            cases,
2154                        } => {
2155                            children.extend([&**discriminant, &**motive]);
2156                            children.extend(cases.iter());
2157                        }
2158                        Term::Fix { body, .. } => children.push(body),
2159                        Term::MutualFix { defs, .. } => {
2160                            children.extend(defs.iter().map(|(_, b)| b))
2161                        }
2162                        Term::Let {
2163                            ty, value, body, ..
2164                        } => children.extend([&**ty, &**value, &**body]),
2165                        Term::Annotated { term, .. } => children.push(term),
2166                    }
2167                    for child in children.into_iter().rev() {
2168                        work.push(Task::Enter(child));
2169                    }
2170                }
2171                Task::Exit(t) => {
2172                    let mut take = |n: usize| -> Vec<NodeRef> { out.split_off(out.len() - n) };
2173                    let node = match t {
2174                        Term::Sort(u) => TermNode::Sort(u.clone()),
2175                        Term::Var(n) => TermNode::Var(n.clone()),
2176                        Term::Global(n) => TermNode::Global(n.clone()),
2177                        Term::Const { name, levels } => {
2178                            TermNode::Const(name.clone(), levels.clone())
2179                        }
2180                        Term::Lit(l) => TermNode::Lit(l.clone()),
2181                        Term::Hole => TermNode::Hole,
2182                        Term::Pi { param, .. } => {
2183                            let k = take(2);
2184                            TermNode::Pi(param.clone(), k[0], k[1])
2185                        }
2186                        Term::Lambda { param, .. } => {
2187                            let k = take(2);
2188                            TermNode::Lambda(param.clone(), k[0], k[1])
2189                        }
2190                        Term::App(..) => {
2191                            let k = take(2);
2192                            TermNode::App(k[0], k[1])
2193                        }
2194                        Term::Match { cases, .. } => {
2195                            let k = take(2 + cases.len());
2196                            TermNode::Match(k[0], k[1], k[2..].to_vec())
2197                        }
2198                        Term::Fix { name, .. } => {
2199                            let k = take(1);
2200                            TermNode::Fix(name.clone(), k[0])
2201                        }
2202                        Term::MutualFix { defs, index } => {
2203                            let k = take(defs.len());
2204                            TermNode::MutualFix(
2205                                defs.iter().map(|(n, _)| n.clone()).zip(k).collect(),
2206                                *index,
2207                            )
2208                        }
2209                        Term::Let { name, .. } => {
2210                            let k = take(3);
2211                            TermNode::Let(name.clone(), k[0], k[1], k[2])
2212                        }
2213                        // A certificate carries kernel meaning only: an annotated term's row is its
2214                        // term's, already on top of `out`.
2215                        Term::Annotated { .. } => continue,
2216                    };
2217                    let r = self.row(node);
2218                    out.push(r);
2219                }
2220            }
2221        }
2222        out.pop().expect("one root")
2223    }
2224}
2225
2226impl EnvironmentCertificate {
2227    fn check_schema(&self) -> Result<(), EnvironmentError> {
2228        if self.schema != OPAQUE_ENVIRONMENT_SCHEMA && self.schema != ENVIRONMENT_SCHEMA {
2229            return Err(EnvironmentError::new(
2230                EnvironmentErrorCode::UnsupportedSchema,
2231                format!("unsupported certificate schema {:?}; this checker reads {OPAQUE_ENVIRONMENT_SCHEMA} and {ENVIRONMENT_SCHEMA}", self.schema),
2232            ));
2233        }
2234        if self.schema == ENVIRONMENT_SCHEMA
2235            && self
2236                .entries
2237                .iter()
2238                .any(|entry| matches!(entry, EnvironmentEntry::Opaque { .. }))
2239        {
2240            return Err(EnvironmentError::new(
2241                EnvironmentErrorCode::UnsupportedSchema,
2242                format!("an opaque entry requires {OPAQUE_ENVIRONMENT_SCHEMA}; {ENVIRONMENT_SCHEMA} has no opaque entry"),
2243            ));
2244        }
2245        Ok(())
2246    }
2247
2248    /// Capture the target's complete dependency closure from a checked context, in dependency
2249    /// order. Every captured declaration is exactly what the context checked; capture decides
2250    /// nothing, and [`EnvironmentCertificate::rebuild`] re-decides everything.
2251    pub fn capture(
2252        request: &CaptureRequest<'_>,
2253    ) -> Result<EnvironmentCertificate, EnvironmentError> {
2254        let ctx = request.context;
2255        let base = base_context(request.base);
2256        let in_base = |n: &str| declared(&base, n);
2257        let unsupported =
2258            |why: String| EnvironmentError::new(EnvironmentErrorCode::Unsupported, why);
2259        // A StandardLibrary certificate reconstructs this semantic base, not
2260        // a same-spelled set of assumptions with revoked primitive identities.
2261        if request.base == EnvironmentBase::StandardLibrary
2262            && !crate::quotient::same_prelude_base(ctx, &base)
2263        {
2264            return Err(unsupported(
2265                "source context does not retain the standard quotient primitive identities".into(),
2266            ));
2267        }
2268        let item_of = |name: &str| -> Result<Option<Item>, EnvironmentError> {
2269            if request.base == EnvironmentBase::Kernel
2270                && crate::quotient::prelude_kind(ctx, name).is_some()
2271            {
2272                return Err(unsupported(
2273                    "legacy prelude quotient identities require the StandardLibrary base".into(),
2274                ));
2275            }
2276            if in_base(name) {
2277                return Ok(None);
2278            }
2279            let inductive = if ctx.is_inductive(name) {
2280                Some(name.to_string())
2281            } else {
2282                ctx.constructor_inductive(name).map(str::to_string)
2283            };
2284            if let Some(ind) = inductive {
2285                let members = ctx
2286                    .mutual_block_of(&ind)
2287                    .map(|b| b.to_vec())
2288                    .unwrap_or_else(|| vec![ind.clone()]);
2289                return Ok(Some(Item::Block(members)));
2290            }
2291            if declared(ctx, name) {
2292                return Ok(Some(Item::Declaration(name.to_string())));
2293            }
2294            Err(EnvironmentError::new(
2295                EnvironmentErrorCode::UnresolvedObligation,
2296                format!("the context does not declare {name}"),
2297            ))
2298        };
2299        // The terms of one item, for dependency discovery.
2300        let item_terms = |item: &Item| -> Result<Vec<Term>, EnvironmentError> {
2301            match item {
2302                Item::Declaration(name) => {
2303                    let mut terms = Vec::new();
2304                    if let Some((_, ty, proof)) = checked_theorem(ctx, name) {
2305                        terms.extend([ty.clone(), proof.clone()]);
2306                    } else if let Some((_, ty, body)) = ctx.get_universe_poly(name) {
2307                        terms.extend([ty.clone(), body.clone()]);
2308                    } else if let Some(body) = ctx.get_definition_body(name) {
2309                        terms.push(body.clone());
2310                        terms.extend(ctx.get_definition_type(name).cloned());
2311                    } else {
2312                        let ty = ctx
2313                            .poly_declaration(name)
2314                            .map(|(_, t)| t.clone())
2315                            .or_else(|| ctx.get_global(name).cloned())
2316                            .expect("declared");
2317                        terms.push(ty);
2318                        if ctx.checked_opaque_dependencies(name).is_some() {
2319                            let proof = request.opaque_proofs.get(name).ok_or_else(|| EnvironmentError::new(
2320                                EnvironmentErrorCode::MissingEvidence,
2321                                format!("the checked proof of the opaque theorem {name} was not retained"),
2322                            ))?;
2323                            terms.push(proof.clone());
2324                        }
2325                        if let Some(certificate) = ctx.quotient_primitives.get(name) {
2326                            for dependency in &certificate.dependencies {
2327                                terms.push(Term::Global(dependency.clone()));
2328                            }
2329                        }
2330                    }
2331                    Ok(terms)
2332                }
2333                Item::Block(members) => {
2334                    let mut terms = Vec::new();
2335                    for member in members {
2336                        terms.extend(ctx.get_global(member).cloned());
2337                        terms.extend(
2338                            ctx.get_constructors(member)
2339                                .into_iter()
2340                                .map(|(_, t)| t.clone()),
2341                        );
2342                    }
2343                    Ok(terms)
2344                }
2345            }
2346        };
2347        let own_names = |item: &Item| -> BTreeSet<String> {
2348            match item {
2349                Item::Declaration(n) => BTreeSet::from([n.clone()]),
2350                Item::Block(members) => members
2351                    .iter()
2352                    .flat_map(|m| {
2353                        std::iter::once(m.clone()).chain(
2354                            ctx.get_constructors(m)
2355                                .into_iter()
2356                                .map(|(c, _)| c.to_string()),
2357                        )
2358                    })
2359                    .collect(),
2360            }
2361        };
2362
2363        let mut order: Vec<Item> = Vec::new();
2364        let mut done: BTreeSet<Item> = BTreeSet::new();
2365        let mut on_stack: BTreeSet<Item> = BTreeSet::new();
2366        let root = item_of(request.target)?.ok_or_else(|| {
2367            EnvironmentError::new(
2368                EnvironmentErrorCode::TargetNotProved,
2369                format!("{} belongs to the base", request.target),
2370            )
2371        })?;
2372        {
2373            let mut stack: Vec<(Item, Vec<Item>)> = Vec::new();
2374            let children = |item: &Item| -> Result<Vec<Item>, EnvironmentError> {
2375                let own = own_names(item);
2376                let mut names = BTreeSet::new();
2377                for term in item_terms(item)? {
2378                    names.extend(cited_names(&term));
2379                }
2380                let mut out = Vec::new();
2381                for name in names.into_iter().filter(|n| !own.contains(n)) {
2382                    if let Some(child) = item_of(&name)? {
2383                        if !out.contains(&child) {
2384                            out.push(child);
2385                        }
2386                    }
2387                }
2388                out.reverse();
2389                Ok(out)
2390            };
2391            on_stack.insert(root.clone());
2392            let first = children(&root)?;
2393            stack.push((root, first));
2394            while let Some((item, pending)) = stack.last_mut() {
2395                if let Some(next) = pending.pop() {
2396                    if done.contains(&next) {
2397                        continue;
2398                    }
2399                    if on_stack.contains(&next) {
2400                        return Err(EnvironmentError::new(
2401                            EnvironmentErrorCode::DependencyCycle,
2402                            format!("{next:?} depends on itself through {item:?}"),
2403                        ));
2404                    }
2405                    on_stack.insert(next.clone());
2406                    let grandchildren = children(&next)?;
2407                    stack.push((next, grandchildren));
2408                } else {
2409                    let (item, _) = stack.pop().expect("nonempty");
2410                    on_stack.remove(&item);
2411                    done.insert(item.clone());
2412                    order.push(item.clone());
2413                    // A structure's projections belong right after it, so its η is in force for
2414                    // every later declaration exactly as it was when they were checked. They cite
2415                    // only the structure and what the structure itself cites.
2416                    if let Item::Block(members) = &item {
2417                        for member in members {
2418                            let Some(info) = ctx.struct_info(member) else {
2419                                continue;
2420                            };
2421                            for projection in &info.projections {
2422                                let projection = Item::Declaration(projection.clone());
2423                                if done.contains(&projection) {
2424                                    continue;
2425                                }
2426                                if let Some(missing) = children(&projection)?
2427                                    .into_iter()
2428                                    .find(|c| !done.contains(c))
2429                                {
2430                                    return Err(unsupported(format!(
2431                                        "the projection {projection:?} of {member} cites {missing:?}, outside its structure"
2432                                    )));
2433                                }
2434                                done.insert(projection.clone());
2435                                order.push(projection);
2436                            }
2437                        }
2438                    }
2439                }
2440            }
2441        }
2442
2443        let mut writer = TableWriter {
2444            nodes: Vec::new(),
2445            index: HashMap::new(),
2446            refusal: None,
2447        };
2448        let mut entries = Vec::new();
2449        let mut pending_structures: Vec<(String, Vec<String>)> = Vec::new();
2450        for item in &order {
2451            match item {
2452                Item::Block(members) => {
2453                    for member in members {
2454                        if let Some(info) = ctx.struct_info(member) {
2455                            pending_structures.push((member.clone(), info.projections.clone()));
2456                        }
2457                    }
2458                    let level_params = ctx
2459                        .poly_declaration(&members[0])
2460                        .map(|(p, _)| p.to_vec())
2461                        .unwrap_or_default();
2462                    let types = members
2463                        .iter()
2464                        .map(|member| InductiveType {
2465                            name: member.clone(),
2466                            arity: writer.write(ctx.get_global(member).expect("inductive")),
2467                            num_params: ctx.inductive_num_params(member),
2468                            constructors: ctx
2469                                .get_constructors(member)
2470                                .into_iter()
2471                                .map(|(name, ty)| ConstructorClaim {
2472                                    name: name.to_string(),
2473                                    ty: writer.write(ty),
2474                                })
2475                                .collect(),
2476                        })
2477                        .collect();
2478                    entries.push(EnvironmentEntry::Inductive {
2479                        level_params,
2480                        types,
2481                    });
2482                }
2483                Item::Declaration(name) => {
2484                    if let Some(certificate) = ctx.quotient_primitives.get(name) {
2485                        let (params, ty) = ctx.poly_declaration(name).expect("quotient primitive");
2486                        let equality = if certificate.kind == crate::quotient::Kind::Lift {
2487                            certificate
2488                                .dependencies
2489                                .iter()
2490                                .find(|d| ctx.is_inductive(d))
2491                                .cloned()
2492                        } else {
2493                            None
2494                        };
2495                        entries.push(EnvironmentEntry::Quotient {
2496                            primitive: QuotientPrimitive::of(certificate.kind),
2497                            level_params: params.to_vec(),
2498                            ty: writer.write(ty),
2499                            equality,
2500                        });
2501                    } else if let Some((level_params, ty, proof)) = checked_theorem(ctx, name) {
2502                        entries.push(EnvironmentEntry::Theorem {
2503                            name: name.clone(),
2504                            level_params: level_params.to_vec(),
2505                            ty: writer.write(ty),
2506                            proof: writer.write(proof),
2507                        });
2508                    } else if let Some((params, ty, body)) = ctx.get_universe_poly(name) {
2509                        entries.push(EnvironmentEntry::Definition {
2510                            name: name.clone(),
2511                            level_params: params.clone(),
2512                            ty: writer.write(ty),
2513                            body: writer.write(body),
2514                        });
2515                    } else if let Some(body) = ctx.get_definition_body(name) {
2516                        let ty = ctx.get_definition_type(name).expect("definition type");
2517                        entries.push(EnvironmentEntry::Definition {
2518                            name: name.clone(),
2519                            level_params: Vec::new(),
2520                            ty: writer.write(ty),
2521                            body: writer.write(body),
2522                        });
2523                    } else {
2524                        let level_params = ctx
2525                            .poly_declaration(name)
2526                            .map(|(p, _)| p.to_vec())
2527                            .unwrap_or_default();
2528                        let ty = ctx
2529                            .poly_declaration(name)
2530                            .map(|(_, t)| t)
2531                            .or_else(|| ctx.get_global(name))
2532                            .expect("declared");
2533                        if ctx.checked_opaque_dependencies(name).is_some() {
2534                            let proof = &request.opaque_proofs[name];
2535                            entries.push(EnvironmentEntry::Opaque {
2536                                name: name.clone(),
2537                                level_params,
2538                                ty: writer.write(ty),
2539                                body: writer.write(proof),
2540                            });
2541                        } else {
2542                            entries.push(EnvironmentEntry::Axiom {
2543                                name: name.clone(),
2544                                level_params,
2545                                ty: writer.write(ty),
2546                            });
2547                        }
2548                    }
2549                    // A structure's η follows its last projection.
2550                    pending_structures.retain(|(inductive, projections)| {
2551                        let placed = projections.iter().all(|p| {
2552                            entries.iter().any(|e| matches!(e, EnvironmentEntry::Definition { name, .. } if name == p))
2553                        });
2554                        if placed {
2555                            entries.push(EnvironmentEntry::Structure {
2556                                inductive: inductive.clone(),
2557                                projections: projections.clone(),
2558                            });
2559                        }
2560                        !placed
2561                    });
2562                }
2563            }
2564        }
2565        if let Some((inductive, _)) = pending_structures.first() {
2566            return Err(unsupported(format!(
2567                "the structure η of {inductive} could not be ordered after its projections"
2568            )));
2569        }
2570        if let Some(why) = writer.refusal {
2571            return Err(unsupported(why));
2572        }
2573        let binder_kinds = entries
2574            .iter()
2575            .flat_map(|e| e.names())
2576            .filter_map(|name| {
2577                ctx.binder_kinds(name).map(|kinds| {
2578                    (
2579                        name.to_string(),
2580                        kinds.iter().copied().map(BinderKind::of).collect(),
2581                    )
2582                })
2583            })
2584            .collect();
2585        Ok(EnvironmentCertificate {
2586            schema: if entries
2587                .iter()
2588                .any(|entry| matches!(entry, EnvironmentEntry::Opaque { .. }))
2589            {
2590                OPAQUE_ENVIRONMENT_SCHEMA
2591            } else {
2592                ENVIRONMENT_SCHEMA
2593            }
2594            .to_string(),
2595            prelude_version: PRELUDE_VERSION.to_string(),
2596            base: request.base,
2597            conversion: request.conversion,
2598            nodes: writer.nodes,
2599            entries,
2600            target: request.target.to_string(),
2601            binder_kinds,
2602        })
2603    }
2604}
2605
2606// ---------------------------------------------------------------------------
2607// The independent re-check tool's door.
2608// ---------------------------------------------------------------------------
2609
2610/// How an independent re-check of one certificate document ended.
2611#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2612pub enum RecheckOutcome {
2613    /// Checked by the kernel and corroborated by the independent kernel.
2614    Verified,
2615    /// Checked by the kernel; the independent kernel does not cover every term. Never a pass.
2616    SingleChecked,
2617    /// Refused.
2618    Rejected,
2619}
2620
2621/// What `recheck <certificate.json>` reports: its outcome and one line per finding.
2622#[derive(Debug, Clone, PartialEq, Eq)]
2623pub struct RecheckReport {
2624    pub outcome: RecheckOutcome,
2625    pub lines: Vec<String>,
2626}
2627
2628/// Re-check a certificate document of any kind, dispatched by its schema.
2629///
2630/// An environment certificate is rebuilt on a stack sized for its deepest term. A certificate by
2631/// itself establishes that its proof checks against the environment it carries; that the statement
2632/// is the Lean source's is established by replaying the whole package, which binds it to the export.
2633pub fn recheck_document(json: &str, limits: &EnvironmentLimits) -> RecheckReport {
2634    let rejected = |line: String| RecheckReport {
2635        outcome: RecheckOutcome::Rejected,
2636        lines: vec![line],
2637    };
2638    let certificate = match read_certificate(json) {
2639        Ok(certificate) => certificate,
2640        Err(error) => return rejected(format!("REJECTED: {error}")),
2641    };
2642    match certificate {
2643        AnyCertificate::Proof(certificate) => match recheck(&certificate) {
2644            Ok(()) => RecheckReport {
2645                outcome: RecheckOutcome::Verified,
2646                lines: vec!["VERIFIED".into()],
2647            },
2648            Err(e) => rejected(format!("REJECTED: {e}")),
2649        },
2650        AnyCertificate::Theorem(certificate) => {
2651            let out = recheck_theorem(&certificate);
2652            let axioms = out
2653                .axioms
2654                .axioms
2655                .iter()
2656                .cloned()
2657                .collect::<Vec<_>>()
2658                .join(", ");
2659            match (out.rechecks, out.two_kernel_agreement) {
2660                (true, true) => RecheckReport {
2661                    outcome: RecheckOutcome::Verified,
2662                    lines: vec![format!("VERIFIED theorem {} (two kernels agree; axioms: [{axioms}])", certificate.theorem)],
2663                },
2664                (true, false) => RecheckReport {
2665                    outcome: RecheckOutcome::SingleChecked,
2666                    lines: vec![format!(
2667                        "SINGLE-CHECKED theorem {}: the independent kernel does not cover this proof (axioms: [{axioms}])",
2668                        certificate.theorem
2669                    )],
2670                },
2671                _ => rejected(format!("REJECTED: {}", out.error.unwrap_or_default())),
2672            }
2673        }
2674        AnyCertificate::Environment(certificate) => {
2675            let depth = match certificate.admitted_depth(limits) {
2676                Ok(depth) => depth,
2677                Err(error) => return rejected(format!("REJECTED: {error}")),
2678            };
2679            let rungs = depth
2680                .saturating_mul(3)
2681                .saturating_add(crate::term_store::INLINE_DEPTH);
2682            let report = std::thread::scope(|scope| {
2683                std::thread::Builder::new()
2684                    .name("recheck-environment".into())
2685                    .stack_size(crate::term_store::stack_bytes_for_depth(rungs))
2686                    .spawn_scoped(scope, || environment_report(&certificate, limits))
2687                    .map(|handle| {
2688                        handle
2689                            .join()
2690                            .unwrap_or_else(|p| std::panic::resume_unwind(p))
2691                    })
2692            });
2693            report.unwrap_or_else(|e| {
2694                rejected(format!("REJECTED: cannot reserve the re-check stack: {e}"))
2695            })
2696        }
2697    }
2698}
2699
2700fn environment_report(
2701    certificate: &EnvironmentCertificate,
2702    limits: &EnvironmentLimits,
2703) -> RecheckReport {
2704    let rebuilt = match certificate.rebuild(limits) {
2705        Ok(rebuilt) => rebuilt,
2706        Err(error) => {
2707            return RecheckReport {
2708                outcome: RecheckOutcome::Rejected,
2709                lines: vec![format!("REJECTED: {error}")],
2710            }
2711        }
2712    };
2713    let unsupported: Vec<String> = rebuilt
2714        .independent
2715        .iter()
2716        .filter_map(|(name, verdict)| match verdict {
2717            IndependentVerdict::Unsupported(why) => Some(format!("{name} ({why})")),
2718            IndependentVerdict::Agreed => None,
2719        })
2720        .collect();
2721    let outcome = if unsupported.is_empty() {
2722        RecheckOutcome::Verified
2723    } else {
2724        RecheckOutcome::SingleChecked
2725    };
2726    let mut lines = vec![
2727        format!(
2728            "{} environment certificate for {}: {} declarations rebuilt from the {} base under {} conversion",
2729            if outcome == RecheckOutcome::Verified { "VERIFIED" } else { "SINGLE-CHECKED" },
2730            certificate.target,
2731            certificate.entries.len(),
2732            match certificate.base { EnvironmentBase::Kernel => "bare kernel", EnvironmentBase::StandardLibrary => "standard library" },
2733            match certificate.conversion { ConversionDiscipline::Exact => "exact", ConversionDiscipline::Cumulative => "cumulative" },
2734        ),
2735        format!("primary check: passed ({} {})", if rebuilt.target_is_theorem { "theorem" } else { "definition" }, rebuilt.target),
2736        if unsupported.is_empty() {
2737            format!("independent re-check: passed ({} terms)", rebuilt.independent.len())
2738        } else {
2739            format!("independent re-check: unsupported for {}", unsupported.join("; "))
2740        },
2741        format!(
2742            "assumptions (transitive, statement and proof): [{}]",
2743            rebuilt.axioms.axioms.iter().chain(rebuilt.axioms.unresolved.iter()).cloned().collect::<Vec<_>>().join(", ")
2744        ),
2745        "statement binding: not established by a certificate alone; replay its package to bind the statement to the Lean source".into(),
2746    ];
2747    if !rebuilt.axioms.unresolved.is_empty() {
2748        lines.push(format!("unresolved: {:?}", rebuilt.axioms.unresolved));
2749    }
2750    RecheckReport { outcome, lines }
2751}