Repository navigation
host: keep the export's pins in the CLI and paths only in ExportPlan (SL6.5b) - #1549
Merged
Pedro Henrique Penna (ppenna) merged 1 commit intoNov 12, 2026
Conversation
Contributor
This pull request changes existing testsIt modifies or removes 38 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
Relocated unchanged: 0; added: 3. Compared the merge base |
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
Cleanup rejects older restored sandbox records missing export_pins, blocking deprovisioning and leaving the openvmm view and state behind.
1 open issue
What changed in this PR
This implements SL6.5b of #1125, separating public export descriptions from CLI-owned file identity tracking as part of NVX’s library slimming.
Changes:
- Makes
ExportPlanpath-only and introduces privatePlannedExportandPinstypes for planning and verification. - Persists pins separately as
export_pinsand checks them before sandbox launches. - Updates tests, API snapshots, and documentation for the new representation and caller responsibilities.
| File | Description |
|---|---|
nvx-rs/tests/cli/openvmm.rs |
Removes identity data from expected export fixtures. |
nvx-rs/tests/cli/lifecycle.rs |
Checks separate pin records and malformed configurations. |
nvx-rs/src/host/vm/tests.rs |
Uses path-only export fixtures. |
nvx-rs/src/host/sandbox/tests.rs |
Adapts planning and verification tests to PlannedExport. |
nvx-rs/src/host/sandbox/options/tests.rs |
Reads lowering inputs from the export description. |
nvx-rs/src/host/sandbox/mount/tests.rs |
Checks descriptions and pins separately. |
nvx-rs/src/host/sandbox/mount/aggregate.rs |
Returns planned exports with their pins. |
nvx-rs/src/host/filesystem/verify.rs |
Moves pin checks to PlannedExport and internalizes lowering helpers. |
nvx-rs/src/host/filesystem/tests.rs |
Adapts verification tests and adds pin-slot validation cases. |
nvx-rs/src/host/filesystem/policy.rs |
Gates writable-boundary helpers behind cli. |
nvx-rs/src/host/filesystem/mod.rs |
Separates descriptions from pins and documents path stability. |
nvx-rs/src/host/config/tests.rs |
Removes identities from configuration fixtures. |
nvx-rs/src/host/cli/sandbox/state/tests.rs |
Adds persistence and changed-pin rejection coverage. |
nvx-rs/src/host/cli/sandbox/state.rs |
Stores, reloads, and validates exports with separate pins. |
nvx-rs/src/host/cli/sandbox/mod.rs |
Carries planned exports through sandbox setup. |
nvx-rs/src/host/cli/sandbox/lifecycle/start/tests/restore.rs |
Uses path-only exports in restore fixtures. |
nvx-rs/src/host/cli/sandbox/lifecycle/start/tests.rs |
Adapts command-line checks to wrapped exports. |
nvx-rs/src/host/cli/sandbox/lifecycle/mod.rs |
Includes pins in provisioning inputs. |
nvx-rs/src/host/cli/run/guest_tokens/tests.rs |
Tests guest tokens using the export description. |
nvx-rs/src/host/cli/run.rs |
Passes only the description to one-shot VM configuration. |
nvx-rs/README.md |
Documents API boundaries, path stability, and stored pins. |
nvx-rs/public-api/windows.txt |
Removes identity fields and internalized methods from the Windows API snapshot. |
nvx-rs/public-api/linux.txt |
Records the same API removals for Linux. |
doc/run.md |
Explains separate pin storage and older-record rejection. |
doc/acceptance-case-matrix.md |
Links the new persistence test to pin-verification acceptance coverage. |
Review effort: Balanced
Pedro Henrique Penna (ppenna)
force-pushed
the
agents/slim-sl6-5a2-path-rules-private
branch
from
November 12, 2026 21:20
c765ea3 to
43ba688
Compare
Base automatically changed from
agents/slim-sl6-5a2-path-rules-private
to
feature/nvx-refactor
November 12, 2026 21:54
ExportPlan, the public description of a VM's export, combined the host
paths with the file identities that only the CLI's store needs: each
child's denied_identities, each bind's identity, and FileIdentity, with
check's rules that a recorded plan pins each object that a workload
could move. The paths are what nvx lowers; the identities are the CLI's
recheck of its own records before each start.
The CLI now owns PlannedExport { description: ExportPlan, pins: Pins },
crate-private and cli-gated beside the planner and the verifier that
SL6.5a2 made private:
- The planner returns a PlannedExport: the description, and the pins
of each child's denied paths and of each bind, in their orders,
which it computes as before. The binds keep their pins as they are
sorted.
- PlannedExport::check runs ExportPlan::check, then the pin rules that
moved out of it: a slot for each child and each of its denied paths,
one for each bind, and the identity of each denied or mapped object
strictly inside a part that the guest may write, with the same
messages. PlannedExport::verify is the old ExportPlan::verify, which
reads the identities from the pins.
- The sandbox's config.json keeps the description under export, paths
only, where CI's share checks read it, and records the pins beside it
under export_pins, as it records each image beside its binding.
Config::new requires the field and refuses an export without pins or
pins without an export as malformed; Config::check checks and
verifies the pair before vm_config passes the description to the VM.
plan_mounts, provision's ProvisionConfig and check_images carry the
PlannedExport; nvx-rs run, which launches its plan at once and never
again, keeps the description and checks no pins, as before.
nvx's public API loses FileIdentity, ExportChild::denied_identities,
Bind::identity, and ExportPlan::openvmm_arguments and kernel_token,
which only VmConfig's lowering calls and which become pub(crate);
lowering is unchanged. ExportPlan::check stays structural and needs no
identity data. host::filesystem documents what the caller keeps stable:
the launch race, the writable-part recheck and durable denials.
A configuration that an earlier build wrote has no export_pins, so a
start refuses it as "sandbox configuration has no export pins;
deprovision the sandbox and provision it again", as for every field
that the record gained before; nothing is migrated, and the format
stays 10.
Tests: the planner's and verifier's unit tests read the pins from the
PlannedExport, and narrowed_writes_pin_only_what_lies_in_writable_paths
also checks the new slot rules; ExportPlan's own tests and fixtures,
in host::config, host::vm, the restore tests and tests/cli/openvmm.rs,
build plans without identities. A new state test records an export
with its pins, reads both back, and refuses a moved, a gone and an
uninspectable pinned object at Config::check. The CLI's lifecycle
tests expect export_pins in each record, and malformed_configuration
refuses a record without it, an export without pins, and pins without
an export.
Part of #1125 (SL6.5b).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pedro Henrique Penna (ppenna)
force-pushed
the
agents/slim-sl6-5b-export-paths-only
branch
from
November 12, 2026 22:01
21ce29a to
03b7ccb
Compare
Pedro Henrique Penna (ppenna)
marked this pull request as ready for review
November 12, 2026 22:02
Pedro Henrique Penna (ppenna)
merged commit Nov 12, 2026
14ea1a4
into
feature/nvx-refactor
87 checks passed
Pedro Henrique Penna (ppenna)
deleted the
agents/slim-sl6-5b-export-paths-only
branch
November 12, 2026 22:27
This was referenced Nov 12, 2026
Closed
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.

Part of #1125 (SL6.5b, "The export holds paths only"). Parent #1088, umbrella #1076.
Based on
feature/nvx-refactorat19b5404dd0d, where SL6.5a2 (#1544) merged. SL6.5b needs SL6.5a2, which made the export planner and its host rechecks private to the CLI; the planner, the verifier and their callers change together here.What and why
ExportPlan, the public description of a VM's export, combined the host paths thatnvxlowers with the file identities that only the CLI's store needs: each child'sdenied_identities, each bind'sidentity, andFileIdentity, withcheck's rules that a recorded plan pins every object that a workload could move. This PR does SL6.5b's Scope in one commit,host: keep the export's pins in the CLI and paths only in ExportPlan(03b7ccb2):PlannedExport { description: ExportPlan, pins: Pins }, crate-private andcli-gated inhost::filesystem, beside the planner and the verifier that SL6.5a2 made private there (host's children may not depend onhost::cli, and the planner, whichhost::sandbox::mountcalls too, produces it).FileIdentitystays as the pins' crate-private type.Pins::deniedholds, per child, the identity of each denied path that a workload could move, andPins::bindsthat of each bind's object, computed as before; the binds are sorted with their pins.PlannedExport::checkrunsExportPlan::check, then the pin rules that moved out of it, with the same messages: a slot per child and per denied path, one per bind, and the identity of each denied or mapped object strictly inside a part of its child that the guest may write.PlannedExport::verifyis the oldExportPlan::verify, which reads the identities from the pins.config.jsonkeeps the description underexport, paths only, where CI's share checks read it, and records the pins beside it under the newexport_pins(nullwithout an export), as it records each image beside its binding.Config::newrequires the field, and refuses an export without pins or pins without an export as malformed;Config::check, which eachstart,sandbox runandprovisionrun, checks and verifies the pair beforevm_configpasses the description to the VM.plan_mounts,ProvisionConfigandcheck_imagescarry thePlannedExport;nvx-rs run, which launches its plan once, right after planning it, keeps the description and checks no pins, as before.ExportPlanholds paths only.FileIdentity,ExportChild::denied_identities,Bind::identityand their structural rules leave it and itscheck, which stays structural and needs no identity data.ExportPlan::{openvmm_arguments, kernel_token}, which onlyVmConfig's lowering calls, becomepub(crate); lowering is unchanged, so OpenVMM's arguments, thenvx_mapstoken and the guest mapping table are the same.host::filesystemgains "What the caller keeps stable": the launch race, the writable-part recheck and durable denials, with what NVX's CLI does for each;ExportPlanandcheckpoint there. The README'snvx::host::filesystemsection andconfig.jsonrow,doc/run.md's shares section and the store's module docs follow. The module row ofhost::filesystemkeeps its purpose, so it doesn't change.FileIdentitywith its two fields,ExportChild::denied_identities,Bind::identity,ExportPlan::kernel_tokenandExportPlan::openvmm_arguments.What the CLI plans, protects and pins doesn't change, nor do the export's wire formats, guest mappings or OpenVMM arguments,
BOOT_CONTRACT_VERSIONorCONTROL_CONTRACT_REVISION.State written before this change
The stored form changes:
exportlosesdeniedIdentitiesandidentity, and the record gainsexport_pins. The record's format stays 10; a configuration that an earlier build wrote, with or without shares, has noexport_pins, sostartrefuses it before anything else witherror[format-unsupported]: sandbox configuration has no export pins; deprovision the sandbox and provision it again, through the existing check that refuses a record without one of its typed fields, as it refused records before the bindings or the network were recorded. Nothing is migrated: nothing of this workstream is deployed (#1076, "No backward compatibility"), and #1125 excludes a persistence framework. Bumping the format to 11 would refuse the same records, but would also change CI's share checks inrun-nvx-microvm-tests.ymland thesandbox-ram-upperscenario, which readformat == 10, two more counted files for no change of behavior.What each command does with such a sandbox:
deprovisionreads no configuration for it.snapshot restore, keeps the view of its snapshot in its state directory once it has started.deprovisionreads its configuration throughConfig::newto find the snapshot whose lock guards the view's removal (lifecycle/stop.rs), so it refuses an earlier build's restored sandbox with the same message, and leaves its view and state in place. That is how it has refused a format-9 restored sandbox since533bbb81f8d; a scratch test ofdeprovisionshowed both, at this head. Copilot's review thread askeddeprovisionto read the snapshot's fields alone, independently of the launch schema; that needs no change here: only this unreleased branch's builds since SL3.8a1 write such records,dev's releases (formats 1, 7 and 8) never create the view, so their sandboxes deprovision without reading a record, and nvx-rs slimming workstream: makenvxa library of mechanisms and value types #1076 keeps no backward compatibility. Such a sandbox is removed by hand: stop it, then remove its state directory while no restore of its snapshot runs.execrefuses it, with the same message when these artifacts started it.stopends it all the same: by forced termination after the grace period, with a warning, as for any failed guest shutdown, or as it ends any sandbox of another runtime identity.Size
Measured against
19b5404dd0d(feature/nvx-refactor, with SL6.5a2), by #1076's rules: the non-blank, non-comment lines that the commit adds or changes in production Rust files.host/filesystem/verify.rs131, most of itPlannedExport::check, with the pin rules that leftExportPlan::check, andverify's body, moved underimpl PlannedExport,host/filesystem/mod.rs44,host/cli/sandbox/state.rs20,host/sandbox/mount/aggregate.rs7,host/cli/sandbox/mod.rs3,host/cli/sandbox/lifecycle/mod.rs2 andhost/cli/run.rs2.host/filesystem/policy.rsonly gatesWritablebycli, which only the planner andPlannedExport::checknow use, so it and its 2 lines don't count.nvxa library of mechanisms and value types #1076's usual 5, within the 6 or 7 that it allows where a split would be harder to follow: the planner's return type reaches its two callers (run.rsandaggregate.rs), and the store's input reaches provision'sProvisionConfig(lifecycle/mod.rs) and its caller (sandbox/mod.rs); nvx-rs slimming 4.3.2: path rules are private to the CLI, and the export holds paths only #1125 has "the planner and verifier ... and their callers change together", and none of them builds alone.Old tests of the area
PlannedExport: the planner's and verifier's unit tests inhost::filesystem(swapped_objects_fail_verification,objects_that_cannot_be_inspected_fail_verification_by_what_the_failure_shows,read_only_binds_that_the_host_cannot_enforce_fail_verification,denied_paths_belong_to_the_directory_that_contains_them,policy_paths_hide_show_and_narrow_writes,the_caller_owner_reaches_openvmm_on_linux_hosts,an_exported_object_that_a_workload_could_move_is_checked_again_before_a_start, among them), the sandbox's planning and launch-validation tests inhost::sandbox, and the CLI's tests that plan shares.narrowed_writes_pin_only_what_lies_in_writable_pathschecks the pin rules throughPlannedExport::check, where they moved, with the same messages, and adds the slot rules (Test changes).ExportPlan::check's own tests,recorded_plans_hold_no_more_policy_than_one_aggregate_acceptsand those ofhost::configandhost::vm, build plans without identity data and refuse the same structural errors.Done when
records_keep_the_export_with_its_pins_and_starts_refuse_changed_pins(host/cli/sandbox/state/tests.rs) records a planned export throughserialize_launch, finds the description underexportand the pins underexport_pins, reads both back throughConfig::new, and gets the description back fromvm_config;lifecycles_of_every_configuration(tests/cli/lifecycle.rs) checks whatprovisionrecords for every share configuration, and the KVM and WHP runs below drive the binary.nvx: the same test refuses, atConfig::check, which comes beforevm_config, a pinned denied directory that moved with a decoy at its path or went (error[other],... no longer names the host object it named at provision), and one that the host cannot look up (error[io],cannot inspect ...); the verifier's unit tests above keep their cases; and on KVM and WHP,sandbox startrefused a decoy before it launched anything (noruntime.json).ExportPlan::checktests pass without identity data and still reject the same structural errors: above.host::filesystemandhost::configthat pin the arguments,nvx_mapsand the table, and thedry_runandone_shottests oftests/cli, which compare the CLI's OpenVMM command lines with an export that they derive on their own, pass unchanged.host::filesystem's "What the caller keeps stable", and the README.Test changes
python scripts/check_test_changes.py --base origin/feature/nvx-refactorlists 38 changes to existing tests and 3 added items, hencetests-modified:ExportPlanwithout identity data (8):host::config::tests's helperexport,mapping_table_is_the_exact_export_projection_counted_by_the_boot_tokenandconfiguration_command_lines_keep_the_same_reserve_on_every_host;host::vm::tests'scapture_creation_refuses_layered_roots_and_exports_without_creating_outputsandprepared_record_keeps_checked_root_view_export_resources_and_transaction; the restore tests' helperrestored_config;recorded_plans_hold_no_more_policy_than_one_aggregate_accepts; andtests/cli/openvmm.rs's helperfixture_export, which builds the fixture's export ashost::filesystemdocuments it, now paths only. Each only dropsdenied_identitiesoridentity; the assertions don't change.fixture_exportwas the rebase's one conflict: SL6.5a2's tests commit,fe269acbe9a, gave each movable denied path a zeroFileIdentity, whichExportPlan::checkthen required; this PR drops it with the field.host::filesystem(12): the helperplanreturns the description of the new helperplanned(added), which returns thePlannedExport, andplan_blocklessreturns thePlannedExport;denied_paths_belong_to_the_directory_that_contains_them,swapped_objects_fail_verification,objects_that_cannot_be_inspected_fail_verification_by_what_the_failure_shows,read_only_binds_that_the_host_cannot_enforce_fail_verification,policy_paths_hide_show_and_narrow_writes,the_caller_owner_reaches_openvmm_on_linux_hostsandnarrowed_writes_pin_only_what_lies_in_writable_pathsread the pins or verify thePlannedExport;explicit_targets_place_mappings_at_any_free_guest_path,read_write_mappings_to_which_writes_are_narrowed_stay_visibleanddenied_mapped_directories_inside_denied_ones_show_their_allowed_pathstake the description.narrowed_writes_pin_only_what_lies_in_writable_pathsalso refuses a record with a missing slot for a denied path, a missing bind slot, or an extra child slot, the rules that keep the verifier from skipping a pin.host::sandbox::tests's helpersplanned,validatedandvalidated_withand their three tests,host::sandbox::mount::tests::shares_are_planned_through_the_export_planner,host::sandbox::options::tests's helperrun,host::cli::run::guest_tokens::tests::planned_boot_shares_keep_table_sources_targets_access_and_file_kind, andstarts_check_the_kernel_command_line_that_they_launch, with the imports of the two test modules, read the description or verify thePlannedExport, with the same assertions.tests/cli/lifecycle.rs(6, and 1 added):minimal_configexpects"export_pins": null,shared_configexpects the pins of its export through the new helperunpinned, since no share of these configurations pins an object, andchildand the policy and file configurations ofconfigurationslosedeniedIdentities, solifecycles_of_every_configurationchecks the new record;malformed_configurationrefuses a record withoutexport_pins(format-unsupported, with the advice), an export without pins and pins without an export (io, malformed);start_refuses_recorded_shares_of_its_own_fileswrites its export with its pins, so that it still reaches the refusal that it names.records_keep_the_export_with_its_pins_and_starts_refuse_changed_pins(host/cli/sandbox/state/tests.rs), and the helpersplannedandunpinned.doc/acceptance-case-matrix.md's rowa_start_refuses_an_export_whose_mapped_objects_changed_before_any_vmm_startsalso names the new state test; no inventoried test changes, andcheck_matrix.pypasses.Validation
I ran the
check-nvx-rssteps and the test-layout, test-changes and case-matrix checks from this branch's definitions, with Rust 1.95.0, locally on Windows and onnvx-mshv-test(Azure Linux 3, kernel 6.18, linux-mshv, virtual machine), in temporary worktrees, on this head,03b7ccb22dc. It is21ce29a1881rebased onto19b5404dd0d, andgit range-diffshows no change to the production patch, only tofixture_export(Test changes); the same checks had passed on21ce29a1881.cargo fmt --all --check-D warnings:--all-targets --all-features,--lib,--lib --no-default-features, andserde,cli,testingandguestaloneguestalone for the guests' musl target, belowx86_64-unknown-linux-musl,guestalone (check-guest-binaries), andbuild-guest-binarieswithtest-guest-binariescargo check --all-targets --all-features --target aarch64-apple-darwincargo test --locked --all-featurescli113 passed, 2 ignored; every other integration and doc testcli123 passed; every other integration and doc testcargo test --locked,cargo build --locked --lib --no-default-features,cargo test --locked --lib --no-default-featuresRUSTDOCFLAGS=-D warnings cargo doc --locked --no-deps --all-features, and the compile-only examplescargo +1.89 check --locked --all-targets --all-featurespython scripts/public_api.pycheck_test_layout.py,check_test_changes.py --base origin/feature/nvx-refactor,check_matrix.py <worktree> 99d2022085fpython scripts/nvx.py test-microvmwithNVX_AUTO_DOWNLOAD=0, this head's releasenvx-rs, and the artifacts of this pull request's CI run 43316146269 on this head: the kernel, the Alpine initramfs, the Ubuntu distro layer and scratch template, and OpenVMM (openvmm-linux-gnu,openvmm-windows-msvc, of the unchanged pin14dc4116ec7), each checked by SHA-256 against the run's artifacts and OpenVMM's provenance. All 14 passed on KVM (nvx-kvm-test, kernel 6.6) and on WHP (nvx-whp-test): the public sandbox scenariosmanaged-exec-config,sandbox-gpt-layer,sandbox-lifecycle,sandbox-proxy-only,sandbox-ram-upper,snapshot-core,snapshot-tiers,untiered-network-policyandvm-lifecycle, and the--mountand filesystem-policy scenariosfilesystem-shares,filesystem-policy,filesystem-owner,filesystem-comma-pathsanddenied-filesystem-paths.sandbox provisionof the Ubuntu layer with--mount /workspace,<share>,rw --mount-deny nvx-deniedrecordedexportwith paths only andexport_pinswith the denied directory's identity ({"device": 2065, "index": 29511296}on KVM,{"device": 2595290065, "index": 3377699720794538}on WHP);start,exec /bin/ls -a /workspace(which showedmarkerand not the denied directory) andstoppassed, and on KVMexec /bin/cat /workspace/nvx-denied/secretfailed withPermission denied; with the denied directory moved and a decoy at its path,startfailed witherror[other]: <share>/nvx-denied no longer names the host object it named at provision; deprovision the sandbox and provision it againand left noruntime.json; on KVM, it failed the same way once the directory went, and with the record rewritten as an earlier build wrote it, identities inline and noexport_pins, it failed witherror[format-unsupported]: sandbox configuration has no export pins; deprovision the sandbox and provision it again; once the directory was back,startandstoppassed, anddeprovisionremoved the sandbox.Required status checkamong them: thenvx-rsand NVX CLI jobs on Linux and Windows, the guest and OpenVMM builds, the microVM tests on KVM, MSHV and WHP, the three Platform jobs and the performance regression gate.OpenVMM CI of the pinned revisiondidn't run, since the pin is unchanged.