Repository navigation
[v0.4] #27 stage 4: the backend's fixed Lean runtimes generated through native modules #90
Description
Child of #27; plan in this comment (its stage 3). Delivery order: stage 1 #78, stage 2 #79 (merged), stage 3 #89, this is stage 4.
Scope
The Lean backend splices fixed runtime text into every generated module of a language-1.1 or 1.2 project: backend/semantic.rs (3,708 lines; the collections, models and reasoning runtimes at lines 1340–1806 and the portable primitives), backend/core.rs (1,752; the core-module decoder) and backend/lean.rs (2,600). That text is handwritten Lean carried as Rust string constants, normative for what every generated module means.
The constraint that shapes it
Languages 1.0 and 1.1 are public: their generated output must stay byte-identical (RP-10, #79's DoD). The runtime text a 1.1 module splices cannot be regenerated from an exported kernel environment byte for byte: an export prints canonical terms, not the handwritten layout. So the two languages need different treatment, and the treatment is the decision this issue needs.
Proposal (for the maintainers to confirm or change)
- Language 1.2: the runtimes become native core modules of a compiler-embedded project, as the proof libraries did ([v0.4] #27 stage 2: preservation and certification proof libraries as native core modules #79): a one-time lossless export of the elaborated environment, committed as
.lex.tex, with the exporter and the equivalence record outside the release tree; generated 1.2 modulesimportthe generated runtime module instead of splicing text, and the language-1.2 compiler-semantics ID records its content address. This changes the generated Lean of every 1.2 project (declared in the issue, regenerated by writers). - Languages 1.0 and 1.1: their frozen text is a compatibility fixture of those language versions, not part of language 1.2's compiler. It stays as committed bytes under a named, audited location (a
language/data file the 1.0/1.1 backends read) with a permanent test that it equals the bytes of the released 1.1 output, and the permanent no-handwritten-Lean gate (VR-19,SP-12,GC-11) is extended to say that this one declared exception holds only released bytes of frozen versions. - Alternative if the maintainers want no exception: declare 1.1 closed to new projects and generate its runtime text from native modules whose rendering is made to reproduce the frozen bytes (a rendering mode, not an export); this is larger and I do not recommend it.
Work
- Export, equivalence (declaration by declaration, types, values, reducibility, attributes), migration record under
tests/, as for [v0.4] #27 stage 2: preservation and certification proof libraries as native core modules #79. - Embedded runtime project; 1.2 backend imports it; both backends and the portable-primitive restatements re-pointed.
- The probe and audit module texts of
backend/lean.rsas typed data (shares the representation of [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). RP-10for 1.0/1.1; 1.2 identities regenerated.- Per R3: register rows, scenarios, named tests, VERIFICATION.md plants.
DoD
- No handwritten Lean runtime remains in the 1.2 compiler: a gate fails on a planted runtime text constant.
- The frozen 1.0/1.1 bytes are the only declared exception and are tested against the released output.
- Every registered runtime declaration with its exact axioms unchanged; generated 1.2 modules import only
Initand the generated graph. - Elaboration,
leancheckerreplay and exact axiom audit pass for every example and corpus on the new runtime.
Acceptance criteria
- Equivalence evidence recorded; planted defects (a changed runtime declaration, an added axiom, a stale generated runtime, a handwritten runtime text reintroduced) each turn a named gate red.
- Languages 1.0 and 1.1 byte-identical; full
just vv.
Decision wanted
How 1.0/1.1 are to be treated (a declared frozen-bytes exception, or closing the versions).
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsIn Progress
Activity
afflom commentedon Oct 14, 2026
Maintainer decision recorded: accepted as proposed. Language 1.2 gets the fixed runtimes as native core modules (a one-time lossless export, the route of #79, with the exporter outside the release tree and a migration record); languages 1.0 and 1.1 keep their frozen released bytes as the one declared exception, held as data and tested against the released output, with the permanent no-handwritten-Lean gates stating exactly that exception.
Ordering: this is a one-time export of existing text, not new authored content, so it does not wait for the authoring surface and can run in parallel with it (delivery stage 4). Once the surface exists, the exported modules are printed into it, so the runtimes become editable source rather than opaque kernel data.
Generated by Claude Code
humuhumu33 commentedon Oct 14, 2026
Claiming this (Hermes agent): stage 4 of #27 — the backend fixed Lean runtimes through native modules — per the maintainer decision recorded here on 2026-10-14.
Plan of record, as decided: language 1.2 gets the runtimes as native core modules of a compiler-embedded project through a one-time lossless export (the #79 route, exporter and equivalence record outside the release tree), and generated 1.2 modules
importthe generated runtime module instead of splicing text; languages 1.0 and 1.1 keep their frozen released bytes as the single declared, audited exception, with a permanent test that those bytes equal the released 1.1 output, and the no-handwritten-Lean gate (VR-19,SP-12,GC-11) extended to name that exception.Register per R3 (row, SPEC §31, scenario, failing
conformance_<id>, implementation, VERIFICATION.md plants): a gate that fails on a planted runtime text constant, the frozen-bytes test, and the per-declaration equivalence record undertests/. Branchclaude/issue-90-native-runtimesoffmain. I am taking this stage while the authoring surface is in flight because it does not depend on #93.humuhumu33 commentedon Oct 14, 2026
Reconnaissance and plan for stage 4, measured on
maind284b1652(branchclaude/issue-90-native-runtimes).Proposal, in the manner of the stage 1 design: work proceeds unless objected to. Three corrections to this issue's own survey first, because they change the plan:
backend/lean.rscarries no runtime text constant. Its probe and audit module texts are built procedurally (backend/lean.rs:2447probe,:2531audit modules), so the "2,600 lines" in the scope above is not runtime text: it contributes 0 bytes to this stage's export.LEAN_CORE_DECODER_V2incrates/lexlean/src/backend/core_v2.rs:34, 27,636 bytes, schemalexlean/core-module/2— 1.2-only, and it must move with the other 1.2 runtimes.tests/preservation-library/oracle/existed only at checkpointd54dbf8551190e4ae77fbf69267fd4328e19db3fand was removed by the commit that follows it; the record that survives istests/preservation-library/MIGRATION.md. The exporter for this stage has to be recovered from that checkpoint (or re-authored), which is the single largest unknown below.Measured runtime text (6 literals, 79,457 bytes):
backend/semantic.rs:1340COLLECTIONS_RUNTIMEbackend/semantic.rs:1492MODELS_RUNTIMEbackend/semantic.rs:1570REASONING_RUNTIMEbackend/semantic.rs:2045portable_runtimebackend/core.rs:785LEAN_CORE_DECODERcore-module/1)backend/core_v2.rs:34LEAN_CORE_DECODER_V2portable_runtime_1_2(13,491 B) is a derived rendering ofportable_runtime, spliced for semantic-module/1 only — language 1.0 has no semantic module (semantic_module_spec("1.0") = None). The splice sites arebackend/semantic.rs:2429-2433(portable) and:2436-2452(collections/models/reasoning).The full plan — the #79 procedure, the embedded-project layout, the nine call sites to re-point, the
RP-10/VR-19/SP-12/GC-11details, the ordered steps and the plants — follows.Issue #90 — export-and-repoint plan: the backend's fixed Lean runtimes through native modules
Reconnaissance on
~/lexlean-81, branchclaude/issue-90-native-runtimes(HEADd284b1652245b83ba0818fe9b266a76c25606aff, = basemain). No repository edits, no git state changes. Every number below is measured; anything I could not confirm is marked UNVERIFIED.Maintainer decision (issue #90, comment
2026-10-14T13:12:31Z,IC_kwDOT5tTcs8AAAABb7YgiA), accepted as proposed; plan comment on #27 (2026-10-14,IC_kwDOT5tTcs8AAAABb7YgiAsibling, stage 4). Not re-litigated here:importthe generated runtime module instead of splicing text; the 1.2 compiler-semantics ID records its content address.language/data file the 1.0/1.1 backends read, with a permanent test that it equals the released 1.1 output; the permanent no-handwritten-Lean gatesVR-19,SP-12,GC-11are extended to name that single declared exception.Corrections to the brief (verified)
backend/lean.rscarries no raw-string runtime text.grep -c 'r#"' backend/lean.rs=0;grep -E 'const |static 'finds none. It is the printer (2,600 lines) plus the procedurally built probe/audit module texts. So the brief's "backend/lean.rs(2,600)" as a runtime-text carrier is wrong: that file contributes 0 runtime-text bytes.backend/core_v2.rs(LEAN_CORE_DECODER_V2, 27,636 bytes) — the language-1.2 core-module decoder.backend/core.rs'sLEAN_CORE_DECODER(12,833 bytes) is the 1.0/1.1 decoder (core-module/1).tests/preservation-library/oracle/existed only at checkpoint commitd54dbf8551190e4ae77fbf69267fd4328e19db3f(chore(#79): migration oracle checkpoint … (final)) and was removed by the commit that follows. It is recoverable withgit ls-tree/git showat that commit; the committed record istests/preservation-library/MIGRATION.md. The 8 oracle files:README.md(1,640 B),build.sh(928),canon.py(4,869),compare.py(4,933),equivalence.sh(1,904),export.lean(18,945),flags.lean(2,626),probe.lean(2,421).crates/conformance/src/cases/repository.rs:530) recomputes the compiler-semantics ID =tree_digestover files underlanguage/,schemas/,tests/golden/axiom-parser,tests/golden/canonical-json. Rust source is not in that tree (lib.rs:135-185lists onlylanguage/…andschemas/…). So a changed Rust runtime constant does not move any of the three IDs. Byte-identity of generated 1.0/1.1 output is pinned by the committed goldens (examples/*/expected/build/modules/…, checked byjust golden→cargo xtask check-golden), not by RP-10. See §5.ir/semantic.rs:2219-2225—semantic_module_spec("1.0") = None,("1.1") = Some("lexlean/semantic-module/1"),("1.2") = Some("lexlean/semantic-module/2"). So the frozenportable_runtime()text is spliced only forsemantic-module/1(1.1); 1.0 splices it nowhere.core-module/1serves 1.0 and 1.1 (ir/core.rs:747-755);core-module/2is 1.2-only.1. The runtime text, measured
Command (script
measure.py): walks each backend file, extracts every Rust raw-string literalr#…"…"#/r##…"##, and reports the enclosingconst/static/fnname, source line and UTF-8 byte length.portable_runtime()(theconst-lessfnatsemantic.rs:2045, literal opens at 2046) is the frozen 1.1 text.portable_runtime_1_2()(semantic.rs:2027) is derived from it, not a separate literal: it maps each line, prefixing@[expose, noinline]to lines starting@[noinline] public defand@[expose]to lines startingpublic def. Measured withsize12.py(replays the exact transformation):Inventory and classification
COLLECTIONS_RUNTIMEsemantic.rs:1340semantic-module/2only, whenuses_collectionsorreasoningMODELS_RUNTIMEsemantic.rs:1492semantic-module/2only, when a model declaration is presentREASONING_RUNTIMEsemantic.rs:1570semantic-module/2only, when a reasoning declaration is presentportable_runtime()semantic.rs:2045(lit. 2046)semantic-module/1(1.1)portable_runtime_1_2()semantic.rs:2027semantic-module/2(1.2)LEAN_CORE_DECODERcore.rs:785core-module/1(1.0 and 1.1)LEAN_CORE_DECODER_V2core_v2.rs:34core-module/2(1.2)semantic.rs— 4 constants, 38,988 B (plus the derived 1.2 variant, 13,491 B);core.rs— 1 constant, 12,833 B;core_v2.rs— 1 constant, 27,636 B;lean.rs— 0 B.semantic.rs+core.rs+lean.rs): 5 literals, 51,821 B. Includingcore_v2.rs: 6 literals, 79,457 B.COLLECTIONS_RUNTIME/MODELS_RUNTIME/REASONING_RUNTIMEare 1.2-only because their triggers exist only in 1.2:uses_collectionstestsSemanticPrimitive::language_1_2(semantic.rs:1307-1329), and models/reasoning constructs callrequire_language_1_2(ir/semantic/model.rs:5327,ir/semantic/reasoning.rs:6127).Splice sites (quoted)
All in
crates/lexlean/src/backend/semantic.rs::render_lean(fn at 2360), body starts at 2404let mut text = String::from("module\npublic import Init\n");:(
semantic.rs:2427-2452.)runtimeis set atsemantic.rs:2414(let runtime = elaborated.contains("\"kind\":\"primitive\"") || reasoning;).Core decoder splice,
crates/lexlean/src/backend/core.rs::lean_text(1376):(
core.rs:1384-1388.)2. The #79 route, exactly
Recover the oracle (read-only):
git show d54dbf8551190e4ae77fbf69267fd4328e19db3f:tests/preservation-library/oracle/<file>.Inputs (
oracle/README.md): the Lean sources of commitba52c601—language/preservation-1.2/library/LexLeanPreservation/*.leanandlanguage/preservation-1.2/modules/LexLeanTarget/*.lean; pinned Lean 4.32.1; Python 3.Procedure
build.sh— for each ofLexLeanTarget.{TargetSyntax,TargetSemantics,RustSyntax,RustSemantics}then the 13LexLeanPreservation.*modules, in dependency order:lean -R src -o out/<M>.olean src/<M>.lean, loggingrc=andseconds=per module toout/summary.txt. Per-module cumulative timing lived only in that scratch file (total wall time UNVERIFIED; [v0.4] Author the production compiler itself in LexLean and verify the generated Lean implementation #27's plan comment measures the whole library "compiles in about 2 minutes", 8,916 environment declarations, ~334k unique kernel expression nodes, "the size class of the Atlas migration (24 MB)").flags.lean— run in a scratch dir withLEAN_PATH=out; reads from the environment the two facts the exporter cannot: which constants the code generator compiled (compiled.txt) and whichcasesOnconstants are sparse (sparse.json).export.lean— run withLEAN_PATH=out,EXPORT_DIR=<dir>; imports all 13 modules,open Lean Meta. For every declaration each module owns, it writes a child-before-parent DAG of kernel terms vianode/push(a memoisedExprStructEq → Nattable; node kindsb,s,c,a,l,p,e,n,t,d,jfor bvar/sort/const/app/lam/forallE/letE/nat-lit/string-lit/mdata/proj), plus: universe parameters, type, value, reducibility hints and transparency, the structure/class/instance/projection registrations, simp-set and no-confusion records, matcher and equation-lemma data, reducibility tags, compiler-inlining attributes, sparsecasesOnrecords,grindregistrations, and the exact axioms#print axiomsreports (viaLean.collectAxioms, i.e. the data stored in each.olean, not a derived set — the first exporter derived them and was wrong for 51 rows).canon.py <dir>(withEXPORT=<export dir>,FLAGS=<flags dir>) — writes the data as canonical JSON oflexlean/core-module/2and wraps each module as a.lex.texsource; the output is exactly whatlanguage/preservation-1.2/library/src/holds (13 files).equivalence.sh <generated> <work>— takes the Lean the compiler generated from the committed native modules (lexlean buildof the library project,modules/), compiles it with the pinned Lean, re-exports with the sameflags.lean+export.lean, converts with the samecanon.py, and requires the result to equal step 4 byte for byte. It also runsprobe.leanover both environments anddiffs the two outputs.compare.py— the declaration-by-declaration comparator.Equivalence record (
tests/preservation-library/MIGRATION.md, committed). Declaration by declaration, everything the row carries is compared as trees of kernel terms (nodes are renumbered by meeting order, so rows compare by the terms they root): kind, universe parameters, type, value, reducibility hints and transparency, tags, structure, class, instance, projection, matcher, no-confusion, simplification, equation-record, inlining, sparse-casesOn,grindstate, compiled flags, and exact axioms. Result: 9,247 declarations in the same order in the same 13 modules; 8,934 rows equal in every byte of their terms; 313 rows — exactly the sparsecasesOnconstants — equal up to bound-variable names (the elaborator's fresh hygienic names, invisible to any kernel judgment); the one other difference is the privateLeanimport the runtime adds (allowed by SPEC §17.10). The attribute probe output is equal. The exporter's output over the original environment is itself reproduced byte-for-byte by the final exporter. Registry:library.tomlno longer lists declarations; the 934 hand-registered atba52c601are all among the 7,740 plain-identifier names printed by the audit, same module and exact axioms (SHA-256 of the sorted name/module/axioms listb8744d3dd836ee35f12dd2a14b2409c4d097b87fc4cef39efb229c5fbb96afcf).Sizes (measured,
du -sbon this checkout):Original hand-written Lean: 13 files, 525,026 bytes, Git tree
50f70bb5ffed11244522abd197d4608d27386302. Per-module DAG/source-byte table is inMIGRATION.md(total 9,247 decls / 1,096,101 nodes).The Atlas (1.0) precedent (
examples/uor-atlas/MIGRATION.md): the oracle closed at commitf972285f…; the exhaustiveconformance_vr_19comparison passed in 1,019.88 seconds; 5,577 source declaration records (5,519 native env declarations + 58 private source-compiler records kept as hashed provenance);examples/uor-atlas/src= 24,796,287 B. After equivalence the exporter was removed andVR-19enforces the permanent condition.What differs for a runtime rather than a proof library. A proof library is one self-contained import-closed project with a module registry and an entrypoint set; the runtimes are not one project:
semantic.rs,core.rs,core_v2.rs) and four independent namespaces (LexLeanRuntime,LexLeanCollections,LexLeanModels,LexLeanReasoning), plus theLexLeanCore.Runtimedecoder, which is a program (JSON decoder +Lean.addDeclsubmission), not merely declarations — it cannot be represented as pure core-module data at all (a decoder that concatenates and submits Lean declarations is compiler behaviour). This is the same wall the [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 recon hit:3266656cd recon(#89 vehicle): certificate text is not core-module data.if runtime,if uses_collections,if model…,if reasoning), so a runtime project has no single "one module per generated module" shape; it must be a small set of always-imported modules with the conditional economy expressed as ordinary imports.@[expose]d (a definition's body is invisible across the module system otherwise —semantic.rs:2021-2026), so the exported 1.2 runtime is not byte-equal to the frozen 1.1 text; the frozen text is a deliberately un-exposed rendering. The exporter must reproduce the exposed form, and a separate compatibility fixture must hold the un-exposed bytes.core_v2.rs:9-13), so the exported runtime's rendering must clear the same audit.3. The embedded project
language/preservation-1.2/library/is compiled into the binary, not read as a path at runtime. The mechanism:crates/lexlean/build.rs(SPEC §12.3, §21.2) walksSEMANTIC_DIRS = [("language","language"),("schemas","schemas"),("tests/golden/axiom-parser",…),("tests/golden/canonical-json",…)], bytewise-sorts, and writesOUT_DIR/embedded.rsasbuild.rs:25-33,:48-63). A missing normative directory is a build failure.crates/lexlean/src/production/preserve.rs:150-157reads it:preserve.rs:28-41):native_source_path(module)="{LIBRARY_DIR}/src/{rel}.lex.tex"(preserve.rs:283-289);module_path(module)="{module with '.'→'/'}.lean"(preserve.rs:162-165).library()(:393) readsLIBRARY_PATHthroughembedded_text, andbuild_native(:295-…) parses each module withCoreModule::from_source(&source, "1.2")and checks each import isInit, aTARGET_MODULEScalculus module, or an earlier library module.language/preservation-1.2/library/lexlean.toml):spec = "lexlean/project/1",name = "preservation-library",language = "1.2",module_prefix = "LexLeanPreservation",source_roots = ["src"],entrypoints = ["src/Compose.lex.tex", "src/Fixed.lex.tex"],lean_workspace = ".",lean_toolchain = "leanprover/lean4:v4.32.1", a glossary sourceglossary/preservation.titles, and[limits] … child_timeout_ms = 10800000. The 13 sources arelibrary/src/*.lex.tex(schemalexlean/core-module/2);library.tomllists onlymodules.support.rs:373(repo_root().join(LIBRARY_DIR)),cases/preservation.rs:1551, and the shipped calculus modules viaMODULES_DIR(calculus.rs:3684-3689).language/preservation-1.2/is inside the RP-10 tree, it is hashed into the language-1.2 compiler-semantics ID:lib.rs:175|| path.starts_with("language/preservation-1.2/").By analogy the runtime's project should be a new embedded 1.2 project, e.g.
language/runtime-1.2/library/(mirroring the preservation layout —lexlean.tomlwithlanguage="1.2", amodule_prefixsuch asLexLeanRuntimeGraph,src/*.lex.texcore-module/2 sources, aglossary/*.titles), plus a registry (language/runtime-1.2/runtime.tomlwithmodules = […],spec,toolchain) and constsRUNTIME_REGISTRY,RUNTIME_DIR,RUNTIME_PREFIXalongsideLIBRARY_*inpreserve.rs(or a sibling module). It enters the RP-10 tree viabuild.rsautomatically (it is underlanguage/), andlib.rs::is_v1_2_filemust gainpath.starts_with("language/runtime-1.2/")so the frozen 1.0/1.1 IDs are untouched.4. The re-point
Where a generated 1.2 module gets the runtime text today:
crates/lexlean/src/backend/semantic.rs::render_leanat 2429-2433 —if module.spec == "lexlean/semantic-module/2" { text.push_str(portable_runtime_1_2()); }. (The 1.1 branch pushesportable_runtime(); the 1.0 path never reaches this function.)What replaces it for 1.2: a
public importof the generated runtime module. The import list is built bycrates/lexlean/src/backend/lean.rs::module_imports(2031-2044):and consumed by the ordinary-module printer
render_module(lean.rs:2083-2140) at2111inside thesink.kw("public")/importloop, which already emits, per the comment there, every import aspublic import. The semantic path (cg.lean.rs:2094) builds its own header:semantic.rs:2404String::from("module\npublic import Init\n")then a loop overdocument.imports(2406-2412).Call sites / functions that must change
backend/semantic.rs:2429-2433— drop theportable_runtime_1_2()splice forsemantic-module/2and instead prepend the runtime module to the module's imports; the runtime must appear indocument.imports(or be added byrender_lean) so it lands in thepublic importloop.backend/semantic.rs:2427— theportable_runtime()branch stays (1.1) but reads its bytes from the new frozen-bytes data file (§5), not the literal.backend/semantic.rs:2436-2452—COLLECTIONS_RUNTIME/MODELS_RUNTIME/REASONING_RUNTIMEsplices become runtime-module imports for 1.2 (they are the 1.2-only texts).backend/lean.rs:2031-2044(module_imports) and:2091-2097(render_moduledispatch) — the runtime module must joinInit+ the generated graph in every 1.2 module's public import set; the missingpublic import Initin the semantic path is generated atsemantic.rs:2404.backend/semantic.rs:2404-2412— the header text ("module\npublic import Init\n"+document.imports) must admit the generated runtime module.verify/mod.rs:948-989—all_names(module-name-conflict rejection) and the externals closure must include the runtime project's modules, andprobe_module/audit_modulesmust see them.config.rs:275-290SHIPPED_ROOTS— add the runtime project's module root(s) (the same wayLexLeanCollections/LexLeanRuntime/… are already listed).lib.rs:135-185is_v1_2_file— add the runtime project path prefix;production/preserve.rs:28-41— add the runtime registry/dir consts.backend/core.rs:1384-1388— unchanged forLEAN_CORE_DECODER(1.0/1.1 frozen) but the 1.2 decoder (core_v2.rs) is a program and stays a text constant unless routed through [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's typed-data representation.The three probe/audit module texts. In
backend/lean.rsthere are exactly two text-building auxiliary-module functions:probe_module(lean.rs:2447,String::from("module\n")at 2484) andaudit_modules(lean.rs:2531,format!("module\nimport {generated_module}\n")at 2543). I could not find a third inlean.rs; the only other probe text in the backend is the test-onlyLexLeanTokenProbebuilder atbackend/lean_tokens.rs:393-400(String::from("namespace LexLeanTokenProbe\n")…end LexLeanTokenProbe\n), which is not a shipped generated module. The brief's "three probe/audit module texts in backend/lean.rs" is UNVERIFIED — the verified statement is: two (probe, audit) inlean.rs, one test-only inlean_tokens.rs. None of the three is inherently 1.2-only —verify/mod.rsgenerates the probe and audit for every language's verification. What is 1.2-only is the typed-data representation: only asemantic-module/2/core-module/2project can carry the probe/audit as native data (the #89 recon3266656cdshows the semantic-module vehicle rejects certificate text at linking). So the #89 coupling affects the probe/audit texts only when compiling language 1.2; for 1.0/1.1 they remain Rust text, like the frozen runtimes.5. 1.0/1.1 byte-identity
Where RP-10 is implemented:
crates/conformance/src/cases/repository.rs:530-660, case id"RP-10"(registered asRP-10 … §21.2). It:language,schemas,tests/golden/axiom-parser,tests/golden/canonical-jsonfrom disk, sorts by bytewise path, computeslexlean::artifact::content_id::tree_digest, and asserts it equalslexlean::compiler_semantics_id_for(lexlean::LATEST_LANGUAGE_VERSION)(repository.rs:565, message "RP-10: the embedded semantics ID differs from the disk recomputation");repository.rs:633-638, "RP-10: the 1.1 ID" / "RP-10: the 1.0 ID");examples/nat-add-zero/lexlean.lock(1.0) andexamples/semantic-1.1/lexlean.lock(1.1) carry the recomputed ids.What it compares: three SHA-256 tree digests of the embedded normative files, against
compiler_semantics_id_for(language)(lexlean/src/lib.rs:191). It does not read Rust source and does not compare generated Lean. So it is the identity pin for the language data, and it holds the 1.0/1.1 IDs stable by construction (lib.rs:135-160is_v1_1_filenames onlylanguage/core-1.1/…,language/std/*-1.1/,language/bootstrap-1.1.toml,language/semantics-1.1.toml,schemas/semantic-module.schema.json,schemas/semantic-snapshot.schema.json). Generated-output byte-identity is pinned separately by the committed goldens (just golden→cargo xtask check-golden;Justfile:71-90) andcheck-reproducibility(Justfile:78). The 1.1 golden carrying the frozen runtime isexamples/semantic-1.1/expected/build/modules/SemanticFixture/Portable.leanand…/KernelReductionDefinitions.lean(both opennamespace LexLeanRuntime); the 1.0 goldens (examples/nat-add-zero,peano-arithmetic,list-induction,propositional-logic,uor-atlas) carry no runtime namespace (measured: only 2 golden files under 1.1 and 27 under 1.2 containnamespace LexLeanRuntime).Where the frozen-bytes data file and its test would live. Put the frozen bytes at a named
language/data file — e.g.language/runtime-1.0-1.1/portable.lex.lean+language/runtime-1.0-1.1/core-decoder.lex.lean(or a singlelanguage/frozen-runtime-1.1/<name>.txt) — read by the 1.0/1.1 backends viaembedded_text(thebuild.rswalker embeds any file underlanguage/). The permanent test that it "equals the released 1.1 output" belongs next to the existing frozen-runtime unit tests inbackend/semantic.rs(language_1_2_runtime_exposes_every_definition_and_keeps_1_1_frozen,semantic.rs:3647-3688, andimported_list_construction_remains_kernel_reducible,:3682) or as a conformance case: load the data file, and assert it equals thenamespace LexLeanRuntime …block of the committed goldenexamples/semantic-1.1/expected/build/modules/SemanticFixture/Portable.lean(and the core decoder against whatever golden carriesLexLeanCore.Runtime). Because the data file would live underlanguage/, adding it changes the RP-10 tree for all three languages unless it is excluded for 1.0/1.1 in SPEC §21.2 — a decision to record: either the file is 1.2-partitioned (language/…-1.2/…) so 1.0/1.1 IDs are untouched, or §21.2's exclusion lists are extended to exclude it from the 1.0/1.1 digests. This is the one place the plan risks moving a frozen 1.0/1.1 ID.Gate wordings to extend (row + case). The current rows name the libraries, not the runtimes; the exception ("released bytes of frozen versions, the only declared exception") must be added to each. Sites:
VR-19SPEC.md:8573CONFORMANCE.md:519crates/conformance/src/cases/verification.rs:1139(verification)SP-12SPEC.md:8678CONFORMANCE.md:322crates/conformance/src/cases/preservation.rs:1537(preservation)GC-11SPEC.md:8666CONFORMANCE.md:178crates/conformance/src/cases/certification.rs:205→fn gc_11()at:2633(certification)support::assert_library_has_no_handwritten_lean(prefix, oracle)(support.rs:37-68)VR-19's row currently reads "…every generated Atlas module publicly depends only on Init and the generated graph, its only backend-support import is Lean, and no independently authored Atlas implementation exists." Extending it to the runtime: every generated 1.2 module publicly depends only onInit, the generated runtime graph, and its own generated graph.assert_library_has_no_handwritten_leanis the shared "no Lean file declares the namespace / lies under the module root" scanner; the extension idiom is to keep that scan but add a narrowly-scoped stated exception for the two frozen data files (they arelanguage/data objects, not.leanunder a module root, so the existing scan would not even flag them — the wording must say so explicitly).6. The plan
Ordered steps. "Regenerations" means running the write recipes (
just golden-write,cargo xtask verify-examples --write,just calculus-write) — the task forbids me from running them now; the implementing PR runs them.portable_runtime()andLEAN_CORE_DECODERintolanguage/runtime-1.0-1.1/<name>data files; change the 1.0/1.1 backends to read them viaembedded_text; add the permanent "equals the released 1.1 output" test against the committed 1.1 goldens. Decide the RP-10 partition question (exclude from 1.0/1.1 digests, or place under a 1.2 path). Register the new capability row.tests/runtime-library/oracle/, deleted by the following commit):build.shcompiling a small Lean project stating the three 1.2 runtimes (portable_runtime_1_2, collections, models, reasoning) as one import-closed project;export.lean(reuse [v0.4] #27 stage 2: preservation and certification proof libraries as native core modules #79's, adaptinglibMods);flags.lean;canon.py. Commit the resulting.lex.texunderlanguage/runtime-1.2/library/src/plusruntime.toml.equivalence.shon the compiler-generated Lean of the runtime project; require declaration-by-declaration equality (types, values, reducibility, attributes, sparse-casesOn,grind, compiled flags, exact axioms) and byte-equal attribute-probe output. Record it intests/runtime-library/MIGRATION.md. Confirm the exported projection is@[expose]-decorated so cross-module reduction works (§17.12).public importthe runtime module and drop the four splices. The 1.1 branch keeps reading the frozen data file; there is no 1.0 splice.expected/verifyrecord (just golden-write,cargo xtask verify-examples --write). The 1.0/1.1 goldens and locks must not change — diff them explicitly.SPEC.md:8363"Complete conformance-ID registry"), the matchingCONFORMANCE.mdrow, one scenario, one failing named testconformance_<id>, the implementation, and aVERIFICATION.mdplant. Which rows/suite: a runtime-library condition in thepreservationsuite (sibling ofSP-12) for "the runtime project is native core modules, no handwritten Lean, registry lists modules only", and the frozen-bytes condition in therepositorysuite next toRP-10; the exported-runtime equivalence is recorded undertests/(atests/runtime-library/MIGRATION.md), andVR-19/SP-12/GC-11are edited in place (wording extended) rather than duplicated.just vv=fmt-check model spec-links lint test features bdd inventory examples golden repro deny(Justfile:7). Specifics:RP-10(three IDs),SP-12,GC-11,VR-19, the new runtime row,cargo xtask check-golden,cargo xtask check-reproducibility,cargo xtask verify-examples, andcargo xtask check-calculus(not--write).VERIFICATION.md) — each must turn a named gate red: (a) a changed runtime declaration inlanguage/runtime-1.2/library/src/…→ equivalence/conformance_<runtime-id>red; (b) an added axiom to a runtime row → the exact-axiom audit red; (c) a stale generated runtime (commit the sources, revert the generated Lean) → the "generated Lean is byte-for-byte what a verification stages" check red (the idiom ofSP-12/GC-11); (d) a handwritten runtime text reintroduced (put backportable_runtime_1_2/COLLECTIONS_RUNTIMEintosemantic.rs) → the new no-handwritten-Lean runtime gate red; (e) mutate the frozen-bytes file → the "equals the released 1.1 output" test red.Riskiest step: step 2–3, the lossless export of the 1.2 runtime environment and its declaration-by-declaration equivalence — because the runtime is not one import-closed library (three files, four namespaces, a conditional splice economy, a program-shaped decoder in
core_v2.rs, and the exposed/un-exposed rendering difference), and because #79 already burned 51 wrong rows on the first pass deriving attributes/axioms instead of reading them. How to falsify: re-runequivalence.sh— export the original environment and the regenerated one with the sameexport.lean/flags.lean/canon.py, and require byte-equality of the converted output and equal attribute-probe output; any row that differs must be shown to be a bound-variable-name or private-import difference only. If the decoder (LEAN_CORE_DECODER_V2) proves not representable as core-module data, that is a genuine scope finding (the #89 wall) and must be surfaced, not papered over.UNVERIFIED list
probe_module:2447,audit_modules:2531); a third probe text is the test-onlylean_tokens.rs:393.conformance_vr_19= 1,019.88 s; per-module seconds lived in an uncommittedout/summary.txt).COLLECTIONS_RUNTIME/MODELS_RUNTIME/REASONING_RUNTIMEever reach a non-1.2 module (reasoned fromlanguage_1_2construct gates, not exercised).language/path partition keeps the frozen data out of the 1.0/1.1 RP-10 digests — a design decision, not yet made.humuhumu33 commentedon Oct 14, 2026
Two findings from starting the implementation (branch
claude/issue-90-native-runtimes), both of which refine the plan above.1. Every runtime text is a namespace-scoped fragment, not a module. Measured from the literals:
COLLECTIONS_RUNTIME(backend/semantic.rs:1340)namespace LexLeanCollectionssemantic.rs:2441MODELS_RUNTIME(:1492)namespace LexLeanModels:2448REASONING_RUNTIME(:1570)namespace LexLeanReasoning:2451portable_runtime()— a function, not a constantnamespace LexLeanCore.Runtime-style body:2432(1.1 frozen),:2430(portable_runtime_1_2, derived)LEAN_CORE_DECODER_V2(backend/core_v2.rs:34)namespace LexLeanCore.RuntimeEach is
text.push_str(...)inside the module the backend is already writing, so they share the generated module's imports and options. Consequence for this stage: before the language-1.2 backend canimportthe runtime instead of splicing it, each fragment has to become a proper module —module+public import+ the same namespace content under a stable module name. The generated 1.2 text changes shape (already declared in this issue's proposal), while every name a generated module uses (LexLeanCollections.Key,LexLeanReasoning.Star, …) still resolves, so what a module means does not change. That is also the thing the equivalence check has to pin.2. The #79 exporter is recovered and its procedure is reproducible, so the export is not blocked on re-authoring anything. From checkpoint
d54dbf8551190e4ae77fbf69267fd4328e19db3f(tests/preservation-library/oracle/, removed by the commit that follows it; 9 files, ~37 KB):build.sh— compile the calculus modules and the library modules intoout/(lean -R src -o out/<M>.olean), pinned Lean 4.32.1, a per-module log and asummary.txt.flags.lean— run withLEAN_PATH=outin a scratch directory; writessparse.jsonandcompiled.txt(the constants the code generator compiled, and the sparsecasesOnrecords).export.lean— run withLEAN_PATH=outandEXPORT_DIR=<dir>; one JSON per module, each declaration with its kernel data, extension state, and the axioms#print axiomsreports.canon.py <dir>withEXPORT=/FLAGS=— the canonicallexlean/core-module/2sources, which is whatlanguage/preservation-1.2/library/src/holds.equivalence.sh <generated> <work>— compile the Lean the compiler generates from those sources, export it again with the same programs, convert with the same script, and require byte-equality;probe.leanadditionally compares the attribute state of every constant read by the pinned Lean's own queries from both environments.So the stage's first implementation step is an extractor: take each runtime fragment out of its Rust literal, wrap it as a module, and drive steps 1-4 over it. Nothing about this stage is blocked; it is a matter of the export, the re-point and the regeneration.
humuhumu33 commentedon Oct 14, 2026
One runtime cannot be a native module of itself: the core-module 2 decoder. A decision is wanted before this issue can close as written.
LEAN_CORE_DECODER_V2(backend/core_v2.rs:34, 27,636 bytes) is how a native core module reaches Lean at all.core::lean_textwrites the module header, splices the decoder, and then onerun_cmd … LexLeanCore.Runtime.decodeAndAdd #[…] (…)per chunk of JSON (core.rs:1376-1397). If the decoder were exported as a native core module like the others, the Lean of that module would need the decoder spliced into it to load. The circle is closed only by printing the decoder's kernel terms back to Lean declarations: the lossless kernel-term printer of #93 stage 3c. For this module it would also have to print matcher registrations, thepartialgroups and the code the generator compiled, because the decoder runs at elaboration time.What this PR will do, per the decision above:
portable_runtime_1_2,COLLECTIONS_RUNTIME,MODELS_RUNTIMEandREASONING_RUNTIMEbecome nativelexlean/core-module/2modules of an embedded project underlanguage/runtime-1.2/. They are made by the one-time lossless export of [v0.4] #27 stage 2: preservation and certification proof libraries as native core modules #79, with the exporter outside the release tree and a migration record undertests/. Generated 1.2 modulespublic importthem instead of splicing the text.portable_runtime()andLEAN_CORE_DECODER(core-module 1) become committed bytes that the 1.0/1.1 backends read. A permanent test checks them against the released 1.1 output, andVR-19,SP-12andGC-11name that exception.backend/lean.rsbecome typed data rendered by the backend.Proposal for the decoder: it stays as the one bootstrap text of language 1.2, named by that gate as the loader of native data. That makes two declared exceptions in all: the frozen 1.0/1.1 bytes and this loader. It is printed from a native module once #93's stage 3c printer exists, tracked as a follow-up issue.
Question: do you accept the decoder as a declared bootstrap exception, so this PR closes #90 and a follow-up carries the decoder? Or should #90 stay open until 3c, with this PR as part of it? Work proceeds on the first reading unless you object; the PR says "Part of #90" until you answer.
humuhumu33 commentedon Oct 14, 2026
A second consequence of "import instead of splice": the native proof libraries name the per-module runtime copies.
Today each generated 1.2 module splices the runtime inside its own namespace (
semantic.rs:2425-2452), so every module that uses it has a private copy:LexLeanTarget.TargetSemantics.LexLeanRuntime.index,LexLeanTarget.RustSemantics.LexLeanRuntime.…, and so on. Once the runtime is one imported module, those constants becomeLexLeanRuntime.…,LexLeanCollections.…. The native libraries are kernel data, and they name the old constants. Measured onmaind284b165(constant nodes in*.lex.tex, excludingexpected/):LexLeanTarget.TargetSemantics.LexLeanRuntime.*LexLeanTarget.TargetSemantics.LexLeanCollections.*LexLeanTarget.RustSemantics.LexLeanRuntime.*LexLeanTarget.Certification.LexLeanRuntime.*LexLeanTarget.Gnaf.LexLeanRuntime.*The references are in 12 modules of
language/preservation-1.2/library/srcand 11 ofcompiler/proofs/src(the certification library). Certificate A also writes one by name (production/certificate.rs:3723,{SEMANTICS}.LexLeanRuntime.append).Plan for it, unless you object: a renaming of those constants to the shared runtime's names, carried out as data and proved the way #79 proved the migration. I rewrite the constant names and merge the nodes that become equal. Then I compile the generated Lean of both libraries against the regenerated calculus modules, so the kernel re-checks every proof. Then I re-export that environment with the #79 exporter and require it to equal the rewritten data byte for byte. The record goes in
tests/runtime-library/MIGRATION.md, beside the export of the runtimes. The copies are identical definitions, so identifying them preserves every typing judgement; the kernel replay confirms that rather than taking it on trust. No library statement changes except for the names of the runtime constants it mentions.If you would rather keep a per-module copy for the calculus modules, say so: that would leave a runtime text in the 1.2 compiler, against this issue's DoD.
afflom commentedon Oct 14, 2026
Heads-up for @humuhumu33 (and for #84 / #89, which touch the same files): PR #103 (stacked on #96/#98, part of #93) makes the calculus and the Rust machine sources.
crates/conformance/src/{calculus_source,rust_source,gnaf_model,certification_model}.rsare deleted.examples/…/compiler/{TargetSyntax,TargetSemantics,RustSyntax,RustSemantics,Gnaf,Certification}.lex.texare now edited as.lex.tex(authoring notation) like any other source;cargo xtask check-calculusno longer rewrites them (it still regenerates the five fixture/oracle records AU-07 names).lx.rs,tl.rs) now delegate tolexlean::sdk(SPEC §24.8), the public authoring API; there is no second authoring path..lex.texinstead. Identities do not change (§21.2: notation is not part of any ID), so no re-verification is triggered.If your branch for this issue edits one of those builders, tell me and I will do the port.
Generated by Claude Code
humuhumu33 commentedon Oct 15, 2026
Thanks. This branch does not edit
calculus_source,rust_source,gnaf_model,certification_model,lx.rsortl.rs, and no committed.lex.texof the compiler project. What it changes in that area is generated output: the calculus modules nowpublic importthe runtime modules instead of splicing a copy, so the shippedlanguage/preservation-1.2/modules/and the regenerated records move. I'll rebase after #103 and regenerate, and no port is needed.humuhumu33 commentedon Oct 15, 2026
The decoder of core module 2 is the loader's seed; #144 closes this issue on that basis.
The open question was whether
LEAN_CORE_DECODER_V2may stay the one Lean text of the language-1.2 compiler. #144 keeps it, declared and gated, for these reasons:addDecl, so Lean's kernel checks it. A defect in the decoder can fail a build; it cannot admit a statement.RT-04holds the decoder to be the only string literal of the compiler that holds Lean declarations under a namespace, and finds a planted second runtime text, raw or escaped.If you would rather track the decoder separately, say so in review and I'll switch #144 back to "Part of #90".
chore(#90): regenerate the models and reasoning records and the certi…
Merge pull request #144 from UOR-Foundation/claude/issue-90-native-ru…
afflom commentedon Oct 16, 2026
#90 closed by #144 (merged 08:01Z, merge commit
32afe5da), verified against its DoD. CI of the merged head6a448857(mainc183f250merged into the PR; counts 386; themodels,reasoningandcertificate-resource-exhaustedrecords regenerated on the combined tree):just vvrest and examples, all four host legs, repro and honesty green.RT-01(the runtime is four native core modules, no per-module copy, only released 1.0/1.1 output declares a runtime namespace),RT-04(no Lean runtime text in the compiler but the core-module-2 decoder; a planted constant, raw or escaped, is found)RT-02:language/runtime-1.2/frozen/*equal the released output byte for byte, and a one-byte change is caught; 1.0 and 1.1 are byte-identicalInitand the generated graphRT-01(every row pins exact axioms),RT-03(verification audits the runtime; a wrongly pinned axiom isLLV7005)leancheckerreplay and exact axiom audit pass for every example and corpus on the new runtimeexamplesleg andbdd, every 1.2 record regenerated over the renamed librariestests/runtime-library/MIGRATION.md(371 declarations: 368 byte-exact, 3 sparsecasesOnrows equal up to binder names), five plants inVERIFICATION.mdOne thing the DoD did not say, now settled: the DoD names the frozen bytes as "the only declared exception". There are two: the core-module-2 decoder (
LEAN_CORE_DECODER_V2) is Lean text too. It is the loader of native modules, so by construction it cannot be one of them (a loader cannot load itself); it is not a runtime of any generated program, every declaration it submits is checked by Lean's kernel (a defect in it can fail a build, never admit a statement), andRT-04bounds it to exactly that one text. The seed is the second declared exception, and #144's description records the argument.The decision wanted in the issue (how 1.0/1.1 are treated) was settled as proposed: a declared frozen-bytes exception, not closing the versions.
Generated by Claude Code
afflom commentedon Oct 16, 2026
Correction to my DoD comment above. It mapped every line of the definition of done and acceptance to a gate and called them met. The post-merge review of #144 (#154) found that three lines are only partly met:
RT-04fails on a literal that holds anamespaceline and a declaration; a runtime definition without anamespaceline, or one placed after a file's first#[cfg(test)](for exampleverify/mod.rslines 2700 to 2783), is not seen. Both reproduced.Initand the generated graph" holds for the runtime project (RT-01) and is exercised for generated modules by other cases, butRT-04's statement says it and its case does not check it.The merged behavior itself is sound (main's push CI for
32afe5dais green); the gaps are in the gates' strength. They are tracked in #154, and #90 stays closed with that issue as its follow-up.Generated by Claude Code