도구
The `t0` command — machine-readable contract (`t0-cli/1`)
이 페이지는 영어입니다. 하나의 언어로 유지된 사양은 하나의 내용만 말합니다.
t0 is how tools, scripts and agents use T0 Lang outside TTT. Everything
below is held by crates/t0-cli/tests/cli.rs, which runs the real binary.
t0 check <file.tz|.tzi|.tzc|.tzr|.tzp|.tzs> [--manifest host.json] [--json]
t0 build <file.tz> [--manifest host.json] [-o artifact.json] [--json]
t0 package <file.tzi|…> [--manifest host.json] [-o package.json] [--json]
t0 validate <artifact.json> [--manifest host.json] [--json]
t0 run <file.tz | artifact.json> [--manifest host.json] [--grant <permission>]… [--json]
t0 fmt <file.tz | folder> [--check | --write] [--json]
t0 explain <CODE> [--json]
t0 manifest <host.json> [--json]
t0 version [--json]
t0 lsp [--manifest host.json]
t0 component <check|inspect|eval|test|explain|activate|deactivate|rollback> …
t0 bindings <check|pin|inspect|log> <file> …The CLI has no host of its own. run executes against a host that
offers nothing; a program needing a host contract is refused before it
starts (E-CAP-…). --manifest lets check, build and validate
judge a program against a real host's published manifest (TTT's snapshot
is contracts/ttt-host-0.1.json) without that host. Permissions are
granted per invocation with --grant, never read from a file or from the
program.
Packaging for the catalog (package)#
package makes what the script marketplace delivers (docs/marketplace/README.md at the repository root): the program compiled against
--manifest, stripped (t0_ir::strip: no spans, no local names, no source name or hash, private functions and constants renamed
f0…/c0…), and the settings skeleton beside it (t0_syntax::settings::skeleton: the Default config line, the Config block's
@param comments and fields, the marked constants -- no code). With --json and no -o, the result line carries it:
{"type":"result","ok":true,"category":"success",…,
"package":{"format":"t0-package/1","kind":"tzi","artifact":{…},"artifact_sha256":"…","settings":"…","needs":{"contracts":[…],"permissions":[…]}}}Otherwise it is written to -o (default <file>.package.json). The extension says what kind of script it is; a program that does not
compile is refused like check (exit 1, its diagnostics on the lines before). The stripped artifact runs exactly like the unstripped one
(t0-testkit/tests/stripped.rs), and its constants that are settings keep their names, so a host can give them values
(t0_compiler::settings::with_settings).
Components and bindings#
A program that declares a role (component risk …;, language spec §12) is checked, inspected and run by these commands; the files that bind
components to accounts are checked and pinned. Nothing here binds anything: a binding is applied by a running terminal.
t0 component check <file.tz> --manifest host.json compile; the role, entry signature, contracts and Config
t0 component inspect <file.tz | artifact.json> [--manifest m.json] the facts: role, name, version, entry, Config fields, contracts, hashes
t0 component eval <file.tz> --manifest m.json [--config c.json] --input case.json
t0 component test <file.tz> --manifest m.json --cases cases.json
t0 component explain <record.json> a recorded decision or rebalance, in words
t0 component activate | deactivate | rollback … exit 2, category unsupported, "changed": false
t0 bindings check <bindings.json> --manifest m.json every source is the pinned one, compiles, fits its slot and its configuration
t0 bindings pin <bindings.json> --manifest m.json [--check] write the SHA-256 of each source into the file; --check only reports
t0 bindings inspect <bindings.json> what the file binds, in words
t0 bindings log <risk-bindings.log> what the terminal journaledevalandtestrun the entry point once per input with no host at all (a policy calls none), with the runtime's own limits, so what a component answers here is what it answers inside the terminal for the same inputs. The input is an object with the role's own fields --intentandcontextfor a risk policy,snapshotandconstraintsfor a portfolio controller -- in the JSON shapest0_runtime::jsondocuments (money as"5000 USDT",nullfor a value that is not known, an enum as"Name"or{"Name": payload}); a field that is missing, unknown or does not fit is an error that names where.--configis the component'sConfigin the same shapes.testcases are{"config"?, "defaults": {<input>}, "cases": [{"name", "set": {"intent.qty": "100"}, "expect_variant" | "expect", "expect_code"?}]}: each case is the defaults with the dotted paths ofsetassigned. Exit 3 when a case differs; the result listspassed,failedandfailures.examples/61_risk_firewall.cases.jsonandexamples/62_portfolio_allocation.cases.jsonare real ones.pinchanges only the hashes in the file (its order and spacing stay), and only when everything else about the binding checks out: a source that does not compile or fills the wrong slot is not fixed by a new hash, and nothing is written.explainreads the JSON the host writes for a decision (execution::risk::AuditRecord) or a rebalance (RebalanceRecord); those field names are a contract the host's tests hold.- Activation is not offered because this tool has no host: it exits 2 with category
unsupportedand"changed": falserather than report a success nobody confirmed. The terminal appliesrisk-bindings.jsonat startup and journals every change torisk-bindings.log(docs/risk/OPERATIONS.md).
Streams#
| mode | stdout | stderr |
|---|---|---|
| human (default) | what the command produces for the reader: program output (run), the formatted text (fmt), the explanation (explain), the listing (manifest), the version line |
diagnostics, rendered with source excerpts; one status line |
--json |
only JSON, one object per line (below) | nothing a tool needs; only a crash of the process itself would land here |
JSON lines (--json)#
Every line is one JSON object with a "type":
"diagnostic"— every field of the diagnostic (code,severity,message,span,expected,found,capability,notes,help,suggestion), pluslocation(file,line,column,end_line,end_column; 1-based, columns in characters) when it has a position, andcategory(below)."output"— one line the program printed,{"type":"output","line":"…"}(runonly), in order, before the result."result"— exactly one, always last, also when the command could not start.
{"type":"result","format":"t0-cli/1","command":"run","ok":false,
"category":"runtime","exit_code":3,"message":"p.tz:4:11: failed[E-NUM-DIV-ZERO]: …",
"outcome":"failed","failure":{"code":"E-NUM-DIV-ZERO","message":"…","line":4,"column":11,"program_error":false},
"steps":17,"host_calls":0,"output_dropped":0,"elapsed_ms":0,"artifact_sha256":"…",
"needs":{"contracts":[],"permissions":[]}}Fields of every result: format ("t0-cli/1"), command, ok,
category, exit_code, and message when there is one line for people.
Per command:
| command | extra fields |
|---|---|
check |
warnings, errors (when refused), needs {contracts: [{id, version}], permissions: […]}, body_sha256 (when it compiles) |
build |
as check, plus artifact (the path written) |
validate |
ir_version, compiler {name, version}, body_sha256; or errors |
run |
outcome (completed/refused/failed/cancelled), value (completed), failure {code, message, line, column, program_error} (failed only: a cancellation has none), steps, host_calls, output_dropped, peak_memory_bytes (the most bytes the program's values held; null if it never ran), elapsed_ms, artifact_sha256, needs |
fmt |
changed; written (--write); text (neither --check nor --write). On a folder (every .tz under it, so a program and its modules; it needs --check or --write): files (how many), changed (the list of files that were not formatted), refused |
explain |
code, diagnostic_category, text |
manifest |
manifest (the validated manifest itself) |
version |
cli, language, compiler, ir (accepted IR range) |
Exit codes#
| code | meaning |
|---|---|
| 0 | success |
| 1 | refused: the program, artifact or file has problems (diagnostics), fmt --check found it unformatted, or fmt refused to format it |
| 2 | the command could not do its job: wrong usage, an unreadable or unwritable file, an invalid manifest |
| 3 | the program ran and failed (a runtime error, a host failure, or main returning Err) |
| 4 | the execution was cancelled: t0 run received SIGINT (Ctrl-C) or SIGTERM |
Cancellation#
t0 run cancels the execution on the first SIGINT (Ctrl-C) or SIGTERM:
it says so on stderr, the runtime stops at its next safe point (within
1 024 evaluation steps, or when the current host operation returns), and
the command ends with exit code 4 and — with --json — the output lines
already printed followed by exactly one result:
{"type":"result","format":"t0-cli/1","command":"run","ok":false,"category":"cancelled","exit_code":4,"message":"cancelled","outcome":"cancelled", …}A second signal ends the process at once with 130 (no result), for a
program stuck where cancellation is not observed. An execution that stops
on an error after its cancellation was asked for is reported cancelled,
not failed. The CLI has no host, so it starts no backtest jobs; how a
cancelled execution stops its jobs inside TTT is in ADR-0010 §5. Other
commands do not install the handler (they are short; an interrupt ends
them the default way). Tested on Unix with real signals
(tests/cli.rs); on Windows the same handler receives Ctrl-C but no test
sends it.
Categories#
A result's category, and a diagnostic's, is one of:
| category | from | examples |
|---|---|---|
success |
— | |
syntax |
E-SYN-* |
an unterminated string, nesting past 128 levels |
type |
E-RES-*, E-TYPE-*, E-STRAT-* |
unknown name, mismatched types, a signal outside a strategy |
domain |
E-FIN-*, E-TIME-*, E-NUM-* before running |
adding money in two currencies, a literal out of range |
capability |
E-CAP-* |
a contract the host does not offer or the program did not declare |
version |
E-VER-* |
the language or host range not met |
permission |
E-PERM-* |
a permission not granted |
ir |
E-IR-* |
an artifact that fails validation (tampered, malformed, built for other contracts) |
host |
E-HOST-* |
the host failed or broke its contract |
runtime |
anything else while running, and main returning Err |
E-NUM-DIV-ZERO, E-RT-INDEX, E-RT-FUEL, E-RT-LIMIT-MEMORY |
build |
— | the artifact could not be written (exit 2) |
format |
— | fmt --check found differences; the formatter refused for a reason other than lexing |
cancelled |
— | exit 4 |
usage, io |
— | exit 2 |
A refused command takes the category of its first error. Tools should
branch on category and report code: categories are few and stable,
codes say exactly what happened. t0_diag::category is the one mapping;
TTT's editor shows the same categories.
Compatibility#
t0-cli/1 changes only by adding fields or commands. Removing or renaming
a field, changing a category, or changing an exit code makes it
t0-cli/2. Diagnostic codes are stable
(docs/versioning/compatibility-policy.md).