ACTIAS

Event delivery

How a publish reaches its followers, and why the cost is per node, not per follower.

The log and the edges

state:publish appends the event to a log inside the publisher's own SQLite file, in the same transaction as the method's writes. Beside the log sit the edges: one row per approved follower, each recording the node its follower lives on.

The edge list is the access control, computed once: the follow hook runs when the edge is created, never during delivery. Delivery walks rows. A new edge starts at the log's head; nothing is replayed down it.

Fan-out by node

Between the publisher's calls, its pump partitions due edges by the follower's node. Local edges deliver directly. Remote edges batch into one call per node.

flowchart LR
    P[Publisher<br/>node A] -- "1 call, events once" --> B[node B]
    P -- "1 call per edge" --> C[node C]
    P --> L[local followers<br/>on node A]
    B --> s1([socket]) & s2([socket]) & s3([socket])
    C --> o1[follower object] & o2[follower object]
Edge kindWhat a remote batch carriesPublish cost
connectionthe due events once, in publish order, beside every edge on that node with its topic, cursor and filter; the receiving node slicesone call and one copy per node holding sockets
objectone delivery per edge, with that edge's events inline up to 32 KB, else the sequence rangeone call per follower object

A channel with 5,000 live watchers spread over 8 nodes costs 8 sends per message. A publisher with 50 object followers costs 50, because each follower's cursor is its own.

Connection edges

Connection edges promise at-most-once. A local follower's events are pushed into its connection inbox of 256 items. Remote batches fan out to the receiving node's own inboxes.

On the receiving nodeEffect
the inbox has roomthe item lands; the program's event handler runs, or the platform writes the forward envelope with no vm
the inbox is fullthe delivery is refused and the publisher prunes every edge that connection holds; the socket stays open
the socket is gonereported back; the publisher prunes its edges

The edge cursor advances whether or not the push landed. A miss is not retried: anything that must survive a tab closing belongs on the durable object one hop back.

Durable edges

Object edges promise at-least-once delivery with exactly-once effect on the follower's own storage. The follower keeps a receive cursor per publisher and topic, checked before the handler runs and advanced inside the handler's own transaction.

sequenceDiagram
    participant P as Publisher (node A)
    participant F as Follower (node B)
    P->>F: delivery for one edge (events inline, or a range)
    loop each event, in order
        F->>F: seq at or below cursor? skip
        F->>F: run receives handler + advance cursor, one transaction
    end
    F-->>P: outcome
    P->>P: advance edge cursor, or back off and retry

A handler that committed never runs again for that event: redelivery after a crash skips instead of repeating. Effects that leave the follower's transaction, a call to a third object or a kv write, are the window the handler-side idempotence in guarantees covers.

FailureEffect
the handler throws, or runs past 10 secondsa failed attempt; the edge backs off 500 ms, doubling
8 failed attempts (the last wait is 32 s)the edge is dropped
the follower's recorded node no longer existsnot a failure: delivery routes the follower's identity to wherever it rehomed
a connection's recorded node no longer existsits edges are pruned; node ids never return

Large payloads

Events ride inline in a durable delivery up to 32 KB. Past that, the delivery carries only the sequence range and the edge's filter, and the receiving node materializes the events through the platform read path: its own copy of the publisher's file, else the holder, else the shipped replica.

Connection batches have no range form and always ride inline, which they can afford because a node's copy is one per event however many sockets are owed it.

Ordering

The log has one home, so per-publisher order is global no matter which path an event took: inline or range, local or forwarded. Events from one publisher reach one follower in publish order; across publishers there is no promise.