first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,53 @@
# decay
## Purpose
Implements the probabilistic-skip decay logic (DMP-15) the request scheduler uses to decide whether to include a peer in the candidate set on each new chunk request. When a peer's miss rate (`missing / advertised`) exceeds the floor (`0.05`), the probability of skipping the peer on each subsequent request is `(missRate - 0.05) * 20`, capped at `1.0`. The decay encodes the empirical observation that peers who advertise chunks they don't actually have should be progressively de-prioritised without being permanently blocked.
## Features
- `probabilityOfSkip(entry)` — pure function over `TrustEntry`; returns `[0, 1]`.
- `shouldSkip(entry, rng)` — deterministic decision given an injected `Rng`.
- Honours terminated entries (`probability === 1`) so the request scheduler never forgets a bad-sig event.
- Honours zero-advertised entries (`probability === 0`) so freshly-met peers are never punished.
## Usage: Basic Example
```ts
import { probabilityOfSkip, TrustMap } from "@suaveplan/drive-mesh-protocol";
const t = new TrustMap();
t.recordAdvertised("peer", "f", 100);
t.recordMissing("peer", "f", 30);
const p = probabilityOfSkip(t.get("peer", "f"));
console.log(p); // 1 (capped at 1.0; 0.30 - 0.05 = 0.25; 0.25 * 20 = 5)
```
## Usage: Advanced Example
```ts
import {
shouldSkip,
TrustMap,
type TrustEntry,
} from "@suaveplan/drive-mesh-protocol";
import { defaultRng } from "@suaveplan/bitpacker-network";
const trust = new TrustMap();
const rng = defaultRng();
function selectCandidates(peers: string[], fileId: string): TrustEntry[] {
return peers
.map((peerId) => trust.get(peerId, fileId))
.filter((entry) => !shouldSkip(entry, rng));
}
```
## API Reference
- `probabilityOfSkip(entry: TrustEntry): number` — returns `1` for terminated entries, `0` for `advertised === 0` or `missRate ≤ 0.05`, otherwise `(missRate - 0.05) * 20` clamped to `[0, 1]`.
- `shouldSkip(entry: TrustEntry, rng: Rng): boolean` — short-circuits when probability is `0` or `1` to avoid burning RNG draws.
## Implementation Notes
The `Math.max(0, p)` clamp is defence-in-depth — the early-return on `missRate <= floor` already guarantees `p > 0` past that point — but JIT optimisers handle it cheaply and it documents the intent. The slope (`20`) is chosen so a peer at 10% miss rate is skipped on 100% of requests; below 10% the skip probability falls off linearly toward the floor at 5%. Tuning these numbers requires re-pinning `TRUST_MISS_RATE_FLOOR` / `TRUST_DECAY_SLOPE` in the schemas module — never hard-code them at call sites.

View File

@@ -0,0 +1,68 @@
# sample
## Purpose
Implements the adaptive sample-verification rate (DMP-17). On every incoming `cd:advertise`, the protocol MAY request a single chunk from the advertised set to verify the peer actually has it. The default rate is 1-in-100 for healthy peers and 1-in-10 for "suspect" peers (trust score `< 0.95` but `> -Infinity`). Sample-verification failure increments the `missing` counter via DMP-15.
## Features
- `classify(entry)` — three-way bucket (`terminated | suspect | healthy`).
- `sampleRate(entry)` — denominator (`Infinity | 10 | 100`) — `Infinity` for terminated peers.
- `shouldSample(entry, rng)` — deterministic decision given an injected `Rng`.
- Constants pinned in `schemas/envelope.schema.ts` so dashboards and the spec share a source of truth.
## Usage: Basic Example
```ts
import {
classify,
sampleRate,
TrustMap,
} from "@suaveplan/drive-mesh-protocol";
const t = new TrustMap();
t.recordAdvertised("peer", "f", 100);
t.recordMissing("peer", "f", 10);
const e = t.get("peer", "f");
console.log(classify(e)); // "suspect"
console.log(sampleRate(e)); // 10
```
## Usage: Advanced Example
```ts
import {
classify,
shouldSample,
TrustMap,
} from "@suaveplan/drive-mesh-protocol";
import { defaultRng } from "@suaveplan/bitpacker-network";
const trust = new TrustMap();
const rng = defaultRng();
async function onIncomingAdvertise(
peerId: string,
fileId: string,
advertisedIds: number[]
): Promise<void> {
const e = trust.get(peerId, fileId);
if (classify(e) === "terminated") return;
if (shouldSample(e, rng)) {
const chunkId = advertisedIds[Math.floor(rng.nextFloat() * advertisedIds.length)];
const ok = await verifyAdvertisedChunk(peerId, fileId, chunkId);
if (!ok) trust.recordMissing(peerId, fileId, 1);
}
}
```
## API Reference
- `type TrustStatus = "terminated" | "suspect" | "healthy"`.
- `classify(entry: TrustEntry): TrustStatus`.
- `sampleRate(entry: TrustEntry): number` — denominator; `Infinity` for terminated.
- `shouldSample(entry: TrustEntry, rng: Rng): boolean`.
## Implementation Notes
Terminated peers are never sampled because the request scheduler shouldn't be talking to them in the first place; the function returns `false` early without consuming an RNG draw. The `1 / denom` probability uses 32-bit float division which has more than enough precision for a 1-in-100 / 1-in-10 decision; switching to integer division would buy nothing and force callers to track the denominator separately. Adjusting the suspect threshold or the per-status rate requires re-pinning the constants in `schemas/envelope.schema.ts` — the spec file MUST move in lockstep.

View File

@@ -0,0 +1,64 @@
# score
## Purpose
Implements `TrustMap`, the per-`(peerId, fileId)` trust state container the request scheduler in `drive-mesh` uses to decide which peers to talk to (DMP-13, DMP-14, DMP-16). Each entry tracks how many chunks a peer claimed to have via gossip vs. how many it failed to deliver on request, plus a terminal `terminated: true` flag set after a bad-signature event.
## Features
- `TrustMap` — mutable, in-process map of `TrustEntry` records keyed by `(peerId, fileId)`.
- `recordAdvertised(peerId, fileId, delta)` — increments the advertised counter; recomputes the score.
- `recordMissing(peerId, fileId, delta)` — increments the missing counter; recomputes the score.
- `terminate(peerId, fileId)` — pins the score to `-Infinity` after a bad-sig event (DMP-14).
- `canServe(peerId, fileId)` — query helper for the request scheduler.
- `dropPeer(peerId)` / `dropFile(fileId)` — cleanup hooks for the registry's `peer:left` / file-removal events.
- Iterable via `Symbol.iterator` for snapshot/persistence.
## Usage: Basic Example
```ts
import { TrustMap } from "@suaveplan/drive-mesh-protocol";
const trust = new TrustMap();
trust.recordAdvertised("peer-A", "fileId", 100);
trust.recordMissing("peer-A", "fileId", 5);
console.log(trust.get("peer-A", "fileId").score); // 0.95
```
## Usage: Advanced Example
```ts
import {
classify,
sampleRate,
shouldSkip,
TrustMap,
} from "@suaveplan/drive-mesh-protocol";
const trust = new TrustMap();
function pickPeers(candidates: string[], fileId: string, rng): string[] {
return candidates.filter((peerId) => {
const e = trust.get(peerId, fileId);
if (!trust.canServe(peerId, fileId)) return false;
return !shouldSkip(e, rng);
});
}
function onBadSignature(peerId: string, fileId: string): void {
trust.terminate(peerId, fileId);
}
function onPeerDisconnect(peerId: string): void {
trust.dropPeer(peerId);
}
```
## API Reference
- `class TrustMap` — `get(peerId, fileId)`, `recordAdvertised(peerId, fileId, delta)`, `recordMissing(peerId, fileId, delta)`, `terminate(peerId, fileId)`, `canServe(peerId, fileId)`, `dropPeer(peerId)`, `dropFile(fileId)`, `size`, `[Symbol.iterator]()`.
- `interface TrustEntry` — `{ peerId, fileId, advertised, missing, score, terminated }`.
## Implementation Notes
`recordAdvertised` and `recordMissing` are no-ops on terminated entries to prevent a bad-sig peer from regaining trust by spamming further advertisements; the score stays at `-Infinity` regardless of subsequent counters. The score formula is `1 - missing/advertised` with `score = 1` when `advertised === 0` so a freshly-met peer starts trusted by default — terminated entries override this. The iterator yields `TrustEntry` records by reference; consumers must NOT mutate them outside the public methods.