Returned messages and state
After a client accepts a command, it returns application evidence through an HTTP service advertised during handshake. Ordinary returned messages describe progress, outcomes, events, and telemetry. State reports describe the runtime's current platform-specific world. Both families are authenticated and fenced before they can affect history, correlation, or topology.
Ordinary returned messages
Ordinary return uses kind, not a top-level result type:
{
"message_id": "cmd-7f94-2",
"timestamp_ms": 1784455301800,
"client_id": "browser-main",
"instance_id": "runtime-018f7",
"stream_key": "browser-main/jobs",
"kind": "result",
"refers_to": { "command_id": "cmd-7f94" },
"seq": 2,
"payload": { "title": "Cheetah", "links": 12 }
}
message_id, a positive timestamp, client_id, instance_id, a non-empty stream suffix, and kind are required. A command response also carries refers_to.command_id and a positive, one-based seq.
| Kind | Meaning | Retained in user history | Terminal for an RPC command |
|---|---|---|---|
progress | non-terminal update about current work | yes | no |
result | successful terminal application outcome | yes | yes |
error | terminal denial, validation, handler, or platform failure | yes | yes |
event | unsolicited application event | yes | no |
telemetry | operational measurement on the ordinary envelope | no | no |
A result or progress payload may be a JSON object, explicit null, or absent. Arrays and primitives are invalid at this boundary; wrap them in a named object. An error requires an object with non-empty code and message values and may carry an object-valued details. Reference clients convert an invalid untyped handler result into a correlated terminal invalid_result_payload error instead of emitting a wire-incompatible result.
Sequence and retry identity
message_id identifies one HTTP delivery. Repeating it allows the server to discard an exact transport retry. Within a command response chain, (command_id, seq) identifies one semantic position. Progress and a terminal message cannot both claim the same sequence number.
These checks are scoped by the authenticated principal. History itself uses the canonical stream plus message_id as its append identity. The distinctions matter when retries and reconnects overlap: an HTTP retry should preserve both message_id and seq, while a new progress update advances seq and uses a new message ID.
Deduplication protects ingestion from recording the same return twice. It does not prove that a handler executed only once, and it does not make an external side effect idempotent. Product actions still need their own idempotency and reconciliation rules where duplicate execution would be harmful.
Trusted stream and runtime scope
The command supplies a client-side stream suffix. The returning client repeats that suffix. After authenticating HTTP, the REST role prefixes the trusted principal and obtains the canonical stream. A client must not send a principal-prefixed stream key and cannot select a different tenant by editing the body.
If a replacement runtime is currently registered for the logical client, a return from the old instance is rejected as stale. If there is no current connection, a late return from the original attempted instance can be accepted. This lets an outcome survive socket loss while preventing a known replacement from satisfying the earlier attempt.
Accepted terminal messages are appended to history before a local waiter is resolved. A distributed response notification can then wake readers in another App process. The ingestion field rpc_resolved_locally means only that a waiter in the same REST process was resolved; false can still accompany successful distributed completion. It is not a deployment-wide success flag.
State reports
A state report is a complete platform-specific snapshot rather than an ordinary response:
{
"type": "state_report",
"message_id": "state-2201",
"timestamp_ms": 1784455400000,
"client_id": "browser-main",
"instance_id": "runtime-018f7",
"trigger": "event",
"event_type": "tabs_changed",
"state": { "windows": [] }
}
The required fields are message identity, positive timestamp, client and runtime identities, one allowed trigger, and an object-valued state. type may be omitted by a host that has already selected the family; when present it must be state_report. Allowed triggers are connect, disconnect, event, config_change, periodic, command, and manual. Optional fields include event_type, in_response_to, trace identity, and a non-empty bounded context_epoch.
The REST role retains an accepted report on the principal-scoped {client_id}/state_reports stream. Compatible adapters may also project a browser or console snapshot into context and window registries. Conversion and platform validation happen before deduplication, history append, and topology mutation, so invalid content cannot partially update the current view.
Reports are complete snapshots. A consumer reconciles current truth from the latest accepted snapshot rather than interpreting omitted items as an incremental patch. A valid report from an unknown or disconnected client type may be retained generically without a topology projection when no compatible adapter is available.
The deprecated ownership_change message is rejected with immutable_context_ownership. Context ownership remains fixed for that context lifetime; current membership and platform state are reconciled through reports.
What the HTTP host must do
RestNode is an ingestion service, not a universal web route. The host decides the route path and must:
- authenticate every request independently and derive the same effective principal used at the WebSocket edge;
- distinguish ordinary messages from state reports and call the matching service path;
- enforce network and framework request limits in addition to the service-layer parsed-JSON limit;
- map successful ingestion, duplication, stale-runtime rejection, and validation errors into its own HTTP response contract;
- preserve safe diagnostic context without logging credentials or unbounded malformed bodies.
The default service limit is 1 MiB measured after JSON parsing and re-serialization. It is configurable and can be disabled, but it is not a complete network denial-of-service defense. Configure the proxy and HTTP framework to reject oversized bodies before expensive parsing as well.
When returning a result fails, retry that HTTP delivery with the same identities. Do not rerun the product action merely because the result transport failed.