# JSON output for automation

Five of the six shipped executables (`scribe`, `scribe-server`, `scribe-auth`,
`scribe-gateway`, and `scribe-ctl`) and the development tool `scribe-perf`
accept global `--json`, before or after a subcommand. `scribe-runner` has no
JSON command results apart from [build info](/docs/json-output.md#build-info); it supports
`--log-format json` for its logs. Read
stdout as one JSON object and also check the process exit status. Diagnostics
and progress stay on stderr (or the configured service log files).

`--log-format json` independently selects JSON lines for diagnostic logs. It
neither enables JSON command results nor guarantees that every stderr message
is JSON. Do not merge stderr into stdout when parsing command results.

## Results

Command results retain their command-specific fields:

| Executable | JSON results |
| --- | --- |
| `scribe` | Repository, workspace, path, history, transfer, lock, and merge reports; simple confirmations use `message`. |
| `scribe-server` | Every command, including initialization, certificates, auth administration, backup/restore, metadata import/export, migration, and health/space reports. |
| `scribe-auth` | `init` paths (`data_dir`, `admin_credential_file`, `pepper_file`, `revision_file`, `trust_domain`), certificate paths/fingerprint, `migrate` schema reports (`data_dir`, `from`, `to`, `users`, `servers`, `dropped`, `dry_run`), `restore` results (`data_dir`, `revoked`, `kept_server_credentials`, `revision`, `incarnation`, `admin_credential_file`, `admin_credential`), `admin-credential issue` results (`data_dir`, `user`, `credential_file`, `credential`, `expires`), and administrative `rows` arrays. No password, private-key, or pepper contents are returned. |
| `scribe-gateway` | Example TOML in `content`, initialized `data_dir`, and the status inventory with observation timestamps. |
| `scribe-ctl` | Command results include `command` and `ok`; cluster snapshots (with the active binary set as `binaries`), setup, add, upgrade, renew and adopt plans and results (a rolled-back upgrade adds `rolled_back`, `failure`, `rollback`, `active_set` and `backups`; a renewal adds `renewed`, per credential `holder`, `credential`, `file`, `replaced`, `replaced_expires_ms`, `new` and `expires_ms`, `certificates`, per server certificate `service`, `file`, `key` (the private key file a `rekey` replaced, else null), `change` (`reissue`, `handover`, `rechain` or `rekey`), `replaced_expires_ms` and `expires_ms`, and `rotation`, `retired` and `authority` for CA changes, or when rolled back `rolled_back`, `failure`, `rollback` and `kept`; cluster snapshots add `ca_rotations`; an adoption plan adds `adopt` (the old `projects` with the services that `running`, the web `database` volume, `was_running`), and a rolled-back adoption `rolled_back`, `failure`, `rollback` and `projects`; nothing to do is `unchanged: true`), `access` `rows` (objects with `account`, `kind`, `state`, `scope`, `access`, `detail`), `doctor` reports (`sections` of `checks` with `id`, `status`, `detail`, `fix` and for expiries `expires`, and a `summary`; see [Controller results](/docs/json-output.md#controller-results)), `bootstrap` results (`remote`, `plan`, `build`, `target`, `controller`, `unchanged`, `current`, `steps`), summaries (a docker cluster's add `docker`: `project`, `directory`, `files`, `images`, `command`; and with the web interface `web`: `url`, `tls_dir`, `tls`), and log lines have their own fields. Plan steps name their data root as `service` (`auth`, a store, `web`). A command run on a remote cluster prints the remote scribe-ctl's result unchanged, except `doctor`, which merges it into its own report. |
| `scribe-perf` | Measurement helper: `info` returns build/format metadata; `workload`, `probe`, and `objects` return `ok` and `measurement` with raw latency samples. The JS scenario harness writes performance reports and compares baselines. |

Administrative `rows` are arrays of arrays of strings, in the same column order
as text mode's tab-separated rows, including pagination cursors. An empty result
is `{"ok":true,"rows":[]}`. Secret contents remain in their designated files:
`credentials create` returns one row `[credential id, file]`, and the device key
and certificate stay in the credential file. `migrate` counts accounts kept
(`users`), servers given ids (`servers`), and token and session records removed
(`dropped`), or with `dry_run` what a run would do; `from` is 3 and `to` is 4.
A directory already at schema 4 reports `from` and `to` 4 and zero counts. `restore` reports the credentials it disabled, the server
credentials kept with `--keep-server-credentials`, the new revision and
incarnation (32 hex digits), and the id of the recovery administrator
credential. `expires` is Unix milliseconds.

`scribe whoami --json` returns `server`, `status`, `username`, `expires_ms`, and
`verified` (always false: the check is local). `status` is `active`, `expired`,
`login_pending`, `logout_pending`, `anonymous`, `credential_file` (a
`--credential-file` or `SCRIBE_CREDENTIAL_FILE` is selected; its identity is in
the file), `plaintext`, or `none`. `username` is set for `active`, `expired`,
and `login_pending`; `expires_ms` only for `active`.

`scribe diagnostics --json` returns `ok`, `offline`, `versions`, `platform`,
`connection`, `paths`, `workspace`, and `issues`. `versions` holds `client`,
`tcp_protocol` (2, plaintext `tcp://`), `tls_protocol` (4, mutual-TLS `tls://`),
`workspace_schema`, and `server` (always `"not checked (offline)"`).
Unavailable or unconfigured values are `null`. Inspection problems retain the available report and return
exit status 1; see [client diagnostics](/docs/reference/cli/scribe.md#diagnostics).

Server maintenance reports use named numeric fields for counts and sizes.
`verify`, `doctor`, `usage`, and `rebuild-locator` include `ok` and `exit`
alongside findings, even when problems are found; they do not append a second
error object. `rebuild-locator` lists per-repository `indexed`, `packed`,
`loose`, `packs`, `skipped` entries (`pack`, `offset`, `id`, `detail`; `pack`
and `offset` are null for a loose file), and verification `problems`.

`backup` returns `files`, `bytes`, `destination`, manifest `metadata`,
`catalog_needs_recovery`, `warnings`, and one `findings` entry per copied
repository. `catalog_needs_recovery` is true when the root catalog was not
closed cleanly and repositories were listed from the directory instead. Each
finding contains `repository`, `id`, nullable `open_pack`,
`uncommitted_tail_bytes`, `uncommitted_packs`, `staging_leftovers`, and
`needs_recovery`. `needs_recovery` is true when the repository database was not
closed cleanly, for example after a killed server; the head is then reported
unknown and the restored server recovers the copy at first start. Nonzero
leftover values describe an interrupted publication that was copied unchanged,
not repaired.

`migrate` returns `ok`, `dry_run`, `target`, `changed`, `pending`, and
`repositories`. Each repository includes its id in `repository`, its descriptor
`schema` before the run, and the ordered `steps` selected by preflight. `pending`
means the initial plan contained work; `changed` means that work was applied
(always false for a dry run). After success, rerunning returns both false and
empty `steps` for each repository. An interrupted or failed run exits nonzero;
rerun to inspect and resume the durable progress. Incompatible pre-snapshot
development formats are refused.

`staging-branches` returns `data_dir`, `ok`, the total `staging` count, and
`repositories` with a `staging` array of `name`, `branch` (id), `operations`
(pending branch-operation ids), `rows` (staged metadata rows), nullable
`source` and `source_change`, `created_change`, and nullable `age_at_most_ms`.
No creation timestamp is recorded for an unfinished branch, so the age is
measured from the commit its snapshot was pinned to and is an upper bound.
`abandoned` is null for a listing; after `--abandon` it contains `repository`,
`repository_id`, `name`, `branch`, `operations`, and `rows_removed`, and the
`repositories` array describes the state the command left behind.

Long-running `serve --json` commands emit `{"ok":true,"state":"stopped"}` only
after normal shutdown. Readiness comes from the runtime directory and diagnostic
lifecycle events. Similarly, `scribe-ctl up --foreground --json` completes its
result after shutdown; use `up --daemon --json` to return when ready.

The JS check and scenario runners accept repeatable `--report FILE` outputs;
the extension selects JSON, text, or self-contained HTML. Performance reports
use schema 2 and `kind: "performance"`; comparison requires complete passing
reports with compatible run metadata and every budgeted metric present. Missing
or invalid required metrics fail comparison. An infinite ratio (nonzero current
value against a zero baseline) is represented as `null`; its `regressed` boolean
still reflects the regression policy. See [test reports](/docs/testing.md#save-reports).

## Controller results

Each dispatched controller command writes one document with `command` and `ok`.
Selected-cluster results include `cluster`. With multiple clusters and no selector,
`status --json` returns a `clusters` array; one cluster or an explicit selector
returns a single snapshot. `up`, `down`, and `status` include `status`, `generation`,
`services`, `logs_dir`, and `warnings`: credentials and certificates that expire
within 30 days (or already did), or whose expiry cannot be read, each with `what`,
`path`, `expires_ms`, and `message`. Service `pid` and `endpoint` values may be
historical; inspect lifecycle status before treating them as live.

`setup --plan --json` returns `plan_only: true` and `plan`: `operation`, `cluster`,
`variant`, `definition`, `runtime`, `endpoints`, `checks` (`name`, `ok`, `detail`),
`steps` (an `action` name, its typed fields, `why`, and any `set_aside` paths),
`notices`, `resumes` (the interrupted run it continues), and `ready`. A plan that
cannot run has `ok: false` and exit status 1. A completed `setup --json` returns
the same `plan`, `results` (per step: `step`, `action`, `messages`), and
`cluster_info`, the summary file it wrote. `add auth` and `add gateway` return
the same shapes (`operation` is `add-auth` or `add-gateway`); their plans also
carry `definition_diff` (lines starting with `@ FILE`, `+`, `-`, a space, or
`…` between hunks) and `afterwards` (what clients and operators do next). An add
that finds its service complete returns `unchanged: true`, an empty `results`,
and runs nothing. `where --json` returns `endpoints` (each `tls://` one with the
`ca_fingerprints` clients verify it by),
`sections` of `files` (`path`, `purpose`, `exists`), `web_tls`, `next` commands, and
`warnings`; with several clusters and no selector it returns a `clusters` array.
`doctor --json` returns `cluster`, `definition`, `runtime`, `remote` (the
target of a remote cluster, else null), `sections` and `summary`. Each section
has `id`, `title`, `host` (null where the command ran; the remote target for
the sections a remote cluster's own scribe-ctl reported) and `checks`; each
check has a stable `id`, a `status` (`ok`, `info`, `warn` or `fail`), a
one-line `detail`, and for most that are not ok a one-line `fix`. A check
that judged an expiry (`expiry.<file>`, `ca.internal.<purpose>`) adds
`expires`: `at` (RFC 3339, UTC) and `days_left` (whole days, negative once
past); a certificate that does not expire has none. `summary`
counts `ok`, `info`, `warn` and `fail`, names the worst `status`, and gives
the `exit` status: 0, 1 (warnings only) or 3 (a failure). `ok` is false only
when a check failed. A remote cluster's report is one document: this
machine's sections, then the remote's.
`logs` includes `service` and a `lines` array of original log strings.
`example --json` includes `format: "toml"` and the template in `content`.
Foreground `up --json` emits its result only after shutdown; daemon mode returns
when ready. Operational errors use `ok: false`, `command`, and `error` alongside
a nonzero exit status.

## Errors, help, and exit codes

With `--json`, argument errors produce a single object containing `ok: false`,
`code: "usage"`, `error`, and `exit: 2`. The message deliberately omits argument
values, which could contain a mistakenly supplied secret. Use text `--help` for
syntax details. `--json --help` and supported `--json --version` requests return
`ok: true`, `content`, and `exit: 0`.

For argument errors to use JSON, an exact `--json` must occur before the `--`
end-of-options marker. After that marker, `--json` is a positional argument.

Runtime failures have `error`, `code`, and `exit`. The client retains its domain
error codes (which may be null), including `merge_incomplete` for a merge
submission that does not cover every source change; logging setup failures use
`logging`. For a semantic refusal `error` keeps the full `name (number): detail`
form while the text-mode stderr line shows `detail (name)` without the number. Other
executables use `command_failed`. Successful client workflows use command-specific result schemas; do not require an `ok` field in every client result.

- Most commands exit 0 on success and 1 on runtime failure.
- `scribe` retains its documented domain-specific exit codes.
- `scribe-server doctor` and `usage` exit 1 for reported problems and 2 when
  the check cannot run, including configuration or ownership-lock failures.
- `scribe-server verify` exits 1 for problems or inability to run.
- `scribe-server rebuild-locator` exits 1 when entries were skipped, verification
  reported problems, or the command could not run.
- `scribe-perf` exits 2 for failed measurement gates and 1 when it cannot run.
- Argument errors exit 2 for every executable.

The process exit status is authoritative. A failed mutating command can have
completed some work before encountering an error; JSON does not change retry,
operation-outcome, durability, or recovery semantics.

File log entries include `path` and nullable `moved_from` (`path`, `change`).
Continue with both fields from the last row: `--before <change> --after-path <path>`.
With `log PATH --all-branches`, every row also has `history_snapshot` (repository
change ceiling) and `in_ancestry` (boolean). Reuse the snapshot as `--at` when
paging. `true` means the revision's accepting commit is in the selected branch's
ancestry, not that its bytes were chosen by a merge; `false` identifies related
work outside that ancestry. Both fields are null for ordinary logs and `info`'s
latest revision. Branch IDs and historical paths identify the revisions;
branch display names are current, even with a pinned snapshot.
Every `log` row carries the submitted `message` and a nullable `note`
(`change`, `revision`, `author`, `text`, `time`): the current description
recorded after acceptance, when there is one. The page's `notes_available` is
false against a server that predates descriptions; every `note` is then null.
`describe CHANGE -m TEXT` returns the recorded `note`; `describe CHANGE
--history` returns `change` and `notes`, oldest first, each in the same shape.
`lock rules [path]` returns `source: "main:.scribe/config.toml"`, `change`, `text`, ordered `rules: [{line, pattern}]`, and
nullable `path`, `required`, and `rule: {line, pattern}`. Revision zero with `text: "version = 1\n"` means the main config file is absent.
`text` is the complete accepted TOML configuration; `rules` contains only the
lock section, with actual TOML source lines. Profile-only edits also advance
the reported config-file revision. `info` additionally returns
`lock_required`, `lock_policy_change`, and nullable `lock_rule`.

Offline edits return `pending_reconciliation`, an array of paths, alongside the
usual `opened` and `skipped` arrays. Local add/delete reports use the same field,
plus `check`: `null` with `--offline` or when nothing was opened (and always
for `edit`, whose lock already made the round trip), `{skipped: reason}` when the
server could not be reached or the check could not run, otherwise `head`,
`ready`, and `paths` in the shape `reconcile` returns, for the opened paths only.
A check with findings does not change the exit status.
`add` exits 1 after its report when a file named on the command line appears in
`skipped`; skips discovered while recursing into a named directory leave the
exit status 0. `revert` and `sync` reports carry `displaced` (`path`,
`recovery`) for every local copy moved to `.scribe/_state/recovery`; the same notice is
printed on stderr in text mode.
Status path entries have boolean `needs_reconcile`; `info` has nullable
`needs_reconcile` (null when not opened). False is not a live ownership guarantee.

`reconcile [--scan]` returns `head`, `policy_change`, `ready`, `exit`, `paths`, and
nullable `discovery`. Each path has `path`, `action`, `ready`, nullable
`base_change` and `server_change`, `lock_required`, and `issues`. Issues contain
`code` and `detail`; codes are `server_changed`, `locked`, `missing`,
`unexpected_file`, `unresolved`, and `move_source_blocked`.

`discovery` contains `tracked_modified` (discovered and opened), `tracked_missing`,
`untracked`, `ignored`, `skipped_links`, `invalid_names` (`path`, `reason`), and
`problems`. Missing/untracked discoveries remain outside opened intent.
`ready` and `exit` refer to opened paths: exit 0 when all pass, 1 when any are
blocked. A blocked reconciliation emits this single report without an appended
error object. An execution failure instead uses the normal runtime error schema;
local intent and already-acquired locks survive for retry.

`get` returns `repository` and `branch` IDs, the requested `revision` and `path`,
`file_change` (that path's last change at the selected snapshot), absolute
`destination`, full `content` digest, `size`, and `bytes_fetched` (chunk payload
bytes). Success means the complete file was verified and published. A refusal or
failed download emits only the normal error object; no partial success report is
printed. An existing destination is a protection refusal (exit 2).

`merge` and `cherry-pick` exit 1 while any path has `applied: false`; the
report is still the only stdout document. Merge/resolve path entries include
nullable `group`, `rename`, and `path_conflict`. `group` identifies paths whose
choices are saved together. `rename` contains `base`, `ours`, and `theirs`
historical names (a null side means deleted).
`path_conflict` is `rename/rename`, `rename/delete`, or `destination collision`,
or null for ordinary/content-only resolution. A resolved row retains this
context; inspect `choice` and `applied` for its current resolution state.

`scribe move --detect --json` returns `dry_run`, `moves` (each `source`,
`destination`, `size`, and `id` of the recorded move, `null` on a dry run),
`unpaired` (each `path` and `reason`) for missing tracked files that no
untracked file could be vouched for, and `hashed`, the number of untracked
files whose length matched and were hashed.

## Build info

`scribe`, `scribe-server`, `scribe-auth`, `scribe-gateway`, `scribe-runner`,
and `scribe-ctl` answer a hidden `--build-info` flag for tooling that checks a
set of binaries before an upgrade (`scribe-ctl upgrade` does). It must be the only argument: it prints one
JSON object on stdout and exits 0 without reading configuration, taking locks,
or starting logging. With any other argument it is an ordinary usage error.

```sh
$ scribe-gateway --build-info
{"build":"0.1.0","executable":"scribe-gateway","formats":{"auth_protocol":2,"route_store_schema":2,"tcp_protocol":2,"tls_protocol":4},"target":{"arch":"aarch64","os":"macos"},"version":"0.1.0"}
```

`version` is the package version and `build` the `SCRIBE_BUILD_ID` set at
compile time, or the version when none was set. `target` names the operating
system and architecture the binary was built for, as Rust's
`std::env::consts` spells them. `formats` maps each persistent format and
protocol the executable reads and writes to its version, and lists only those
it uses:

| Key | Meaning | Reported by |
| --- | --- | --- |
| `tcp_protocol`, `tls_protocol` | Storage protocol on `tcp://` and mutual-TLS `tls://` connections | all but `scribe-auth` |
| `auth_protocol` | Auth wire protocol (`SAU<n>`) | all |
| `workspace_schema` | Newest workspace schema (schema 1 is still read) | `scribe`, `scribe-runner` |
| `catalog_schema`, `repository_schema`, `object_store`, `envelope` | Data-root formats, as the data-root descriptor records them | `scribe-server` |
| `repository_migration` | Repository metadata migration level | `scribe-server` |
| `auth_schema` | Auth store schema | `scribe-auth` |
| `route_store_schema` | Gateway route store schema | `scribe-gateway` |
| `ctl_state` | Format of `scribe-ctl`'s binary-set and backup records in its state directory (`bin/current`, `bin/<build>/SET.json`, `backups/<stamp>/<root>.json`) | `scribe-ctl` |

## Unified status

`status` and `scan` return `workspace` (`id`, `repository`, `repository_name`,
`branch`, `branch_name`, `view`, `view_profile`, `pending_view`), `synced`
(local sync watermark), `scope` (`opened`, `scan`, `scan+verify`), `exit`, and
one canonical-path-sorted `paths` array.
`branch_name` is the branch's name as the working copy last learned it
(recorded at creation and switch, refreshed by `--remote` and by renames);
it is `null` only for a working copy that has never learned it. There is
exactly one row per path; the former separate `opened`, `tracked_modified`,
`tracked_missing`, `untracked`, `conflicts`, `deferred`, and `lock_status` status
lists are replaced directly in this unreleased interface. Other commands retain
their own documented schemas, including reconcile's discovery categories.

Each path contains:

- `path`, `tracked`, nullable `action` (`add`, `edit`, `delete`, `keep`), nullable local
  `state` (`unchanged`, `modified`, `missing`, `present`, `not_file`), `untracked`, and nullable
  inspection `problem`. Null state means uninspected or failed inspection.
- `needs_reconcile` and `deferred`, independently of opened action/local state.
- Nullable `moved_from: {path, change}`, `moved_to` paths, and `move_operations`
  IDs. Accepted move provenance stays separate from physical pending operations.
- Nullable `resolution` containing `state` (`unresolved`, `awaiting_application`,
  `applied`), `choice`, `applied`, nullable `group`, `path_conflict`, and `rename`,
  using the merge/resolve meanings above. An applied choice retains its context.
- Nullable `lock`: null means **unknown**, never unlocked. On successful remote
  checks this contains `required`, nullable `rule: {line, pattern}`,
  `ownership` (`unlocked`, `ours`, `other`), and nullable `holder` with `author`,
  `workspace`, `branch`, and `generation`. Ownership compares workspace IDs.

`remote.state` is `not_checked`, `available`, or `unavailable`. Available includes
`head`, `policy_change`, and `incarnation_matches`; unavailable includes `error`.
A failed explicit remote check retains all local rows, supplies no partial lock
observations, and sets `exit: 1` with process exit 1. It emits one report, without
an appended error object. Lock-only rows have null local state. These observations
are not an atomic server snapshot or a submit-readiness guarantee.

`summary` counts `paths`, `opened`, `unresolved`, `awaiting_application`,
`needs_reconcile`, and `deferred`; categories can overlap. `workflows` contains
nullable `project_publication` (operation id), nullable `resolution`
(`planning`, `aborting`, `conflicts`, `applying`, `resolved`), boolean
`cherry_pick` (the active resolution is a cherry-pick rather than a merge),
nullable `sync_target`, and booleans `switching` and `branch_operation_pending`.
`pending_submits` lists operation IDs with unknown outcomes. `moves` remains in
operation order, with `id`, `dependencies`, `source`, `destination`, `complete`,
`aborting`, and `files` (count). `hints` contains human-readable guidance; use
structured fields, not these strings, for program logic.

`ignored`, `skipped_links`, `invalid_names` (`path`, `reason`), `problems`, and
`files_scanned` describe discovery. `files_scanned` counts additional tracked
paths checked by scanning beyond those already inspected for local workflows.
No `clean` or `ready_to_submit` claim is made from a partial scan. Local path
inspection or ignore-rule problems produce exit 1 while retaining the report;
changes, conflicts, and pending workflows alone produce exit 0.

## Workspace administration

`workspace list --json` returns a `workspaces` array, empty when no active
registrations match. Each row contains `repository` (ID), `repository_name`,
`workspace` (ID), `branch` (ID), nullable `owner` (principal ID), `label`,
`tags` (array of `name` or `name=value` strings, possibly empty),
`created_ms`, and boolean `has_locks`. IDs are full hexadecimal strings;
`created_ms` is milliseconds since the Unix epoch. A null owner means an unowned
registration on a server without auth. Labels and creation times are not
last-active or online indicators.

The CLI follows inventory pages internally and returns one combined array.
Repository or permission failures produce the normal error document rather than
a successful partial inventory. `--everyone` changes owner selection and
`--all-repos` changes repository selection; JSON does not bypass authorization.

Successful `workspace remove --json` returns `repository`, `workspace`,
`operation` (all full hexadecimal IDs), and `retired_ms` (Unix milliseconds).
Rejections, including held locks, use the normal error schema. After a connection
failure, repeat the command with the same per-user configuration directory to
resolve its saved removal intent. See [workspace administration](/docs/workspace-administration.md).

## Repositories and workspaces

`repo create` and `repo info` return `result` (`"created"` or `"info"`) and a
`repository` object with `id`, `name`, `main_branch`, `head`, `incarnation`,
`created`, and `storage_compression`; `repo list` returns the same objects in
`repositories`.

`workspace create`, `workspace register`, `workspace info`, `workspace config`,
and `workspace view show` return `root`, `workspace_id`, `repository`,
`repository_name`, `branch` (id), `branch_name`, `server`, `view`,
`view_profile`, `pending_view`, `author`, `tags`, `readonly_mode`,
`transfer_compression`, `synced`, and `incarnation`. `branch_name` is the name
the working copy last learned from the server: `create` and `register` fill it
in, `switch` and `status --remote` refresh it, and the offline commands (`info`,
`config`, `view show`) report the stored value, `null` only when none was ever
learned.
`switch` returns the sync report with `branch` and `branch_name` added.

## Workspace views and profiles

`workspace info`, `workspace view show`, and workspace creation include
`view_profile` (nullable originating profile name) and `pending_view` (null or
an object with `view`, `profile`, and `target`). The `view` array always describes
the active selection; an empty array means the entire repository. `status` and
`scan` include these fields inside their `workspace` object.

`workspace profiles` returns `revision` and a `profiles` array of `{name, view}`
objects from the accepted definition at the branch head. Local profile overrides
are reflected in workspace `view`, not in the shared profile list.

`workspace view set/add/remove`, `workspace view --continue`, and
`workspace view --abort` (alias `cancel`) return the sync-report schema, including
`complete`, `failed`, `deferred`, and `displaced`. An incomplete transition exits
1 and emits one report, without appending a second error object. `sync --json`
has the same behavior for incomplete file application. Use `workspace view show`
to inspect the saved transition; resume with `sync` or `--continue`, or abandon
it with `--abort`.

## Diffs

`diff [--revision N] [paths...]` returns `revision` (the pinned change, or null
for the baseline comparison), `compared` (files examined), `changed` (files
whose status is not `unchanged`), and `files`. Each file has `path`,
`base_path` (the origin of a pending move, otherwise the same path), nullable
opened `action`, `status`, `added` and `removed` line counts, and `diff`: the
unified hunks (`@@` headers and `+`/`-`/` ` lines, without the `---`/`+++`
file header) for a text change, otherwise null. `status` is `unchanged`,
`modified`, `added` (no base side), `deleted` (no working-copy file),
`missing` (neither side), `binary` (a NUL byte in the first 8 KiB of either
side), or `too_large` (either side above the 2 MiB review bound). The exit
status is 0 whether or not differences exist.

### Controller live inventory

`scribe-ctl df` (alias `usage`), `repo list`, and `status --storage` emit one
object with `command`, `ok`, `cluster` (the selected name, or null), and `clusters`.
Each cluster contains `cluster`, `ok`, `servers`, `totals`, and `lifecycle` (null
except with `status --storage`). Each server has `name`, `endpoint`, `ok`, `error`,
`instance`, `used_bytes`, `available_bytes`, and `repositories`; repository rows
have `id`, `name`, `head`, and `bytes`. Size fields and instance are null on
failure, and the repository array is empty. Unknown available space is also null.
Totals contain `servers`, `responding_servers`, `repositories`, `used_bytes`, and
`complete`. Available space is never summed across potentially shared filesystems.
Partial inventory retains successful servers, sets `ok` and `complete` false,
and exits 1 without emitting a second error object.

### Repository mount reports

`workspace mounts` returns `{ change, mounts }`; each declaration contains `name`,
`path`, `repository`, `branch`, `revision` (decimal string), and `selected`.
It does not resolve target permissions. Sync reports add `mounts` entries with the
same declaration fields plus `state` (`ready`, `excluded`, `unavailable`, `retained`)
and nullable `detail`. Retained directories have empty identity/pin/name strings
because they no longer have a declaration. Failed selected mounts also appear in
`failed`, keep `complete: false`, and return a nonzero command exit status.

### Project orchestration reports

`project status` returns `configuration` (`"working_copy"`, or
`"publication_journal"` if interrupted config replacement temporarily left it
missing), `exit`, `components`,
and optional `publication`. Components contain `name`, `path`, `selected`,
`problem`, optional `mount` (repository ID, branch ID, decimal revision string),
`submitted` (decimal revision string or null), and optional `local` status.
Local status includes repository/branch IDs, decimal-string `synced`, scope,
remote state (with decimal-string head/policy revisions when available), view and
pending view, per-path status, pending operation IDs, moves, sync target,
resolution, branch operation/switch state, and project ownership. Per-path and
pending-view records reuse ordinary status fields and their existing types.
Only selected components affect the aggregate inspection exit code. Opened work
and pending publication alone are not inspection failures.

`project publish` (including `--continue` and `--abort`) returns `id`, `complete`,
`aborted`, `problem`, and ordered `components`. Each participant contains `name`,
`path`, repository/branch IDs, decimal-string `revision`, operation ID, and `state`:
`planned`, `prepared`, `outcome_pending`, `accepted`, `unchanged`, `rejected`, or
`retired_after_restore`. The parent has path `.` and is last. Exit status is nonzero
while incomplete or when cleanup reports a problem. A successful abort preserves
accepted history and returns `aborted: true`.

`project sync` uses the existing sync/mount report. `project update` returns the
normal `message` confirmation; it stages a pin and does not submit. Ordinary
`status.workflows` adds nullable `project_publication` and gives root-level
recovery hints before suggesting ordinary submit retries.

## Partial-view integration reports

Partial-view integration reports add `outside_view` to each resolution path and
to `status.paths[].resolution`. This flag derives from the saved workspace view.
An applied excluded path represents a saved metadata decision, not a local file.
Unresolved excluded paths block submission just like included conflicts.

Resolution reports also expose the pinned `source` (`branch`, `change`) and
`merge_base`, or null without an active integration. These identify the exact
selection to restart after expanding a view. For cherry-pick, `merge_base` is
the selected change's first-parent revision.
