Skip to content

Actor transport protocol

wabe-disk/actor implements this protocol on one broker-to-Worker WebSocket per generation. Browser clients exchange ordinary text/binary application messages with wabe Cloud; they do not send these envelopes. The endpoint runs on the developer’s host. Register its public HTTPS URL through the Actor API. The endpoint accepts the broker’s WebSocket upgrade through Actor.upgradeEndpoint(request, endpointSecret) or an implementation of this contract. On each activation the broker connects directly to a validated public address and verifies TLS for the registered hostname.

The direct upgrade includes X-WabeCloud-Endpoint-Secret and X-WabeCloud-Activation. Compare the secret against the handler’s configured value before decoding or trusting the context. The context is unpadded base64url of UTF-8 JSON with wabecloud and wabedisk properties. wabecloud has version:1, actorId, invocationId, instanceName and actor:{instanceId,generation,protocol,clientFrameBytes}. wabedisk carries the SDK’s existing version 1 disk context and rotating session credentials. Treat both upgrade headers as sensitive.

All envelopes are JSON text frames with type and integer generation. Generation must equal the current trusted actor context. Reserved routing/context data comes from the broker, not from client payloads. Text application payloads are strings with binary:false; binary application payloads use standard base64 with binary:true. The application-frame ceiling is clientFrameBytes, initially 65,536 decoded bytes. HTTP chunks remain at most 65,536 bytes; the internal envelope ceiling is 163,840 UTF-8 bytes including encoding overhead.

After receiving 101, the broker sends:

{"type":"init","generation":1,"protocol":"wabecloud.actor.v1"}

The SDK constructs one handler, completes onStart and sends {"type":"ready","generation":1}. The broker validates current ownership/generation, marks it running and sends ready-ack. Application events wait for that barrier. Heartbeats and transport control can run while initialization awaits disk I/O.

Direction/type Additional fields and effect
Broker connect connectionId, request:{url,method,headers}; headers are string pairs from the sanitized bodyless client handshake
Worker accept connectionId; permits completion of that pending client’s 101
Worker reject connectionId, HTTP status; rejects the pending admission without exposing application error details
Broker message connectionId, payload, binary; identifies the actual sender independently of payload content
Broker disconnect connectionId, observed code, reason; removes only that physical connection
Worker send op, connectionId, payload, binary; targets that connection, including a bounded welcome queue during pending admission
Worker broadcast op, payload, binary, optional except ID array; targets the currently accepted connection set
Worker close op, connectionId, code, reason; closes one connection
Broker ack matching op, Boolean ok; confirms operation/queue acceptance, not delivery

Operation IDs are strings up to 128 bytes. The SDK generates UUIDs and bounds pending acknowledgements. A welcome send must be acknowledged before 101 without waiting for the client to receive it, otherwise an awaited send inside onConnect would deadlock admission. Rejection discards that pending connection’s queue.

Broadcast is not atomic. Some recipients may accept a frame while another exceeds its queue budget; ok:false does not undo accepted frames. No broadcast history is retained for disconnected/future clients. Unknown targeted send returns a failed acknowledgement. Client close is idempotent at the wire level; the SDK rejects use of stale/closed JS handles.

The broker starts an instance HTTP call with requestId and request:{method,url,headers} in a request envelope. It enters the same JS object’s fetch callback as connected clients. A Worker returns a response envelope with requestId, integer status and header string pairs, followed by its body stream. Headers and application data remain subject to bounds and HTTP validation; hop-by-hop headers do not reach the external response.

Each request has independent body flow control in both directions:

  • Worker credit with requestId permits one more request-body chunk from the broker.
  • Broker credit with requestId permits one more response-body chunk from the Worker.
  • body carries requestId and base64 payload of at most 65,536 bytes.
  • end closes that direction’s body. A request-body end does not finish the response; a response-body end completes the logical HTTP call.
  • cancel aborts the logical call, cancels its streams and releases pending state. Cancellation of queued HTTP work prevents it from entering the application callback later.
  • Worker stream-error aborts an already started response without failing unrelated clients or falsely reporting a clean EOF.

Each credit grants one chunk; implementations must not create an unbounded credit balance or collect entire bodies. Body chunks, cancellation, membership removal, operation acknowledgements and heartbeat run outside the application callback mailbox. This permits a fetch callback to await its request body or a broker acknowledgement while other transport control continues. Application callbacks themselves are awaited in broker order; response-body streaming continues independently after the callback returns a Response.

heartbeat receives heartbeat-ack in either direction and is never an application message. Broker heartbeat is initially ten seconds with a 30-second observed upstream-silence deadline. Lease renewal is separate and uses PostgreSQL time. Application events are checked against current authority before forwarding; new actor-bound disk operations also require a current generation/lease.

An upstream close, timeout, malformed protocol or lost authority ends that generation. wabe Cloud closes its downstream client WebSockets with 1012 where possible, fails dispatched HTTP calls with 502 or stream abort, and rejects pending undispatched calls/upgrades with 503 and Retry-After. It releases generation queues and does not automatically replay them. A later client reconnect receives a new ID and runs admission; new incoming use may activate a fresh handler and credential family on the same disk.

There are no retained-client snapshots, recovery buffer, onResume, durable mailbox or persistent transport acknowledgements. Broker death may prevent delivery of a close frame and loses in-memory state. Applications own reconnect/backoff, persistence, request acknowledgements and handling of unknown mutation outcomes. See credential lifetimes and limits.