A signed claim
Last updated
Presence is a signed claim a device makes about itself, carried by a frame the relay never stores. The relay computes no presence and keeps no server-side last seen. A relay able to synthesise presence could show a person at their keyboard when they were away.
The LIVE frame
Protocol version 2 adds frame tag 21, the same shape in both directions: a group, an epoch and a sealed body. The body is a LiveBody, signed by the device it describes with the same Ed25519 key that signs envelopes, and the relay cannot read it. The relay sees a group id, a size and a time.
Nothing about a beat is durable, and a beat never gets a sequence number and is never written to Postgres or returned by a reconciliation round. No head attestation, checkpoint or compaction manifest names one. A beat published into a group nobody is watching is discarded, and that raises no error.
The retired ephemeral kind
The kind space reserved 0x00F0 ephemeral for presence, but it could never be implemented. Under enc: 1 an envelope's kind lives inside the ciphertext, so a relay told to drop one kind and keep the rest cannot tell them apart. The WRAP and HANDSHAKE frames left the envelope to close the same hole, and presence gets a frame for the same reason.
0x00F0 stays allocated forever, unused.
What a beat claims
| Field | Meaning |
|---|---|
| kind | 1 presence, 2 typing. 3 and 4 are reserved and never sent |
| member | the author's device key, the same 32 bytes an envelope carries |
| state | 1 active, 2 idle, 3 away. Presence only |
| channel | the channel slug. Typing only |
| at | the author's clock, in milliseconds |
| ttl | how long the claim is good for, in seconds |
| cert | the delegation certificate, when the author speaks under a delegated key |
| sig | Ed25519 over the canonical body |
at is compared and never rendered. A receiver uses it to discard a beat older than one already held from the same device, so a replayed beat cannot mark somebody away. A member list's last seen is computed on the reader's own machine from the newest envelope it observed.
ttl is clamped by the receiver at 120 seconds. Without the clamp, one beat claiming a year would leave a dot on a laptop shut since then. The receiver applies the clamp because the sender is the party with the motive.
Limits
| Limit | Value | Why |
|---|---|---|
| ct size | 4 KiB | A beat is a signed struct with no payload |
| frames per connection per minute | 60 | Budgeted separately from the envelope allowance, so presence can never starve a durable write |
| client beacon interval | 20 seconds | Also why a shed frame needs no error |
| receiver ttl clamp | 120 seconds | A sender cannot claim to be present indefinitely |
A refused beat is refused on the frame. The connection stays up and durable traffic is unaffected.
Shedding
A subscriber whose send queue is full is normally downgraded to reconciliation and told so. A dropped envelope is a hole in an author chain, which raises a security alarm on somebody else's screen.
A beat is not durable, so a full queue sheds it. The relay drops it silently, counts it for the operator and never reports a hole in the client's log. The next beat arrives twenty seconds later.
Operator settings
WEALD_RELAY_LIVE is on or off, and off refuses every beat as a deliberate posture. A self-hoster who does not want to carry a beat every twenty seconds per connected device turns it off. Clients then render presence as unavailable.
WEALD_RELAY_LIVE_FANOUT defaults to process, which is correct in a single-process deployment and in no other. A relay told it runs more than one instance refuses to start, since each member would otherwise see half the room. A relay that will not start gets a human's attention.
Presence unavailable
A version 1 relay, a relay with the ephemeral path off, and a workspace synced through git all produce the same client state: presence unavailable. That state differs from everybody being offline. A client that rendered it as offline would tell every member that the whole team had gone home.