Skip to content

cli: take the downloads and the control session off the old supervisor (SL3.10a) - #1491

Merged
Pedro Henrique Penna (ppenna) merged 2 commits into
feature/nvx-refactorfrom
agents/slim-sl3-10a-downloads-session
Nov 12, 2026
Merged

Pedro Henrique Penna (ppenna) merged 2 commits into
feature/nvx-refactorfrom
agents/slim-sl3-10a-downloads-session

Conversation

@ppenna

@ppenna Pedro Henrique Penna (ppenna) commented Nov 11, 2026 •

Copy link
Copy Markdown
Contributor

Part of #1112 (SL3.10a: the CLI's downloads and session no longer use the old supervisor or instance).

What and why

The CLI is moving off nvx's old supervisor and instance layer. This PR does SL3.10a's Scope and no more:

  • Downloads (download::{archive, git, github}): tar, git and curl start with std::process::Command::spawn instead of host::supervisor::spawn_command, which only held the old supervisor's Windows spawn lock around the same spawn (nvx-rs: keep processes spawned during a launch from holding its VMM's log on Windows #966). Their standard streams, environment, working directory and errors are unchanged, and the CLI still checks for Ctrl-C once each tool exits; the tools still share the CLI's console and process group, so Ctrl-C reaches them as before.
  • Session: Session::connect_launched_cancellable, with which a managed cold run waits for its control endpoint, takes the verified Process of the run's host::vm::Launched, beside the endpoint and capability that the launch supplies, instead of the supervisor-owned child (host::spawn::Launched), and ends the wait once that process has exited, as the child's status ended it. host::vm::Launched::child_mut, which lent that child to the session, becomes private to vm, where only wait_status uses it, and the session no longer mentions host::instance. sandbox exec and sandbox stop already attach through Session::reattach, which verifies a saved VM through host::vm, and do not change.

Not in this PR (per #1112): download formats, validation, retries, repositories or authentication; NVXC messages, exec semantics or stop policy; a process supervisor or command wrapper; new commands, flags or output.

#966 stays in the tests. The Windows spawn lock stays private to host::vm::spawn, and a caller keeps its own spawns out of a VM's start (#1076). The CLI does so in production by downloading before it launches: run, sandbox run and sandbox start download first, and no command starts a VM while it downloads, so its tools spawn without the lock. The library's tests, though, run the download beside tests that launch stand-in VMs, so they keep #966's protection themselves: each archive and GitHub test that runs tar or curl holds the tests' spawn lock, from the new host::tests::hold_spawns, while it runs them, and the archive tests' helper that packages a release spawns its tar with host::tests::spawn_locked. No production code takes the lock.

Holding the lock delays the stand-ins' own spawns while a tool runs, about a second for the retried request of the_last_response_to_a_retried_request_counts. Before #1495 (SL3.7b follow-up), that made host::cli::snapshots::capture::tests::a_capture_ack_and_successful_output_without_timely_exit_are_stopped_but_never_finished fail on Windows whenever it ran beside that test (3 of 3; it passed alone and beside the archive tests), since its capture's one-second deadline also covered its fake VMM's start. In five full library runs without #1495, it was the only test that failed: nothing else fails under the lock. #1495 starts that fake before the capture, and merged into feature/nvx-refactor as ac3bbefb7d4, on which this PR now builds: here the capture test passes in the full library run beside the download tests (see Validation), as it did beside the retried request (3 of 3) and in two full library runs with #1495's head (9b816f7e523, the same patch) applied before it merged.

First implementation. Enrique Saurez (@esaurez)'s esaurez/private-sl310a-0325 (05a7eb2, acd18ef, on an older base) is replayed as two commits that keep his authorship, adapted to the current session code (the cancellable attachment of #1460), with these corrections: child_mut becomes private, and its docs and the field's docs say what remains; the download tests hold the spawn lock while they run their tools, and the archive tests' helper spawns its own tar under it, rather than spawning bare; a new test checks the session's wait on a real process; and his second new GitHub test, of a failing curl, is left out, since this change does not touch that path.

Size. 11 core lines (estimate 30, stop 60): session.rs 4, archive.rs 2, run.rs 2, git.rs 1, github.rs 1, vm.rs 1. 5 counted files (archive.rs, git.rs, github.rs, session.rs, run.rs), and vm.rs, whose edit only makes child_mut private and updates two doc comments (6 if it counts). The public API, BOOT_CONTRACT_VERSION and CONTROL_CONTRACT_REVISION do not change.

Validation

Merged at 6262d145367 (on feature/nvx-refactor 1b902ac16a5, after #1486, #1487 and #1489): the rebase had no conflicts (with rerere off; only run.rs overlapped, in other hunks), rebase_check.py verify passed, and git range-diff shows both commits unchanged (=), with the patch IDs that Copilot reviewed at e6b2dec437f. CI run 43129157648 passed, 34 jobs and 9 skipped, KVM, MSHV and WHP included. Locally on Windows, the Format and Lint commands of check-nvx-rs, 103 unit tests of the downloads, session, capture and host::tests, and the cli target (110) passed. Every download of the CLI still runs before its command launches a VM (run, sandbox start and sandbox run, restored starts included), as the #966 paragraph above requires.

At 2c4bcc67545, on feature/nvx-refactor f834bcb9672: the rebase from d4edeeb9ab9 over #1495, #1488, #1494 and #1492, none of which touches this PR's files, had no conflicts (with rerere off), rebase_check.py verify passed, and git range-diff shows both commits unchanged (=), with the same patch IDs and still signed; the diff over the base is byte-identical.

Check Result
Windows, the check-nvx-rs commands: cargo fmt --check; Clippy -D warnings (--all-targets --all-features, --lib, --lib --no-default-features, --lib --no-default-features --features serde, --lib --bins --features cli, --lib --bins --no-default-features --features testing); cargo test --all-features; cargo test; --lib --no-default-features build and tests; bundled-artifacts test; cargo doc with -D warnings; MSRV 1.89; runtime adapter protocol-only and host-only Passed: lib 1466 (the capture test among them), cli 110, security_checklist 11, and every other target
check_test_layout.py; nvx.py verify Passed

Linux and the microVM scenarios were not rerun locally at 2c4bcc67545, whose commits are unchanged; their results at 5fda1863414 follow.

At 5fda1863414, on feature/nvx-refactor d4edeeb9ab9, where #1490 merged: the rebase from #1490's branch (git rebase --onto origin/feature/nvx-refactor 279679d1839) had no conflicts, and git range-diff shows both commits unchanged, with the same patch IDs and still signed. rebase_check.py verify reports only the stacked commits that merged upstream, as 7bc238eb3d0 and 9086b4a6a78 with the same patches and as d4edeeb9ab9 with #1490's review fixes, none of which touch this PR's files.

Check Result
Windows: cargo fmt --check; Clippy -D warnings (--all-targets --all-features, --lib, --lib --no-default-features, --lib --bins --features cli, --lib --bins --no-default-features --features testing); cargo test; --lib --no-default-features build and tests; bundled-artifacts test; cargo doc with -D warnings; MSRV 1.89; runtime adapter protocol-only and host-only Passed
Windows: cargo test --all-features Every integration target passed (cli 110); lib 1465 passed and 1 failed, the capture test above, which #1495 has since fixed
Linux (nvx-kvm-test): cargo fmt --check; Clippy --all-targets --all-features -D warnings; the download and session unit tests (93); cargo test --all-features --test cli (119, the Ctrl-C tests of interrupts among them); macOS check Passed
nvx.py test-microvm --backend kvm: managed-exec-config, sandbox-lifecycle, sandbox-ram-upper, sandbox-blocks, managed-lifecycle, filesystem-comma-paths Passed
check_test_layout.py Passed

At e6b2dec437f, on #1490's branch, with the same commits: the same Windows results (lib 1464 and the capture test), and on Linux the checks above (cli 118) and four of the scenarios.

At the first head (0c4cb9c0d99, on 1008a3e0056), with the same production code:

Check Result
The check-nvx-rs commands above, on Windows and on Linux Passed, except the 28 guest:: unit tests that fail on nvx-kvm-test at the base too (lib 2268 passed); nvx.py check-guest-binaries fails 26 of them, in a guest-only build that compiles none of the changed files
macOS check, python3 scripts/public_api.py, runtime adapter guest-only, guest binaries build and tests Passed (Linux)
New tests launched_waits_end_once_their_process_exits fails when the exit check always finds OpenVMM exited (the wait ends while it runs) or never does (the wait lasts its 60 s timeout); a_missing_curl_reports_the_program_and_query_context fails when the spawn's error loses its context (code io, not unavailable)
check_test_layout.py, check_markdown_links.py; validate-nvx on Windows (compileall, the 22 scripts/test_*.py modules, 1834 tests, nvx.py help) Passed
nvx.py test-microvm --backend kvm (nvx-kvm-test, Azure Linux 3, virtual machine): managed-exec-config, sandbox-lifecycle, sandbox-proxy-only, sandbox-ram-upper, sandbox-blocks, managed-lifecycle, filesystem-comma-paths, l3-l4-egress-policy Passed
nvx.py test-microvm --backend whp (Windows 11 developer machine): the same but managed-lifecycle Passed
Managed nvx-rs run --cmdline "nvx_mode=managed nvx_identity=65534:65534", Ctrl-C after 15-20 s, KVM and WHP The control session attaches through the VM's process (the stop goes through it); status 130 and Interrupted
Managed run whose guest ends before its control endpoint (nvx_identity=1001:1001, which the guest lacks), KVM and WHP Ends at once with the guest's status 125, as at the base
nvx-rs download into a checkout of c3ff9d6cf527, KVM and WHP Installs v0.1.0-dev.c3ff9d6cf527 (271 and 142 MiB packages)
The same download, Ctrl-C once the package is arriving Status 130 and Interrupted; no staging directory, nothing installed, no curl left
The same download through an unreachable HTTPS_PROXY Status 1, error[unavailable]: GitHub release query failed: curl: (7) ...

The KVM runs used OpenVMM 14dc4116 and the guest kernel and initramfs built on the host from this branch, and the Ubuntu distro layer of CI run 42994995631 (a53a2626d6c, the same guest inputs); the WHP runs used that run's Windows OpenVMM and guest artifacts. On Windows, the base binary (1008a3e0056) gave the same results for the downloads, Ctrl-C included, and for both managed runs.

Observed at the base too, not changed here: a managed blockless run that Ctrl-C ends prints VM graceful stop failed: EndpointClosed and loses OpenVMM's outcome report, on KVM and WHP, with this branch's binary and the base's alike; it is recorded as a follow-up.

Left to CI: MSHV, and the remaining microVM scenarios.

Changes to existing tests (tests-modified)

check_test_changes.py --base origin/feature/nvx-refactor lists 11 changes, 11 added items, none relocated:

  • host::cli::download::archive::tests::archive (helper): spawns the tar that packages a test release with crate::host::tests::spawn_locked instead of supervisor::spawn_command, under the same lock over the process's creation.
  • host::cli::download::archive::tests::{gzipped_packages_extract_once_their_names_and_checksums_pass, zip_packages_extract_with_the_tar_of_windows, packages_whose_checksums_fail_are_refused, packages_with_symlinks_are_refused, archives_that_tar_cannot_read_are_refused}: each holds hold_spawns() while it runs, since extract runs tar without the lock now; their packages, calls and assertions do not change.
  • host::cli::download::github::tests::{queries_and_downloads_carry_the_token_to_the_origin_of_the_api_alone, requests_without_a_token_carry_none, redirects_resolve_relative_locations_against_the_urls_of_their_requests, the_last_response_to_a_retried_request_counts}: each holds hold_spawns() while it runs, since the client runs curl without the lock now; their servers, requests and assertions do not change.
  • host::tests::spawn_locked (helper): also builds with the cli feature on hosts other than Linux and Windows, since the archive tests, which macOS builds too, now use it; its body does not change.

Every other test of the area is kept unchanged, Curl::plain stays test-only in download/github/tests.rs, and download/tests.rs does not change. Added: host::tests::hold_spawns; download::github::tests::a_missing_curl_reports_the_program_and_query_context (the error of a curl that does not run, which the changed spawn produces); and session::tests::endpoints::launched_waits_end_once_their_process_exits on Linux and Windows, with its Sleeper helper and check_launched_waits_end_once_their_process_exits.

Case matrix. No row of doc/acceptance-case-matrix.md changes. The changed and added tests are unit tests of nvx, outside its inventory of adapter tests, CI steps, doctests and microVM scenarios. No row names them, their files or this slice as coverage. This PR renames, moves or deletes no test, and its files hold no doctest.

@github-actions

github-actions Bot commented Nov 11, 2026 •

Copy link
Copy Markdown
Contributor

This pull request changes existing tests

It modifies or removes 11 existing test asset(s), listed below. Review each with care: a change to a test can hide a regression that the test caught. See doc/contribute.md#changes-to-existing-tests.

Test code

Change What Before After
modified helper fn archive in host::cli::download::archive::tests nvx-rs/src/host/cli/download/archive/tests.rs:36 nvx-rs/src/host/cli/download/archive/tests.rs:42
modified helper fn spawn_locked in host::tests (moved, and it now compiles where any ( target_os = "linux" , windows , feature = "cli" ) and feature = "host" and test hold, not where any ( target_os = "linux" , windows ) and feature = "host" and test did) nvx-rs/src/host/tests.rs:36 nvx-rs/src/host/tests.rs:36
modified test fn gzipped_packages_extract_once_their_names_and_checksums_pass in host::cli::download::archive::tests nvx-rs/src/host/cli/download/archive/tests.rs:67 nvx-rs/src/host/cli/download/archive/tests.rs:74
modified test fn zip_packages_extract_with_the_tar_of_windows in host::cli::download::archive::tests nvx-rs/src/host/cli/download/archive/tests.rs:82 nvx-rs/src/host/cli/download/archive/tests.rs:90
modified test fn packages_whose_checksums_fail_are_refused in host::cli::download::archive::tests nvx-rs/src/host/cli/download/archive/tests.rs:93 nvx-rs/src/host/cli/download/archive/tests.rs:102
modified test fn packages_with_symlinks_are_refused in host::cli::download::archive::tests nvx-rs/src/host/cli/download/archive/tests.rs:122 nvx-rs/src/host/cli/download/archive/tests.rs:132
modified test fn archives_that_tar_cannot_read_are_refused in host::cli::download::archive::tests nvx-rs/src/host/cli/download/archive/tests.rs:135 nvx-rs/src/host/cli/download/archive/tests.rs:146
modified test fn queries_and_downloads_carry_the_token_to_the_origin_of_the_api_alone in host::cli::download::github::tests nvx-rs/src/host/cli/download/github/tests.rs:136 nvx-rs/src/host/cli/download/github/tests.rs:142
modified test fn requests_without_a_token_carry_none in host::cli::download::github::tests nvx-rs/src/host/cli/download/github/tests.rs:255 nvx-rs/src/host/cli/download/github/tests.rs:262
modified test fn redirects_resolve_relative_locations_against_the_urls_of_their_requests in host::cli::download::github::tests nvx-rs/src/host/cli/download/github/tests.rs:277 nvx-rs/src/host/cli/download/github/tests.rs:285
modified test fn the_last_response_to_a_retried_request_counts in host::cli::download::github::tests nvx-rs/src/host/cli/download/github/tests.rs:331 nvx-rs/src/host/cli/download/github/tests.rs:340

Relocated unchanged: 0; added: 11. Compared the merge base 1b902ac16a58 with 6262d1453677.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Critical shutdown and image-handoff races can kill OpenVMM during guest filesystem flushing or bypass checked-inode locks through pathname replacement, allowing concurrent image writes.

2 open issues
What changed in this PR

This PR advances #1112 by moving CLI downloads and control-session attachment off the legacy supervisor. The supplied changes also include the stacked #1490 migration of sandbox run to the managed VM API.

Changes:

  • Spawn download tools directly and attach cold-run sessions through verified process references.
  • Run sandbox workloads through managed sessions, with shutdown, cleanup, and protected-path checks.
  • Update tests, CI scenarios, and documentation for the managed sandbox behavior.
File Description
scripts/​test-layout-allowlist.json Removes obsolete test-layout allowance.
scripts/​test_microvm_tests.py Updates sandbox acceptance fixtures and regression cases.
scripts/​nvx_tools/​sandbox_ram_upper_tests.py Checks managed RAM-upper runs and forwarded reports.
scripts/​nvx_tools/​sandbox_lifecycle_tests.py Updates unavailable-identity expectations.
nvx-rs/​tests/​cli/​validation.rs Adjusts execution-option validation coverage.
nvx-rs/​tests/​cli/​sandbox_run.rs Adds managed sandbox-run integration coverage.
nvx-rs/​tests/​cli/​openvmm.rs Adds managed-run expectations and output normalization.
nvx-rs/​tests/​cli/​one_shot.rs Removes superseded sandbox one-shot cases.
nvx-rs/​tests/​cli/​lifecycle.rs Adds protected-path tests and shared helpers.
nvx-rs/​tests/​cli/​dry_run.rs Updates expected managed sandbox commands.
nvx-rs/​tests/​cli/​download.rs Adapts download and provisioning tests.
nvx-rs/​tests/​cli.rs Registers run tests and updates lifecycle expectations.
nvx-rs/​src/​host/​vm.rs Makes foreground child access private.
nvx-rs/​src/​host/​tests.rs Adds a test-only spawn-lock guard.
nvx-rs/​src/​host/​sandbox/​tests/​specification.rs Replaces boot-token expectations with mapping tables.
nvx-rs/​src/​host/​sandbox/​tests.rs Updates sandbox launch-contract tests.
nvx-rs/​src/​host/​sandbox/​prepared.rs Removes private one-shot preparation.
nvx-rs/​src/​host/​sandbox/​plan/​tests.rs Removes obsolete one-shot rendering checks.
nvx-rs/​src/​host/​sandbox/​plan.rs Removes private boot-token delivery paths.
nvx-rs/​src/​host/​sandbox/​options/​tests.rs Tests run execution options and managed shares.
nvx-rs/​src/​host/​sandbox/​options.rs Allows execution options for sandbox runs.
nvx-rs/​src/​host/​cli/​session/​tests.rs Tests attachment waits against a real process.
nvx-rs/​src/​host/​cli/​session.rs Uses verified processes and extracts request validation.
nvx-rs/​src/​host/​cli/​sandbox/​tests.rs Updates option tests and relocates overlap coverage.
nvx-rs/​src/​host/​cli/​sandbox/​options.rs Updates help for managed run options.
nvx-rs/​src/​host/​cli/​sandbox/​mod.rs Routes managed runs and checks protected paths.
nvx-rs/​src/​host/​cli/​sandbox/​lifecycle/​start/​tests.rs Checks share overlap before launch.
nvx-rs/​src/​host/​cli/​sandbox/​lifecycle/​start.rs Implements temporary managed workload runs.
nvx-rs/​src/​host/​cli/​sandbox/​lifecycle/​mod.rs Exposes the managed run implementation.
nvx-rs/​src/​host/​cli/​run.rs Passes the verified process to session attachment.
nvx-rs/​src/​host/​cli/​download/​github/​tests.rs Locks tool spawns and checks missing-curl diagnostics.
nvx-rs/​src/​host/​cli/​download/​github.rs Spawns curl directly.
nvx-rs/​src/​host/​cli/​download/​git.rs Spawns git directly.
nvx-rs/​src/​host/​cli/​download/​archive/​tests.rs Protects archive tool spawns in concurrent tests.
nvx-rs/​src/​host/​cli/​download/​archive.rs Spawns tar directly.
nvx-rs/​README.md Documents managed sandbox runs and path protections.
doc/​usage.md Updates sandbox options and execution behavior.
doc/​ci.md Describes managed smoke-test expectations.
.github/​workflows/​run-nvx-microvm-tests.yml Adapts sandbox probes to nvx-init execution.

Review effort: Balanced

Enrique Saurez and others added 2 commits November 11, 2026 16:39
The download started its tools with supervisor::spawn_command, which
holds the old supervisor's spawn lock while it creates a process, so
that on Windows the tool inherits no handle of a VMM that another
thread of the process starts meanwhile (#966). The CLI is to stop using
the old supervisor, and needs no lock for its tools: run, sandbox run
and sandbox start download before they launch, and no CLI command
starts a VM on another thread while it downloads.

tar, git and curl now start with Command::spawn, with the same standard
streams, environment and working directory as before. Their errors stay
as they were: a tool that does not run fails the download as
unavailable, naming the program; a tool that fails reports its standard
error; and the CLI checks for Ctrl-C once a tool exits, which reaches
the tools as before, since they still share the CLI's console and
process group.

The library's tests, though, run the download beside tests that launch
stand-in VMs, so #966's protection moves into them: each archive and
GitHub test that runs tar or curl holds the tests' spawn lock, which
host::tests::hold_spawns returns, while it runs them, and the archive
tests' helper that packages a release spawns its own tar with
host::tests::spawn_locked, which therefore builds wherever the CLI
does. A new GitHub test checks the error of a curl that does not run,
which the changed spawn produces.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
A managed cold run waits for its control endpoint with
Session::connect_launched_cancellable, which ends the wait once OpenVMM
exits, rather than at its timeout. It took the old supervisor's child
type, spawn::Launched, which host::vm::Launched lent it through
child_mut, and polled that child's status.

It now takes the verified process of the run's host::vm::Launched,
beside the endpoint and capability that the launch supplies, and asks
that process whether it has exited. The process is the same OpenVMM,
held through its pidfd or process handle, so the wait notices its exit
as the child's status did, without reaping it: the foreground still
reaps it through wait_status. A check that fails still keeps the wait
going until the timeout. child_mut becomes private to host::vm, whose
wait_status alone uses it, and the session's comments no longer name
host::instance. sandbox exec and sandbox stop attach through
Session::reattach, which verifies a saved VM through host::vm, and do
not change.

A new test gives the wait a process that runs and then exits: the wait
goes on while the process runs, and ends as a closed endpoint once it
exits, rather than at its timeout, on Linux and Windows.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@ppenna
Pedro Henrique Penna (ppenna) force-pushed the agents/slim-sl3-10a-downloads-session branch from 2c4bcc6 to 6262d14 Compare November 12, 2026 00:41
@ppenna
Pedro Henrique Penna (ppenna) marked this pull request as ready for review November 12, 2026 00:41
@ppenna
Pedro Henrique Penna (ppenna) merged commit 14ca267 into feature/nvx-refactor Nov 12, 2026
86 checks passed
@ppenna
Pedro Henrique Penna (ppenna) deleted the agents/slim-sl3-10a-downloads-session branch November 12, 2026 00:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feat-nvx-refactor tests-modified Changes existing tests, test data, or test wiring

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants