Repository navigation
wasi: a task's named grants as preopens, and the two faults that hid every preopen on Kore - #26
Open
SnowCheetos wants to merge 8 commits into
Open
SnowCheetos wants to merge 8 commits into
SnowCheetos wants to merge 8 commits into
Conversation
A guest's filesystem was one preopen, `.`, the task's root view, walked one containment step at a time — deliberately never through the task's named grants, since a guest opening `data/x` would reach authority its embedder holds and never handed it. That left a guest unable to see what its launcher sees: on kore the app store seeds programs and their data under `shared`, a grant the shell's own walk resolves by name, and CPython launched as `shared/wasm/python.wasm` could stat neither itself nor `shared/wasm/lib/python312.zip` beside it (`getpath` then fell back to a prefix of `/` and died for want of `encodings`). `Filesystem::with_named_grants` lets the embedder offer them: each named grant becomes a preopen of its own under its store name beside `.`, opened with the widest rights the service answers (the root's own ladder), the private root and unnamed grants left out. The guest walks beneath a grant exactly as beneath `.` and still never reaches one *through* `.`; an embedder that says nothing offers nothing, as before. The listing is read whole before any grant is opened, since the next request reuses the pair's window. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TmEMGSFqb56Tcsvuk9PQPx
`open-at` of `.` reopens a directory by its path from the preopen, since a view has no duplicate verb — and the preopen was always the task root. With named grants offered as preopens of their own, a descriptor beneath one reopened as `.` would have walked its path from the root instead and named a different object, or none. A descriptor now carries its origin, the root or the grant it descends from, and reopens from there. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TmEMGSFqb56Tcsvuk9PQPx
`get-directories` answered an empty list when the task's root could not be opened, and left out a named grant the service refused, so a guest read "no files" where the service had said why. The interface has no error to return; the refusal is now a trap naming what was refused. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TmEMGSFqb56Tcsvuk9PQPx
…n once Two faults kept a guest from ever seeing its preopens on Kore, where a filesystem call parks and is driven to completion across turns. `FsHost::exec` drives the task's one filesystem op through a single slot: it installs the op, polls it to `Ready`, and the op writes its result into a shared cell. The loop's final arm — reached once the op has been taken and the slot has drained — returned `None` unconditionally, throwing away the result the op had just written whenever the op queued no view to close (the common case: `get-directories`' opens return their views owned, so nothing is orphaned). The one caller reads that `None` as a lost pair, so every `get-directories` answered an empty preopen list and a program that looks beside its executable — CPython for its stdlib — found nothing. The arm now hands back what the op produced. And `get-directories` reopened the root and every named grant on each call. The preview1 adapter asks once at startup and again for each preopen name it reads back, so a handful of fds became dozens of opens and immediate closes. The preopens are now opened once, on the first call, and every later call answers descriptors over the same shared views — one open per preopen for the guest's life, one route held per grant, released with the filesystem. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TmEMGSFqb56Tcsvuk9PQPx
The slot driver's dropped result (d2fcddb) hid every preopen from a guest on Kore and passed every host test, because nothing on the host parks an op across turns. Two tests drive `FsHost::exec` over a host with no face, with ops that park once, as every op with a request in flight does on Kore: an op is answered with its result on the turn it finishes, and a caller dropped mid-op leaves its op to finish on the next caller's turn, which is still answered with its own. Both fail against the old arm, which answered `Ready(None)`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TmEMGSFqb56Tcsvuk9PQPx
There was a problem hiding this comment.
🟡 Changes recommended
Named file grants can be published as directory preopens, violating the WASI interface contract.
1 open finding
What changed in this PR
Adds opt-in named-grant preopens to kore-wasi, enabling guests such as CPython to find files outside the task root while preserving containment.
Changes:
- Exposes and caches named grants as preopens.
- Preserves descriptor origins and reports preopen refusals as traps.
- Fixes delayed filesystem results and adds regression tests.
| File | Description |
|---|---|
| crates/wasi/src/filesystem/walk.rs | Documents containment for named-grant preopens. |
| crates/wasi/src/filesystem/mod.rs | Tracks descriptor origins and publishes cached preopens. |
| crates/wasi/src/filesystem/host.rs | Opens and caches grants; fixes and tests delayed results. |
🧠 Review effort: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
`Fs::open_grant` enters a granted file as readily as a subtree root, and `get-directories` answered every named grant it opened as a directory preopen, so a task holding a named file grant would hand its guest a "directory" whose every walk fails with NotDirectory. A preopen is a directory: a grant whose root is not one is now closed and left out. It is not refused, since a refused preopen traps the guest (acad200) and this guest asked for nothing it lacks. The open loop records what it opened rather than inferring it from the last preopen's name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TmEMGSFqb56Tcsvuk9PQPx
Brings in Lua 5.5 (#23, #25) and the Wasmtime 48.0.3/48.0.5 RUSTSEC bumps, so kore can pin one revision carrying both its kore-lua worker and this branch's kore-wasi changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TmEMGSFqb56Tcsvuk9PQPx
`wasi:cli/terminal-stdin`, `-stdout` and `-stderr` answered "no terminal" unconditionally, so a guest's `isatty` was always false and CPython never opened its REPL without `-i` (and block-buffered its output on the shell). Whether the far end of the standard streams is a terminal is something the embedder knows and the streams do not: on Kore it is the profile the launcher declares on the process stream page. `WasiCtx::with_terminal` takes that declaration; with it the three getters hand the guest a terminal handle (a resource with no methods: holding one is the answer), and without it they keep answering `None`, as for redirected stdio. All three streams share the one process stream, so they answer together. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TmEMGSFqb56Tcsvuk9PQPx
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
Lets an embedder offer a task's named grants to a WASI guest as preopens, and fixes two faults in
kore-wasi's filesystem host that kept a guest from ever seeing its preopens on Kore. Together they let CPython 3.12, run by kore'swasmcommand as awasi:cli/commandcomponent, find its stdlib beside its own executable. The kore half is MicroPerceptron/kore#1273, which pins this branch's head,7afc2d0.Changes
90f5fa8). A guest's one preopen was., the task root, walked a containment step at a time and never through a named grant, by design. On Kore the app store seeds programs and their data undershared, a grant the shell's walk resolves by name, so CPython launched asshared/wasm/python.wasmcould stat neither itself norshared/wasm/lib/python312.zip.Filesystem::with_named_grantslets the embedder opt in: each named grant becomes a preopen under its store name beside., opened with the widest rights the service answers. The private root and unnamed grants are left out, a guest still never reaches a grant through., and an embedder that says nothing offers nothing, as before.7010ac6).open-atof.reopens a directory by its path from the preopen, which was always the root; beneath a grant that named a different object, or none. A descriptor now carries its origin (the root or its grant) and reopens from there.acad200).get-directorieshas no error to return. It answered an empty list when the root was refused and silently left out a refused grant, so a guest read "no files" where the service had said why. It now traps, naming what was refused.d2fcddb).FsHost::execdrives the task's one filesystem op through a single slot. Its last arm, reached when an op installed on an earlier turn finishes, returnedNoneunconditionally, dropping the result whenever the op had queued no view to close. That is always the case forget-directories, whose opens return their views owned, so every call answered an empty preopen list. It now hands back what the op produced. Andget-directoriesreopened every preopen on each call (the preview1 adapter asks once at start and again per name); the preopens are now opened once and shared for the filesystem's life.eb94cfe). Two tests driveFsHost::execover a host with no face, with ops that park once: an op is answered with its result on the turn it finishes, and a caller dropped mid-op leaves its op to finish on the next caller's turn, which still gets its own result. Both fail against the old arm, which answeredReady(None).daee213, from review).open_grantenters a granted file as readily as a subtree root, and every named grant opened was published as a directory preopen. A grant whose root is not a directory is now closed and left out. It is not refused, because a refused preopen traps the guest, and this guest asked for nothing it lacks.alphamerged in (7afc2d0): Lua 5.5 (lua: Lua 5.5 as a crate, with io, os and kore.fs #23, lua: add bounded read-only worker policy #25) and the Wasmtime 48.0.3/48.0.5 RUSTSEC bumps. koremainhad pinned3f82da8, the head of lua: add bounded read-only worker policy #25, whose treealphaholds as7a76d19; with this merge one libkore revision carries both that and this branch'skore-wasi, so kore can pinkore-std,kore-wasiandkore-luatogether. The merge changes nothing ofalpha's: againstalpha, this branch differs only in the threecrates/wasifiles above.The two
d2fcddbfaults show only where a filesystem call parks and is driven to completion across turns, which never happens under a host Wasmtime's blocking calls; that is why no host test saw them. They were found on Kore under QEMU with a small C probe guest (clangwasm32-wasi, the preview1 adapter) that lists its preopens and stats what it is handed. The origin fix (7010ac6) and the file-grant rule (daee213) have no unit tests: each turns on anopen_root/open_grantround trip that needs a real Kore face, and libkore does not mock its owners. The kore keytest below covers the directory-grant path end to end; no keytest holds a named file grant.Verification
7afc2d0:cargo fmt --check,cargo clippy --workspace --all-targets --all-features -- -D warnings,cargo test --workspace --all-features(75 passed,alpha's Lua suite included),cargo doc --no-depsunder-D warnings,check-ownersandcheck-native: pass locally with Zig 0.16.0. CI was green ondaee213.miri(excludeskore-wasi),crossandbindings-check(they cover onlykore-std,kore-ffiandkore-entry, which this branch's own commits do not touch).keytest-python-x86runs CPython importingsys,jsonanddecimalthrough zipimport and requires the computed linekore 3 97, with libkore pinned at7afc2d0: pass. Not yet run on real hardware.Landing
Merge this first, with a merge commit (as #21 and #23 were), so
7afc2d0stays reachable fromalphaand kore's pin names an acceptedalphacommit with no follow-up. If this is squashed instead, MicroPerceptron/kore#1273's pin has to move to the squash commit before it merges.🤖 Generated with Claude Code
https://claude.ai/code/session_01TmEMGSFqb56Tcsvuk9PQPx