Skip to content

nvx-rs: attribute a clone's failures, record them, and reattach it - #951

Merged
Pedro Henrique Penna (ppenna) merged 12 commits into
devfrom
esaurez/844-pr4-failures-reattach-20261025
Oct 29, 2026
Merged

Pedro Henrique Penna (ppenna) merged 12 commits into
devfrom
esaurez/844-pr4-failures-reattach-20261025

Conversation

@esaurez

@esaurez Enrique Saurez (esaurez) commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Stacked on #950; this PR retargets when its parent merges.
Chain: #888 → #949 → #950 → this PR, on dev; #879, #881, #885, #886, #877, #932, #884, #887, #933, #880, #936, #937, #939, #940, and #941 have merged.

Part of #844 (PR 4 of 5: failures, teardown, and reattach).

What and why

#950 runs a clone's restore sequence to its sandbox's start and the launch's commit. A failure there ends the launch with an error that says what failed, but not whose failure it is, and nothing of it outlives the caller. This PR attributes every failure of a clone, from its plan to its commit, records it in the clone's sandbox with the teardown that followed, and lets a caller that restarted take its clones over.

Attribution

A clone's plan, launch, and restore sequence fail with a CloneFailure, whose Evidence attributes the failure to an Outcome, at a Stage, once the clone got as far as its Progress:

  • the request: a denied authorization; a stale binding; invalid targets, format, settings, Activate, or mappings; an image, a scratch, or a root that the template cannot serve; and a supplied file of the wrong identity, length, or geometry, which the host's staging takes instead once the caller proves it, CloneFailure::with_staging_proven;
  • the host: an endpoint that cannot be bound, resource exhaustion, a host that cannot restore the template, another runtime, or a failure of its own operations;
  • unknown: a lost status, a timeout, a reset, a forced kill, or a broken protocol;
  • the template: a malformed generation, or a defect of its capture or repair that a diagnosis attributes to it. This is the only outcome that quarantines the generation, through its tombstone, CloneFailure::quarantine;
  • the repair stage, for a status of the clone's repair alone.

OpenVMM's outcome report gives the status with which the guest ended the clone's VM, which status_evidence attributes: a status of the repair at any point, and one of the activation before the start was sent. It prevails over what the host or the caller saw of the same end, but not over evidence of the template, which shows the provenance that a status alone leaves to establish.

A VMM that exits before its restore-ready event, as OpenVMM does once it cannot restore the snapshot, leaves its fatal error in its log. The failure's message keeps that error, and its time ABI code attributes the failure to the host when it says that the host cannot present the template's CPU profile, TSC, or LAPIC timer. The wait for the event gives a VMM that closed the endpoint without sending it ten seconds to exit.

Records and teardown

From the launch on, a failure ends the clone's VMM through its verified identity. The sandbox keeps two more files, which a release removes, as a removal of the sandbox does, and which a cold launch never writes:

  • clone-progress.log, the clone's progress log: a first line that names the launch and the boot of the host, then a line for each step from the spawn to the commit, without a sync. The launch writes the spawn before it returns, since a teardown of the launch removes its journal, which records the spawn too. It removes an earlier launch's log through a handle of the sandbox's directory, which its lock checked, as a release removes the clone's files, and creates its own log where none may exist: a path that names another sandbox's directory by then fails the launch, rather than lose that sandbox's log;
  • clone-failure.json, the record of the failure, JSON in format FAILURE_FORMAT. It is written before the teardown, with the teardown pending, so that a crash of the caller during the teardown leaves it for a reattach to finish, and again once the teardown ended, each time durably: the write syncs the sandbox's directory once the record is in place. It holds the stage, the evidence and the status, the original outcome, the transaction, the generation, the progress, and the teardown: no VM, an exit or a termination that the host verified, or an unresolved one. It keeps at most 8192 characters of the failure's message, and 1024 of the reason of an unresolved teardown: of a longer text, its first and last ones, with an ellipsis between them. So a reattach reads every record that a failure writes, however long a caller's diagnosis.

An unresolved teardown keeps the launch's journal or runtime record, and with it the lease. So does a failure whose pending record cannot be written, as on a full disk: nothing that tells that the clone launched is removed before its failure is recorded. A failure of the restore sequence, or of the caller's, then ends the clone's VMM through its handle and keeps the launch's journal, Starting::end_keeping_journal, with its teardown unresolved; a launch that fails before it returns keeps its journal for recovery; and a reattach fails before it ends or forgets anything of the clone's VM, which it leaves as it found it. A record that a write put in place, but whose sync failed, may not be durable yet, so a reattach that finds it syncs the sandbox's directory before it ends or forgets anything of the clone's launch, and fails the same way if that sync fails. A crash of the caller before the record then leaves a launch that a reattach records as failed, rather than a clone that ended. A clone whose sandbox records a failure launches no more under its lease. A release ends the reservation of the lock that acquired the lease before it removes anything (#949), so one that fails after it removed the failure record leaves no launch to begin. A launch that spawned its VMM but could not record it in its journal records the failure as of the spawn, and then ends the VMM through its handle.

A step of the caller's own between Ready and the commit, such as one that maps a directory of the host into the clone, fails the clone with the caller's evidence through Activated::fail or Started::fail. These record the failure and end the launch, as the sequence's own failures do.

Reattach

reattach_clone takes a clone over once the caller of its launch restarted:

  • A VMM of the clone's launch that runs under the caller's runtime identity is checked in a new session. The clone answers the byte-equal Activate of clone-activation.bin with the Ready that it answered once, and the reattach checks the transaction ID that it reports. ReattachedClone::Live returns the VM with the session.
  • A clone of another runtime identity, or one that fails the check, is ended through its verified identity, and fails at the reattach stage.
  • It records the failure of a launch that a crash interrupted, and of a VM that exited, which it classifies by one reading of its outcome report, before it ends the launch or forgets the VM. It finishes a failure that a crash left pending, and resolves the teardown of an earlier one with what the VMM left as it ended.
  • A record of a failure that it cannot read, malformed or of another format, still tells that the clone launched and failed, though not how. The reattach takes it as the record of an earlier failure. If the store holds the journal or the runtime record of the clone's launch, which the reattach may remove, it first replaces the record with its own failure at the reattach stage, with the read error and the host's evidence, its teardown pending, which keeps how far the clone got for a reattach after a crash. It then ends a running VMM through its verified identity and, whatever it found of the VM, records the failure with its teardown, over whose evidence a status of the guest's in OpenVMM's outcome report prevails. It never returns the clone live, ended, or indeterminate.
  • A record that it can read, but whose template store, lease, generation, key, or launch is not the binding's, is another clone's failure, which tells nothing of this clone. The reattach takes it as a record that it cannot read, with an error that names what differs, so the failure that it records and returns is this clone's, and a quarantine of it reaches only the generation that the clone leases.
  • A progress log that is damaged, missing, or written during another boot of the host leaves how far the clone got uncertain, so a status that a workload may have given is no evidence of the activation.
  • It reads nothing of the template store, so a tombstone or a retirement of the generation leaves the clone its lease.
  • Its termination bound applies to a VMM whose launch committed. A launch that a crash interrupted ends as LockedStore::recover ends it, within the store's own wait for the VMM's termination.

CloneRecords::read reads what a sandbox keeps of a clone, for the operations that list, inspect, or remove clones. It fails for a failure record of another clone, as for one that cannot be read. It returns the outcome report of the clone's VM only once the clone got past its plan: the launch removes the report of an earlier VM of the sandbox, which the sandbox otherwise keeps until the next launch begins, before it creates the progress log.

Stops

A holder that checks whether its clone's VMM has exited, then stops the VM, would attribute to its stop a VMM that exits in between: Vmm::stop finds it exited, runs no step, reports StopOutcome::Graceful, and clears the runtime record, so a reattach finds no record, and ends the clone as one that its holder stopped, rather than with the loss of its status. Vmm::stop_unless_exited stops the VM as Vmm::stop does, unless its own check, before the graceful step, finds the VMM exited. It then returns None, runs no step, signals nothing, and keeps the runtime record, so that reattach_clone finds that the VM exited unheld, and records the loss of the clone's status, if no report of the guest's own end tells why, before it clears the record. Once that check found the VMM running, its exit is the stop's. Vmm::stop, Vmm::terminate, and their callers are unchanged.

Also fixes

Two fixes to code from #949, which its review found:

  • The restore-ready socket gets mode 0600 right after the bind, whatever the umask, so that a VMM that is not root can connect to it (review thread).
  • An acquisition opens the leased generation's payload as the release of the lease opens it, following no link from the template store's directory on, and checks the payload's owner and mode through that handle, so that it grants no lease that the release could not end (review thread).

Not in this PR

Validation

At this head, 0352bb9, with OpenVMM at d2338ea2, per commit: on Windows, fmt and Clippy with -D warnings for nvx-rs in CI's three configurations (--all-targets --all-features, --lib, and --lib --no-default-features), tests/host_modules.rs, the tests of the platform, template, store, sandbox, and supervisor modules, and the test adapter's fmt, Clippy, and host tests; and for the x86_64 Linux target, Clippy with -D warnings in the same three configurations, the aarch64-apple-darwin check, and the test adapter's Linux host and musl guest Clippy.

The gates, every step of check-nvx-rs on Windows and on Linux x86_64 with the host tests as root on Linux, and the test adapter's VM suites, vm_layered, vm_template, vm_restart, and vm_steps, on Windows (WHP) and Linux (MSHV), run at the final heads of the stack once its reviews are clean.

Tests added, with the fake VMM and the fake control:

  • the restore-ready listener, which reads a client that closed before its connection was accepted to its end, on both platforms;
  • the attribution table, row by row: the repair and activation statuses, the precedence of a status over what was seen, but not over the template's evidence, OpenVMM's fatal error and its codes, and resource exhaustion;
  • the teardowns, and the records: progress logs that a crash cut short or that another launch wrote, failure records of another format, failure records of another clone, refused with what differs, and the line of OpenVMM's fatal error read from the end of its log;
  • the request's refusals: unauthorized images, malformed generations, keys that the read refuses for what they are (a link, a directory on Linux, and a key that holds more than a key may), whose failures quarantine their generation, keys of another encoding version than the build reads, whose failures are the host's and quarantine nothing, and paths that no longer name their supplied files;
  • failures recorded before their teardown and again once it ended, and launches that cannot be ended, which keep their records and their lease until a reattach ends them;
  • reattaches: a committed clone in a new session; a clone that the reattach may not keep; launches that a crash interrupted; VMs that exited, classified by one reading of their reports; earlier failures finished; records of a failure that cannot be read, with a live VM, one under another runtime identity, VMs that exited with a status that is evidence, one that is not, or no report, launches that a crash interrupted before and after the recovery, an unbegun clone beside an earlier VM's report, and an indeterminate VM; a record of another clone's failure with the template's evidence, which the reattach replaces with this clone's failure, whose quarantine tombstones nothing; and progress logs that a reboot or a crash may have cut short;
  • a caller that fails a ready or started clone with its own evidence;
  • stops through stand-in VMMs: of a VMM that had exited before the stop began, whose runtime record, and with it the lease, only Vmm::stop_unless_exited keeps, and of running ones, which it stops as Vmm::stop does; and a clone whose VMM exits once its holder found it running, before the holder's stop: it fails at its run with the loss of its status, which quarantines nothing, while Vmm::stop ends it as stopped;
  • inspections of a clone whose launch has not begun, which take no report that an earlier VM of the sandbox left, even once a reattach recorded a failure of its own, and launches, which remove that report before they record anything;
  • inspections of clones whose launch began, which take their VM's report, including that of a VMM that the launch's journal did not record, once a reattach of the clone or a recovery of the sandbox ended the launch;
  • a caller's diagnosis too long for a record: the record keeps its ends and its evidence through a reattach, and the reattached failure quarantines the generation;
  • failure records that are in place when their directory is synced, and that keep both ends of a message or a reason that a read could not take whole;
  • failures whose record cannot be written: a caller's diagnosis of the template keeps the launch's journal and the lease, then a reattach records the clone as failed; reattaches of an interrupted launch, of a VM that exited, of one under another runtime identity, and of one that fails its activation check end and forget nothing, then record the failure once they can; the supervisor keeps the journal of a launch, and the runtime record of a VM that exited, whose caller cannot record them; and a reattach makes the record of an earlier failure durable before it ends the launch, and ends nothing when it cannot;
  • a progress log that replaces an earlier one only in the directory that its handle holds, while the sandbox's path names another sandbox's directory, whose log then fails the creation;
  • a launch that its caller abandons before the clone's activation, whose progress log records the spawn, so that a reattach reads the outcome report of the clone's VM and records the failure that the guest's status gives;
  • on Linux, the restore-ready socket's mode 0600 under a umask that withholds its owner's permission to write it, and an acquisition's refusal of a link in the place of the template store's directory, of the directory of the generations, or of the payload, leasing nothing.

These tests run no VM: clones' real restores come with PR 5's VM tests.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Lease release can remove the retry-blocking failure marker before revoking the reservation, allowing another launch under the same lease if cleanup fails.

3 open issues
What changed in this PR

This PR extends NVX’s clone restore lifecycle with attributed failures, persistent failure and teardown records, and reattachment after the caller restarts. It implements the failure-handling and recovery portion of #844; real-VM integration remains for the next PR.

Changes:

  • Adds typed failure attribution across clone planning, launch, activation, and commit.
  • Records clone progress and teardown outcomes, with cleanup when the lease is released.
  • Adds clone reattachment and fake-process tests for failures, recovery, and restore-ready connections.
File Description
nvx-rs/​src/​host/​template/​store/​leases/​tests.rs Tests release cleanup of progress and failure records.
nvx-rs/​src/​host/​template/​store/​leases.rs Documents clone records removed during release.
nvx-rs/​src/​host/​template/​store.rs Exposes directory validation for failure attribution.
nvx-rs/​src/​host/​template/​mod.rs Exports and documents failure and reattachment APIs.
nvx-rs/​src/​host/​template/​clone/​tests/​restore.rs Tests attribution, teardown records, and caller-reported failures.
nvx-rs/​src/​host/​template/​clone/​tests.rs Tests planning failures and their evidence.
nvx-rs/​src/​host/​template/​clone/​record.rs Attributes activation-record construction errors.
nvx-rs/​src/​host/​template/​clone/​reattach.rs Implements clone reattachment and failure recovery.
nvx-rs/​src/​host/​template/​clone/​launch.rs Tracks launch progress and records failures.
nvx-rs/​src/​host/​supervisor/​tests.rs Adapts tests to reattachment reporting callbacks.
nvx-rs/​src/​host/​supervisor/​mod.rs Adds callbacks before teardown and runtime-record removal.
nvx-rs/​src/​host/​store/​tests.rs Extends clone-file cleanup coverage.
nvx-rs/​src/​host/​store/​mod.rs Registers progress and failure files with the sandbox store.
nvx-rs/​src/​host/​store/​lifecycle/​tests.rs Tests removal and validation of the new records.
nvx-rs/​src/​host/​sandbox/​scratch.rs Exposes binding version for attribution checks.
nvx-rs/​src/​host/​sandbox/​distro.rs Exposes binding version for attribution checks.
nvx-rs/​src/​host/​platform/​windows.rs Tests restore-ready clients that disconnect before acceptance.
nvx-rs/​src/​host/​platform/​linux.rs Tests restore-ready clients that disconnect before acceptance.
nvx-rs/​README.md Documents failure attribution, records, teardown, and reattachment.

Review effort: Balanced

Comment thread nvx-rs/src/host/store/mod.rs
Comment thread nvx-rs/src/host/template/clone/launch.rs Outdated
Comment thread nvx-rs/src/host/template/clone/reattach.rs

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

An unreadable failure record is treated as absent, allowing a clone with unresolved teardown to become usable again under the same lease instead of preserving the error or safely terminating the VM.

1 open issue
3 resolved since last review

Review effort: Balanced

Comment thread nvx-rs/src/host/template/clone/reattach.rs Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The reattach wildcard branch discards clone-failure.json read errors and records a generic run-stage exit failure, losing diagnostics that should be preserved at Stage::Reattach.

1 open issue
1 resolved since last review

Review effort: Balanced

Comment thread nvx-rs/src/host/template/clone/reattach.rs Outdated
@esaurez
Enrique Saurez (esaurez) force-pushed the esaurez/844-pr3-restore-sequence-20261025 branch 3 times, most recently from 40ee8e4 to a4893fd Compare October 28, 2026 08:55
@esaurez
Enrique Saurez (esaurez) force-pushed the esaurez/844-pr4-failures-reattach-20261025 branch from 2e5e5be to 49e0abb Compare October 28, 2026 13:25
@esaurez
Enrique Saurez (esaurez) requested a balanced review from Copilot October 28, 2026 13:25

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.

🔵 Needs a closer look

Cross-platform VMM termination and crash-recovery changes need human review because tests use fakes and real clone restore/reattach validation is deferred to PR 5.

No open issues 🎉

1 resolved since last review

Review effort: Balanced

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The spawn state is not durably recorded before Restoring is returned, so early teardown can cause reattachment to skip failure reporting and incorrectly report a failed guest repair as Ended.

1 open issue

Review effort: Balanced

Comment thread nvx-rs/src/host/template/clone/launch.rs Outdated
@esaurez
Enrique Saurez (esaurez) force-pushed the esaurez/844-pr3-restore-sequence-20261025 branch from 933dc80 to a53a0fa Compare October 28, 2026 15:29
Enrique Saurez and others added 11 commits October 28, 2026 21:05
A clone's plan, launch, and restore sequence now fail with a
CloneFailure, whose Evidence attributes the failure to an Outcome: the
request, the host's staging once the caller proves it, the host,
unknown, the template, which alone quarantines the leased generation
through its tombstone, or the repair stage. OpenVMM's outcome report
gives the status with which the guest ended the clone's VM, which
prevails over what the host saw: a status of the repair at any point,
and one of the activation before the start was sent. A VMM that exits
before its restore-ready event leaves OpenVMM's fatal error in its log,
whose time ABI code may attribute the failure to a host that cannot
present the template's CPU or time; the wait for the event gives a VMM
that closed the endpoint without the event ten seconds to exit.

From the launch on, a failure ends the clone's VMM through its verified
identity, and the sandbox records it in clone-failure.json, before the
teardown, with the teardown pending, and again once the teardown ended,
with its stage, evidence, status, original outcome, transaction,
generation, progress, and teardown. The clone's progress log,
clone-progress.log, whose first line names the launch and the host's
boot, records each step from the spawn to the commit without a sync, in
one write for each step from the restore-ready event on. A teardown
that is unresolved keeps the launch's journal or runtime record, and
with it the lease, and a clone whose sandbox records a failure launches
no more under its lease. A release removes both files, and a cold
launch writes neither.

reattach_clone takes a clone over once the caller of its launch
restarted: it checks a committed clone in a new session, with the
byte-equal Activate of clone-activation.bin, which the clone answers
with its cached Ready; ends a clone of another runtime identity, or one
that fails the check, through its verified identity; records the
failure of a launch that a crash interrupted, and of a VM that exited,
before it ends the launch or forgets the VM; classifies a VM that
exited by one reading of its outcome report; and finishes a failure
that a crash left pending, or resolves the teardown of an earlier one.
A progress log that is damaged, missing, or written during another boot
leaves how far the clone got uncertain, so that a status that a
workload may have given is no evidence of the activation. It reads
nothing of the template store, so a tombstone or a retirement of the
generation leaves the clone its lease. CloneRecords::read reads what a
sandbox keeps of a clone.

Part of #844.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a424884c-018a-45f5-a7fe-c57548a873f0
A step of the caller's own between a clone's Ready and the commit of its
launch, such as one that maps a directory of the host into the clone,
may fail. Activated::fail and Started::fail fail the clone with the
caller's evidence, at the start and the commit stages, through the
recorded teardown that the restore sequence's own failures take: the
failure is recorded before the teardown and once it ended. Abandoning a
launch records nothing, as before.

A status of the guest's that is evidence prevails over what the host or
the caller saw of the same end, but no longer over evidence of the
template, a check of the generation or a diagnosis that attributes a
defect to it, which shows the provenance that the status alone leaves
to establish. The record holds the status beside it, for a failure's
record, the finish of a pending one, and the resolution of an
unresolved one alike.

Part of #844.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a424884c-018a-45f5-a7fe-c57548a873f0
A launch whose VMM was spawned, but whose identity it could not record
in its journal, recorded the clone's failure as if no VMM had been
spawned: the launch then ends the VMM through its handle, and removes
the journal, so nothing else tells it. The failure now records the
spawn, and Progress::Spawned says that the journal records the VMM
unless the launch could not record it.

The documentation of reattach_clone says that its bound applies to a
VMM whose launch committed: a launch that a crash interrupted ends as
LockedStore::recover ends it, within the wait that the store's recovery
allows the VMM's termination. Evidence::Protocol says, as the restore
sequence now does, that the bytes that it refuses at the restore-ready
endpoint are those that differ from the event.

Part of #844.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a424884c-018a-45f5-a7fe-c57548a873f0
A reattach took a record of the clone's failure that it could not read
for none, so a clone that had failed came back live if its VM still ran
and answered, ended without a failure if its VM had exited, and
indeterminate if its VM could not be examined. The record still tells
that the clone launched, and failed: the reattach now fails such a clone
at its own stage, with the error of the record's read, the host's
evidence, once it ended a VMM of the clone that runs through its
verified identity, or with the teardown unresolved for one that it
cannot examine, which a later reattach resolves. The failure that it
records replaces the record that cannot be read.

Part of #844.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a424884c-018a-45f5-a7fe-c57548a873f0
A reattach that found a record of the clone's failure that it could not
read failed the clone with the error of the read only where it would
otherwise have kept the clone, found it ended, or found its VM
indeterminate. Elsewhere the error was lost: before it ended a launch
that a crash of its caller interrupted, or cleared the runtime record of
a VM that exited with a status of the guest's that is evidence, or
without a report of the guest's own end, the reattach recorded a new
failure in place of the record, at the stage of the clone's progress,
as it did for a VM that runs under another runtime identity.

The reattach now takes such a record as the record of an earlier
failure that cannot tell how the clone failed. If the store holds the
journal or the runtime record of the clone's launch, which the reattach
may remove, it first replaces the record with its own failure at its
own stage, with the error of the record's read and the host's evidence,
its teardown pending, which keeps how far the clone got for a reattach
after a crash: before the restore-ready event, only the journal holds
the spawn. It then ends a VMM of the clone that still runs through its
verified identity, and records the failure with the teardown that it
found or made, over whose evidence a status of the guest's in OpenVMM's
outcome report prevails.

Part of #844.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a424884c-018a-45f5-a7fe-c57548a873f0
…launch returns

A clone's launch noted the spawn of its VMM without writing it in the
progress log, which recorded it with the next step, since the launch's
journal records the spawn too. A launch that its caller abandoned, or
dropped, before the clone's activation removed the journal as it
ended, and nothing recorded the spawn any more: a later reattach then
took the clone for one whose VMM never spawned, read no outcome report
of its VM, and found it ended, even when the guest had ended the VM
with a status that is evidence of a failure, such as one of its repair.

The launch now appends the spawn to the progress log before it
returns. A launch that cannot write it fails at the launch stage,
which ends its VMM and records the failure, as the restore sequence
does when it cannot record a step.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a424884c-018a-45f5-a7fe-c57548a873f0
…e read

A reattach accepted any failure record that it could read in the
sandbox as the failure of the clone that the sandbox's binding leases,
whatever the template store, lease, generation, key, and launch that
the record names. A record of another clone's failure, misplaced in
the sandbox, was then returned as this clone's, and a failure that it
attributed to the template could quarantine a generation that this
clone does not lease.

The reattach, and the read of a clone's records, now read the failure
record only as the failure of the binding's clone: a record that names
another store, lease, generation, key, or launch fails to read, as a
malformed record does, naming what differs. The reattach takes it as a
record that it cannot read: it replaces it with its own failure at the
reattach stage, with the host's evidence, so the failure that it
returns, and records, is this clone's.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a424884c-018a-45f5-a7fe-c57548a873f0
On Linux, a clone's launch listens for the restore-ready event of its
VMM at a Unix socket in the sandbox's directory, which the bind creates
with the mode that the process's umask leaves. A restrictive umask,
such as 0200, could withhold even its owner's permission to write it,
and a VMM that is not root cannot connect to a socket without that
permission: the launch would then fail as if the VMM had sent nothing.

The launch now gives the socket mode 0600 right after the bind, before
it spawns the VMM. It opens the socket's path only to locate it,
following no link, refuses anything but a socket, and sets the mode
through the descriptor's link in /proc, as the store sets the modes of
the directories that it creates.

The review of #949, which added the restore-ready socket, found this.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a424884c-018a-45f5-a7fe-c57548a873f0
On Linux, an acquisition refused a generation whose payload was not a
directory of the current user with mode 0700, but checked the payload
by its path, which follows links. The release of the lease opens the
payload through the template store's directory instead, following no
link at the store's directory, at the directory of the generations, or
at the payload. A link put in one of those places once the store was
opened thus let the acquisition grant a lease that its release could
not end until the link was gone.

The acquisition now opens the payload as the release does, through the
store's directory, whose identity it reads there, and checks the
payload's owner and mode through the payload's handle. Before it writes
anything, it refuses a payload that it cannot open that way, one that
is gone or whose store's directory holds another store, and one that is
not such a directory.

The review of #949, which made the release open the payload through a
handle, found this.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a424884c-018a-45f5-a7fe-c57548a873f0
A holder that checks whether its VM's VMM has exited, then stops the
VM, attributes to the stop a VMM that exits in between: Vmm::stop finds
it exited, runs no step, reports StopOutcome::Graceful, and clears the
runtime record of its launch. A clone's reattach then finds no record,
and ends the clone as one that its holder stopped, rather than with the
loss of its status.

Vmm::stop_unless_exited stops the VM as Vmm::stop does, unless its own
check, before the graceful step, finds the VMM exited. It then returns
None, runs no step, signals nothing, and keeps the record, so that
reattach_clone finds that the VM exited unheld, records the loss of the
clone's status if no report of the guest's own end tells why, and only
then clears the record. Once that check found the VMM running, its exit
is the stop's, as before.

Vmm::stop, Vmm::terminate, and their callers are unchanged, and so are
the rules of quarantine and of leases: the loss of a status quarantines
nothing, and a lease is released only once its VM's exit is verified.

Tests cover both stops through stand-in VMMs, and a clone whose VMM
exits once its holder found it running, before the holder's stop
begins: the clone fails at its run with the loss of its status.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a424884c-018a-45f5-a7fe-c57548a873f0
A clone's failure is recorded with its teardown pending before the
teardown, so that a crash of the caller during the teardown leaves the
record for a reattach to finish. But a pending record that could not be
written, as on a full disk, was ignored: the teardown still removed the
launch's journal, or the VM's runtime record, and a crash before the
final record then left nothing of the failure. A reattach found a clone
that ended, the lease was released as if nothing had failed, and the
failure was lost, a diagnosis of the template's included.

A clone's lifecycle record, the journal of its launch or the runtime
record of its VM, and with it the lease, now outlives every failure
that cannot be recorded before its teardown:

- A failure of the restore sequence, or of the caller's Activated::fail
  or Started::fail, ends the clone's VMM through its handle, and keeps
  the launch's journal, through Starting::end_keeping_journal, as a
  failed commit does. Its teardown is unresolved until a reattach ends
  the launch, which records the failure, though not how it happened.
- A launch that fails before it returns kills a VMM that it spawned and
  could not record, as before, and keeps its journal for recovery: the
  `failing` callback of supervisor::launch_reporting now returns
  whether the caller recorded the failure.
- reattach_clone fails before it ends or forgets anything of the
  clone's VM: the VM and its records stay as it found them, running or
  not, for a later reattach. The `exited` callback of
  supervisor::reattach_reporting now fails the reattach, and keeps the
  runtime record, if the caller cannot record the VM's exit.
- A record that a write put in place, but whose sync of the sandbox's
  directory failed, may not be durable yet, and a reattach takes it as
  the record of an earlier failure, which it does not write again. So
  reattach_clone syncs the sandbox's directory before it ends, or
  forgets, anything of a launch whose failure the sandbox records, and
  fails as above if the sync fails.

The rules of quarantine and of leases are unchanged: a lease is
released only once its VM's exit is verified, and a failure that a
caller still holds quarantines as before.

Tests make the record of the failure unwritable with a directory in its
place: a caller's diagnosis of the template keeps the launch's journal,
then a reattach finds the clone failed; reattaches of an interrupted
launch, of a VM that exited, of one under another runtime identity, and
of one that fails its activation check end and forget nothing, then
record the failure once they can; and the supervisor keeps the journal
of a launch, and the runtime record of a VM that exited, whose caller
cannot record them. Another refuses the sync of an earlier failure's
record: the reattach then ends nothing, and one that can sync ends the
launch only once it synced.

Reviews of this pull request found these.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a424884c-018a-45f5-a7fe-c57548a873f0
@ppenna
Pedro Henrique Penna (ppenna) force-pushed the esaurez/844-pr4-failures-reattach-20261025 branch from 0352bb9 to 1abc278 Compare October 29, 2026 04:05
Copilot AI balanced review requested due to automatic review settings October 29, 2026 04:05

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

A critical recovery bug can leave clone progress at Launching after a caller crash, causing reattach_clone to return Ended even for repair status 195 because the guard incorrectly requires a recorded spawn.

1 open issue

Review effort: Balanced

Comment thread nvx-rs/src/host/template/clone/reattach.rs Outdated
A reattach read OpenVMM's outcome report of a clone's VM only once
the clone's progress showed the spawn of its VMM, which the launch's
journal records, and its progress log once the launch returns. A
launch whose caller crashed after the spawn, before either recorded
it, is one that a recovery of the sandbox, as supervisor::reattach
runs it, ends once the VMM has exited, removing the journal: nothing
recorded the spawn any more, and a later reattach_clone found the
clone ended at its step launching, even when the guest had ended the
VM with a status that is evidence of a failure, such as one of its
repair, and recorded no failure.

The launch removes the report of an earlier VM of the sandbox before
it creates the clone's progress log, and CloneRecords::read takes a
report as the clone's VM's from then on. The reattach now does too:
exit_fault attributes the report's status once the clone got past its
plan, ReattachedClone::Ended returns the report from that point, and a
reattach that cannot read the record of a failure takes the teardown
of a VM that left a report as an exit, whose status then prevails, as
it does once a record keeps the spawn.

Tests crash a launch once it began, write the report of the VMM that
its journal does not record, and let a recovery of the sandbox end the
launch: a reattach then fails the clone with the evidence and the
status of the guest's repair, or finds it ended with the report of its
VM, and one that cannot read the record of a failure takes the
repair's status too. The case of a launch that never began no longer
writes a report, which no VM of the clone could have left.

The review of this pull request found this.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings October 29, 2026 04:33

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.

🔵 Needs a closer look

Human assessment is needed because recovery can overlook a valid activation record, causing a guest repair failure to go unrecorded and the clone to be reported as ended.

No open issues 🎉

1 resolved since last review

Review effort: Balanced

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants