Files
2026-05-27 23:29:53 +01:00

168 lines
11 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# OpenSpec Change: drive-mesh-protocol-v1
- **Change ID**: drive-mesh-protocol-v1
- **Package**: — (cross-cutting wire-protocol spec consumed by `@suaveplan/drive-mesh` and `@suaveplan/network-plugin-content-delivery`)
- **Capability**: `drive-mesh-protocol` (new)
- **Status**: PROPOSED
- **Date**: 2026-05-02
---
## Summary
Define the canonical wire protocol for chunk discovery, request, and delivery across the mesh. Three envelope types — `cd:advertise`, `cd:request`, `cd:response` — with three request forms (`whole` / `specific` / `bitset`), bitpacker-compressed presence bitsets, response-side presence piggyback, and a probabilistic trust model. The protocol is a contract, not an implementation: it lives in its own capability spec so that `@suaveplan/drive-mesh` (the requester side) and `@suaveplan/network-plugin-content-delivery` (the forwarder/redundancy side) can be developed and tested against the same source of truth.
---
## Why
The current docs ([`drive-mesh-port`](../drive-mesh-port/proposal.md), [`network-plugin-content-delivery`](../network-plugin-content-delivery/proposal.md)) describe `cd:request` per chunk. This produces:
1. **Wire amplification.** A 1,000-chunk file with 800 missing chunks generates 800 request envelopes — ~16 KiB of pure framing overhead per file before any bytes flow. With a 50-peer mesh gossiping 200 active files, the control plane saturates before the data plane gets a turn.
2. **No standardized advertisement encoding.** `MESH-GOSSIP-01` says "gzipped bitset" without pinning a schema, codec, or framing. The 10 bitpacker packages already shipped (`@suaveplan/bitpacker`, `bitpacker-compressed`, `bitpacker-network`, `bitpacker-schema`, `bitpacker-streaming`, `bitpacker-strategies`, `bitpacker-delta`, `bitpacker-game`, `bitpacker-validation`, `bitpacker-crypto`) cover this exactly — but without a binding contract every implementer would pick a different one.
3. **No trust model beyond bad-sig.** A peer can advertise chunks it doesn't have indefinitely without consequence (until we ask, get nothing, and waste the round-trip). The existing `MESH-SCORE-01` only triggers on bad signatures.
4. **No response piggyback.** Every request that returns chunks is a missed opportunity to learn the holder's full inventory for that file. With piggyback, an active partner avoids a separate advertisement cycle.
5. **Empty-array semantics ambiguous.** "Empty `chunkIds` array" could mean "request nothing" or "request everything." Current docs leave the reader guessing.
The architecture this proposal codifies is documented in [`docs/drive/mesh-protocol.md`](../../../docs/drive/mesh-protocol.md). This change folder is the formal OpenSpec mandate for that design.
---
## What changes
### ADDED — `drive-mesh-protocol` capability spec
22 ADDED requirements under `specs/drive-mesh-protocol/spec.md`:
- **`DMP-01..05`** — `ChunkRequest` discriminated union, three forms, encoding selection threshold (>64 chunks → bitset).
- **`DMP-06..09`** — `ChunkRequestEnvelope` priority tier, `maxChunks` cap, presence-bitset length invariant, generation counter monotonicity.
- **`DMP-10..12`** — Advertisement throttling, response-side presence piggyback, chunkId ordering.
- **`DMP-13..17`** — Verification (Ed25519 + AEAD + BLAKE3), trust-decay model, per-file scope, sample-verification rate.
- **`DMP-18..19`** — Error envelope shape and codes.
- **`DMP-20..22`** — Codec mandates (bitpacker-network framing, bitpacker-schema validation), empty-request rejection.
### MODIFIED — references in downstream proposals
- `drive-mesh-port/proposal.md` — replace inline `cd:request`/`cd:advertise` descriptions with references to this capability.
- `network-plugin-content-delivery/proposal.md` — same for the forwarder/redundancy side.
### REMOVED — none.
This is purely additive at the protocol layer; no existing requirements are revoked.
---
## Source mapping
> Genesis-original — no `enterprise/<pkg>.md` reference exists for this protocol because it is being designed for the first time on the genesis branch. Antecedents: BitTorrent's bitfield message (BEP-3), IPFS Bitswap (want-list reconciliation), and Hypercore's wire protocol (have/want).
| Requirement | Source / inspiration | Notes |
|---|---|---|
| Three request forms | Genesis-original; partial parallel to BEP-3 `bitfield` + `request` | We add `kind: 'whole'` to disambiguate the BitTorrent "I have nothing yet" case. |
| Bitset encoding via `@suaveplan/bitpacker-compressed` | Genesis — uses an already-shipped foundation library | RLE + DEFLATE; ~12 bytes for an 800-bit map with long runs. |
| Generation counter | Hypercore protocol; vector-clock-lite | Wraps mod 2^32; one-cycle anomaly tolerated. |
| Response presence piggyback | Genesis-original | Saves a round-trip for active partners. |
| Probabilistic trust decay | Genesis-original | Bad-sig still terminal; missed-advertisement is recoverable. |
| Error codes | Standard pattern from `@suaveplan/error` | `defineCodes("DMP", [...])`. |
### Requirements with no source line
| Requirement | Justification |
|---|---|
| 64-chunk threshold for bitset cutover | Empirically: 4 bytes/chunkId × 64 = 256 bytes; bitpacker-compressed of 64-bit map is typically <40 bytes; cutover in the 60-70 range is correct. |
| 1024-entry generation LRU | Bounded memory while supporting 1k peer × 1k file fan-out. |
| 5% miss rate threshold for trust demotion | Allows for clock skew between advertise and request without false positives; aggressive enough to neutralize a buggy peer within ~20 requests. |
---
## Parity Checklist
> No upstream `suaveplan/main` analogue exists. Genesis-original protocol.
| Capability | Upstream | Migrated | Notes |
|---|---|---|---|
| Bitset advertisements | n/a | ✅ | Genesis-original; `MESH-GOSSIP-01` ambiguous, this proposal pins it. |
| Three request forms | n/a | ✅ | Whole / specific / bitset. |
| Per-file trust scoping | n/a | ✅ | New on genesis. |
| Response presence piggyback | n/a | ✅ | New on genesis. |
| Generation counter | n/a | ✅ | New on genesis. |
| Per-(peerId, fileId) trust score | partial (suaveplan had per-peer) | ⬆️ | Enhanced: per-file scope prevents reputation laundering. |
---
## Out of scope
- **Implementation of `@suaveplan/drive-mesh` itself.** Handled by [`drive-mesh-port`](../drive-mesh-port/proposal.md) which depends on this protocol.
- **Implementation of `@suaveplan/network-plugin-content-delivery`.** Handled by its own proposal which consumes this protocol.
- **DHT-style chunk discovery.** Out of scope — this protocol assumes peers are already connected via the mesh; how they got there is a network-layer concern (`network-plugin-registry`, `network-discovery-*`).
- **Encryption of envelopes.** Already covered by `network-plugin-protocol` (length-prefix → CryptoTransform → compressStream pipeline).
- **Cross-mesh forwarding.** `network-plugin-relay` carries these envelopes across mesh boundaries; loop-prevention and TTL are its concern, not this protocol's.
---
## Verification approach
- `tests/three-form-roundtrip.test.ts` — every `ChunkRequest` form roundtrips through encode/decode.
- `tests/bitset-rle.test.ts` — fast-check property: `decode(encode(bitset)) === bitset` for random bit-densities.
- `tests/generation-monotonic.test.ts` — out-of-order advertisements drop correctly.
- `tests/piggyback-presence.test.ts` — receiving a `cd:response` updates peer-index without a separate `cd:advertise`.
- `tests/trust-decay.test.ts` — repeatedly-advertising-but-not-delivering peer loses trust score predictably; bad sig terminates.
- `tests/cap-respected.test.ts` — `maxChunks` is honored by responders; over-cap requests rejected with `DMP.MAX_CHUNKS_EXCEEDED`.
- `tests/empty-specific-rejected.test.ts` — `kind: 'specific'` with `chunkIds: []` rejected as `DMP.EMPTY_REQUEST`.
- `tests/bitset-length-invariant.test.ts` — advertisement with `presence.length` ≠ `totalChunks` rejected.
- `tests/error-envelope-shape.test.ts` — every `ChunkErrorCode` round-trips.
- `tests/codec-mandate.test.ts` — payloads not encoded via bitpacker-network framing fail validation.
These belong in a new package `@suaveplan/drive-mesh-protocol` (types + codecs + validators only — zero runtime side effects), which both `drive-mesh` and `network-plugin-content-delivery` will consume.
---
## Impact
### Affected dependents
Run `bunx turbo ls --filter=...@suaveplan/drive-mesh-protocol` once the package exists.
- `@suaveplan/drive-mesh` — [ ] tests run / [ ] docs reviewed / [ ] no action needed (reason: depends on this protocol; will be implemented after this lands)
- `@suaveplan/network-plugin-content-delivery` — [ ] tests run / [ ] docs reviewed / [ ] no action needed (reason: forwards these envelopes; will reference this spec)
- `@suaveplan/network-plugin-relay` — [ ] tests run / [ ] docs reviewed / [ ] no action needed (reason: carries these envelopes across meshes via `CrossMeshEnvelope`)
- `@suaveplan/drive-transport-webrtc` — [ ] tests run / [ ] docs reviewed / [ ] no action needed (reason: low-level transport; doesn't parse envelope semantics, but framing must round-trip)
- `@suaveplan/drive-transport-https` — [ ] tests run / [ ] docs reviewed / [ ] no action needed (reason: server-side fallback; only relevant insofar as the same envelope shapes are accepted via HTTP)
This is a **foundation-tier change** for the drive/network boundary: every package that exchanges chunks across the mesh references it. Per AGENTS.md §13 step 10, the dependent sweep is mandatory before archive.
---
## Migration
### From the previous design
Authors of [`drive-mesh-port`](../drive-mesh-port/proposal.md) and [`network-plugin-content-delivery`](../network-plugin-content-delivery/proposal.md) MUST update their proposals to:
1. Replace any inline description of "cd:request per chunk" with a reference to `DMP-01..05`.
2. Replace any inline description of "gzipped bitset advertisement" with a reference to `DMP-08..09` and the `@suaveplan/bitpacker-compressed` codec mandate (`DMP-20`).
3. Replace any per-chunk reputation logic with the `(peerId, fileId)`-scoped model in `DMP-14..17`.
Search command:
```bash
grep -rln "cd:request\|cd:advertise\|cd:response" openspec/changes/ --include="*.md"
```
### From `MESH-GOSSIP-01` (in `drive-mesh-port` spec)
`MESH-GOSSIP-01` becomes a thin wrapper over `DMP-08..09`. Recommended replacement text:
> The plugin SHALL emit `ChunkAdvertiseEnvelope` per `drive-mesh-protocol/spec.md` `DMP-08`. Receivers SHALL handle generation ordering per `DMP-09`. Encoding via `@suaveplan/bitpacker-compressed` per `DMP-20`.
This keeps `MESH-GOSSIP-01` as the entry point while delegating wire-format details to the protocol spec.
---
## References
- Architectural reference: [`docs/drive/mesh-protocol.md`](../../../docs/drive/mesh-protocol.md)
- System review: [`docs/system-review.md`](../../../docs/system-review.md) §1, §2, §10, §11
- Bitpacker package map: [`docs/drive/mesh-protocol.md`](../../../docs/drive/mesh-protocol.md) §10
- Companion change: [`drive-template-v1`](../drive-template-v1/proposal.md)