Repository navigation
Conversation
require_relative now resolves its argument against the running file's
directory in the VFS and runs the pre-desugared body of the file it names; it
previously gated ("needs source-file resolution"). Plain require is unchanged.
This is the first slice of the require/gem/marshaling plan in
ruby-lean/notes/model/marshal-require-gem-plan.md.
The library fixture (prelude/vfs/) is generated the way the prelude is:
gen_vfs.rb writes the bodies as JSON plus raw bytes and pre-split path
components (VfsFixtureJson.lean), then the genvfs executable decodes them to
RubyCore terms (VfsFixture.lean): the raw files, and requireBodies, each body
keyed by its canonical path. Both are build outputs, not committed; `make`
writes them before the model builds.
Prelude.boot ends by mounting the files into the VFS, so they are part of what
the boot leaves: genbootedheap writes them into the booted-heap literal, and
initWithPrelude = initOnBooted holds unchanged (both set the same constant
requireBodies). require_relative resolves its argument to a heap object and
runs the entry whose path resolves to the same object, caching under that path,
so any spelling of a file loads once. Terms rather than Json.parse, and
pre-split components rather than String.splitOn, keep the boot
kernel-reducible, so boot_eq_booted still holds.
Relative paths reuse the step-1 absolute VFS.resolve, and nothing in initHeap
moves. Proof fallout: notDone and framing lemmas for the new helpers,
pushRootK_requireBodies for the new Machine field, and FastPower's two pinned
object ids shift +5 (the mount allocates five objects before the program runs).
Verified: make lean-exes and make books (every book, from regenerated sources);
make feature-loading (model vs CRuby: load, transitive, cache, circular,
explicit .rb and ./ forms); axioms of the affected lemmas are the standard
three; ruff and RuboCop clean.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sam-xif
force-pushed
the
claude/ruby-gem-marshaling-model-f7a461
branch
from
November 5, 2026 02:57
6b97546 to
4823c7d
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
require_relativenow resolves its argument against the running file's directory in the symbolic filesystem (VFS) and runs the pre-desugared body of the file it names. It previously gated (Require.lean"require_relative needs source-file resolution"). Plainrequireis unchanged — still the name-keyed modeled standard libraries;$LOAD_PATH-basedrequireover the VFS is the next slice.This is the first slice of M1 in a staged plan toward modeling
require, the gem system andMarshal, so that deserialization gadget chains become ordinary, analyzable program traces. The plan is inruby-lean/notes/model/marshal-require-gem-plan.md; this step is recorded inruby-lean/notes/model/implementation-notes.md.Stacked on #71 (generated Lean sources are build outputs, not committed): this PR's
VfsFixture.lean/VfsFixtureJson.leanare generated and not committed either. Merge #71 first; GitHub then retargets this tomain.Design
The fixture, generated like the prelude. Every
.rbunderruby-lean/prelude/vfs/is mounted into the VFS as a regular file (its bytes) and desugared to a RubyCoreExpr; the model never parses Ruby at run time.scripts/gen_vfs.rbwrites the bodies as JSON plus raw bytes and pre-split path components (Generated/VfsFixtureJson.lean);genvfsdecodes them to RubyCore terms (Generated/VfsFixture.lean):rawFiles, andrequireBodies, each body keyed by its canonical path. Both come from the same source file in one generation step, soFile.readof a library file and the bodyrequire_relativeruns cannot drift.makeregenerates both before the model builds.Mounted by the boot, so it is in the booted-heap literal.
Prelude.bootends by mounting the files (Boot.mountFiles, ordinary heap writes).genbootedheaptherefore writes them intoGenerated/BootedHeap.lean,boot_eq_bootedcompares them like the rest of the heap, andinitWithPrelude = initOnBootedholds with no change to its proof: both start from that heap and set the same constantrequireBodies. Nothing inBoot.initHeapmoves.Lookup by object, not by spelling.
require_relativejoins a relative argument onto the source file's directory, resolves it with the existing step-1VFS.resolve(which already handles./..), then runs therequireBodiesentry whose canonical path resolves to the same object, caching under that path. Any spelling of a file (greeting,./greeting,greeting.rb) loads once, as CRuby's realpath-keyed cache does. Re-entry and retry ride the existingrequireKcontinuation.Kernel-reducible boot.
boot_eq_bootedhas the kernel run the boot.Json.parse(partial) andString.splitOn(well-founded recursion) do not reduce, so decoding happens ingenvfsand components are split in the generator; the mount is a fold over literals.Honest gates. A path that does not resolve, or resolves to a file with no modeled body, gates by name (CRuby raises
LoadError; error classes are issue #7 step 7).require_relativewith no source file (e.g. fromeval) gates.Proof fallout.
notDoneand framing lemmas forrunLibraryBody/callRequireRelative,pushRootK_requireBodiesfor the newMachinefield, and FastPower's two pinned object ids+5(the mount allocates five objects before the program runs).Verification
Locally, from regenerated sources (
CRubyNames.lean/Unicode.leantaken frommain, since they only regenerate identically on the pinned Ruby):make lean-exes;make books(every book);make feature-loading— model vs CRuby 4.0.5, byte-for-byte, on three newrequire_relativecases (load with a transitiverequire_relative; caching, where a repeat and an already-loaded file both returnfalse; a circular pair seeingfalsemid-load; explicit.rband./spellings loading once) alongside the existingrequirecases; axioms of the affected lemmas are the standard three; ruff and RuboCop clean. CI runs the fullmake check.Authored by Claude (an agent). A human should review before merging.
🤖 Generated with Claude Code