6.7 KiB
OpenSpec Change: drive-mesh-port
- Change ID: drive-mesh-port
- Package:
@suaveplan/drive-mesh(new) - Status: PROPOSED
- Date: 2026-04-28
Summary
P2P mesh layer over the chunks plugin. DriveMesh orchestrates peer discovery, availability gossip (compressed bitsets per file), parallel chunk pull (multi-peer racing window), peer scoring, server fallback, and upload backpressure. Mesh consumes the ChunkSource interface returned by @suaveplan/drive-plugin-chunks — it does NOT import drive core. Mesh consumes one or more DriveTransport implementations (HTTPS, WebRTC, libp2p) for the wire.
Why mesh is a separate package
A vault running on a server doesn't need mesh — it just wants drive + storage-s3. A browser session in the CDN scenario needs drive + chunks plugin + mesh + drive-transport-webrtc + drive-transport-https (server fallback). Bundling mesh into drive or chunks would push WebRTC peer code and Bloom-filter logic into every consumer.
Trust model
- Server is the root of trust. Server signs every chunk and every manifest with its ed25519 key.
- Peers are caches, never authorities. Mesh treats every byte from a peer as untrusted until the chunks plugin's sig-verify passes.
- Bad-sig peers are punished. A peer that ever delivers a chunk with an invalid server signature is added to a deny list (configurable: per-session or persistent).
- Mesh never holds the file decryption key — that's in the manifest, decrypted client-side via the recipient's key.
API surface
const mesh = new DriveMesh({
chunks, // ChunkSource handle from chunksPlugin
transports: [webrtcTransport, httpsTransport],
serverPubKey: Uint8Array,
serverFallback: { url: string, transport: "https" },
scheduler: { concurrency: 8, perChunkTimeoutMs: 4000, racingWindowMs: 200 },
peerPolicy: { maxConcurrentUploadsPerPeer: 4, blocklistOnBadSig: true },
rng: types.Rng,
clock: types.Clock,
});
await mesh.start(); // open transports, begin advertising
await mesh.fetchFile(fileId); // resolves manifest, schedules missing chunks
await mesh.fetchChunks(fileId, [12, 13, 14]);
mesh.on("chunk:received", ({ fileId, chunkId, fromPeer }) => …);
await mesh.stop();
Sub-systems
1. Peer index (PeerIndex)
Tracks live peers and their advertised availability per file (compressed bitset keyed by fileId), latency EMAs, error rates. This is an index overlay, not a registry. Peer identity and lifecycle events come from network-plugin-registry via the NetworkBus. The index only adds file-specific metadata on top.
// drive-mesh consumes registry events from the network layer
bus.on("peer:joined", ({ peerId, tier, latency }) => {
driveMesh.peerIndex.add(peerId, { tier, latency });
});
bus.on("peer:left", ({ peerId }) => {
driveMesh.peerIndex.remove(peerId);
});
Methods: addPeer(peerId, transport, metadata), removePeer, peersFor(fileId, chunkId).
2. Availability gossip (AvailabilityIndex)
- Each peer maintains a
BitSetper known fileId of chunks it has. - On chunk acquisition, peer pushes
advertise(fileId, BitSet)to known peers via the transport. - On peer-join, peer pulls
advertisement(fileId)from new neighbours. - Bitsets are gzipped on the wire.
3. Parallel-pull scheduler (Scheduler)
- Takes a list of
(fileId, chunkId)requests. - For each, picks top-K peers by score from
peersFor(fileId, chunkId). - Issues request to the best peer; after
racingWindowMs, issues a parallel request to the second-best (cancelled when the first arrives). - Per-chunk timeout
perChunkTimeoutMstriggers retry on next-best peer. - Honours
concurrencyglobal cap. - After all peer attempts exhaust, falls back to
serverFallback.urlvia the HTTPS transport.
4. Peer scoring (PeerScore)
score = w_latency / latency_ema + w_success * success_rate - w_errors * error_rate.- Updated on every chunk-receive completion or failure.
- Sig-verify failure → permanent score = -∞ (effectively blocked).
5. Upload backpressure (UploadGovernor)
- Limits concurrent uploads per peer (
maxConcurrentUploadsPerPeer). - Honours WebRTC datachannel
bufferedAmountwatermark; pauses sends when high. - Emits
upload:throttledevents for observability.
6. NetworkBus integration
DriveMesh listens to network-plugin-registry events on the NetworkBus to maintain its PeerIndex without duplicating peer lifecycle tracking:
bus.on("peer:joined", ({ peerId, tier, latency }) => {
driveMesh.peerIndex.add(peerId, { tier, latency });
});
bus.on("peer:left", ({ peerId }) => {
driveMesh.peerIndex.remove(peerId);
});
bus.on("peer:updated", ({ peerId, latency }) => {
driveMesh.peerIndex.updateLatency(peerId, latency);
});
This ensures that drive-mesh and network-plugin-registry share a single source of truth for peer identity. File-specific availability bitsets remain the responsibility of drive-mesh's AvailabilityIndex.
7. Manifest bootstrap
Defaults to manifest-as-chunk (chunks plugin's default). Mesh requests manifest:<fileId> from peers like any other chunk; if unavailable, falls back to serverFallback.url. mesh.fetchFile(fileId) is an end-to-end happy-path that:
- Fetch manifest (peer or server).
- Verify manifest sig.
- Diff manifest's chunk list against
chunks.hasChunks(fileId). - Schedule missing chunks via Scheduler.
- Resolve when bitset is full.
Error codes
MESH_CODES = defineCodes("MESH", [PEER_NOT_FOUND, NO_PEERS_AVAILABLE, ALL_PEERS_FAILED, SERVER_FALLBACK_FAILED, MANIFEST_FETCH_FAILED, ABORTED, TRANSPORT_ERROR, PEER_BLOCKED] as const). MeshError extends SuaveplanError<MeshErrorCode>.
Dependencies
- Consumes:
@suaveplan/drive-plugin-chunks(interface only —ChunkSource),@suaveplan/drive-transport-https(server fallback), and any other registeredDriveTransport. Plus@suaveplan/error,@suaveplan/types,@suaveplan/events,@suaveplan/streams,@opentelemetry/api,zod. - Consumes (NetworkBus events):
@suaveplan/network-plugin-registry(peer:joined,peer:left,peer:updated). Drive-mesh does NOT import the registry package directly; it listens to bus events. - Does NOT depend on:
@suaveplan/drivecore. - Blocked on:
drive-plugin-chunks-port, at least onedrive-transport-*-port(HTTPS minimum for fallback),network-plugin-registry(for peer identity events).
Out of scope
- Wire-protocol implementations →
drive-transport-https-port,drive-transport-webrtc-port. - DHT-based peer discovery → future change
drive-mesh-discovery-dht. - BitSwap-style pairwise want-list reconciliation → optimisation in v2.
- Sharing primitives (envelope encryption, grant tokens) →
@suaveplan/drive-sharing(separate change).