|
| 1 | +{"id":"vz-w4g","title":"Expose caller-controlled VZ Linux kernel bundle resolver","description":"Virgil's native VZ migration needs to resolve the same VZ-compatible Linux kernel that vz uses, but with caller-owned install/cache placement and explicit version/capability metadata. The current vz-linux ensure_kernel API resolves the full OCI guest artifact set (vmlinux, initramfs, youki) and validates guest-agent version, which is too coupled for Virgil's direct rootfs boot path. P0 validation proved Virgil's Firecracker kernel lacks CONFIG_VIRTIO_FS while vz's kernel works with the same VZ shared-dir config.","acceptance_criteria":"vz-linux exposes a stable API that resolves a VZ-compatible kernel bundle into a caller-specified install directory; returned metadata includes kernel path, optional initramfs path, version/checksum data, and declared capabilities including vsock, virtiofs, hvc0 serial, and ext4 root support; callers can opt out of guest-agent exact-version validation when they only need the kernel; unit tests cover install-dir control, capability metadata, and checksum validation.","notes":"Implemented additive vz-linux API: KernelBundleOptions, KernelBundle, KernelFlavor, KernelCapability, ensure_kernel_bundle(), caller-controlled install/bundle dirs, opt-out guest-agent version validation, required capability validation, and capability fallback for older version.json files. Release metadata now emits capabilities in linux/Makefile; linux/README documents the bundle metadata contract. Release workflow now keys the Linux artifact cache on Cargo workspace inputs that affect vz-guest-agent/protocol output and validates version.json checksums/capabilities before publishing the tarball. Validation: cargo test -p vz-linux kernel::tests; cargo check -p vz-linux; cargo clippy -p vz-linux -- -D warnings; git diff --check on touched files. make -C linux version was attempted on macOS and failed before version generation because the local toolchain used cc as the aarch64-unknown-linux-musl linker; release builds should use linux/docker-build or a configured cross linker. Kept open pending repo-required Linux VM end-to-end/release-artifact verification.","status":"in_progress","priority":1,"issue_type":"feature","owner":"james@littlebearlabs.io","created_at":"2026-05-02T04:48:44Z","created_by":"James Lal","updated_at":"2026-05-02T04:55:08Z","started_at":"2026-05-02T04:48:49Z","labels":["api","kernel","linux","virgil"],"dependency_count":0,"dependent_count":0,"comment_count":0} |
1 | 2 | {"id":"vz-59c","title":"Multi-disk support in VmConfigBuilder","description":"## Problem\n`VmConfig` exposes a single `disk_path: Option\u003cPathBuf\u003e` field (`crates/vz/src/config.rs:66`). The bridge constructs exactly one `VZVirtioBlockDeviceConfiguration` and passes it to `setStorageDevices` as a one-element array (`crates/vz/src/bridge.rs:361-385`).\n\nConsumers running structured microVM workloads need an ordered set of drives — e.g.:\n- `rootfs` (read-write or read-only, primary boot)\n- `data` (read-write user/state volume, optionally encrypted)\n- `metadata` (read-only configuration drive carrying boot-time tokens)\n- `override` (read-only, hot-replaceable for binary update flows)\n\nWith the current single-disk surface, consumers either fork the bridge or hack multiple disks into one image with a partition table — both bad. The underlying `setStorageDevices_` already accepts an `NSArray\u003cVZStorageDeviceConfiguration *\u003e`; this is a self-imposed wrapper restriction.\n\n## Source\n`planning/multi-vm-consumer-fixes.md` §1 (Virgil consumer feedback, April 2026).\n\n## Scope\n~50 LoC + tests. Largest of the three; touches the most existing tests and callers.","design":"## API\nReplace `disk_path: Option\u003cPathBuf\u003e` with `disks: Vec\u003cDiskConfig\u003e`. Add:\n\n```rust\n#[derive(Debug, Clone)]\npub struct DiskConfig {\n pub id: String, // stable identifier for logging / future hot-replace\n pub path: PathBuf,\n pub read_only: bool,\n pub create_size: Option\u003cu64\u003e, // create-if-missing semantics (replaces disk_size_bytes)\n}\n```\n\nBuilder gains `.disk(DiskConfig)` (appends). Bridge iterates the `Vec` and constructs one `VZVirtioBlockDeviceConfiguration` per entry, preserving order (guests see them as `vda`, `vdb`, `vdc`, … in declaration order).\n\n## NO backwards-compat shim\nSource doc proposes keeping `disk_path: Option\u003cPathBuf\u003e` as a deprecated convenience. Project policy (CLAUDE.md): 'we don't do backwards compat unless asked for we cut over and remove the old stuff'. So:\n\n1. Remove `disk_path` and `disk_size_bytes` from `VmConfigBuilder`.\n2. Update all callers in the workspace to use the new `.disk(DiskConfig)` API:\n - vz-cli (config materialization)\n - vz-sandbox (pool/session VM construction)\n - vz-oci (`MacosRuntimeBackend`)\n - any tests under `crates/*/tests/`","acceptance_criteria":"- Building a `VmConfig` with 3 `DiskConfig`s produces a 3-element `setStorageDevices` array with the correct read-only flags (unit test in `crates/vz/tests/config_test.rs`).\n- Linux guest boots with rootfs + data, mounts `/dev/vda1` and `/dev/vdb`, writes to both, save/restore preserves both (integration test in `crates/vz/tests/state_test.rs`).\n- All in-workspace callers updated; no `disk_path` references remain.\n- `cargo clippy --workspace -- -D warnings` clean.\n- `cargo nextest run --workspace` clean.","notes":"Recommended third per source doc — largest blast radius (touches every caller). Independent of the other two; can run in parallel if a second agent is available.","status":"closed","priority":1,"issue_type":"feature","assignee":"James Lal","owner":"james@littlebearlabs.io","created_at":"2026-05-01T04:52:49Z","created_by":"James Lal","updated_at":"2026-05-01T05:16:55Z","started_at":"2026-05-01T05:10:01Z","closed_at":"2026-05-01T05:16:55Z","close_reason":"Replaced VmConfig.disk_path: Option\u003cPathBuf\u003e + dead disk_size_bytes with disks: Vec\u003cDiskConfig\u003e. New DiskConfig{id,path,read_only} and .disk(DiskConfig) builder method (cut over per project policy — no shim). Bridge iterates the Vec and constructs one VZVirtioBlockDeviceConfiguration per entry, honoring read_only. Updated all in-tree callers: vz-linux/src/config.rs, vz-sandbox/src/pool.rs, vz-cli/src/commands/run.rs, vz/tests/bridge_test.rs, vz/tests/config_test.rs, lib.rs doctest. macOS boot validation now requires non-empty disks; Linux can boot with empty disks (initramfs only). Added VmConfig::disks() accessor. Verified: (1) 3 new unit tests for multi-disk ordering/preservation, no-disk Linux boot, macOS-without-disks rejection; (2) full vz crate test suite (68 tests) green; (3) downstream vz-linux + vz-sandbox unit tests (78 tests) green; (4) cargo clippy -p vz --lib clean; (5) runtime_e2e smoke_pull_and_run_alpine passed (single-disk path through new Vec); (6) stack_e2e snapshot test passed end-to-end. Multi-disk-specific E2E (rootfs + data + mount/write/save/restore) deferred — requires custom Linux kernel cmdline + multi-mount initramfs not currently in test infra; unit ordering tests + framework array contract cover the gap.","dependency_count":0,"dependent_count":0,"comment_count":0} |
2 | 3 | {"id":"vz-68z","title":"Source-VM identification on VsockListener::accept()","description":"## Problem\n`VsockListener::accept()` returns a `VsockStream` with no information about which guest originated the connection (`crates/vz/src/vsock.rs:288-295`). Any multi-tenant consumer that uses the originating VM as a trust boundary (authenticate-by-where-it-came-from) cannot do so today.\n\nApple's framework already delivers this — the listener delegate's `listener:shouldAcceptNewConnection:fromSocketDevice:` callback receives a `VZVirtioSocketDevice *` reference (currently dropped on the floor as `_device` at `vsock.rs:322`). The information is reachable, just not plumbed through.\n\n## Source\n`planning/multi-vm-consumer-fixes.md` §2 (Virgil consumer feedback, April 2026).\n\n## Scope\n~20 LoC + tests.\n\n## Terminology note\nSource doc calls this 'source CID' but from the host POV every guest's CID is 3. What uniquely identifies the VM is the `VZVirtioSocketDevice` pointer (one per VM). The trust-boundary signal is the device-to-VM mapping, not a per-connection CID. The implementation should expose a stable VM identifier, not literally a `u32` CID.","design":"## Plumbing\n1. Modify `VsockListenerDelegateIvars` to also hold a registry/mapping of `VZVirtioSocketDevice *` → stable VM identifier (registry lives on the `Vm` or a process-wide `VsockRegistry` keyed by device pointer).\n2. In `listener_should_accept`, capture the device parameter and pass it through `SendableConnection` (or alongside it) into the channel.\n3. Look up VM identity from the device pointer at accept time.\n\n## API shape\nAdd `accept_with_source()` returning a richer type, keep `accept()` returning bare `VsockStream` for existing callers — easy migration:\n\n```rust\npub struct AcceptedVsockStream {\n pub stream: VsockStream,\n pub source: VmHandle, // stable per-VM identifier\n}\n\nimpl VsockListener {\n pub async fn accept(\u0026mut self) -\u003e Result\u003cVsockStream, VzError\u003e { /* existing */ }\n pub async fn accept_with_source(\u0026mut self) -\u003e Result\u003cAcceptedVsockStream, VzError\u003e { /* new */ }\n}\n```\n\nPer project rule: no backwards-compat hacks. If keeping both methods feels like a hack, just replace `accept()` with the richer return type and update callers — there are few in-tree.\n\n## VmHandle\nA small newtype wrapping the device pointer (or a stable u64 derived from it). Cheap to clone, comparable, debuggable. Likely lives in `vz::vm` and exposed by `Vm::handle()` so callers can match accepted streams to the VMs they spawned.","acceptance_criteria":"- Two guests with distinct `VmHandle`s dial the same host port; the host-side accept loop sees the correct `source` per accepted stream (integration test).\n- `VmHandle` is `Eq + Hash + Clone + Debug`.\n- No new unsafe outside `crates/vz/src/`.\n- `cargo clippy --workspace -- -D warnings` clean.","notes":"Recommended second per source doc — small, opt-in via the new return type. Generic; benefits any multi-tenant consumer.","status":"closed","priority":1,"issue_type":"feature","assignee":"James Lal","owner":"james@littlebearlabs.io","created_at":"2026-05-01T04:52:28Z","created_by":"James Lal","updated_at":"2026-05-01T05:09:55Z","started_at":"2026-05-01T05:04:27Z","closed_at":"2026-05-01T05:09:55Z","close_reason":"VsockListener::accept() now returns AcceptedVsockStream { stream, source: VmHandle } instead of bare VsockStream. VmHandle is a Copy/Eq/Hash/Send/Sync u64 derived from the source VZVirtioSocketDevice pointer that the framework delivers on every accept callback (was previously dropped as _device at vsock.rs:322). Verified: (1) 2 vsock_test unit tests confirm trait derivations; (2) workspace build clean; (3) runtime_e2e smoke_pull_and_run_alpine passed end-to-end. No external callers updated — only vz internal code consumes accept(). VmHandle is exposed at crate root via vz::VmHandle.","dependency_count":0,"dependent_count":0,"comment_count":0} |
3 | 4 | {"id":"vz-srr","title":"Per-VM unique MAC address (fix fixed-MAC bug)","description":"## Problem\nEvery VM created in a vz process gets the same hard-coded MAC `76:c4:f2:a0:00:01` (`crates/vz/src/bridge.rs:400-408`). The constant was introduced because `VZVirtioNetworkDeviceConfiguration::new()` randomizes the MAC on each construction, breaking save/restore (config mismatch → \"invalid argument\").\n\nThe fix-once-bake-in approach solved save/restore but introduced a multi-VM L2 collision: two NAT-networked VMs in the same process share a MAC. Dormant for single-VM consumers; lights up the moment a consumer (e.g., Virgil) spawns concurrent NAT VMs.\n\n## Source\n`planning/multi-vm-consumer-fixes.md` §3 (Virgil consumer feedback, April 2026).\n\n## Scope\n~10 LoC + tests. Cut over (no backwards compat per project rules).","design":"1. Add field `network_mac` (e.g., `Option\u003c[u8; 6]\u003e` or our own newtype) on `VmConfig`, populated lazily on first VM construction with `VZMACAddress::randomLocallyAdministratedAddress()`.\n2. Builder gets `.mac(addr)` for the deterministic case (e.g., consumers deriving from vm_id).\n3. Persist the MAC alongside the rest of the config so save/restore round-trips correctly.\n4. Remove the hardcoded `\"76:c4:f2:a0:00:01\"` literal in bridge.rs.\n5. Locally-administered (LAA, second-LSB of first octet set) — guaranteed not to collide with hardware OUIs.\n\nNo backwards-compat shim. Existing single-VM consumers that depended on the constant get a behavior change documented in CHANGELOG.","acceptance_criteria":"- Two `VmConfig` instances built via the same builder produce different MACs (unit test).\n- `VmConfig` with explicit `.mac()` round-trips through serde (unit test).\n- Two NAT-networked Linux guests boot concurrently, each gets a distinct MAC visible via `ip link show eth0`, both DHCP successfully against the host bridge (integration test).\n- `cargo clippy --workspace -- -D warnings` clean.\n- CHANGELOG entry noting the behavior change.","notes":"Recommended first per source doc — smallest, lowest risk, immediately lights up multi-VM scenarios.","status":"closed","priority":1,"issue_type":"bug","assignee":"James Lal","owner":"james@littlebearlabs.io","created_at":"2026-05-01T04:52:06Z","created_by":"James Lal","updated_at":"2026-05-01T05:04:20Z","started_at":"2026-05-01T04:53:07Z","closed_at":"2026-05-01T05:04:20Z","close_reason":"Random LAA MAC generated per VmConfig at build() time, persisted on the VmConfig so save/restore round-trips. Replaces the hardcoded 76:c4:f2:a0:00:01 literal. Verified: (1) 4 unit tests in config_test.rs (LAA bit set, distinct MACs across configs, explicit .mac() preserved, Clone shares MAC); (2) runtime_e2e smoke_pull_and_run_alpine passed end-to-end with the new MAC path; (3) stack_e2e complex_stack_snapshot_restore_rewinds_shared_vm_state passed end-to-end (save/restore correctness preserved).","dependency_count":0,"dependent_count":0,"comment_count":0} |
|
0 commit comments