Repository navigation
SL3.5b: migrate persistent sandbox lifecycle to CLI-owned VM state - #1460
Pedro Henrique Penna (ppenna) merged 15 commits into
Conversation
This pull request changes existing testsIt 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
End-to-end tests
Test doubles
CI test wiring
... and 17 more, which the job's summary lists. Relocated unchanged: 0; added: 15. Compared the merge base |
There was a problem hiding this comment.
🟢 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
369ad2d to
5b7882a
Compare
|
Not merged: CLI-owned sandbox workloads lose their namespace isolation This branch is rebased onto Run 42960814178 at Cause. Effects.
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:
#1466 stays stacked on this branch. |
|
Correction to my comment above: the plan already decides both questions, so I've updated the acceptance rather than the guest.
|
a6a3695 to
d9c518e
Compare
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
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>
d9c518e to
bcaaa60
Compare
821de19
into
feature/nvx-refactor
Scope
Refs #1110, SL3.5b only. Switch persistent
sandbox start,exec,stop, anddeprovisiontogether to the CLI store andhost::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 matchingSavedVmafter readiness and mappings. The CLI suppliesnvx_sandbox=1, managed mode and workload identity without an OpenVMM lifecycle option. Reattachment reuses the existing NVXC framing and one authenticatedHostConnection. 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
initargument 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 onto5947e4e834efa0a93291ce6dd13cafef1f6aedafas4d76f035ea..5b7882adb02478d237c3fc823d19ef411039e2a6, and one commit after them, below, fixes an acceptance expectation. The head is88c6bca99fb2e7a201e3e0282d0db42bd09415be, tree72675c56ff40137a2afe4865a137145d7e5832d5. The merge commitc0d706e7, which joined #1458's head928edaad, is dropped:git show --remerge-diff c0d706e7shows no change of its own.git range-diff 928edaad5a..369ad2d512 5947e4e834..5b7882adb0reports seven of the eleven patches identical. The other four carry these resolutions:e4843296.scripts/test-layout-allowlist.jsonkeeps the base'snvx-rs/src/host/cli/sandbox/state.rsentry, from host: keep immutable named CLI snapshots #1445, and dropsnvx-rs/src/host/cli/store/mod.rs, as this commit intends;check_test_layout.pypasses.Session::connectinto the session tests, where it now passesNonefor the attachment cancellation that cli: run cold blockless VMs through the verified lifecycle (SL3.4a2) #1441 added toconnect_unless_exited. It also retiresSession::connect_launched: cli: run cold blockless VMs through the verified lifecycle (SL3.4a2) #1441'sruncalled it on this branch's original base but now callsconnect_launched_cancellable, so the lifecycle migration removed its last caller. Its description moves toconnect_launched_cancellable, and the module and test documentation name that function.serve_clientkeeps both the base's handshake-phasefake_foreground_gatecheck, from cli: run cold blockless VMs through the verified lifecycle (SL3.4a2) #1441, and this commit'sfake_exit_during_attachandfake_guest_delaywait, the gate first. The session module's documentation keeps this commit'sSession::reattachsentence and namesconnect_launched_cancellable.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 throughhost::vmwithout the old OpenVMMnvx_lifecycle=token, sonvx-initserves its executions, as #1453 routes them. That agent refuses a working directory that the workload cannot enter with thecwd-failedapplication error, which nvx-rs reports asexec-refusedwith 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.88c6bca9tests: expect the exec refusal of an unusable working directory changes two existing test items, whichcheck_test_changes.pylists:run_managed_exec_configuration, the end-to-end scenario driver: for/does-not-exist,/etc/passwdand/root, it expects status 1 with theexec-refusederror and thecwd-failedcategory, 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-refactorreports 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-rswhere they apply: rustfmt; Clippy with-D warningsfor--all-targets --all-features,--lib,--lib --no-default-features,--lib --bins --features cliand--lib --bins --no-default-features --features testing, and for--all-targets --all-featuresand--lib --bins --features clionx86_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; andtest_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 operationrun, where the acceptance expectedmanaged. 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:startpass "no OpenVMM lifecycle option", for which OpenVMM reports the operationrun(report.rs).The acceptance still checked the sandbox agent's executions. Three commits follow
88c6bca9; the head was thend9c518e3684301eba829ad9f43252dbcc2e61bc0, tree181ca18f3e124e56fc72e87c0182e050b2aecc0c, before the rebase onto #1470's merge below.d231dfc3sandbox: generate the machine ID of a started sandbox's VM:startsent 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, asdoc/run.mddocuments and the smoke probe checks, sostartnow sendsnvx_machine_id=generated.persistent_expected, the CLI test's projection of the persistent fixtures onto the new boot, expects that token.89df1a1atesting: report a run of a VM that the CLI's tokens manage: the fake OpenVMM reportedmanagedfor a VM whose lifecyclenvx_mode=managedselects, which real OpenVMM reports as a run, so the CLI's tests never saw the operation that failed in CI. It now reportsmanagedonly with--microvm-lifecycle managed.d9c518e3tests: 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/containercgroup. Every other check stands: the identity, the empty capability sets andno_new_privs, the single private mounts of the overlay root,/proc,/sys,/dev,/dev/ptsand/dev/shm, the device view, the refusedmknod, the generated machine ID, the refused port I/O, the read-only image, the shares and, withentry,nodevon every mount but/devand its devpts, as nvx-init's root mounts them. Withoutinit, the probe checks the one-shotsandbox 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 passinitand expect the operationrun. They no longer require a clean scratch journal afterstop, 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 tohvc1, so the caller-owned share step reads the guest's refusal of the share's mapping, with EPERM, fromstart's error,managed guest could not mount a mapped host path (error 1), in place of the sandbox agent'sNVX-MANAGED-ERROR: stage=host-mappingsdiagnostic in that log.doc/run.mdanddoc/guests/ubuntu-guest.mddescribe the executions and the stop as nvx-init runs them.check_test_changes.py --base 88c6bca9lists 89 changed existing items:check_lifecycle,lifecycle_of_a_sandbox_whose_upper_is_in_ram,stops_keep_an_outcome_report_that_the_caller_reads_whatever_its_umaskanda_mapping_that_the_guest_refuses_leaves_no_usable_startexpect the operationrun.persistent_expectedexpects the generated machine ID. The fake OpenVMM'sOptions,parse,mainandserve_clientcarry the report's operation.test_namespace_check_rejects_the_initial_namespacesalso loadscheck_hostname, whichcheck_namespacesnow calls;test_main_body_runs_the_profile_then_limits_and_sharesstubs it and also runs the body withinit. A new test,test_init_hostname_check_reads_the_encoded_command_line, checks the hexadecimal host name.MANAGED_SUCCESSandMANAGED_REFUSALexpect the operationrun.check_security_profileandRamUpperAcceptance.check_managedpassinit.stopno longer reads the scratch's journal, soscratch_journal_clean,EXT4_INCOMPAT_RECOVER, thestructimport and their unit testtest_scratch_journal_check_reads_the_ext4_recovery_flagare retired. The fake CLI's_FakeSandboxCli._execexpects the probe argumentsinitandentry.run-nvx-microvm-tests.yml: the six managed share steps'initargument,runoperation 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'sstart, which now keeps its standard error, and the Windows steps' outcome checks, which now compare the operation withrun.Validation, on Windows unless named:
--all-targets --all-featureswith-D warnings; rustdoc with-D warnings; the fake OpenVMM's unit tests.cliintegration 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.PublicManagedExecAcceptanceTests,PublicSandboxLifecycleTests,PublicSandboxProxyOnlyTestsandPublicSandboxRamUpperTests: 29 tests on Windows, 2 of them Linux-only skips, and 29 on Linux under WSL.SandboxSmokeProfileTestsandSandboxSmokeShareTests, 21 tests, on Linux under WSL.CiConfigurationTestson Linux under WSL. Its one failure there,test_linux_runners_terminate_openvmm_left_by_earlier_jobs, fails on88c6bca9under WSL too.The hardware acceptance runs in CI. Its run at
a6a36952, the previous form ofd9c518e3, 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, whichd9c518e3replaces as above; the steps after it did not run there. Its run atd9c518e3, 42979403236, passed every job.#1470 then merged into
feature/nvx-refactorat8b775211c295389602924864f633ef9da60efe53, so the fifteen own commits are rebased onto it as1c887d5a1f..bcaaa60e2879715f877bb1233f5b5a19379ae65c, treee61c4ecb275aaddb2e6fc61c0ce3d62c4b2adf01. They apply without conflict: #1470 also changesnvx-rs/src/host/vm.rsandscripts/test-layout-allowlist.json, elsewhere. At this head, on Windows:-D warningsfor the five configurations of check-nvx-rs and the two onx86_64-unknown-linux-musl;cargo +1.89 check; rustdoc with-D warnings;scripts/public_api.py; the test layout; and Markdown links all pass.cliintegration tests pass: 85, with 2 ignored.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 atd9c518e3.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 pinnede4843296(old 1441/67); the consumer then ordinary-merged the actual published parent928edaad5a0cebbd55d85735dded0032cbf91b6b, which contains public 1441/6bc. Native remains14dc4116ec7e57567633c4b7b07892aca3a58522; feature input is4192cfd1.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 successor369ad2d5is 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 at9bf02890: 156/156 passed. Windows CLI/store/session: 145/145 passed, including the inherited-directory preservation regression.The unchanged production source at
9bf02890passes 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 andorigin/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-
cliexecutables were rebuilt after testing and copied outside mutable Cargo outputs (Linuxe634d29c, Windows5c92188a).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/devinventories passed. The remote clones lack theorigin/devalias, so their failed alias attempts remain failures; the real dev inventory was executed in the canonical checkout at256ebe3d, without fetching private origins or fabricating refs.At exact
369ad2d5, both MSHV and WHP also passed the real production lifecycle/recovery driver with the changedLifecycleAcceptance.require_startedmethod: 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-clibinaries built at9bf02890, 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
9bf02890retained-context review is CLEAR. Runtime-observed configuration and actualclaude-opus-5-5usage (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 original9bf02890Copilot request was admitted PENDING at2026-11-10T23:18:30Z(HTTP 201); it is not a request for the successor. The distinct exact369ad2d5request was admitted PENDING at2026-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 source023d9772, nvx-init5e304155, initramfs3f9378a3, 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
StateDirandVmDirpolicies 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, anda_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):
state_dir; removedremovals_keep_the_messages_of_deprovisionassert_runtime_record,check_lifecycle,failed_start, imports, RAM lifecycleSavedVm/verified process and managed argv; keep full repeated lifecycle/RAM coverage and meaningful failed-start status/cleanup.configurations_of_every_format_start_and_execa_mapping_that_the_guest_refuses_leaves_no_usable_startOptions,main,parse,serve_clientRUNTIME_FIELDS,read_runtime,LifecycleAcceptance.require_startedSavedVmschema 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._FakeSandboxCli._launch, complete-public-lifecycle evidence oracleThe failed-start refusal still requires a prompt
exitederror 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::waitreturns that proof, whilevm::Stoppedseparately exposes a fallible optional native report. Startup uses the genuine report category/status when available, and otherwise remains an abnormalerror[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_terminationhelper is deletion-only cleanup: its last callers were the private launcherterminate/killmethods retired by this migration. VerifiedProcessHandle::killand 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-onlyhost/session.rsandcli/store/mod.rschanges are excluded from the file count; the forced re-export line is counted. The eighth file is the explicitly approved, minimal crate-privatehost/vm.rstransport 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, andci.pyexcludes it from production benchmark inputs. Their changes and this exclusion are itemized in the inventory/measurement receipt. The 600-core hard stop is unchanged.