Skip to content

SL3.5b: migrate persistent sandbox lifecycle to CLI-owned VM state - #1460

Merged
Pedro Henrique Penna (ppenna) merged 15 commits into
feature/nvx-refactorfrom
esaurez/sl35b-lifecycle-4d48405a-20261110
Nov 11, 2026
Merged

Pedro Henrique Penna (ppenna) merged 15 commits into
feature/nvx-refactorfrom
esaurez/sl35b-lifecycle-4d48405a-20261110

Conversation

@esaurez

@esaurez Enrique Saurez (esaurez) commented Nov 10, 2026 •

Copy link
Copy Markdown
Contributor

Scope

Refs #1110, SL3.5b only. Switch persistent sandbox start, exec, stop, and deprovision together to the CLI store and host::vm. Provision still writes today's EROFS configuration with scratch/RAM uppers, network, export and image bindings.

Start journals the actual prepared capability/outputs before spawn, records the verified ProcessRef, and commits the matching SavedVm after readiness and mappings. The CLI supplies nvx_sandbox=1, managed mode and workload identity without an OpenVMM lifecycle option. Reattachment reuses the existing NVXC framing and one authenticated HostConnection. A bounded crate-private VM transport factory lets the CLI retain its existing verified transport/probe through attachment; the public SDK API is unchanged.

Stop requests NVXC shutdown, waits for exit independently of its acknowledgement, and forces only the verified process after grace. Indeterminate launches retain their evidence. Deprovision removes only this stopped sandbox's records, outputs and owned temporary files.

No new provision format, GPT/RAM-cap option, protected-path feature, stateless-run migration, exec lock, public SDK API, running-old-store migration, producer fix, or guest/native/kernel source change, but the smoke probe's init argument that Started sandboxes on nvx-init describes.

Rebase onto feature/nvx-refactor

#1453, #1441 and #1464 merged, and the inputs that #1458 joined are all in feature/nvx-refactor, so this pull request now targets that branch instead of #1458's. Its eleven own commits are rebased onto 5947e4e834efa0a93291ce6dd13cafef1f6aedaf as 4d76f035ea..5b7882adb02478d237c3fc823d19ef411039e2a6, and one commit after them, below, fixes an acceptance expectation. The head is 88c6bca99fb2e7a201e3e0282d0db42bd09415be, tree 72675c56ff40137a2afe4865a137145d7e5832d5. The merge commit c0d706e7, which joined #1458's head 928edaad, is dropped: git show --remerge-diff c0d706e7 shows no change of its own. git range-diff 928edaad5a..369ad2d512 5947e4e834..5b7882adb0 reports seven of the eleven patches identical. The other four carry these resolutions:

The first CI run of the rebased branch, at 5b7882ad, failed only the public managed execution acceptance, the first scenario of the "Guests and sandboxes" job, on KVM, MSHV and WHP: public sandbox exec returned 1, expected 125; stderr='error[exec-refused]: managed guest rejected exec (status=2, category=cwd-failed)'. This pull request starts a persistent sandbox through host::vm without the old OpenVMM nvx_lifecycle= token, so nvx-init serves its executions, as #1453 routes them. That agent refuses a working directory that the workload cannot enter with the cwd-failed application error, which nvx-rs reports as exec-refused with status 1, as the error code table and the CLI's lifecycle test of a missing working directory, nvx-rs/tests/cli/lifecycle.rs, already specify. The legacy sandbox agent reported status 125 with a diagnostic of its own, which the acceptance still expected. 88c6bca9 tests: expect the exec refusal of an unusable working directory changes two existing test items, which check_test_changes.py lists:

  • run_managed_exec_configuration, the end-to-end scenario driver: for /does-not-exist, /etc/passwd and /root, it expects status 1 with the exec-refused error and the cwd-failed category, instead of 125 and the legacy diagnostic. It still checks that the workload never ran, since the workload would print on standard output.
  • PublicManagedExecAcceptanceTests._run_acceptance, the fake CLI of the driver's unit tests, reports that refusal. Without the driver's change, those 12 tests fail.

check_test_changes.py --base origin/feature/nvx-refactor reports 26 changed existing items and 12 added items: the 24 that the inventory below lists, and these two.

Rechecked on Windows at this tree, with the commands of check-nvx-rs where they apply: rustfmt; Clippy with -D warnings for --all-targets --all-features, --lib, --lib --no-default-features, --lib --bins --features cli and --lib --bins --no-default-features --features testing, and for --all-targets --all-features and --lib --bins --features cli on x86_64-unknown-linux-musl; cargo +1.89 check --all-targets --all-features; rustdoc with -D warnings; cargo test --locked --all-features, also in a process that ignores Ctrl-C, as the CI runner's Windows step does; scripts/public_api.py; the test layout; Markdown links; Ruff, and Pyright for Windows and Linux, of the two changed Python files; and test_microvm_tests. CI runs the full matrix on this head, including the public managed sandbox acceptance on KVM, MSHV and WHP.

Started sandboxes on nvx-init

The second CI run, at 88c6bca9 (run 42960814178), failed "Guests and sandboxes" on KVM, MSHV and WHP, in the public lifecycle acceptance: the refused start's outcome named the operation run, where the acceptance expected managed. I first read this as a loss of isolation, together with the workloads that #1466 saw share the guest's initial namespaces (comment). The plan settles it:

The acceptance still checked the sandbox agent's executions. Three commits follow 88c6bca9; the head was then d9c518e3684301eba829ad9f43252dbcc2e61bc0, tree 181ca18f3e124e56fc72e87c0182e050b2aecc0c, before the rebase onto #1470's merge below.

  • d231dfc3 sandbox: generate the machine ID of a started sandbox's VM: start sent nvx-init the view with the image's machine ID. The sandbox agent gave each boot a generated one, bound read-only over the image's, as doc/run.md documents and the smoke probe checks, so start now sends nvx_machine_id=generated. persistent_expected, the CLI test's projection of the persistent fixtures onto the new boot, expects that token.
  • 89df1a1a testing: report a run of a VM that the CLI's tokens manage: the fake OpenVMM reported managed for a VM whose lifecycle nvx_mode=managed selects, which real OpenVMM reports as a run, so the CLI's tests never saw the operation that failed in CI. It now reports managed only with --microvm-lifecycle managed.
  • d9c518e3 tests: check a started sandbox's executions as nvx-init runs them: the smoke probe takes a first argument, init. With it, the probe checks the host name, which nvx-init's command line encodes in hexadecimal (nvx_hostname=HEX), in place of the private namespaces and PID 1, and skips the /container cgroup. Every other check stands: the identity, the empty capability sets and no_new_privs, the single private mounts of the overlay root, /proc, /sys, /dev, /dev/pts and /dev/shm, the device view, the refused mknod, the generated machine ID, the refused port I/O, the read-only image, the shares and, with entry, nodev on every mount but /dev and its devpts, as nvx-init's root mounts them. Without init, the probe checks the one-shot sandbox run, which still boots the sandbox agent, as before. The lifecycle and RAM-upper acceptance and the workflow's managed share steps on Linux and Windows pass init and expect the operation run. They no longer require a clean scratch journal after stop, since nvx-init powers off with the scratch mounted; the lifecycle acceptance's restart reads the sandbox's state back instead. The guest's console no longer reaches OpenVMM's log once the guest's kernel moves it to hvc1, so the caller-owned share step reads the guest's refusal of the share's mapping, with EPERM, from start's error, managed guest could not mount a mapped host path (error 1), in place of the sandbox agent's NVX-MANAGED-ERROR: stage=host-mappings diagnostic in that log. doc/run.md and doc/guests/ubuntu-guest.md describe the executions and the stop as nvx-init runs them.

check_test_changes.py --base 88c6bca9 lists 89 changed existing items:

  • The CLI tests of started sandboxes check_lifecycle, lifecycle_of_a_sandbox_whose_upper_is_in_ram, stops_keep_an_outcome_report_that_the_caller_reads_whatever_its_umask and a_mapping_that_the_guest_refuses_leaves_no_usable_start expect the operation run. persistent_expected expects the generated machine ID. The fake OpenVMM's Options, parse, main and serve_client carry the report's operation.
  • The probe, as above. Its unit test test_namespace_check_rejects_the_initial_namespaces also loads check_hostname, which check_namespaces now calls; test_main_body_runs_the_profile_then_limits_and_shares stubs it and also runs the body with init. A new test, test_init_hostname_check_reads_the_encoded_command_line, checks the hexadecimal host name.
  • MANAGED_SUCCESS and MANAGED_REFUSAL expect the operation run. check_security_profile and RamUpperAcceptance.check_managed pass init. stop no longer reads the scratch's journal, so scratch_journal_clean, EXT4_INCOMPAT_RECOVER, the struct import and their unit test test_scratch_journal_check_reads_the_ext4_recovery_flag are retired. The fake CLI's _FakeSandboxCli._exec expects the probe arguments init and entry.
  • 67 lines of CI test wiring in run-nvx-microvm-tests.yml: the six managed share steps' init argument, run operation and removed journal checks, and the caller-owned step's check of the mapping's refusal. The four lines that the tool flags as added lines that may skip are that step's start, which now keeps its standard error, and the Windows steps' outcome checks, which now compare the operation with run.

Validation, on Windows unless named:

  • rustfmt; Clippy --all-targets --all-features with -D warnings; rustdoc with -D warnings; the fake OpenVMM's unit tests.
  • The cli integration tests: 84 of 85 passed in one run, and the 85th, one_shot::a_ready_console_stream_is_not_throttled_by_idle_sleeps, a timing test that the machine's load delayed, passed alone.
  • Ruff, and Pyright for Linux and Windows, of the changed Python files; ShellCheck and the pinned shfmt of the probe; the test layout; Markdown links.
  • PublicManagedExecAcceptanceTests, PublicSandboxLifecycleTests, PublicSandboxProxyOnlyTests and PublicSandboxRamUpperTests: 29 tests on Windows, 2 of them Linux-only skips, and 29 on Linux under WSL.
  • SandboxSmokeProfileTests and SandboxSmokeShareTests, 21 tests, on Linux under WSL.
  • CiConfigurationTests on Linux under WSL. Its one failure there, test_linux_runners_terminate_openvmm_left_by_earlier_jobs, fails on 88c6bca9 under WSL too.

The hardware acceptance runs in CI. Its run at a6a36952, the previous form of d9c518e3, 42977072177, passed "Guests and sandboxes" on WHP, the public lifecycle, proxy and RAM-upper acceptance and the managed share steps among them. On KVM and MSHV, its one failure was the caller-owned share step's search of OpenVMM's log for the guest's diagnostic, which d9c518e3 replaces as above; the steps after it did not run there. Its run at d9c518e3, 42979403236, passed every job.

#1470 then merged into feature/nvx-refactor at 8b775211c295389602924864f633ef9da60efe53, so the fifteen own commits are rebased onto it as 1c887d5a1f..bcaaa60e2879715f877bb1233f5b5a19379ae65c, tree e61c4ecb275aaddb2e6fc61c0ce3d62c4b2adf01. They apply without conflict: #1470 also changes nvx-rs/src/host/vm.rs and scripts/test-layout-allowlist.json, elsewhere. At this head, on Windows:

  • rustfmt; Clippy with -D warnings for the five configurations of check-nvx-rs and the two on x86_64-unknown-linux-musl; cargo +1.89 check; rustdoc with -D warnings; scripts/public_api.py; the test layout; and Markdown links all pass.
  • The cli integration tests pass: 85, with 2 ignored.
  • Of 1,443 library tests, 1,442 pass. The exception, host::cli::snapshots::capture::tests::a_capture_ack_and_successful_output_without_timely_exit_are_stopped_but_never_finished, a test of Add named BLOCKLESS snapshot capture (SL3.7b) #1464's capture that gives its own spawned helper one second to acknowledge, also fails alone once in three runs on this loaded machine, and passed in CI at d9c518e3.

CI runs on this head.

Dependency and source

Before the rebase above, a dependent Draft on #1458, base esaurez/sl35b-published-inputs-e1c83f5b-20261110. Intake pinned e4843296 (old 1441/67); the consumer then ordinary-merged the actual published parent 928edaad5a0cebbd55d85735dded0032cbf91b6b, which contains public 1441/6bc. Native remains 14dc4116ec7e57567633c4b7b07892aca3a58522; feature input is 4192cfd1.

No parent QA/review/runtime result is inherited. Known parent review findings remain with their producer owner; this PR contains only the consumer delta.

Validation status

Production-code qualification complete at 9bf02890; acceptance-only successor 369ad2d5 is now scoped-qualified on both approved hosts. Not a full-CI or merge verdict; CI runs on the rebased head. The successor changes only the Python acceptance reader and its unit fixtures; all other source blobs, including the complete Rust tree, are unchanged. Linux CLI/store/session at 9bf02890: 156/156 passed. Windows CLI/store/session: 145/145 passed, including the inherited-directory preservation regression.

The unchanged production source at 9bf02890 passes relevant warning-denied CLI/all-targets/all-features Clippy on both hosts, Rust 1.89 all-targets/all-features checks, and warning-denied documentation. Linux additionally passes the action's default/no-default/testing lint sets, all/default/no-default tests, synthetic bundling, both-platform public API snapshots, format and test layout. Independent adapter protocol/host lint/build/test/doc/MSRV pass on both hosts; the static-musl guest configuration also passes on Linux. Required test-change inventories were generated against both the actual parent and origin/dev.

Actual production CLI + current production guest: MSHV and WHP both passed today's EROFS scratch and RAM flows: provision -> start -> exec -> stop -> start -> exec -> stop -> deprovision. Both also pass recorded pre-commit CLI-death recovery, held native-process exit proof, mapping/workload identity across restart, reusable scratch claim, preserved caller-owned input and complete owned state/VM teardown. Native outcomes were read on both hosts; Linux additionally verifies mode 0600 despite child umask 0777. Pure-cli executables were rebuilt after testing and copied outside mutable Cargo outputs (Linux e634d29c, Windows 5c92188a).

Final acceptance-only qualification: all three existing lifecycle/RAM/proxy Python unit classes passed on Linux (18 tests) and Windows (18 tests, 2 existing platform skips), along with affected Ruff format/lint, native-platform Pyright and test layout. Own-parent and actual canonical origin/dev inventories passed. The remote clones lack the origin/dev alias, so their failed alias attempts remain failures; the real dev inventory was executed in the canonical checkout at 256ebe3d, without fetching private origins or fabricating refs.

At exact 369ad2d5, both MSHV and WHP also passed the real production lifecycle/recovery driver with the changed LifecycleAcceptance.require_started method: scratch twice, RAM twice and recovered launch once. These five actual checks per host verify the strict saved schema, matching owned VM directory/capability, live process, endpoint, exact owned process set and credential-free evidence. Both complete today's scratch/RAM provision/start/exec/stop/restart/deprovision, pre-commit CLI-death recovery, held-process exit proof and complete owned-state teardown. This is actual changed-reader coverage, not a claim that the full Ubuntu lifecycle/RAM/proxy scenario suite was rerun.

Only the two Python acceptance/unit files differ from 9bf02890; all other source blobs and the complete Rust tree are identical. These new runs deliberately reuse the qualified immutable pure-cli binaries built at 9bf02890, retain that producer identity, and reverify their full hashes plus the complete unchanged native/kernel/production-guest input dictionaries. They do not relabel old Rust results as a new build or rebuild unchanged heavy inputs. Both hosts now have clean final source and no live original owned jobs/transfer roots or VM state directories.

The actual Opus 5.5/MAX/LONG production review initially found three medium issues; the follow-up found one refusal-cleanup issue. All four are fixed, and the exact 9bf02890 retained-context review is CLEAR. Runtime-observed configuration and actual claude-opus-5-5 usage (952000-token context) are retained separately from the request settings. The acceptance-only delta and its final guarded-list typing fix have separate retained Opus 5.5/MAX/LONG incremental reviews, both CLEAR. The original 9bf02890 Copilot request was admitted PENDING at 2026-11-10T23:18:30Z (HTTP 201); it is not a request for the successor. The distinct exact 369ad2d5 request was admitted PENDING at 2026-11-11T00:21:41Z (HTTP 201, RR_kwDOUGPhMc7ulG0S), after all scoped checks, publication/readback, known-request reconciliation, a new durable once-intent and matching immediate before/after head guards. The old request was not replayed; no feedback or CI verdict was awaited. Request admission is not a verdict; full CI, feedback handling and readiness/merge remain separate.

Earlier failed compile/diagnostic/fixture attempts and old-head passes remain historical receipts, not relabeled final-head proof. The final Windows fixture creates its own private parent instead of inheriting broad data-drive permissions; the test still requires refusal without ACL mutation, unchanged identity/data/refusal, no config and the exact original directory entry set.

Full input comparison rejected reuse of the older SL7.8a guest at five changed inputs. The guest was regenerated from the current consumed inputs at e5eaf029: guest source 023d9772, nvx-init 5e304155, initramfs 3f9378a3, with packed /init, helper and provenance checks. Verified unchanged native/kernel inputs are reused only after complete current-input comparison.

Approved Windows contract limit: provision now explicitly refuses a state directory that the VM API cannot open as a private VM directory. It does not rewrite, weaken or otherwise mutate the caller's ACL. An existing inherited user-profile DACL may still fail this check; producer alignment of StateDir and VmDir policies is a separate follow-up, not a claim that every such directory is supported.

Explicitly approved ownership change: adopt the new caller-owned stable-path contract. The caller keeps its image paths stable during launch and while the VM uses them. This no longer promises Linux replacement-after-check safety through inherited descriptors, or Windows inherited-image guards with a Ready-time release boundary. No private checked-file bridge, public FD API or fallback is added.

Existing tests

Kept the CLI store's journal-before-spawn, interrupted/unverified-launch recovery, runtime identity and restrictive-umask regressions. Rewrote the old runtime-record-format expectations for the exact saved VM, the old stop-timeout scenario for verified forced stop (including ACK without exit), and old removal-error conversion checks as real ownership/removal cases. Added a connection-framing regression proving that an already authenticated connection is not attached twice.

The approved two race-test rewrites are start_hands_openvmm_the_images_that_it_checked -> start_opens_the_stable_caller_paths_that_provision_checked, and a_start_that_ends_before_openvmm_opens_the_images_leaves_it_the_checked_ones -> an_interrupted_start_keeps_its_claim_until_recovery_confirms_exit. They now assert the actual stable image paths, prepared journal values, unchanged caller-owned inputs, inherited scratch claim after CLI death, and verified recovery/exit before claim and record cleanup. They do not claim the retired FD/Ready-guard guarantees. Existing stale-binding refusals remain.

The persistent configuration/format fixtures project onto CLI-owned managed boot tokens without changing the legacy stateless path. The exact own-parent inventory contains 24 modifications/removals of existing test/helper symbols and 12 added symbols (not a claim of 12 new test cases):

Existing symbols Disposition
Lock state_dir; removed removals_keep_the_messages_of_deprovision Point at CLI-store names; replace obsolete error-conversion checks with real live-record, foreign-file, owned-temporary and neighbour-preservation cases.
Lifecycle assert_runtime_record, check_lifecycle, failed_start, imports, RAM lifecycle Assert exact SavedVm/verified process and managed argv; keep full repeated lifecycle/RAM coverage and meaningful failed-start status/cleanup.
Format imports and configurations_of_every_format_start_and_exec Project persistent launches onto current tokens/stable paths while retaining accepted provision-reader formats and stale-binding refusals.
a_mapping_that_the_guest_refuses_leaves_no_usable_start Keep the primary protocol refusal and cleanup; additionally require the secondary shutdown timeout when STOP is ignored.
Old runtime-format case Refuse earlier runtime stores without migrating a running sandbox or adding a dual reader.
Old stop-timeout case Require verified forced exit after grace even when STOP was acknowledged; no ACK-as-exit assumption.
Two image race cases Only the explicitly approved stable-path/claim-recovery rewrites named above; no retired descriptor or Ready-guard claim.
Consumed CLI-store test hook Remove the used-up dead-code expectation and tighten its existing layout allowlist entry.
Fake Options, main, parse, serve_client Support the cold persistent route and focused authenticated-WAIT/late-report fixtures; legacy stateless behaviour remains unchanged.
Python acceptance RUNTIME_FIELDS, read_runtime, LifecycleAcceptance.require_started Require the exact SavedVm schema and matching owned VM directory/capability. Preserve runtime/process/endpoint/liveness and refusal/restart/recovery/cleanup oracles; replace the newly secret-bearing evidence field with its SHA256 fingerprint. No old/new compatibility fallback.
Python _FakeSandboxCli._launch, complete-public-lifecycle evidence oracle Emit the actual saved DTO and require credential-free evidence. Add a focused valid-record/old-schema/foreign-directory/invalid-or-mismatched-capability matrix. Existing lifecycle/RAM/proxy scenarios remain intact.

The failed-start refusal still requires a prompt exited error and complete record cleanup; when the detached VM produces no report, the diagnostic now says so rather than inventing the raw child status that the new verified-process API does not expose. The mapping-refusal case retains its primary protocol error and now also checks the explicitly surfaced secondary shutdown timeout when the guest ignores STOP.

The status boundary is explicit in host/process.rs: Exited(()) proves exit, not status; Process::wait returns that proof, while vm::Stopped separately exposes a fallible optional native report. Startup uses the genuine report category/status when available, and otherwise remains an abnormal error[exited] with an absent-report diagnostic.

Added focused regressions for EOF while the broker waits followed by delayed report publication; Ctrl-C during endpoint retry, authenticated WAIT and failure grace, with held-process exit proof; and Windows early private-directory refusal preserving the directory, foreign file and original refusal. The Windows request_termination helper is deletion-only cleanup: its last callers were the private launcher terminate/kill methods retired by this migration. Verified ProcessHandle::kill and owned-child cleanup remain.

Size

Measured against the actual parent 928edaad: 558 core lines, 8 counted files. Deleted old code is reported separately, not subtracted. Tests/fixtures, deleted-only consumed private helpers, and the visibility/import-only host/session.rs and cli/store/mod.rs changes are excluded from the file count; the forced re-export line is counted. The eighth file is the explicitly approved, minimal crate-private host/vm.rs transport bridge. The two late Python files are explicitly classified acceptance/unit-test infrastructure, not a ninth production file: the lifecycle helper's header and sole scenario callers identify its test purpose, and ci.py excludes it from production benchmark inputs. Their changes and this exclusion are itemized in the inventory/measurement receipt. The 600-core hard stop is unchanged.

@esaurez Enrique Saurez (esaurez) added tests-modified Changes existing tests, test data, or test wiring feat-nvx-refactor labels Nov 10, 2026
@github-actions

github-actions Bot commented Nov 10, 2026 •

Copy link
Copy Markdown
Contributor

This pull request changes existing tests

It modifies or removes 107 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 state_dir in host::cli::sandbox::lock::tests (only the paths that it names changed) nvx-rs/src/host/cli/sandbox/lock/tests.rs:3 nvx-rs/src/host/cli/sandbox/lock/tests.rs:3
modified helper fn assert_runtime_record in cli/lifecycle (tests) nvx-rs/tests/cli/lifecycle.rs:142 nvx-rs/tests/cli/lifecycle.rs:138
modified helper fn check_lifecycle in cli/lifecycle (tests) nvx-rs/tests/cli/lifecycle.rs:550 nvx-rs/tests/cli/lifecycle.rs:549
modified helper fn failed_start in cli/lifecycle (tests) nvx-rs/tests/cli/lifecycle.rs:1159 nvx-rs/tests/cli/lifecycle.rs:1158
modified helper def PublicManagedExecAcceptanceTests._run_acceptance in scripts/test_microvm_tests.py scripts/test_microvm_tests.py:416 scripts/test_microvm_tests.py:416
modified helper def _FakeSandboxCli._launch in scripts/test_microvm_tests.py scripts/test_microvm_tests.py:1172 scripts/test_microvm_tests.py:1175
modified helper def _FakeSandboxCli._exec in scripts/test_microvm_tests.py scripts/test_microvm_tests.py:1241 scripts/test_microvm_tests.py:1244
modified imports the imports of cli/formats (tests) nvx-rs/tests/cli/formats.rs:13 nvx-rs/tests/cli/formats.rs:13
modified imports the imports of cli/lifecycle (tests) nvx-rs/tests/cli/lifecycle.rs:6 nvx-rs/tests/cli/lifecycle.rs:6
modified test fn a_mapping_that_the_guest_refuses_leaves_no_usable_start in cli (tests) nvx-rs/tests/cli.rs:177 nvx-rs/tests/cli.rs:177
modified test fn runtime_records_of_earlier_formats_fail_and_name_their_format in cli (tests) (renamed to runtime_records_of_earlier_stores_are_refused_without_migration) nvx-rs/tests/cli.rs:1087 nvx-rs/tests/cli.rs:1098
modified test fn stop_reports_its_timeout_and_leaves_the_vm_running in cli (tests) (renamed to stop_forces_the_verified_vm_after_its_grace_period_even_after_an_acknowledgement) nvx-rs/tests/cli.rs:1144 nvx-rs/tests/cli.rs:1156
modified test fn start_hands_openvmm_the_images_that_it_checked in cli (tests) (renamed to start_opens_the_stable_caller_paths_that_provision_checked) nvx-rs/tests/cli.rs:1920 nvx-rs/tests/cli.rs:1920
modified test fn a_start_that_ends_before_openvmm_opens_the_images_leaves_it_the_checked_ones in cli (tests) (renamed to an_interrupted_start_keeps_its_claim_until_recovery_confirms_exit) nvx-rs/tests/cli.rs:2021 nvx-rs/tests/cli.rs:2006
modified test fn configurations_of_every_format_start_and_exec in cli/formats (tests) nvx-rs/tests/cli/formats.rs:188 nvx-rs/tests/cli/formats.rs:188
modified test fn lifecycle_of_a_sandbox_whose_upper_is_in_ram in cli/lifecycle (tests) nvx-rs/tests/cli/lifecycle.rs:637 nvx-rs/tests/cli/lifecycle.rs:636
modified test fn stops_keep_an_outcome_report_that_the_caller_reads_whatever_its_umask in cli/lifecycle (tests) nvx-rs/tests/cli/lifecycle.rs:720 nvx-rs/tests/cli/lifecycle.rs:719
modified test def PublicSandboxLifecycleTests.test_acceptance_drives_the_complete_public_lifecycle in scripts/test_microvm_tests.py scripts/test_microvm_tests.py:1564 scripts/test_microvm_tests.py:1601
modified test def SandboxSmokeProfileTests.test_namespace_check_rejects_the_initial_namespaces in scripts/test_nvx_tools.py scripts/test_nvx_tools.py:20303 scripts/test_nvx_tools.py:20303
modified test def SandboxSmokeProfileTests.test_main_body_runs_the_profile_then_limits_and_shares in scripts/test_nvx_tools.py scripts/test_nvx_tools.py:20668 scripts/test_nvx_tools.py:20692
removed hook the test hooks of host::cli::store nvx-rs/src/host/cli/store/mod.rs:1
removed test fn removals_keep_the_messages_of_deprovision in host::cli::sandbox::lock::tests nvx-rs/src/host/cli/sandbox/lock/tests.rs:66
removed test def PublicSandboxLifecycleTests.test_scratch_journal_check_reads_the_ext4_recovery_flag in scripts/test_microvm_tests.py scripts/test_microvm_tests.py:1782

End-to-end tests

Change What Before After
modified file guest/common/nvx-sandbox-smoke guest/common/nvx-sandbox-smoke guest/common/nvx-sandbox-smoke
modified helper def run_managed_exec_configuration in scripts/nvx_tools/managed_exec_tests.py scripts/nvx_tools/managed_exec_tests.py:176 scripts/nvx_tools/managed_exec_tests.py:176
modified helper assignment MANAGED_SUCCESS in scripts/nvx_tools/sandbox_lifecycle_tests.py scripts/nvx_tools/sandbox_lifecycle_tests.py:71 scripts/nvx_tools/sandbox_lifecycle_tests.py:70
modified helper assignment MANAGED_REFUSAL in scripts/nvx_tools/sandbox_lifecycle_tests.py scripts/nvx_tools/sandbox_lifecycle_tests.py:75 scripts/nvx_tools/sandbox_lifecycle_tests.py:74
modified helper assignment RUNTIME_FIELDS in scripts/nvx_tools/sandbox_lifecycle_tests.py scripts/nvx_tools/sandbox_lifecycle_tests.py:116 scripts/nvx_tools/sandbox_lifecycle_tests.py:115
modified helper def read_runtime in scripts/nvx_tools/sandbox_lifecycle_tests.py scripts/nvx_tools/sandbox_lifecycle_tests.py:376 scripts/nvx_tools/sandbox_lifecycle_tests.py:366
modified helper def LifecycleAcceptance.require_started in scripts/nvx_tools/sandbox_lifecycle_tests.py scripts/nvx_tools/sandbox_lifecycle_tests.py:730 scripts/nvx_tools/sandbox_lifecycle_tests.py:747
modified helper def LifecycleAcceptance.check_security_profile in scripts/nvx_tools/sandbox_lifecycle_tests.py scripts/nvx_tools/sandbox_lifecycle_tests.py:779 scripts/nvx_tools/sandbox_lifecycle_tests.py:802
modified helper def LifecycleAcceptance.stop in scripts/nvx_tools/sandbox_lifecycle_tests.py scripts/nvx_tools/sandbox_lifecycle_tests.py:857 scripts/nvx_tools/sandbox_lifecycle_tests.py:880
modified helper def RamUpperAcceptance.check_managed in scripts/nvx_tools/sandbox_ram_upper_tests.py scripts/nvx_tools/sandbox_ram_upper_tests.py:188 scripts/nvx_tools/sandbox_ram_upper_tests.py:188
modified imports the imports of scripts/nvx_tools/sandbox_lifecycle_tests.py scripts/nvx_tools/sandbox_lifecycle_tests.py:1 scripts/nvx_tools/sandbox_lifecycle_tests.py:1
removed helper assignment EXT4_INCOMPAT_RECOVER in scripts/nvx_tools/sandbox_lifecycle_tests.py scripts/nvx_tools/sandbox_lifecycle_tests.py:70
removed helper def scratch_journal_clean in scripts/nvx_tools/sandbox_lifecycle_tests.py scripts/nvx_tools/sandbox_lifecycle_tests.py:288

Test doubles

Change What Before After
modified helper struct Options in testing::fake_openvmm nvx-rs/src/testing/fake_openvmm.rs:348 nvx-rs/src/testing/fake_openvmm.rs:350
modified helper fn main in testing::fake_openvmm nvx-rs/src/testing/fake_openvmm.rs:486 nvx-rs/src/testing/fake_openvmm.rs:507
modified helper fn parse in testing::fake_openvmm nvx-rs/src/testing/fake_openvmm.rs:1301 nvx-rs/src/testing/fake_openvmm.rs:1335
modified helper fn serve_client in testing::fake_openvmm nvx-rs/src/testing/fake_openvmm.rs:2913 nvx-rs/src/testing/fake_openvmm.rs:2963

CI test wiring

Change What Before After
added a line that may skip line if nvx-rs/target/release/nvx-rs sandbox start --state-dir "${state}" 2>"${errors}"; then .github/workflows/run-nvx-microvm-tests.yml:550
added a line that may skip line if ($outcome.operation -ne "run" -or $outcome.category -ne "success" -or $outcome.status_code -ne 0) { .github/workflows/run-nvx-microvm-tests.yml:987
added a line that may skip line if ($outcome.operation -ne "run" -or $outcome.category -ne "success" -or $outcome.status_code -ne 0) { .github/workflows/run-nvx-microvm-tests.yml:1136
added a line that may skip line if ($outcome.operation -ne "run" -or $outcome.category -ne "success" -or $outcome.status_code -ne 0) { .github/workflows/run-nvx-microvm-tests.yml:1234
modified line python3 - "${state}/outcome.json" "${scratch}" <<'EOF' .github/workflows/run-nvx-microvm-tests.yml:460 .github/workflows/run-nvx-microvm-tests.yml:461
modified line expected = {"operation": "managed", "category": "success", "status_code": 0} .github/workflows/run-nvx-microvm-tests.yml:467 .github/workflows/run-nvx-microvm-tests.yml:467
modified line if nvx-rs/target/release/nvx-rs sandbox start --state-dir "${state}"; then .github/workflows/run-nvx-microvm-tests.yml:553 .github/workflows/run-nvx-microvm-tests.yml:549
modified line grep -q 'NVX-MANAGED-ERROR: stage=host-mappings' "${state}/openvmm.log" .github/workflows/run-nvx-microvm-tests.yml:559 .github/workflows/run-nvx-microvm-tests.yml:559
modified line "${smoke_arguments[@​]}" .github/workflows/run-nvx-microvm-tests.yml:693 .github/workflows/run-nvx-microvm-tests.yml:694
modified line python3 - "${state}/outcome.json" "${scratch}" <<'EOF' .github/workflows/run-nvx-microvm-tests.yml:697 .github/workflows/run-nvx-microvm-tests.yml:696
modified line expected = {"operation": "managed", "category": "success", "status_code": 0} .github/workflows/run-nvx-microvm-tests.yml:704 .github/workflows/run-nvx-microvm-tests.yml:702
modified line "${smoke_arguments[@​]}" .github/workflows/run-nvx-microvm-tests.yml:829 .github/workflows/run-nvx-microvm-tests.yml:823
modified line python3 - "${state}/outcome.json" "${scratch}" <<'EOF' .github/workflows/run-nvx-microvm-tests.yml:833 .github/workflows/run-nvx-microvm-tests.yml:825
modified line expected = {"operation": "managed", "category": "success", "status_code": 0} .github/workflows/run-nvx-microvm-tests.yml:840 .github/workflows/run-nvx-microvm-tests.yml:831
modified line if ($outcome.operation -ne "managed" -or $outcome.category -ne "success" -or $outcome.status_code -ne 0) { .github/workflows/run-nvx-microvm-tests.yml:997 .github/workflows/run-nvx-microvm-tests.yml:987
modified line Invoke-Nvx sandbox exec --state-dir $state @​smokeArguments .github/workflows/run-nvx-microvm-tests.yml:1148 .github/workflows/run-nvx-microvm-tests.yml:1125
modified line if ($outcome.operation -ne "managed" -or $outcome.category -ne "success" -or $outcome.status_code -ne 0) { .github/workflows/run-nvx-microvm-tests.yml:1159 .github/workflows/run-nvx-microvm-tests.yml:1136
modified line Invoke-Nvx sandbox exec --state-dir $state @​smokeArguments .github/workflows/run-nvx-microvm-tests.yml:1259 .github/workflows/run-nvx-microvm-tests.yml:1223
modified line if ($outcome.operation -ne "managed" -or $outcome.category -ne "success" -or $outcome.status_code -ne 0) { .github/workflows/run-nvx-microvm-tests.yml:1270 .github/workflows/run-nvx-microvm-tests.yml:1234
removed line import struct .github/workflows/run-nvx-microvm-tests.yml:462
removed line with open(sys.argv[2], "rb") as image: .github/workflows/run-nvx-microvm-tests.yml:470
removed line image.seek(1024 + 0x60) .github/workflows/run-nvx-microvm-tests.yml:471
removed line if struct.unpack("<I", image.read(4))[0] & 0x4: .github/workflows/run-nvx-microvm-tests.yml:472
removed line sys.exit("managed stop left the scratch filesystem mounted") .github/workflows/run-nvx-microvm-tests.yml:473
removed line import struct .github/workflows/run-nvx-microvm-tests.yml:699
removed line with open(sys.argv[2], "rb") as image: .github/workflows/run-nvx-microvm-tests.yml:707
removed line image.seek(1024 + 0x60) .github/workflows/run-nvx-microvm-tests.yml:708
removed line if struct.unpack("<I", image.read(4))[0] & 0x4: .github/workflows/run-nvx-microvm-tests.yml:709
removed line sys.exit("managed stop left the scratch filesystem mounted") .github/workflows/run-nvx-microvm-tests.yml:710
removed line import struct .github/workflows/run-nvx-microvm-tests.yml:835
removed line with open(sys.argv[2], "rb") as image: .github/workflows/run-nvx-microvm-tests.yml:843
removed line image.seek(1024 + 0x60) .github/workflows/run-nvx-microvm-tests.yml:844
removed line if struct.unpack("<I", image.read(4))[0] & 0x4: .github/workflows/run-nvx-microvm-tests.yml:845
removed line sys.exit("managed stop left the scratch filesystem mounted") .github/workflows/run-nvx-microvm-tests.yml:846
removed line } .github/workflows/run-nvx-microvm-tests.yml:999
removed line $image = [IO.File]::OpenRead($scratch) .github/workflows/run-nvx-microvm-tests.yml:1002
removed line try { .github/workflows/run-nvx-microvm-tests.yml:1003
removed line $null = $image.Seek(1024 + 0x60, [IO.SeekOrigin]::Begin) .github/workflows/run-nvx-microvm-tests.yml:1004
removed line $incompat = New-Object byte[] 4 .github/workflows/run-nvx-microvm-tests.yml:1005
removed line if ($image.Read($incompat, 0, 4) -ne 4) { throw "scratch superblock is truncated" } .github/workflows/run-nvx-microvm-tests.yml:1006
removed line } finally { .github/workflows/run-nvx-microvm-tests.yml:1007
removed line $image.Dispose() .github/workflows/run-nvx-microvm-tests.yml:1008
removed line } .github/workflows/run-nvx-microvm-tests.yml:1009
removed line if ([BitConverter]::ToUInt32($incompat, 0) -band 4) { .github/workflows/run-nvx-microvm-tests.yml:1010
removed line throw "managed stop left the scratch filesystem mounted" .github/workflows/run-nvx-microvm-tests.yml:1011
removed line } .github/workflows/run-nvx-microvm-tests.yml:1161
removed line $image = [IO.File]::OpenRead($scratch) .github/workflows/run-nvx-microvm-tests.yml:1164
removed line try { .github/workflows/run-nvx-microvm-tests.yml:1165
removed line $null = $image.Seek(1024 + 0x60, [IO.SeekOrigin]::Begin) .github/workflows/run-nvx-microvm-tests.yml:1166
removed line $incompat = New-Object byte[] 4 .github/workflows/run-nvx-microvm-tests.yml:1167

... and 17 more, which the job's summary lists.

Relocated unchanged: 0; added: 15. Compared the merge base 8b775211c295 with bcaaa60e2879.

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 remain for the migration of persistent sandbox lifecycle commands to the CLI-owned store and host::vm.

No open issues 🎉

What changed in this PR

This PR implements SL3.5b of #1110, moving persistent sandbox lifecycle commands onto the CLI-owned store and host::vm. It preserves the existing provision format and leaves stateless runs and the public SDK API unchanged.

Changes:

  • Migrates start, exec, stop, and deprovision together, with journaled launches, verified process identity, and authenticated reattachment.
  • Adds graceful shutdown followed by verified forced termination, plus ownership-aware cleanup that retains indeterminate launch evidence.
  • Updates lifecycle fixtures and adds regressions for interrupted starts, delayed outcome reports, connection reuse, and Windows directory refusal.
File Description
scripts/​test-layout-allowlist.json Removes the consumed CLI-store allowance.
nvx-rs/​tests/​cli/​openvmm.rs Adds managed-boot expectations and stable image-path assertions.
nvx-rs/​tests/​cli/​lifecycle.rs Checks saved VM records and migrated lifecycle behavior.
nvx-rs/​tests/​cli/​formats.rs Updates persistent launch expectations across configuration formats.
nvx-rs/​tests/​cli.rs Covers forced stop, recovery, stable paths, delayed reports, and interrupts.
nvx-rs/​src/​testing/​fake_openvmm.rs Supports migrated launches and attachment-delay failure fixtures.
nvx-rs/​src/​host/​vm.rs Adds a crate-private transport factory for reattachment.
nvx-rs/​src/​host/​supervisor/​launch.rs Removes the obsolete CLI child-process accessor.
nvx-rs/​src/​host/​spawn.rs Removes superseded private termination helpers.
nvx-rs/​src/​host/​session.rs Makes the authenticated connection available within the crate.
nvx-rs/​src/​host/​platform/​windows/​spawn.rs Removes the unused Windows termination helper.
nvx-rs/​src/​host/​cli/​store/​names.rs Identifies owned temporary files for cleanup.
nvx-rs/​src/​host/​cli/​store/​mod.rs Exposes the locked store internally and removes its dead-code exemption.
nvx-rs/​src/​host/​cli/​store/​locked.rs Verifies that an opened directory contains the held lock.
nvx-rs/​src/​host/​cli/​session/​tests.rs Tests connection reuse without a second authentication handshake.
nvx-rs/​src/​host/​cli/​session.rs Adds verified, interruptible reattachment with transport observation.
nvx-rs/​src/​host/​cli/​sandbox/​lock/​tests.rs Tests directory refusal, owned cleanup, and foreign-file preservation.
nvx-rs/​src/​host/​cli/​sandbox/​lock.rs Migrates lifecycle locking and implements owned-state removal.
nvx-rs/​src/​host/​cli/​sandbox/​lifecycle/​stop.rs Implements graceful shutdown, verified forced stop, and deprovision.
nvx-rs/​src/​host/​cli/​sandbox/​lifecycle/​start.rs Builds VM configuration, journals launches, and migrates start and exec.
nvx-rs/​src/​host/​cli/​sandbox/​lifecycle/​mod.rs Switches runtime lookup and identity checks to saved VM state.
nvx-rs/​src/​host/​cli/​sandbox/​lifecycle/​directory.rs Removes the obsolete unlink helper.

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

The persistent sandbox lifecycle migration has no active review issues.

No open issues 🎉

Review effort: Balanced

@ppenna
Pedro Henrique Penna (ppenna) changed the base branch from esaurez/sl35b-published-inputs-e1c83f5b-20261110 to feature/nvx-refactor November 11, 2026 03:57
@ppenna
Pedro Henrique Penna (ppenna) force-pushed the esaurez/sl35b-lifecycle-4d48405a-20261110 branch from 369ad2d to 5b7882a Compare November 11, 2026 03:57
@ppenna
Pedro Henrique Penna (ppenna) marked this pull request as ready for review November 11, 2026 03:58
@ppenna

Copy link
Copy Markdown
Contributor

Not merged: CLI-owned sandbox workloads lose their namespace isolation

This branch is rebased onto feature/nvx-refactor at 5947e4e8, after #1453, #1457, #1441 and #1464 merged. Its CI fails, and the cause is in production code, so I'm leaving it unmerged and returning it to draft.

Run 42960814178 at 88c6bca9 fails "Guests and sandboxes" on KVM, MSHV and WHP, in the sandbox lifecycle scenario: the refused managed start's outcome is {'operation': 'run', 'category': 'guest-exit', 'status_code': 125}.

Cause. vm_config starts the sandbox through host::vm with nvx_sandbox=1 nvx_mode=managed nvx_identity=UID:GID. It passes no nvx_lifecycle= token, and host::config passes OpenVMM no --microvm-lifecycle. Since #1453, the guest's init hands such a boot to nvx-init. nvx-init serves executions through AgentConfig::layered, which is direct: true, entry: None. Without an entry, sys::spawn forks the workload in the guest's initial namespaces. The sandbox agent cloned it into new mnt, pid and uts namespaces.

Effects.

  1. A working directory that the workload cannot enter is now refused with exec-refused, status 1, instead of 125. 88c6bca9 updated the managed execution acceptance to expect this, so CI now gets past its first scenario.
  2. OpenVMM reports operation run, because it reports managed only under --microvm-lifecycle managed (report.rs). MANAGED_SUCCESS and MANAGED_REFUSAL expect managed. This is the current failure. The unit tests miss it because the fake OpenVMM treats nvx_mode=managed as the managed lifecycle and reports managed.
  3. Workloads share the guest's initial mnt, pid and uts namespaces, which the smoke probe's check_namespaces rejects. CI would hit this next. SL3.5a: provision typed EROFS/GPT images and bounded RAM uppers #1466 already reproduced it on MSHV and WHP hosts: NVX-UBUNTU-SANDBOX-FAIL: the workload shares the initial mnt namespace.

Effect 3 breaks the sandbox isolation contract, so I didn't change any acceptance expectation for effects 2 or 3. The fix needs a decision from the owner:

  • Either give nvx-init's sandbox executions the namespace entry, or start CLI sandboxes through the sandbox agent again. If the old refusal comes back, revert 88c6bca9.
  • Either pass --microvm-lifecycle managed for sandbox starts, or make the acceptance expect run.

#1466 stays stacked on this branch.

@ppenna

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

Copy link
Copy Markdown
Contributor

Correction to my comment above: the plan already decides both questions, so I've updated the acceptance rather than the guest.

d9c518e3 gives the smoke probe an init mode for these executions. The lifecycle, RAM-upper and workflow checks of started sandboxes use it and expect run. d231dfc3 keeps the generated per-boot machine ID that the sandbox agent gave. 89df1a1a makes the fake OpenVMM report run as OpenVMM does. The PR description has the details and the test changes. CI runs on d9c518e3.

@ppenna
Pedro Henrique Penna (ppenna) force-pushed the esaurez/sl35b-lifecycle-4d48405a-20261110 branch from a6a3695 to d9c518e Compare November 11, 2026 07:25
Enrique Saurez and others added 8 commits November 10, 2026 23:50
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 4d48405a-34b7-4fe1-8802-e0076917b3a5
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 4d48405a-34b7-4fe1-8802-e0076917b3a5
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 4d48405a-34b7-4fe1-8802-e0076917b3a5
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 4d48405a-34b7-4fe1-8802-e0076917b3a5
Preserve provision binding refusals, inherited scratch claims and verified
interrupted-start recovery without claiming the old descriptor/Ready-guard races.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 4d48405a-34b7-4fe1-8802-e0076917b3a5
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 4d48405a-34b7-4fe1-8802-e0076917b3a5
… refusals

Reuse the single authenticated transport through the bounded crate-private
bridge; retain startup self-exit reports and interruptible grace. Refuse
unstartable inherited VM directories during provision without ACL mutation.
Retire the Windows child signal helper left unused by this migration.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 4d48405a-34b7-4fe1-8802-e0076917b3a5
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 4d48405a-34b7-4fe1-8802-e0076917b3a5
Enrique Saurez and others added 7 commits November 10, 2026 23:50
Exercise the inherited owner-only DACL case independently of a shared
temporary parent's ACL, and preserve the directory's exact entry set.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 4d48405a-34b7-4fe1-8802-e0076917b3a5
Read the exact SDK runtime contract, verify its owned VmDir and capability, and retain lifecycle identity, recovery and endpoint oracles without copying the credential into acceptance evidence. Update only the directly coupled acceptance fixtures and regression cases.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 4d48405a-34b7-4fe1-8802-e0076917b3a5
Keep the same runtime list/byte/range refusal guards, with explicit object and checked integer-list views so warning-denied Python type analysis does not introduce Unknown values.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 4d48405a-34b7-4fe1-8802-e0076917b3a5
A persistent sandbox now starts through host::vm without the old OpenVMM
nvx_lifecycle= token, so nvx-init serves its executions. Its agent refuses a
working directory that the workload cannot enter with the cwd-failed
application error, which nvx-rs reports as exec-refused with status 1, as the
error code table of doc/usage.md and the CLI's lifecycle test of a missing
working directory already specify. The legacy sandbox agent reported status
125 with a diagnostic of its own instead, which the public managed execution
acceptance still expected, so it failed on KVM, MSHV and WHP.

The acceptance now expects the exec-refused error and its cwd-failed
category, and still checks that the workload never ran: it prints nothing.
The fake CLI of its unit tests reports the same refusal.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
nvx-init applies the view that start sends, whose machine-ID mode was the
image's. The sandbox agent gave each boot a generated machine ID, bound
read-only over the image's, as doc/run.md documents and the smoke probe
checks, so start asks nvx-init for a generated one.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
OpenVMM reports the operation managed only for the lifecycle that
--microvm-lifecycle selects (openvmm_entry/src/microvm/report.rs), and a
run for a VM whose init owns its lifecycle, as a started sandbox's does.
The fake reported managed for both, so the CLI's tests expected an outcome
that OpenVMM never reports for a started sandbox.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
nvx-init runs each exec of a started sandbox as a plain process in the
VM's switched root, with no cgroup and no namespace of its own (#1115),
and powers the VM off after a sync, with its file systems mounted. Its
VM starts without an OpenVMM lifecycle option, so OpenVMM reports a run.

The smoke probe takes a first argument, init, with which it checks the
host name, as nvx-init's command line encodes it, in place of the private
namespaces, and no cgroup. The public lifecycle and RAM-upper acceptance
and the workflow's managed share checks pass it, expect the run
operation, and no longer require a clean scratch journal after stop: the
lifecycle acceptance reads the sandbox's state back after its restart.

The guest's console no longer reaches OpenVMM's log once the guest's
kernel moves it, so the caller-owned share check reads the guest's
refusal of the share's mapping from start's error rather than from a
diagnostic in that log.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@ppenna
Pedro Henrique Penna (ppenna) force-pushed the esaurez/sl35b-lifecycle-4d48405a-20261110 branch from d9c518e to bcaaa60 Compare November 11, 2026 08:02
@ppenna
Pedro Henrique Penna (ppenna) merged commit 821de19 into feature/nvx-refactor Nov 11, 2026
47 checks passed
@ppenna
Pedro Henrique Penna (ppenna) deleted the esaurez/sl35b-lifecycle-4d48405a-20261110 branch November 11, 2026 08:15
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.

3 participants