28 KiB
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:
- Install Firecracker, a guest kernel, and a rootfs template.
- Allocate an ID, private IPv4 address, MAC address, TAP device, and VM runtime directory.
- Copy the rootfs template to a private writable VM disk and provision the requested existing guest account password.
- Configure and launch one Firecracker process.
- List, stop, SSH to, and destroy the VM.
- 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
startorrestartcommand 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:
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/kvmaccessible - 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
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
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
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
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
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 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:
/var/lib/uvm/
├── bin/
│ ├── firecracker
│ └── jailer
├── images/
│ ├── vmlinux
│ └── ubuntu.ext4
├── vms/
│ └── vm-<uuid>/
│ ├── 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 is the schema authority. The current state
version is 1.
{
"schema_version": 1,
"next_mac_index": 2,
"vms": {
"vm-<uuid>": {
"id": "vm-<uuid>",
"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.jsonwrites use a sibling temporary file,fsync, atomic replace, and parent-directoryfsync.state.jsonis mode0600; it must be treated as a credential registry and excluded from logs and bug reports.state.lockserializes short JSON read-modify-write transactions.operations.lockserializes 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; missingdiskandprocess_start_timefields have safe defaults. - Records created before credential provisioning default to username
rootand passwordnull; UVM does not apply a password retroactively. next_mac_indexis 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 follows this sequence:
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:
- Terminate a started Firecracker process when its identity is known.
- Remove the TAP when it was created.
- Remove the VM runtime directory and private disk.
- Remove the reserved state record if cleanup succeeds.
- Preserve a
failedrecord withlast_errorif cleanup itself fails.
Do not reorder these operations without considering leaked TAPs, orphaned VMM processes, and allocation reuse.
Stop and Destroy
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:
PUT /machine-configPUT /boot-sourcePUT /drives/rootfsPUT /network-interfaces/eth0PUT /actionswithInstanceStart
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/<pid>/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, and the requested account. UVM does not create missing
users. It replaces conventional RSA, ECDSA, and Ed25519 host keys in each
private disk and removes the known public login key from Firecracker's bionic
image. A VM must never rely on reusable SSH private keys from a shared template.
Integrity Model
integrity.py has two concepts:
- A configured trusted SHA-256 from the environment.
- A local
integrity.jsonmanifest recording artifact hashes and whether the installation was verified.
Strict installation requires all of these values:
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
guest eth0
|
virtio-net / Firecracker
|
per-VM TAP: uvm-<suffix>
|
Linux bridge: uvm0 (10.42.0.1/24 by default)
|
iptables forwarding + MASQUERADE
|
host default uplink
|
internet
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 intoCommandError.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
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:
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:
X-UVM-Token: <UVM_API_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:
- Only
LifecycleServicemay coordinate VM state, disks, TAPs, and Firecracker as one operation. - Only
StateStoremay read/writestate.json. - All long host mutations acquire
operations.lock. - All short inventory mutations acquire
state.lock. - The installer shares
operations.lockwith lifecycle actions, preventing a Firecracker binary replacement between verification and launch. - A VM rootfs is private before it becomes writable.
- A PID is not signaled unless its persisted start token still matches
/proc/<pid>/stat. - A new VM never adopts a pre-existing TAP.
- A server must have an API token; a remote server must also have usable TLS.
- Routers call application services; they do not run shell commands or touch state files directly.
- Plaintext guest passwords remain in the mode-
0600registry 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
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
--serveparsing. - 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:
- Strict checked installation.
createand successful guest boot.- Guest IP reachability and egress.
- SSH with unique guest host keys.
- Stop and retained disk inspection.
- Destroy and IP/TAP release.
- API launch with token and TLS on a non-loopback test address.
- Firecracker crash and interrupted create recovery.
18. Common Development Tasks
Add a CLI Command
- Put business logic in an existing service or a cohesive new internal
module, not
cli.py. - Add parser arguments in
build_parser(). - Add display/dispatch behavior in
_run_command(). - Use
UvmErrorsubclasses for expected failures. - Add unit tests for parsing and service behavior.
- Update
README.mdand this document if the architecture changes.
Add an HTTP Endpoint
- Add or extend a Pydantic request/response model in
api_models.py. - Put the endpoint in the appropriate
routers/module. - Use
get_authorized_application()for every privileged endpoint. - Keep
/healthas the only intentionally unauthenticated management route. FastAPI's default documentation endpoints are informational and public. - Add the router to
create_api(). - Add
httpx/FastAPI tests and update the API table in both docs.
Add a Host Primitive
- Put subprocess construction in
system.py,network.py,install.py, or a specific Firecracker adapter. - Use
CommandRunnerwhen invoking external commands. - Make repeated application safe where possible.
- Define compensation behavior before calling it from lifecycle code.
- Add fake-runner tests; do not require root in ordinary unit tests.
Change State
- Increment
STATE_SCHEMA_VERSIONonly when the serialized interpretation changes incompatibly. - Keep older state readable or provide an explicit migration path.
- Update
VmRecord,State, state tests, and runtime cleanup behavior. - 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/<pid>, 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:
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
sudo cat /var/lib/uvm/state.json # Contains plaintext guest passwords; do not share.
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.
iptablesrules 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:
- Run the complete unit suite.
- Run
git diff --check. - Confirm a new feature stays inside the
uvm/project directory. - Verify command/API errors use
UvmErrorrather than raw tracebacks. - Verify host mutations have a lock owner and cleanup path.
- Verify persistent state remains readable across upgrades.
- Update
README.mdfor operator-facing behavior. - Update this document for architectural, trust-boundary, or lifecycle changes.