393 lines
24 KiB
Markdown
393 lines
24 KiB
Markdown
# Disposable systemd VM acceptance contract
|
||
|
||
Status: Milestone 9 is paused at the user's request. The exact disposable VM
|
||
was powered off after a guarded first start and an installer progress screen;
|
||
read-only `list runningvms` was empty afterward. Debian installation completion,
|
||
guest provisioning, reset, cleanup, and systemd acceptance are unverified.
|
||
|
||
This contract supplements [Testing and Acceptance](TESTING.md) and the M9
|
||
criteria in [PLANS.md](../PLANS.md). Product behavior is governed by
|
||
[SPEC.md](SPEC.md), [Runtime observation and verification](RUNTIME.md), and
|
||
[ADRs 0001–0003](adr/).
|
||
|
||
## Selected target
|
||
|
||
The selected target is exactly one new VirtualBox VM:
|
||
|
||
| Field | Required value |
|
||
| --- | --- |
|
||
| Provider | Oracle VirtualBox, local `VBoxManage` 7.2.8_Gentoo |
|
||
| VM name | `wga-m9-debian137-20261025` |
|
||
| Guest base | Official Debian 13.7.0 (Trixie) amd64 netinst ISO |
|
||
| ISO path | `/media/data/wm/Virtualbox/wga-m9-lab/debian-13.7.0-amd64-netinst.iso` |
|
||
| ISO SHA-256 | `a7ef94ac2fb9a7fec454552abd629b7cc9d5155c886165a45649f5ce6167e355` |
|
||
| VirtualBox OS type | `Debian13_64` |
|
||
| Lab root | `/media/data/wm/Virtualbox/wga-m9-lab` (owner `sasa`, mode `0700`) |
|
||
| Networking | One NAT NIC only, no forwarding; all other NICs disabled |
|
||
| VM UUID | `b14c850b-a21e-4a56-acca-785590f0c4ac`, pinned in the mode-`0600` identity manifest |
|
||
| Guest marker | `/etc/wga-m9-lab-marker`, exact content `wga-m9-debian137-20261025` after provisioning |
|
||
|
||
The ISO is present, has the stated SHA-256, and was obtained from Debian's
|
||
[official amd64 netinst directory](https://cdimage.debian.org/debian-cd/current/amd64/iso-cd/).
|
||
VBoxManage command forms were checked against the local 7.2.8 help and Oracle's
|
||
[VirtualBox 7.2 VBoxManage manual](https://docs.oracle.com/en/virtualization/virtualbox/7.2/user/vboxmanage.html).
|
||
Read-only `VBoxManage unattended detect --iso=... --machine-readable` reported
|
||
`OSTypeId=Debian13_64`, `OSVersion=13.7.0 Trixie`, and
|
||
`IsInstallSupported=on`, but then exited 1 with `NS_ERROR_NOT_IMPLEMENTED`.
|
||
The later bounded `unattended install --dry-run --start-vm=none` succeeded for
|
||
this exact VM with private synthetic password files, a fully qualified `.invalid`
|
||
hostname, and the local Guest Additions ISO. The dry run made no VM changes;
|
||
actual unattended preparation and guest boot remain unverified. VirtualBox's
|
||
dry-run output contains plaintext password fields, so the wrapper suppressed
|
||
them and displayed only redacted output. Any actual unattended step needs a
|
||
reviewed exact-target procedure and post-prepare media inspection before start.
|
||
|
||
The approved read-only inventory outside the sandbox found `/dev/vboxdrv`
|
||
available and `VBoxManage` 7.2.8 working. It initially listed Windows 7 and
|
||
Windows 11 VMs; neither is a test target or was modified. The create-only run
|
||
registered only the new Debian target and stopped before configuration. The
|
||
default sandbox does not expose the host driver/device, so host inventory and
|
||
later VBoxManage operations must use the approved outside-sandbox path. The
|
||
guard matches the exact new name/UUID and does not inspect existing VMs'
|
||
per-VM files.
|
||
|
||
## Guarded host operations
|
||
|
||
[`tests/vm/guard.py`](../tests/vm/guard.py) is a narrow standard-library Python
|
||
guard, not a general VirtualBox wrapper. Its default `check`, `start`, and
|
||
`authorize-guest` actions are outside Cargo's default test targets. The guard:
|
||
|
||
- Requires the exact lab root to be a real directory owned by the current user
|
||
with mode `0700`, and `target-identity.json` to be a regular current-user file
|
||
with mode `0600`.
|
||
- Requires the manifest to pin the exact VM name, current owner UID, one valid
|
||
UUID, and host marker `<VM name>:<UUID>`.
|
||
- Uses `VBoxManage list vms` to require that exact name occur once with the pinned
|
||
UUID, then `showvminfo <UUID> --machinereadable` to compare UUID/name/marker.
|
||
- Requires the VM configuration and its one VDI to reside below the lab root,
|
||
the VDI to be named exactly `wga-m9-debian137-20261025.vdi`, and any mounted
|
||
installer ISO to be the pinned Debian ISO in that root.
|
||
- Requires a complete inventory of all eight NIC slots, with only NIC 1 in NAT
|
||
mode. NAT forwarding and all VirtualBox shared folders are refused.
|
||
- Performs every identity, path, and isolation check before `startvm`. A missing,
|
||
duplicate, malformed, or mismatched identity fails closed. The only start
|
||
target is the pinned UUID, in the GUI frontend for console installation.
|
||
- Before guest authorization, repeats all host checks, requires a current-user
|
||
password file with mode `0600`, and reads only the guest marker via
|
||
`VBoxManage guestcontrol ... run /usr/bin/cat` with `--wait-stdout` and a
|
||
15-second guest process timeout. Host stdout/stderr capture is also capped at
|
||
1 MiB while the process runs. The password is supplied using `--passwordfile`;
|
||
it is never placed in argv. Marker output is compared exactly and is not
|
||
echoed. This read is unprivileged inside the guest.
|
||
|
||
The host marker is a pre-install bootstrap identity: it binds the created
|
||
VirtualBox object and protected manifest before the first start. It is not proof
|
||
of guest identity. The guest marker is installed only after Debian setup and is
|
||
checked separately after boot. Do not perform root provisioning, run application
|
||
acceptance, or access the guest system bus until `authorize-guest` succeeds for
|
||
the exact UUID. The guest marker check is deliberately read-only; it does not
|
||
grant the caller a general guest command runner.
|
||
|
||
The exact current focused checks are:
|
||
|
||
```sh
|
||
python3 -m unittest discover -s tests/vm -p 'test_*.py' -v
|
||
python3 -m py_compile tests/vm/guard.py tests/vm/test_guard.py
|
||
git diff --check
|
||
```
|
||
|
||
They use a temporary synthetic VM inventory and fake VBoxManage responses. They
|
||
do not call VBoxManage, start a guest, contact a host service, or read any
|
||
registered VM configuration. The behavioral cases cover normal exact identity,
|
||
missing/duplicate/wrong UUID, wrong name, manifest/root permissions, storage
|
||
escape, NIC mode and incomplete inventory, NAT forwarding, shared folders,
|
||
host/guest marker mismatch, no `startvm` on refusal, and exact UUID start scope.
|
||
|
||
The guard supports these later commands once the selected VM has been created,
|
||
its manifest and marker have been recorded, and the coordinator has reviewed the
|
||
mutation step:
|
||
|
||
```sh
|
||
python3 tests/vm/guard.py check
|
||
python3 tests/vm/guard.py start
|
||
python3 tests/vm/guard.py authorize-guest --password-file /path/to/private/guest-password
|
||
```
|
||
|
||
The placeholder password path above must name a private file owned by the
|
||
current user with mode `0600`. Do not put a password value in the command line,
|
||
environment, terminal history, or output. The `start` command is an implemented
|
||
guarded operation but has not been run. Running it requires the approved
|
||
outside-sandbox path because the default sandbox does not expose the host
|
||
VirtualBox device.
|
||
|
||
## Create-only procedure (ran once; do not rerun)
|
||
|
||
[`tests/vm/create.py`](../tests/vm/create.py) exposes only the fixed create
|
||
operation; it accepts no target, path, or action override. After confirming the
|
||
lab root owner/mode, exact ISO digest, absence of both the identity manifest and
|
||
the exact target VM path (using `lstat`, so dangling symlinks also count as
|
||
occupied), and a well-formed VirtualBox inventory without the selected name, it
|
||
issues bounded VBoxManage calls to:
|
||
|
||
1. Register `wga-m9-debian137-20261025` as `Debian13_64` below the lab root.
|
||
2. Configure 1024 MiB RAM, one CPU, NIC 1 in NAT mode, NICs 2–8 disabled, audio
|
||
disabled, USB disabled, and DVD then disk boot order.
|
||
3. Create one dynamically allocated standard 8192 MiB VDI, attach it on SATA
|
||
port 0, and attach the pinned Debian ISO on SATA port 1.
|
||
4. Write the actual VM UUID and `<name>:<UUID>` host marker in
|
||
`target-identity.json` with mode `0600`, set the same marker as the VirtualBox
|
||
description, then run the existing read-only host guard.
|
||
|
||
Run this exact command only after coordinator review of the current host state
|
||
and the scoped mutations:
|
||
|
||
```sh
|
||
python3 tests/vm/create.py
|
||
```
|
||
|
||
The command must run through the approved outside-sandbox path because the
|
||
default sandbox cannot access the VirtualBox host driver. Its effects are VM
|
||
registration, storage/network/device/boot configuration, ISO attachment, and
|
||
the protected host identity manifest. It does not start the VM, install Debian,
|
||
use guest control, create snapshots, or delete/clean up resources. VBoxManage
|
||
calls have 30-second timeouts and 1 MiB per-stream output limits. Errors omit
|
||
VBoxManage output; after registration they report the exact target name and
|
||
known UUID. A failed procedure retains any partial target and its identity for
|
||
inspection; it performs no automatic rollback or broad cleanup. The final guard
|
||
must pass before the script reports success. The first run registered the UUID
|
||
above, then stopped when strict parsing rejected VirtualBox's indented
|
||
`rec_screen0` recording marker. The parser was narrowly repaired and
|
||
independently re-reviewed. The target path is now occupied, so `create.py` must
|
||
not be rerun; use the reviewed resume procedure below.
|
||
|
||
## Resume the registered partial target (ran once; do not rerun)
|
||
|
||
The earlier create attempt registered the selected target before stopping at
|
||
the initial machine-readable parse. The exact retained target is
|
||
`wga-m9-debian137-20261025` with UUID
|
||
`b14c850b-a21e-4a56-acca-785590f0c4ac`; it is powered off and has only its
|
||
`.vbox` configuration file. Its observed NIC inventory was NIC 1 in NAT with
|
||
NICs 2–8 disabled. It had no identity manifest or VDI. That prior state was
|
||
the sole input accepted by [`tests/vm/resume.py`](../tests/vm/resume.py).
|
||
|
||
The resume-only procedure is fixed to that UUID and lab root. Before its first
|
||
mutation it checks the private root, exact pinned ISO path and SHA-256, absent
|
||
manifest and target VDI, unique registered name/UUID identity, exact `.vbox`
|
||
path, powered-off state, empty host description, default memory/CPU settings,
|
||
the observed NIC inventory (NIC 1 in NAT and NICs 2–8 disabled), no attached
|
||
media or storage controller, no shared folders, and no NAT forwarding. It
|
||
refuses an unexpected adapter or preexisting state.
|
||
After preflight it configures memory/CPU, NAT-only networking, disabled audio
|
||
and USB, boot order, one standard 8192 MiB VDI, and the pinned ISO; then it
|
||
writes the protected host manifest, sets the host marker, and runs the existing
|
||
read-only guard. It does not create, start, unregister, delete, or clean up a
|
||
VM. A failure after mutation retains the exact target and reports its pinned
|
||
UUID for inspection; no rollback is attempted.
|
||
|
||
The focused command is:
|
||
|
||
```sh
|
||
python3 -m unittest discover -s tests/vm -p 'test_resume.py' -v
|
||
```
|
||
|
||
It uses only a temporary synthetic lab and fake VBoxManage responses. It does
|
||
not call VBoxManage or establish host/VM evidence. The implementation command
|
||
was `python3 tests/vm/resume.py`; it ran once via the approved outside-sandbox
|
||
path. It configured the exact target and wrote the host marker/manifest. Its
|
||
final guard call initially refused observed VirtualBox storage metadata;
|
||
focused repairs and independent re-review closed those parser and guard
|
||
findings. A subsequent read-only `python3 tests/vm/guard.py check` passed
|
||
against the actual powered-off target. Do not rerun the resume command on this
|
||
occupied state.
|
||
|
||
Do not infer `unregistervm --delete`, snapshot restore, guest-control, or broad
|
||
cleanup commands from the CLI help. No provisioning command is documented
|
||
because Debian installation, Guest Additions/management channel, the unprivileged
|
||
`wga` account, and guest marker placement have not been validated. No reset or
|
||
deletion command is documented because a baseline snapshot and exact owned
|
||
resource IDs do not yet exist. Existing registered VMs must remain untouched.
|
||
|
||
## Unattended preparation (ran once; do not rerun)
|
||
|
||
[`tests/vm/install_prepare.py`](../tests/vm/install_prepare.py) is pinned to the
|
||
VM UUID above and accepts no target, path, credential, or action overrides. It
|
||
first checks the current user's `/tmp/wga-m9-private` directory is a real mode
|
||
`0700` directory and its nonempty `wga-password` and `root-password` files are
|
||
current-user-owned regular mode-`0600` files. These are synthetic install
|
||
credentials only; password contents never enter argv or output. It verifies the
|
||
pinned Debian ISO and `/usr/share/virtualbox/VBoxGuestAdditions.iso` SHA-256
|
||
digests, then calls the existing exact-target read-only guard and additionally
|
||
requires the VM to be powered off, with the expected VDI at `SATA-0-0`, the
|
||
pinned installer ISO at `SATA-1-0`, and every other reported SATA attachment
|
||
slot set to `none` or `emptydrive`. This accepts VirtualBox 7.2.8's real
|
||
`showvminfo` inventory (`SATA-2-0` through `SATA-29-0` are `none`) while
|
||
rejecting any additional attached media. It refuses before mutation if any
|
||
check fails.
|
||
|
||
The script runs a bounded `VBoxManage unattended install <pinned UUID>` dry run
|
||
first with `--iso=<pinned Debian ISO>` and
|
||
`--admin-password-file=/tmp/wga-m9-private/root-password`, using the synthetic
|
||
`wga` account and password file, `.invalid` FQDN, `en_US`, `UTC`, US country,
|
||
minimal package adjustment, and the pinned Guest Additions ISO. The root
|
||
credential option is `--admin-password-file`; `--root-password-file` is not a
|
||
valid option for the local VBoxManage 7.2.8. Only a successful dry run permits
|
||
the same bounded command without `--dry-run`. Both commands request
|
||
`--start-vm=none`; the script has no start, cleanup, or retry operation.
|
||
VBoxManage output is captured privately because its dry-run output contains
|
||
plaintext password fields; errors and status output omit captured text. The
|
||
command has a 120-second deadline and 1 MiB per-stream capture limit.
|
||
|
||
After independent review, the command ran once through the approved
|
||
outside-sandbox VBoxManage path:
|
||
|
||
```sh
|
||
python3 tests/vm/install_prepare.py
|
||
```
|
||
|
||
The command exited 0 and left the VM powered off. Read-only `showvminfo`
|
||
inspection found the exact VDI at SATA 0 and a VirtualBox-generated auxiliary
|
||
`.viso` at SATA 1, both in the private target folder. The generated installer
|
||
files are mode `0600`; the target folder is mode `0700`. The normal host guard
|
||
refuses the `.viso`, as designed, so the first start requires a separate guard
|
||
for this exact installer state and independent review. Do not rerun preparation
|
||
on the occupied state. Guest boot, Guest Additions setup, and guest provisioning
|
||
are not established by this result or the synthetic tests.
|
||
|
||
The focused behavioral tests use only temporary synthetic files and a fake
|
||
VBoxManage runner:
|
||
|
||
```sh
|
||
python3 -m unittest discover -s tests/vm -p 'test_install_prepare.py' -v
|
||
```
|
||
|
||
## First start of the prepared installer (ran once; VM stopped)
|
||
|
||
[`tests/vm/install_boot.py`](../tests/vm/install_boot.py) adds a separate,
|
||
fixed-target `check`/`start` command for the one powered-off installer state.
|
||
The ordinary [`guard.py`](../tests/vm/guard.py) deliberately continues to
|
||
reject the generated `.viso`; this installer guard does not loosen that normal
|
||
guard. It reuses the strict machine-readable parser and checks the exact
|
||
current-user-owned mode-`0600` identity manifest, pinned UUID and matching host
|
||
description, exact target name/configuration path, and current-user-owned
|
||
mode-`0700` target folder. It requires the VDI at SATA 0 and exactly
|
||
`Unattended-b14c850b-a21e-4a56-acca-785590f0c4ac-aux-iso.viso` at SATA 1;
|
||
SATA ports 2–29 must all be `none`. The `.viso` and any generated
|
||
`isolinux`/`preseed`/`postinstall` files are checked as current-user-owned
|
||
mode-`0600` regular files with exactly one hard link. The VM configuration and
|
||
VDI are also required to be regular current-user-owned files with exactly one
|
||
hard link. The guard never opens or prints installer-file contents.
|
||
The VM must remain powered off during preflight, have only NIC 1 in NAT mode,
|
||
and have no forwarding rules or shared folders. Symlinked paths, extra media,
|
||
unknown attachment metadata, and incomplete or mismatched identity refuse
|
||
before start.
|
||
|
||
The exact commands are:
|
||
|
||
```sh
|
||
python3 tests/vm/install_boot.py check
|
||
python3 tests/vm/install_boot.py start
|
||
```
|
||
|
||
`check` only reads the protected manifest, filesystem metadata, and bounded
|
||
VirtualBox inventory; it does not change VM state. `start` repeats all checks,
|
||
then issues only `VBoxManage startvm b14c850b-a21e-4a56-acca-785590f0c4ac
|
||
--type=headless`. It accepts no UUID, root, media, credential, or action
|
||
override. Each VBoxManage call uses the existing guard's 15-second deadline and
|
||
1 MiB per-stream output cap. Output is not copied into errors. If the start call
|
||
fails or times out, the result explicitly says the VM may have started and
|
||
requires a separate read-only inspection; there is no retry, cleanup, or
|
||
rollback. A successful command means only that VirtualBox accepted the start
|
||
request. It does not prove guest boot, Debian installation, or Guest Additions.
|
||
|
||
Focused fake-runner coverage is:
|
||
|
||
```sh
|
||
python3 -m unittest discover -s tests/vm -p 'test_install_boot.py' -v
|
||
```
|
||
|
||
These tests create synthetic private files and never call VBoxManage. Both
|
||
commands ran once against the actual VM after independent review. `check`
|
||
passed; `start` returned success; VirtualBox listed only the pinned target as
|
||
running. A private console screenshot showed Debian's base-system installer at
|
||
6%. The user then stopped Milestone 9, so the exact target was powered off and
|
||
read-only `list runningvms` returned no running VMs. Do not restart it without
|
||
a new explicit request. Installation completion and guest boot remain
|
||
unverified.
|
||
|
||
After installation, provision only synthetic lab packages,
|
||
fixed reviewed service units, synthetic WireGuard keys/configuration, and the
|
||
guest-local marker. Keep all server/client data, outputs, and working backups
|
||
under a guest-only root-owned `0700` directory; secret files are `0600`. The
|
||
guest-local marker must be readable by the unprivileged `wga` account for the
|
||
read-only marker gate. This work belongs to the next task after target creation
|
||
and guard review; it is not claimed as implemented here.
|
||
|
||
## Isolation and acceptance boundary
|
||
|
||
- Use synthetic server/client keys, configuration, settings, and vault contents
|
||
generated inside the guest. Preserve imported originals. Do not copy or read
|
||
production keys, configuration, backups, or credentials.
|
||
- The VM may have only one NAT management adapter, with no forwarding rules;
|
||
do not use bridged, host-only, internal, NAT-network, USB network, or physical
|
||
adapters. NAT-only VM egress still traverses the host's ordinary network; do
|
||
not claim host-independent network isolation. No host D-Bus, `/etc`, `/run`,
|
||
`/var`, WireGuard interface, service control, or general shared folder may be
|
||
mounted or forwarded into the guest.
|
||
- A guest system D-Bus and PID 1 must be guest-local. Keep console management
|
||
independent of the VPN under test. Tunnel probes target only synthetic
|
||
addresses reachable through the test tunnel, never the NAT management path.
|
||
- Provision only fixed, reviewed lab units/drop-ins and fixture hooks. Imported
|
||
configuration text remains data. Preserve administrator settings and prove
|
||
preflight refusal using synthetic fixtures rather than silently rewriting
|
||
`SaveConfig`.
|
||
- Exercise the public `Application::restart` flow with the real zbus service
|
||
adapter and Rustix observer. A test seam may inject persistence failures only
|
||
where already established; it may not replace service submission or runtime
|
||
observation as evidence of systemd behavior. Alpine fixture apply remains
|
||
separate evidence.
|
||
- Run the actual release executable separately from any `cfg(test)` driver.
|
||
Capture source/dirty identity, compiler/locked dependencies, target, artifact
|
||
SHA-256, and dynamic-library requirements for both artifacts. Prove each
|
||
executable starts in the guest.
|
||
- Keep secrets out of argv, environment variables, image metadata, logs,
|
||
transcripts, and evidence. Use bounded typed control, private files/channels,
|
||
monotonic timeouts, and bounded output. A timeout after service submission
|
||
means it may still complete; it is not cancellation.
|
||
- Before every scenario, reset to a recorded clean guest state and verify both
|
||
the pinned host identity and guest marker. Afterward, stop only resources
|
||
created by that run and verify their exact IDs. If cleanup is uncertain,
|
||
preserve IDs and report them; never use broad host cleanup. Preserve the
|
||
original failure if cleanup also fails.
|
||
|
||
## Required scenario evidence
|
||
|
||
For every run, record UTC start/end, guest and base identities, kernel/systemd/
|
||
D-Bus/WireGuard versions, both marker results, source/artifact identities, exact
|
||
commands, exit status and bounded redacted outcomes, backup/archive identities,
|
||
reset/cleanup result, and the acceptance criteria supported. Keep raw console
|
||
output private; evidence must contain no secrets or complete client config.
|
||
Record each scenario independently so failures cannot be masked by unrelated
|
||
fixture behavior.
|
||
|
||
| Scenario | Required observable evidence |
|
||
| --- | --- |
|
||
| Successful apply and fast job | Existing synthetic client has positive tunnel-only IPv4 traffic. Through one `Application`, create and Commit a client; show saved/runtime divergence and that Commit did not submit a service job. Cancel preview with no backup/job/mutation. Confirm restart: pre-restart backup completes first, real zbus job for the resolved primary unit reaches `done`, Rustix observes the expected interface and complete matching peer/routes, and durable verification is written. Verify new tunnel traffic, reopen and show verification is historical. Repeat with fast completion and a configured alias resolving to the primary unit; retain the completion signal. Include application outcomes, not journal inference alone. |
|
||
| Deletion and reservations | Establish positive traffic before deleting/Committing. Show the client remains in runtime and its address remains reserved until successful real restart and complete verification. Then show it absent, tunnel-only traffic fails within a bounded probe, durable reservation releases, and old backups remain. |
|
||
| Failure and deadline | Using fixed guest-only units/drop-ins, show terminal job failure and submitted-job deadline expiry with bounded results, accurate submitted/may-still-complete status, no automatic retry/rollback, no false Verified state, and retained reservations. Also show unresolved unit fails before submission and backup failure prevents restart. After late completion, explicitly observe and reset the guest; timeout is not cancellation. |
|
||
| Job succeeds, verification fails | A dedicated fixed fixture service completes while leaving the interface absent or peers mismatched. Show the real adapter reports verification failure and reservations remain. Separately inject saved-state write failure only through the established persistence seam after real restart/observation; show runtime changed without durable Verified state and `RecoveryRequired` blocks unsafe mutations. |
|
||
| Restore and manual recovery | Preview and confirm a known-good archive without vault password. Show precautionary backup, full saved settings/config/vault replacement, and unchanged runtime before explicit restart. Then run real restart/verification and expected tunnel check. Demonstrate documented manual recovery; no hidden rollback. Confirm permissions and reopen behavior. |
|
||
| Release executable smoke | In guest console/PTY, prove identified release binary starts from its private directory and settings resolve beside it. Exercise current-client/statistics view and one controlled Commit/restart through the real worker path; confirm exit/cancel restores terminal. Help/version work as non-root without settings; non-root management is rejected. Record output/backup permissions. |
|
||
|
||
The final M9 evidence must map these observations to M9-A1–A9 and the
|
||
specification-to-evidence matrix. State the tested scope, including IPv4 tunnel
|
||
coverage, synthetic dual-stack tests, unverified real IPv6 behavior, and
|
||
QR/device interoperability limits. A mocked bus, Alpine fixture apply, missing
|
||
prerequisite, skipped test, or guest boot alone does not satisfy real systemd
|
||
acceptance.
|
||
|
||
## Next action
|
||
|
||
None while Milestone 9 is paused by the user. The disposable VM is powered off;
|
||
do not resume installation, provision it, or run acceptance without a new
|
||
explicit request. No M9-A1–A9 check is complete.
|