Anchor: add EXT-009 scoped presence query
Lets a client ask "is peer X currently online in network Y?" without joining that network first — a stateless O(1) lookup against the anchor's existing client registry. Scoped to (net, id) rather than a global lookup by id alone, so it can't be used as a cross-network "is this pubkey online anywhere" oracle: the caller must already know a network the peer belongs to, matching the knowledge already required to join it and observe presence the slow way via peer-join/peer-leave. Motivated by flit's known-devices list, where each pairing is its own isolated anchor network and the app holds no persistent connection while idle — today the only way to know if a device is reachable is to attempt a full connect. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
106
EXTENSIONS.md
106
EXTENSIONS.md
@@ -379,3 +379,109 @@ clients receive the full reaction state on reconnect.
|
||||
- Incoming `reaction` wire frames are dispatched as `reaction` IPC events.
|
||||
- `BrowserAdapter.send()` handles `send_reaction` commands and both broadcasts to all peers and emits a local `reaction` event.
|
||||
- `sendChat` includes the `mid` in the wire frame so reactions can reference it correctly across peers.
|
||||
|
||||
---
|
||||
|
||||
## EXT-009 — Scoped Presence Query
|
||||
|
||||
**Status:** proposed
|
||||
**Affects:** anchor WS protocol only (new request/response pair); no DataChannel or peer-to-peer wire changes
|
||||
|
||||
### Motivation
|
||||
|
||||
YAW/2 §5.2 presence (`peer-join` / `peer-leave`) is push-only and scoped to peers
|
||||
who are simultaneously joined to the same network — you only learn someone is
|
||||
online by already being in the room with them. That's fine for chat, but it's
|
||||
the wrong shape for flit's "known devices" list: each paired device is its own
|
||||
isolated network (`net = NetHash(PairRoomName(idA, idB))`), and the app doesn't
|
||||
hold a persistent anchor connection per pairing while idle. Today the only way
|
||||
to find out if a known device is reachable is to attempt a full connect.
|
||||
|
||||
This extension adds a lightweight, stateless query: "is peer X currently
|
||||
online in network Y?" — answerable from the anchor's existing in-memory
|
||||
`clients` registry with an O(1) lookup, no new server-side state.
|
||||
|
||||
**Deliberately not** a global "is this pubkey online anywhere" query. The
|
||||
anchor's `clients` map is keyed globally by peer id, so an unscoped query
|
||||
would let anyone who knows a pubkey probe its online status across every
|
||||
network on the anchor, with no relationship required — a cross-identity
|
||||
presence oracle. Scoping the query to `(net, id)` keeps the access model
|
||||
identical to today's: knowing a network's hash is already the bar for
|
||||
joining it and observing presence the slow way (§5.1 `joined`, §5.2
|
||||
`peer-join`); this extension only removes the need to actually join and wait.
|
||||
|
||||
### Wire messages
|
||||
|
||||
Sent over the anchor WS connection. Does **not** require a prior `join` —
|
||||
the query is anchor-local and stateless, so a client may ask before joining,
|
||||
after leaving, or without ever joining any network on this connection.
|
||||
|
||||
#### `presence_query`
|
||||
|
||||
```json
|
||||
{ "type": "presence_query", "net": "<64-hex net hash>", "id": "<64-hex peer id>" }
|
||||
```
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|--------|--------------|
|
||||
| `net` | string | Network hash, computed the same way as `join` (§5.1). |
|
||||
| `id` | string | Peer id being queried. |
|
||||
|
||||
#### `presence`
|
||||
|
||||
```json
|
||||
{ "type": "presence", "net": "<64-hex net hash>", "id": "<64-hex peer id>", "online": true }
|
||||
```
|
||||
|
||||
`online` is `true` iff a client is currently registered with that exact
|
||||
`(net, id)` pair. Unknown `net`/`id` combinations return `online: false`,
|
||||
identical in shape to a peer who simply isn't connected — the anchor does
|
||||
not distinguish "never seen this net" from "peer not currently in it."
|
||||
|
||||
### Anchor implementation notes
|
||||
|
||||
No new registry. `a.clients` is already keyed globally by peer id
|
||||
(`cmd/anchor/main.go`); the query handler does:
|
||||
|
||||
```go
|
||||
a.mu.RLock()
|
||||
c, ok := a.clients[id]
|
||||
online := ok && c.net == net
|
||||
a.mu.RUnlock()
|
||||
```
|
||||
|
||||
Same lock, same map, no writes. YAW/2-only anchors that don't implement this
|
||||
extension simply never emit a `presence` reply — per §8, unknown request
|
||||
types are ignored by clients, so callers should treat "no reply within a
|
||||
short timeout" the same as `online: false` rather than blocking indefinitely.
|
||||
|
||||
### Security considerations
|
||||
|
||||
- **No new information beyond what §5.2 already permits** — the requester
|
||||
must already know both `net` and `id` to ask, exactly the knowledge
|
||||
required to join that network and observe presence natively. This
|
||||
extension is a latency/statefulness shortcut, not a new capability.
|
||||
- **Does not enable identity-wide presence enumeration.** Because the query
|
||||
is scoped to a specific `net`, checking whether pubkey X is online
|
||||
requires already knowing at least one network X is a member of. An anchor
|
||||
operator or a client cannot use this to ask "where is X online" across
|
||||
all networks it serves.
|
||||
- **Rate limiting recommended** at the anchor (e.g. per-connection token
|
||||
bucket) to prevent a connection from being used to hammer many `(net,
|
||||
id)` guesses cheaply. This mirrors the existing `history_request`
|
||||
rate-limit precedent (EXT-007, one request per peer/room per 60s) even
|
||||
though the abuse shape here is different — presence queries are free of
|
||||
disk I/O but still worth bounding.
|
||||
- **No change to what the anchor can already infer.** An anchor could
|
||||
already tell that two ids are members of the same net by virtue of
|
||||
relaying between them; this extension doesn't let the anchor learn new
|
||||
relationships, only lets *clients* ask a question the anchor already had
|
||||
the answer to.
|
||||
|
||||
### Client usage (flit)
|
||||
|
||||
Each known device already has a deterministic `net` (`PairRoomName(idA,
|
||||
idB)`, hashed). A "known devices" list can send one `presence_query` per
|
||||
entry — no persistent connection required per pairing — and render
|
||||
online/offline before the user taps Connect. A short client-side cache
|
||||
(a few seconds) is recommended to avoid re-querying on every render.
|
||||
|
||||
Reference in New Issue
Block a user