Files
Hydra-OS/docs/verification.md

11 KiB
Raw Permalink Blame History

Graphics and recovery verification

The 2026-10-13 preview record identifies the exact ISO, firmware and harness used for the passing twelve-check acceptance run. The unmodified ISO installed onto a fresh 32 GiB qcow2 disk and booted from that disk with no ISO attached. Password login, persistent data, removed live overrides, installed services, DNS lookup, routed LAN-to-VM TCP, normal-user nested KVM/bridge access and normal shutdown passed. An actual logout followed by 15 seconds without an open login and a new login preserved the same VM, QMP/SPICE sockets and user audio services. Audio playback was not measured.

Live graphical recovery was also observed through a private QEMU screenshot; the earlier two-guest compositor and profile-refresh probes cover fullscreen switching and hidden workload continuity. These virtual checks leave the physical ASUS checks listed below pending. No current host configuration or existing user VM disk was changed for this validation.

Run from the checkout. Tools are temporary Nix development dependencies; no persistent host installation is required. The two-guest smoke test needs access to /dev/kvm and creates its own headless Wayland display, VM disks and sockets under a fresh /tmp/hydra-smoke-* directory. It never uses the inherited display, active TTY, existing guests or existing disks. Evidence remains in that directory; the script stops only its own verified QEMU instances on exit.

python3 -m unittest discover -s tests -v
nix-build dev/tools.nix -A viewer --no-out-link
nix-build dev/tools.nix -A qemu --no-out-link
nix-shell dev/tools.nix --run 'python3 dev/smoke.py'
nix-shell dev/tools.nix --run 'python3 dev/session-refresh-smoke.py --keyboard "$(dev/build-input-probe.sh)"'
nix-shell dev/tools.nix --run 'python3 dev/check-network.py'

The smoke test builds a private input helper with an ordinary evdev/pc105 US keyboard map. It boots two tiny counter workloads from separate scratch qcow2 disks, connects two fullscreen viewers, and checks reserved switching/recovery, manual USB selection, explicit reconnect and independent QMP lifecycles. After each confirmed workspace switch, the hidden guest is sampled across a half-second interval with no focus changes, switches or reconnects; the check is then repeated with the other guest hidden. result.json records both intervals. Guest memory counters must advance and QEMU PIDs remain unchanged through switching. Viewer protocol logs must show no reconnection during switching. A test-only ToggleKeybinds action and F12 marker establish native labwc keybinding inhibition before checking the production reserved bindings. Those extra controls are absent from the production session configuration. See the labwc configuration manual for overrideInhibition and the actions manual for native ToggleKeybinds behavior.

USB acceptance checks the actual selector window title, holds the modifiers through opening, releases them inside the modal dialog, then asserts fresh keyboard leave/enter transitions for both Right and Left switches. QEMU's USB device list must stay unchanged without a selection. Both viewer USB filters deny automatic hotplug and connection redirection. This check does not attach a physical USB device or prove guest driver behavior.

The viewer adaptation handles only the configured manual USB accelerator before forwarding an event to the guest. Upstream keyboard grabs remove the GTK application accelerator group; without this adaptation, the configured USB shortcut reaches the viewer but does not open its selector.

The pinned labwc 0.20.2 runtime has no documented runtime XWayland-disable option. This native-Wayland probe sets WLR_XWAYLAND=/bin/false so no X server is launched, and explicitly disables activation-environment updates. It can still log an occupied X socket while wlroots reserves a display number. Only [xwayland/sockets.c] Failed to bind socket ... Address already in use is classified as expected noise in result.json; any other compositor [ERROR] fails the probe. Full logs remain available. This classification does not suppress viewer errors or compiler/test diagnostics. The viewer build's upstream Meson boolean-check and compiler-warning-option guidance are build noise; the four-test result, compiler errors and process exit status remain the build acceptance gate.

Fresh-session recovery and profile changes

A session with no profiles opens recovery with first-use guidance. Enter :shell to run hydra add NAME --iso PATH and hydra start NAME; leave the shell and enter :refresh. After adding or removing profiles, refresh reloads the profile list, rebuilds workspace rules, restarts only the session's own compositor, and attaches new isolated viewers to running guests. QEMU processes and workloads survive. The recovery prompt can then reconnect the new names; reserved Left/Right chords switch between their workspaces. :quit closes the recovery prompt.

dev/session-refresh-smoke.py exercises the actual product launch loop, recovery helper, :shell and :refresh, starting with an empty profile directory. A test-only labwc wrapper selects private headless backends; a terminal command feeder drives recovery and a scratch shell adds and starts two counter guests. Actual labwc and native Wayland viewers then demonstrate fresh keyboard focus transitions in both directions, hidden workload continuity, unchanged QEMU PIDs/UUIDs through refresh, and guest survival after normal compositor exit. This checks the recovery command flow, not physical terminal rendering/usability. It leaves snapshots, recovery output, protocol logs and result.json in /tmp/hydra-session-refresh-* and stops only its verified scratch QEMU processes.

Bridge identities

New profiles persist a locally administered unicast mac in their TOML file. It remains stable across stop/start, media changes and runtime socket-directory changes. The generated address incorporates the VM disk path and user identity, so separate profiles/users avoid QEMU's identical default MAC on a shared bridge. Existing profiles without mac use the same deterministic calculation without rewriting their file. Relocating a legacy disk or changing its owner changes that fallback; explicitly persisting its current address retains the identity. Explicit TOML addresses must also be locally administered unicast, and users copying profiles must assign distinct addresses to concurrently used copies.

dev/check-network.py requires unprivileged user/network/mount namespaces. It asserts all three namespace identities changed before creating a private hydrabr0 and mounting a private QEMU bridge-helper ACL over /etc within that namespace. Two actual scratch QEMUs must report different configured NIC MACs, both bridge TAP ports must exist, and restart must preserve the first guest's address. Evidence remains under /tmp/hydra-network-*; host bridges, host /etc and existing guests are unchanged. This does not prove guest network drivers, DHCP leases or external connectivity; those need the installed-guest harness.

Installed guest workflow

--iso is optional. Attach or detach media only while the VM is stopped:

bin/hydra add guest --iso /absolute/path/installer.iso
bin/hydra start guest
bin/hydra view guest
# Shut down from inside the guest, then check it is stopped.
bin/hydra status guest
bin/hydra media guest --detach
bin/hydra start guest

For an existing stopped guest, bin/hydra media guest --iso PATH attaches installation/rescue media. A missing configured ISO prevents a new start with an actionable error; it does not prevent listing profiles, status, stopping a running guest, reconnecting a viewer or detaching the missing media. Media updates share the VM lifecycle lock and preserve its qcow2 disk. Abrupt stopping still requires bin/hydra stop guest --force and verified QMP identity.

Display preferences and EDID

New developer profiles use user networking, 1280×800 and refresh_millihz=0. Zero omits the patched QEMU refresh property and remains compatible with stock QEMU. Hydra's installed defaults can use this flat /etc/hydra/defaults.toml:

network = "bridge"
bridge = "hydrabr0"
width = 1920
height = 1080
refresh_millihz = 144000

Only add reads distribution defaults. Explicit --network, --bridge, --width, --height and --refresh-millihz options win; existing profiles do not change. --defaults PATH selects another preferences file for development. Missing defaults use developer preferences; unknown or invalid fields fail before creating a profile. Dimensions are integers from 1 to 16384, refresh is zero for automatic refresh or an integer from 50000 to 255000 mHz, matching the vertical range descriptor limits. This bound does not establish every resolution/refresh combination; pixel-clock and horizontal timing limits are mode-dependent and the verified target remains 1920×1080/144000 mHz. Nonzero refresh requires the tracked patched QEMU provided by dev/tools.nix and distribution packaging.

The patch keeps the requested refresh through frontend resizing and extends EDID monitor ranges to include the generated preferred mode. To verify the actual patched QEMU generator with the pinned source archive:

qemu_source=$(nix-build dev/tools.nix -A qemu.src --no-out-link)
nix-shell dev/tools.nix --run "python3 dev/check-edid.py '$qemu_source'"

The probe applies the tracked patch to four relevant source files in a temporary directory, compiles the actual EDID and virtio GPU generator functions with a small dependency harness, and decodes the result. It checks 60/75 Hz fallback, 144 Hz configuration, a conflicting 60 Hz frontend request, resized dimensions, checksums and monitor ranges. It also checks the supported vertical-refresh boundaries: 1920×1080/50000 mHz and 1280×800/255000 mHz. Range endpoints use floor/ceiling of the encoded timing after pixel-clock rounding, so the 49.998895 Hz lower-boundary timing is included by a 49 Hz minimum. Normal automatic 60/75 Hz cases retain the legacy 50–125 Hz range. At 1920×1080/144000 mHz the preferred timing is 143.997231 Hz after EDID clock rounding. This is an advertised guest mode, not measured frame delivery or proof of a 144 Hz physical output.

Still requiring a dedicated physical session

  • Physical reserved chords, brightness/backlight permissions and touchpad input.
  • Physical held keys/modifiers across guest switching, grabs and modal dialogs; guest key-up delivery, pointer grab release and absence of stuck modifiers.
  • Physical USB selection/redirection and guest drivers; no automatic redirection.
  • Toolbar behavior at physical screen edges and actual recovery shell usability.
  • ASUS output selection at 1920×1080/144 Hz, guest-reported display mode, delivered frame rate, hidden guest graphics continuity, tearing and switching latency.

Headless checks establish CPU workload continuity, protocol focus transitions and the exercised compositor actions. They cannot prove physical input or scanout.