diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-09-12 09:47:07 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-09-12 16:36:54 +0200 |
| commit | 4cce50f09a73739387d5058a6f8183ebac65ae2c (patch) | |
| tree | 30e9d72280295d913983910a956b0d3306696ec8 /docs/MESHBAY_DESIGN.md | |
| parent | 9cc2909cb4a360c81b471ceab1d9578a7655a88e (diff) | |
| download | meshbay-4cce50f09a73739387d5058a6f8183ebac65ae2c.tar.gz | |
fix: an empty group claim is a claim on nothing
A node that hosts no groups sends no `group_ids` on its hub socket, and the
hub resolved the claim with `set(claimed_groups or authorized)` — so "I host
nothing" arrived as "I host every group this account belongs to", other
members' included. Such a node can serve none of them: it holds no GEK, and
its own handshake refuses them with "Group not hosted on this node".
`/v1/groups/{id}/nodes` answers in registration order and `_node_groups` is
in-memory, so which node a client was sent to depended on who reconnected
first after a hub restart. GroupPage took `nodes[0]` with no fallback. On
2026-09-11 a hub deploy at 20:14 reshuffled the registry, a second member's
unconfigured node won the race, and a group stopped opening for everyone in
it with its only real host online throughout. Any member could take one of
their groups down, by accident, by leaving an empty node running.
Four changes, because no one of them is sufficient:
- the hub never widens an absent claim, and `update_groups` goes through
the same ceiling as registration — it assigned its list verbatim, so the
bound that makes C2 hold at authentication was one message wide
- the node states the empty set rather than omitting the field
- the refusal carries `not_hosted`, so a client can tell "try the next
node" from "you, here, must do something first"
- GroupPage walks the list instead of indexing into it
The three lines involved date from 13, 20 and 23 August and each is
defensible alone. The defect is in the seam, which is where the last two
also were: a falsy empty collection must never mean "unspecified".
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T4YmK41VsEURWFdop4EEeT
Diffstat (limited to 'docs/MESHBAY_DESIGN.md')
| -rw-r--r-- | docs/MESHBAY_DESIGN.md | 12 |
1 files changed, 11 insertions, 1 deletions
diff --git a/docs/MESHBAY_DESIGN.md b/docs/MESHBAY_DESIGN.md index 39a24da..f125825 100644 --- a/docs/MESHBAY_DESIGN.md +++ b/docs/MESHBAY_DESIGN.md @@ -1519,7 +1519,17 @@ account has and when each last signed in. Registration on the node socket requires a **node-scoped token**, verifies the node record against the token subject, and **derives group claims from the database**: a node may narrow the set to what it hosts but cannot widen it, and cannot displace a -live registration (**C2**). +live registration (**C2**). Narrowing goes all the way down: **an empty +claim is a claim on nothing**, never on everything. Reading it as "all of this +account's groups" made an unconfigured node a registered source for groups it could +not serve — including other members' — and since `/v1/groups/{id}/nodes` answers in +registration order, one such node reaching the hub first made a group unopenable for +everyone in it (2026-09-11). The ceiling applies to **every** message that changes the +set, not only to the registration: a node that may narrow on connecting and widen on +reload has no ceiling. + +A client must therefore treat that list as candidates rather than a ranking, and try +the next node on a `not_hosted` refusal (`MESHBAY_NODE_PROTOCOL.md` §6.3). The node authenticates to the hub with an Ed25519 signature over a domain-separated timestamped message — **no password and no auth key on a node** — and receives a |