Skip to content

sandbox: record the network's options and build its policy at each start (SL3.10c) - #1501

Merged
Pedro Henrique Penna (ppenna) merged 1 commit into
feature/nvx-refactorfrom
agents/slim-sl3-10c-network-record
Nov 12, 2026
Merged

Pedro Henrique Penna (ppenna) merged 1 commit into
feature/nvx-refactorfrom
agents/slim-sl3-10c-network-record

Conversation

@ppenna

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

Copy link
Copy Markdown
Contributor

Part of #1112: SL3.10b, step 2 of 3 (the CLI no longer uses the old launch, store or config APIs). Follows #1497, which has merged.

What and why

SL3.10b removes the last uses of nvx's old launch, store and config APIs from the CLI's four shared sandbox modules: host/cli/sandbox/{mod,options,state}.rs and sandbox/lifecycle/mod.rs. Its measured scope came to about ten times its estimate of 20 core lines, since SandboxLaunch, SandboxSettings, SandboxUpper and SandboxNetwork still carry provision, start and the sandbox's record. The split of SL3.10b into three stacked pull requests, each with one goal, was approved for Pedro Henrique Penna (@ppenna):

  1. sandbox: name the runtime identity and gateway without the old store and plan (SL3.10b) #1497: the store and old-plan leftovers.
  2. This pull request: the sandbox network moves to NetworkConfig, and the record to format 10.
  3. sandbox: record the launch's options without the old launch profile (SL3.10d) #1503 (SL3.10d), stacked on this one: SandboxLaunch, SandboxSettings and SandboxUpper leave the four modules for the CLI's record and a VmConfig.

This step:

  • sandbox_network (sandbox/options.rs) returns the host::config::NetworkConfig that the VMs take, rather than the old plan's host::sandbox::SandboxNetwork, after the checks that SandboxNetwork::new made, in the same order and with the same messages: the guest network, and that OpenVMM's options can express the policy (host::network::openvmm_arguments). nvx-rs run takes it directly, and start no longer converts one into the other.
  • config.json records the CLI's own network options, as a NetworkRecord, in place of SandboxNetwork's serde form, which it does not copy: --net, --network-egress, --network-ingress, --host-loopback, --network-proxy and the --host-loopback-forward options as given, and the egress rules in OpenVMM's form, as the rule options gave them or a policy file compiled them, so no start reads that file again. Each start, and the record that sandbox run writes and reads back, rebuilds the options with the parser's own receiver, NetworkOptions::take, and builds the network from them with sandbox_network, as provision did. provision and sandbox run pass the record in ProvisionConfig.
  • Restored sandboxes take the same path: snapshot restore and run --restore-snapshot, which share snapshots::restore_source since cli: restore named snapshots once with run (SL3.8b) #1489, provision through run_from, so the record keeps their network options, for run at the NIC of the snapshot, which run::restored_network sets, and each restored start builds the network from them.
  • The record requires every field, null for an option not given, and Config::new builds the network as it reads the record, so a record whose options the parser or sandbox_network refuses fails as sandbox configuration is malformed when it is read, as the old plan's network did.

Record format 10. The record changes form, so the configuration moves from format 9 to format 10. #1076 is not backward compatible ("a deploy starts from fresh state"), and nothing that this workstream changes is deployed, so a format-9 record is refused rather than migrated, as format 8 was: start fails with format-unsupported, sandbox configuration has an unsupported format: PATH; deprovision the sandbox and provision it again, and deprovision still removes the sandbox. The advice is new, for every unsupported format. The README's state-directory section and doc/run.md say so. Format 9 and restored sandboxes exist only on feature/nvx-refactor, which is not deployed: dev records formats 1, 7 and 8 and restores no sandbox. So deprovision does not read an older record either, not even the source of a format-9 sandbox restored by a build of this workstream, whose view of its snapshot it removes only once it reads that source; a cold format-9 sandbox, which has no view, deprovisions.

Size. 124 core lines, 7 counted files: sandbox/state.rs 84, sandbox/mod.rs 16, sandbox/options.rs 11, .github/workflows/run-nvx-microvm-tests.yml 5, sandbox/lifecycle/mod.rs 3, sandbox/lifecycle/start.rs 3 and cli/run.rs 2. Seven counted files is the slice's ceiling, which was accepted for this step: the workflow's checks of format 10 belong with the record's move to it. The public API, BOOT_CONTRACT_VERSION and CONTROL_CONTRACT_REVISION do not change.

Stack. This branch is based on feature/nvx-refactor 07c508c74a5, which holds #1497. Review its one commit, c5afedcba44.

Case matrix. doc/acceptance-case-matrix.md lists the sandbox-ram-upper scenario, whose check of the record this changes; its row, which SL5.1b rewrote, now also says that the scenario checks that provision records format 10. It also lists CI's Ubuntu sandbox share tests, whose checks of the record this changes too; their row says so. No row lists another test that this changes, or names one as coverage.

Validation

CI's first run of this step on its current base, 43153917007 at b107c0956f1, failed in the Ubuntu sandbox share tests on KVM, MSHV and WHP: five inline checks of .github/workflows/run-nvx-microvm-tests.yml still expected format 9. This head, c5afedcba44, makes them expect format 10, and gives start_refuses_a_configuration_without_a_valid_network_policy (tests/cli.rs) the record's new form of its proxy-only network, whose old form the record refused for its unknown fields, so that its cases test what they name again; that test fails once its refused network is a valid one. On Windows, CiConfigurationTests (91 tests), the CLI tests (110), Clippy, check_test_layout.py and check_markdown_links.py pass at this head.

At b107c0956f1, on feature/nvx-refactor 07c508c74a5, whose tree is that of the head that I requalified, 83b0eca6555, on #1497's bdf0c50e1f4:

Check Result
Windows: cargo fmt --check; Clippy --all-targets --all-features with -D warnings; cargo test --all-features --lib -- host::cli and --test cli; test_microvm_tests.PublicSandboxRamUpperTests and PublicSandboxGptLayerTests; check_test_layout.py; check_markdown_links.py Passed: lib 226, cli 110
Linux (nvx-kvm-test, Azure Linux 3, KVM, virtual machine): the same, with all of scripts/test_microvm_tests.py (353 tests) Passed: lib 226, cli 119
Linux, KVM: nvx.py test-microvm --backend kvm with every public sandbox scenario: managed-exec-config, sandbox-gpt-layer, sandbox-lifecycle, sandbox-proxy-only, sandbox-ram-upper, snapshot-core, snapshot-tiers and vm-lifecycle Passed: nvx-rs and the Alpine initramfs built at this head; OpenVMM, the kernel and the scratch template of release v0.1.0-dev.53ad09678bc1, which pins the same OpenVMM; the Ubuntu distro layer of feature/nvx-refactor 64ad98afacc's CI run, whose guest inputs this head keeps

The rebase onto SL5.1b, SL5.1c, SL5.2 and #1508 and #1511 changed no line of this step's code. It merged the sandbox-ram-upper row of the case matrix, which SL5.1b rewrote, and the fake nvx-rs of the new sandbox-gpt-layer scenario's tests records format 10 too. rebase_check.py verify reports only #1497's commit, which merged as 07c508c74a5. Earlier, on the base with #1489 and #1491 merged, the same checks and the four public sandbox scenarios passed at 8b606c6c78e, and, on the temporary base, every gate of check-nvx-rs and validate-nvx passed at d5fdcc648f0 but for failures that the base shares.

A code review found that a hand-edited record could leave out a network field, such as egress, which then defaulted to allow, and that a network that its checks refuse failed only later, with another code. The record now requires every field and builds the network when it is read, and malformed_configuration covers both.

CI runs the remaining gates, MSHV and WHP among them.

Tests modified

check_test_changes.py --base origin/feature/nvx-refactor lists 25 changes to existing tests, for the record's new form and format:

  • minimal_config and configurations (tests/cli/lifecycle.rs): the expected records take format 10, and the network and proxy-only cases the NetworkRecord; their starts still launch OpenVMM with the same network options.
  • malformed_configuration: the format cases take 10 ({"format": 11}, {"format": 10.0}), and four new cases refuse network records without a field, of a gateway address, with another egress, or with a broken rule as malformed. Without the check when the record is read, start accepted the record without egress, and allowed egress.
  • configurations_of_every_format_start_and_exec (tests/cli/formats.rs): asserts format 10. old_configuration_formats_are_refused_without_migration: adds format 9 and the advice, which it lacked before.
  • RamUpperAcceptance.check_managed (scripts/nvx_tools/sandbox_ram_upper_tests.py) expects format 10, and the fake nvx-rs of its tests (_FakeRamUpperSandboxCli._provision, scripts/test_microvm_tests.py) records 10, and 9 as its old format. So does the fake nvx-rs of the sandbox-gpt-layer scenario's tests (_FakeGptLayerSandboxCli._provision), which SL5.1b added, and whose scenario checks no format.
  • CI's Ubuntu sandbox share tests (.github/workflows/run-nvx-microvm-tests.yml): the caller-owned live-share, concurrent shares and share access policy steps on Linux, and the concurrent shares and share access policy steps on Windows, check that provision records format 10, where they checked format 9; the tool reports each changed line twice, as modified and as added.
  • recorded_proxy_only_network (tests/cli.rs) returns the record's new form of a proxy-only network, so that start_refuses_a_configuration_without_a_valid_network_policy refuses an unknown field of it and a network that the checks refuse, rather than the old form's unknown fields.
  • a_one_off_restore_installs_the_network_options_of_run_as_its_own_policy (start/tests/restore.rs) takes the NetworkConfig directly. It, deprovision_removes_a_restored_sandboxs_view_only_under_its_snapshots_lock and a_restored_start_refuses_a_share_of_its_snapshot_store (start/tests/restore.rs), run_checks_again_that_its_shares_do_not_overlap (start/tests.rs) and restored_sandbox_records_round_trip_only_their_own_source (state/tests.rs) give ProvisionConfig its network: None, and the imports of start/tests/restore.rs follow.

@ppenna Pedro Henrique Penna (ppenna) added tests-modified Changes existing tests, test data, or test wiring feat-nvx-refactor labels Nov 11, 2026
@github-actions

github-actions Bot commented Nov 11, 2026 •

Copy link
Copy Markdown
Contributor

This pull request changes existing tests

It modifies or removes 25 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 recorded_proxy_only_network in cli (tests) nvx-rs/tests/cli.rs:1644 nvx-rs/tests/cli.rs:1644
modified helper fn minimal_config in cli/lifecycle (tests) nvx-rs/tests/cli/lifecycle.rs:76 nvx-rs/tests/cli/lifecycle.rs:76
modified helper fn configurations in cli/lifecycle (tests) nvx-rs/tests/cli/lifecycle.rs:262 nvx-rs/tests/cli/lifecycle.rs:262
modified helper fn malformed_configuration in cli/lifecycle (tests) nvx-rs/tests/cli/lifecycle.rs:1226 nvx-rs/tests/cli/lifecycle.rs:1212
modified helper def _FakeRamUpperSandboxCli._provision in scripts/test_microvm_tests.py scripts/test_microvm_tests.py:2746 scripts/test_microvm_tests.py:2746
modified helper def _FakeGptLayerSandboxCli._provision in scripts/test_microvm_tests.py scripts/test_microvm_tests.py:3043 scripts/test_microvm_tests.py:3043
modified imports the imports of host::cli::sandbox::lifecycle::start::tests::restore nvx-rs/src/host/cli/sandbox/lifecycle/start/tests/restore.rs:1 nvx-rs/src/host/cli/sandbox/lifecycle/start/tests/restore.rs:1
modified test fn run_checks_again_that_its_shares_do_not_overlap in host::cli::sandbox::lifecycle::start::tests nvx-rs/src/host/cli/sandbox/lifecycle/start/tests.rs:18 nvx-rs/src/host/cli/sandbox/lifecycle/start/tests.rs:18
modified test fn a_one_off_restore_installs_the_network_options_of_run_as_its_own_policy in host::cli::sandbox::lifecycle::start::tests::restore nvx-rs/src/host/cli/sandbox/lifecycle/start/tests/restore.rs:361 nvx-rs/src/host/cli/sandbox/lifecycle/start/tests/restore.rs:359
modified test fn deprovision_removes_a_restored_sandboxs_view_only_under_its_snapshots_lock in host::cli::sandbox::lifecycle::start::tests::restore nvx-rs/src/host/cli/sandbox/lifecycle/start/tests/restore.rs:666 nvx-rs/src/host/cli/sandbox/lifecycle/start/tests/restore.rs:660
modified test fn a_restored_start_refuses_a_share_of_its_snapshot_store in host::cli::sandbox::lifecycle::start::tests::restore nvx-rs/src/host/cli/sandbox/lifecycle/start/tests/restore.rs:721 nvx-rs/src/host/cli/sandbox/lifecycle/start/tests/restore.rs:716
modified test fn restored_sandbox_records_round_trip_only_their_own_source in host::cli::sandbox::state::tests nvx-rs/src/host/cli/sandbox/state/tests.rs:7 nvx-rs/src/host/cli/sandbox/state/tests.rs:7
modified test fn configurations_of_every_format_start_and_exec in cli/formats (tests) nvx-rs/tests/cli/formats.rs:10 nvx-rs/tests/cli/formats.rs:10
modified test fn old_configuration_formats_are_refused_without_migration in cli/formats (tests) nvx-rs/tests/cli/formats.rs:100 nvx-rs/tests/cli/formats.rs:100

End-to-end tests

Change What Before After
modified helper def RamUpperAcceptance.check_managed in scripts/nvx_tools/sandbox_ram_upper_tests.py scripts/nvx_tools/sandbox_ram_upper_tests.py:213 scripts/nvx_tools/sandbox_ram_upper_tests.py:213

CI test wiring

Change What Before After
added a line that may skip line if config["format"] != 10 or config["export"]["owner"] != "caller": .github/workflows/run-nvx-microvm-tests.yml:550
added a line that may skip line if config["format"] != 10 or "mounts" in config or shares != expected: .github/workflows/run-nvx-microvm-tests.yml:693
added a line that may skip line if config["format"] != 10 or "mounts" in config or shares != expected: .github/workflows/run-nvx-microvm-tests.yml:822
added a line that may skip line if ($config.format -ne 10 -or $shares -ne "/workspace,,rw,nvx-denied,directory /config/settings.json,,ro,,file /opt/h... .github/workflows/run-nvx-microvm-tests.yml:1128
added a line that may skip line if ($config.format -ne 10 -or @​($config.export.children).Count -ne 1 -or @​($config.export.binds).Count -ne 1 -or $bin... .github/workflows/run-nvx-microvm-tests.yml:1226
modified line if config["format"] != 9 or config["export"]["owner"] != "caller": .github/workflows/run-nvx-microvm-tests.yml:550 .github/workflows/run-nvx-microvm-tests.yml:550
modified line if config["format"] != 9 or "mounts" in config or shares != expected: .github/workflows/run-nvx-microvm-tests.yml:693 .github/workflows/run-nvx-microvm-tests.yml:693
modified line if config["format"] != 9 or "mounts" in config or shares != expected: .github/workflows/run-nvx-microvm-tests.yml:822 .github/workflows/run-nvx-microvm-tests.yml:822
modified line if ($config.format -ne 9 -or $shares -ne "/workspace,,rw,nvx-denied,directory /config/settings.json,,ro,,file /opt/ho... .github/workflows/run-nvx-microvm-tests.yml:1128 .github/workflows/run-nvx-microvm-tests.yml:1128
modified line if ($config.format -ne 9 -or @​($config.export.children).Count -ne 1 -or @​($config.export.binds).Count -ne 1 -or $bind... .github/workflows/run-nvx-microvm-tests.yml:1226 .github/workflows/run-nvx-microvm-tests.yml:1226

Relocated unchanged: 0; added: 0. Compared the merge base 07c508c74a5e with c5afedcba44a.

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

Deprovisioning a stopped format-9 sandbox with an existing restored openvmm/ view fails with format-unsupported before cleanup, blocking the recommended deprovision/reprovision recovery path.

1 open issue
What changed in this PR

This PR advances #1112 by replacing the sandbox CLI’s old network-plan type with NetworkConfig and CLI-owned network records. The supplied diff also includes stacked changes for named snapshot restores and removal of old supervisor dependencies.

Changes:

  • Record network options in format 10 and rebuild validated policies at each start.
  • Reject older configuration formats with reprovisioning advice, updating tests and documentation.
  • Include prerequisite snapshot-restore, process-handling, and obsolete raw-restore cleanup changes.
File Description
scripts/​test-layout-allowlist.json Remove exemptions for hooks now used in production.
scripts/​test_microvm_tests.py Update format fixtures and remove raw-restore tests.
scripts/​nvx_tools/​sandbox_ram_upper_tests.py Expect configuration format 10.
scripts/​nvx_tools/​microvm_tests.py Remove the raw console-restore scenario.
scripts/​nvx_tools/​microvm_test_scripts/​run-console-restore.sh Delete the obsolete scenario script.
scripts/​nvx_tools/​microvm_shards.json Remove the scenario and rebalance shards.
nvx-rs/​tests/​cli/​validation.rs Update refusals for named snapshot restores.
nvx-rs/​tests/​cli/​usage.rs Check restore options and removed flags.
nvx-rs/​tests/​cli/​openvmm.rs Remove raw snapshot test helpers.
nvx-rs/​tests/​cli/​one_shot.rs Remove obsolete raw-restore cases.
nvx-rs/​tests/​cli/​lifecycle.rs Check network records and malformed configurations.
nvx-rs/​tests/​cli/​formats.rs Check format 10 and older-format recovery advice.
nvx-rs/​tests/​cli/​dry_run.rs Remove unsupported restore dry-run cases.
nvx-rs/​tests/​cli/​download.rs Check restore refusals before downloads.
nvx-rs/​tests/​cli.rs Adapt restore-related diagnostic coverage.
nvx-rs/​src/​host/​vm/​tests.rs Add a prepared-launch substitution helper.
nvx-rs/​src/​host/​vm.rs Make owned-child access private.
nvx-rs/​src/​host/​tests.rs Add shared spawn-lock test helpers.
nvx-rs/​src/​host/​template/​mod.rs Remove the attachment-reader re-export.
nvx-rs/​src/​host/​snapshot/​manifest/​tests.rs Test attachments through the strict manifest reader.
nvx-rs/​src/​host/​snapshot/​manifest.rs Remove the raw-restore attachment reader.
nvx-rs/​src/​host/​restore_driver.rs Remove the unused-code allowance.
nvx-rs/​src/​host/​platform/​windows/​tests.rs Remove tests for the deleted foreground launcher.
nvx-rs/​src/​host/​platform/​windows/​spawn.rs Delete obsolete foreground-spawn helpers.
nvx-rs/​src/​host/​platform/​windows/​mod.rs Remove the foreground-spawn re-export.
nvx-rs/​src/​host/​cli/​tests.rs Validate the composed run-option list.
nvx-rs/​src/​host/​cli/​snapshots/​tests.rs Cover restore parsing and locked view cleanup.
nvx-rs/​src/​host/​cli/​snapshots/​capture/​tests.rs Adapt tests to capture-only parsing.
nvx-rs/​src/​host/​cli/​snapshots/​capture.rs Delegate subcommand selection to the snapshot parser.
nvx-rs/​src/​host/​cli/​snapshots.rs Add restore dispatch and snapshot-view management.
nvx-rs/​src/​host/​cli/​session/​tests.rs Check connection waits against process exit.
nvx-rs/​src/​host/​cli/​session.rs Use verified processes instead of supervisor children.
nvx-rs/​src/​host/​cli/​sandbox/​state/​tests.rs Check restored-source record round trips.
nvx-rs/​src/​host/​cli/​sandbox/​state.rs Introduce format-10 network records and validation.
nvx-rs/​src/​host/​cli/​sandbox/​options.rs Build and validate NetworkConfig directly.
nvx-rs/​src/​host/​cli/​sandbox/​one_shot.rs Delete the obsolete one-shot launcher.
nvx-rs/​src/​host/​cli/​sandbox/​mod.rs Pass network records and coordinate restores.
nvx-rs/​src/​host/​cli/​sandbox/​lifecycle/​stop.rs Remove restored views under snapshot locks.
nvx-rs/​src/​host/​cli/​sandbox/​lifecycle/​start.rs Drive restores and carry network records into runs.
nvx-rs/​src/​host/​cli/​sandbox/​lifecycle/​mod.rs Carry network and source metadata through provision.
nvx-rs/​src/​host/​cli/​run/​tests.rs Cover shared restore parsing and snapshot NIC policy.
nvx-rs/​src/​host/​cli/​network.rs Remove direct OpenVMM argument emission.
nvx-rs/​src/​host/​cli/​mod.rs Dispatch snapshot capture and restore commands.
nvx-rs/​src/​host/​cli/​download/​github/​tests.rs Guard concurrent spawns and check missing-curl errors.
nvx-rs/​src/​host/​cli/​download/​github.rs Spawn curl directly.
nvx-rs/​src/​host/​cli/​download/​git.rs Spawn Git directly.
nvx-rs/​src/​host/​cli/​download/​archive/​tests.rs Protect archive-test spawns with the shared lock.
nvx-rs/​src/​host/​cli/​download/​archive.rs Spawn tar directly.
nvx-rs/​README.md Document network records and named restore behavior.
doc/​run.md Update format references and direct OpenVMM restore examples.

Review effort: Balanced

Comment thread nvx-rs/src/host/cli/sandbox/state.rs Resolved

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.

🟢 Approval recommended

No active review issues block approval of the CLI network-configuration and format-10 changes.

No open issues 🎉

1 resolved since last review

Review effort: Balanced

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.

🟢 Approval recommended

No active review issues are recorded for the network configuration refactor and format-10 sandbox records.

No open issues 🎉

Review effort: Balanced

The sandbox's network was `host::sandbox::SandboxNetwork`, one of the
old config types: `sandbox_network` returned it, the launch carried it,
and `config.json` stored it in the serde form of the old plan's network,
from which `start` read it back. `nvx-rs run` and `start` converted it
into the `NetworkConfig` of `host::config` that their VMs take.

`sandbox_network` now returns that `NetworkConfig`, after the checks
that `SandboxNetwork::new` made: the guest network, and that OpenVMM's
options can express the policy, with `host::network::openvmm_arguments`.
The configuration records the CLI's own network options in place of the
policy, as a `NetworkRecord`: `--net`, `--network-egress`,
`--network-ingress`, `--host-loopback`, `--network-proxy` and the
`--host-loopback-forward` options as given, and the egress rules in
OpenVMM's form, as the rule options gave them or a policy file compiled
them, so that no start reads that file again. Each start, and the
configuration that `sandbox run` writes and reads back, rebuilds the
options with the parser's own receiver and builds the network from them
with `sandbox_network`, as provision did. `provision` and `sandbox run`
pass the record in `ProvisionConfig`, beside the other options that
provision records. The checks of the network, their order and their
messages do not change. The record requires every field, an option
that was not given as `null`, and `Config::new` builds the network as
it reads the record, so a record whose options the parser or
`sandbox_network` refuses fails as `sandbox configuration is
malformed` when read, as the old plan's network did.

The record changes form, so the configuration moves to format 10.
Nothing in this workstream is deployed, so format 9 is refused rather
than migrated, as format 8 was, and the refusal of an unsupported format
now also says to deprovision the sandbox and provision it again.

This is the second step of SL3.10b, whose split was approved once its
measured scope came to about ten times its estimate: SL3.10d moves the
launch off `SandboxLaunch`, `SandboxSettings` and `SandboxUpper`.

Tests: the CLI's lifecycle cases of a network with a policy file, host
loopback and forwards, and of a proxy-only network, check the new
record, and their starts still launch OpenVMM with the same network
options; `old_configuration_formats_are_refused_without_migration`
refuses a format-9 configuration with the advice, which it lacked
before; the format checks of `malformed_configuration`,
`configurations_of_every_format_start_and_exec` and the
`sandbox-ram-upper` scenario take format 10, as does the fake `nvx-rs`
of that scenario's tests, whose old format is now 9, and that of the
`sandbox-gpt-layer` scenario's tests;
`malformed_configuration` also refuses network records without a
field, of a gateway address, with another egress or with a broken rule
as malformed, all of which the record would otherwise read or refuse
later; and the restore test of `run`'s network options takes the
`NetworkConfig` directly. The case matrix's row of the
`sandbox-ram-upper` scenario says that it checks format 10. CI's
Ubuntu sandbox share tests on Linux and on Windows check that
`provision` records format 10, and the matrix's row of those steps
says so too. `start_refuses_a_configuration_without_a_valid_network_policy`
in tests/cli.rs writes its proxy-only network in the record's new form,
so that its cases of an unknown field and of a network that the checks
refuse still test what they name, rather than refusing the old form's
unknown fields.

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.

🟢 Approval recommended

There are no active review issues for the network-record and format-10 migration.

No open issues 🎉

Review effort: Balanced

@ppenna

Copy link
Copy Markdown
Contributor Author

CI's run 43156460151 of this head, c5afedcba44, failed once in nvx-rs / ubuntu-latest, in one_shot::one_shot_runs_exit_with_the_workload_or_openvmm_status (tests/cli/one_shot.rs:372): for its "run: Ubuntu" and "run: network outcome report" cases, nvx-rs run exited with status 0 without forwarding the fake OpenVMM's console line, openvmm: booted the microVM.

The race is in the base, not in this change, which doesn't touch console forwarding. Under CPU load on nvx-kvm-test, the test failed 37 of 150 times at feature/nvx-refactor 07c508c74a5 and 32 of 150 times at this head, each time with the same assertion. The fake OpenVMM accepts the console connection, writes its line and exits before connect_endpoint has finished checking that its peer is alive, so run gives up on the console without an error. A separate investigation follows the race.

The run's failed jobs were rerun once, as its attempt 2, which passed: 33 jobs succeeded and 9 were skipped, nvx-rs / ubuntu-latest and Required status check among the successes.

@ppenna
Pedro Henrique Penna (ppenna) merged commit 533bbb8 into feature/nvx-refactor Nov 12, 2026
88 of 90 checks passed
@ppenna
Pedro Henrique Penna (ppenna) deleted the agents/slim-sl3-10c-network-record branch November 12, 2026 05:21
Pedro Henrique Penna (ppenna) added a commit that referenced this pull request Nov 12, 2026
A cold `nvx-rs run` on Linux could exit with OpenVMM's status, 0, and
print none of OpenVMM's console output, with no error. The CLI test
`one_shot_runs_exit_with_the_workload_or_openvmm_status` failed that
way in CI run 43156460151 (job 130977435086, on #1501), and once in a
local run of SL6.2b's on nvx-mshv-test.

The run connects to the boot console on which OpenVMM listens, then
checks the socket's original peer: its `SO_PEERCRED` PID must be
OpenVMM's, and its `SO_PEERPIDFD` pidfd must not report an exit. The
fake OpenVMM accepts the connection, writes its line and exits at
once. When it exits between the run's `connect` and that check, the
check refuses the peer as exited, and `connect_before_exit` yields to
OpenVMM's exit status, dropping the connection with the output already
queued in it. A real OpenVMM, too, writes its output and exits without
waiting for the run to read it, so a run that stalls at that point on
a loaded host loses its output silently.

The console now connects with `connect_child_endpoint`, which checks
the peer as `connect_endpoint` does, but accepts an original peer that
has exited and has not been reaped, and still refuses a reaped one.
That is safe because the run's own unreaped child keeps its PID, which
no other process can reuse until the run reaps it, so a peer that
holds the child's PID is the child. `PidFd::holds_its_id` tells an
unreaped process from a reaped one with signal 0 through the pidfd,
which fails with `ESRCH` only once the process is reaped. Every other
connection, the control sessions and the restore-ready endpoint, still
refuses an exited peer. Windows is unchanged: its `connect_endpoint`
checks only the pipe's server process ID.

Tests: `a_child_endpoint_accepts_its_exited_child_until_the_child_is_reaped`
connects to a listener whose creator, the test's child, has exited
and is not reaped: `connect_endpoint` refuses it,
`connect_child_endpoint` accepts it and reads through it, and both
refuse it once the test has reaped the creator. It shares the Python
launcher of `a_transferred_listener_cannot_authenticate_its_exited_creator`,
which now runs through the same helper and still reaps the creator
first.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pedro Henrique Penna (ppenna) added a commit that referenced this pull request Nov 12, 2026
A cold `nvx-rs run` on Linux could exit with OpenVMM's status, 0, and
print none of OpenVMM's console output, with no error. The CLI test
`one_shot_runs_exit_with_the_workload_or_openvmm_status` failed that
way in CI run 43156460151 (job 130977435086, on #1501), and once in a
local run of SL6.2b's on nvx-mshv-test.

The run connects to the boot console on which OpenVMM listens, then
checks the socket's original peer: its `SO_PEERCRED` PID must be
OpenVMM's, and its `SO_PEERPIDFD` pidfd must not report an exit. The
fake OpenVMM accepts the connection, writes its line and exits at
once. When it exits between the run's `connect` and that check, the
check refuses the peer as exited, and `connect_before_exit` yields to
OpenVMM's exit status, dropping the connection with the output already
queued in it. A real OpenVMM, too, writes its output and exits without
waiting for the run to read it, so a run that stalls at that point on
a loaded host loses its output silently.

The console now connects with `connect_child_endpoint`, which checks
the peer as `connect_endpoint` does, but accepts an original peer that
has exited and has not been reaped, and still refuses a reaped one.
That is safe because the run's own unreaped child keeps its PID, which
no other process can reuse until the run reaps it, so a peer that
holds the child's PID is the child. `PidFd::holds_its_id` tells an
unreaped process from a reaped one with signal 0 through the pidfd,
which fails with `ESRCH` only once the process is reaped. Every other
connection, the control sessions and the restore-ready endpoint, still
refuses an exited peer. Windows is unchanged: its `connect_endpoint`
checks only the pipe's server process ID.

Tests: `a_child_endpoint_accepts_its_exited_child_until_the_child_is_reaped`
connects to a listener whose creator, the test's child, has exited
and is not reaped: `connect_endpoint` refuses it,
`connect_child_endpoint` accepts it and reads through it, and both
refuse it once the test has reaped the creator. It shares the Python
launcher of `a_transferred_listener_cannot_authenticate_its_exited_creator`,
which now runs through the same helper and still reaps the creator
first.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pedro Henrique Penna (ppenna) added a commit that referenced this pull request Nov 12, 2026
A cold `nvx-rs run` on Linux could exit with OpenVMM's status, 0, and
print none of OpenVMM's console output, with no error. The CLI test
`one_shot_runs_exit_with_the_workload_or_openvmm_status` failed that
way in CI run 43156460151 (job 130977435086, on #1501), and once in a
local run of SL6.2b's on nvx-mshv-test.

The run connects to the boot console on which OpenVMM listens, then
checks the socket's original peer: its `SO_PEERCRED` PID must be
OpenVMM's, and its `SO_PEERPIDFD` pidfd must not report an exit. The
fake OpenVMM accepts the connection, writes its line and exits at
once. When it exits between the run's `connect` and that check, the
check refuses the peer as exited, and `connect_before_exit` yields to
OpenVMM's exit status, dropping the connection with the output already
queued in it. A real OpenVMM, too, writes its output and exits without
waiting for the run to read it, so a run that stalls at that point on
a loaded host loses its output silently.

The console now connects with `connect_child_endpoint`, which checks
the peer as `connect_endpoint` does, but accepts an original peer that
has exited and has not been reaped, and still refuses a reaped one.
That is safe because the run's own unreaped child keeps its PID, which
no other process can reuse until the run reaps it, so a peer that
holds the child's PID is the child. `PidFd::holds_its_id` tells an
unreaped process from a reaped one with signal 0 through the pidfd,
which fails with `ESRCH` only once the process is reaped. Every other
connection, the control sessions and the restore-ready endpoint, still
refuses an exited peer. Windows is unchanged: its `connect_endpoint`
checks only the pipe's server process ID.

Tests: `a_child_endpoint_accepts_its_exited_child_until_the_child_is_reaped`
connects to a listener whose creator, the test's child, has exited
and is not reaped: `connect_endpoint` refuses it,
`connect_child_endpoint` accepts it and reads through it, and both
refuse it once the test has reaped the creator. It shares the Python
launcher of `a_transferred_listener_cannot_authenticate_its_exited_creator`,
which now runs through the same helper and still reaps the creator
first.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pedro Henrique Penna (ppenna) added a commit that referenced this pull request Nov 13, 2026
A cold `nvx-rs run` on Linux could exit with OpenVMM's status, 0, and
print none of OpenVMM's console output, with no error. The CLI test
`one_shot_runs_exit_with_the_workload_or_openvmm_status` failed that
way in CI run 43156460151 (job 130977435086, on #1501), and once in a
local run of SL6.2b's on nvx-mshv-test.

The run connects to the boot console on which OpenVMM listens, then
checks the socket's original peer: its `SO_PEERCRED` PID must be
OpenVMM's, and its `SO_PEERPIDFD` pidfd must not report an exit. The
fake OpenVMM accepts the connection, writes its line and exits at
once. When it exits between the run's `connect` and that check, the
check refuses the peer as exited, and `connect_before_exit` yields to
OpenVMM's exit status, dropping the connection with the output already
queued in it. A real OpenVMM, too, writes its output and exits without
waiting for the run to read it, so a run that stalls at that point on
a loaded host loses its output silently.

The console now connects with `connect_child_endpoint`, which checks
the peer as `connect_endpoint` does, but accepts an original peer that
has exited and has not been reaped, and still refuses a reaped one.
That is safe because the run's own unreaped child keeps its PID, which
no other process can reuse until the run reaps it, so a peer that
holds the child's PID is the child. `PidFd::holds_its_id` tells an
unreaped process from a reaped one with signal 0 through the pidfd,
which fails with `ESRCH` only once the process is reaped. Every other
connection, the control sessions and the restore-ready endpoint, still
refuses an exited peer. Windows is unchanged: its `connect_endpoint`
checks only the pipe's server process ID.

Tests: `a_child_endpoint_accepts_its_exited_child_until_the_child_is_reaped`
connects to a listener whose creator, the test's child, has exited
and is not reaped: `connect_endpoint` refuses it,
`connect_child_endpoint` accepts it and reads through it, and both
refuse it once the test has reaped the creator. It shares the Python
launcher of `a_transferred_listener_cannot_authenticate_its_exited_creator`,
which now runs through the same helper and still reaps the creator
first.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pedro Henrique Penna (ppenna) added a commit that referenced this pull request Nov 13, 2026
A cold `nvx-rs run` on Linux could exit with OpenVMM's status, 0, and
print none of OpenVMM's console output, with no error. The CLI test
`one_shot_runs_exit_with_the_workload_or_openvmm_status` failed that
way in CI run 43156460151 (job 130977435086, on #1501), and once in a
local run of SL6.2b's on nvx-mshv-test.

The run connects to the boot console on which OpenVMM listens, then
checks the socket's original peer: its `SO_PEERCRED` PID must be
OpenVMM's, and its `SO_PEERPIDFD` pidfd must not report an exit. The
fake OpenVMM accepts the connection, writes its line and exits at
once. When it exits between the run's `connect` and that check, the
check refuses the peer as exited, and `connect_before_exit` yields to
OpenVMM's exit status, dropping the connection with the output already
queued in it. A real OpenVMM, too, writes its output and exits without
waiting for the run to read it, so a run that stalls at that point on
a loaded host loses its output silently.

The console now connects with `connect_child_endpoint`, which checks
the peer as `connect_endpoint` does, but accepts an original peer that
has exited and has not been reaped, and still refuses a reaped one.
That is safe because the run's own unreaped child keeps its PID, which
no other process can reuse until the run reaps it, so a peer that
holds the child's PID is the child. `PidFd::holds_its_id` tells an
unreaped process from a reaped one with signal 0 through the pidfd,
which fails with `ESRCH` only once the process is reaped. Every other
connection, the control sessions and the restore-ready endpoint, still
refuses an exited peer. Windows is unchanged: its `connect_endpoint`
checks only the pipe's server process ID.

Tests: `a_child_endpoint_accepts_its_exited_child_until_the_child_is_reaped`
connects to a listener whose creator, the test's child, has exited
and is not reaped: `connect_endpoint` refuses it,
`connect_child_endpoint` accepts it and reads through it, and both
refuse it once the test has reaped the creator. It shares the Python
launcher of `a_transferred_listener_cannot_authenticate_its_exited_creator`,
which now runs through the same helper and still reaps the creator
first.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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