runa/docs/protocol.md

115 lines
5.4 KiB
Markdown
Raw Normal View History

# Runa Protocol
`runa-protocol` defines versioned request, response, and event data contracts
for clients and the `runa-server` dispatcher. It is independent of transport
and runtime implementation. `runa-server` accepts JSON Lines over stdio or,
when started with `--listen`, over TCP bound to an IPv4 or IPv6 loopback
address. The TCP listener is unauthenticated and is intended only for trusted
local development.
## Version 3
Every request and response carries `version: 3`. Requests also carry a
non-empty `request_id` used to correlate the response. A server must reject
unsupported versions with `unsupported_version` rather than interpreting a
request using a different contract. Events carry the protocol version and do
not have a request ID.
Requests have a tagged command object:
```json
{
"version": 3,
"request_id": "req-1",
"command": {
"operation": "send",
"parameters": {
"actor_ref": "xK8_pQ2-Lm5_012345678",
"message": {
"type": "record",
"data": {
"op": { "type": "string", "data": "send" },
"value": { "type": "integer", "data": 9 },
"target": { "type": "actor_ref", "data": "Hj2_9sM0-pQ4_7aBcD3eF" }
}
}
}
}
}
```
The version 3 operations are behavior publication, rollback, lookup, source
retrieval, actor spawn, send, tick, inspect, actor listing, event retrieval,
stop, restart, pause, resume, cancellation, and behavior installation.
Migration closures are intentionally excluded: they are host callbacks, not
portable wire values.
Responses echo the request ID and carry either a typed reply or a protocol
error. Events use a separate versioned envelope. Actor and behavior snapshots
are wire DTOs, not serialized runtime objects; implementation identities are
represented by their stable display strings. Public actor identity is a
21-character URL-safe NanoID string carried in `actor_ref`. The server maps known service
failures to the defined error codes and otherwise uses `runtime_failure`
rather than claiming a more specific category.
### Portable values
Spawn state, send messages, snapshot state/mailbox/reservations, and migration
event states use the same tagged value shape:
| Value | JSON |
| --- | --- |
| Null | `{"type":"null"}` |
| Boolean | `{"type":"bool","data":true}` |
| Signed 64-bit integer | `{"type":"integer","data":42}` |
| String | `{"type":"string","data":"hello"}` |
| List | `{"type":"list","data":[<value>, ...]}` |
| Record | `{"type":"record","data":{"key":<value>, ...}}` |
| Actor reference | `{"type":"actor_ref","data":"<21-character-NanoID>"}` |
References are distinct from strings. Their shape is validated even when
nested; existence is checked when a reference is used as a send destination,
not merely stored as data. The old message envelope and protocol v2 are not
accepted. The REPL's natural JSON and `$actor`/`$record` conveniences are
client syntax, not wire encodings.
Each state/message permits a maximum nesting depth of 32 (root depth 0),
4,096 nodes, and 1 MiB of UTF-8 string/key bytes. Requests are also subject to
the transport's 1 MiB total line limit, including tags and envelope.
Unsupported value tags, malformed references, floats, and out-of-range integers
fail decoding; well-formed requests exceeding value limits receive
`invalid_request`. Invalid Rune transition output fails the actor without
acknowledging the reserved message, changing committed state, or enqueueing
any of its sends.
`get_events` drains and returns the events currently accumulated by this
single-worker image. It is not a durable subscription or replayable event log.
Both transports limit each request line to 1 MiB before parsing. Malformed
JSON or an oversized line terminates that stream with an error; no protocol
response is emitted when a request cannot be decoded. Stdio EOF terminates the
process, while TCP EOF closes only that client connection. The TCP listener
allows up to 16 simultaneous clients and serializes each dispatch through the
same image. TCP response payloads are capped at 1 MiB, with at most one
response queued per client. Every local client can use the full development
API; loopback binding is not authentication or authorization. Remote access
must not be enabled until appropriate authentication and authorization exist.
The `runa-client` crate launches the stdio server locally and validates
response protocol versions and request IDs. Its `TcpClient` also connects to
loopback endpoints using the same bounded framing and correlation checks.
Connection establishment has a 5-second timeout; reads and writes have
30-second timeouts. A failed TCP request invalidates that client connection.
No automatic retry is performed: loss of a response does not imply that the
operation failed to commit.
The interactive REPL uses TCP and never owns the image. Disconnecting clients
does not stop actors or reset behavior revisions; reconnecting clients can
retrieve them through `list_actors` and `get_behaviors`. The TCP executable
publishes the bundled `counter` behavior on image startup, not on connection.
The stdio executable continues to start with an empty image.
The current validation checks the protocol version, request ID, actor-reference
shape, structured-value limits, positive revisions where required, non-empty behavior names, mailbox
capacity, and instruction budget. Behavior-name grammar, actor existence,
current behavior revisions, mailbox capacity at send time, and all runtime
invariants remain authoritative at the application-service boundary.