145 lines
8.5 KiB
Markdown
145 lines
8.5 KiB
Markdown
# Drive Mesh and Sharing
|
|
|
|
This document covers two of the most-asked-about pieces of the drive layer:
|
|
|
|
1. How **n drives** find each other and replicate chunks (`drive-mesh`).
|
|
2. How files become **shareable** between specific drives (`drive-sharing`).
|
|
|
|
Both are plugins that compose with the chunks plugin and the network layer.
|
|
|
|
## `drive-mesh` — per-file scheduling
|
|
|
|
`drive-mesh` makes a drive participate in a chunk-sharing mesh. It does **not** own peer identity or per-peer health — those live in `network-plugin-registry`. Mesh's job is, for one specific file:
|
|
|
|
1. **Advertise** which chunks of which fileIds this drive holds (gzip-compressed bitset).
|
|
2. **Discover** which peers hold the chunks this drive needs (read advertisements off the bus).
|
|
3. **Schedule** a fetch plan: which chunks to pull from which peers, with parallel races and per-chunk timeouts.
|
|
4. **Score** peers per file: a peer that gave you a bad signature gets `-Infinity`; a peer that consistently delivers fast bumps up.
|
|
|
|
Spec excerpts:
|
|
|
|
- `MESH-DEPS-01`: mesh consumes `ChunkSource` from the chunks plugin; it does not import drive core.
|
|
- `MESH-SCHED-01..03`: parallel-pull window, per-chunk timeout, server fallback.
|
|
- `MESH-SCORE-01`: bad sig → score `-Infinity` permanently.
|
|
- `MESH-GOSSIP-01`: bitset advertisements over the network bus.
|
|
- `MESH-FETCH-FILE-01`: end-to-end happy path — mesh resolves, requests, verifies, hands plaintext up the chain.
|
|
|
|
### The mesh fetch flow (one file)
|
|
|
|
```text
|
|
drive-mesh.fetchFile(fileId)
|
|
│
|
|
├── 1. fetch manifest (peer-first, server-fallback)
|
|
├── 2. local check: which chunks do we already have?
|
|
├── 3. for each missing chunk:
|
|
│ ├── pick top-N candidates from per-peer score
|
|
│ ├── race them; first verifying response wins
|
|
│ ├── on bad sig: blacklist that peer, retry next candidate
|
|
│ ├── on timeout: try the next candidate
|
|
│ └── on full miss: fall through to drive-transport-https (server)
|
|
├── 4. verify each chunk's Ed25519 sig + AEAD tag
|
|
├── 5. reassemble; verify whole-file BLAKE3
|
|
└── 6. emit `cd:advertise` so other peers know we now have these chunks
|
|
```
|
|
|
|
### `drive-mesh` does NOT do
|
|
|
|
- **Peer discovery.** That's the network discovery plugins (`network-plugin-registry`, `network-discovery-*`).
|
|
- **Mesh-wide redundancy** (ensuring N copies of every chunk exist). That's `network-plugin-content-delivery`.
|
|
- **Cross-mesh relay.** That's `network-plugin-relay`.
|
|
- **The actual transport.** That's `drive-transport-webrtc` / `drive-transport-https`.
|
|
|
|
Mesh sits in the middle: scheduler for one specific file's chunks, leaving identity, redundancy, and bytes-on-wire to the layers around it.
|
|
|
|
## `drive-sharing` — partitions and access control
|
|
|
|
`drive-sharing` is the access-control layer. The unit of access is a **partition**.
|
|
|
|
### Mental model
|
|
|
|
Think of a drive as a tree of partitions. The root partition belongs to the drive's owner. Sub-partitions can be created and granted to other drives. Each partition has its own key-encryption-key (KEK); files inside a partition are encrypted with per-file `fileKey`s, each of which is wrapped with the partition's KEK.
|
|
|
|
```text
|
|
drive (alice@example.com)
|
|
└── root partition (KEK_root)
|
|
├── /personal/... (file keys wrapped by KEK_root)
|
|
└── shared partition "wedding" (KEK_wedding)
|
|
├── /wedding/photo1.jpg (file key wrapped by KEK_wedding)
|
|
└── /wedding/photo2.jpg
|
|
```
|
|
|
|
Granting `bob@example.com` access to "wedding" means: rewrap `KEK_wedding` with bob's public key (X25519 sealed-box) and hand bob the wrapped blob. Bob's drive unwraps with its private key, caches `KEK_wedding`, and can now unwrap any `fileKey` in that partition.
|
|
|
|
### Public API (from `@suaveplan/drive-sharing` proposal)
|
|
|
|
| Component | Role |
|
|
| --- | --- |
|
|
| `PartitionManager` | Create/delete/list partitions. Each has an id and a KEK. |
|
|
| `KeyRing` | Holds the unwrapped KEKs the current drive has access to. |
|
|
| `WebCryptoProvider` | KEK wrap/unwrap. X25519 sealed-box plus AES-KW for symmetric rewrap. |
|
|
| `GrantManager` | `grant(partition, peerPubKey)` / `revoke(partition, peerPubKey)`. |
|
|
| `TokenManager` | Time-limited share tokens (URL-safe) — give someone read access for 24h without exchanging keys. |
|
|
| `TransferManager` | Move a file between partitions: rewrap its `fileKey` from the source KEK to the destination KEK. |
|
|
|
|
### What revocation actually does
|
|
|
|
Naive revocation is impossible after the fact: a peer who has already decrypted a chunk will always have the plaintext. Revocation in this design is *forward*:
|
|
|
|
1. `revoke(partition, bob)` removes bob from the partition's authorized list.
|
|
2. The next `write()` to that partition rotates `KEK_wedding` to `KEK_wedding'`.
|
|
3. New `fileKey`s are wrapped with `KEK_wedding'`. Old `fileKey`s remain wrapped with `KEK_wedding`.
|
|
4. Files written before revocation are *re-keyed* lazily: on first read after revocation, the drive rewraps the `fileKey` with the new KEK and re-uploads the wrap blob (not the data — just the small wrap).
|
|
|
|
Bob can read what he already cached. He cannot read anything new, including content uploaded after revocation. This is the standard envelope-encryption revocation pattern.
|
|
|
|
### Tokens vs grants
|
|
|
|
- **Grants** are per-peer-identity (X25519 public key). Revocable. Use for stable relationships.
|
|
- **Tokens** are bearer (anyone with the token can use it for the validity window). Self-expiring. Use for one-off shares ("here's a link to the wedding photos for 24 hours").
|
|
|
|
`TokenManager` builds tokens server-side (or client-side with a server signature, depending on the deployment). The token contains the partition id, an expiry, and a wrapped KEK. The receiver's drive unwraps and uses it like any other KEK until expiry.
|
|
|
|
### React UI
|
|
|
|
`@suaveplan/drive-react-sharing` ships:
|
|
|
|
- `<ShareDialog drive={drive} path={...} />` — full UI for granting/revoking/tokenizing.
|
|
- `<PartitionBadge>` — show which partition a file belongs to.
|
|
- `<FileAccessList>` — render the current grant list.
|
|
- `useSharing()` — hook that exposes the imperative API.
|
|
|
|
It is a thin UI layer over `@suaveplan/drive-sharing` — no business logic of its own.
|
|
|
|
## Composition — mesh + sharing + chunks
|
|
|
|
The four plugins (`chunks`, `sharing`, `mesh`, `transport-*`) compose cleanly because they each touch a different concern:
|
|
|
|
| Plugin | Concern | Where it intercepts |
|
|
| --- | --- | --- |
|
|
| `chunks` | Confidentiality + integrity of bytes on disk and on wire | `drive:read` (verify+decrypt), `drive:write` (encrypt+sign) |
|
|
| `sharing` | Authorisation: which drives can read which partition | `drive:read` (unwrap fileKey), `drive:write` (wrap fileKey) |
|
|
| `mesh` | Where to fetch missing chunks | `drive:read` (when local store misses) |
|
|
| `transport-*` | Bytes on the wire to/from peers | Below mesh; serves chunks-by-id |
|
|
|
|
Chunks alone gives you encryption-at-rest. Chunks + sharing gives you encryption-at-rest with multi-drive access control. Chunks + sharing + mesh + transport gives you n drives that share files end-to-end-encrypted across a tiered network.
|
|
|
|
## Cross-references with the network layer
|
|
|
|
The `drive-network-integration` change folder ([proposal](../../openspec/changes/drive-network-integration/proposal.md)) is the canonical contract. Summary:
|
|
|
|
| Direction | Event | Producer | Consumer | Meaning |
|
|
| --- | --- | --- | --- | --- |
|
|
| Out | `cd:advertise` | `drive-mesh` | `network-plugin-content-delivery` | "I have these chunks of these fileIds" |
|
|
| Out | `cd:request` | `drive-mesh` | `network-plugin-content-delivery` | "Anyone got chunk X of file Y?" |
|
|
| In | `cd:response` | `network-plugin-content-delivery` | `drive-mesh` | "Here's the chunk" (verifies via chunks plugin) |
|
|
| In | `peer:joined`/`peer:left`/`peer:updated` | `network-plugin-registry` | `drive-mesh` | Peer identity lifecycle |
|
|
|
|
`drive-mesh` knows nothing about *which* transport delivers a chunk. `network-plugin-content-delivery` knows nothing about *which* file a chunk belongs to. The bus carries content-addressed chunk requests; everything else is layer-local.
|
|
|
|
## Reading further
|
|
|
|
- [`plugins.md`](./plugins.md) — the full plugin chain.
|
|
- [`encryption.md`](./encryption.md) — the chunks plugin in detail.
|
|
- [`../network/plugins-catalog.md`](../network/plugins-catalog.md) — `network-plugin-content-delivery` and `network-plugin-relay`.
|
|
- [`../network/topology-and-failover.md`](../network/topology-and-failover.md) — how the mesh tolerates partitions and server outages.
|