# The Context — A shared context that gets passed between operations In [Thanks, Computer](https://www.thanks.computer) operations are glued together by passing a JSON `context`. > Read JSON. Write JSON. Merge JSON. We coordinate across operations via the context, a serialized state of the event flow at a point in time. Because every [operation](../resonators.md) of the same step runs in parallel, they all start from the same step input as JSON (an operation's own `SELECT` clause can then narrow what it receives). When operations finish, they emit JSON as their "answer", which gets merged into the context. Because of this, each operation needs to be careful about the namespace it uses, as **two operations running at the same step could clobber each other's responses if they write to the same part of the context tree**. You'll find yourself creating merge operations that merge together previous steps responses, occasionally, and that's totally ok--it's the trade-off we're making. ## Private Context By convention, anything starting with a `_` is considered private and won't be returned in the final result under normal production paths. Subsequent operations will be able to see the entire context, even those branches that start with a `_`. An operation that shouldn't receive internal branches restricts **its own** input with the `SELECT` [txcl](./txcl/txcl.md) clause — with `SELECT`, the operation is dispatched only the branches it selected (plus `_ts` and the runtime identity stamp). ## System Context The `_txc` branch is for system-related data, such as identity and routing information. As syntactic sugar you can use `@` which is a shorthand for `_txc.` Note that this shorthand is for chassis `txcl`, operations must still return as `_txc` in their response. > `@src` and `_txc.src` are equivalents. ### Identity and routing data: | Field | Meaning | |---|---| | `@src` | The inlet that started the run: `http`, `lmtp`, `cron`, `tcp`, `scheduled`, `source`, `ipp`, `state`, … | | `@rid` | Request id (trace correlation) | | `@client.ip` | The client's address, on every run a network head starts (web, websocket, the DAV heads, IPP, IMAP lanes). The socket peer — or, behind [`--web-trusted-proxies`](./serve.md#behind-a-reverse-proxy-whose-address-is-it), the client your proxies recorded. Read-only and chassis-stamped: key a rate limit on this, never on `@web.req.headers.X-Forwarded-For`, which is whatever the client sent | | `@tenant` / `@stack` | Resolved by [ingress](../routing.md); pinned per request | | `@ingress` / `@hostname_verified` | Matched ingress key / ownership-verification bit | | `@op` / `@step` | The firing op's identity and scope (stamped on dispatched envelopes) | | `@principal.{id,kind,credential}` | Who the request acts as, when someone signed in: the principal a head verified a [credential](./users.md#signing-in) for (`pony:paris`, `user:usr_…`), and the credential's id. Present only on runs the IMAP, CalDAV, CardDAV and IPP heads start (a print job's run acts as whoever printed it); read-only — a copy of what the chassis pinned, never taken from the request | ### Per-head request data: | Head | Namespace highlights | |---|---| | web | `@web.req.method`, `@web.req.url.{path,hostname,port,full,query..0,query.raw}`, `@web.req.headers..0` (arrays), `@web.req.cookies.*`, `@web.req.body` (base64), `@web.req.host`, `@web.req.proto` | | lmtp | `@lmtp.rcpt[]`, `@lmtp.msg.{subject,text,html,from[].addr,to[],headers.*,attachments[],raw}` (`text`/`html` are the parsed bodies; `attachments[]` entries are `{name,type,size,sha256,content,inline}`, attached parts first; `raw` is the b64 original), `@lmtp.listener`; spam verdict under `@mail.spam.{score,verdict}` when an upstream Rspamd stamped it | | cron | `@cron.job`, `@cron.tenant` | | scheduled | `@scheduled.payload.*`, `@scheduled.idempotency_key`, `@scheduled.event_id`, `@scheduled.fired_at` — a due event from `txco://schedule` | | grant | `@grant.{kind,name,verb,sandbox,env,run,generation,grant,via}`, `@grant.principal.{id,kind}`, `@grant.node.class`, `@grant.secret.{pull,version,scope}`, `@grant.checks.{exists,allowlist,standing,pull,budget}`, `@grant.proposed.{allow,reason}` — a request made with a run grant, read-only; the stack answers in `@grant.res.{allow,reason,hold}`; see [the grant inlet](./protocols/grant.md) | | state | `@state.{machine,id,from,to,version,event_id,attempt}`, `@state.cause.{source,stack,trace,run}` — a committed state transition, read-only; see [state](./protocols/state.md) | | source | `@source.msg.*` (same shape as `@lmtp.msg.*`), `@source.id`, `@source.key`, `@source.stack`, `@source.meta.{uid,flags}` — a message pulled from a watched remote mailbox | | ipp | `@ipp.printer` (the label after `/p/` — the operation selector), `@ipp.job_id`, `@ipp.job_number`, `@ipp.job_name`, `@ipp.requesting_user` (client-claimed, untrusted), `@ipp.document.{sha256,size,format,name}`, `@ipp.host`, `@ipp.printer_uri`, `@ipp.submitted_at`, `@ipp.attempt`, `@ipp.node` — all read-only; the document is a blob reference (`txco://blob/put from_sha` / `txco://blob/get sha256`), never bytes, and there is no `@ipp.res` | | tcp | `@tcp.listener`, `@tcp.inlet`, `@tcp.host` (routing hostname from SNI), `@tcp.tls.{enabled,sni,alpn,version}`, `@tcp.local.{ip,port}`, `@tcp.remote.port`; the stack answers in `@tcp.res.{write,action}` | ## Flow control Once operations results are merged, the chassis looks to see if flow control should be altered. By default the chassis moves from the current step, to the next step in the stack, noting that steps do not need to be sequential and can be sparse. (eg: step 2 to step 200). Operations may set these in their response JSON which will effect the flow. ```jsonc # stop the execution, return the context as is { _txc: { halt: true } } ``` | Field | Effect | |---|---| | `_txc.halt = true` | Terminate after this scope's merge; return the document | | `_txc.goto = "stack/0"` or `"200"` | Jump to a stage (bare number = current stack); `EXEC "goto://stack/0"` writes the same | | `_txc.ttl = N` | Lower (never raise) the remaining hop budget ([fuel](./fuel.md)) | | `_txc.web.res.status` | HTTP response status | | `_txc.web.res.headers.` | Response headers (arrays) | | `_txc.web.res.body` | Response body (base64); set in a non-terminal scope it streams | | `_txc.lmtp.res.{code,msg,recipients[]}` | SMTP verdict ([lmtp](./protocols/lmtp.md)) | Everything else under `_txc.*` is chassis-owned — writes to reserved fields (`tenant`, `fuel_used`, `computed.*`, …) are rejected. Payload fields (no `_txc.` prefix) are always available on the context (a rule's own `SELECT` narrows only what its op is dispatched, never the context itself); fields starting with `_` are dropped from the final answer by convention.