Skip to content

Local mvm validation

CI exercises the host CLI and SDK contracts against a fake-mvmctl shim. To prove the generated flake actually evaluates against the real mvm/guest-lib and (eventually) boots a VM, you run two recipes locally.

Both recipes need a local mvm checkout. Set its path via MVMFORGE_MVM_REPO:

Terminal window
export MVMFORGE_MVM_REPO=/path/to/mvm

mvmctl itself doesn’t need to be on PATH — the recipes invoke it via cargo run --manifest-path "$MVMFORGE_MVM_REPO/Cargo.toml".

just real-mvm-check

Compile an SDK entry, then run nix flake check against the generated artifact. No VM boot. Lighter than real-mvm-up; runs on macOS without a Lima dev VM. Requires native nix on PATH.

Terminal window
just real-mvm-check # default: tests/corpus/hello-app/app.py
just real-mvm-check tests/corpus/with-source/app.py

What this validates:

  • The Python SDK loads and emits canonical IR.
  • The host parses and validates the IR.
  • The compile step writes deterministic flake.nix, launch.json, and src/.
  • Real Nix evaluates the flake, resolves all inputs (the pinned mvm flake, nixpkgs via follows), and reports mvmforge.workload’s shape.
  • The mkGuest derivation is actually constructible.

What it does not validate:

  • Actual VM boot. The flake-check stops short of nix build; for a bootable rootfs, see real-mvm-up.

just real-mvm-up

The full pipeline: mvmforge up <entry> calls mvmctl up --flake <artifact>. mvmctl evaluates and builds the flake, then boots the resulting VM via your platform’s backend. Requires:

  • Linux/KVM, or
  • macOS 26+ on Apple Silicon (Apple Virtualization), or
  • macOS < 26 with a Lima dev VM (mvmctl bootstrap first).
Terminal window
just real-mvm-up tests/corpus/with-source/app.py

Forward extra mvmctl up flags via MVMFORGE_UP_ARGS:

Terminal window
MVMFORGE_UP_ARGS='--hypervisor apple-container --name myvm' \
just real-mvm-up tests/corpus/with-source/app.py

Or pass them on the command line directly via mvmforge up’s -- separator:

Terminal window
mvmforge up tests/corpus/with-source/app.py -- --hypervisor apple-container --name myvm

Useful flags from mvmctl up --help:

FlagPurpose
--hypervisorfirecracker, apple-container, qemu, docker
--nameVM name (auto-generated if omitted)
--cpusOverride vCPU count
--memoryOverride memory (e.g., 512M, 4G)
-p HOST:GUESTPort mapping
-e KEY=VALInject env var
--detach / -dBackground mode
--watchAuto-rebuild + reboot on flake changes

What success looks like

A successful boot of tests/corpus/with-source/app.py:

  1. Compile lands flake.nix + launch.json + src/hello/{__init__,__main__}.py.
  2. mvmctl evaluates the flake; nix build produces mvm-with-source containing vmlinux, rootfs.ext4, and image.tar.gz.
  3. The selected backend boots the VM.
  4. mkGuest’s busybox init starts the with-source service.
  5. services.<id>.preStart symlinks the bundled source to /app.
  6. services.<id>.command runs cd /app && exec python -m hello.
  7. hello from a bundled mvmforge workload appears in the boot log.

Capture the log to file when validating:

Terminal window
MVMFORGE_UP_ARGS='--hypervisor apple-container' \
just real-mvm-up tests/corpus/with-source/app.py 2>&1 | tee /tmp/mvm-boot.log

The first successful boot’s log gets committed to tests/fixtures/real-mvm-boot-log.md per the runbook in that file.

Working against a local mvm without a real boot

If you just want to iterate on the generated flake without going through mvmctl:

Terminal window
export MVMFORGE_MVM_FLAKE_URL="path:$MVMFORGE_MVM_REPO/nix/guest-lib"
mvmforge compile manifest.json --out /tmp/artifact
nix flake check /tmp/artifact
nix eval path:/tmp/artifact#packages.x86_64-linux.default.name

Note: byte-reproducibility holds per-machine but not across machines with different MVMFORGE_MVM_FLAKE_URL overrides (per ADR-0007 §6).