Skip to content

This is the multi-page printable view of this section. .

Return to the regular view of this page.

Reference

Exact contracts for the Pigsty-compatible Inventory and Barn command line.

This reference describes the Barn 0.9.0 release candidate. Check barn version before scripting against these contracts. See the release status.

  • Configuration — discovery, accepted variables, defaults, disks, shares, naming, and drift.
  • CLI — commands, important flags, output modes, and exit codes.
  • Mac Commands — the unreleased barn mac: commands, JSON results, and failure reasons.
  • Images — signed catalogs, aliases, cache layout, pulls, imports, and pruning.
  • Image Pipeline — candidate validation and offline normalization.

Barn exposes no supported Go library API. Packages under internal/ are implementation details.

1 - Configuration

The Pigsty-compatible Inventory fields Barn reads, their defaults, and node-level drift behavior.

This reference describes the Barn 0.9.0 release candidate. Check barn version before scripting against these contracts. See the release status.

Discovery

Configuration lookup order is explicit -f, then barn.yml, barn.yaml, pigsty.yml, and pigsty.yaml in the current directory. Every name uses the same Pigsty-compatible YAML Inventory format.

For plan, up, reload, and recreate, absence of a file falls back to the applied spec when a deployment exists. validate has no fallback. A configuration must be a regular non-symlink file no larger than 4 MiB. The first existing discovery candidate wins; if it is invalid, Barn reports the error instead of trying the next filename. Renaming a file or moving directories does not create another deployment; applied state lives in BARN_HOME.

A complete inventory

all:
  vars:
    admin_ip: 10.10.10.10
    vm_image: u24
    vm_cpu: 2
    vm_mem: 4GiB
    vm_disk: 64
  children:
    lab:
      hosts:
        10.10.10.10: { nodename: meta }
        10.10.10.11:
          nodename: worker
          vm_mem: 8GiB
          vm_disks:
            - { path: /data, size: 128, fs: auto, persistent: true }

This creates two managed definitions; the control node is meta. Save it as barn.yml, then inspect it without starting VMs:

barn validate -f barn.yml
barn --json validate -f barn.yml
barn plan -f barn.yml

The JSON validation result includes valid, source, spec_hash, and resolved. In the 0.9 candidate, validate also resolves catalog image references and accepts --repo; an unreadable catalog adds a warning instead of a false success claim about images, and local-* image-byte checks remain the responsibility of up. Validation does not prove host resources, share access, networking, image bytes, or guest readiness. It never starts VMs or downloads images.

What Barn reads

Barn reads host IPs, nodename, admin_ip, pg_cluster, pg_seq, node_admin_username, node_admin_uid, and the documented vm_* variables. admin_ip is read from all.vars; it selects the control node, or the first managed host is used. All nodes must resolve the same login username. For the default dba user, an explicit node_admin_uid must be 88. For a custom user, node_admin_uid is validated as an integer but does not set the guest UID; Barn does not expose a general UID customization contract.

Everything else is opaque and cannot create drift. This means unconsumed fields such as pg_role, pg_version, repo_*, and node_packages, not all possible pg_* or node_* names.

Inside this namespace validation is strict: unknown vm_* names, wrong types, Jinja expressions, invalid addresses, and conflicting sibling-group values are errors. Inheritance is all.vars → deeper children.<group>.vars → host variables. Host values replace an entire list such as vm_disks; lists are not appended. Different values inherited from groups at the same depth must be resolved with a host-level override. YAML anchors and merge keys are supported; explicit keys win, and the first mapping in a merge sequence wins. Duplicate mapping keys and multiple YAML documents are errors.

Host keys must be IPv4 addresses even for vm_skip: true entries. Skipped hosts do not consume the 20-node budget or determine the managed subnet, but the inventory must still contain at least one managed host. Skipping an already applied node marks it as removed; it does not destroy that VM.

The 0.9 candidate improves errors with the offending rule, value, and line where available, and suggests a nearby vm_* spelling. Those diagnostics do not add new inventory variables.

VM variables

Variable Default Meaning
vm_skip false do not virtualize this real/external host
vm_image u24 image family, channel reference, or image@version selector
vm_version unset newest numeric version matching this prefix, such as 9 or 9.7
vm_arch native deployment-wide Guest architecture: native, amd64, or arm64
vm_cpu 2 vCPU count
vm_mem 4096 MiB integer, or a size such as 8GiB
vm_disk 64 root disk: GiB integer or an explicit size such as 64GiB
vm_disks [{path: /data}] extra disks (one 128 GiB non-persistent disk at /data by default)
vm_alias [] guest /etc/hosts, SSH-config, and optional host aliases
vm_shares [] QEMU 9p host-directory shares

An empty host entry is a complete VM. A deployment contains 1–20 managed hosts; vm_cpu accepts 1–256 and memory must be at least 512 MiB. Bare integer memory is MiB; bare integer disk sizes are GiB. Explicit size strings accept positive integers plus B, KiB, MiB, GiB, TiB, KB, MB, GB, or TB (case-sensitive). 8GiB is valid; 8G, 1.5GiB, and the quoted unitless string "8192" are not. Root/data disk sizes must be positive; up also checks the root disk against the selected base image’s virtual size.

Omitting vm_image selects Ubuntu 24.04. To use Debian 13, set vm_image: d13 under all.vars. Inspect barn plan before applying changes to an existing Barn inventory.

vm_version keeps short version intent separate from the image family:

vm_image: el9
vm_version: 9.7

An exact Catalog version wins first. Otherwise Barn matches only on a dot component boundary and selects the numerically newest match: 9.7 resolves to the newest 9.7.* build, while 9 resolves to the newest 9.x release. Numeric components are compared as integers, so 9.10 sorts after 9.9. Do not combine vm_version with a vm_image that already contains :channel or @version.

vm_arch is stricter than ordinary per-host VM fields: when present it must resolve to one value on every managed host, so define it once in all.vars. Changing it is a deployment-envelope change and requires whole-deployment recreation. Linux setup installs only the native emulator; a foreign architecture also needs its matching qemu-system-* binary and firmware.

Data disks

vm_disks:
  - path: /data
    size: 128
    fs: auto
    persistent: false

path is the disk identity and mount point. fs is auto (the default), xfs, or ext4. A blank auto disk is formatted XFS when the guest has mkfs.xfs and ext4 otherwise, which matches what the Vagrant flow did. Explicit xfs and ext4 never fall back. Healthy existing filesystems are reused. persistent: true keeps the disk across an ordinary destroy; vm_disks: [] means no extra disk. size defaults to 128 GiB for each entry; integer sizes are GiB and explicit size strings are accepted.

Use clean absolute mount paths such as /data or /data/pg. The derived disk identity trims leading/trailing / and replaces inner / separators with - and must match [a-z][a-z0-9-]{0,31}; identities and mount paths must be unique within the node. /, system paths such as /etc, /usr, /root, and /var/lib/barn, and their overlapping parents/children are refused. Changing a persistent disk’s identity or declaration can require explicit migration; persistent does not mean every new definition can automatically reuse the old disk.

Data disks are disposable test storage. up resets unrecognized or confirmed damaged filesystems to the configured type and reports that old data was discarded. This includes persistent disks: persistence controls destroy/recreate, not retention of corrupt contents. Probe errors, missing devices, busy mounts, and backend I/O failures do not authorize formatting. Root disks and host shares are outside this recovery path.

Shares

macOS limitation: Barn’s guarded directory sharing is not supported on macOS; a node with vm_shares cannot start. The candidate also warns during validate and plan. Omit shares in new macOS labs; full macOS sharing support remains pending. Changing an existing node’s shares requires recreate, which replaces the root disk. Preserve needed data before considering that operation. Barn does not fall back to unchecked host paths.

vm_shares:
  - host: /absolute/owned/source
    guest: /src
    readonly: true

readonly defaults to true; each node accepts at most eight shares. Host and guest paths must be clean absolute paths; ~ and relative paths are not expanded. Host directories must already exist, be caller-owned, have no symlink path components, and not overlap BARN_HOME. On Linux, prefer the real path (realpath /path/to/source). The 0.9 candidate includes that replacement path in a symlink diagnostic.

Within one node, host sources and guest targets must not overlap. Across nodes, overlapping host sources are allowed only when all are read-only. Guest targets must not overlap data-disk mounts, reserved system paths, or the login user’s .ssh directory. Shares are for trusted development files, not PostgreSQL data. If a requested writable share cannot support guest writes, Barn tries read-only access and reports the limitation. Correct permissions and repeat up to retry; Barn does not recursively change the ownership of host files.

A missing source fails only its node during up or start. Other selected nodes continue. Restore the original directory or its host mount and retry that node; Barn never creates an empty replacement. Restart, reload and recreate validate sources before stopping existing nodes.

Names and addresses

Node name order: nodename, then <pg_cluster>-<pg_seq>, then node-<last-octet>. Names must be unique, 1–63 lowercase letters/digits/hyphens, and may not begin or end with -. An explicit non-empty nodename takes precedence, so unrelated pg_cluster or pg_seq values need not derive a name. vm_alias is a list of lowercase DNS-style names; aliases must not duplicate a node name or any other alias in the deployment.

All managed hosts must be in one RFC1918 /24: .1 is the host, .2–.8 are reserved, and nodes use .9–.254.

Inside the guest the fixed-IP interface is the one carrying the inventory address (ip -br addr); its name is not a Barn contract.

Drift

Barn hashes each resolved node. Added hosts are created by up; selected existing stopped nodes are started; running peers keep their processes while unfinished guest setup is retried. Changed VM definitions require per-node recreate; removed hosts are reported but never destroyed. Deployment architecture, user, or subnet changes require whole-deployment recreation. Image selectors are resolved to exact image identities by plan/up; review the plan after a catalog update, even if the inventory text is unchanged. Changing a field used to derive a node name appears as a missing old node plus a new node, so prefer stable explicit nodename values.

2 - CLI

Barn commands, important flags, structured output, and exit codes.

This reference describes the Barn 0.9.0 release candidate. Check barn version before scripting against these contracts. See the release status.

barn [--json|--yaml] [-v|--verbose] <command> [flags] [node...]

The installed binary is the authoritative reference for its own version. Every visible command includes its operational boundary and copyable examples:

barn --help
barn setup --help
barn image pull --help

Bare barn prints a short welcome with next commands, exits 0, and exposes actions[] in JSON/YAML. A bare namespace such as barn image still exits 2 (human help in text mode, a structured usage error in JSON/YAML). Explicit --help always renders human help and exits 0. Use barn --version or barn version to inspect the build identity.

Commands

Area Commands
Prepare setup, init, validate, doctor
Lifecycle plan, up, start, stop, restart, reload, recreate, status, destroy, purge
Access ssh, exec, logs, provision, ssh-config, hosts install/uninstall
Images update, image list/info/pull/import/sync/prune/reset, repo scan/build/verify
Host network network status/install/uninstall
macOS guests (unreleased) mac …; see Mac Commands
Misc version, completion

No command refreshes the Catalog implicitly. update fetches the configured repository’s Catalog, verifies it, and activates it; image sync is the explicit recovery path for an exact URL or file. Ordinary commands use the active local Catalog. Neither operation updates the Barn executable.

Frequently used commands have scoped aliases:

Command Aliases Command Aliases
setup s validate v
plan pl recreate rc
status st destroy de
ssh-config sc image images, im
doctor dt network n, net
exec / logs ex / l version ver

up, ssh, init, start, stop, restart, reload, provision, hosts, and completion have no aliases. Use the explicit barn purge spelling; rm is not a command alias. Inside a namespace, hosts and network use i/u for install/uninstall; network status uses st; and image maps list=ls, info=in, pull=p, prune=pr, sync=sy, and import=i.

Barn manages one deployment in the selected BARN_HOME (default ~/.barn), independently of the inventory directory. Commands using applied state work from any directory. Configuration selection is command-scoped; -f is deliberately not a global flag:

Commands Desired-state source
setup [template] explicit -f, otherwise discovery, otherwise generate meta; template and -f are exclusive
init [template] generate a new inventory; read no desired state; --force explicitly replaces the output
validate explicit -f, then discovery; never applied state
plan, up, reload, recreate explicit -f, then discovery, then the applied resolved specification
other lifecycle/access commands no desired-state inventory; they use applied state

When neither an inventory nor an applied deployment exists, interactive up can generate the default inventory. It can also prepare missing host dependencies and restore an intact inactive Barn network. This implicit preparation accepts the setup plan; sudo may still request credentials. Use setup --dry-run to review the host plan first. Scripts should run setup --yes explicitly; init is optional when no inventory exists.

Important flags

Flag Meaning
--json, --yaml machine-readable stdout; progress remains on stderr; intentionally no shorthand
-v, --verbose bounded diagnostics on stderr
-c, --cidr select the RFC1918 /24 for generated init/setup templates or host-network inspection/install
-f, --file select an Inventory for commands that read desired state
-r, --repo select a repository where exposed; overrides --mirror and BARN_REPO; validate gains this flag in the 0.9 candidate
--mirror use the China official repository for setup, Catalog, and image-resolving lifecycle commands
-m, --mode select host or shared where the command exposes the macOS network mode
-d, --dry-run show a setup/image plan without changing state
-y, --yes apply a displayed host/setup/image plan
--force (init, destroy, recreate) overwrite generated output or skip the typed confirmation; long-only because -f selects the Inventory
-n, --no-wait return once QEMU is running, without readiness or guest recovery checks
--rollback (up, reload) remove the prepare artifacts of nodes that failed to prepare in this run
--delete-persistent during whole destroy, also delete retained data disks; invalid with node selectors
--purge whole-deployment disposal: delete disks, keys, and deployment state; keep images

Flags belong to commands; the following table lists the less obvious scopes:

Command Local controls
init --output/-o (default ./barn.yml; - prints), --cidr/-c, --force
plan --file/-f, --repo/-r; no --mirror or --dry-run
start, restart --no-wait/-n; no --file or repository selection
provision required --script/-s; --sudo uses guest sudo -n; --parallel/-p is 1–4 (default 1); --timeout/-t is positive, at most 24h (default 1h)
ssh-config --install/-i or --remove (exclusive); --name defaults to barn; removal accepts no nodes and requires no deployment
logs --source/-s serial|qemu|events (default serial); --follow/-f; events accept no node
image info, image pull optional image selector, --arch/-a amd64|arm64, --repo/-r; only pull accepts --mirror
image import --sha256/-s; --name local-* also requires --boot/-b bios|uefi and --source-user/-u
image prune --dry-run/-d or --yes/-y (exclusive); --repo/-r
image sync URL or path, --repo/-r, and explicit --allow-downgrade
network install --cidr/-c (default 10.10.10.0/24), --mode/-m (default host), --yes/-y; macOS-only --archive/-a, --interface-id/-i
network status optional --cidr/-c; no --file
network uninstall, hosts install/uninstall --yes/-y

setup --dry-run and setup --yes are mutually exclusive. --cidr rebases a generated template; it does not rewrite an explicitly selected inventory. validate --repo is available in the 0.9 candidate and has no --mirror; use --repo https://repo.pigsty.cc/barn when checking that catalog.

In the 0.9 candidate, network and hosts install/uninstall show a plan and ask on a terminal (install defaults to yes, uninstall to no); without a terminal they only show the plan unless --yes is supplied. For a fresh macOS network, use setup; the candidate’s network install directs you there before asking for sudo. --yes accepts Barn’s plan but cannot supply a sudo password.

Rare, selection, or safety-widening controls such as --mirror, --force, --rollback, --remove, --allow-downgrade, --sudo, --delete-persistent, and --purge are long-only. On commands that read an Inventory, -f always selects a file; logs -f retains the conventional --follow. -n always means --no-wait, and -d always means a dry run.

With a deployment, barn purge performs the same disposal as barn destroy --force --purge, without confirmation. It accepts no nodes or Inventory, removes the complete deployment plus persistent disks, keys, state, and the default SSH fragment, and keeps images and the host network. With no deployment it succeeds without changing the image cache, and can remove provably owned retained disks. Missing state never authorizes deletion of residual node artifacts whose identity cannot be proven. In the 0.9 candidate, plain whole-deployment destroy without a deployment also succeeds; destroy --force --delete-persistent and destroy --force --purge without state instead fail with a hint to use purge.

Structured failures (0.9 candidate)

The following unified failure contract describes the Barn 0.9.0 release candidate.

Ordinary failures print error: <message> on stderr, the failing program’s last stderr lines when an external tool failed, and a next: line when there is one clear action. SSH child exit failures are silent because the child has its own output. If a failing command supplies no richer typed result, structured mode writes a generic failure object before returning the exit code: error (the class below), message, and where they apply a stable reason, next, operation_id, and command (name, argv, exit_status, signal, timed_out, stderr) for a failed external program. Existing typed failure results are never followed by a second JSON/YAML document. The closed generic error classes are listed below. recreate_required and nodes_removed are now reason values under error: "conflict", and the old resource_conflict class is now resource.

A nonzero exit does not guarantee that stdout has this generic envelope. doctor, network, provision, lifecycle operations, and remote commands can return their own report schemas. SSH child exits are internally classified as remote_exit, but their public result has fields such as success, exit_code, stdout, and stderr; its optional error is not the generic class contract. Always preserve the process exit code and interpret the payload for that command.

Lifecycle results

plan is read-only and returns success even when its action is recreate or blocked-removal; automation must inspect the action and create, recreate, start, missing, and blocked fields. For catalog images, plans read local configuration and catalog data without requiring QEMU or host networking. A registered local-* image also undergoes cache validation and requires qemu-img. Plans download no images. They show exact images, total resources, change reasons, and disk effects. The 0.9 candidate also lists each data disk, including the implicit 128 GiB /data. up checks host capabilities and address availability before applying changes. up creates missing nodes, starts stopped ones, re-checks readiness of running ones, and rewrites the SSH client configuration Barn installed from the complete applied deployment. recreate performs the same full refresh; node destroy removes stale entries, and whole destroy removes that configuration. start powers on stopped nodes and re-checks readiness of running ones; start and restart also refresh SSH aliases. Destructive drift returns a conflict that names the next commands: barn plan, then barn recreate <node> or barn destroy <node>. On a terminal those commands ask you to type the confirmation word; --force is for scripts. If VM lifecycle succeeds but the SSH client configuration cannot be written, the command reports a warning and remains successful; barn ssh still works. Structured output carries integration warnings in warnings[]. The 0.9 candidate leaves symlinked or hard-linked ~/.ssh/config untouched, publishes its fragment, and reports the Include line to add manually.

Guest management SSH is the readiness boundary. Optional setup failures are reported in nodes[].warnings; completed recovery actions, including data resets, are in nodes[].repairs. A usable guest with these limitations exits 0. Repeat up to retry unfinished stages without restarting running VMs. Inspect the warning fields when automation requires every configured feature.

A lifecycle batch with isolated node failures can exit 5 even when every selected node failed; it reports N of M node(s) failed: <node> (<stage>: <error>); .... Common stages include prepare, start, readiness, bootstrap, guest-setup, stop, and status; readiness or bootstrap failures add run \barn logs ` for the guest console. Structured output carries failures[]withnode, stage, and error(and optionalreasonin the **0.9 candidate**), plusrolled_backwhen–rollback` removed the prepare artifacts of nodes that never committed. See A node did not become ready.

status shows node, state, IP, exact image, and CPU/memory. Use --verbose for SSH ports, architecture, accelerator, and PID. TCG is marked in ordinary text as well. One degraded node does not hide its peers; status exits 5 and retains per-node errors and failures[]. Running means the VM process is running; status does not claim to have checked guest readiness.

Starting commands also refresh Barn hosts and control-node SSH entries in running guests. Stopped guests catch up when started. --no-wait skips guest readiness, guest recovery, and that refresh; a later up completes them. Selected recreate refuses remaining peer drift before stopping or deleting disks; select the required nodes together as directed.

The control guest’s Barn-managed SSH entries accept replacement host keys without recording them in known_hosts, so recreated lab nodes remain reachable. User-added SSH entries are preserved.

Recovery

up and start isolate missing host-share failures by node; up also continues existing stopped peers when a new node fails to prepare. Partial results keep exit code 5 and preserve successful nodes. Retry hints retain the inventory, repository and applicable flags; a start retry remains start.

Setup and its lifecycle retry share one operation_id. Even before deployment state exists, barn logs --source events --json can read the bounded phase trace after failed setup. Setup traces omit command arguments and authentication data; retain the command output for its detailed cause. setup --dry-run writes no trace. Successful destroy --delete-persistent and purge summaries describe the final deletion/retention result; purge leaves the image cache and host network. Owned persistent disks left by a previously removed node no longer block destroying the remaining nodes. Ordinary destroy retains those disks; explicit persistent deletion or purge is still required to remove them.

On macOS, fresh network setup finishes Homebrew discovery/installation or the pinned-archive download before requesting administrator authentication. Homebrew can invalidate an earlier sudo credential; the new order avoids that failure without widening the privileged operation. Failed downloads do not prompt.

Interrupted operations (0.9 candidate)

The candidate recognizes a recorded QEMU PID reused by an unrelated process as a stopped node. An interrupted stop whose VM still runs is reconciled to running; other unfinished transitions name the command that can finish them. destroy settles interrupted transitions itself. A failed first up can be retried after editing the inventory because its uncommitted artifacts are rolled back from the journal.

Logs and environment

logs defaults to the guest serial console; --source qemu reads QEMU diagnostics, and --source events reads the deployment-wide bounded event log. With --follow, text output streams bytes and JSON emits NDJSON records (YAML emits a document stream). The 0.9 candidate renders ordinary event/QEMU log reads as readable records, showing a QEMU argv only with --verbose.

Environment variable Purpose
BARN_HOME absolute private state directory; default ~/.barn; not a symlink or broad directory such as your home
BARN_REPO repository default; overridden by --mirror, then --repo where exposed
BARN_OUTPUT text, json, or yaml; presentation flags override it
BARN_VERBOSE boolean diagnostic default; presentation flags override it
BARN_VMNET_ARCHIVE absolute path to the pinned socket_vmnet archive for macOS setup; digest checks still apply
NO_COLOR non-empty disables color

SSH passthrough and completion

barn ssh [node] [--] [command ...] opens a session or runs an optional command. barn exec [node] [--] <command ...> requires a command and passes through its exit status. Presentation flags before -- belong to Barn; ssh arguments after -- are joined with spaces and interpreted by the remote shell, like plain SSH. exec preserves argument boundaries; explicitly use sh -c when you need shell expansion or pipelines. A single command string retains the shell shorthand. Before --, only zero or one known node is accepted. For convenience, omitting -- uses a known first argument as the node, or runs all arguments as a command on the default node with a warning. In the 0.9 candidate, a first argument containing a digit or - is checked for a near-miss node name: at most one edit for words of four characters or fewer, or two edits for longer words. Such a typo is refused; ordinary commands such as ls, df, and wc still run. Use an explicit -- in scripts.

Load barn completion bash|zsh|fish|powershell for command and scoped-flag completion. It also provides command aliases, templates, image aliases, closed flag choices, and best-effort node names from the desired or applied specification. In the 0.9 candidate, -f completion filters for YAML files.

Exit codes

This table gives the Barn 0.9.0 release candidate exit-code contract. Missing inventory and unknown images are usage errors (2); setup and network failures use runtime (1) or capability (3), according to their cause.

Code error Meaning
0 success, including usable guests with optional limitations
1 runtime the operation ran and failed (a tool, download, or guest failed)
2 usage the command line or inventory is wrong
3 capability the host lacks a tool, the Barn network, or a privilege
4 conflict the deployment’s state forbids it, or another barn command holds it
5 partial node-level batch failures; inspect failures[]; any successful peers are retained
6 resource a host address, port, subnet, or disk is taken
7 integrity a verified digest, signature, identity, or ownership did not match
130 cancelled interrupted (SIGINT/SIGTERM) or confirmation declined

In the 0.9 candidate, a modifying command that finds another Barn command holding the deployment waits up to 10 minutes and names it; timeout is exit 4 with reason: "deployment_busy". status, ssh, exec, ssh-config, and hosts do not wait. status reports the concurrent operation in note while showing recorded state; this is not a guarantee of a completed deployment.

ssh and exec pass through the SSH child exit code unchanged, including 255. That value may indicate an SSH connection failure or a remote command returning 255; text, JSON, and process exit status agree.

3 - Mac Commands

barn mac commands and flags, machine rules, JSON results, failure reasons, networks, and files.
Important

Barn 0.9.0 release candidate; unreleased. See Status for current validation and release checks. Use barn mac --help from the binary you run.

barn [--json|--yaml] [-v|--verbose] mac <command> [flags] [name...]

barn mac requires Apple Silicon and macOS 27 or later, and runs as the logged-in user; it refuses to run as root. On other hosts the command is hidden, and commands that need the Mac component fail with mac_host_unsupported. Bare barn mac is barn mac ls.

Commands

Command Purpose
ls every machine with state, address, SSH, macOS, resources and shares; aliases list, status, st
up [name] create the machine if needed, start it, wait for SSH and sudo
start [name...] start existing machines
stop [name...] shut down normally; power off after two minutes
restart [name] stop, then start, applying configuration changes
open [name] show the desktop, starting the machine first if needed
ssh [name] interactive shell, or, after --, a command line for the guest shell
exec [name] -- cmd run a command, keeping argument boundaries
configure name change a machine’s settings
recreate name replace the machine with a fresh macOS, keeping its settings
destroy name... delete machines
password [name] show or copy the login password
ssh-config print, install or remove the OpenSSH entries
logs [name] recent runtime log
setup prepare the macOS base without creating a machine
image ls restore images and bases with the machines using them; alias list
image update prepare Apple’s newest macOS 27 as the default base
image prune list, and with --yes delete, unused images
doctor check the host, the component, the base and every machine

Flags by command

Command Flags
up --cpu --memory --disk --user --share --clipboard --subnet --ipsw -y/--yes -n/--no-wait --open
start --all -n/--no-wait --open --recovery
stop --all --force
restart -n/--no-wait --open
configure --cpu --memory --share --unshare --clipboard --subnet
recreate --update --force -n/--no-wait
destroy --force
password -c/--copy
ssh-config -i/--install --remove
logs -n/--lines (default 100, at most 10000)
setup --ipsw --disk -y/--yes
image update --ipsw -y/--yes
image prune --installers -y/--yes

A command without a machine name acts on the only machine, or on mac1; with several machines and none named mac1, it asks for a name. start and stop accept several names, or --all. destroy and recreate describe what they delete and ask for the command name to be typed; --force confirms without a terminal.

Flag values

Flag Value
--cpu virtual CPUs, at least 2 and at most the Mac’s logical CPUs; default 4
--memory 16G, 16GiB, 16GB or bytes; at least 4 GiB and at most installed memory; default 8 GiB
--disk capacity of the base, at least 32 GiB; default the prepared base’s, 100 GiB. Another capacity installs another base
--user administrator account; default your macOS user name, or barn when that is not a valid account name
--share [name=]path[:ro|:rw], repeatable, at most 8; the name defaults to the last path component; ~/ expands to your home
--clipboard on or off; default on
--subnet a canonical private /24 such as 10.10.30.0/24, or auto for the first free one

up refuses creation options that differ from an existing machine instead of ignoring them, and its next: line names the fix. CPUs, memory, shares, the subnet and the clipboard change with configure. The account and disk capacity are fixed for a machine’s lifetime, and recreate keeps them: other values need another machine. A different macOS comes from image update, then recreate --update.

Machines

  • Names: 1–32 lowercase letters, digits and inner hyphens, starting with a letter: mac1, dev, build-2.
  • Running limit: two macOS virtual machines per Mac, counting other tools and macOS installation. Barn never stops a machine to make room.
  • Account: an administrator with passwordless sudo, SSH key login, desktop automatic login and Remote Login. SSH password login is disabled. The login password is random and stored in the machine’s password file.
  • Guest names: the computer name is the machine name; the local host name is barn-<name>, so the guest answers as barn-<name>.local.
  • Shares: one VirtioFS device mounted by macOS under /Volumes/My Shared Files/<name>. Shares must be existing directories, not symlinks, and change only while the machine is stopped.
  • Clipboard: plain text, synchronized over the machine’s SSH connection when its window gains or loses focus; at most 1 MiB; items marked concealed are never sent.
  • Stop: normal shutdown through macOS in the guest; if the machine is still running after two minutes it is powered off and the result carries "forced": true. --force powers off at once.

Networks

Each machine’s network is created by its own runner process when the machine starts and disappears when it stops. No daemon and no root is involved.

Item Value
Subnet first free private /24 from 10.10.20.0/24 to 10.10.59.0/24, avoiding host routes and other machines; --subnet selects one
Gateway .1, the Mac
Guest address .10, by DHCP reservation for the machine’s MAC address
Reachability the Mac and the internet through NAT; not other machines, not the LAN

start refuses a subnet that a host route now overlaps, such as a VPN, with reason mac_subnet_in_use. SSH host keys are pinned to the machine instance, not its address, so configure --subnet keeps trust.

macOS Local Network privacy stops third-party programs from connecting to these networks unless their app is allowed under Privacy & Security → Local Network; the error is “No route to host”. Barn itself connects through Apple’s /usr/bin/nc and /usr/bin/ssh, which are exempt.

JSON output

Every command honors --json and --yaml. Progress goes to stderr.

ls

{
  "schema_version": 2,
  "root": "/Users/alice/.barn/mac",
  "prepared": true,
  "base": {"version": "27.0", "build": "26A428", "base_id": "26A428-e16af589f4b705ca397f5b21"},
  "machines": [
    {
      "name": "mac1",
      "kind": "macos",
      "state": "running",
      "ready": true,
      "ssh": "ready",
      "address": "10.10.20.10",
      "address_stable": true,
      "ssh_host": "10.10.20.10",
      "ssh_port": 22,
      "user": "alice",
      "image": {"version": "27.0", "build": "26A428", "base_id": "26A428-e16af589f4b705ca397f5b21"},
      "cpus": 4,
      "memory_bytes": 8589934592,
      "disk": {"capacity_bytes": 107374182400, "allocated_bytes": 1202647040},
      "network": {"subnet": "10.10.20.0/24", "gateway": "10.10.20.1", "address": "10.10.20.10"},
      "shares": [{"name": "src", "host": "/Users/alice/src", "guest": "/Volumes/My Shared Files/src", "readonly": false}],
      "clipboard": true,
      "pid": 2545,
      "instance_id": "b1482ffc-2da7-45d9-ace5-c5f3193a9172",
      "warnings": []
    }
  ],
  "running": 1,
  "limit": 2
}
Field Values
state prepared (never booted to readiness), starting, running, stopping, stopped, unknown
ready true only when this check reached the guest over SSH with sudo
ssh ready, pending (first boot in progress), unavailable, offline, unchecked
observed present when the guest reports another macOS build than its base
window_visible present while the desktop window is shown
error, warnings the last recorded failure, and SSH problems found by this check

Lifecycle results

up, restart, open, recreate and configure return one result; start, stop and destroy return {"machines": [...]}, one result per machine, even for a single name.

{"name": "mac1", "action": "created", "state": "running", "ready": true,
 "address": "10.10.20.10", "user": "alice", "version": "27.0", "build": "26A428"}
Field Values
action created, started, running (already running), restarted, recreated, opened, configured, stopped, powered_off, already_stopped, destroyed, absent
forced true when a normal stop had to power the machine off
window true when the desktop was shown
warnings non-fatal follow-ups, such as a change that applies at the next start

exec --json returns the same object as Linux barn exec --json: node is the machine name, and exit_code, stdout and stderr come from the guest. An interactive ssh has no JSON form.

Failures

Exit codes are the Barn CLI’s. A remote command’s own exit status passes through ssh and exec unchanged. JSON failures carry a stable reason and a next command:

Reason Exit Meaning and next step
mac_host_unsupported 3 not Apple Silicon, or older than macOS 27
mac_runner_missing 3 the Mac component is not installed next to the CLI
mac_runner_protocol 3 CLI and component come from different builds; install them together
mac_root 2 run as your normal login user, not with sudo
mac_download_consent 2 downloading macOS needs --yes without a terminal, or use --ipsw
mac_machine_absent 4 no machine by that name; barn mac up NAME
mac_not_initialized 4 the machine has not finished its first boot; barn mac up NAME
mac_not_running 4 ssh/exec need a running machine; barn mac start NAME
mac_running 4 a change needs the machine stopped; barn mac stop NAME
mac_configuration_conflict 4 up options differ from the existing machine; the named configure or recreate
ssh_config_linked 4 ~/.ssh/config is a link; add the printed Include line yourself
mac_vm_limit 6 two macOS VMs already run; stop the named one
mac_subnet_in_use 6 a host route now overlaps the machine’s network; configure NAME --subnet auto
disk_full 6 not enough free space to download or install macOS
mac_machine_damaged 7 a machine that booted before lost disk or identity files; its directory is kept
mac_readiness_interrupted 130 a stop interrupted a first-boot SSH wait

Files

$BARN_HOME/mac/
  config.json                        installation identity and default base
  images/ipsw/<build>.ipsw(.json)    Apple restore images; .partial while downloading
  images/base/<id>/                  read-only, unbooted macOS bases
  slots/<name>/state.json            the machine record (schema 2)
  slots/<name>/disk.asif             copy-on-write disk layer over the base
  slots/<name>/machine-id.bin        Apple machine identifier
  slots/<name>/auxiliary-storage.bin boot storage
  slots/<name>/id_ed25519(.pub)      the machine's SSH key pair
  slots/<name>/known_hosts           the pinned host key, under barn-mac-<instance>
  slots/<name>/password              login password, mode 0600
  slots/<name>/runner.log            runtime log (barn mac logs)

Runtime sockets live in /tmp/barn-mac-<uid>-<hash>/. ssh-config writes ~/.ssh/barn-mac_config and one # barn-mac:include block in ~/.ssh/config, independent of the Linux # barn:include block. The desktop window positions are saved in ~/Library/Preferences/io.pgsty.barn.mac-runner.plist.

Inside the guest, Barn writes ~/.ssh/authorized_keys, /private/etc/sudoers.d/80-barn, /etc/ssh/sshd_config.d/000-barn.conf, the computer and local host names, and disables sleep with pmset.

4 - Images

Signed catalogs, built-in aliases, repository selection, local cache verification, imports, and pruning.

Barn uses a materialized static-file Catalog plus immutable qcow2 artifacts. Official and HTTP Catalogs are signed; explicitly selected local and HTTPS repositories may be unsigned. A Catalog update does not require a new Barn binary, but the binary decides which signing keys and image safety rules are trusted.

Warning

EL7, EL9 9.3/9.6, and EL10 10.0 are deprecated compatibility images. All other built-in versions are supported.

Aliases and pull order

Barn 0.9.0 embeds Catalog 2026092902: 9 families and 39 artifacts. el7 is amd64-only; every other family has amd64 and arm64 artifacts. EL9 includes 9.3, 9.6, 9.7, and 9.8; EL10 includes 10.0, 10.1, and 10.2. u24:stable (Ubuntu 24.04) on the native architecture is the default request.

The stable versions below include both amd64 and arm64. This is the embedded Catalog snapshot, not a live repository listing. Run barn update then barn image list to inspect the currently selected repository. Dated public endpoint checks and guest point-release observations are recorded in Status.

Family Embedded stable Distribution series
d12 20260923.2610.1 Debian 12
d13 20260914.2601.2 Debian 13
el8 8.10.20240528.2 Rocky Linux 8.10
el9 9.8.20260525.2 Rocky Linux 9.8
u22 20260926.0.0 Ubuntu 22.04 LTS
u24 20260926.0.0 Ubuntu 24.04 LTS
u26 20260927.0.0 Ubuntu 26.04 LTS

Debian retains offline-installed XFS tools and the generated en_US.UTF-8 locale, with C.UTF-8 still the default. Ubuntu retains Canonical’s original image bytes; cloud-init configures accounts and networking at startup. The Debian 12 upstream build dated September 23 still lacks XFS tools and en_US.UTF-8, so those adjustments remain necessary. The Ubuntu builds above already include both; their amd64 and arm64 guests passed locale and XFS data-disk checks on September 29. A Catalog refresh changes newly resolved stable requests. Existing VMs and explicitly pinned versions continue using their original base images.

Alias Distribution Architectures Boot Status
el7 CentOS Linux 7.9 / 2211 amd64 BIOS deprecated
el8 Rocky Linux 8.10 amd64, arm64 UEFI supported
el9 Rocky Linux 9.7 / 9.8 amd64, arm64 UEFI supported
el9 Rocky Linux 9.3 / 9.6 amd64, arm64 UEFI deprecated
el10 Rocky Linux 10.1 / 10.2 amd64, arm64 UEFI supported
el10 Rocky Linux 10.0 amd64, arm64 UEFI deprecated
d12, d13 Debian amd64, arm64 UEFI supported
u22, u24, u26 Ubuntu amd64, arm64 UEFI supported
barn image list
barn image info d13
barn image info d13:stable
barn image info el9@9.7
barn image pull d13@20260914.2601.2
barn image pull d13 --arch arm64
barn update

Catalog status values are advisory rather than an activation switch: supported has passed the declared support gate; testing is available for explicit test/risk acceptance but is not supported; deprecated is retained only for EOL compatibility; and unknown has no support classification. Non-supported entries remain runnable and print a warning.

For a pull, Barn:

  1. reads the selected repository’s active local Catalog once for the complete command: the Catalog embedded in this build, or the one last activated for that repository by barn update or image sync;
  2. resolves image[:channel] or image@version-prefix, defaulting to u24:stable with the official Catalog; standalone image pull defaults to the native architecture and accepts --arch, while lifecycle resolution honors vm_arch;
  3. reuses a local file only after size, SHA-256, and qcow2 checks pass;
  4. otherwise downloads the exact Catalog-named artifact, with retries and resumption; the two official repositories can fall back to one another, while custom repositories remain exclusive. All accepted bytes must match the Catalog. An immutable upstream URL is provenance, not a fallback.

Released builds use https://repo.pigsty.io/barn by default. Long-only --mirror selects https://repo.pigsty.cc/barn; precedence is --repo, --mirror, BARN_REPO, then the global default. Both official roots retain canonical signed-Catalog trust. Repository selection determines both the local Catalog slot and the source of downloads. Keep selecting the same custom repository even when its image bytes are cached. Barn never refreshes the Catalog on its own; ordinary image resolution can work offline with an active local Catalog and verified cache. Run barn update to fetch, verify, and activate the selected repository’s current Catalog. Catalog updates use that selected source; a failed update is an error. An image download fails when none of its permitted sources supplies verified bytes. Changing --repo alone does not fetch or activate that repository’s Catalog; run barn update --repo <root> before using its custom aliases.

Verified writable cache files are made read-only again. A damaged, unreferenced cache file is preserved with a .corrupt-<timestamp> suffix before replacement; a base image still referenced by a VM is kept in place and reported as an error.

Runtime policy

Matching architectures use native HVF/KVM except one catalogued incompatibility: the stock EL8 arm64 64K-granule kernel cannot run through Apple HVF, so Apple Silicon uses visible same-architecture TCG automatically. Explicit foreign vm_arch also uses TCG. amd64-on-arm64 uses a single translation thread to preserve x86 memory ordering. TCG results are not performance evidence.

EL7 is deliberately limited to native Linux/amd64. Linux setup installs only the native QEMU family; foreign architectures require the matching system emulator and UEFI firmware before up or recreate can proceed. For Catalog images, plan resolves the intended image and runtime without requiring those tools. Named local-* imports are byte-checked during resolution and still need qemu-img.

barn image pull d13 --mirror
barn image pull d13 --repo https://mirror.example/barn
BARN_REPO=/absolute/local/repository barn up

Unsigned repositories must be local paths or HTTPS. HTTP repositories require a Catalog signed by a trusted key. Immutable upstream artifact URLs must be HTTPS.

Trust and verification

Current ordinary builds embed both production public verification keys. The private signing keys are external to the source repository. Catalog activation rejects unknown keys, malformed content, equivocation, and revisions below the repository-scoped high-water mark unless the operator explicitly allows a downgrade.

Every accepted image must be a size- and SHA-256-matched plain qcow2 with no backing file, external data file, encryption, or unknown incompatible feature. Verified base images become read-only; node root disks are overlays and never modify the base.

barn update
barn image sync --repo https://repo.example/barn \
  https://repo.example/barn/catalog.json
barn image sync --repo /absolute/repo --allow-downgrade /absolute/repo/catalog.json
barn image reset

image reset restores the embedded Catalog but keeps the anti-rollback high-water mark.

barn update checks the repository now and activates a newer Catalog. Barn never refreshes the Catalog on its own; the Catalog embedded in each release is used until you update. image sync is the recovery path for an exact URL or file, including a downgrade.

For repository-scoped recovery, pass the same root explicitly:

barn image sync --repo /srv/barn --allow-downgrade /srv/barn/catalog.json
barn image reset --repo /srv/barn

--repo selects the independent active-Catalog and high-water slot. The source argument does not change this selection. image sync and image reset accept --repo, but not --mirror; when --repo is omitted they use BARN_REPO or the compiled default. For an unsigned custom Catalog, the exact source must be the selected root’s catalog.json.

Static repository format

The published root is deliberately small:

barn/
├── repo.yaml
├── catalog.json
├── catalog.json.minisig       # required for official and HTTP repositories
└── images/
    └── <image>-<version>-<arch>.qcow2

repo.yaml stores author intent: defaults, aliases, channels, exact versions, architectures, boot mode, status, and optional provenance-only upstream URLs. source_user records the image’s declared source login identity, for example rocky in an upstream image or dba after Barn’s official normalization. The pipeline takes the upstream account separately when sanitizing a candidate. Catalog/import metadata does not replace the deployment SSH user (dba by default) or itself normalize the image. The file contains no generated checksum or size fields. catalog.json uses the same logical tree but materializes each variant’s file, SHA-256, artifact size, and virtual size. repo.yaml is schema: 1; the generated catalog.json is the schema-3 Catalog that Barn embeds and signs.

schema: 1
revision: 1
defaults: { image: u24, channel: stable, arch: native, boot: uefi }
images:
  u24:
    aliases: [ubuntu24, noble, ubuntu]
    channels: { stable: "1" }
    versions:
      "1":
        status: unknown
        variants:
          amd64: {}
          arm64: {}

With no explicit file, the two expected artifacts are images/u24-1-amd64.qcow2 and images/u24-1-arm64.qcow2. A variant may use a safe basename override for an existing custom file.

Channels and numeric prefixes are movable selectors. An exact key wins; otherwise a prefix matches on dot-component boundaries and chooses the numerically newest version (el9@9.7 selects the newest 9.7 build, while el9@9 selects the newest 9.x release). The immutable artifact identity remains (image, exact version, arch):

d13:stable + native
  -> d13@20260914.2601.2 + arm64
  -> images/d13-20260914.2601.2-arm64.qcow2

barn repo scan is read-only. build performs strict YAML validation, full qcow2 inspection/checking, and atomic Catalog replacement without changing repo.yaml or QCOW bytes. verify requires the generated Catalog bytes to match a fresh materialization exactly. build and verify require local qemu-img; scan does not. Build on a QEMU host, then publish immutable artifacts first and the Catalog plus its matching signature last. Update a signed Catalog/signature pair together where possible; an inconsistent pair fails verification. Increase revision when changing Catalog contents.

Local layout and imports

Images live under BARN_HOME/images (default ~/.barn/images): family directories contain downloaded artifacts, manifests/ stores the active Catalog with an independent high-water entry per repository, and local/ plus local-images.json hold imports.

barn image import --sha256 <digest> /path/to/base.qcow2
barn image import --name local-mybase --boot uefi \
  --source-user ubuntu --sha256 <digest> /path/to/base.qcow2

The expected --sha256 is optional in the CLI; supplying an independently obtained trusted digest adds an explicit authenticity check to the mandatory qcow2 inspection. Import copies and verifies the file; it does not clean credentials, install cloud-init, detect the guest CPU architecture, or prove that it boots.

Named local aliases must begin with local-, so a future signed Catalog cannot shadow them. --name, --boot, and --source-user must be supplied together. A named import records the importing host’s native architecture; there is no image import --arch option. Use a static repository with explicit variants for foreign-architecture artifacts. Aliases are immutable: choose a new name for different bytes or metadata. Use vm_image: local-mybase in an inventory to select a named import; unnamed imports only populate the cache.

Pruning

barn image prune --dry-run
barn image prune --yes

Bare prune and --dry-run only report candidates; --yes deletes them. Prune protects the union of all artifacts in the selected active Catalog, applied node image digests, and registered local aliases. Therefore a cached Catalog image or named import is retained even when no VM uses it. Unprotected images and recognized stale staging files are candidates; unsafe or damaged files cause an error. Use the same --repo when inspecting a custom Catalog’s cache policy. Images remain cached after destroy, destroy --purge, and purge.

The compiled schema-3 Catalog can be exported byte-for-byte with go run ./tools/catalogexport /absolute/new/catalog.json. A public Catalog at the embedded version must use those exact bytes; same-version different bytes are rejected as equivocation. Release signing and image Catalog signing remain separate trust domains.

5 - Image Pipeline

Validate or offline-normalize an explicit qcow2 candidate without downloading, uploading, or signing it.

The low-level packaging/image-pipeline/build.sh accepts one already-downloaded immutable qcow2 and an independently obtained SHA-256. It never downloads, uploads, touches Barn runtime/network state, reads signing keys, or marks an image supported.

Run these commands from the Barn source checkout. Python 3 and qemu-img are required; offline additionally needs working virt-customize and virt-cat tools from libguestfs. These are build-host dependencies, separate from the dependencies that barn setup installs for running VMs.

Modes

  • validate: copy/re-hash, force qcow2 inspection, validate the single backing chain, run qemu-img check, and emit an explicitly unpublishable evidence bundle. Guest credentials are not changed.
  • offline: additionally use libguestfs virt-customize --no-network and virt-cat on the staged copy. It rejects unrelated UID/GID 88 occupants, normalizes the locked dba/admin identity, disables password/root SSH, removes keys/history/host identity/cloud-init cache, restores targeted SELinux labels, and reads back a deterministic marker.

Official candidate matrix

build-official.py wraps the same offline boundary for a fixed eight-target matrix: Debian 12/13 and Rocky Linux 8/9, each on amd64 and arm64. Every upstream qcow2, RPM/DEB input, release name, digest, and source epoch is pinned in official-v1.json.

./packaging/image-pipeline/build-official.py --list

./packaging/image-pipeline/build-official.py \
  --source-cache /absolute/source-cache \
  --package-cache /absolute/package-cache \
  --output /absolute/existing-output-root \
  --target d13/arm64 --fetch

Without --fetch, every locked input must already exist in the two canonical cache directories. With it, the wrapper downloads only the pinned HTTPS URLs and rejects any digest mismatch before invoking offline normalization. Debian 12/13 install the locked XFS userspace closure; Rocky Linux 8 installs the locked python36 and python3-pip RPMs, and Rocky Linux 9 needs no extra package input. Rocky Linux 8 uses its shipped RHEL chrony template and chronyd service when cloud-init enables NTP. SELinux label restoration belongs to normalization, not an additional package set.

Debian also generates en_US.UTF-8 while retaining C.UTF-8 as the default. Both the guest normalization script and host-side marker validation check these postconditions so a base-image refresh preserves these guest requirements. Ubuntu uses dated, unmodified official images outside this offline matrix.

Each result remains an unsigned testing candidate. To build the complete matrix, omit --target; repeat it to select several targets. --list shows the exact releases pinned by this checkout. The matrix currently contains Debian 20260923.2610.1/20260914.2601.2 and Rocky Linux 8.10.20240528.2/9.8.20260525.2.

Assembly takes parent directories containing the named bundles, not the individual bundle directories. If all eight builds were written below one output root, assemble them with:

./packaging/image-pipeline/build-official.py \
  --assemble-from /absolute/existing-output-root \
  --output /absolute/new-candidate-repository

Repeat --assemble-from if builds are split across roots. Assembly requires exactly one bundle for each of the eight targets, creates a new static repository, and runs barn repo build plus verify using barn on PATH (or --barn /absolute/path/to/barn). Unlike build mode’s existing output root, the assembly destination must not exist. Its channels are candidate, not stable: use d13:candidate, for example. This does not perform native smoke, signing, upload, or Catalog publication.

Validate one downloaded image

SOURCE_DATE_EPOCH=1787486400

./packaging/image-pipeline/build.sh \
  --mode validate \
  --source /absolute/source.qcow2 \
  --expected-sha256 <digest> \
  --output /absolute/new/evidence-directory \
  --name u24 --release 20260801.0.0 --arch amd64 \
  --source-user ubuntu --boot uefi \
  --source-uri https://immutable.example/source.qcow2 \
  --artifact-url 'https://images.example/u24/{sha256}.qcow2' \
  --license NOASSERTION \
  --source-date-epoch "$SOURCE_DATE_EPOCH" \
  --manifest-version 2026082903

Source/output paths must be absolute; source is canonical, regular, non-symlinked, stable while copied, and at most 16 GiB. Output must not exist. The builder uses an exclusive adjacent lock, mode-0700 staging, and one final rename. Failure removes only its guarded staging directory.

Every successful bundle contains the read-only qcow2, recipe, SLSA provenance, SPDX boundary SBOM, manifest-candidate.json (testing), validation evidence, and checksums. The candidate manifest is a pipeline evidence format; repository assembly produces the runtime schema-3 catalog.json. The SPDX file describes the declared input/build boundary and is not a complete package inventory of the guest filesystem. Signing is deliberately outside this pipeline. Validate mode is byte-reproducible for fixed inputs/tools; offline mutation must be built twice and compared before release evidence is accepted.

A release still needs runtime smoke on each declared host/guest path, explicit review of support status and provenance, a new Catalog revision, production signing, and public artifact verification. Build success alone does not authorize a supported status or prove that a candidate is publicly available.