# UVM Developer Guide This document is the developer handoff for the entire UVM codebase. Read it before changing lifecycle behavior, host networking, Firecracker launch semantics, persistent state, or the HTTP API. `uvm` is a local, privileged Firecracker microVM manager. It is an application with a CLI and optional FastAPI management server. It is not a general-purpose Python library or a multi-host control plane. ## 1. Goals and Boundaries UVM currently provides a small host-local workflow: 1. Install Firecracker, a guest kernel, and a rootfs template. 2. Allocate an ID, private IPv4 address, MAC address, TAP device, and VM runtime directory. 3. Copy the rootfs template to a private writable VM disk and provision the requested existing guest account password. 4. Configure and launch one Firecracker process. 5. List, stop, SSH to, and destroy the VM. 6. Optionally expose those operations through an authenticated FastAPI API. The design deliberately does not provide these capabilities yet: - Multi-host scheduling or remote state coordination. - User tenancy, quotas, or a public-facing control plane. - A `start` or `restart` command for stopped VMs. - Guest snapshots, extra disks, port forwarding, or DHCP. - Cgroup CPU enforcement for fractional CPU requests. - Jailer-based Firecracker execution, namespaces, or seccomp hardening. - Production-grade image building or guest SSH-key injection. Treat UVM as a privileged local systems tool. It manipulates `/dev/kvm`, TAP interfaces, bridge addresses, iptables rules, host filesystems, and processes. ## 2. First Run All UVM-specific files live in this directory. Start here: ```sh cd uvm python3 -m pip install -e '.[test]' PYTHONDONTWRITEBYTECODE=1 python3 -B -m unittest discover -s tests -v ./uvm.py --help ``` The `uvm` console script is registered by `pyproject.toml`. During source development, `./uvm.py` and `python3 -m uvm` are equivalent entry points. Requirements for real host operations: - Python 3.12+ - Linux with KVM enabled and `/dev/kvm` accessible - Root privileges for installation and lifecycle mutations - `iproute2`, `iptables`, `e2fsprogs`, `util-linux`, `openssl`, and OpenSSH client tools - An x86_64 host for the default kernel/rootfs URLs Do not run real integration operations on a developer workstation unless the bridge, subnet, iptables changes, and guest workload are acceptable there. ## 3. Project Layout ```text uvm/ ├── DEVELOPER.md # This document ├── README.md # Operator-facing usage guide ├── pyproject.toml # Package metadata and dependencies ├── uvm.py # Executable source-tree launcher ├── uvm/ │ ├── __main__.py # `python -m uvm` │ ├── cli.py # argparse, CLI rendering, serve dispatch │ ├── app.py # Composition root │ ├── config.py # Settings and environment loading │ ├── domain.py # VmSpec, VmRecord, State │ ├── errors.py # Expected error hierarchy │ ├── validation.py # CPU and RAM parsing │ ├── system.py # CommandRunner, root/KVM/directory checks │ ├── state.py # JSON state, atomic writes, file locks │ ├── integrity.py # SHA-256 and integrity manifest logic │ ├── install.py # Apt, Firecracker, kernel, rootfs installation │ ├── images.py # Template lookup, private disks, and guest credentials │ ├── network.py # IP/MAC/TAP/bridge/NAT/forward rules │ ├── lifecycle.py # Create/list/stop/destroy orchestration │ ├── api_models.py # Pydantic HTTP request/response models │ ├── server.py # FastAPI factory and Uvicorn launcher │ ├── routers/ │ │ ├── dependencies.py # API application/token dependencies │ │ ├── health.py # Liveness endpoint │ │ ├── installation.py # Installation endpoint │ │ └── vms.py # VM lifecycle endpoints │ └── firecracker/ │ ├── config.py # Firecracker JSON and boot arguments │ ├── api.py # Unix-socket HTTP client │ └── process.py # Process launch, liveness, termination └── tests/ # Unit tests and conditional API tests ``` Keep feature code in the most specific existing module. Do not add a new abstraction layer merely to wrap one function. The current module boundaries are intentionally small and operationally focused. ## 4. Architecture Overview ```mermaid flowchart LR CLI[CLI: uvm] --> APP[app.py composition root] HTTP[FastAPI server] --> APP APP --> INSTALL[Installer] APP --> LIFE[LifecycleService] APP --> STATE[StateStore] LIFE --> IMAGES[ImageStore] LIFE --> NET[NetworkManager] LIFE --> FC[Firecracker adapters] INSTALL --> RUNNER[CommandRunner] NET --> RUNNER FC --> HOST[Linux host primitives] RUNNER --> HOST STATE --> DISK[(state.json)] IMAGES --> DISKSTORE[/var/lib/uvm/images and vms] FC --> KVM[/dev/kvm] ``` The public entry points are thin adapters. The lifecycle layer is the only place that coordinates multiple host resources for a VM. Lower-level modules must not import `cli.py`, `server.py`, or `lifecycle.py`. ### Dependency Direction ```text cli.py / server.py / routers/ | v app.py | +-------+--------+ | | install.py lifecycle.py | +------------+------------+ | | | state.py network.py images.py | firecracker/* | | | +------ system.py --------+ | Linux host ``` Leaf modules are `config.py`, `domain.py`, `errors.py`, and `validation.py`. They must remain free of subprocess, network, CLI, and FastAPI side effects. ## 5. Entrypoints and Request Paths ### CLI Path ```mermaid sequenceDiagram participant U as User participant C as cli.py participant A as app.py participant L as LifecycleService participant H as Linux host U->>C: uvm create --cpu 1 --ram 512 C->>A: build_application() A->>L: composed service graph C->>L: create(VmSpec) L->>H: allocate files, TAP, process, Firecracker API L-->>C: VmRecord C-->>U: formatted VM details ``` `cli.py` owns parsing, terminal output, exit handling, SSH `execvp`, and the `--serve` switch. It must not manipulate state files, interfaces, or Firecracker sockets directly. ### HTTP Path ```mermaid sequenceDiagram participant Client participant Uvicorn participant API as server.py / routers participant L as LifecycleService participant H as Linux host Client->>Uvicorn: HTTP(S) request + X-UVM-Token Uvicorn->>API: route dispatch API->>API: token dependency and Pydantic validation API->>L: lifecycle operation L->>H: host mutation L-->>API: VmRecord or UvmError API-->>Client: JSON response or JSON error ``` Use only `uvm --serve` or `./uvm.py --serve` to launch the API. The `create_api()` factory requires an explicit host specifically to prevent it from being used as an insecure generic Uvicorn factory. ## 6. Application Composition [`app.py`](uvm/app.py) constructs one concrete object graph per CLI or server process: | Component | Constructor | Responsibility | |---|---|---| | Settings | `Settings.from_environment()` | Immutable runtime configuration | | CommandRunner | `CommandRunner` | Logged/testable subprocess execution | | StateStore | `StateStore` | Inventory, locks, JSON persistence | | ImageStore | `ImageStore` | Template verification, VM disks, and offline credentials | | NetworkManager | `NetworkManager` | Guest allocation and Linux networking | | FirecrackerProcessManager | `FirecrackerProcessManager` | VMM process control | | LifecycleService | `LifecycleService` | Ordered VM resource changes and rollback | | Installer | `Installer` | Host packages and artifacts | The FastAPI factory calls `build_application(emit=None)` so API-driven host commands do not print shell-style command traces to the server output. The CLI uses the default emitter and prints commands as `+ ...` for transparency. ## 7. Persistent Data and Models ### Host Data Layout Default runtime storage is `/var/lib/uvm`: ```text /var/lib/uvm/ ├── bin/ │ ├── firecracker │ └── jailer ├── images/ │ ├── vmlinux │ └── ubuntu.ext4 ├── vms/ │ └── vm-/ │ ├── rootfs.ext4 │ ├── config.json │ ├── firecracker.log │ └── firecracker.sock ├── state.json ├── state.lock ├── operations.lock └── integrity.json ``` Per-VM runtime directories are mode `0700`; copied VM disks are mode `0600`. `state.json` is mode `0600` because VM records contain plaintext guest passwords. Registry-backed commands therefore need access to the root-owned state file. ### State Schema [`domain.py`](uvm/domain.py) is the schema authority. The current state version is `1`. ```json { "schema_version": 1, "next_mac_index": 2, "vms": { "vm-": { "id": "vm-", "cpu": 1.0, "ram_mib": 512, "guest_ip": "10.42.0.2", "gateway": "10.42.0.1", "tap": "uvm-...", "mac": "02:fc:00:00:00:01", "socket": "/var/lib/uvm/vms/.../firecracker.sock", "config": "/var/lib/uvm/vms/.../config.json", "log": "/var/lib/uvm/vms/.../firecracker.log", "disk": "/var/lib/uvm/vms/.../rootfs.ext4", "username": "root", "password": "replace-me", "status": "running", "pid": 1234, "process_start_time": "...", "created_at": 0, "updated_at": 0, "last_error": null } } } ``` `VmSpec` is the validated create request. `VmRecord` is the persisted local record. Do not make API models or CLI namespaces the persistence format. ### State Guarantees - `state.json` writes use a sibling temporary file, `fsync`, atomic replace, and parent-directory `fsync`. - `state.json` is mode `0600`; it must be treated as a credential registry and excluded from logs and bug reports. - `state.lock` serializes short JSON read-modify-write transactions. - `operations.lock` serializes long lifecycle and installation operations. - State reads are lock-free because atomic replacement gives readers either a complete old file or a complete new file. - Legacy state without a schema version is accepted as version `0`; missing `disk` and `process_start_time` fields have safe defaults. - Records created before credential provisioning default to username `root` and password `null`; UVM does not apply a password retroactively. - `next_mac_index` is reconstructed from older locally administered MACs when absent. When adding a persisted field, update `VmRecord.to_dict`, `VmRecord.from_dict`, the state schema tests, and this document. Never silently discard existing state fields without a migration plan. ## 8. VM Lifecycle ### Stored and Observed Status The persisted status is a string, not an enum, to keep legacy JSON readable. | Status | Meaning | |---|---| | `starting` | Identity reserved; host resources are being created | | `running` | Firecracker configuration and `InstanceStart` succeeded | | `stopping` | Stop mutation is in progress | | `stopped` | Process/TAP removed; private disk remains | | `terminating` | Destruction is in progress | | `failed` | A lifecycle step or cleanup failed; `last_error` explains why | | `dead` | Observed-only list status when a supposedly active process is gone | `dead` is not persisted. `destroy` removes the record rather than retaining a tombstone. ### Create Order [`LifecycleService.create`](uvm/lifecycle.py) follows this sequence: ```mermaid flowchart TD A[Validate root, KVM, binary, integrity] --> B[Acquire operations.lock] B --> C[Reserve ID, IP, MAC, and starting state] C --> D[Create 0700 runtime directory] D --> E[Copy template to private rootfs.ext4] E --> F[Provision guest password and SSH policy] F --> G[Ensure bridge, NAT, and forwarding rules] G --> H[Create TAP] H --> I[Write Firecracker config atomically] I --> J[Spawn Firecracker and wait for API socket] J --> K[Persist PID and process start token] K --> L[Configure Firecracker through Unix HTTP] L --> M[Persist running state] ``` The record is persisted as `starting` before host side effects. The PID and Linux process-start token are persisted before Firecracker API configuration. This makes cleanup and later diagnosis possible if API configuration fails. ### Rollback Rules Any `Exception`, `KeyboardInterrupt`, or `SystemExit` during create triggers compensation: 1. Terminate a started Firecracker process when its identity is known. 2. Remove the TAP when it was created. 3. Remove the VM runtime directory and private disk. 4. Remove the reserved state record if cleanup succeeds. 5. Preserve a `failed` record with `last_error` if cleanup itself fails. Do not reorder these operations without considering leaked TAPs, orphaned VMM processes, and allocation reuse. ### Stop and Destroy ```text stop: running/failed/stopped -> stopping -> terminate process -> delete TAP -> stopped private disk and config remain destroy: any non-transition state -> terminating -> terminate process -> delete TAP -> remove VM runtime directory -> remove state record ``` `stop` is intentionally disk-preserving. There is no public `start` command yet, so do not add start behavior by modifying stop semantics. ## 9. Firecracker Integration The `firecracker/` package has three separate responsibilities. | File | Responsibility | |---|---| | `config.py` | Build boot args and Firecracker JSON; atomically write config | | `api.py` | Typed HTTP-over-Unix-socket requests | | `process.py` | Spawn, socket readiness, liveness, signal safety, cleanup | ### Configuration Order `FirecrackerClient.configure_and_start()` must keep this ordering: 1. `PUT /machine-config` 2. `PUT /boot-source` 3. `PUT /drives/rootfs` 4. `PUT /network-interfaces/eth0` 5. `PUT /actions` with `InstanceStart` The guest receives a static IP via the kernel `ip=` boot argument. The netmask comes from `Settings.network`, not a hard-coded `/24` value. ### Process Safety Firecracker starts detached in a new session, with stdout/stderr redirected to the per-VM log. The manager records `/proc//stat` start time and only signals a process when its persisted token still matches. This prevents a stale state record from killing an unrelated process after PID reuse. Legacy records with a PID but no start token are only cleaned up automatically when that PID is already gone. If it remains alive, UVM fails closed and asks the operator to inspect it. ## 10. Guest Images and Integrity ### Template Contract `images/ubuntu.ext4` is a template only. Each create operation copies it into the VM runtime directory before UVM modifies the requested existing account's shadow entry and OpenSSH password policy with `debugfs`. Firecracker only sees the resulting private copy. Never change that to a shared writable rootfs. Guest images must provide `/etc/passwd`, `/etc/shadow`, OpenSSH's `/etc/ssh/sshd_config`, the requested account, and their own first-boot SSH host-key generation. UVM does not create missing users. The known public demo key in Firecracker's bionic image is removed from private VM disks. A shared template must never contain reusable SSH host private keys. ### Integrity Model [`integrity.py`](uvm/integrity.py) has two concepts: - A configured trusted SHA-256 from the environment. - A local `integrity.json` manifest recording artifact hashes and whether the installation was verified. Strict installation requires all of these values: ```text UVM_FIRECRACKER_SHA256 UVM_KERNEL_SHA256 UVM_ROOTFS_SHA256 ``` `UVM_ALLOW_UNVERIFIED_DOWNLOADS=1` is a local-development escape hatch. It writes a manifest marked `verified: false`; a later strict run will not treat that manifest as publisher trust. Use `install --force` with trusted digests to refresh an existing artifact without manually deleting managed files. The optional `UVM_FIRECRACKER_BINARY_SHA256` validates a local Firecracker binary when no verified manifest is available. ## 11. Networking ### Topology ```text guest eth0 | virtio-net / Firecracker | per-VM TAP: uvm- | Linux bridge: uvm0 (10.42.0.1/24 by default) | iptables forwarding + MASQUERADE | host default uplink | internet ``` [`network.py`](uvm/network.py) owns all networking details: - IP allocation from `Settings.network`, excluding network, broadcast, and gateway addresses. - Locally administered MAC generation: `02:fc:00:xx:xx:xx`. - Interface names limited to Linux `IFNAMSIZ` (15 characters). - Bridge creation or validation that an existing interface is actually a Linux bridge. - Gateway address setup and IPv4 forwarding. - One egress NAT rule and forward/return rules for the default uplink. - TAP creation, cleanup, and error reporting. Network actions are idempotent where practical. Do not adopt an existing TAP: it may belong to another VM. A name collision is an error. ## 12. Installer and Host Commands `Installer` is the only owner of apt installation, Firecracker artifact handling, downloads, and integrity-manifest publication. `system.py` centralizes host command execution: - `CommandRunner.run()` converts subprocess failures into `CommandError`. - `require_root()` is called by host-mutating operations. - `check_kvm()` validates the KVM device. - `ensure_data_directories()` creates the UVM storage layout. Use `CommandRunner` rather than direct `subprocess.run` in installation or network code. This keeps command construction observable in the CLI and mockable in tests. Downloads use temporary sibling files, optional checksum verification, and atomic replacement. Archive extraction rejects path traversal before using Python's safe tar extraction filter. ## 13. HTTP API ### Server Startup ```sh cd uvm python3 -m pip install . export UVM_API_TOKEN='long-random-visible-ascii-token' sudo env UVM_API_TOKEN="$UVM_API_TOKEN" \ ./uvm.py --serve --host 127.0.0.1 --port 8000 ``` For non-loopback binds, both TLS variables are required and loaded into an `ssl.SSLContext` before Uvicorn starts: ```sh export UVM_API_TLS_CERT='/etc/uvm/api-cert.pem' export UVM_API_TLS_KEY='/etc/uvm/api-key.pem' sudo env \ UVM_API_TOKEN="$UVM_API_TOKEN" \ UVM_API_TLS_CERT="$UVM_API_TLS_CERT" \ UVM_API_TLS_KEY="$UVM_API_TLS_KEY" \ ./uvm.py --serve --host 0.0.0.0 --port 8443 ``` The token must contain visible ASCII characters without whitespace. It is never accepted as a UVM command-line argument, so it does not appear in UVM process arguments. If you type an `export` command interactively, your shell may record it in history; use a protected environment file, service manager, or secret manager when that matters. ### API Contract | Method | Route | Router | Notes | |---|---|---|---| | `GET` | `/health` | `health.py` | Unauthenticated liveness response | | `POST` | `/install` | `installation.py` | `{ "force": false }` body | | `GET` | `/vms` | `vms.py` | List persisted VM records and observed status | | `POST` | `/vms` | `vms.py` | `cpu`, `ram`, optional `guest_ip`, `username`, and `password` | | `GET` | `/vms/{id-or-ip}` | `vms.py` | Lookup by ID or guest IP | | `POST` | `/vms/{id}/stop` | `vms.py` | Stop while retaining private disk | | `DELETE` | `/vms/{id}` | `vms.py` | Remove runtime files and state | All management routes use `get_authorized_application()` and require: ```http X-UVM-Token: ``` Request schemas reject unexpected fields. `UvmError` instances are rendered as JSON errors by `server.py`; Pydantic request validation uses FastAPI's normal 422 response format. VM responses include the username but never the password. The API intentionally does not expose interactive SSH, raw Firecracker calls, arbitrary command execution, or host path access. ## 14. Configuration Reference | Variable | Default | Used by | |---|---|---| | `UVM_BASE` | `/var/lib/uvm` | Storage layout | | `UVM_NETWORK` | `10.42.0.0/24` | IP allocation and bridge subnet | | `UVM_GATEWAY` | `10.42.0.1` | Bridge and guest boot args | | `UVM_BRIDGE` | `uvm0` | Linux bridge name | | `UVM_KERNEL_URL` | Firecracker quickstart URL | Guest kernel download | | `UVM_ROOTFS_URL` | Firecracker quickstart URL | Guest rootfs download | | `UVM_FIRECRACKER_SHA256` | unset | Trusted release archive checksum | | `UVM_KERNEL_SHA256` | unset | Trusted kernel checksum | | `UVM_ROOTFS_SHA256` | unset | Trusted rootfs checksum | | `UVM_FIRECRACKER_BINARY_SHA256` | unset | Optional direct binary checksum | | `UVM_ALLOW_UNVERIFIED_DOWNLOADS` | `false` | Development-only integrity bypass | | `UVM_API_TOKEN` | unset | Required API bearer token | | `UVM_API_TLS_CERT` | unset | TLS certificate for remote API bind | | `UVM_API_TLS_KEY` | unset | TLS key for remote API bind | `Settings` validates the gateway, bridge-name length, API token format, and environment boolean values at process startup. Add configuration fields there, not as module-level global constants in feature code. ## 15. Error Handling Rules Expected failures inherit from `UvmError`: | Error | Use | |---|---| | `ConfigurationError` | Invalid local settings or environment | | `ValidationError` | Invalid CLI/API resource input | | `StateError` | Unsafe or malformed persisted state | | `CommandError` | Required host command failure | | `FirecrackerError` | VMM process or Unix API failure | | `TapCreationError` | TAP setup failed after interface creation | CLI code catches `UvmError` and prints `uvm: error: ...` without a traceback. The FastAPI server maps the same class to an error envelope. New expected operational failures should use this hierarchy rather than leaking raw `OSError`, `CalledProcessError`, or implementation-specific exceptions. ## 16. Concurrency and Ownership Invariants These invariants are critical: 1. Only `LifecycleService` may coordinate VM state, disks, TAPs, and Firecracker as one operation. 2. Only `StateStore` may read/write `state.json`. 3. All long host mutations acquire `operations.lock`. 4. All short inventory mutations acquire `state.lock`. 5. The installer shares `operations.lock` with lifecycle actions, preventing a Firecracker binary replacement between verification and launch. 6. A VM rootfs is private before it becomes writable. 7. A PID is not signaled unless its persisted start token still matches `/proc//stat`. 8. A new VM never adopts a pre-existing TAP. 9. A server must have an API token; a remote server must also have usable TLS. 10. Routers call application services; they do not run shell commands or touch state files directly. 11. Plaintext guest passwords remain in the mode-`0600` registry and never enter Firecracker config, helper command arguments/traces, or API responses. When a change would violate one of these rules, redesign the change rather than adding a special-case bypass. ## 17. Testing Strategy ### Standard Unit Suite ```sh cd uvm python3 -m pip install -e '.[test]' PYTHONDONTWRITEBYTECODE=1 python3 -B -m unittest discover -s tests -v ``` The suite covers: - CPU/RAM parsing and validation. - State serialization, legacy migration, locking, and permissions. - Checksum verification and integrity-manifest provenance. - Private rootfs behavior. - Guest credential persistence, validation, and offline rootfs provisioning. - IP, MAC, bridge, TAP, NAT, and forwarding command construction. - Firecracker configuration and API request ordering. - PID safety and startup interrupt cleanup. - Lifecycle success, rollback, stop, and destroy behavior. - CLI SSH safety and `--serve` parsing. - Server token/TLS validation and conditional API route tests. FastAPI route tests are conditionally enabled when both `fastapi` and `httpx` are installed. They should be enabled in CI using the `test` extra. ### Real Integration Tests No current test automatically boots a real microVM. Before a release, run an isolated host test that covers: 1. Strict checked installation. 2. `create` and successful guest boot. 3. Guest IP reachability and egress. 4. SSH with unique guest host keys. 5. Stop and retained disk inspection. 6. Destroy and IP/TAP release. 7. API launch with token and TLS on a non-loopback test address. 8. Firecracker crash and interrupted create recovery. ## 18. Common Development Tasks ### Add a CLI Command 1. Put business logic in an existing service or a cohesive new internal module, not `cli.py`. 2. Add parser arguments in `build_parser()`. 3. Add display/dispatch behavior in `_run_command()`. 4. Use `UvmError` subclasses for expected failures. 5. Add unit tests for parsing and service behavior. 6. Update `README.md` and this document if the architecture changes. ### Add an HTTP Endpoint 1. Add or extend a Pydantic request/response model in `api_models.py`. 2. Put the endpoint in the appropriate `routers/` module. 3. Use `get_authorized_application()` for every privileged endpoint. 4. Keep `/health` as the only intentionally unauthenticated management route. FastAPI's default documentation endpoints are informational and public. 5. Add the router to `create_api()`. 6. Add `httpx`/FastAPI tests and update the API table in both docs. ### Add a Host Primitive 1. Put subprocess construction in `system.py`, `network.py`, `install.py`, or a specific Firecracker adapter. 2. Use `CommandRunner` when invoking external commands. 3. Make repeated application safe where possible. 4. Define compensation behavior before calling it from lifecycle code. 5. Add fake-runner tests; do not require root in ordinary unit tests. ### Change State 1. Increment `STATE_SCHEMA_VERSION` only when the serialized interpretation changes incompatibly. 2. Keep older state readable or provide an explicit migration path. 3. Update `VmRecord`, `State`, state tests, and runtime cleanup behavior. 4. Consider lock ownership and crash/retry behavior before changing fields. ## 19. Debugging Guide | Symptom | First places to inspect | |---|---| | `guest assets missing` | `/var/lib/uvm/images`, integrity variables, `integrity.json` | | Firecracker exits early | VM `firecracker.log`, VM config, `/dev/kvm` access | | VM is `dead` in `list` | PID/start token in state, `/proc/`, VM log | | Cannot stop legacy VM | Missing process start token; inspect PID manually before cleanup | | Guest has no egress | `ip route show default`, bridge address, TAP state, iptables rules | | API server refuses to start | `UVM_API_TOKEN`, requested host, TLS cert/key files | | API returns 401 | `X-UVM-Token` header mismatch | | API returns 422 | Request schema or UVM input validation failure | Useful host commands during isolated debugging: ```sh ip link show uvm0 ip addr show dev uvm0 ip route show default iptables -t nat -S POSTROUTING iptables -S FORWARD ls -la /var/lib/uvm/vms cat /var/lib/uvm/state.json ``` Do not use these commands to mutate UVM resources manually while a lifecycle operation is active. Prefer fixing the service behavior and retrying through the CLI or API. ## 20. Current Production Gaps The code is intentionally honest about current limits: - Firecracker is launched directly, even though the Jailer binary is downloaded. - No cgroup CPU, memory, I/O, or process limits are applied. - Fractional CPU is represented by rounding vCPUs up and printing an advisory. - Guest health is inferred from VMM process state, not application readiness. - There is no DHCP, port mapping, ingress firewall policy, or network tenancy. - `iptables` rules are incrementally managed rather than rendered from a full declarative firewall policy. - The JSON inventory is suitable for one host, not distributed coordination. - Guest passwords are stored in plaintext in the root-only local registry. - API authentication is a single bearer token, not a user/tenant model. Do not market UVM as a hardened multi-tenant platform until these boundaries are deliberately addressed with architecture, tests, and operational review. ## 21. Contributor Checklist Before submitting a change: 1. Run the complete unit suite. 2. Run `git diff --check`. 3. Confirm a new feature stays inside the `uvm/` project directory. 4. Verify command/API errors use `UvmError` rather than raw tracebacks. 5. Verify host mutations have a lock owner and cleanup path. 6. Verify persistent state remains readable across upgrades. 7. Update `README.md` for operator-facing behavior. 8. Update this document for architectural, trust-boundary, or lifecycle changes.