aboutsummaryrefslogtreecommitdiffstats
path: root/docs/MESHBAY_NODE_PROTOCOL.md
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-09-30 12:00:58 +0200
committerChristophe Besson <cbesson@gmail.com>2026-09-30 12:00:58 +0200
commit1684fcb64531eaf2e9bb8567ba4bdcbada6469d8 (patch)
treee6f5135087323d9e7a0b5e47bc93118c0db4df4d /docs/MESHBAY_NODE_PROTOCOL.md
parent5612dbbac41609b3f84784f57f1de538262db9a9 (diff)
downloadmeshbay-1684fcb64531eaf2e9bb8567ba4bdcbada6469d8.tar.gz
docs: state hosting, invitations, the roster check and link previews as they are
Present-tense statements of what holds, in place of before/after phrasing: host designation (§7.2, AV32), invitations (§3.4, §7.3, AV33), the roster check at the handshake (§5.2, protocol §6.3), the wrong-code lock (protocol §8.3), and what the link-preview gate does and does not refuse (§6.5). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'docs/MESHBAY_NODE_PROTOCOL.md')
-rw-r--r--docs/MESHBAY_NODE_PROTOCOL.md10
1 files changed, 6 insertions, 4 deletions
diff --git a/docs/MESHBAY_NODE_PROTOCOL.md b/docs/MESHBAY_NODE_PROTOCOL.md
index 47f508b..b760354 100644
--- a/docs/MESHBAY_NODE_PROTOCOL.md
+++ b/docs/MESHBAY_NODE_PROTOCOL.md
@@ -481,7 +481,7 @@ absent. `verify_proof` compares with `hmac.compare_digest`.
| not on the denylist for `user_id`, `jti` **or** `group_id` | `Token revoked` | all three targets, and persisted to disk: a revocation that a restart forgets is not one |
| `group_id ∈ token.groups` | `Not a member of this group`, code `not_a_member` | the membership check itself — a token is proof of an account, never of a group |
| `group_id ∈ node.hosted_groups` | `Group not hosted on this node`, code `not_hosted` | the hub may hand a client several nodes for one group, and only some of them host it |
-| *(after the proof)* the roster admits `sub` for `group_id` — an active member row, or the node-wide operator row | `This node has not admitted you to this group`, code `not_authorized_for_group` | the key proves possession and the token the hub's view; the node's own answer is the roster. Without it, someone revoked here but still a hub member kept a session with the key they held |
+| *(after the proof)* the roster admits `sub` for `group_id` — an active member row, or the node-wide operator row | `This node has not admitted you to this group`, code `not_authorized_for_group` | the key proves possession and the token the hub's view; the node's own answer is the roster. Someone revoked here but still a hub member, holding the key, is refused a session |
`AuthorizedPeer` carries `user_id`, `group_id`, `username`, `jti` — and deliberately
**no user public key**. `username` is read from a `username` claim that neither the MNP
@@ -494,12 +494,14 @@ node's roster. The hub certifies accounts, not keys.
minted the token. The MNP token is minted for each connection and names **only** that
connection's group (`POST /v1/nodes/mnp-token {node_pk, group_id}`; `groups` is empty
for a non-member), so the operator it is handed to learns nothing of the member's
-other groups. A stale `groups` claim is therefore no longer the usual cause; the
+other groups. A stale `groups` claim is therefore not the usual cause; the
client still refreshes its session once and retries on that code before telling someone
who was just invited that they are not a member.
`not_hosted` is the client's signal to try the **next** node the hub offered for the
-group rather than to report a failure. `/v1/groups/{id}/nodes` returns every node
+group rather than to report a failure. The hub offers only nodes whose account owns
+the group or that the group's owner approved as hosts — a member's node holds the
+group key and would pass this handshake like the real host. `/v1/groups/{id}/nodes` returns every node
registered for the group, in hub registration order, and that order is not a ranking:
a node listed first is not necessarily one that holds the group's files. Refusing
without a code made this indistinguishable from a refusal the reader has to act on,
@@ -834,7 +836,7 @@ Evaluated in order (`_do_join_request`):
| account has devices here, this key is not one | `unknown_device` — the way in is a device-add (§9), not a new invite |
| device known, no member row, group policy `open` | member row created (`approved_by: "open-join"`) |
| device known, a **pending invite** exists for this user | code required even for a known device; `code_required` / `code_invalid` on failure |
-| a code is about to be tried and this account has `>= 5`, or the node `>= 20`, wrong codes in 600 s | `error: Pairing temporarily locked`, audited `join_throttled`. Only `code_invalid` counts, and nothing else is gated by it: every member reconnecting gets the key through this message, so a lock checked before recognition let one member refuse it to everybody |
+| a code is about to be tried and this account has `>= 5`, or the node `>= 20`, wrong codes in 600 s | `error: Pairing temporarily locked`, audited `join_throttled`. Only `code_invalid` counts, and nothing else is gated by it: every member reconnecting gets the key through this message, so a lock applied before recognition would let one member refuse it to everybody |
| device known, not an active member of the session group, a code offered | redeemed like any code — an invitation **link** reaches here from someone pinned through another group, or removed and invited back; `code_invalid` on failure |
| device known, member row resolved | `join_result{ok, recognised:true, role}` + wrapped GEK |
| unknown device, no code, policy `open` | pin TOFU, admit, wrap (`via: "tofu"`, audited) |