Repository navigation
[v0.4] #27 stage 5: certificates A, B, E and the audit modules as typed data, with no Lean text template in the production compiler #89
Description
Child of #27; plan in this comment (its stage 2; numbered 3 here because #79 was stage 2 of the delivery, #78 stage 1). Stage 2 of the delivery, the libraries (#79, #80, #86), is merged: neither proof library is handwritten Lean any more.
Scope
The Lean the production compiler writes for each certified root is still produced from text templates in Rust. After this issue no template of Lean source remains in the production compiler: the certificates and audit modules are built as typed data and rendered to Lean by the one Lean backend (R8), like every other generated module.
Surveyed on main (39acab42):
crates/lexlean/src/production/certificate.rs(8,796 lines): certificate A and E, about 570format!templates and 114 library lemma names (lib("…"));production/rust_cert.rs(2,348) andrust_term.rs(375): certificate B and the Rust-term renderer;production/preserve.rs(1,725): the staged audit module and the forbidden-token scan;backend/lean.rs: the probe and axiom-audit module texts.
A generated certificate is large (examples/reasoning/expected/verify/preserve: certificate A of one root 143 KB, certificate B 188 KB), and states the program as a literal of the target calculus followed by instances of the library's lemmas.
Why this is not a rewrite of the strings
A kernel term has every argument explicit; the templates rely on Lean's elaborator for implicit arguments, universe levels and unification. Producing typed data therefore needs one of:
- Explicit-argument forms in the library: for each lemma a certificate applies, a wrapper whose arguments are the program data (no implicit argument, no tactic), so a certificate is an application tree built from signatures the native library data already records (the registry lists every declaration and its type). The wrappers are added to the native libraries by the same one-time round trip as the migrations (regenerate Lean from the native source, state the wrapper, re-export, record the migration), so they are not handwritten Lean in the release tree.
- A generic soundness theorem per certificate kind quantified over the program, with its side conditions decided by
Decidableinstances, so a certificate istheorem c : Sound P := soundness P (by decide)expressed as data (the GNAF certification library of §17.18 is built this way: its theorems are instantiated at each fixture). - A typed term builder with its own elaboration of implicit arguments (rejected: it re-implements the elaborator inside the trusted base).
Recommendation: 2 where a certificate kind has a decidable side condition (certificates A, E, the audit module), 1 for certificate B's per-function correspondence terms.
Work
- A closed typed representation of the certificate modules (language 1.2 core-module data,
lexlean/core-module/2), built byproduction::certificateand rendered by the Lean backend. - The library wrappers or generic theorems of 1/2 above, registered per R3.
preserve::stageand the audit read the data, not text;FORBIDDEN_TOKENSis checked on the rendered Lean as today.- Certificates A, B, E are produced for every committed root and every production corpus;
leancheckerreplay and the exact axiom audit unchanged. - The compiler-semantics ID, every package, golden, fixture expectation and verified record are regenerated; SPEC §22, §17.17, §31 and VERIFICATION.md follow.
DoD
- No Lean source text template (a string literal that is Lean syntax) remains in
production/orbackend/lean.rs; a gate (in the manner ofVR-19) scans for it and is shown to fail on a planted template. - Every certified root's certificates A, B and E elaborate, replay in
leanchecker, and pass the exact axiom audit; their statements (the theorem types) are byte-identical to today's, so no certificate's meaning changes. - The generated Lean is deterministic and source-mapped; two roots equal the previous output's statements.
Acceptance criteria
- Planted defects each turn a named gate red: a wrong lemma application, a changed statement, a forged program literal, an added axiom, a reintroduced text template.
- Languages 1.0 and 1.1 byte-identical; language 1.2 identities regenerated and recorded.
- Full
just vv.
Decision wanted from the maintainers
The route above (1 or 2 per certificate kind), because it fixes what the libraries must gain. I will start with certificate A and the audit module (route 2) and post the measured size of the result before certificate B.
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsTodo
Activity
humuhumu33 commentedon Oct 14, 2026
Claiming this for implementation (Hermes agent): stage 3 of #27 — certificates A, B, E and the audit modules as typed data, no Lean text template left in the production compiler.
Plan of record, following the route in the issue: start with certificate A and the audit module under route 2 (a generic soundness theorem per certificate kind, side conditions decided by
Decidable), post the measured size of the result, then certificate B under route 1 (explicit-argument library forms). Register per R3: theVR-19-style template scan (row, §31, scenario, failing test, implementation), then the typed representation, then regeneration of every language-1.2 identity, package, golden, fixture expectation and verified record.Branch:
claude/issue-89-certificates-as-typed-dataoffmain. I will post the measured size of certificate A before starting certificate B, as the issue asks.humuhumu33 commentedon Oct 14, 2026
Stage 3 reconnaissance, measured on
maind284b1652(branchclaude/issue-89-certificates-as-typed-data).Route I am taking, as the issue proposes: route 2 (a generic soundness theorem per certificate kind, side conditions by
Decidable) for certificate A, certificate E and the audit module; route 1 (explicit-argument library forms) for certificate B's per-function correspondence terms. Route 2's ingredients are already in the migrated library — per-construct soundness theoremsLexLeanPreservation.Rust.{armPat,build,const,foldB,foldN,foldStep}_soundandDecidableinstances such asinstDecidableEqIntKind_lexLeanPreservation. Whether a single program-quantifiedsoundnesstheorem already exists is the first thing the first slice settles (RustSoundholds only 29 declarations, so it is readable in full).Measured:
crates/lexlean/src/production/certificate.rs8,796)format!sites underproduction/lib("…")lemma namesFunRel,ListEnc*,LookOps,Obs*,OptEnc,Rel, …)examples/reasoning/expected/verify/preserve/)R2/RustStd.lean283,057 B ·R3/RustStd.lean229,776 B ·R5/RustCore.lean175,647 B ·R7.lean60,880 B ·Compose/*5–8 KB ·audit.txt735,864 Blanguage/preservation-1.2/library/src/*.lex.tex, schemalexlean/core-module/2, 9,247 declarations, 52.9 MB of sourcetests/preservation-library/oracle/export.lean+ script under pinned Lean 4.32.1; recordtests/preservation-library/MIGRATION.md(the library was 13 handwritten Lean files, 525,026 bytes, atba52c601)The gate:
VR-19is not a template scan — it states that the native Atlas source graph is self-contained (suiteverification, casecrates/conformance/src/cases/verification.rs:1139). So "a gate in the manner ofVR-19" means a new sibling row in suiteverification, registered per R3 with its own scenario andconformance_<id>, whose plant is a Lean-syntax string literal left inproduction/orbackend/lean.rs.First slice, in order: (1) settle the program-quantified soundness question by reading
RustSoundand one committed certificate; (2) register the template-scan gate per R3 and make it fail on a planted template; (3) certificate A as typed data, constructed as a core module and rendered by the one Lean backend, with its emitted statements byte-identical; (4) the audit module on the same representation; (5) regenerate the 1.2 identities, packages, goldens and records, with languages 1.0/1.1 byte-identical; then post the measured size of the result before starting certificate B.humuhumu33 commentedon Oct 14, 2026
Route 2 validated — with one measured caveat (read out of the native library on
maind284b1652).1. The program-quantified soundness theorem exists.
RustSoundholds 29 declarations in full —sound,simulate,funSem_of,fl_of,fnFallible.eq_1,isTemp.eq_*,plainItem.eq_*,item_ne,kind_ne, and the private match helpers.LexLeanPreservation.Rust.soundis a theorem of exact policy (axiomsClassical.choice,Quot.sound,propext) with the Π-spinesimulateandfunSem_ofhave the same shape (their types are 12,140 and 563 bytes of kernel data). So the certificate side already has one theorem quantified over the program, which is what route 2 needs.2. Its side condition is not decidable today.
LexLeanPreservation.Rust.CrateOKis a definition (kind: definition,fun p => …, transparencysemireducible, axiomsQuot.sound,propext), defined inRustCorr, and no declaration in the library mentions it besides itself. The library's onlyDecidableinstance isinstDecidableEqIntKind_lexLeanPreservation. So route 2 for certificate A needs a decision procedure forCrateOK(and whatever its body requires) added to the native library by the one-time round trip — an addition to the library, not just a re-rendering of the certificate. That addition is the measured cost the route decision hinges on, and I will post its size before certificate B, as the issue asks.3. What a committed certificate actually holds (
examples/reasoning/expected/verify/preserve/…/R2/RustStd.lean, 283,057 B):It cites
Rust.soundzero times — the soundness application is not in the certificate module; the certificate states the program, the crate, the flags and aFunOKobligation per function, plus aCrateOKassembly. So certificate A's statements are already data-shaped; what the 897 template sites actually produce is the proof bodies. Route 1/2 must reproduce those as kernel terms (every implicit argument explicit) while the statements stay byte-identical — which is what makes "no certificate's meaning changes" the acceptance test rather than a slogan.humuhumu33 commentedon Oct 14, 2026
Correction to my reconnaissance above, and the vehicle finding.
Correction — I mislabelled the artifacts.
examples/reasoning/expected/verify/preserve/LexLeanPreserve/C21e79bf….R2/RustStd.leanis certificate B (module_for(a, "rust-std")), not certificate A. Certificate A of that root is the siblingR2.lean(189,200 bytes), which opens with twelveimport LexLeanPreservation.*lines anddef __prog : _root_.LexLeanTarget.TargetSyntax.Program :=. Thedef program/def krate/def flagsdefinitions I described belong to certificate B and are emitted byproduction/rust_cert.rs::certificate_b(lines 2263-2267), usingcertificate::render_programandrust_term::crate_term. The measurements (sizes, 897format!sites, 114lib("…")names, theRust.soundΠ-spine,CrateOKbeing a definition with noDecidableinstance) stand.The vehicle, checked by execution:
CoreModule::parse/from_source(crates/lexlean/src/ir/core.rs:689,699), accepts exactly one\coredata{…}payload — what a native.lex.texexporter writes. Fed certificate text it refuses:from_source(certificate) -> a native core source holds exactly one core payload.backend/core.rs::lean_textrenders a core module asmodule/public import …/namespace LexLeanCore.Runtime+ a JSON decoder + encoded chunks — a different species of file from a certificate module.compiler/expected/build/modules/LexLeanTarget/TargetFixtures.lean:439is dense, fully-qualified and ascriptive, where the certificate writes a compact multi-line{ adts := […], functions := […] }with opened short names. So neither existing backend reproduces the certificate's current text.crates/conformance/src/lx.rs(module_tex:318, withinductive/structure/definition/theorem/mutual:265-299, emittinglexlean/semantic-module/2), the typed calculus term buildercrates/lexlean/src/calculus/term.rs(program:320), and the render dispatcherbackend/lean.rs::render_module:2083.What this means for the route. The certificate's
program/krate/flagsvalues are already typed (calculus::Program,calculus::rust::ast::Crate); what is text is their rendering, the proof bodies, and the module layout. So the work is the library + representation change the issue names — route 1's explicit-argument wrappers and/or route 2's generic theorem with decidable side conditions, plus a typed representation of the certificate module itself — not a mechanical replacement offormat!inproduction/certificate.rs.One clarification of the DoD I will hold to as stated: it requires each certificate's statements (the theorem types) to be byte-identical to today's, with the files themselves regenerated — not whole-file byte-identity. That is what makes a typed path viable while the renderer's form changes.
Evidence is executable: a unit test was added at
crates/lexlean/src/backend/core.rsasserting the certificate text is rejected as a core source and printing the head oflean_text(RustSound); it passes (cargo test -p lexlean --lib certificate_text_is_not_core_module_data).afflom commentedon Oct 14, 2026
Maintainer decision recorded: the route proposed above is accepted. Certificates A and E and the audit module use a generic soundness theorem per certificate kind, with its side conditions decided by
Decidableinstances; certificate B uses explicit-argument forms of the library lemmas.Ordering change (see the #27 comment of 2026-10-14): the new theorems and wrappers this needs are new LexLean content in the proof library, and the maintainers want the repository to own its authoring surface for that. So this issue now depends on the authoring surface (the new stage 3 of #27) and starts after it; the library growth is authored in
.lex.texthrough it, not by a one-time Lean round trip. Delivery stage is now 5.Generated by Claude Code
[-][v0.4] #27 stage 3: certificates A, B, E and the audit modules as typed data, with no Lean text template in the production compiler[/-][+][v0.4] #27 stage 5: certificates A, B, E and the audit modules as typed data, with no Lean text template in the production compiler[/+]humuhumu33 commentedon Oct 14, 2026
Vehicle verdict: the existing typed representation cannot state a certificate — with two independent reasons, both executed.
The experiment is committed as a test (312 lines,
crates/lexlean/tests/typed_certificate_vehicle.rs, two tests, both passing). It states certificate B's two declarations inlexlean/semantic-module/2— adefinitionof typeTargetSyntax.Programand atheoremwhose statement appliesFunOK— decodes them, renders them withbackend::semantic::render_lean, and compares against the committed certificate.1. The typed module is rejected at linking.
A certificate names
TargetSyntax.Program,FunOK,CrateOK— constants no\useglossarypackage declares (the certificate relies onimport LexLeanPreservation.RustSoundplusopen LexLeanTarget LexLeanPreservation.Rust). The semantic-module IR has no way to reference an external constant, so a certificate cannot be stated in it at all, before any question of rendering.2. Even rendered, the statements are not byte-identical. The backend emits
where the certificate writes
Structural differences, not cosmetics: an attribute and a visibility modifier the certificate does not have (
@[expose] publicvs baredef); a module-qualified name (LexLeanTarget.TargetSyntax.Program, or a leading-dot.TargetSyntax.Programwith an empty prefix) where the certificate relies onopen; every application argument parenthesised and ascribed (FunOK (program) (krate) (flags) (0)) where the certificate writes the bare form; a proof rendered as a tactic script (:= by rfl) where the certificate carries a proof term; and a prefixed import (public import LexLeanTarget.LexLeanPreservation.RustSoundvsimport LexLeanPreservation.RustSound).What this means for the route, and the decision I need.
Either way, this issue is not a replacement of
format!insideproduction/certificate.rs: it is (a) a representation for certificate modules that can name the library, and (b) the library additions of route 1/2 (including aDecidableforCrateOK, which the earlier comment showed is missing). Which of the two readings you intend decides how much of (a) has to exist, and I would rather ask than guess: is the requirement on the emitted text, or on the statement up to elaboration?humuhumu33 commentedon Oct 14, 2026
Parking this work, per the ordering recorded on #27 (2026-10-14): #89 now depends on the authoring surface (#93) and starts after it, and the library additions are authored in
.lex.texthrough that surface rather than by a one-time Lean round trip. The reconnaissance and the vehicle verdict above stand as input for that stage — in particular that a certificate cannot be stated in the existing semantic-module representation (linking refuses the library references) and that the backend renders neither the certificate's baredefform nor its proof terms. Branchclaude/issue-89-certificates-as-typed-datais kept as an archive (with the executable vehicle test); I am not proposing it for merge, and I will not implement against the superseded route.humuhumu33 commentedon Oct 15, 2026
Resuming #89 now that #93 is verified (its DoD comment of 2026-10-15). Plan of record, measured on
mainatc257a657, and what starts today.What the certificates are, on
mainR<i>.lean)__progliteral, encoders__enc_k, width predicates__fits_k, onetheorem __rel_k : FunRel __prog k […] (Rel …)per closure member with a proof term built from 114 library lemmas, thenrootanddenoteproduction/certificate.rs, 568format!sitesR<i>/Rust*.lean)program,krate,flagsliterals;theorem fun<n> : FunOK …from a typed derivation tree of 20 rules;theorem crate_ok : CrateOK … := by intro …; match …(the one tactic proof)rust_cert.rs41,rust_term.rs71R<i>/Compose/Rust*.lean)theorem __wt_k : ∀ v, WT program krate flags (enc v) (.adt k)per encoded type,theorem root : ∃ ro, RealizesFn … ∧ RCI …byRust.compose; 8 lemma names in allcertificate.rs(compose, from line 7733)import …,#print axioms …per library declaration and certificate root, the vocabulary#checkspreserve.rs::audit_text(The 897
format!sites of my earlier count include diagnostics and the lowering; the Lean text ones are the 680 above.) The 76 is 7 + 6 + 36 + 27 roots overexamples/{production,production-coverage,models,reasoning}with their targets;lexlean verifywrites them intoexpected/verify/preserve/andpreservation.json.The representation
A certificate becomes a native core module (
lexlean/core-module/2, SPEC §17.10, §17.21.8): kernel terms as a shared DAG with every argument and universe level explicit, built byproduction::certificateas aCoreModulevalue, rendered to Lean by the one core backend (backend::core::lean_text, the same decoder the libraries use), and written into the verify records in the authoring notation (sdk::to_notation), so a committed certificate is.lex.texa person can read, not Lean. The same species as the libraries, which is what #79 made them; the_root_.-qualification pass (from_the_root), thequalifypass of E and theFORBIDDEN_TOKENSscan have nothing to scan in kernel data (the schema has noaxiomkind, and every row carries theexact{axioms}policy the decoder enforces), so the scan stays on the rendered Lean and the policy row is the guard, as for the libraries.This answers the two vehicle findings of 2026-10-14: a core module names any constant of its imports (
Reasoning.Clinic.feverish,LexLeanPreservation.Rust.compose), so linking does not refuse it (the semantic-module IR cannot name them, and is not the vehicle); and no rendering form has to be imitated.The two routes, as accepted, and what each actually costs here
A kernel term has every implicit argument written. The generator can write them when the argument is determined by the lemma's recorded Π-type (
language/preservation-1.2/library, every declaration's type is kernel data) and the arguments it already has: that is substitution into a Π-type, not elaboration. Where the elaborator today unifies (anrfl, a_), the explicit term isEq.reflat the type read off the instantiated signature. Where an implicit is not determined by the explicit arguments and the expected type, the lemma gets an explicit-argument form in the library (route 1), authored in the notation through #93's surface inlanguage/preservation-1.2/library/src/*.lex.texand kernel-checked with the library (asLexLeanCertification.authored_eq_symmwas in #104). Route 2 (a generic theorem with aDecidableside condition) is for the one tactic proof,crate_ok: a library theoremcrateOK_of_funs : (∀ i, FunOK p k A i) → CrateOK p k Aor aDecidable (CrateOK p k A)instance, whichever is smaller when measured. I will post the count of lemmas that need a route-1 form before the stage that uses them, since that count is the library growth the maintainers asked to see.The statement clause of the DoD, as I will hold it
"Statements byte-identical to today's" cannot mean the module text: a core module renders as a decoder and data, and §17.17 states the certificate as a Lean statement, not a text. I hold it as: the type of every theorem of the typed certificate, after the decoder installs it, is the kernel term the current text elaborates to, declaration by declaration, for every committed root (57 A, 76 B, 76 E). Evidence: an oracle under
tests/(outside the release tree, astests/preservation-library/oracle/is) elaborates the current certificates and compares. If the maintainers meant the text, say so here and I stop before stage 1.Stages, smallest first, each a PR green on full
just vv, each registered per R3 before its implementationproduction/(nodes, constants with levels, binders, literals, instantiation of a recorded Π-type),Certificatecarrying aCoreModule,preserve::stagerendering through the core backend, the record in notation; certificate E on it (76 modules, 8 lemmas:compose,rel_ne_stuck,wt_adt,wtl_cons/wtl_nil, thewt_*encoders,RealizesFn,RCI); the statement oracleprogram/krate/flagsas kernel literals,crate_okby route 2crate_ok's theoremfitsas kernel definitions,denoteAuxiliaryModule(the probe and axiom-audit texts ofbackend/lean.rsare already typed on that branch, so this issue leavesbackend/lean.rsto #90 and takespreserve.rs::audit_textafter #90 lands); the gate: averification-suite row that no Lean-syntax string literal remains inproduction/, failing on a planted template; SPEC §17.17/§22 wording, §31, CHANGELOG, VERIFICATION, the regenerated language-1.2 identities and recordsLanguages 1.0 and 1.1 have no certificates, so their bytes do not move. Stage 1's branch is
claude/issue-89-typed-certificatesoffc257a657; the archivedclaude/issue-89-certificates-as-typed-datastays as it is.humuhumu33 commentedon Oct 15, 2026
Stage 1 is up as #146: the vehicle, and certificate E on it, for all 76 certificates E of the four corpora. Measured on the branch at its head, Lean 4.32.1, Linux x86-64.
What the stage delivers
lexlean/core-module/2), built byproduction/certificate/data.rsover a kernel-term builder (production/kernel.rs: a hash-consed DAG, lifting and instantiation,apply_pi, which applies a library lemma by instantiating its recorded Π-type one binder at a time; anrflisEq.reflat the stated equation)T.recandT.rec_jterms, the motives in the order the kernel nests the containers (the block's constructors first, then what the auxiliaries nest), an induction hypothesis where the text recursed; the five groups ofproduction-coverage(Grove, nested throughList,Option,List (Nat × ·),Except,Nat × ·;Tree;Rose Nat; the mutualExpr/Stmt;Term) pass the kernellean_text_legacywritesimportlines, nomoduleheader, and the decoder under the module's own namespace, because amodulecannot import the legacy A and B texts (Lean refuses:cannot import non-module … from module) and two legacy-rendered core modules imported together would declare the decoder twice (found by the audit module)…/Compose/RustStd.lex.tex, 1,424,062 bytes for the 76 (18.7 KB each on average), reading back to the data byte for byte;preservation.jsonbinds those bytesaudit_core): imports within the environment, only safe theorems/definitions/abbreviations, every policy exact, no forbidden constant in any spelling; every row pins its axioms from the library registry (for example__wt_krows pin[propext],rootthe three), and the audit module prints and checks every rowisDefEq),tests/preservation-certificates/E-STATEMENTS.mdSP-14, with five plants refused: a wrong lemma, a changed statement, a forged crate reference (by the kernel), an added axiom (by the data audit, before Lean), a lying policy (by the audit module);SP-05and thecertificate-e-rejectedfixture read and plant the data form; the text generator of E (compose,wt_theorems,wtf,wt_apply,wtl,wt_auxiliary,qualify, 472 lines) is deletedlexlean verifyof the four corpora end to end (A and B as text, E as data), every example's records regenerated,check-fixtures269 equal,check-golden,check-calculus,check-production-exportgreen; fulljust vvin the PR's CIWhat the measurement says for stages 2 and 3
max_total_source_bytesis reached earlier (certificates-total-limitnow refuses atr14instead ofr25, sameLLS8002); the elaboration time of the four corpora did not move noticeably (production 3m47s wall for its whole verify).Next: stage 2, certificate B (
rust_cert.rs:program,krate,flagsas kernel literals, the 20 rules as explicit applications,crate_okby a library theorem or aDecidableinstance, measured first), on the same vehicle.49 remaining items
#89 stage 2: per-phase timing of a rule application (to be removed)
#89 stage 2: node-walk counter (to be removed)
#89 stage 2: the kernel builder opens a lemma once (checkpoint)
#89 stage 2: scaling lens at 400 arms (checkpoint)
#89 stage 2: build/render timing (checkpoint)
#89 stage 2: the kernel builder remembers the loose-variable bound of…
#89 stage 2: scaling lens (checkpoint)
#89 stage 2: the placeholder-free shortcut belongs to resolve, not to…
#89 stage 2: remove the timing lenses
#89 stage 2: the crates the Rust machine evaluates are computable def…
#89 stage 2: keep the machine module on failure (to be removed)
#89 stage 2: remove the machine-module diagnostic
feat(preservation): no Lean source text template remains in the produ…
train: regenerate every record the combined tree moves
train: no ignored test remains; SM-32 precedes SM-33; one README row …