diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-08-28 10:15:51 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-08-28 10:15:51 +0200 |
| commit | 49a1174114bb3cd50bbcf13157833d59a0eb50a6 (patch) | |
| tree | 6dbe8a20064087538865d325c00e7e4723409510 /docs | |
| parent | 232ce4ae82179245b402ffc3bd473565329e5e16 (diff) | |
| download | meshbay-49a1174114bb3cd50bbcf13157833d59a0eb50a6.tar.gz | |
docs: draft v6 — instance policy, per-owner group names, chat link previews
Amend v6 (§1 rows 11–13, §§2.8–2.10) with the three architectural changes
made after the 2026-08-17 desktop-client discussion:
- §2.8 hub_settings — a runtime instance-policy store; public groups can be
switched off hub-wide, enforced on every hub-mediated path. Records the
suspend-vs-revoke distinction (hub flag vs signed node-enforced revocation).
- §2.9 group names unique per owner account; identity still the UUID; shown
as name@owner.
- §2.10 chat link previews as a new instance of the "node on demand, asking
device caches, nothing durable" rule; the SSRF gate; MNP 0.12.
Also notes hub_settings in §2.5's list of what the hub holds, so that rule
stays accurate.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018gKJ85aZyvEwarXMFzFEwi
Diffstat (limited to 'docs')
| -rw-r--r-- | docs/meshbay-draft-v6.md | 105 |
1 files changed, 103 insertions, 2 deletions
diff --git a/docs/meshbay-draft-v6.md b/docs/meshbay-draft-v6.md index 72cfdbf..a5fb8d3 100644 --- a/docs/meshbay-draft-v6.md +++ b/docs/meshbay-draft-v6.md @@ -10,6 +10,11 @@ > devices are admitted, how authorship is established, and which shell the native client > uses. It also records one rule v5 assumed without writing down. > +> **Amended 2026-08-28** with three changes that post-date that discussion: the hub gains a +> runtime instance-policy store (public groups can be switched off hub-wide), group names +> become unique per owner account (`name@owner`), and chat gains link previews. See §1 +> rows 11–13 and §§2.8–2.10. Wire versions at that point: MNP 0.12, MHP 0.1; packages 0.8.0. +> > The v5 convention is carried forward and is not negotiable: **a claim in this document > must name the adversary it holds against.** A property that holds against a passive hub > and not an active one is written that way. @@ -45,6 +50,9 @@ | 8 | Authorship | Chat senders are **cryptographically authenticated to each other**; an upload has a **provable owner** who may delete it, as the operator may. v5's node-asserted attribution is replaced | operator decision, §2.4b | | 9 | Node authority | The operator may **close uploading to everyone but themselves**, per group. Signed MNP op, stored on the node, enforced by the node — the hidden button is a courtesy, the refusal is the control | §2.1b | | 10 | Client | A group's UI is a **set of pluggable applications** (Chat, Files today), not one monolithic page. Which are shown is a per-group, operator-signed setting on the same pattern as change 9 | §2.7 | +| 11 | Hub role | The hub gains a **runtime instance-policy store** (`hub_settings`). First policy: an admin switches **public groups off** hub-wide, enforced server-side on every hub-mediated path. `suspend` vs `revoke` on a group are now written down as the distinct things they are | §2.8 | +| 12 | Group registry | A group name is **unique per owner account**, not globally; the group's identity is still its UUID. Listed everywhere as `name@owner` | §2.9 | +| 13 | Enrichment | **Chat link previews** — the node unfurls a pasted URL into an OpenGraph card. A new instance of the §2.7 "node on demand, asking device caches, nothing durable" rule; new SSRF surface, gated. MNP 0.12 | §2.10 | --- @@ -210,8 +218,8 @@ And the rule v5 assumed without writing: > **Group-related server state lives on the node.** Files, indexes, members' devices, > pending device requests, invitations, chat, per-root availability, and anything a future > feature wants to keep about a group — all on the node. The hub holds accounts, the group -> registry and membership, signaling, notifications and the moderation surface, and -> nothing else about content. +> registry and membership, signaling, notifications, the moderation surface, and +> instance policy (`hub_settings`, §2.8) — and nothing else about content. Verified for the multi-root change: `SwarmSource` carries `content_hash`, `node_id` and `endpoint` — **no paths, no filenames** — and private groups register nothing (H7). The @@ -261,6 +269,99 @@ application — is `docs/apps.md`, on the same basis `docs/desktop-client-v1.md` desktop client's detail: this document states what changed and what holds, not how to build on it. +### 2.8 §6.1 Hub role — instance policy, and public groups can be switched off + +New (2026-08-28). The hub had no runtime-configurable setting: everything was `hub.toml` +(read once, restart to change) or code. It now has `hub_settings`, a key/value table an +admin edits from the panel. The first entry is `allow_public_groups`. + +**This does not contradict change 5 / §2.5.** `hub_settings` is *instance policy, not group +content*: it says how this hub behaves, and holds nothing about any group's files, index, +membership or keys. "Group-related server state lives on the node" is untouched. + +**What "off" does** — server-side, on every path the hub mediates, read live: + +- `create_group` refuses `visibility=public` — staff included; the way back is to + re-enable it, not to slip past +- the public directory (`GET /v1/groups`) returns nothing, local **and** federated +- open-joining a public group is refused +- `group_online_nodes` hands a non-member no node to connect to +- `webrtc_offer` drops its "the node hosts an open-join group, admit anyone" fallback +- the MHP directory export advertises nothing to peers + +Flipping it back restores every path. **Existing members of a group that predates the +switch keep their membership row and their access** — this is a directory-and-brokering +control, not a purge. + +**What it is not.** The node still reads `visibility` and `join_policy` from its own +`node.toml`, never the hub (`invite-pairing-v1.md`). A node whose operator set +`join_policy = "open"` still TOFU-pins and serves whoever reaches it directly over MNP. +The switch removes the hub-provided ways to *find and reach* such a node; it is **not a +remote kill**. For a hard stop on one group, `POST /v1/admin/revoke` is the tool. + +**`suspend` vs `revoke`, made explicit** — the two moderation verbs on a group were never +written down as distinct, and they are: + +| | `suspend` (`PATCH /v1/admin/groups/{id}`) | `revoke` (`POST /v1/admin/revoke`) | +|---|---|---| +| Hub DB | `status = "suspended"` | `status = "revoked"` | +| Node | nothing | signed revocation broadcast → denylist + live sessions dropped, **persisted across a node restart** | +| Reversible from the panel | yes | no | + +The client shows the real state now (`Group is revoked`, not a blanket `Group is +suspended`). + +### 2.9 §6.1 Hub role — group names unique per owner, shown as `name@owner` + +New (2026-08-28). "The name is unique" was an unenforced expectation — the `groups` table +had only a non-unique index. Two changes: + +- A functional unique index `uq_groups_owner_name (admin_id, lower(name))`. Uniqueness is + **per owner account**, case-insensitively, trimmed — two different owners may each have a + `photos`. `create_group` returns 409 on a clash, with an `IntegrityError` backstop for + the race. The migration pre-flights and **aborts listing the offending pairs** rather + than renaming anyone's group. +- **The group's identity is still its UUID.** `#/group/<uuid>`, `_node_groups`, + `node.toml [[groups]] id`, membership — all unchanged. `name@owner` is a *label plus a + create-time check*, not an addressing scheme. + +`owner_username` (derived from `admin_id`, nothing stored anew) rides `/v1/groups/mine`, +`GET /v1/groups` (local rows), the join response and `/v1/admin/groups`. The SPA renders it +under the name, smaller and grey, prefixed `@`. The handle is **hub-local**: +`foofoo@grenet` on two federated hubs are different groups, and a federated row shows +`@<source_hub>`, not an account. + +Design record and the decisions behind it: `~/next/groupnames.md` (kept out of the repo). + +### 2.10 Chat link previews + +New (2026-08-28). A URL pasted in a group's chat unfurls into an OpenGraph card — title, +description, site name, image. + +**Whose request it is: the node's**, never the browser's or the hub's. The browser cannot +(a strict `img-src`/`connect-src` and CORS), and a direct fetch would leak every reader's +IP to the linked host on each render. The hub must not touch group content (§2.5). The node +already fetches third-party metadata for the Videos and Music apps, over the same +authorised path. + +This is a new instance of the §2.7 / draft-v5 §5.2 rule: **the node produces enrichment on +demand and the asking device caches; neither node nor hub keeps durable derived state.** +The card text lives in a bounded in-memory TTL cache on the node; the OG image rides the +existing `media_cache` thumb store (blake3-keyed) and the browser fetches it over the +normal `file_req` path, exactly like a poster. Nothing reaches the hub — this is node↔ +browser P2P. + +**New adversary surface: SSRF.** The URL is a *member's* choice and it triggers an outbound +HTTP request from the operator's machine. `linkpreview.safe_url` is the gate — http(s) +only, no credentials, and every resolved address must be globally routable (loopback, +private, link-local, multicast, reserved and cloud-metadata ranges refused); redirects are +followed by hand so each hop is re-checked. Residual, documented in the module: a name that +resolves clean at check time and internal at connect time (rebinding), closed properly by +pinning the checked IP — deferred. + +**MNP 0.12** — `link_preview_req` / `link_preview_resp`, additive: an older node logs +"unknown type" and the client shows the bare link. + --- ## 3. Filesystem portability as a security property |