Ir al contenido

Herramientas

The `t0` command — machine-readable contract (`t0-cli/1`)

Estas páginas están en inglés. Una especificación en un solo idioma dice una sola cosa.

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 journaled
  • eval and test run 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 -- intent and context for a risk policy, snapshot and constraints for a portfolio controller -- in the JSON shapes t0_runtime::json documents (money as "5000 USDT", null for 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. --config is the component's Config in the same shapes.
  • test cases are {"config"?, "defaults": {<input>}, "cases": [{"name", "set": {"intent.qty": "100"}, "expect_variant" | "expect", "expect_code"?}]}: each case is the defaults with the dotted paths of set assigned. Exit 3 when a case differs; the result lists passed, failed and failures. examples/61_risk_firewall.cases.json and examples/62_portfolio_allocation.cases.json are real ones.
  • pin changes 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.
  • explain reads 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 unsupported and "changed": false rather than report a success nobody confirmed. The terminal applies risk-bindings.json at startup and journals every change to risk-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), plus location (file, line, column, end_line, end_column; 1-based, columns in characters) when it has a position, and category (below).
  • "output" — one line the program printed, {"type":"output","line":"…"} (run only), 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).