Repository navigation
nvx-rs: move the CLI's sandbox lifecycle onto the store and supervisor - #772
Merged
Enrique Saurez (esaurez) merged 6 commits intoOct 22, 2026
Merged
Conversation
This was referenced Oct 22, 2026
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
FIFO runtime records or capability files can block lifecycle operations indefinitely while holding the sandbox lock, bypassing the configured timeout.
1 open issue
What changed in this PR
Moves the managed sandbox CLI onto the shared host store and supervisor, advancing the lifecycle consolidation in #620 while retaining CLI configuration and policy.
Changes:
- Delegates runtime records, launch recovery, and process supervision to shared host layers.
- Adds runtime-identity checks, stale-state recovery tests, and nonblocking lock-file validation.
- Includes guest provenance in setup bundles and updates lifecycle documentation and fixtures.
| File | Description |
|---|---|
| scripts/test_microvm_tests.py | Updates lifecycle test doubles and regression expectations. |
| scripts/setup/setup-windows-whp.ps1 | Requires guest provenance in setup bundles. |
| scripts/setup/setup-linux-mshv.sh | Packages and checksums guest provenance. |
| scripts/setup/README.md | Documents bundle provenance. |
| scripts/nvx_tools/sandbox_lifecycle_tests.py | Adapts acceptance checks to store records and crash cleanup. |
| nvx-rs/tests/cli.rs | Tests runtime identity, migration refusals, timeouts, and restart behavior. |
| nvx-rs/src/testing/fake_openvmm.rs | Adds delayed exit after acknowledged stop. |
| nvx-rs/src/host/supervisor/mod.rs | Exposes the launched child for CLI failure cleanup. |
| nvx-rs/src/host/store/tests.rs | Tests prompt rejection of FIFO lock files. |
| nvx-rs/src/host/store/mod.rs | Updates record-format documentation. |
| nvx-rs/src/host/store/files.rs | Opens lock files without waiting and rejects non-regular files. |
| nvx-rs/src/host/python/windows.rs | Removes unused Windows file-status helpers. |
| nvx-rs/src/host/python/mod.rs | Updates filesystem helper documentation. |
| nvx-rs/src/host/python/fs.rs | Removes unused open-file status helpers. |
| nvx-rs/src/host/process.rs | Documents the remaining legacy helper consumer. |
| nvx-rs/src/host/platform/windows.rs | Removes obsolete byte-range lock helpers. |
| nvx-rs/src/host/cli/sandbox/state.rs | Retains CLI configuration handling; removes its runtime-format constant. |
| nvx-rs/src/host/cli/sandbox/mod.rs | Adds runtime-identity lookup and managed executable checks. |
| nvx-rs/src/host/cli/sandbox/lock.rs | Delegates locking to the sandbox store. |
| nvx-rs/src/host/cli/sandbox/lifecycle.rs | Moves lifecycle operations onto the store and supervisor. |
| nvx-rs/src/host/cli/sandbox/identity.rs | Removes CLI-specific process identity handling. |
| nvx-rs/README.md | Documents shared lifecycle behavior and runtime identity. |
| doc/run.md | Updates operational guidance and acceptance expectations. |
Review effort: Balanced
Enrique Saurez (esaurez)
force-pushed
the
esaurez/a1-host-h4-switch-20261022
branch
from
October 22, 2026 21:44
d0553aa to
9951076
Compare
Base automatically changed from
esaurez/a1-host-h4-delegation-20261020
to
dev
October 22, 2026 21:50
The CLI kept runtime records, a lock, and spawn, wait, and kill code of its own. Its state directory is now the sandbox's store: the store's lock serializes the lifecycle transitions, and the store's launch journal and runtime record of format 4 replace the CLI's runtime record of format 1, while the CLI keeps its configuration record. start launches OpenVMM with supervisor::launch, under the runtime identity that the install metadata of the artifacts gives, every other transition finds the VM with supervisor::reattach, as an orchestrator does after it restarts, and stop runs the supervisor's graceful step through the control session, and reports a timeout without taking the forced step. A VM that another runtime identity started is reported and never driven. The first transition that finds the runtime record of an OpenVMM that exited clears it and reports it once, so start launches such a sandbox again, where it refused to before. Switching formats takes one stop of every running sandbox, with the build that started it: the store refuses a runtime record of format 1 with a message that names the format, and nothing is migrated. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: aa7b9fda-3774-4817-aa98-af05f8d50215
A managed start reads the runtime identity of the kernel and the Alpine initramfs from their build provenance, so a host whose guest artifacts come from a guest bundle, as a Windows host's do, needs it too. The bundle now carries vmlinux.provenance.json and initramfs.provenance.json, with their checksums, and the Windows setup requires and installs them. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: aa7b9fda-3774-4817-aa98-af05f8d50215
The lock file was opened for writing only, which waits for a reader when the path names a FIFO, before the deadline of the lock applies. The runtime record, the launch journal, and the capability were opened for reading, which waits for a writer, and read until the writer closed them, while the sandbox was locked. The opens no longer wait, and a lock, a record, or a capability that is not a regular file is refused before anything is read from it, as the CLI checked of its own records before it moved onto the store. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: aa7b9fda-3774-4817-aa98-af05f8d50215
…n the store The install metadata of the artifact root identifies the root's own OpenVMM, so a managed start refuses another one that NVX_OPENVMM names, which the runtime record would otherwise misname. Provision creates a missing state directory only through the store, even when an existing one is removed meanwhile, so that it is private to the user on every host. The documentation of the format switch also asks to end an OpenVMM that an interrupted start of an earlier version left without a record. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: aa7b9fda-3774-4817-aa98-af05f8d50215
The runtime identity was read from a release's SHA256SUMS or a checkout's build provenance with an open that waits for a writer when the path names a FIFO, and with a read that waits until the writer closes it. A sandbox's transitions read the identity while they hold the store's lock, so the open no longer waits, and a file of metadata that is not a regular file is refused before anything is read from it, as the store's own files are. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: aa7b9fda-3774-4817-aa98-af05f8d50215
Enrique Saurez (esaurez)
force-pushed
the
esaurez/a1-host-h4-switch-20261022
branch
from
October 22, 2026 22:11
9951076 to
0e458e5
Compare
Enrique Saurez (esaurez)
marked this pull request as ready for review
October 22, 2026 22:11
On Windows, the store opened its lock file through a link, and checked and locked the file that the link names. A lock taken through a link to a file that a launch replaces, such as the capability, held the replaced file, so the next transition locked the replacement and ran beside the first. The CLI's own lock refused reparse points before the CLI moved onto the store. The lock file is now opened as itself on Windows, and a reparse point, or a file that is not a regular file, is refused through the opened handle on every platform. On Unix, where O_NOFOLLOW refuses a link, the link is reported as not a regular file too. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: aa7b9fda-3774-4817-aa98-af05f8d50215
Enrique Saurez (esaurez)
deleted the
esaurez/a1-host-h4-switch-20261022
branch
October 22, 2026 23:21
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.

nvx-rs sandboxkept its own runtime records, lock, and spawn, wait, and kill code. This PR moves its managed lifecycle onto the host layers that every host of a sandbox shares, and the CLI keeps only its configuration record and its policy. #675 prepared the move without changing behavior: it added the reader of runtime identities thatstartrecords, and shapedstartfor the supervisor.host::instancefollows in #721, stacked on this PR, withaci_edge_sandboxesand the removal of the oldhost::processhelpers.Commits
nvx-rs: move the CLI's sandbox lifecycle onto the store and supervisor: the move, with its docs, tests, and golden fixture. The CLI's lock, runtime record, and process code move together, since the store's lock guards the store's records, in which the supervisor records the OpenVMM that it launches.scripts: carry guest build provenance in setup bundles.nvx-rs: open the store's files without waiting on a FIFO.nvx-rs: start only the root's OpenVMM, and create state directories in the store.nvx-rs: read install metadata without waiting on a FIFO.nvx-rs: refuse a store lock file that is a link.What changes
host::store. Its lock,sandbox.lock, serializesprovision,start,stop, anddeprovision;execholds it only while it finds the running VM, so a long workload still cannot delay astop. The store's launch journal and its runtime record of format 4 replace the CLI's runtime record of format 1.config.json, in the CLI's formats 1 to 6 with the distro binding of nvx-rs: separate distro verification from launch planning #653, stays the CLI's own record beside them, andstartstill checks that binding from metadata alone, and plans the launch from it as nvx-rs: plan sandbox launches with the image format and upper #676 does, before it launches OpenVMM.startlaunches OpenVMM withsupervisor::launch, commits once the guest answers with the required control features, and otherwise ends OpenVMM as before and abandons the launch. Every other transition finds the VM withsupervisor::reattach, as an orchestrator does after it restarts within the same boot: it ends an interrupted start, holds a live OpenVMM through its verified process identity, and reports, without driving it, a VM that another runtime identity started.stopruns the supervisor's graceful step through the control session, and reports a timeout without ever taking the forced step:managed control response timed out, orOpenVMM did not terminate after managed stop.host::sessionover the NVXS host connection, as before.startrecords the runtime identity thatArtifactDir::runtime_identity, from nvx-rs: prepare the CLI's move onto the store and supervisor #675, reads from the install metadata of the artifacts: a release'sSHA256SUMS, or a checkout's build provenance. Nothing hashes an artifact. A managedstartruns the root's own OpenVMM, which the metadata identifies, and refuses another one thatNVX_OPENVMMnames.scripts/setupcarriesvmlinux.provenance.jsonandinitramfs.provenance.json, which the Windows setup requires and installs, so that a host whose guest artifacts come from a bundle can start managed sandboxes.store::files::open_lockopenssandbox.lockwithout waiting for a reader,store::files::read_privateopens the runtime record, the launch journal, and the capability without waiting for a writer, andhost::artifactsreads the install metadata of the runtime identity as the store reads its records. Each refuses a file that is not a regular file before it uses it. The CLI's own lock was opened for reading and writing, which never waited, and the CLI checked that its records were regular files before it read them.store::files::open_lockrefuses asandbox.lockthat is a link, or on Windows any other reparse point, as the CLI's own lock did: a lock taken through a link would hold the file that the link names, which a launch may replace while the lock is held, so that the next transition would lock the replacement. On Windows the lock file is opened as itself and checked through the opened handle; on Unix,O_NOFOLLOWrefuses a link.cli/sandbox/identity.rs), the Windows byte-range lock helpers, and thefstathelpers that only they used.Starting::child, behind theclifeature, gives the CLI the launched child, so that a failed start ends OpenVMM as it did before abandoning the launch.Behavior changes
reattachclears the runtime record of an OpenVMM that exited without astop. The firstexecorstopreports the stale state once; thenstopreports that the sandbox is not running,startlaunches it again, where it used to refuse, anddeprovisionremoves it. The crash step of thesandbox-lifecyclescenario follows this. It is the one lifecycle test that changes beyond the format switch: nvx-rs: share the VMM supervisor and the sandbox store #620 expects the lifecycle tests to pass unchanged, but keeping the old refusal would need a record of the CLI's own beside the store's.provisionrefuses an existing state directory that is not private to the current user, as the store does, instead of making it private.startreplacesopenvmm.log, which the store creates fresh for each launch, instead of appending to it.startrefuses an OpenVMM thatNVX_OPENVMMnames instead of the root's, since no metadata identifies it.Format switch
Switching to this build needs one stop of every running sandbox, with the build that started it. Nothing is migrated: the store refuses a runtime record of format 1 in every transition, with a message that names the format and asks to stop the sandbox with the build that started it. A stopped sandbox keeps its
config.jsonand starts with this build, but the earlier build'slifecycle.lockstays in its state directory, anddeprovisionreports it as a file not owned by NVX: deprovision stopped sandboxes with the earlier build, or remove that file. On Windows, a state directory that an earlier build created outside the user's profile may not be private to the user, and the store refuses it: deprovision it with the earlier build, and provision it again. An OpenVMM that an interruptedstartof an earlier build left behind has no runtime record, and that build asked for it to be ended by hand, with itscontrol.capabilityandcontrol.sock: do so before the switch too, since this build finds no VM in that sandbox.The golden fixture
nvx-rs/tests/fixtures/cli.jsonis updated by hand for the switch, asnvx-rs/README.mdasks of deliberate changes: the lock's name, the store's runtime record, the log that each start replaces, and the build provenance in the fixture's root.Tests
nvx-rs/tests/cli.rs:stopreports a guest that never answers and an OpenVMM that outlives the wait, and leaves the VM running;startrefuses an OpenVMM thatNVX_OPENVMMnames.host::storeunit tests: a FIFO atsandbox.lockfails at once, with and without a reader, and so does one at the runtime record, the launch journal, or the capability, with and without a writer; and asandbox.lockthat links to the capability fails at once, and leaves the link and the capability as they are. The link test skips on a host that does not let it create a symbolic link.host::artifactsunit test: a FIFO at a build provenance or at a release's checksum list fails at once, with and without a writer.fake_exit_delay_ms, which delays its exit after an acknowledged stop.Part of #620. Refs #535.
Validation
Run on Windows 11 (MSVC, WHP) and on an Azure Linux 3 host with MSHV. The MSHV host cannot present the built-in profile of its CPU (
amd.genoa.v1), so OpenVMM ran there through a wrapper that adds--cpu-profile host.be28d631), ondevat72e8ea26, which merged nvx-rs: prepare the CLI's move onto the store and supervisor #675. Real VMs ran withnvx-rsbuilt from this branch and the artifacts ofv0.1.0-dev.72e8ea2677bd, the release of72e8ea26, fromnvx.py download, whose OpenVMM is7ff31ed7, the gitlink ofdev.python scripts/nvx.py verify.nvx-rs, withNVX_BUNDLE=skip, on Windows and Linux:cargo fmt --all --check;cargo clippy --locked --all-targets --all-features -- -D warnings;cargo clippy --locked --lib --no-default-features -- -D warnings;cargo test --locked --all-features, thencargo test --locked;cargo doc --locked --no-deps --all-featureswithRUSTDOCFLAGS=-D warnings. On Linux, the other steps ofcheck-nvx-rs: the 1.89 check, the macOS check, the artifact-bundling test, and the three configurations oftests/runtime-adapter. The new link test ran on both, and fails on Windows without the last commit, where the lock holdscontrol.capability.aci_edge_sandboxes: the steps ofcheck-aci-edge-sandboxeson Linux (fmt, clippy, tests,nvx.py test-agent-bundling, docs, the 1.89 check, the macOS check), and its fmt, clippy, tests, and docs on Windows.cargo check --locked --all-targets --all-featuresinnvx-rsandaci_edge_sandboxes, on Linux.shellcheckandshfmton the scripts thatcheck-qualitylists, and the tests ofvalidate-nvx,test_setup_azure.pyincluded.python scripts/nvx.py test-microvm --scenario managed-lifecycle --scenario sandbox-lifecycle --scenario managed-exec-config, andpython scripts/nvx.py test-aci-edge-sandboxes(18 tests).sudofor/dev/mshv: the same three scenarios; andtest-aci-edge-sandboxes, where 13 of 18 tests pass and the 5 that write through read-write mappings fail as they do ondev, because the unprivileged workload cannot write the root-owned directories that a root test creates.0e458e5e, without the last commit, which changes onlynvx-rs: the same gates, and also, on Windows, the Python steps ofcheck-qualitywith the pinned tools ofrequirements-dev.txt(ruff check,ruff format --check,pyrightfor Linux and Windows, the Markdown link check), the PowerShell syntax check, the tests ofvalidate-nvx, and thenvx.py --helpsmoke commands. On Windows, four process-containment tests oftest_adversarial.pyfail identically ondevwithout this PR, and itstest_close_is_bounded_when_a_surviving_child_holds_executor_stdoutfails in some runs, as it does ondev's Python, because the removal of its temporary directory races the exit of the child that it killed. Real VMs: on WHP, the same three scenarios andtest-aci-edge-sandboxes(18 tests), with the artifacts ofv0.1.0-dev.bdaed9c6a7ca; on MSHV, the same as at this head, with those ofv0.1.0-dev.8483e3757469.141a04b4, ondevate714acbd, held the same changes but the FIFO and link checks of the store and of the install metadata, and passed the same gates on Windows and Linux, and the same real-VM runs on WHP, KVM, and MSHV with the artifacts ofv0.1.0-dev.e714acbd47bf.Left to CI: the full matrix on KVM, MSHV, and WHP.