machinemode.io
/
landscape · measured

Landscape.

Everyone already built half of this. Nobody built the same half.

The usual objection to a standard like this is that the problem is already solved — terraform emits JSON, ripgrep emits JSON, just use those. So we measured. Tools with a real machine-output mode were run and inspected byte by byte; where a flagship mode needs credentials or a cluster, the answer comes from primary documentation instead. Every row says which.

The result is not that the problem is solved. It is that 7 of 8 tools with a streaming machine mode independently invented a type discriminator, and spelled it 5 different ways. That convergence is the argument for a standard. The divergence is the cost of not having had one.

§ 01
Where the argument lives

The streaming tier.

These are the tools that emit a stream a consumer can process incrementally — the closest thing to what AOI‑CLI specifies. Four properties matter: line‑delimited framing, a type discriminator, a version of the output format carried in the output, and a terminal event that says whether the run succeeded.

ToolNDJSONDiscriminatorFormat versionTerminal eventSuccess bool
cargo✓reason—build-finishedsuccess
terraform✓typeui——
ripgrep✓type—summary—
git trace2✓eventevtatexit—
git—leading sigil character (#, 1, 2, u, ?, !)———
pytest-reportlog✓$report_typepytest_versionSessionFinish—
journalctlvaries————
stripe-clitty-dependenttype———
  • 5/8 chose newline‑delimited JSON. Independently, without coordinating.
  • 7/8 invented a discriminator — spelled reason, type, event, leading sigil character (#, 1, 2, u, ?, !), and $report_type. Cargo carries two in one stream: reason on the envelope and $message_type inside embedded rustc diagnostics.
  • 3/8 declare an output‑format version. Terraform's lives under a key called ui, inside an event typed version — invisible to any key‑name search.
  • 4/8 emit a terminal completion event, and 1/8 carries an explicit success boolean. Cargo's build-finished is the only one. Ripgrep's summary carries statistics but no verdict, so a clean no‑match run is indistinguishable from an aborted one.
  • None has all four. Cargo and terraform each have three, and they are missing different ones.
§ 02
Named, not hand-waved

What each is missing.

The useful form of this data is not a score. It is a list of specific, filable gaps — most of these tools are a field or two from a contract an agent could rely on.

cargo--message-format jsonrun locally

No version declaration anywhere in the stream. A consumer cannot branch on format version.

terraform-json on plan/apply/initrun locally

No terminal event on failure, and no success boolean at any time.

ripgrep--jsonrun locally

No version field of any kind, and no success indicator on the terminal event.

git trace2GIT_TRACE2_EVENTfrom docs

Integer code rather than a boolean; no stability promise and no offline documentation (api-trace2 ships no man page).

gitstatus --porcelain=v2run locally

Not JSON at all, and no in-band version. Arguably the most-invoked tool in any agent shell session.

pytest-reportlog--report-log=FILEfrom docs

File-only — it can never write to stdout, so it cannot be piped.

docker--format json / system events --format jsonrun locally

Three different output shapes under one --format json flag: `events`/`ps` are NDJSON, `inspect` is a JSON array, `version` is a single object. The tool does not agree with itself.

kubectl-o jsonrun locally

Buffered single document, no terminal event, no success boolean, no offline schema discovery.

syft-o syft-jsonfrom docs

A single buffered document — structurally cannot stream, so no terminal event and no success boolean.

gh--json f1,f2 (fields mandatory)from docs

Buffered array, no envelope, no version, no completion signal.

aws-cli--output jsonfrom docs

No envelope, no version, no completion signal.

journalctl-o json / json-seq / json-prettyfrom docs

`--show-cursor` emits a non-JSON `-- cursor: …` trailer onto stdout.

stripe-clilisten --format jsonfrom docs

Stdout is not pure: EndpointResponse lines, POST errors, and logs-tail warnings print regardless of --format.

ansibleANSIBLE_STDOUT_CALLBACK=ansible.posix.json (no flag)from docs

Buffered and emitted only at run end, so an aborted run produces nothing at all. `-v` writes to stdout by default and corrupts the document.

eslint-f jsonfrom docs

A bare array with no envelope and no totals — the consumer must sum errorCount. An inherited TIMING env var injects a text table into stdout.

trivy-f jsonrun locally

A version number with no policy attached is not a compatibility contract.

§ 03
Convergence

Three ideas, invented three times each.

12 of 22 surveyed tools independently invented a type discriminator, in 9 mutually incompatible spellings.

type · reason · event · Type+Action · kind · $report_type · $message_type · metadataType · leading sigil char · level

cargo carries TWO differently-spelled discriminators in one stream: `reason` at the envelope level and `$message_type` inside embedded rustc diagnostics.

Three tools independently invented a sigil to mark envelope keys apart from payload keys. All three chose a different sigil.

terraform: @ (@level, @message, @module, @timestamp) · pytest/rustc: $ ($report_type, $message_type) · journalctl: __ and _ (__CURSOR, _BOOT_ID)

A consumer reading `.version` gets a different KIND of answer from each tool.

semgrep: the tool's version · npm ls: the described project's version · cargo metadata: the output format's version

1 of 22 tools emits a terminal event with an explicit success boolean: cargo's build-finished.success.

Everyone else who signals completion downgrades to an integer requiring an out-of-band code table: git trace2 `code`, pytest-reportlog `exitstatus`, pytest-json-report `exitcode`. Ansible gives counters. ESLint makes you sum errorCount.

No tool in the survey can be asked, offline and unauthenticated, to describe its own output.

gh enumerates field NAMES offline but publishes no types; aws ships 428 API models that describe the API rather than the CLI envelope; syft publishes 111 schemas but its own schema.url needs network and points at main.

§ 04
So you can check it

How this was measured.

Two independent methods, deliberately. Probe: an assumption‑free instrument runs the command and inspects the bytes — it makes no AOI assumptions, which is what lets it measure non‑conforming tools at all. Every probed command is credential‑free and network‑free so the run reproduces anywhere. Docs: primary vendor documentation and source, for tools whose flagship mode needs a cluster, a daemon, or a token.

The two methods were run separately and agreed. That matters more than either result alone — the headline below was reached twice, by people and processes that could not see each other's work.

No surveyed tool has all four of {NDJSON, type discriminator, in-band format version, terminal event with an explicit success boolean}. Cargo and terraform each have three.

Two traps worth naming, because they are the easiest errors to make with this data. Syft's schema promise does not extend to grype — grype's published schemas cover grype db search, never grype -o json. And cargo's “stable and versioned” promise covers cargo metadata, not the build stream, which carries no version at all.

The raw dataset is landscape.json. Nothing on this page is written by hand — it is rendered from that file so the prose cannot drift from the evidence.