Skip to content

fix(cli): resolve and run hub imports in independent checkup scopes - #1500

Open
oxura wants to merge 3 commits into
bendlang:mainfrom
oxura:fix/1499-checkup-hub-imports-20261011
Open

oxura wants to merge 3 commits into
bendlang:mainfrom
oxura:fix/1499-checkup-hub-imports-20261011

Conversation

@oxura

@oxura oxura commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

Current-head review qualification

Head cfd7b3d8 preserves the maintainer's generated-root refactor and fixes its reproduced traversal/symlink regressions. Both the initial parse and checked load use a hub-owned wrapper namespace so the existing loader's library-confinement checks apply. Protected bend.ts and compiler APIs are unchanged; no resolver/name/hash-cache copies or token-cap changes were added.

Before 67d6f8e4: both 0x<hash>/../../outside.bend and a cached entry symlinked outside the library executed the outside file (99n, exit 0). After: exit 1 and no outside output, while a later valid named IO import still executes. Cold hash/named loads use real loopback HTTP manifests and SHA verification. Warm hash, local IO, a no-main library, failure continuation, independent Base scope and SHA-mismatch rejection were exercised with the source CLI and a newly compiled CLI. Temporary roots were removed after success and failure.

The exact existing/new ping hub-consumer assertion block passed against this compiled CLI; repository gate 54/54. The full private-site installer suite, mini cluster and macOS were not run here. The older proof below is retained as historical qualification, not represented as a rerun on this head.

Fixes #1499.

--checkup currently turns a documented content-hash or named hub import into a file beside the aggregation. The ordinary checker/runner resolves the same cached package and returns 5n; independent checkup instead fails with ENOENT.

Resolve package names using the existing public name/version grammar, name-cache layout and immutable hash lookup protocol. Hand acquisition and manifest/file digest verification to the unchanged loader. Keep the module's hub namespace while parsing/checking, then select its actual pure or IO main; the shared JS generator now accepts an explicit entry root rather than only rooting main. Ordinary CLI, JS and module entry defaults remain unchanged. Reuse checked Base when source is already present, but do not inject Base into a module that did not import it. Cache confinement is checked before executing an entry.

The permanent installed-CLI consumer uses the gate's existing manifest hash helper to create a valid isolated cache: one content-hash pure entry returns 5n, and a different named effectful entry actually prints hub-io. The exact new assertion fails against unchanged main and passes against the genuinely rebuilt standalone CLI. No wiring/source-shape/diagnostic-text assertion. README documents independent hub scope.

Actual Linux x64 evidence:

  • Four cold source/installed runs fetch real SHA-verified manifest/file responses from an isolated HTTP content server selected by BEND_HUB. Hash imports return 5n; named imports resolve and execute hub-io. This is local HTTP protocol proof, not a claim that a test package was published to the public hub.
  • Twenty-four source/installed boundary runs cover warm hash/name/IO entries, a conflicting local decoy, rejection of a bad proof and missing Base, a quiet exit status 37, continued good execution after a bad module, ordinary local IO, package traversal, invalid version/hash and a missing package.
  • Eight actual source/installed integrity/confinement runs reject corrupt manifests/files without writing the package, and reject warm/cold external-cache symlinks before IO entry execution.
  • Eleven ordinary consumer executions preserve direct root CLI, native C, Bun/Node JS, Bun/Node MJS and actual Bun .bend-loader behavior; two real emission commands built those artifacts.
  • Exact new permanent consumer assertion: fail-before/pass-after with real CLI processes.
  • bun gates/repo.ts: 54/54.

The human-written bend2/bend.ts, GUIDE, CHANGELOG, kernel, gate policy and permanent token limits are untouched. Full installer/archive/hub ping was not run because the sibling site checkout is unavailable; executing its new assertion is not a claim that the complete gate passed. No GPU/platform qualification.

oxura and others added 2 commits October 11, 2026 21:15
…resolves it

--checkup wrote each hub import into a temporary root (import <path> as
Hub) and loads that, so name lookup, fetching, hash checks and library
confinement are the loader's own; the module keeps its hub namespace
and its main becomes the root's main. A first parse with Base marked
seen finds the module's file to see whether it imports Base, so Base
is seeded as for local imports. The entry parameter through io_type,
io_run, js_lib, book_main and book_run and the ns parameter of
book_read go: comp.ts is unchanged, main.ts is +246 tokens over main
instead of +608.
@nicolas-abril

Copy link
Copy Markdown
Collaborator

Unsupervised AI review (Claude, run by @nicolas-abril). Nobody has read this by hand yet, so please double-check it.

I pushed 67d6f8e on top of this PR. It keeps the behavior and lets the loader do all of the resolving:

  • For each hub import (0x…/ or name@version/), --checkup writes a one-line temporary root, import <path> as Hub, and loads that. Name lookup, fetching, SHA checks and library confinement are then exactly what book_load already does, so the copies of NAMED, the name cache, hub/plain/hash and the two realpath checks in cli_checkup go away.
  • The module still loads in its own hub namespace. After loading, book.tlds.main is set to <ns>:main. That removes the entry parameter from io_type, io_run, js_lib, book_main and book_run, and the ns parameter from book_read. comp.ts is back to main's version.
  • To keep the "seed Base only if the module imports it" rule, a first book_load parse runs with Base already marked as seen. This finds the module's real file without parsing Base. Without this step, warm hub checkups were about 40 ms slower each.
  • Tokens: main.ts is +267 over main instead of +608. comp.ts is +0 instead of +18.

Checks on macOS:

  • gates/repo.ts 54/54, gates/ping.ts 48/48 (this includes your hash and named hub checkup), gates/test.ts 1602/1602.
  • I made a local BEND_LIB with a pure hash entry, a named IO entry, a hub file that imports ../../foo.bend, a module with no main, and a local import. The output is byte-identical to this PR's head, including the confinement error.
  • hyperfine on a 3-import checkup: 56.3 ± 1.6 ms (PR) vs 58.0 ± 1.7 ms (this commit).

I left the README line and the ping test unchanged.

@oxura

oxura commented Oct 12, 2026

Copy link
Copy Markdown
Contributor Author

I reproduced two confinement regressions in 67d6f8e4 on Linux before accepting the refactor:

import 0x<package>/../../outside.bend as Outside
before: exit 0; stdout contains 99n from outside.bend
cached package entry symlinked outside BEND_LIB
before: exit 0; stdout contains 99n from outside.bend

The synthesized root was loaded with the empty/local namespace. book_load therefore treated a normalized or symlink-resolved outside entry as a legal local import; the loader's hub(ns) confinement check was not active for this root.

Fixed in cfd7b3d: mark the generated wrapper 0x0/checkup (a hub-owned namespace) on both loader passes. This lets the existing loader enforce confinement before descending into the outside module. The wrapper contains no declarations. I kept your name/hash resolution reuse, did not restore the duplicated resolver/realpath checks, did not touch protected bend.ts, and left compiler APIs / comp.ts unchanged. Only book_read's internal namespace argument is needed again for the second pass.

After-fix proof with real loopback HTTP package/name/manifest responses, source CLI and freshly compiled CLI:

  • traversal and outside-cache symlink: exit 1, no 99n;
  • later valid named IO still runs after a rejected traversal (hub-io);
  • cold hash, cold named IO, warm hash, local IO and module without main work;
  • type failure does not stop later entries; Base is not inherited by a module that omits it;
  • SHA mismatch remains rejected; temporary wrapper directories are removed on success/failure.

The existing ping gate now contains two consumer regressions for traversal/continuation and named-cache symlink confinement. I ran the exact assertion block against the freshly compiled CLI (all three hub checks pass) and the repository gate passes 54/54. README documents the boundary. Full private-site installer ping, the mini cluster and macOS were not run here.

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.

CLI: --checkup treats valid hub imports as unrelated local files

2 participants