Skip to content

JSON contract

Arnel Robles edited this page Sep 26, 2026 · 2 revisions

The JSON contract

This page is for anything that calls BaryoVM rather than reads its output: a script, an agent, an MCP server, a scheduled job piping to jq.

Pass -o json to any command. The rule the project holds itself to is that the JSON surface is the real interface and the human output is a rendering of it, so anything you can do by reading the terminal you can do by reading the envelope.

The envelope

{
  "ok": true,
  "action": "vm harden",
  "message": "policy applied to oracle",
  "data": { }
}
Field Meaning
ok whether the command did what it was asked
action which command produced this, so a log of envelopes is readable without context
message a human sentence; do not parse it, it is not stable
data the command's own payload, shaped per command
error present when ok is false

Read ok and data. Never parse message. It is written for a person and is expected to change wording between releases without notice.

Known rough edges, stated rather than hidden

A failing command currently prints two JSON documents: its own envelope, then a second from the root command. A single json.Unmarshal of stdout fails with "Extra data" on any failure. Until that is fixed, read the first document, or split on the top-level object boundary.

$ baryovm doctor -o json          # on a machine missing rsync
{ "ok": false, "action": "doctor", "data": [ ... ], "error": "missing: rsync" }
{ "ok": false, "action": "baryovm", "error": "missing: rsync" }

This is tracked as #10, along with the wider work of turning -o json from a convention into a guarantee: a declared shape per command (#11) and stable error codes rather than prose (#31). Until those land, treat data shapes as stable in practice but not yet promised, and match errors on behaviour rather than on message text.

Distinguishing empty from failed

A recurring bug class here, worth knowing because the fixes are visible in the payloads.

stack logs used to answer {"output": ""} for three different situations: a healthy container whose app logs to a file, a stack that is not running, and a read that never reached Docker. It now carries a state, so a machine can tell them apart:

{ "output": "", "lines": 0, "state": "silent",
  "note": "the stack's containers are running and wrote nothing to stdout or stderr" }

state is one of read, silent, not-running, unknown. stack backups carries count and backups for the same reason: an empty listing is a real answer and must not look like a failed one.

vm exec keeps the remote result apart rather than folding it into message:

{ "command": "df -h /", "stdout": "...", "stderr": "", "exitCode": 0 }

A non-zero exitCode sets ok: false and makes the CLI exit with that code.

Exit codes

Zero on success, non-zero on failure. A failed command sets ok: false and error, and returns a non-zero status, so shell-level checks and envelope-level checks agree.

Previewing without acting

--dry-run exists on stack update and vm harden. It reports what would change and writes nothing. Most other commands cannot yet be previewed, which is #34.

If you are an LLM driving this tool

  • Ask for -o json on every invocation. Do not scrape human output.
  • Check ok before using data. On failure read error, and expect the second envelope described above.
  • vm harden --dry-run and stack update --dry-run are safe to run for information.
  • These mutate real infrastructure: stack release, stack deploy, stack update, stack restore, vm bootstrap, vm harden, vm provision, vm exec, deploy, up. Confirm with a person before running them against a host you were not explicitly pointed at.
  • stack restore refuses without --yes, which is deliberate. Do not add --yes to make an error go away; it overwrites a live database.

Clone this wiki locally