Repository navigation
Description
Objective. Make nvx a library of mechanisms and value types for microVMs, which NVX's own binaries and downstream users build on, and release it as a breaking prerelease. Today nvx also keeps durable state (sandbox and template stores, leases, clone records and quarantine), NVX's CLI, an exec engine with the guest's process modules, the guest's root assembly, and NVX's own control protocol. After this workstream, nvx keeps the mechanisms that every user needs: a VM's configuration and launch, its snapshots and restores, its transport, and the guest's capture and activation. Each user keeps its own state and policy. NVX's CLI is the first such user, and NVX's Rust PID 1, nvx-init, the reference for a guest init. NVX's own programs stay in nvx, private to it: the public API holds only what users need. The release is not backward compatible: users that need alpha.7 stay on it.
Today (dev e42b1795, published as nvx 0.1.0-alpha.7) and after:
| Area | Today | After |
|---|---|---|
| State | nvx::host keeps a sandbox store, a template store, leases, capture transactions, clone records, failure records and quarantine, and reattaches through its store. |
No durable state. Each user writes its own store from values that nvx returns: the capability and outputs before a spawn, the process reference after it, and a saved VM to verify and reattach. The CLI writes NVX's. |
| Launch | LaunchSpec, SandboxPlan, SandboxLaunch, CloneRequest and ClonePlan, with separate paths for cold boots, templates and clones. |
One entry point, host::vm::create(&Runtime, &VmConfig, from: Option<&Snapshot>, &VmDir), in two phases: create, which a caller can inspect or hand to its own launcher, then spawn. It serves cold boots, captures and restores. |
| Snapshots | Templates and clones, with a template store and a compatibility key. | One Snapshot type with a strict directory format. A capture finishes into it, and the caller names and keeps its snapshots. |
| Restore | A blocking clone driver over NVX's control protocol. | A sans-I/O restore machine, which any caller drives with its own I/O and protocol. NVX's binaries use a blocking driver, private to nvx. |
| Exec | nvx::exec (the frames and their engine) and guest::{exec, spawn, process, cgroup, entry}, with a cgroup and namespaces per exec. |
No exec in nvx. Each init runs its executions itself. NVX publishes guidelines, and nvx-init runs each exec as a plain process in its own process group, with no cgroups, no namespaces and no limits. |
| Guest space | guest::{root, mappings} assemble the root, and each exec enters it with its own /proc, /sys and /dev. |
One space per VM, which the init builds and switches into once. It is the init's own code: the space that nvx-init uses, private to nvx, is NVX's reference, and the guidelines give its interface. nvx keeps the contracts that every space reads, and the account of the host's mapping table (MappingTable). |
| Guest capture | CapturePreparation, CaptureBarrier, an admission fence, and a public repair. |
One call, guest::snapshot::capture, which also repairs a restore. Each failure says whether the guest resumed or must end. |
| Protocols | NVXS, the codecs, and NVXC v1 with FEATURES, all in nvx. |
NVXS and the codecs stay in nvx. NVXC, NVX's own protocol, becomes private to nvx, then v2, which carries capture and restore and streams an exec's input and output. |
| Errors | One crate-wide Error, ErrorKind and ErrorCode. |
One public Error and ErrorKind, whose codes and constructors are private; the restore's, the guest's and the codecs' own errors stay. |
| Crate | nvx alone, with the bundled, cli and testing features. Its public API also holds NVX's programs, NVXC and the CLI's helpers. |
nvx alone. NVX's programs (the CLI, nvx-init, the managed agent, the guest tools and the fake OpenVMM), NVXC and the code that only they use are private to it; each binary calls a hidden entry point. A checked snapshot records the public API. bundled goes; cli and testing build NVX's own binaries. |
| Tests | A 44K-line test adapter (tests/runtime-adapter). |
Public-API tests, smoke tests on real VMs, and the CLI's cells on KVM, MSHV and WHP. |
Design.
- Mechanisms and value types.
nvxkeeps no durable state, runs no execution and builds no guest space. Its only lock is internal: the Windows spawn lock, private tohost::vm::spawn. It spawns no helper or workload; the one exception is the time daemon, whichTimeAbi::start_daemonforks in the guest. - What a user gets from
nvx:- a VM through one entry point: a cold boot, a cold boot that may be captured, or a restore (
host::vm::create); - one snapshot type with a strict directory format (
host::snapshot,host::config::CaptureConfig); - clear outputs for the files that a VM uses: the log, the outcome report, the control socket or pipe, and the console (
host::vm::{VmDir, VmOutputs}); - the transport, and reattaching to a running VM (
protocol::nvxs,host::transport,host::vm::{verify, reattach}); - activation after a restore (
activation,host::restore,guest::activation::Activation); - the contracts of the guest's space: the tokens of the root, the view, the export and the shares, the mapping table, and the activation record (
protocol::{root, view, export, share, maps},activation); - the guest's side of a snapshot, in one call that checks the devices, captures and repairs a restore (
guest::snapshot::capture); - one public error, whose kinds callers act on, policy description types, and one VM configuration for cold boots and restores (
Error,host::{network, filesystem, config}).
- a VM through one entry point: a cold boot, a cold boot that may be captured, or a restore (
- Described, not provided. The guest's space (the root, an image and its upper combined by overlayfs; its runtime filesystems; the switch into it; the view files and the host shares) and the executions are each init's own code.
doc/guest-agent-guidelines.mddescribes both, with the space's interface and rules, and nvx-init implements them. NVX enforces none of it. - One crate, with NVX's programs private in it.
nvxstays the only package, published as today. Its public API is the library: the featureshost(the default),guestandserde. NVX's own programs live in it, private: thenvx-rsCLI and its stores, locks, path rules, image staging and guest tokens; nvx-init, NVX's Rust PID 1 for layered roots, and the managed agent for blockless VMs, with their plain-process exec core; NVXC v2, NVX's own protocol, with the host's control session and a blocking restore driver; the guest helper binaries (nvx-time,nvx-time-probe,nvx-ingress,nvx-console-pending,nvx-mmio-write,nvx-exit,nvx-device-ioandnvx-reseed); the fake OpenVMM, for tests; and the OS layers. A binary can only call a public item, so each one calls amainthat#[doc(hidden)]hides: these entry points are the only public items that NVX's programs add. Thecliandtestingfeatures build the CLI and the fake OpenVMM. CI compares the public API, hidden items marked, with snapshots in the repository, so each PR shows its API change. Downstream users install the guest tools from the package:cargo install nvx --no-default-features --features guest --profile guest --bin nvx-ingress. Nightly publishing continues throughout: each nightly, then the release, takes the next prerelease number that crates.io hasn't used. - No backward compatibility. Nothing reads alpha.7's records, keys or snapshots: a deploy starts from fresh state, and snapshots are captured again.
The API-ready commit. Phases 1 and 2 check the public API, hide NVX's programs from it, and add the new interface. The first commit at which all their PRs have merged is the API-ready commit; no PR of phases 4 and 5 merges before it. That commit still has the old surfaces that a migration needs, but it is not source- or behavior-compatible with alpha.7: activation record v2, the RAM upper's cap (now in the root's tokens, in activation::Upper::Tmpfs and in the hidden guest::root::Upper::Tmpfs), Process::wait, Process::open, report::read, and activation without its fence and announcer need downstream changes. The guest's space is still there, #[doc(hidden)] and documented as unstable. A downstream user pins it to migrate, does its own cleanup there, then crosses the removals once, at a commit that contains SL6.8c. NVX's PRs never wait for a downstream PR.
PR rules. Each PR:
- has one goal, which its group issue states as what is true once it merges, and leaves
devgreen; - does what its Scope in the group issue says and no more: a case, input or layout that the Scope doesn't name, and whatever its "Not in this PR" lists, is out of scope;
- addresses every review comment within its scope, and asks before acting on one that asks for more;
- stops and asks before continuing once its measured core lines pass twice its estimate, or the 600-line limit if that comes first, unless it changes only code that leaves;
- changes at most 600 core lines, the non-blank lines of code that it adds or changes (comments, docs and tests don't count), and at most 5 counted files (a few may take 6 or 7, where a split would be harder to follow): the Rust files outside tests, build scripts, Cargo manifests, CI workflows and actions, and shell and Python scripts that it adds or edits (a file that it moves with only its import, visibility and link edits doesn't count, though those edited lines do; a file whose items it only makes crate-private or hidden, or gates by the feature that uses them, with the import edits that this forces, doesn't count, nor do those lines; nor do files that it only deletes, tests, docs, fixtures, lockfiles and generated files);
- keeps code where it is: code that only NVX's programs use becomes crate-private there, gated by the features of its users, and new code of a program goes under that program's module (
host::cli,guest::init). A file that does move, moves withgit mv, counting only the lines that it then edits; - adds no code without a production caller in the same PR, except a temporary public entry point whose docs name the PR that ends it, or crate-private code whose caller comes in a later PR, which carries
#[cfg_attr(not(test), expect(dead_code, reason = "…"))]naming that caller; nothing becomes public, even hidden, only to avoid that. Each PR passescargo clippy -p nvx --lib --no-default-features -- -D warningsbeside the crate's other checks; - keeps the public-API snapshots in
nvx-rs/public-api/current: a PR that changes the public API updates them, and their diff shows the change. Nothing that only NVX's programs use becomes public, and each binary calls a hiddenmain; - deletes consumers before their providers, and with them the crate-private code that the deletion leaves unused, which the compiler's dead-code check finds in CI's warning-denied build of each feature set;
- keeps, rewrites or retires each old test of the area that it changes, by name, and explains in its description each change to an existing test that
python3 scripts/check_test_changes.py --base <its base>reports, the list behind the Test changes workflow'stests-modifiedlabel: a test that moves shows as removed and added, and the description pairs the two. An integration test that uses an item that the PR makes crate-private becomes a unit test of the item's owner, or tests through a binary, asserting the same things; - follows the repository's test layout (
doc/contribute.md, Test layout umbrella: move tests out of production code, and flag changes to existing tests #1207): a module's unit tests, and the code that only they use, live in itstests.rsortests/directory and move with it; a production file declares them only as#[cfg(test)] mod tests;and holds no other test code than the hooks that the seams ofscripts/test-layout-allowlist.jsonallow; and a PR that moves, splits or deletes a file with seams updates the file's entry.check_test_layout.pypasses; - leaves
BOOT_CONTRACT_VERSIONandCONTROL_CONTRACT_REVISIONas they are, with the revision's copies: nothing that this workstream changes is deployed, so no PR increments them, althoughcontract's docs ask for an increment with each change. A test that pins a contract's tokens or features takes the new content, at the same version; - updates the rustdoc, examples and docs that it affects;
- records its measured core lines and counted files in its description. The estimates in the group issues come from line counts at
e42b1795, rechecked at4f9a3214.
Code that leaves. A PR that changes only code that this workstream deletes from NVX, with the edits that remove references to it, has no line or file limit. It keeps one goal and one area, records its core lines and counted files, and changes nothing that stays and nothing that a later PR uses. Its issue and its description open with this note: "Leaving code only. This PR changes only code that this effort removes from NVX: area. Nothing that stays changes, and no later PR uses what it changes, so the size limits for kept code don't apply." Code that becomes crate-private stays in NVX, and follows the limits. No group issue mixes the two.
The test layout. #1207 moved NVX's tests out of the code that they test (#1188 and #1195–#1198, merged on 2026-11-02), so every PR of this workstream starts from a dev with the layout, and follows it.
Phases. Each phase ends at a milestone. Its subphases run in parallel lanes, as far as their groups' Needs allow; each group issue lists its PRs, in order, with their Needs.
| Phase | Milestone | Subphases |
|---|---|---|
| 1. Foundation | CI checks nvx's public API against committed snapshots, and NVX's programs (the guest tools, the managed agent, the CLI and the fake OpenVMM) are hidden from it: each binary calls a hidden entry point. |
#1077 |
| 2. The new interface, beside the old one | The API-ready commit. | #1079, #1080, #1081, #1082 |
| 3. NVX's own binaries on the new interface | The CLI runs every command, snapshots included, and nvx-init boots, captures and restores layered roots, through the new interface only. | #1083, #1084, #1085 |
| 4. The slim library | nvx holds only its new interface: the old stores, configs, launch paths, exec engine and process modules are gone, and the code that only NVX's programs use, the error codes included, is private to the crate. |
#1086, #1087, #1088 |
| 5. NVXC v2, the docs and the release | NVX's binaries speak NVXC v2, the docs describe only the final library, and the release is published. | #1089, #1090 |
Children and order. Each subphase issue lists its group issues, and each group issue its PRs. "Needs" names the subphases of earlier phases that a subphase's groups need; inside a phase, the subphase and group issues name their own.
| Issue | Phase | Scope | Groups (PRs) | Needs |
|---|---|---|---|---|
| #1077 | 1 | A checked public API, without NVX's programs | 1 (2) | — |
| #1079 | 2 | A VM's configuration, checked and lowered without touching the host | 2 (9) | #1077 |
| #1080 | 2 | A VM's lifecycle: create, spawn, stop, exits and reattach | 2 (10) | #1077 |
| #1081 | 2 | One snapshot type, activation record v2, and restores that the caller drives | 3 (12) | #1077 |
| #1082 | 2 | The guest's space as reference code, and the guest's activation and capture | 2 (7) | #1077 |
| #1083 | 3 | The CLI switches to the new interface | 5 (15) | #1080 |
| #1084 | 3 | Snapshots through NVXC and the CLI | 2 (6) | #1081 |
| #1085 | 3 | nvx-init boots, captures and restores layered roots | 3 (9) | #1077, #1082 |
| #1086 | 4 | Acceptance on real VMs, then the test adapter goes | 2 (4) | Phases 2 and 3 |
| #1087 | 4 | The old code is deleted, consumers first | 4 (13) | #1077, #1081, #1082, #1083, #1085 |
| #1088 | 4 | The code that only NVX's programs use, and the error codes, become private | 4 (9) | #1079, #1082 |
| #1089 | 5 | NVXC v2 replaces v1 | 2 (4) | #1083, #1085, #1088 |
| #1090 | 5 | The docs, and the release | 3 (10) | Phases 1 to 4 |
Inside phase 2, #1080 needs #1079; #1081 needs #1079 and #1080; #1082 needs #1079 and #1081. Inside phase 3, the lanes meet at group level: the CLI's store, run and sandbox-lifecycle groups come before its snapshot commands, which come before its last switch to the new interface; the CLI's sandbox-lifecycle and managed-sandbox run groups, and its snapshot-command group, wait for nvx-init's capture, restore and layered boot; nvx-init's last capture-and-restore group waits for NVXC's group that adds the capture request, the activate message and the blocking restore driver; and the managed agent's group waits for the CLI's run group, whose shell /init reads the CLI's own tokens. Inside phase 4, #1087 needs #1086, and #1088 needs #1087. Inside phase 5, #1090 needs #1089.
Not in scope (later work):
- OpenVMM restoring any snapshot from any location, which would replace the hard links that a restore uses today;
- snapshot-and-continue, and captures of restored VMs;
- the paired snapshot tiers: a workload-start snapshot with a copied or reflinked scratch, and an instance checkpoint that claims its scratch;
- re-addressing a restored VM's NIC;
- snapshots of blockless VMs;
- kernel tuning tokens in a capture's command line (nvx-rs: boot templates with an adapter's kernel tuning parameters, such as mitigations=off #995);
- performance work.
Acceptance. nvx is released, and:
- its public API holds exactly the planned surface, and one hidden entry point per binary, as the checked snapshots show;
- CI builds each feature set and compiles the documented examples, and a checklist test covers each security requirement that the docs of
filesystem,vmandsnapshotstate; - before the test adapter is deleted, a case matrix in the repository lists each test of the adapter, of CI and of the docs, by name, as kept, rewritten or retired;
- smoke tests on real VMs create, spawn, reattach and stop a VM; capture a snapshot, open it and restore from it, with nvx-init as the guest; run concurrent restores of one snapshot under the CLI's per-snapshot lock; and check that a failed restore reports its attribution;
- the CLI runs its 12 cold cells (EROFS and GPT, with a scratch or a RAM upper, on KVM, MSHV and WHP) and its restores (nvx-rs: final acceptance for the image-backed runtime #624);
- the release CI checks the package's contents, installs the guest tools from it outside the repository with the guest profile, and runs
nvx-ingress; - the docs describe only the final library, and
doc/migrate-from-alpha.7.mdsays what the release breaks.
Related:
- NVX host/guest library wiring workstream: move NVX's binaries and tools onto the shared host and guest libraries #816 (the NVX library wiring workstream) stays open, rescoped to what this workstream keeps of it, which this workstream's groups deliver: nvx-init (nvx-rs slimming, phase 3.3: nvx-init boots, captures and restores layered roots #1085; the old layered init goes in nvx-rs slimming, phase 4.2: the old code is deleted, consumers first #1087, and the space becomes private in nvx-rs slimming, phase 4.3: the code that only NVX's programs use, and the error codes, become private #1088), the CLI's image formats, uppers and protected paths (nvx-rs slimming, phase 3.1: the CLI switches to the new interface #1083),
run's shares through the export planner (nvx-rs slimming, phase 3.1: the CLI switches to the new interface #1083), the exec lock (nvx-rs slimming, phase 3.1: the CLI switches to the new interface #1083), NVXC v2 (nvx-rs slimming, phase 5.1: NVXC v2 replaces v1 #1089), snapshots in NVX's binaries (nvx-rs slimming, phase 3.2: snapshots through NVXC and the CLI #1084, nvx-rs slimming, phase 3.3: nvx-init boots, captures and restores layered roots #1085), DNS through the view (nvx-rs slimming, phase 2.4: the guest's space as reference code, and the guest's activation and capture #1082), and a minimal acceptance gate (nvx-rs slimming, phase 4.1: acceptance on real VMs, then the test adapter goes #1086, nvx-rs: final acceptance for the image-backed runtime #624). Its obsolete child issues close, with these dispositions:- nvx-rs: give each guest build an identity from its build inputs, and package it #820: the runtime's labels already come from the build's provenance files or
SHA256SUMS, and NVXC v2's Ready checks only the protocol version; - runtime adapter: the consumer-contract scenarios C2, C3, C6 and C7 on real VMs #859: the test adapter goes (nvx-rs slimming, phase 4.1: acceptance on real VMs, then the test adapter goes #1086);
- Test the managed agent's direct-mode launch without privileges #601: the new exec core's error paths are tested on VMs as root, with no fork-safe stubs and no
setprivshim; - nvx-rs: decide whether adapters' layered launches need OpenVMM's workload lifecycle #861: layered roots are always managed, blockless
runkeeps both modes through the CLI's own tokens, and NVX stops passing OpenVMM's lifecycle and identity flags.
- nvx-rs: give each guest build an identity from its build inputs, and package it #820: the runtime's labels already come from the build's provenance files or
- nvx-rs: keep processes spawned during a launch from holding its VMM's log on Windows #966: the Windows spawn lock stays, private to
host::vm::spawn; a caller keeps its own spawns out of a VM's start. - nvx-rs: boot templates with an adapter's kernel tuning parameters, such as mitigations=off #995 is deferred: a capture accepts only OpenVMM's platform tokens.
- nvx-rs: let an adapter read the boot console of a template's clones #996: a restored VM has a console listener when its snapshot was captured with one.
Activity
36 remaining items
tests: give the exec lock's forced stop and retry race a loaded host'…
guest: drop guest::clone, the alias of guest::activation
guest: delete what only the exec core's tests used
guest: hold the switched layered root without a dead-code expectation
host: drop the VM directory's copies of the snapshot names
nvx-rs: return the modules that the slimming replaces to their base
nvx-rs: delete the bundled feature and its build script
doc: tell users of alpha.7 what the release breaks
scripts: check that the public API hides one main per binary
nvx-rs: declare 0.2.0-alpha.1, the breaking release, and its changelog
tests: give the exec lock's forced stop and retry race a loaded host'…
guest: drop guest::clone, the alias of guest::activation
guest: delete what only the exec core's tests used
guest: hold the switched layered root without a dead-code expectation
host: drop the VM directory's copies of the snapshot names