Skip to content

Workload IR

The Workload IR is the single contract between language SDKs and the host toolchain. Everything above it (Python decorators, TypeScript higher-order calls) generates IR; everything below it (compile, up, the substrate) consumes IR. Cross-language byte-identity is enforced at the IR layer.

ADR-0002 makes this commitment binding: SDKs may not emit Nix or mvm artifacts directly, only IR.

Schema source of truth

The canonical Rust types live in crates/mvmforge-ir/src/workload.rs. The JSON Schema at schema/workload-ir-v0.json is generated from those types via schemars and committed alongside them. Per-language SDK types are then generated from that schema.

You should never hand-author IR types or serialized IR. The toolchain maintains drift checks at every layer.

v0 field set (schema version 0.1)

The IR document is canonical JSON (RFC 8785). Top-level shape:

{
"schema_version": "0.1",
"id": "<workload-id>",
"apps": [...],
"volumes": [],
"extensions": {}
}

Workload

FieldTypeNotes
schema_version"0.1"Host rejects unsupported MAJOR; rejects MINOR greater than its known minor.
idstring [a-z0-9-]{1,64}Workload name. Used as mkGuest’s name and hostname.
appsApp[]v0: exactly one entry.
volumesVolume[]Optional; default [].
extensions{ [substrate]: object }Reserved partition for substrate-specific fields. v0 has no registered extensions.

App

FieldTypeNotes
namestringUnique within the workload.
sourceSourceHow user code enters the VM. Required.
imageImageRuntime environment.
entrypointEntrypointcommand, working_dir, env.
env{ [k]: EnvValue }Optional. App-level env vars; merged under entrypoint env.
mountsMount[]Optional. Volume / host-path / tmpfs.
networkNetwork | nullOptional. mode: "none" | "bridge" plus ports.
resourcesResourcescpu_cores, memory_mb, rootfs_size_mb.

Source

Tagged union, exactly one variant per app:

{ "kind": "local_path", "path": "src", "include": ["**"], "exclude": ["target/**"] }

v0 implements only local_path. The reserved variants nix_derivation and oci_image are accepted by serde but rejected by host validation with E_SOURCE_KIND_DEFERRED.

Image

{ "kind": "nix_packages", "packages": ["python312", "curl"] }

packages is a list of nixpkgs attribute names. v0 implements only nix_packages.

Entrypoint

{
"command": ["python", "-m", "hello"],
"working_dir": "/app",
"env": {}
}

command is parsed argv; no shell interpolation. working_dir defaults to /app. The bundled source is symlinked to working_dir at boot (see Source bundling).

EnvValue

{ "kind": "literal", "value": "production" }

or

{ "kind": "secret_ref", "ref": { "name": "api-token", "mount": { "kind": "env", "var": "TOKEN" } } }

v0 host rejects any secret_ref with E_SECRETS_NOT_IMPLEMENTED. Secrets resolution awaits a dedicated ADR.

Other types

Mount, Network, PortForward, Resources, Volume, SecretRef, SecretMount follow the same tagged-union pattern. Full normative reference lives in specs/plans/0002-ir-and-codegen-tracer.md Appendix A.

Validation

mvmforge validate <manifest.json> runs every v0 rule and accumulates errors (no fail-fast):

  • Schema version gating (E_UNSUPPORTED_MAJOR, E_MINOR_TOO_HIGH, E_MALFORMED_VERSION).
  • Closed-world field check (E_UNKNOWN_FIELD).
  • App count (E_EMPTY_APPS, E_MULTI_APP_DEFERRED).
  • Reserved source kinds (E_SOURCE_KIND_DEFERRED).
  • Secret-ref presence (E_SECRETS_NOT_IMPLEMENTED).
  • Persistent volumes (E_PERSIST_DEFERRED).
  • Network sanity (E_NETWORK_PORTS_WITH_NONE).

The full registry lives at Error codes.

Versioning

schema_version is a MAJOR.MINOR string. Per ADR-0002:

  • MINOR bump: additive — new optional fields, new variants in open sum types.
  • MAJOR bump: breaking — removing fields, renaming, changing meaning. Requires a superseding ADR.
  • The host rejects MAJOR it doesn’t know.
  • Within a known MAJOR, the host rejects MINOR higher than its own known minor (fail-closed).
  • Unknown fields within a supported MAJOR.MINOR are rejected (closed-world).

Why “canonical”

Two semantically equivalent IR documents must produce byte-identical output after mvmforge canonicalize. This is the test the golden corpus uses to enforce cross-SDK conformance: Python and TypeScript SDKs declaring the same workload must produce the exact same bytes.

See Canonicalization for the algorithm.