Skip to content

vfs-bundle: guards where the disk is read, os/cpu/libc target, early option checks - #197

Merged
ChALkeR merged 1 commit into
mainfrom
claude/sharp-edison-cmebue
Oct 2, 2026
Merged

ChALkeR merged 1 commit into
mainfrom
claude/sharp-edison-cmebue

Conversation

@exo-nikita

@exo-nikita exo-nikita commented Oct 1, 2026 •

Copy link
Copy Markdown
Collaborator

Follow-up to #196, #198 and #194, rebased on #194 (Solidity from soldeer.lock). #198 owns the runtime disk-read test.

Guards where the disk is read

  • createMetroResolver: takes host and refuses one that isn't the disk. It loads and runs the project's metro-resolver, which reads the disk.
  • checkVfsOptions(name, pm, packageManager, options): this new export holds buildVfsBundle's up-front refusals. It allows only the package manager's kind (JS for pnpm/yarn1, Solidity for soldeer), refuses metroResolver, and runs every classifyEntries check.
  • buildGitHubBundle checks before downloading: it now runs checkVfsOptions over an empty tree before any request, so a bad option no longer costs a whole repo download first.

Target machine: os, cpu, libc

  • Where: loadNodeModules, buildVfsBundle and buildGitHubBundle take os and cpu (Node's names, e.g. linux, x64) and libc (glibc | musl | unknown).
  • What they do: they describe the machine packages are matched against. Each defaults to this machine's.
  • Libc for another OS: if os names a different OS than this machine's and no libc is given, libc defaults to unknown instead of this machine's detected one.
  • Validation: checkTarget checks all three at each entry point, before any fetch.
  • Per package manager: pnpm takes all three, yarn 1 takes os and cpu, and Soldeer takes os. A value a package manager doesn't match against is accepted and has no effect.

Static test

The host-aware modules must not name node:fs, fs or fs/promises in any form. That covers multi-line imports, either quote style, export … from, import() and require(). The only exception is tree.js's import { constants }.

  • Modules covered: scan, both resolvers, tsc's mapping, bundle-util, vfs-bundle/* and loaders/foundry.js.
  • Not covered: loaders/solidity.js. It still imports readFile for loadSolidity, the .sol.txt loader, which is unused in stasis/src and isn't on the buildSolidityBundle path.
  • Self-check: the test checks that its own pattern catches each form.

Not covered here

The Soldeer path (buildVfsBundle / buildGitHubBundle with packageManager: 'soldeer') has no runtime disk-read trace yet. #198's trace builds pnpm workspaces only.

Checks

  • Full suite on Node 24.14.0: 2133 passed, 0 failed.
  • node --run lint: clean.

🤖 Generated with Claude Code

https://claude.ai/code/session_01C4wWtS6P5ji71GhZ56NZeh

@exo-nikita
exo-nikita force-pushed the claude/sharp-edison-cmebue branch from adf7491 to 2015865 Compare October 1, 2026 21:19
@exo-nikita exo-nikita changed the title vfs-bundle: guard host-less loaders off disk, libc option, hermetic test vfs-bundle: guards where the disk is read, os/cpu/libc target, early option checks Oct 1, 2026
…option checks

Rebased onto #194 (Solidity from soldeer.lock), re-applied against its
reshaped tree.js and buildVfsBundle:

- createMetroResolver takes `host` and refuses one that isn't the disk
  (it loads the project's metro-resolver, which reads the disk).
- checkVfsOptions(name, pm, packageManager, options): buildVfsBundle's
  up-front refusals (the package manager's kind alone, no
  metroResolver, every classifyEntries check), which buildGitHubBundle
  now runs over an empty tree before downloading anything.
- `os`, `cpu` and `libc` for loadNodeModules, buildVfsBundle and
  buildGitHubBundle: the machine packages are matched against, this
  one's but for what is given; for another os, libc defaults to
  'unknown'. checkTarget at each entry point. pnpm takes all three,
  yarn 1 os/cpu, Soldeer os: what one matches nothing against changes
  nothing.
- A static test: the host-aware modules (scan, both resolvers, tsc's
  mapping, bundle-util, vfs-bundle/*, foundry.js) name node:fs in no
  form, and the pattern catches each form.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01C4wWtS6P5ji71GhZ56NZeh
@exo-nikita
exo-nikita force-pushed the claude/sharp-edison-cmebue branch from 507dca4 to 5671342 Compare October 1, 2026 21:49
@ChALkeR
ChALkeR merged commit 322c821 into main Oct 2, 2026
5 checks passed
exo-nikita pushed a commit that referenced this pull request Oct 2, 2026
buildGitHubBundle and suggestedEntries check their options against an
empty Vfs, before anything is fetched: "what the tree is never decides
them" (#197). #178's directoryEntryError then found every extensionless
entry missing from that empty tree. So `src` with pnpm was refused as "no
such file or directory" rather than "only JS bundles are built with pnpm",
and main's "buildGitHubBundle reads nothing from disk" test fails.

The pre-fetch checks now pass `fetched: false`, which skips the
missing-entry errors. buildVfsBundle still checks entries against the
project's own tree.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01C6oBS5QX4oqZcd2d3STiGA
ChALkeR pushed a commit that referenced this pull request Oct 2, 2026
…uote; green main after #178 (#201)

* fix(vfs-bundle): check entries before the fetch without looking for them

buildGitHubBundle and suggestedEntries check their options against an
empty Vfs, before anything is fetched: "what the tree is never decides
them" (#197). #178's directoryEntryError then found every extensionless
entry missing from that empty tree. So `src` with pnpm was refused as "no
such file or directory" rather than "only JS bundles are built with pnpm",
and main's "buildGitHubBundle reads nothing from disk" test fails.

The pre-fetch checks now pass `fetched: false`, which skips the
missing-entry errors. buildVfsBundle still checks entries against the
project's own tree.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01C6oBS5QX4oqZcd2d3STiGA

* fix(bundle): a package.json that's there but can't be read is refused, as Node refuses it

host.stat returns null for any failure, as Node's module lookup does. So
every package.json reader took a link loop, or a file behind a directory
that may not be searched, for no package.json and walked past it:

- the strict Solidity reader, which bucketed a forge lib's files as the
  project's own;
- the resolver the Vfs host uses, and the Vfs host's findPackageJSON;
- State's walk up to a bucket's name, and its root discovery;
- readModuleManifest.

Node refuses such a package.json with ERR_INVALID_PACKAGE_CONFIG, and
counts only "nothing there" as none, a link to nothing included. Real stat
tells the two apart and host.stat doesn't. That's right for the module and
lib probes, which treat any failure as absent as Node and Rust do, so stat
stays as it is. When a package.json reader's stat fails, a read now says
why:

- statStrict, for configs and the strict package.json read, names the file
  from the project root (`lib/dep/package.json: can't be read (ELOOP)`).
  readRegularFileOrNull uses it too, instead of rethrowing the raw fs
  error with its absolute path.
- packageJSONStat throws Node's ERR_INVALID_PACKAGE_CONFIG, for the
  resolver, the Vfs host's findPackageJSON and State.

Lenient lookups (Bash, Rust, `stasis add`, --package-json) still walk
past a malformed or unreadable one. A differential against Node's
require.resolve and findPackageJSON now agrees on every case: missing,
dangling, a directory, malformed, a self-loop, a loop through
directories, and (unprivileged) a link into an unsearchable directory.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01C6oBS5QX4oqZcd2d3STiGA

* fix(bundle): invalid-remapping errors name the entry, never its text

An invalid remapping's error quoted it. The text is normally a remapping
the project wrote, but a file named as a mapping by mistake can hold
anything: `--mapping=token.txt` printed `token.txt:1: invalid remapping
"ghp_…"` to stderr, and into CI logs with it. A token pasted into
FOUNDRY_REMAPPINGS did the same.

Errors now give the location and the form the entry should have:
`remappings.txt:2: invalid remapping, expected [context:]prefix=target`.
A foundry.toml entry is named by its position (`` `remappings` entry 2 ``,
`` `remappings` entry 1 is not a string ``). This is the last error of
stasis's own that quoted file content.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01C6oBS5QX4oqZcd2d3STiGA

---------

Co-authored-by: Claude <[email protected]>
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.

3 participants