Skip to content

feat(#90): the runtime of generated Lean is native modules (RT-01..RT-04) - #144

Merged
humuhumu33 merged 4 commits into
mainfrom
claude/issue-90-native-runtimes
Oct 16, 2026
Merged

humuhumu33 merged 4 commits into
mainfrom
claude/issue-90-native-runtimes

Conversation

@humuhumu33

@humuhumu33 humuhumu33 commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Closes #90 (stage 4 of #27). The one Lean text the language-1.2 compiler keeps, the core-module-2 decoder, is declared as the loader's seed; the reasons are below and on #90.

What this does

The language-1.2 runtimes are LexLean source. The portable primitives, ordered collections, model predicates, and reasoning lemmas were Rust string constants that backend/semantic.rs spliced into every generated module that used them, one copy per module. They are now four native lexlean/core-module/2 modules of a new embedded project, language/runtime-1.2/library/, written in the core notation of #104:

  • LexLeanRuntime.Portable
  • LexLeanRuntime.Collections
  • LexLeanRuntime.Models
  • LexLeanRuntime.Reasoning

They were exported once, losslessly, by the #79 route. The record is tests/runtime-library/MIGRATION.md. The exporter is in the checkpoint commit, the first of the two, and the second commit removes it. A generated 1.2 module public imports the runtime modules it uses and carries no runtime text (SPEC §17.12, Runtime modules).

verify stages them. A verification compiles the runtime modules a project imports before the project's own modules. That includes the runtime modules imported by the calculus modules its certificates are checked against. It replays them with leanchecker and audits every runtime declaration against its exact axioms (§22.3, §18.9). The certificate workspace, the extraction stage, the oracle differential, and TC-04's evaluator include them too.

Languages 1.0 and 1.1 are frozen. portable_runtime() and the core-module-1 decoder are now committed data: language/runtime-1.2/frozen/portable-runtime-1.1.txt and .../core-decoder-1.txt. Only the 1.0/1.1 backends read them. They lie in the 1.2 partition of §21.2, so the 1.0 and 1.1 IDs do not move, and no 1.0 or 1.1 output changed. conformance_rt_02 checks that the bytes equal the released output.

The probe and audit modules are typed data. backend::lean::AuxiliaryModule holds imports plus a closed command set (set_option autoImplicit false, universe, example, #print axioms), rendered once by the backend. The rendered bytes are unchanged.

New runtime suite, RT-01..RT-04. Each has its SPEC §31 row, a scenario, and a conformance_rt_0N test. The register goes from 380 to 384.

ID What it checks
RT-01 The runtime is native source. The registry lists only modules, every row pins exact axioms, and a missing, weakened, or escaping source is refused. No native source, in either form, names a per-module copy, and only released 1.0/1.1 output declares a runtime namespace.
RT-02 The frozen bytes equal the released output, and a one-byte change is caught.
RT-03 Verification audits the runtime. The runtime project verifies, and a wrongly pinned axiom fails with LLV7005.
RT-04 No Lean runtime text remains in the compiler but the core-module-2 decoder, and a planted runtime text constant, raw or escaped, is found.

VR-19, SP-12, and GC-11 now state the one Lean text their generated modules carry beside data, and their cases check it.

Consequences the issue did not spell out

The libraries' references were renamed. A runtime name is now LexLeanRuntime.x, not <module>.LexLeanRuntime.x. The native preservation and certification libraries are kernel data that named the per-module copies. The renaming was derived, not assumed:

  • Matchers moved. Lean shares a matcher within a module but never with an imported one (tested). TargetSemantics.primitive and RustSemantics.literal had reused their module copy's checkedMultiplyInt64.match_1, so their later matchers renumbered by two.
  • The walk. walk.py compiled the calculus modules as main generated them and as they are generated now, then exported both. It walked every declaration's kernel type and value in parallel. It found 3,914 pairs, each the same tree up to constant and binder names, pairing 480 names.
  • The rename. rename.py applied that pairing to the data form of 23 library sources, converted from the notation and back with lexlean::sdk. The round trip alone changes no byte. Only names changed: 551 occurrences in the preservation library and 280 in the certification library. The kernel re-checks every proof when the libraries compile against the regenerated calculus modules.
  • One duplicate name. LexLeanRuntime.equal.eq_1 is now declared by two preservation modules that don't import each other, with identical rows up to binder names. The pinned Lean imports such equation lemmas from both modules (tested by elaboration and leanchecker). preserve.rs now accepts a name declared twice only when it is an equation lemma and both rows are the same declaration.

These findings were posted on #90 beforehand (decoder, rename).

The oracle answers differently. The pinned oracle (lean4-prod, §17.19) exports one module's closure and does not follow a call into another module. A collection template or a reasoner's iteration that Lean's compiler keeps as a call is now an unresolved-external-call refusal. Two list roots the oracle refused before are now generated, and they return their list into a caller's buffer. The harness passes such a buffer and compares the elements written. The tables are re-versioned with digests in oracle/table-versions.toml:

  • known-differences v3: rows move between KD-01/02 and KD-03, and the new KD-13 output-buffer is added.
  • known-non-overlap v2: 20 constructs change refusal, and map_entries and set_elements leave the table.

The corpus counts in §17.19 changed from 30 to 32 generated, 72 to 70 refused, 23 to 25 compiled, and 1411 to 1446 inputs.

The core-module-2 decoder is the seed

LEAN_CORE_DECODER_V2 is the one Lean text of the 1.2 compiler not generated from a native module. It is what loads native modules into Lean, so it cannot be one of them: a loader cannot load itself. The printer of #93's stage 3c prints kernel terms into the core notation, not into Lean, so it does not close that circle. Printing the decoder back as elaboratable Lean would be a large new path with no gain in assurance:

Evidence

  • Runtime equivalence. 371 declarations: 368 byte-exact and 3 sparse casesOn rows equal up to binder names. The attribute probe matches (47 lines).
  • Regenerated. Language 1.2 moves from 974c98b5… to a4111005…; 1.0 and 1.1 are unchanged. Everything that depends on it was regenerated:
    • the 229 language-1.2 locks (by substituting the ID only)
    • the goldens and the shipped calculus copies
    • the fixture expectations and production-export packages
    • every 1.2 example's and the compiler project's expected/verify/ records, which cover certificates A, B, and E of every production root over the renamed libraries
  • Full suite on c257a657, one run, --test-threads 2. 382 of 383 passed. The one failure, RT-01, failed because its plants and its name scan read only the data form. It now reads the notation too, and RT-01..RT-04 pass. The rebase onto feat(rust): close the runtime's library boundary (RB-09, stage 1 of #91) #102 (70de63d3) changed only the register count. After it, RP-07, RB-09, RT-01..RT-04, model, spec-links, and inventory pass.
  • CI rest-leg recipes, run locally and passing: fmt-check, lint (clippy -D warnings), model, spec-links, test, bdd, inventory, golden, and deny. repro is left to CI.
  • Planted defects. Five runtime plants, each caught, are recorded in VERIFICATION.md (runtime modules can fail).
  • Not run locally. examples/uor-atlas (language 1.0, whose output and records this change does not touch) needs more memory than the host has. It runs in CI's examples leg.

🤖 Generated with Claude Code

…-04)

The portable primitives, ordered collections, model predicates, and reasoning
lemmas of language 1.2 were Rust string constants the backend spliced into
every module that used them. They are now four native core modules of the
embedded project language/runtime-1.2/library (LexLeanRuntime.Portable,
.Collections, .Models, .Reasoning), migrated once and losslessly, which a
generated module imports. Verification compiles, replays, and audits the
runtime modules a project uses. Languages 1.0 and 1.1 are frozen: their
released runtime text is data under language/runtime-1.2/frozen/, and their
output is byte-identical. The decoder of core module 2 stays the one Lean
text of the language-1.2 compiler, since it is what loads native modules.

The preservation and certification libraries' references to the per-module
runtime copies are renamed by a pairing walked through the kernel terms. The
differential oracle's tables are re-versioned for what it answers once the
runtime is imported. The language-1.2 compiler-semantics ID changes, and every
language-1.2 record is regenerated.

This commit is the migration checkpoint: it carries the one-time oracle,
tests/runtime-library/oracle/, which the next commit removes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The record of what it established is tests/runtime-library/MIGRATION.md,
which now names the checkpoint commit 34d940f that carries it
(RT-01: the completed migration input is not in the release tree).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

afflom commented Oct 15, 2026

Copy link
Copy Markdown
Member

Heads-up from the maintenance of #99/#101/#102, not a review (I have not read this diff and have not touched the branch). Main moved under this PR after its CI started: #101 merged (26e4dea7), so main is 26e4dea7, the register is 381, and this PR (384 on 70de63d3) reads as 385 once its four rows are added; GitHub reports it conflicting.

What overlaps, so the merge is not a surprise. #101 (SP-13) changed the text of certificates that hold a list over 32 elements, and this PR regenerated every language-1.2 record without that change, so these conflict or silently keep the older text:

  • examples/models/expected/verify/preserve/…/R0/RustStd.lean, …/R1/RustStd.lean, …/preservation.json
  • examples/reasoning/expected/verify/preserve/…/{R0,R1}/RustCore.lean, …/{R2,R3}/RustStd.lean, …/R5/RustCore.lean, …/R6/RustStd.lean, …/preservation.json
  • tests/negative/certificate-resource-exhausted/expected/{command,diagnostics}.json and tests/negative/certificate-generation-limit/expected/{command,diagnostics}.json (the second one's quoted derivation bound moved, 5,285,358 to 4,259,854, and was stale on fix(preservation): certificates write a list of any length without binders (#97) #101's branch until I regenerated it)

Procedure that worked for #101, and that you reproduced independently there: merge main with a merge commit, take main's side of those generated files, regenerate them (verify-examples --write restricted to models and reasoning took 607 s and 510 s under the pinned Lean; check-fixtures --write for the fixtures), and set the four register counts by hand: the merge of identical edits to README.md, SPEC §31, repository.rs and spec_links.rs reads as clean when it is wrong by one. Your renamed runtime names change the certificate text again, so the regeneration has to be on the combined tree, not copied from #101's.

If you want me to do that merge for this branch, say so here and I will, with the same guard (nothing pushed until you say, since a push cancels the run in progress). If you would rather keep your CI run going to its end first and merge main afterwards, that is also fine; I will not touch it either way.


Generated by Claude Code

afflom commented Oct 15, 2026

Copy link
Copy Markdown
Member

Status and one question, @humuhumu33.

CI. All eight checks are green on d01688ed (examples leg finished 23:16Z; the runs were on base 70de63d3). Main is now 96b382df (after #101, #140 and #139), so the branch needs main merged and the language-1.2 records regenerated on the combined tree before it can merge.

Order against #146. Both PRs change the certificate text of examples/models and examples/reasoning (this one by the runtime rename, #146 by certificate E as data), and both add register rows (this one four, #146 one: 381 on main, so 385 for this PR alone and 386 for both). Whichever lands second regenerates those records again and recounts. This PR is the one that is complete and green, and #146 has three host legs red on its latest head, so I would land this one first.

Question. Who does this PR's main merge? (a) I do it, on this branch, with the procedure from my earlier comment: a merge commit, main's side of the generated files, verify-examples --write restricted to models and reasoning, check-fixtures --write, the four register counts by hand, the gates, and one push (a push restarts CI, so nothing is pushed until the gates pass locally). (b) You do it. If I have not heard by 00:30Z, I will take silence as "not now" and leave the branch alone: I will not push to it unasked.


Generated by Claude Code

afflom commented Oct 16, 2026

Copy link
Copy Markdown
Member

Correction to the counts above: #130 (PX-13) merged, so main is d58c4ef9 and the register is 382. This PR's four rows make it 386, and with #146's SP-14 it would be 387 (#146 alone: 383). Same rule as before: the four places read as a clean merge and are wrong by one, so set them by hand and check validate-model, validate-spec-links, conformance_rp_07 and conformance_rp_11. The question above (who does this PR's main merge) stands.


Generated by Claude Code

# Conflicts:
#	CONFORMANCE.md
#	README.md
#	SPEC.md
#	VERIFICATION.md
#	crates/conformance/src/cases/repository.rs
#	crates/lexlean/src/production/certificate.rs
#	examples/models/expected/verify/preserve/preservation.json
#	examples/reasoning/expected/verify/preserve/preservation.json
#	tests/negative/certificate-resource-exhausted/expected/command.json
#	tests/negative/certificate-resource-exhausted/expected/diagnostics.json
#	xtask/src/spec_links.rs

afflom commented Oct 16, 2026

Copy link
Copy Markdown
Member

No answer to the question above in over six hours, and no push from you on this branch or on #146 since 22:43Z, so I am doing the main merge, and I am reversing what I wrote ("I will not push to it unasked"): this PR is the critical path of #90 → #89 → #91 → #92, it is complete and green on its own base, and what blocks it is mechanical.

What I am doing, so nothing is a surprise. In a scratch tree, on top of your tip d01688ed: one ordinary merge commit of main c183f250 (no history rewritten), then one push, after the gates pass locally. If you push first, my push is refused as non-fast-forward and I stand down and re-merge on top of yours.

  • Counts: 382 on main + this PR's four rows = 386 (README, SPEC §31, repository.rs, spec_links.rs by hand; SP-12's row keeps your wording and main's SP-13 row is added). CONFORMANCE.md regenerated.
  • One code conflict, production/certificate.rs: fix(preservation): certificates write a list of any length without binders (#97) #101's lean_list argument form ({} over list(encoded)) with your runtime rename (LexLeanRuntime.append, no per-module prefix). No other .LexLeanRuntime spelling is left in production/.
  • Records regenerated on the combined tree, since fix(preservation): certificates write a list of any length without binders (#97) #101's list change moves the text of certificates that your rename also moves: examples/models, examples/reasoning, and the certificate-resource-exhausted fixture (check-fixtures --write). check-calculus, check-golden and check-production-export already pass on the merged tree.

Then full CI on the new head (it restarts on the push).


Generated by Claude Code

…ficate-resource-exhausted fixture on the combined tree

The merge of main (#101's list helper, #130's PX-13, #139, #140, #135, #137, #143, #147) into this branch: counts set to 386 by hand, certificate.rs keeps #101's lean_list argument form with this PR's LexLeanRuntime names, CONFORMANCE.md regenerated. verify-examples --write (models, reasoning) and check-fixtures --write change only these four files.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013QppGKetUURseXowZun9KY
@humuhumu33

Copy link
Copy Markdown
Contributor Author

Thanks for taking the merge. I had the same merge in progress locally (on 26e4dea7) and stood down when your push landed, so nothing from me will race it. I checked 6a448857 against my own resolution and they agree:

  • the register reads 386 in all four places
  • SP-12 keeps the decoder sentence, and main's SP-13 row is there
  • certificate.rs has (LexLeanRuntime.append {} {}) with no per-module runtime spelling left
  • d01688ed is an ancestor, so nothing was rewritten

Agreed on the order: this one first, then #146 regenerates models/reasoning and recounts (387). Nothing further from me on this PR unless CI says otherwise.

🤖 Addressed by Claude Code

@humuhumu33
humuhumu33 merged commit 32afe5d into main Oct 16, 2026
8 checks passed
humuhumu33 added a commit that referenced this pull request Oct 16, 2026
…age-model; the register carries only the wired text rows LM-01..LM-03 and LM-13 (390 IDs)
humuhumu33 added a commit that referenced this pull request Oct 16, 2026
The runtime modules of #144 are shipped data: every part elaborates
them before its own modules, the part that publishes replays and
audits them, and its records lead the attestation as on main. The
register has 387 IDs.
humuhumu33 pushed a commit that referenced this pull request Oct 16, 2026
…sue-92-stdlib-bytes-text

The register has 387 rows; SL-01 stays the last row.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

afflom commented Oct 16, 2026

Copy link
Copy Markdown
Member

Post-merge review of this PR, merged at 08:01:59Z, 32 s after its last check finished and before the maintainer review of the two commits the afflom session had pushed to it (ab4e9eba, 6a448857). The merged behavior is sound and main's push CI on 32afe5da is green, but four gates and a docs join are weaker than #90 asked: #154 lists them with evidence, a definition of done, and a note on protecting main so a merge cannot precede its review.


Generated by Claude Code

afflom added a commit that referenced this pull request Oct 16, 2026
…ards

ci: verify UOR Atlas as the parts of one run (#131, steps 2 and 3 of its agreed order).

Tested head 55583ba, which contains main 32afe5d (#144): run 38905653756, all 22 checks green. Atlas side (prefix start to join end) 95.9 min, the rest leg 111.5 min (the longer side now), the examples leg without Atlas 23.4 min, against 178 min for the examples leg on main at 514e95a. The join published Atlas with attestation fa0885f9... and found its 267 normalized records equal to expected/verify. #131 stays open: the bdd certificates partition and step 1 remain.
humuhumu33 pushed a commit that referenced this pull request Oct 16, 2026
…to claude/issue-89-typed-certificates

The four register counts are set by hand to 387 (main's 386 with SP-14). A staged runtime module carries no
authoring-notation record (`source: None`, as a library module), the one semantic conflict of the two
changes. VR-13's production slots gain certificate E's data record,
`preserve/LexLeanPreserve/*/*/Compose/*.lex.tex`, which §22.8 already lists. The language-1.2 records are
regenerated on the combined tree: verify-examples --write (every corpus but uor-atlas, which neither side
touches), check-fixtures --write, check-production-export --write.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[v0.4] #27 stage 4: the backend's fixed Lean runtimes generated through native modules

3 participants