Machine output¶
Unobin commands use versioned JSON or Unobin output when --format json or --format unobin is selected. Most commands write one complete document. Apply streams one complete record per line as work progresses; in JSON, this is JSON Lines.
Each actual JSON document or stream record is compact and followed by one newline. Examples in this document may be indented for readability. Actual Unobin output is also one compact literal per line.
Selecting an output format¶
Standard commands accept --format text|json|unobin; text is the default. Developer and compiled print-graph commands additionally accept dot. DOT is a payload format, not a versioned machine contract, and its application errors are text on stderr.
An unsupported format is reported as text on stderr, for example:
Graph errors also list dot.
The following payload and protocol commands use command-specific output:
unobin fmtwrites formatted source, changed paths, or files.factory schema templatewrites a.ubstack skeleton or a requested file.factory state pullwrites a raw decrypted JSON snapshot that may contain sensitive values.unobin lspowns stdio for the language-server protocol.- Help remains text, and shell completion retains its protocol output.
For those commands, --format produces a plain-text unknown-flag error.
When machine output takes effect¶
Arguments and flags are parsed and validated before the format selected by --format takes effect. Help, usage, unknown commands, unknown or malformed flags, missing flag values, missing required flags, and positional-argument errors are therefore text. Help goes to stdout. Invocation errors go to stderr. Shell completion retains its protocol output.
After invocation validation succeeds, source, state, provider, backend, resolver, dependency, and external-tool failures use the selected machine contract.
| Situation | stdout | stderr | Exit |
|---|---|---|---|
| Help or successful shell completion | Text or completion protocol | Empty | 0 |
| Invocation error | Empty unless the invocation is a completion request | Bare text | 1 |
| Successful text command | Result or payload | Warnings, notices, and progress | 0 |
| Failed text command | Any command-specific partial output | Bare error and diagnostics | 1 |
| Successful single-document machine command | One result document | Empty | 0 |
| Negative machine result | One result with ok: false |
Empty | 1 |
| Machine operation failure | One command-error document |
Empty | 1 |
| Machine apply | A versioned JSON Lines or Unobin record stream | Empty | 0 or 1 |
| Response encoding or stdout write failure | May be malformed or truncated | Bare response-channel error if the process survives | 1 or signal status |
Plain-text invocation errors contain the error message followed by one newline.
After invocation validation succeeds, machine output uses stdout. Response encoding or stdout write failures may report a bare response-channel error on stderr if the process survives. A closed stdout can also terminate the process through SIGPIPE before stderr is written.
Encoding and values¶
Every document and record starts with kind and format-version. All contracts in this reference currently use format version 1. Single-document results have no timestamps.
JSON documents are UTF-8, compact JSON with HTML escaping disabled and one trailing newline. They are encoded completely before the first write. Apply JSON is JSON Lines: one complete compact object per line, no blank lines, and a trailing newline after every record.
Unobin documents and stream records use the same logical fields as JSON. Each is one compact literal followed by one newline.
Machine values may contain:
- null, booleans, strings, and finite numbers;
- arrays and slices of permitted values;
- maps with string keys and permitted values;
- response objects composed recursively from those values;
- RFC 3339 Nano timestamps.
Cycles, non-finite numbers, non-string map keys, duplicate keys after invalid UTF-8 replacement, functions, channels, complex numbers, json.Number, raw durations in dynamic values, and format-specific custom marshalers are rejected. Invalid UTF-8 is replaced with the Unicode replacement character in both formats.
Required collection fields are always arrays or objects, never null. This applies to diagnostics, files, dependencies, inputs, outputs, nodes, edges, state-moves, steps, replacement-reasons, depends-on, sensitive, sensitive-inputs, sensitive-outputs, entries, snapshots, and mismatches.
In the contract tables below, every field is required unless it is explicitly marked "omitted when absent." A field whose type includes null is still required and is encoded as null when absent.
Deterministic ordering¶
Unobin sorts string map keys, as does Go's JSON encoder. Other collections use these rules:
- dependencies and verification mismatches by id;
- composed file changes by path, then action;
- diagnostics by path, position, severity, code, message, hint, and compatibility details;
- graph nodes by address and edges by
from, thento; - schema inputs and outputs in declaration order;
- plan steps by address and state moves in semantic execution order;
- state entries by address and snapshots in backend chronological order;
- sensitivity names,
depends-on, and replace triggers lexically; - apply records by
sequence, which is authoritative over timestamps.
Compatibility¶
Each kind has an independent format-version sequence. Consumers must ignore unknown fields. Apply consumers must also ignore unknown nonterminal record kinds, but must recognize the last record as apply-result or apply-error. An unknown terminal kind is an unsupported stream contract.
Adding an optional field does not increment a version. Adding a nonterminal apply kind starts that kind at version 1 without changing other kinds. Removing or renaming a field, changing its type, nullability, semantics, or required ordering, making an optional field required, or changing an enum meaning increments the containing kind. Adding an enum value also increments the containing kind unless that field explicitly accepts unknown values. An incompatible shared nested-value change increments every kind that contains it. Changing terminal apply kinds or the terminal-last guarantee increments both terminal kinds.
Paths and sensitive values¶
Dedicated machine path fields always use forward slashes.
- A user-supplied relative path retains its lexically cleaned relative spelling.
- A user-supplied absolute path remains absolute.
- A file below a user-supplied directory uses that display path plus its suffix.
- Discovered project files are relative to the project.
- Dependency source uses logical dependency display paths.
- Cache roots, temporary directories, module-cache paths, and other discovered local absolute paths are not exposed in dedicated fields or known internal messages.
- An unmappable absolute path is reduced to its base name in a diagnostic path.
File-change paths are relative to the command working directory unless the corresponding destination argument was absolute.
These guarantees apply to dedicated fields and messages produced by Unobin. Opaque provider, resolver, operating-system, and external-tool messages remain verbatim except for known workspace, project, cache, and temporary prefixes. They may contain paths or sensitive text.
Asset path and content values remain logical in plans and state. Machine values never replace them with a source path, cache path, or embedded content. Consumers must treat their encoded strings as opaque references. Text plans render them as source-like values such as <asset.lambda.path> and <asset.lambda['main.go'].content>.
Known sensitive values are replaced with exactly <sensitive> in apply outputs, output results, state entry inputs and outputs, and text plans. Plan summaries do not include values or sensitivity lists. Unobin does not claim to redact arbitrary provider or external-tool error strings; those diagnostic messages remain verbatim.
Shared nested values¶
Diagnostic¶
| Field | Type | Meaning |
|---|---|---|
code |
string | Stable code where one is defined; diagnostic codes are otherwise extensible. |
severity |
enum | error, warning, or info. |
message |
string | Prefix-free diagnostic message. |
hint |
string | Omitted when absent. |
path |
string | Omitted when absent. |
span |
span | Omitted when absent. |
library-compatibility |
library compatibility details | Omitted when absent. |
A span has required start and optional, exclusive end positions. Each position has required integer line, column, and offset fields. Line and column are one-based; column counts bytes. Offset is zero-based bytes from the input start.
Source error codes initially map to unobin.parse, unobin.lex, unobin.schema, unobin.type, unobin.resolve, and the fallback unobin.error.
Library compatibility details contain optional fields. Fields are omitted when unavailable. API entries and requirement steps retain their deterministic order.
| Field | Type | Meaning |
|---|---|---|
dependency, package, module-path |
string | Owning project ID, Go package import path, and Go module path. |
version, commit |
string | Inspected release and source commit. Local source has no commit. |
actual-version, actual-required-api |
string | Conflicting module version or declaration found during build verification. |
candidate-version, query, floor |
string | Trial floor, original query, and existing root requirement. |
required-api, implemented-apis |
string, string array | Authored requirement and toolchain API entries. |
unobin-version, suggested-unobin-version |
string | Running CLI version and optional publisher upgrade hint. |
required-core-version, minimum-go-version |
string | Direct core module floor and minimum Go version. |
replacement, actual-replacement |
string | Effective and conflicting local source directories, with normal path normalization. |
requirement-chain |
requirement step array | Ordered requirements from the root to the conflict. |
A requirement step has string fields dependency, requires, and minimum-version, and an optional version. The root requirement uses dependency: "project.ub"; later steps identify the project and version that declared each requirement.
File change¶
| Field | Type | Meaning |
|---|---|---|
path |
string | Public path of the attempted mutation. |
action |
enum | created, updated, removed, or unchanged. |
Actions compare the path before its first command mutation with its final observed state, including partial failure. Repeated mutations are composed to at most one entry per path. An absent path created and then removed has no final effect and is omitted.
Factory identity¶
| Field | Type | Meaning |
|---|---|---|
name |
string | Factory name. |
version |
string | Factory version. |
content-revision |
string, except nullable in compile-result |
Compiled content identity. |
library-path |
string or null | Public factory library path, if configured. |
Binding¶
| Field | Type | Meaning |
|---|---|---|
library-path |
string or null | Public library path. |
alias |
string | Source import alias. |
export |
string | Selected library export. |
Target¶
A target has required string fields path and type. Target type is one of factory, library, stack, project, project-lock, or directory.
Single-document contracts¶
Every successful single-document result has a required diagnostics array. A negative result such as ok: false is still a normal result document, not a command-error.
Command error¶
Any single-document command can emit kind command-error version 1.
| Field | Type | Meaning |
|---|---|---|
command |
string | Command path without the executable name. |
code |
enum | Failure class. |
message |
string | Stable command-level summary. |
diagnostics |
diagnostic array | Every collected and converted cause. |
files |
file-change array | Known effects before the failure. |
Command error codes are unobin.command.invalid-args, unobin.command.io, unobin.command.failed, and unobin.command.stdout-conflict.
Developer command results¶
version¶
Produced by unobin version. Fields are name (unobin), version, and diagnostics.
check-result¶
Produced by unobin check. Fields are ok, target, and diagnostics. Error diagnostics make ok false and exit 1; warnings do not. Failure before the target can be identified uses command-error.
compile-result¶
Produced by unobin compile.
| Field | Type | Meaning |
|---|---|---|
factory |
compile factory identity | content-revision is null without --build. |
source |
object | Required path and project-dir strings. |
output |
object | Required dir, main-go, go-mod, built, and binary; optional assets; binary is string or null. |
files |
file-change array | Composed effects for generated Go and UB files, go.sum, and the binary. |
diagnostics |
diagnostic array | Includes captured Go tool output in machine mode. |
binary and content-revision are non-null only after a build. Machine compile rejects -o - with unobin.command.stdout-conflict. External-tool stdout and stderr are each bounded to 1 MiB and reported as diagnostics rather than written outside the document.
output.assets is the public path to factory.assets and is omitted when the compiled source has no captured assets. Changes to the sidecar also appear in files, including its removal when a later compile has no assets.
Dependency results¶
| Kind | Command | Required fields after the common header |
|---|---|---|
dependency-list |
unobin deps list |
dependencies, diagnostics |
dependency-sync-result |
unobin deps sync |
project-file, lock-file, direct, indirect, selected, files, diagnostics |
dependency-get-result |
unobin deps get |
dependency, version, indirect, project-file, lock-file, direct, selected, files, diagnostics |
dependency-verify-result |
unobin deps verify |
ok, checked, mismatches, diagnostics |
dependency-cache-clean-result |
unobin deps clean |
removed, diagnostics |
Each dependency entry has required id, kind, version, and indirect fields.
The get result's version is the requested or chosen floor. Optional selected-version records a different final release selected by the dependency graph. Rejected automatic or prefix candidates appear as info diagnostics with their reason code and candidate-version, including when fallback succeeds. Compatibility failures use command-error, leave dependency files unchanged, and include the conflicting selected source and its requirement chain when available. The diagnostic array has the standard normalized order; text notices follow candidate trial order. Local replacement and core replacement notices describe the effective source checked by get or sync. These optional additions retain format version 1.
Dependency kind is ub or go. A verification mismatch has required id, expected-hash, actual-hash, and message fields. Mismatches produce ok: false and exit 1; fetch or I/O failure uses command-error. removed is true only when the dependency cache existed before cleaning.
Generator results¶
| Kind | Command | Required fields after the common header |
|---|---|---|
factory-generation-result |
unobin generate factory |
output-dir, files, diagnostics |
ub-library-generation-result |
unobin generate ublibrary |
output-dir, type, files, diagnostics |
go-library-generation-result |
unobin generate golibrary |
output-dir, module-path, provider, resources, data-sources, files, diagnostics |
provider preserves the exact requested provider, including an organization prefix. Partial generation failures attach known effects to command-error.files.
Shared graph result¶
Developer and compiled print-graph produce kind graph. Fields are name, nodes, edges, and diagnostics.
Each node has required address, category, binding, and composite fields. binding is a binding object or null. Category is resource, data-source, action, output, or library-config. Each edge has required from and to strings. An edge may target an input.* root that is absent from nodes.
Compiled factory results¶
Identity, validation, and schema¶
| Kind | Command | Required fields after the common header |
|---|---|---|
factory-version |
factory version |
factory, diagnostics |
validation-result |
factory validate |
ok, target, diagnostics |
schema |
factory schema show |
factory, inputs, outputs, diagnostics |
Validation error diagnostics make ok false and exit 1; warnings do not. Each schema input has required name, type, default, description, and sensitive fields. default is canonical Unobin expression text or null. Each schema output has required name, description, and sensitive fields. Type is canonical Unobin type-expression text. Missing descriptions are empty strings.
Bare factory schema prints help and exits 0. schema template remains the payload command described above.
plan-summary¶
Produced by factory plan.
| Field | Type | Meaning |
|---|---|---|
factory |
factory identity | Compiled factory identity. |
stack |
string | Stack name. |
plan-digest |
string or null | sha256: digest of a written sealed artifact. |
file |
file change or null | Plan artifact effect. |
state-rev |
string or null | State revision used by the plan. |
parallelism |
integer | Effective apply parallelism. |
destroy |
boolean | Whether this is a destroy plan. |
summary |
object | Required count for every decision. |
state-moves |
array | Required {from, to} objects in execution order. |
steps |
array | Public plan step summaries. |
diagnostics |
diagnostic array | Collected notices and warnings. |
The summary has required integer fields create, read, update, replace, destroy, rerun, skip, no-op, and eval. These are also all decision enum values.
When a resource has multiple permitted outcomes, summary.conditional counts it instead of the individual decision counts. This field is omitted when zero.
Each step has required address, category, decision, composite, drift, gone, replacement-reasons, and deferred-config. deferred-config is string or null. Step category uses the graph category enum. A summary contains no input, output, prior, or observed values and no sensitivity lists. Without -o, both plan-digest and file are null.
A step with unresolved lifecycle comparisons also has allowed-decisions. Its decision is the conservative operation used for dependency planning; apply selects one of the permitted decisions after dependencies finish. replacement-reasons contains confirmed reasons. Optional pending-replacement-reasons lists checks awaiting resolved inputs, configuration, or an observation deferred by pending configuration. Apply events report the resolved operation. These fields use the existing version 2 format.
Refresh and output¶
| Kind | Command | Required fields after the common header |
|---|---|---|
refresh-result |
factory refresh |
factory, stack, ok, refreshed, removed, state-rev, diagnostics |
outputs |
factory output |
factory, stack, outputs, sensitive, diagnostics |
output |
factory output NAME |
factory, stack, name, value, sensitive, diagnostics |
refresh-result.state-rev is string or null. If refresh wrote state and then failed, it reports ok: false, completed counts, the latest revision, error diagnostics, and exits 1. Before any observed write, failure uses command-error. Sensitive output values are masked and their names are listed lexically.
Pin and state inspection¶
| Kind | Command | Required fields after the common header |
|---|---|---|
pin-result |
factory pin |
stack, action, file, factory, diagnostics |
state-list |
factory state list |
factory, stack, state-rev, entries, diagnostics |
state-entry |
factory state show |
factory, stack, state-rev, entry, diagnostics |
state-snapshots |
factory state snapshots list |
factory, stack, current, snapshots, diagnostics |
Pin action is added-factory-block, added-pin-block, added-supported-versions, appended-entry, or already-pinned. The last action uses an unchanged file change.
state-list and state-entry use format version 2. Their entries have required address, category, composite, and binding fields. Category is resource, data-source, or action; composite is a boolean that marks a call boundary independently of category. Version 1 used entry-type for this distinction. Binding is required for valid current entries. state-list.state-rev and state-snapshots.current are string or null. state-snapshots remains at format version 1.
A detailed state-entry.entry adds required schema-version, trigger-hash, inputs, outputs, depends-on, sensitive-inputs, and sensitive-outputs. trigger-hash is string or null. Each snapshot has required revision and current fields. Snapshots remain in backend chronological order.
State mutation¶
| Kind | Command | Required fields after the common header |
|---|---|---|
state-move-result |
factory state move |
factory, stack, ok, from, to, moved, state-rev, diagnostics |
state-remove-result |
factory state remove |
factory, stack, ok, address, state-rev, diagnostics |
state-gc-result |
factory state snapshots gc |
factory, stack, ok, deleted, kept, current, failed-revision, diagnostics |
state-force-unlock-result |
factory state force-unlock |
factory, stack, unlocked, diagnostics |
state-gc-result.current and failed-revision are string or null. A mutation that writes a new current snapshot and then fails, including during unlock, reports its normal result kind with ok: false, completed effects, the latest revision, error diagnostics, and exit 1. A failure before a new revision is observed uses command-error. GC similarly retains completed deletion counts and the failed revision.
Apply stream¶
factory apply --format json emits JSON Lines. Unobin apply emits the equivalent one-literal-per-line stream. Every record has required kind, format-version, sequence, and timestamp fields. Sequence starts at 1 and increments after each complete record write. Timestamp is RFC 3339 Nano UTC and is sampled immediately before encoding; sequence defines order.
The stream order is:
command-diagnostic*
apply-ui?
(apply-event | command-diagnostic)*
apply-output*
apply-result | apply-error
Startup diagnostics precede the optional UI record. First-interrupt and browser-open diagnostics are asynchronous and may follow runtime events. Their sequence records their actual order. No diagnostic or event follows the first output. Failure emits no outputs. Runtime failure events go to the UI but are not encoded as apply-event; the terminal apply-error represents them.
command-diagnostic¶
Adds one required diagnostic object to the common stream fields.
apply-ui¶
Adds one required url string. It appears at most once. --ui is valid in every format. Browser-open failure or the five-second timeout emits a diagnostic with code unobin.ui.browser-open and apply continues; UI server startup failure is a setup-stage apply-error.
apply-event¶
Adds required stage, decision, and address. Stage is start or done. elapsed is omitted for start and required for done. Decision uses the plan decision enum. Composite boundaries, outputs, no-op resources, and skipped actions retain the command's silent-event filtering.
apply-output¶
Adds required name, value, and sensitive. Records sort by name. All effective values are validated before the first output is written. A sensitive value is replaced before validation, so its original value is never inspected by the machine encoder. An apply with no outputs emits no output records.
apply-result¶
This success terminal adds required started-at, finished-at, elapsed, state-rev, and output-count. The timestamps are RFC 3339 Nano UTC; elapsed uses Unobin's short duration rendering. output-count equals the number of preceding apply-output records.
apply-error¶
This failure terminal has the following fields:
| Field | Type | Meaning |
|---|---|---|
started-at |
timestamp | Apply stream start. |
finished-at |
timestamp | Terminal-record time. |
elapsed |
duration string | Total elapsed time. |
stage |
enum | setup, execute, or finalize. |
code |
enum | Stable apply failure code. |
message |
string | Stable operation summary. |
state-rev |
string or null | Latest observable current revision. |
diagnostics |
diagnostic array | Converted underlying causes. |
address |
string | Omitted when unavailable. |
decision |
decision enum | Omitted when unavailable. |
library |
string | Public library path; omitted when unavailable. |
skipped |
integer | Required, including zero, when a runtime step error supplies counts; otherwise omitted. |
succeeded |
integer | Required, including zero, when a runtime step error supplies counts; otherwise omitted. |
Apply error codes are unobin.apply.setup-failed, unobin.apply.step-failed, unobin.apply.finalize-failed, and unobin.apply.interrupted. Setup failures omit step fields. Execution step failures include available step fields. Finalization includes output evaluation, final state persistence, and lock release.
Interrupts and terminal records¶
The first SIGINT requests a scheduler drain and emits unobin.apply.drain-requested with this prefix-free diagnostic message:
After 60 seconds, a second SIGINT, or any SIGTERM, cancellation produces terminal code unobin.apply.interrupted. A handled interrupt exits 1 after that terminal record. Abrupt process termination uses the platform signal status and may not write a terminal record.
When response encoding and stdout writes succeed, an apply stream contains exactly one recognized apply-result or apply-error, and it is the last record. An encoding failure may be replaced by a primitive finalization apply-error if stdout remains usable. A stdout write failure cannot safely emit another record.
Consumers must treat malformed output, a partial line, or a stream without a recognized terminal record as a transport failure. A transport failure does not prove that apply had no effects. Inspect current state and compute a new plan before applying again; do not blindly retry the same plan.
Exit status¶
- 0: successful commands and help, including results with warnings.
- 1: invocation errors, text application failures,
command-error, negative results such asok: false, handled interrupts, andapply-error. - Platform signal status: abrupt termination, including default SIGPIPE behavior.
There are no additional numeric status classes. Machine consumers classify nonzero results by kind, code, and result fields.