Repository navigation
Description
Parent: #1080, in #1076. Needs: #1099. Unblocks: #1102, #1108, #1109, #1118.
Goal. A VM stops only with a confirmed exit, and its outcome report can be read; a caller that restarts can prove that a VM exited; a host connects to a VM and checks who serves the endpoint; and a running VM can be verified and reattached from values that the caller saved.
Notes.
- In-place changes update their callers.
Process::waitandreport::readchange their signatures in place; a crate-private wait keeps the old wrappers' behavior until their callers go in phase 4.
PRs, in order. Each PR has one goal, leaves dev green, and names in its description each old test of its area as kept, rewritten or retired, with each change that check_test_changes.py reports. Its tests follow the repository's test layout: a module's unit tests, and the code that only they use, sit in its tests.rs or tests/ and move with it. The estimates count core lines (code, not comments, docs or tests) and counted files; each PR records its measured values. Each PR does what its Scope says and no more: a case, input or layout that its Scope doesn't name, and whatever "Not in this PR" lists, is out of scope. Address every review comment within the scope, and ask before acting on one that asks for more. A PR whose measured core lines pass the stop that its Estimate names (twice the estimate, at most 600) stops and asks before continuing.
-
SL2.9a: The old lifecycle wrappers keep a stable internal wait.
- Objective:
Process::waitis about to return exit proof instead of a boolean, but four legacy lifecycle callers still need the old answer. A crate-private compatibility helper isolates those callers so the public signature can change without broad lifecycle edits. - Contents: A crate-private
Process::wait_bool; the four old callers (supervisor/mod.rs,store/vmm.rs,template/clone/restore.rs,template/store/capture.rs) use it, and the publicwaitdelegates to it, so SL2.10 changeswaitwithout editing them. It goes with its last caller. - Scope:
Process::wait_bool, crate-private innvx-rs/src/host/process.rs, containing today's boolean wait behavior.- The four legacy callers in
nvx-rs/src/host/supervisor/vmm.rs,nvx-rs/src/host/store/vmm.rs,nvx-rs/src/host/template/clone/restore.rsandnvx-rs/src/host/template/store/capture.rsswitch towait_bool. - The public
Process::waitdelegates towait_booluntil SL2.10 changes its result.
- Not in this PR:
- New exit-proof types or a public wait-signature change: SL2.10 adds both.
- Changes to deadlines, polling intervals, pidfd behavior or Windows process-handle waits: behavior stays identical.
- A generic compatibility trait or wrapper layer: one crate-private method serves four known callers.
- Migrating or redesigning the legacy lifecycle wrappers: later removal takes
wait_boolwith its last caller.
- Approach: Extract the existing body of
Process::waitinto one crate-privatewait_bool, make the public method delegate, and update exactly the four named callers. Do not duplicate platform waits, add an abstraction, or alter error and timeout behavior. - Done when:
- The four named legacy callers use
wait_booland retain their current boolean behavior. - Public
Process::waitstill passes the existing deadline, exit-event and polling tests through its delegation. - No other caller or lifecycle behavior changes in this compatibility PR.
- The four named legacy callers use
- Needs: SL2.9, in nvx-rs slimming 2.2.1: a VM is created in a private directory, with its restore-ready listener, then spawned #1099.
- Estimate: 60 core lines, 5 counted files. Past 120 core lines, stop and ask before continuing.
- Objective:
-
SL2.10: A VM stops only with a confirmed exit, and its outcome can be read.
- Objective: Callers must not treat a kill request or elapsed deadline as proof that a VM stopped. The new stop result is returned only after a confirmed exit, preserves whether the VM exited itself or was forced, and carries a missing, valid or erroneous outcome-report read separately.
- Contents:
host::vm::stop(&Process, &VmOutputs, graceful, grace, deadline)→Stopped { self_exited, forced, report }, and an error of the new kindErrorKind::StillRunning(new) when the exit isn't confirmed;Process::wait(deadline)→Option<Exited>;report::read→Result<Option<Report>>, where an unreadable or malformed report is anIoerror. The old wrappers keep their behavior until their callers move. - Scope:
- New
host::vm::stop(&Process, &VmOutputs, graceful, grace, deadline)andStopped { self_exited, forced, report }. Process::wait(deadline)now returnsOption<Exited>: proof after exit, orNonewhen the deadline passes first.host::report::readchanges in place toResult<Option<Report>>: missing isOk(None), while an unreadable or malformed report is anIoerror.- Stop runs the supplied graceful step, waits until
grace, forces through the verified process if needed, and waits untildeadline; an unconfirmed exit is an error of the new kindErrorKind::StillRunning. - The old supervisor, store, clone and capture wrappers keep their behavior through
wait_bool.
- New
- Not in this PR:
- Proving an exit after the caller restarts: SL2.10b changes
Process::openfor that case. - A background reaper, signal service, restart manager or unbounded wait: the caller supplies two deadlines.
- Changing the legacy lifecycle wrappers' public behavior or report handling: their migration comes later.
- Interpreting free-form outcome fields or inventing a report schema:
Reportkeeps today's summary. - Treating a PID, kill success or timeout as exit proof: only the verified process wait produces
Exited.
- Proving an exit after the caller restarts: SL2.10b changes
- Approach: Build
stopfrom the existing verifiedProcess::{wait, kill}path and the existing report summarizer. Represent exit proof as a small value produced only by a successful verified wait. Read the report after exit and store its result insideStopped, so a report error never hides the confirmed exit. Keep the old wrappers onwait_boolrather than changing them here. AddStillRunningtoErrorKind, with its spelling and its code. - Done when:
stopreturnsStoppedonly after a confirmed exit and reports whether it self-exited or was forced.stopfails with kindStillRunningwhen the deadline passes without confirmed exit.- An unreadable or malformed outcome is an
Ioerror inStopped::report, and a missing report isOk(None). Process::waitreturnsNoneat its deadline and anExitedproof after the process exits.
- Needs: SL2.9a, above.
- Estimate: 210 core lines, 5 counted files. Past 420 core lines, stop and ask before continuing.
-
SL2.10b: A caller that restarts can prove that a VM exited.
- Objective: After its own restart, a caller must distinguish a still-running recorded VM from one that definitely exited without trusting a reused process ID.
Process::openreturns either a verified running process or exit proof based on the recorded boot, ID and start time. - Contents:
Process::open(&ProcessRef)→Opened::{Running(Process), Exited(Exited)}(new):Exitedwhen no process of that ID and start time runs on this boot, or the boot has changed; never from the ID alone. Its old callers follow:nvx-rs/src/host/store/vmm.rs,nvx-rs/src/host/store/locked.rsandnvx-rs/src/host/supervisor/reattach.rs. - Scope:
Process::open(&ProcessRef)changes toOpened::{Running(Process), Exited(Exited)}innvx-rs/src/host/process.rs.Exitedis returned when the recorded boot differs, or no process with both the recorded ID and start time runs on the current boot.- The existing callers in
host/store/vmm.rs,host/store/locked.rsandhost/supervisor/reattach.rshandleOpenedwithout weakening their process checks. - Linux uses the existing boot ID,
/procstart time and pidfd or polling path; Windows uses its boot ID, creation time and process handle.
- Not in this PR:
- Exit proof from a process ID alone: IDs can be reused.
- Process enumeration, ancestry checks, command-line matching or a general process database: the recorded identity already has the required three fields.
- Persisting
ProcessRef, capture context or VM records: callers own their durable state. - Changing stop, reattach policy or interrupted-launch recovery beyond adapting the three existing callers.
- Recovering an exit status from a process the caller did not retain:
Exitedproves exit, not how it exited.
- Approach: Reuse the current
open_referencechecks and replaceNonewith the explicitOpened::Exitedresult; a matched handle becomesOpened::Running. Keep permission and indeterminate host errors as errors. Update only the three callers that currently branch onOption, without adding scans, caches or fallback identity heuristics. - Done when:
Process::openreturnsExitedfor another boot, a missing ID or the same ID with another start time.Process::openreturnsRunningonly for a process matching the recorded boot, ID and start time.- Tests show that ID reuse alone never proves either identity, and indeterminate host checks remain errors.
- The store and supervisor callers preserve their existing recovery behavior with the new result.
- Needs: SL2.10, above.
- Estimate: 80 core lines, 4 counted files. Past 160 core lines, stop and ask before continuing.
- Objective: After its own restart, a caller must distinguish a still-running recorded VM from one that definitely exited without trusting a reused process ID.
-
SL2.11a: A host connects to a VM, and checks who serves the endpoint.
- Objective: A host connection must verify that the recorded VM process serves its endpoint before authentication begins. The retained transport and NVXS connection APIs move into one public host module that returns the crate's error, while the old session connection remains as a temporary wrapper.
- Contents:
host::transport, whose functions return the crate'sResultwherehost::session'sSessionErrorandHostConnectionErrorwere, each case with its kind and source:Transport: Send, with timedreadandwrite, andunread, whichdevadded: what the peer hasn't read yet, where the platform can tell, moved fromnvx-rs/src/host/platform/mod.rs;connect_verified(endpoint, &ProcessRef, deadline) -> Result<Box<dyn Transport>>, fromconnectinnvx-rs/src/host/session.rs, which retries until the endpoint exists and checks that the VM's process serves it; andHostConnection(attach,send,receive,identity,session), fromnvx-rs/src/host/nvxs.rs. The oldconnectstays a wrapper until its callers move. - Scope:
- New
host::transport, which returns the crate'sResultwherehost::session'sSessionErrorandHostConnectionErrorwere: a deadline isTimedOut, an exited VMExited, a closed, reset, refused or broken sessionProtocol, and a transport failureIo, each with its source. Transport: Sendwith timedreadandwrite, plusunreadwhere the platform can report bytes the peer has not read, moved fromhost/platform/mod.rs.connect_verified(endpoint, &ProcessRef, deadline), adapted fromhost/session.rs, retries until the endpoint exists or the deadline passes and verifies its server against the VM process.- Linux verifies the Unix-socket peer process; Windows verifies the named-pipe server process.
HostConnection::{attach, send, receive, identity, session}moves fromhost/nvxs.rs, while the oldconnectdelegates until its callers move.
- New
- Not in this PR:
- A public endpoint-owner lookup that returns arbitrary process information: callers do not need one.
- TCP, TLS, remote hosts, pluggable transports or a general networking framework: supported hosts use Unix sockets or Windows named pipes.
- An asynchronous transport trait or executor integration: methods remain blocking with deadlines.
- Changing the NVXS protocol, application messages or control-session behavior: this PR moves and unifies the retained host connection.
- VM persistence, process discovery or reattach policy: SL2.11b composes this connection with saved values.
- Approach: Move the existing platform transport trait and
HostConnectionimplementation, then adapt the existing retryingsession::connectintoconnect_verifiedusingProcessRef. Preserve Unix peer credentials, Windows server-PID checks and the current deadline behavior. Map the two old error enums' cases onto the crate's error kinds, keeping each source, instead of layering wrappers or defining a new I/O framework. - Done when:
connect_verifiedretries a not-yet-created endpoint within its deadline and connects when the recorded VM process serves it.- Linux and Windows tests refuse an endpoint served by another process.
HostConnectionretains attach, send, receive, identity and session behavior, and each failure has the kind and source that the scope names.Transport::unreadkeeps its Linux result and WindowsNonebehavior, with the oldconnectstill serving unmoved callers.
- Needs: SL2.9, in nvx-rs slimming 2.2.1: a VM is created in a private directory, with its restore-ready listener, then spawned #1099.
- Estimate: 250 core lines, 5 counted files. Past 500 core lines, stop and ask before continuing.
-
SL2.11b: A running VM can be verified and reattached from values that the caller saved.
- Objective: A caller that saved a VM's launch values needs to verify or reattach it after restarting without a library-owned store. Verification checks the runtime and process without connecting; reattach then peer-verifies the endpoint and authenticates, while async callers can obtain an owned exit-wait handle where the host supports one.
- Contents:
host::vm::SavedVm { vm_dir, endpoint, capability, process, runtime }, which the caller saves;verify(&SavedVm, &RuntimeIdentity)checks the runtime identity and the process without connecting, and another runtime is an error of kindRuntimeMismatch;reattach(&SavedVm, &Runtime, deadline)verifies, then connects and attaches, and returnsReattached { process, connection }.Process::exit_handle()returnsOption<ExitHandle>(new): a duplicated pidfd or process handle for an async runtime, orNonewhen the host only polls.Process::bytes_readandhost::process::own_bytes_read, whichdevadded for restore checks, stay public, with their platform rules. - Scope:
host::vm::SavedVm { vm_dir, endpoint, capability, process, runtime }, plusverify,reattachandReattached { process, connection }.verify(&SavedVm, &RuntimeIdentity)compares the saved runtime and opens the verified process without connecting to the endpoint; another runtime is an error of the kindRuntimeMismatch.reattach(&SavedVm, &Runtime, deadline)verifies, callsconnect_verified, attaches with the saved capability and returns the held process andHostConnection.Process::exit_handle() -> Option<ExitHandle>and the new ownedExitHandle: a duplicated pidfd on supporting Linux hosts, a duplicated process handle on Windows, orNoneon a Linux host that only polls.Process::bytes_readandhost::process::own_bytes_readremain public with today's Linux and Windows accounting rules.
- Not in this PR:
- Serializing, encrypting, journaling or locating
SavedVm: the caller saves and reloads its values. - A library store, VM registry, recovery daemon or interrupted-launch policy: callers build those policies.
- Hashing runtime files or accepting a different runtime identity: the caller supplies and compares the existing labels and contract versions.
- An asynchronous runtime adapter, future, watcher thread or callback API: callers adopt the duplicated exit handle or poll.
- Reattaching to a mismatched runtime or unverified endpoint: such a VM may only be handled through its verified process.
- Serializing, encrypting, journaling or locating
- Approach: Make
SavedVma value bundle, then compose the existing runtime comparison,Process::open, SL2.11a's verified connection andHostConnection::attach. Duplicate the process's existing OS wait object rather than creating a thread or event abstraction; returnNonefor polling-only hosts. Keep the existing read-counter implementations and documentation unchanged. - Done when:
verifyaccepts matching saved runtime and process values, refuses a different runtime identity with the kindRuntimeMismatch, and opens no endpoint.reattachrefuses a runtime mismatch or wrong endpoint server and returns a verified process with an attached connection on success.exit_handlereturns an independently owned duplicated pidfd or Windows process handle, orNoneon a polling-only host; its cases inhost/process/tests.rsuse the platform's private no-pidfd helper.Process::bytes_read,own_bytes_readand their Linux and Windows behavior keep their existing tests.
- Needs: SL2.11a, above.
- Estimate: 250 core lines, 5 counted files. Past 500 core lines, stop and ask before continuing.
Tests. stop returns Stopped only after a confirmed exit, and fails with the new error kind StillRunning when it can't confirm one; an unreadable or malformed report is an Io error in Stopped::report. report::read returns Ok(None) for a missing report. Process::wait returns None when its deadline passes first, and an Exited proof once the process exits. Process::open returns Exited only when no process of that ID and start time runs on this boot, or the boot has changed, never from the ID alone. verify and reattach refuse a different runtime identity with the kind RuntimeMismatch, and verify connects to nothing. exit_handle gives a duplicated handle, or None on a host that only polls. connect_verified refuses an endpoint that another process serves. Transport::unread, Process::bytes_read and own_bytes_read keep their tests.
Done when: every PR above has merged, with dev green.
Activity
esaurez commentedon Nov 10, 2026
Workstream source/progress checkpoint - 2026-11-10 UTC
Checked boxes below mean merged scope, not merely an implemented first iteration. Implementation branches are source checkpoints, not automatic qualification, Ready or merge approval. Some starting inputs are frozen/anonymous commits rather than standalone branches; their exact SHAs and known merged producer refs are recorded explicitly.
SL2.11a - merged via pull request 1406.
esaurez/slim-2.2.2-sl2.11a-rebase-20261109at81f58751f77787bc3fc9e6abfe4a3cfe12696be1.feature/nvx-refactorat2b5b44fc38e3893de9e92d6f60caaabe67386101; merge commit81f58751f77787bc3fc9e6abfe4a3cfe12696be1.SL2.9a - merged via pull request 1412.
esaurez/slim-2.2.2-sl2.9a-20261107at4e8db36c746f734f7676b545898894d09b222e6c.feature/nvx-refactorat3a7bd293fbc3f997c1999e5c29000e2c13eeacae; merge commit4e8db36c746f734f7676b545898894d09b222e6c.SL2.11b - merged via pull request 1414.
esaurez/slim-2.2.2-sl2.11b-20261108at8b2a392ae6fd5f73f1b27fb2a0e109ce36d068e3.feature/nvx-refactorat4e8db36c746f734f7676b545898894d09b222e6c; merge commit8b2a392ae6fd5f73f1b27fb2a0e109ce36d068e3.SL2.10 - First implementation committed; not merged.
esaurez/slim-2.2.2-sl2.10-20261109; exact heade4af86899218cde7ad3228b3a63aa678ed19f1c2.esaurez/slim-3.3.2-sl7.6b-20261109; exact commitc7e971607a0ea3b638800b4d90a3fd549685c42b.SL2.10b - First implementation committed; not merged.
esaurez/slim-2.2.2-sl2.10b-20261108; exact head78c94958e9ac63dc234733166ff6af645eb03e49.esaurez/slim-2.2.2-sl2.10-20261107; exact commit7bca08c599cd70453a3e99c2409b314f599429e8.The original technical scope remains authoritative, supplemented only by any portable technical clarification in this issue. No machine-specific execution policy is part of this checkpoint.
esaurez commentedon Nov 10, 2026
Completed all five ordered scopes: SL2.9a (#1412), SL2.10 (#1432), SL2.10b (#1438), SL2.11a (#1406), and SL2.11b (#1414); all are merged. The whole-issue scope is unchanged and there are no subissues.
PR1406's exact merged head
81f58751f77787bc3fc9e6abfe4a3cfe12696be1passed the post-merge feature-branch push CI run 42678132782 and Required status check 129373929510; both actionable review threads are resolved. PR1438's exact headb5f66d7b60f37444cfb6a585e9b10a6cf012f8d1passed CI run 42847634336 and Required status check 129946529426. Currentdevheadcbc20e07a2ec4ec90520d2490bda6ad27b8f3a52has successful exact-head CI run 42862617590 and Required status check 130008525752.This does not relabel the old PR1406 COMMENTED/closer-look summary as a green review or formal APPROVED review, nor erase historical failed/cancelled runs or separate NFV failures. The observed
devaggregate was SUCCESS; successful required full CI is distinguished from any additional checks, not a blanket all-checks-green claim.