Skip to content

Ship nvx-rs in NVX releases - #598

Merged
Pedro Henrique Penna (ppenna) merged 8 commits into
devfrom
agents/fix-issue-359-release
Oct 19, 2026
Merged

Pedro Henrique Penna (ppenna) merged 8 commits into
devfrom
agents/fix-issue-359-release

Conversation

@ppenna

@ppenna Pedro Henrique Penna (ppenna) commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

Closes #359 (PR 5 of 5).

Summary

Every NVX release package now ships the nvx-rs CLI to which nvx.py sandbox and the egress and artifact steps of nvx.py run delegate since #597, and nvx.py download installs it where nvx.py finds it, so a downloaded checkout runs both commands without Cargo. The executable travels with a build provenance, as OpenVMM's does, which packaging, download, and the adversarial executor check.

  • Provenance. build/nvx-rs.provenance.json binds the executable's SHA-256 to a line-ending-independent digest of the files that build nvx-rs (Cargo.toml, Cargo.lock, build.rs, src/, and rust-toolchain.toml), the pinned Rust release, the target, and the cli feature. nvx.py records it after its own Cargo builds, and the new nvx.py record-nvx-rs-provenance [--target TARGET] records it for a build made otherwise.
  • Packaging. nvx.py package ships bin/nvx-rs[.exe] and provenance/nvx-rs.provenance.json, lists both in SHA256SUMS, and records an nvx_rs section in SOURCE-MANIFEST.json. It refuses an nvx-rs whose provenance does not describe it as the build of the checkout's sources for the release target of the host's OS, and a Linux nvx-rs that is not static.
  • CI. The NVX CLI jobs record the provenance of the nvx-rs that they build and test, and upload it beside the executable. The platform jobs hand both to package-release. The platform jobs now also package and archive on pull requests, without uploading, so this PR's platform jobs exercise packaging; before, only dev pushes did. The development release job refuses packages without bin/openvmm and bin/nvx-rs.
  • Download. download verifies SHA256SUMS, then installs bin/nvx-rs[.exe] at nvx-rs/target/release/nvx-rs[.exe], executable, with its provenance in build/, when the provenance describes the build of the checkout's nvx-rs sources that nvx.py runs. Otherwise it installs the rest and leaves nvx-rs to Cargo.
  • Adversarial executor. As a follow-up that Make nvx-rs the backend of nvx.py sandbox, egress, and artifact lookup #597 flagged, the executor's runtime validation and artifact metadata now cover the nvx-rs that the scenarios run, so a stale nvx-rs/target/release/nvx-rs fails the initialization instead of running.
  • Notices. THIRD_PARTY_NOTICES.md covers the crates, the musl, LLVM libunwind, and Microsoft C runtime code that the shipped executables link.

Package changes

Path linux-kvm, linux-mshv windows-whp
bin/nvx-rs[.exe] (new) bin/nvx-rs: static x86_64-unknown-linux-musl executable, mode 0755 bin/nvx-rs.exe: x86_64-pc-windows-msvc, static C runtime
provenance/nvx-rs.provenance.json (new) format, source_sha256, toolchain (1.95.0), target, features (["cli"]), executable_sha256 the same
SOURCE-MANIFEST.json new nvx_rs section with the executable's SHA-256, the source digest, toolchain, target, and features the same
SHA256SUMS lists both new files: 22 entries in a binary-only package instead of 20 the same

Choices:

  • Older releases stay installable. A release without bin/nvx-rs, or whose nvx-rs was built from other nvx-rs sources (a first-parent fallback release), still installs everything else; download then removes any earlier nvx-rs and its provenance, says why, and nvx.py builds nvx-rs with Cargo when a command needs it, as before this PR. A package whose nvx-rs contradicts its own provenance is broken and fails the download before anything is replaced.
  • The bundled pin stays. The bundled build script extracts and checks only OpenVMM, the Alpine guest, and the manifest of a release, so a release with bin/nvx-rs stages as the pinned older one does. The synthetic release of the check-nvx-rs action now has the current layout, and the pinned release still stages and tests.
  • Source-inclusive packages ship nvx-rs too; their project source archive already holds its sources.

Stack

Rebased onto latest origin/dev at cee5471f0256289e04ca2c01052f38afb7898669 after #561, #594, #595, #596, and #597 merged. Only this PR's eight release-specific commits remain. The Ubuntu converter-order fix is already upstream and was dropped during the rebase.

This PR's commits:

  1. scripts: record the provenance of nvx-rs
  2. release: package nvx-rs with its provenance
  3. notices: cover the nvx-rs binary
  4. ci: package releases on pull requests
  5. release: install nvx-rs with nvx.py download
  6. adversarial: check the nvx-rs that the scenarios run
  7. nvx-rs: count a VM as running until its last thread exits (review)
  8. adversarial: hand the scenarios the nvx-rs that the executor checked (review)

Commits 2, 6, and 8 extend the artifact handoff and adversarial executor from #597. Commit 7 fixes the Linux identity check introduced by #596. Fresh review corrections were folded into the owning original commits: commit 5 checks executable/provenance consistency before fallback compatibility, and commit 6 aligns the controller's nine-entry artifact evidence schema with the executor. Both corrections have regressions demonstrated failing before and passing after the fixes; their review threads are resolved.

#359 acceptance criteria

Criterion Evidence
nvx.py sandbox …, and the egress and artifact steps of nvx.py run, go through nvx-rs #594 (egress compiler), #595 (launch profile and artifact lookup), #596 (the CLI), and #597 (the delegation, with its hardware runs). Here, a released nvx-rs ran sandbox run and run's artifact and egress steps on KVM, MSHV, and WHP without Cargo
Python duplicates no control-protocol, lifecycle, or egress logic #597 deleted control_session.py, egress_policy.py, sandbox_lifecycle.py, and the launch profile of sandbox.py, with their tests ported to Rust
Releases ship nvx-rs, and nvx.py download installs it This PR: the package contents, SHA256SUMS, manifest, and provenance (commit 3), the publication check (3), the installer (6), their tests in test_nvx_tools.py and test_development_release.py, and the hardware runs below. This PR's platform jobs package each platform
The managed exec tests pass on KVM, MSHV, and WHP #597: managed-exec-config, sandbox-lifecycle, and managed-lifecycle on prometheus32, prometheus30, and prometheus28, and in CI's microVM jobs, which run again on this PR

Rebase validation (2026-10-19)

Rebased head: fdeb9592765db3d2f5e9f7b57c475bfc8e26636c.

  • Windows: repository verification; Ruff lint and format; Pyright for Linux and Windows; Markdown links; compileall; the full CLI suite (1,000 tests, 35 platform skips); adversarial suite (74 tests, 6 platform skips); host tests (4); all CLI help checks; PowerShell syntax.
  • Both Rust crates: format, all-feature and no-default-feature Clippy, all-feature and default tests, warning-free documentation, Rust 1.89 minimum-version checks. Synthetic current-layout nvx-rs release staging (15 tests) and edge-agent bundle staging/rejection checks also passed.
  • Ubuntu WSL: the Linux-only Specula CI-wiring suite (25 tests). Native Windows collection correctly lacks Unix fcntl; the suite passed on Linux instead.
  • Both fresh review findings were reproduced, fixed, and folded into their original commits; the complete 1,000-test CLI and 74-test adversarial suites passed again. Upstream then advanced only by its automated performance-baseline update; non-data files are identical to the tested tree, and all 83 performance unit tests passed on the final base.
  • Fresh exact-head CI: run 39312377065, attempt 1, passed for fdeb9592765db3d2f5e9f7b57c475bfc8e26636c. Linux/KVM, Linux/MSHV, Windows/WHP, platform packaging, OpenVMM tests, microVM tests, the performance regression gate, and the required status check all passed. Only intentionally skipped jobs remain; PR CI did not publish a development release or persist performance history.
  • The fresh review on the final head reports no open issues. All four review threads are resolved. Both local worktrees are clean, the OpenVMM gitlink is unchanged, and the final head still contains the latest origin/dev (cee5471f0256289e04ca2c01052f38afb7898669).

Original validation

Environment Evidence
Windows 11 (local, Python 3.14) nvx.py verify; nvx-rs: fmt, clippy (all features, and lib without defaults), tests (all features and default), rustdoc with -D warnings, 1.89 check, aarch64-apple-darwin check, and the bundled-artifacts steps with the synthetic current-layout release and the pinned release; aci_edge_sandboxes: the commands of check-aci-edge-sandboxes and test-agent-bundling; Ruff, Ruff format, Pyright (Linux and Windows), the Markdown link check, compileall, the validate-nvx unittest list (996 tests), test_adversarial.py (73), test_hosts.py, and the CLI help commands
Each commit (local) Ruff, Pyright, the link check, test_nvx_tools.py, test_development_release.py, test_markdown_links.py, and test_adversarial.py where it changes
Ubuntu on WSL the validate-nvx list on Python 3.14, 3.12, and 3.10 (996 tests each), test_hosts.py, the Specula wiring tests, and actionlint 1.7.7 (only the pre-existing nvx-adversarial-controller label note). test_adversarial.py passes on 3.14 and 3.12, and on 3.10 fails only test_contained_native_process_preserves_child_signal_status (sigkill), as on origin/dev
Windows 11 (local), packaging package --binary-only and archive-release with #597's CI guest artifacts and OpenVMM, then an install with download's installer into a scratch clone: sandbox run --dry-run and run --dry-run with an egress policy file, without Cargo and with NVX_RS unset
prometheus32, Linux/KVM, bare metal from this branch: nvx-rs built for x86_64-unknown-linux-musl as CI does (static-pie), its provenance recorded, a linux-kvm binary-only package with the host's OpenVMM at 5ab8a84 and #597's CI guest artifacts, archived (269 MB); sha256sum -c SHA256SUMS passes; the extracted release's bin/nvx-rs artifacts finds the release layout; installed into a scratch checkout with download's installer, which verifies SHA256SUMS and the provenance. Without Cargo and with NVX_RS unset: sandbox run --dry-run and a real sandbox run of /bin/echo (hello-from-a-release, outcome success, complete teardown), the egress policy file through sandbox run and run, whose dry run also finds the kernel and initramfs; nothing rebuilt nvx-rs
prometheus30, Linux/MSHV, bare metal the same with a linux-mshv package (268 MB): all pass
prometheus28, Windows/WHP, bare metal the same with nvx-rs.exe built with a static C runtime as CI does, and a windows-whp package (151 MB, 22 SHA256SUMS entries, no mismatches): all pass
Review fixes (commits 8 and 9) Windows: nvx-rs clippy (all features, and lib without defaults), tests, rustdoc with -D warnings, 1.89 and aarch64-apple-darwin checks; Ruff, Pyright, test_adversarial.py (74). WSL: nvx-rs fmt, clippy, all tests, including the new Linux test a_vm_runs_until_its_last_thread_exits, which fails without the fix, and the 1.89 check; test_adversarial.py on 3.14 and 3.12 (74), and on 3.10 with only the known sigkill failure. The new executor test fails without its fix
Left to CI the packaging of all three platforms in this PR's platform jobs, the microVM suites, the guest artifact rebuild, the OpenVMM tests, and the benchmarks. The release upload and publication run only on dev pushes

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

Executable path consistency and thread-aware process liveness need correction before approval.

2 open issues
What changed in this PR

Ships nvx-rs with NVX releases, completing the release-delivery portion of #359 so compatible downloads can run delegated commands without Cargo.

Changes:

  • Adds executable provenance, packaging, installation, and publication checks.
  • Passes nvx-rs artifacts through CI and validates them in adversarial execution.
  • Includes the stacked Rust CLI, guest-build migration, tests, and documentation updates.
File Description
SOURCE-MANIFEST.json Records Rust guest toolchain and sources.
scripts/​test_tool_versions.py Checks musl target setup.
scripts/​test_guest_binaries.py Tests source identities and staged builds.
scripts/​test_development_release.py Tests required release executables.
scripts/​test_adversarial.py Tests executable discovery and metadata.
scripts/​setup/​setup-linux-runner.sh Explicitly selects the target toolchain.
scripts/​setup/​setup-linux-mshv.sh Installs and verifies musl support.
scripts/​publish_development_release.py Checks packages before publication.
scripts/​nvx_tools/​ubuntu.py Normalizes and orders build inputs.
scripts/​nvx_tools/​sandbox_lifecycle_tests.py Updates lifecycle and teardown checks.
scripts/​nvx_tools/​microvm_test_scripts/​sandbox-blocks.sh Tests distro-only block layout.
scripts/​nvx_tools/​guest_binaries.py Validates staged Rust guest binaries.
scripts/​nvx_tools/​ci.py Tracks Rust guest build dependencies.
scripts/​nvx_tools/​build.py Installs shared Rust guest binaries.
scripts/​nvx_tools/​build_constants.py Defines Rust build and source packaging inputs.
scripts/​nvx_tools/​benchmark.py Records Rust guest provenance.
scripts/​nvx_tools/​azurelinux.py Includes normalized Rust build inputs.
scripts/​nvx_tools/​archive.py Excludes generated Rust build output.
scripts/​nvx_tools/​adversarial_executor.py Validates and records nvx-rs.
README.md Explains prebuilt CLI availability.
nvx-rs/​src/​protocol/​mod.rs Updates shared-protocol documentation.
nvx-rs/​src/​protocol/​features.rs Defines layered sandbox requirements.
nvx-rs/​src/​host/​sys/​mod.rs Exposes interrupt handling.
nvx-rs/​src/​host/​sys/​interrupt.rs Records Unix interrupts.
nvx-rs/​src/​host/​session.rs Exposes CLI request and response methods.
nvx-rs/​src/​host/​sandbox/​layer.rs Defines distro-layer parsing and validation.
nvx-rs/​src/​host/​python/​uuid.rs Implements Python-compatible UUID parsing.
nvx-rs/​src/​host/​python/​shell.rs Implements command display quoting.
nvx-rs/​src/​host/​python/​mod.rs Organizes Python compatibility helpers.
nvx-rs/​src/​host/​python/​dumps.rs Implements compatible JSON formatting.
nvx-rs/​src/​host/​process.rs Adds CLI process-control methods.
nvx-rs/​src/​host/​platform/​windows.rs Adds Windows CLI primitives.
nvx-rs/​src/​host/​platform/​other.rs Provides interrupt compatibility hooks.
nvx-rs/​src/​host/​platform/​linux.rs Exposes Linux interrupt hooks.
nvx-rs/​src/​host/​mod.rs Exposes sandbox and CLI modules.
nvx-rs/​src/​host/​cli/​sandbox/​identity.rs Checks sandbox process identity.
nvx-rs/​src/​host/​cli/​output.rs Implements CLI stream output.
nvx-rs/​src/​host/​cli/​egress.rs Adds egress compilation commands.
nvx-rs/​src/​host/​cli/​deadline.rs Adds interruptible deadline handling.
nvx-rs/​src/​host/​cli/​artifacts.rs Locates and reports runtime artifacts.
nvx-rs/​src/​guest/​time/​mod.rs Updates guest-time documentation.
nvx-rs/​src/​guest/​time_probe/​mod.rs Updates probe documentation.
nvx-rs/​src/​guest/​reseed.rs Removes obsolete C references in comments.
nvx-rs/​src/​bin/​nvx-rs.rs Adds the CLI entry point.
nvx-rs/​Cargo.toml Declares CLI features and dependencies.
guest/​common/​nvx-reseed.c Removes the replaced C implementation.
guest/​common/​nvx-mmio-write.c Removes the replaced C implementation.
guest/​common/​nvx-init-agent Restricts layers and reserves runtime paths.
guest/​common/​nvx-exit.c Removes the replaced C implementation.
guest/​common/​nvx-console-pending.c Removes the replaced C implementation.
docker/​Dockerfile Shares Rust binaries across guest builds.
doc/​setup.md Documents Rust build prerequisites.
doc/​run.md Documents delegated sandbox execution.
doc/​project-structure.md Describes the Rust-backed layout.
doc/​guests/​ubuntu-guest.md Documents Rust guest binaries.
doc/​guests/​azurelinux-guest.md Documents guest binary provenance.
doc/​distribution.md Documents CLI packaging and installation.
doc/​contribute.md Clarifies Rust ownership and testing.
doc/​ci.md Documents artifact handoff and release checks.
doc/​build.md Documents build provenance and package layout.
doc/​benchmarks.md Documents Rust benchmark provenance.
doc/​adversarial-testing.md Documents target CLI requirements.
aci_edge_sandboxes/​src/​openvmm/​mod.rs Updates shared egress documentation and tests.
aci_edge_sandboxes/​README.md Documents egress behavior changes.
.github/​workflows/​run-platform.yml Packages on PRs without publishing.
.github/​workflows/​run-nvx-microvm-tests.yml Downloads the CLI for scenarios.
.github/​workflows/​copilot-setup-steps.yml Tracks all Rust source changes.
.github/​copilot-instructions.md Updates build and runtime guidance.
.github/​actions/​validate-nvx/​action.yml Removes replaced Python test suites.
.github/​actions/​package-release/​action.yml Downloads CLI artifacts and gates uploads.
.github/​actions/​check-nvx-rs/​action.yml Updates the synthetic release layout.
.github/​actions/​build-guest-artifacts/​action.yml Keys caches on Rust source identity.
.gitattributes Marks frozen compatibility fixtures generated.

Review effort: Balanced

Comment thread scripts/nvx_tools/adversarial_executor.py
Comment thread nvx-rs/src/host/cli/sandbox/identity.rs Outdated
Pedro Henrique Penna (ppenna) added a commit that referenced this pull request Oct 17, 2026
nvx-rs sandbox identifies the OpenVMM of a running sandbox by its
process ID and start time, which identity.rs reads on Linux from
/proc/PID/stat. Ported from the former sandbox_lifecycle.py, it counted
every zombie as exited. But /proc reports a process as a zombie as soon
as its main thread exits, while its other threads may still run and
hold its descriptors, so exec and stop could reject a live VM as stale,
and deprovision could remove the state of a VM that still runs. The
library's platform::process_start_time, with which the process helpers
wait for and kill OpenVMM, already counts such a process as running
until its last thread exits, as Windows does, and so does the CLI's own
Windows branch; only its Linux branch disagreed.

linux_start_time now asks, for a zombie only, whether other threads
remain, through the platform's other_threads_remain, which reads
/proc/PID/task, and otherwise keeps its Python-compatible parsing and
messages. other_threads_remain becomes pub(crate) for that.

The stat-line test covers a zombie with and without other threads, and
an error while listing them. A new Linux test, like
process::tests::a_process_runs_until_its_last_thread_exits, makes the
main thread of a Python process exit through pthread_exit while another
thread sleeps: process_running holds until the process is killed. It
fails without the fix.

Copilot's review of #598 flagged this in code that #596 added, the
nvx-rs CLI; the platform helper is already on dev. The change stays
within the CLI's Linux identity check.

Part of #359.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pedro Henrique Penna (ppenna) added a commit that referenced this pull request Oct 17, 2026
The executor validates and hashes the nvx-rs that NVX_RS names before
the baseline boot, and nvx.py resolves a relative NVX_RS against its
working directory: the executor's, at that point. The scenarios inherit
NVX_RS unchanged but run in the repository root, so with a relative
NVX_RS they ran another executable than the one validated, or found
none.

sanitized_environment() now makes NVX_RS absolute against the
executor's working directory, so that the scenarios run the executable
that the initialization checked. An empty NVX_RS, which nvx.py
ignores, passes unchanged.

A new test validates a relative NVX_RS in one working directory and
resolves it in a scenario process in another; it fails without the
fix. The environment test now expects the absolute path.

Copilot's review of #598 flagged this. #597 added NVX_RS to the
scenarios' environment, and the earlier commit of this series that
validates the executor's nvx-rs made the mismatch matter.

Closes #359.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings October 17, 2026 11:53

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.

🔵 Needs a closer look

Cross-platform release, provenance, and lifecycle changes need final human review and confirmation of platform CI results.

No open issues 🎉

2 resolved since last review

Review effort: Balanced

Copilot AI balanced review requested due to automatic review settings October 19, 2026 19:14
Pedro Henrique Penna (ppenna) added a commit that referenced this pull request Oct 19, 2026
nvx-rs sandbox identifies the OpenVMM of a running sandbox by its
process ID and start time, which identity.rs reads on Linux from
/proc/PID/stat. Ported from the former sandbox_lifecycle.py, it counted
every zombie as exited. But /proc reports a process as a zombie as soon
as its main thread exits, while its other threads may still run and
hold its descriptors, so exec and stop could reject a live VM as stale,
and deprovision could remove the state of a VM that still runs. The
library's platform::process_start_time, with which the process helpers
wait for and kill OpenVMM, already counts such a process as running
until its last thread exits, as Windows does, and so does the CLI's own
Windows branch; only its Linux branch disagreed.

linux_start_time now asks, for a zombie only, whether other threads
remain, through the platform's other_threads_remain, which reads
/proc/PID/task, and otherwise keeps its Python-compatible parsing and
messages. other_threads_remain becomes pub(crate) for that.

The stat-line test covers a zombie with and without other threads, and
an error while listing them. A new Linux test, like
process::tests::a_process_runs_until_its_last_thread_exits, makes the
main thread of a Python process exit through pthread_exit while another
thread sleeps: process_running holds until the process is killed. It
fails without the fix.

Copilot's review of #598 flagged this in code that #596 added, the
nvx-rs CLI; the platform helper is already on dev. The change stays
within the CLI's Linux identity check.

Part of #359.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pedro Henrique Penna (ppenna) added a commit that referenced this pull request Oct 19, 2026
The executor validates and hashes the nvx-rs that NVX_RS names before
the baseline boot, and nvx.py resolves a relative NVX_RS against its
working directory: the executor's, at that point. The scenarios inherit
NVX_RS unchanged but run in the repository root, so with a relative
NVX_RS they ran another executable than the one validated, or found
none.

sanitized_environment() now makes NVX_RS absolute against the
executor's working directory, so that the scenarios run the executable
that the initialization checked. An empty NVX_RS, which nvx.py
ignores, passes unchanged.

A new test validates a relative NVX_RS in one working directory and
resolves it in a scenario process in another; it fails without the
fix. The environment test now expects the absolute path.

Copilot's review of #598 flagged this. #597 added NVX_RS to the
scenarios' environment, and the earlier commit of this series that
validates the executor's nvx-rs made the mismatch matter.

Closes #359.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

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

The controller rejects expanded executor metadata, and incompatible fallback releases bypass executable-provenance consistency checks.

2 open issues

Review effort: Balanced

Comment thread scripts/nvx_tools/adversarial_executor.py
Comment thread scripts/nvx_tools/release.py Outdated
nvx.py runs the nvx-rs that NVX_RS names or else the one in
nvx-rs/target/release, and never rebuilds one that exists, so nothing
tells whether that executable is a build of the checkout's nvx-rs
sources. OpenVMM has the same problem and solves it with
build/openvmm.provenance.json, which binds the digest of its binary to
its gitlink revision. Releases are about to ship nvx-rs, and packaging
and `nvx.py download` need the same binding for it.

build/nvx-rs.provenance.json now records, for the nvx-rs that nvx.py
runs:

- source_sha256: a digest of the files that build nvx-rs, the crate's
  Cargo.toml, Cargo.lock, build.rs, and src/ tree, and
  rust-toolchain.toml, by path and with line endings normalized, so
  that LF and CRLF checkouts of the same sources agree. The tests,
  README.md, and artifacts.json build nothing, and stay out;
- toolchain: the Rust release that rust-toolchain.toml pins;
- target: the Rust target of the build;
- features: ["cli"], which BUILD_COMMAND builds;
- executable_sha256: the digest of the executable.

nvx.py records it after each Cargo build that it makes, for the host
target that `rustc -vV` reports. The new `nvx.py
record-nvx-rs-provenance [--target TARGET]` command records it for a
build made otherwise, of the executable that NVX_RS names or else of
nvx-rs/target/release/nvx-rs[.exe]. The caller vouches that this
checkout built that executable for that target; the record lets later
steps check the claim against the sources. RELEASE_TARGETS names the
targets that the NVX CLI jobs build for releases,
x86_64-unknown-linux-musl and x86_64-pc-windows-msvc, and a record for
the musl target requires a static ELF executable, since a build for the
host's own Linux target is dynamic.

The new constants live in nvx_tools/nvx_rs.py, which no guest artifact
digests, so the existing guest provenance stays valid.

doc/usage.md documents the command and the record, doc/build.md the
file beside the other provenance sidecars, nvx-rs/README.md who
records it, and doc/ci.md that the Copilot setup's first nvx-rs build
records it too.

Part of #359.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
#359 makes nvx-rs the backend of nvx.py sandbox and of the egress and
artifact steps of nvx.py run, so a release that nvx.py download
installs is of little use without it. Every package now ships the
release build of nvx-rs beside OpenVMM:

- bin/nvx-rs in the Linux packages, the static
  x86_64-unknown-linux-musl executable that runs on every x86-64 Linux
  host, and bin/nvx-rs.exe in the Windows package, the
  x86_64-pc-windows-msvc executable with a static C runtime;
- provenance/nvx-rs.provenance.json, its build provenance;
- an nvx_rs section in SOURCE-MANIFEST.json with the executable's
  digest, the source digest, the Rust release, the target, and the
  features;
- both files in SHA256SUMS, as every packaged file is.

`nvx.py package` takes them from nvx-rs/target/release and build/, as
it takes OpenVMM, and refuses a provenance that does not describe the
executable as a build of the checkout's nvx-rs sources, with its Rust
release and features, for the release target of the host's OS, and a
Linux executable that is not static. It checks the staged copies
against the provenance, as it does for OpenVMM. Source-inclusive
packages ship nvx-rs too; their project source archive already holds
its sources.

CI provides the build. Each NVX CLI job now records the provenance of
the nvx-rs that it built and tested, with --target, and uploads it as
nvx-rs-linux-musl-provenance or nvx-rs-windows-msvc-provenance beside
the executable. The workflows now name the pair by its base name, as
they name the OpenVMM artifacts, so run-nvx-microvm-tests.yml appends
-executable itself. run-platform.yml takes the base name and passes it
to the package-release action, which downloads both into place before
it packages. The platform jobs already wait for their CLI job.

Before it publishes, publish_development_release.py checks, from each
archive's member list, that every platform's package ships bin/openvmm
and bin/nvx-rs, or their .exe forms, so that a development release
without them never goes out.

The bundled feature of nvx-rs extracts and checks only OpenVMM, the
Alpine guest, and SOURCE-MANIFEST.json of a release, so a release with
bin/nvx-rs stages as the pinned older release does, and the pin stays.
The synthetic release of the check-nvx-rs action now has the current
layout, with nvx-rs and its provenance in the bundle and in its
SHA256SUMS.

doc/distribution.md, doc/build.md, doc/usage.md, doc/ci.md, and
nvx-rs/README.md describe the package contents and the handoff.

Part of #359.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Packages now ship bin/nvx-rs[.exe], which statically links the Rust
standard library, the crates of the nvx-rs cli feature, and a C
runtime, so THIRD_PARTY_NOTICES.md, which every package ships, must
cover them, as it has covered the Rust guest binaries since #561.

The crate rows follow `cargo tree --locked --features cli -e normal`
in nvx-rs for both release targets, x86_64-unknown-linux-musl and
x86_64-pc-windows-msvc: 26 crates, with the licenses that
`cargo metadata` reports.

- The rows of the crates that the guest binaries already link, the
  Rust standard library, compiler_builtins, libc, nix, getrandom,
  bitflags, cfg-if, and memoffset, now name bin/nvx-rs too.
- New rows cover serde, serde_core, serde_json and its itoa, memchr,
  and zmij, sha2 and its RustCrypto dependencies, and windows-sys and
  windows-link on Windows. serde_derive and the proc-macro2, quote,
  syn, and unicode-ident crates that it runs on are marked as build
  time only, not linked.
- The musl row now includes the bin/nvx-rs of the linux-kvm and
  linux-mshv packages, under the musl terms that it already
  documents.
- A new LLVM libunwind row covers the unwinder that the Rust musl
  target links into the guest binaries and the Linux nvx-rs, under
  Apache-2.0 WITH LLVM-exception, which lifts the attribution
  conditions for compiled code.
- A new Microsoft C runtime row covers the static CRT of
  bin/nvx-rs.exe, under the Visual Studio redistribution terms.

doc/distribution.md says the same in its licensing section. A test
parses nvx-rs/Cargo.lock, requires each of its crates to be classified
as shipped or not, and checks that every shipped crate has a row with
its exact version that names bin/nvx-rs, and that the runtime rows name
it too.

Part of #359.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The platform jobs packaged a release only on dev pushes, so a pull
request that broke packaging, as one that changes what a package ships
can, passed CI and failed the next development release instead. The
Windows packaging failure that the first commit of this series fixes
had gone unnoticed that way.

The Package release step of run-platform.yml now runs whenever the
platform packages releases, and tells the package-release action
through a new upload input whether to upload the archive: true only on
dev pushes. The action stages the package, which checks every input
against its provenance, and archives it, which checks the staged files
against SHA256SUMS, on every run, and uploads the archive for the
development release only when asked. Pull requests still publish
nothing; the release job runs only on dev pushes. Packaging adds about
a minute and a half to each Linux platform job and twenty seconds to
the Windows one.

doc/distribution.md describes it, and a test checks the conditions.

Part of #359.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
nvx.py download installed OpenVMM and the guest artifacts of a release,
so nvx.py sandbox and nvx.py run still needed Cargo to build nvx-rs
afterwards. It now installs the package's bin/nvx-rs[.exe] at
nvx-rs/target/release/nvx-rs[.exe], where nvx.py finds it, and its
provenance at build/nvx-rs.provenance.json, so that both commands run
without Cargo after a download.

nvx.py runs that executable without ever rebuilding it, and download
may fall back to the release of an earlier first-parent commit, so it
installs nvx-rs only when the package's provenance describes the build
of this checkout's nvx-rs sources that nvx.py runs: the same source
digest, Rust release, and features, for the release target of this OS.
Otherwise, as for a release from before packages shipped nvx-rs, it
installs everything else, removes any earlier nvx-rs and its
provenance, which may not match the checkout either, and says that
nvx.py builds nvx-rs with Cargo when a command needs it, unless NVX_RS
names one. Older releases thus stay installable. A package whose nvx-rs
does not match its own provenance is broken, and fails the download
before it replaces anything.

The installed OpenVMM and nvx-rs are now executable on POSIX whatever
mode the archive records.

The message that nvx.py prints without Cargo, the discovery notes in
nvx_rs.py, doc/usage.md, doc/setup.md, doc/distribution.md, README.md,
and nvx-rs/README.md describe the installed nvx-rs.

Part of #359.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The adversarial executor validates and hashes OpenVMM, the kernel, the
initramfs, and their provenance before the baseline boot, so that a
campaign runs what the staged commit built. Scenarios such as
l3-l4-egress-policy also run nvx-rs through nvx.py, which never
rebuilds an nvx-rs that exists, so one left from before a change of the
nvx-rs sources ran unchecked.

validate_runtime_artifact_provenance() now also requires the nvx-rs
that nvx.py runs, the one that NVX_RS names or else
nvx-rs/target/release/nvx-rs[.exe], with a build/nvx-rs.provenance.json
that describes it as a build of the checkout's nvx-rs sources, for any
target. The executor's artifact metadata hashes both, beside the other
artifacts. A stale, unrecorded, or missing nvx-rs now fails the
initialization instead of running.

Targets already had to build nvx-rs before exposure; the preparation
in doc/adversarial-testing.md now also records its provenance, which
nvx.py does itself for the builds that it makes, and a target with the
release of the staged commit gets both from nvx.py download. doc/ci.md
and doc/usage.md mention the check.

Closes #359.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
nvx-rs sandbox identifies the OpenVMM of a running sandbox by its
process ID and start time, which identity.rs reads on Linux from
/proc/PID/stat. Ported from the former sandbox_lifecycle.py, it counted
every zombie as exited. But /proc reports a process as a zombie as soon
as its main thread exits, while its other threads may still run and
hold its descriptors, so exec and stop could reject a live VM as stale,
and deprovision could remove the state of a VM that still runs. The
library's platform::process_start_time, with which the process helpers
wait for and kill OpenVMM, already counts such a process as running
until its last thread exits, as Windows does, and so does the CLI's own
Windows branch; only its Linux branch disagreed.

linux_start_time now asks, for a zombie only, whether other threads
remain, through the platform's other_threads_remain, which reads
/proc/PID/task, and otherwise keeps its Python-compatible parsing and
messages. other_threads_remain becomes pub(crate) for that.

The stat-line test covers a zombie with and without other threads, and
an error while listing them. A new Linux test, like
process::tests::a_process_runs_until_its_last_thread_exits, makes the
main thread of a Python process exit through pthread_exit while another
thread sleeps: process_running holds until the process is killed. It
fails without the fix.

Copilot's review of #598 flagged this in code that #596 added, the
nvx-rs CLI; the platform helper is already on dev. The change stays
within the CLI's Linux identity check.

Part of #359.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The executor validates and hashes the nvx-rs that NVX_RS names before
the baseline boot, and nvx.py resolves a relative NVX_RS against its
working directory: the executor's, at that point. The scenarios inherit
NVX_RS unchanged but run in the repository root, so with a relative
NVX_RS they ran another executable than the one validated, or found
none.

sanitized_environment() now makes NVX_RS absolute against the
executor's working directory, so that the scenarios run the executable
that the initialization checked. An empty NVX_RS, which nvx.py
ignores, passes unchanged.

A new test validates a relative NVX_RS in one working directory and
resolves it in a scenario process in another; it fails without the
fix. The environment test now expects the absolute path.

Copilot's review of #598 flagged this. #597 added NVX_RS to the
scenarios' environment, and the earlier commit of this series that
validates the executor's nvx-rs made the mismatch matter.

Closes #359.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings October 19, 2026 19:36

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.

🔵 Needs a closer look

Cross-platform release delivery and process-lifetime changes need human review with exact-head platform CI confirmation.

No open issues 🎉

2 resolved since last review

Review effort: Balanced

@ppenna
Pedro Henrique Penna (ppenna) merged commit db65371 into dev Oct 19, 2026
34 checks passed
@ppenna
Pedro Henrique Penna (ppenna) deleted the agents/fix-issue-359-release branch October 19, 2026 20:30
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.

nvx-rs: make the nvx-rs CLI the backend of nvx.py sandbox, egress, and artifact lookup

2 participants