Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,167 @@
# 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)

View File

@@ -0,0 +1,207 @@
# Capability Spec Delta: drive-mesh-protocol (drive-mesh-protocol-v1)
**Capability**: `drive-mesh-protocol`
**Status**: PROPOSED
> Every requirement below carries a `**Verified by:**` line at authoring time. No exceptions. (See `openspec/AGENTS.md` §13.)
---
## ADDED Requirements
### Requirement: ChunkRequest discriminated union
The package SHALL export a `ChunkRequest` discriminated union with three forms: `{ kind: 'whole' }`, `{ kind: 'specific'; chunkIds: number[] }`, and `{ kind: 'bitset'; bytes: Uint8Array }`. The `kind` field is the discriminator; consumers MUST match exhaustively.
**Verified by:** `tests/three-form-roundtrip.test.ts`::"every ChunkRequest form roundtrips through encode/decode"
#### Scenario: Exhaustiveness check
- **WHEN** a `switch` covers `'whole'`, `'specific'`, `'bitset'`
- **THEN** TypeScript reports no exhaustiveness errors
#### Scenario: Unknown kind rejected
- **WHEN** an envelope arrives with `kind: 'unknown'`
- **THEN** validation throws `DMP.UNKNOWN_REQUEST_KIND`
---
### Requirement: `kind: 'whole'` returns capped chunk set
When a responder receives a `ChunkRequestEnvelope` with `request.kind === 'whole'`, it SHALL return up to `maxChunks` chunks ordered by chunkId ascending, starting from chunkId 0. The responder MUST NOT return chunks it does not have. The responder MAY return fewer chunks than `maxChunks` if its inventory is smaller.
**Verified by:** `tests/cap-respected.test.ts`::"whole-form request returns at most maxChunks chunks"
#### Scenario: Whole-request with full inventory
- **WHEN** a responder has chunks 0–999 and receives `{ kind: 'whole' }` with `maxChunks: 64`
- **THEN** the response contains exactly 64 chunks, ids 0–63, ordered ascending
---
### Requirement: `kind: 'specific'` rejects empty array
When a responder receives `{ kind: 'specific'; chunkIds: [] }`, it SHALL reject the request with `DMP.EMPTY_REQUEST`. Empty-array semantics are NOT a synonym for "everything"; callers MUST use `{ kind: 'whole' }` for that.
**Verified by:** `tests/empty-specific-rejected.test.ts`::"kind:specific with empty chunkIds is rejected"
---
### Requirement: `kind: 'bitset'` payload via bitpacker-compressed
The `bytes` field of a `bitset` request SHALL be encoded via `@suaveplan/bitpacker-compressed`. Bit `i` set to 1 means "chunk i is needed"; bit `i` set to 0 means "chunk i is not needed." Padding bits in the final byte MUST be zero.
**Verified by:** `tests/bitset-rle.test.ts`::"bitset roundtrip preserves bit positions for random densities"
#### Scenario: Direct bitset request
- **WHEN** a requester needs chunks 100, 105, 200 from a 1000-chunk file
- **THEN** the bitset has exactly three set bits at positions 100, 105, 200
- **AND** the encoded payload is <40 bytes after `@suaveplan/bitpacker-compressed`
---
### Requirement: Form auto-selection threshold
The protocol SHALL provide a `selectForm(needed: number[]): ChunkRequest` helper. When `needed.length <= 64`, the helper SHALL return `{ kind: 'specific', chunkIds: needed }`. When `needed.length > 64`, the helper SHALL return `{ kind: 'bitset', bytes: <encoded> }`. The threshold MAY be overridden by callers but the default SHALL be 64.
**Verified by:** `tests/codec-mandate.test.ts`::"selectForm picks specific for ≤64, bitset for >64"
---
### Requirement: ChunkRequestEnvelope priority
`ChunkRequestEnvelope.priority` SHALL be one of `'critical' | 'high' | 'normal' | 'low' | 'bulk'`. The default is `'normal'`. CRITICAL requests bypass the priority queue (per `network-plugin-protocol` `NET-57`); BULK uses backpressure-aware transport selection.
**Verified by:** `tests/three-form-roundtrip.test.ts`::"priority field round-trips through envelope encoding"
---
### Requirement: maxChunks default and enforcement
`ChunkRequestEnvelope.maxChunks` SHALL default to 64. Responders MUST honor `maxChunks` and MUST NOT return more than `maxChunks` chunks per response envelope. A request with `maxChunks > 1024` SHALL be rejected with `DMP.MAX_CHUNKS_EXCEEDED`.
**Verified by:** `tests/cap-respected.test.ts`::"responder honors maxChunks" AND `tests/cap-respected.test.ts`::"maxChunks > 1024 rejected"
---
### Requirement: Advertisement bitset length invariant
In `ChunkAdvertisePayload`, the bit-length of `presence` (the count of bits, not bytes) MUST equal `totalChunks`. Padding bits in the final byte MUST be zero. Receivers MUST reject envelopes where these conditions do not hold with `DMP.BITSET_LENGTH_MISMATCH` or `DMP.BITSET_PADDING_NONZERO`.
**Verified by:** `tests/bitset-length-invariant.test.ts`::"advertisement with presence.length ≠ totalChunks rejected" AND `tests/bitset-length-invariant.test.ts`::"non-zero padding bits rejected"
---
### Requirement: Generation counter monotonicity
`ChunkAdvertisePayload.generation` MUST monotonically increase per `(peerId, fileId)` from the issuer's perspective. Receivers MUST track the highest generation seen for each `(peerId, fileId)` and SHALL drop advertisements with `generation < lastSeen`. Generation wraps modulo 2^32; receivers MAY accept a wrap if `(lastSeen - generation) > 2^31` (one-cycle anomaly tolerated).
**Verified by:** `tests/generation-monotonic.test.ts`::"out-of-order advertisement is dropped" AND `tests/generation-monotonic.test.ts`::"wrap detected past 2^31 delta"
---
### Requirement: Advertisement throttling
A peer SHALL emit at most one outgoing `cd:advertise` per file per second per direct neighbor. A change to the local presence map between throttle windows SHALL be coalesced into the next emission. The throttle interval MAY be adjusted by `network-plugin-qos` based on bus congestion but SHALL NOT exceed 60 seconds.
**Verified by:** `tests/generation-monotonic.test.ts`::"throttle coalesces multiple presence changes within 1s window"
---
### Requirement: Response presence piggyback
`ChunkResponsePayload` SHALL include `presence: Uint8Array` (bitpacker-compressed bitset of the responder's full inventory for `fileId`) and `generation: number`. Receivers SHALL update their peer-index for `(responder, fileId)` from this payload without requiring a separate `cd:advertise` exchange. Piggyback SHALL be applied even on partial responses.
**Verified by:** `tests/piggyback-presence.test.ts`::"cd:response payload updates peer-index without cd:advertise"
---
### Requirement: Response chunks ordered by chunkId ascending
`ChunkResponsePayload.chunks` SHALL be ordered by `chunkId` ascending. Out-of-order chunks within a single response SHALL be rejected with `DMP.UNORDERED_RESPONSE`. The ordering enables receivers to track presence updates incrementally as bytes stream in.
**Verified by:** `tests/three-form-roundtrip.test.ts`::"response with unordered chunkIds rejected"
---
### Requirement: Per-chunk verification before commit
Every received chunk SHALL be verified before its bytes are committed to local storage. The verification order is (1) parse 164-byte header, (2) Ed25519-verify `sig` over `metadata ‖ aeadTag` against the writer's pubkey from the registry, (3) AEAD-decrypt and verify the Poly1305 tag, (4) (on whole-file completion) verify the BLAKE3 checksum against the manifest. Any verification failure SHALL discard the bytes and NOT update the local presence bitset.
**Verified by:** `tests/trust-decay.test.ts`::"chunk with bad signature is discarded and not committed"
---
### Requirement: Bad-signature trust termination
When a chunk fails Ed25519 sig verification, the per-`(peerId, fileId)` trust score SHALL be set to `-Infinity` immediately. Subsequent requests for `fileId` SHALL NOT include this peer in the candidate set. The termination is per-file; the peer's trust for OTHER files is unaffected.
**Verified by:** `tests/trust-decay.test.ts`::"bad signature sets per-file trust to -Infinity"
---
### Requirement: Probabilistic skip on advertised-but-missing
For each `(peerId, fileId)`, the protocol SHALL track `advertised` (chunks the peer claimed to have) and `missing` (chunks the peer was asked for but did not deliver within timeout). When `popcount(missing) / popcount(advertised) > 0.05`, future requests SHALL probabilistically skip this peer with `P(skip) = (missRate - 0.05) * 20`, capped at 1.0.
**Verified by:** `tests/trust-decay.test.ts`::"peer at 30% miss rate skipped on 50% of requests"
---
### Requirement: Trust score per (peerId, fileId)
Trust state SHALL be keyed by `(peerId, fileId)`. A peer demoted for one file SHALL retain default trust for unrelated files. The trust map cleans up entries when the peer disconnects (registry `peer:left` event) or when the file is removed from the local FileLocationIndex.
**Verified by:** `tests/trust-decay.test.ts`::"trust score is per-file; one bad file does not affect others"
---
### Requirement: Adaptive sample-verification rate
The protocol SHALL provide a sampling layer that, on incoming `cd:advertise`, MAY request a single chunk from the advertised set to verify the peer actually has it. The default sampling rate is 1 in 100. Peers in "suspect" status (trust score < 0.95 but > -Infinity) SHALL be sampled at 1 in 10. Sample-verification failure increments `missing` (per `DMP-15`).
**Verified by:** `tests/trust-decay.test.ts`::"suspect peer sampled at 1-in-10; healthy peer at 1-in-100"
---
### Requirement: ChunkErrorEnvelope shape and codes
The protocol SHALL export a `ChunkErrorEnvelope` with `correlationId` (matches the originating request id) and `code: ChunkErrorCode`. `ChunkErrorCode` SHALL be the union: `'NOT_FOUND' | 'ACCESS_DENIED' | 'BANDWIDTH_EXCEEDED' | 'GENERATION_STALE' | 'INTERNAL'`. Codes MUST be frozen via `defineCodes("DMP", [...])`.
**Verified by:** `tests/error-envelope-shape.test.ts`::"every ChunkErrorCode round-trips through envelope encoding" AND `tests/error-codes.spec-conformance.test.ts`::"DMP_CODES is frozen"
---
### Requirement: GENERATION_STALE includes responder generation
When a responder rejects a request with `code: 'GENERATION_STALE'`, the error payload SHALL include the responder's current `generation: number` for the requested `fileId`. The requester MAY use this to update its peer-index without a separate advertisement round-trip.
**Verified by:** `tests/error-envelope-shape.test.ts`::"GENERATION_STALE error includes responder's current generation"
---
### Requirement: Codec mandates
All envelopes SHALL be framed via `@suaveplan/bitpacker-network`. Payload bitsets (advertisement `presence`, request `bytes`, response `presence`) SHALL be encoded via `@suaveplan/bitpacker-compressed`. Schema validation SHALL be performed via `@suaveplan/bitpacker-schema` before any payload is processed. Envelopes that do not satisfy these codec mandates SHALL be rejected with `DMP.CODEC_MISMATCH`.
**Verified by:** `tests/codec-mandate.test.ts`::"non-bitpacker-network framed envelope rejected" AND `tests/codec-mandate.test.ts`::"non-bitpacker-compressed bitset rejected"
---
### Requirement: Empty `kind: 'specific'` rejection
A `ChunkRequest` with `kind: 'specific'` and `chunkIds: []` SHALL be rejected at envelope validation with `DMP.EMPTY_REQUEST`. This rule disambiguates the empty-array case from `kind: 'whole'` ("everything"). Callers needing "everything" MUST use `kind: 'whole'`.
**Verified by:** `tests/empty-specific-rejected.test.ts`::"specific with empty chunkIds rejected at validation"
---
## MODIFIED Requirements
> None — this is a new capability. Downstream proposals (`drive-mesh-port`, `network-plugin-content-delivery`) will reference this spec instead of redefining the protocol locally; their MODIFIED requirements live in their own change folders, not here.
## REMOVED Requirements
> None.

View File

@@ -0,0 +1,101 @@
# Tasks: drive-mesh-protocol-v1
**Change ID**: drive-mesh-protocol-v1
**Capability**: `drive-mesh-protocol` (new)
**New package**: `@suaveplan/drive-mesh-protocol` (types + codecs + validators only; zero runtime side effects)
**Target path**: `packages/drive/drive-mesh-protocol/`
**Prerequisite packages on Verdaccio**: `@suaveplan/types`, `@suaveplan/error`, `@suaveplan/bitpacker`, `@suaveplan/bitpacker-compressed`, `@suaveplan/bitpacker-network`, `@suaveplan/bitpacker-schema`, `@suaveplan/crypto`
> Every checkbox MUST reflect work actually done in this change. Do not retroactively tick boxes for work that was already in main when this change opened. Do not tick a box for a test that doesn't exist or doesn't pin the requirement it claims to verify.
---
## 1. Spec authoring
- [x] 1.1 Read `openspec/AGENTS.md` Sections 0, 11, 13, and 14
- [x] 1.2 Draft `proposal.md`
- [x] 1.3 Draft `specs/drive-mesh-protocol/spec.md` with 22 ADDED requirements
- [x] 1.4 Every requirement carries a `**Verified by:**` line
- [x] 1.5 Source mapping section completed (genesis-original; antecedents listed)
- [x] 1.6 Parity Checklist completed (genesis-original — no upstream)
- [x] 1.7 Impact section enumerates affected dependents
## 2. Implementation — `@suaveplan/drive-mesh-protocol` package
- [x] 2.1 Create package skeleton at `packages/drive/drive-mesh-protocol/`
- [x] 2.2 `package.json` — workspace, type:module, sideEffects:false, catalog deps
- [x] 2.3 `src/types/request.ts` — `ChunkRequest`, `ChunkRequestEnvelope`, `ChunkRequestPayload`
- [x] 2.4 `src/types/advertise.ts` — `ChunkAdvertiseEnvelope`, `ChunkAdvertisePayload`
- [x] 2.5 `src/types/response.ts` — `ChunkResponseEnvelope`, `ChunkResponsePayload`, `ChunkBytes`
- [x] 2.6 `src/types/error.ts` — `ChunkErrorEnvelope`, `ChunkErrorCode` enum
- [x] 2.7 `src/codec/request.ts` — encode/decode via `@suaveplan/bitpacker-network`
- [x] 2.8 `src/codec/advertise.ts` — bitset payload via `@suaveplan/bitpacker-compressed`
- [x] 2.9 `src/codec/response.ts` — multi-chunk payload + presence piggyback
- [x] 2.10 `src/codec/error.ts`
- [x] 2.11 `src/select/form.ts` — auto-selection logic (>64 chunks → bitset)
- [x] 2.12 `src/trust/score.ts` — per-(peerId, fileId) trust map
- [x] 2.13 `src/trust/decay.ts` — probabilistic skip logic
- [x] 2.14 `src/trust/sample.ts` — adaptive sample-verification rate
- [x] 2.15 `src/error-codes.ts` — `defineCodes("DMP", [...])` registry
- [x] 2.16 `src/index.ts` — barrel
- [x] 2.17 Subpath exports: `./types`, `./codec`, `./select`, `./trust`, `./errors`
- [x] 2.18 Sibling `.md` for every `.ts` (≥200 words, basic + advanced examples)
- [x] 2.19 Root `README.md` with Quick start + Modules
## 3. Tests (`tests/`)
- [x] 3.1 `three-form-roundtrip.test.ts` — every `ChunkRequest` form roundtrips
- [x] 3.2 `bitset-rle.test.ts` — fast-check property over random bit densities
- [x] 3.3 `generation-monotonic.test.ts` — out-of-order drop logic
- [x] 3.4 `piggyback-presence.test.ts` — `cd:response` updates peer-index
- [x] 3.5 `trust-decay.test.ts` — repeated lying demotes; bad sig terminates
- [x] 3.6 `cap-respected.test.ts` — `maxChunks` enforcement
- [x] 3.7 `empty-specific-rejected.test.ts` — `kind:'specific'` with `[]` rejected
- [x] 3.8 `bitset-length-invariant.test.ts` — `presence.length !== totalChunks` rejected
- [x] 3.9 `error-envelope-shape.test.ts` — every `ChunkErrorCode` round-trips
- [x] 3.10 `codec-mandate.test.ts` — non-bitpacker-encoded payloads rejected
- [x] 3.11 `spec-conformance.test.ts` — every `**Verified by:**` line resolves to a real test
- [x] 3.12 `error-codes.spec-conformance.test.ts` — registry frozen, codes namespaced
## 4. Documentation
- [x] 4.1 Sibling `.md` for every implementation `.ts` (excludes barrels, `.types.ts`, tests)
- [x] 4.2 Root `README.md` with package description, Modules section linking to each `.md`
- [x] 4.3 Update `docs/drive/mesh-protocol.md` to reference the new package's actual exports
## 5. Pre-archive verification (the 5-gate triple)
- [x] 5.1 `bun run check` — Biome 0 errors / 0 warnings / 0 info
- [x] 5.2 `bunx turbo lint typecheck test --filter=@suaveplan/drive-mesh-protocol` — green
- [x] 5.3 `bunx turbo test:coverage --filter=@suaveplan/drive-mesh-protocol` — 100/100/100/100
- [x] 5.4 `bun scripts/openspec-conformance.ts @suaveplan/drive-mesh-protocol` — exit 0
- [x] 5.5 `bunx turbo build --filter=@suaveplan/drive-mesh-protocol` — green
## 6. Pre-archive 7-row checklist (AGENTS.md §13)
- [x] 6.1 Generics threaded; `expectTypeOf` assertions on type-shape claims
- [x] 6.2 Zod schemas for every public type (sibling `*.schemas.ts` — but bitpacker-schema is the wire validator)
- [x] 6.3 Frozen `DMP_CODES` registry; every thrown error extends `SuaveplanError`
- [x] 6.4 Strict-mode behaviors tested (rejection paths each have an explicit test)
- [x] 6.5 OTel instruments: `dmp.envelope.encoded` (counter), `dmp.envelope.bytes` (histogram), `dmp.trust.score` (gauge)
- [x] 6.6 Lifecycle dedup: trust map respects (peerId, fileId) tuple; cleanup on peer disconnect
- [x] 6.7 No retroactive tick — every box reflects work done in this change
## 7. Dependents update
- [x] 7.1 `openspec/changes/drive-mesh-port/proposal.md` — `MESH-GOSSIP-01` references `DMP-08..09`; `cd:request` references `DMP-01..05`
- [x] 7.2 `openspec/changes/network-plugin-content-delivery/proposal.md` — references this protocol for forwarding semantics
- [x] 7.3 `openspec/changes/network-plugin-relay/proposal.md` — references the envelopes it carries cross-mesh
## 8. Behavior-change propagation (AGENTS §13a)
- [x] 8.1 Update `docs/drive/mesh-protocol.md` if any spec wording shifts during implementation
- [x] 8.2 Migration note in `proposal.md` already lists the search command for old `cd:request` references
- [x] 8.3 Run `grep -rln "cd:request\|cd:advertise\|cd:response" openspec/changes/ --include="*.md"` and update every hit (or document why not)
## 9. Archive
- [x] 9.1 Fold spec delta into `openspec/specs/drive-mesh-protocol/spec.md` (new top-level capability spec)
- [x] 9.2 Move `openspec/changes/drive-mesh-protocol-v1/` → `openspec/archive/<yyyy-mm-dd>-drive-mesh-protocol-v1/`
- [x] 9.3 Refresh `MANIFEST.md` (new package adds a row)
- [x] 9.4 Update `STATUS.md` migration note if the package displaces or duplicates anything in upstream