This commit is contained in:
Raven Scott
2026-05-20 20:19:20 -04:00
parent 040f1d05ea
commit d70060ec4e
365 changed files with 23547 additions and 431 deletions
+3 -3
View File
@@ -15,7 +15,7 @@ on:
jobs: jobs:
unit-all: unit-all:
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 60 timeout-minutes: 75
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
@@ -26,12 +26,12 @@ jobs:
- name: Install real_tests deps - name: Install real_tests deps
working-directory: real_tests working-directory: real_tests
run: npm install run: npm install
- name: Run all unit tests (42 modules) - name: Run all unit tests (52 modules)
run: chmod +x real_tests/run-all.sh && ./real_tests/run-all.sh run: chmod +x real_tests/run-all.sh && ./real_tests/run-all.sh
integration: integration:
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 60 timeout-minutes: 75
needs: unit-all needs: unit-all
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
+1 -1
View File
@@ -67,7 +67,7 @@ Demos: [`../examples/p2p-agent-workflow-demo/`](../examples/p2p-agent-workflow-d
Demo: [`../examples/p2p-chaos-mesh-demo/`](../examples/p2p-chaos-mesh-demo/). Demo: [`../examples/p2p-chaos-mesh-demo/`](../examples/p2p-chaos-mesh-demo/).
**Production-ready (Wave 5):** all 42 modules meet [`_shared/PRODUCTION.md`](_shared/PRODUCTION.md) — presence-tier `docs/api.md`, mermaid architecture, ≥3 unit tests, clean app source (no pending markers). **Production-ready (Wave 6):** all **52** modules meet [`_shared/PRODUCTION.md`](_shared/PRODUCTION.md) — presence-tier `docs/api.md`, mermaid architecture, ≥3 unit tests, clean app source (no pending markers). Network stack: [`_shared/WAVE6_NETWORK_STACK.md`](_shared/WAVE6_NETWORK_STACK.md).
### hyper-p2p-presence (v0.3.0) ### hyper-p2p-presence (v0.3.0)
- **Location**: `./hyper-p2p-presence/` - **Location**: `./hyper-p2p-presence/`
+14
View File
@@ -12,3 +12,17 @@ Modules throw plain `Error` instances (no error codes enum in v0.2). Typical mes
| `Invalid parent capability` | capabilities delegation failure | | `Invalid parent capability` | capabilities delegation failure |
Prefer stable message substrings when asserting in tests. Prefer stable message substrings when asserting in tests.
## Wave 6 network modules
| Pattern | Meaning |
|---------|---------|
| `peerId required` | Missing peer identifier |
| `maxDegree exceeded` | overlay-topology neighbor cap |
| `maxConcurrent exceeded` | connection-pool cap |
| `pathCount must be` | multipath-fanout bounds |
| `priority must be control\|data\|bulk` | flow-shaper queue |
| `unknown circuit` / `hopPeers required` | circuit-loom cell errors |
| `unknown offer` | protocol-handshake |
| `bytes must be non-negative` | bandwidth-broker |
| `invalid rtt or loss` | congestion-signal |
+52
View File
@@ -0,0 +1,52 @@
# Wave 6 network stack composition
Layering for the 10 Wave 6 networking modules over existing underlay (Hyperswarm, `relay-tunnel`, `gossip-mesh`, `peer-scheduler`, `bucket-rate-limit`).
## Layer 1 — Session and topology
| Module | Role | Pairs with |
|--------|------|------------|
| `hyper-p2p-protocol-handshake` | Version/features before app mux | `capabilities`, `rpc` |
| `hyper-p2p-connection-pool` | Warm lanes, idle teardown | `topic-lease`, `peer-scheduler` |
| `hyper-p2p-overlay-topology` | Explicit neighbor graph | `peer-scheduler`, `gossip-mesh` |
## Layer 2 — Measurement and routing
| Module | Role | Pairs with |
|--------|------|------------|
| `hyper-p2p-link-probe` | RTT/jitter matrix | `presence` |
| `hyper-p2p-anycast-selector` | Lowest-latency capability pick | `link-probe`, `intent-router` |
| `hyper-p2p-congestion-signal` | Gossip rate hints | `bucket-rate-limit` |
## Layer 3 — Capacity and egress
| Module | Role | Pairs with |
|--------|------|------------|
| `hyper-p2p-bandwidth-broker` | Inter-peer send credits | `trust-graph`, `mycelium-pool` |
| `hyper-p2p-flow-shaper` | Priority QoS queues | `activity-queue`, `intent-router` |
| `hyper-p2p-multipath-fanout` | Striping + reassembly | `relay-tunnel`, `dedup-filter` |
## Layer 4 — Circuits
| Module | Role | Pairs with |
|--------|------|------------|
| `hyper-p2p-circuit-loom` | Multi-hop CREATE/EXTEND/RELAY | `relay-tunnel`, `overlay-topology` |
Hyperswarm provides discovery and single-hop connectivity only; `circuit-loom` does not replace NAT punch.
```mermaid
flowchart TB
HS[Hyperswarm underlay]
RT[relay-tunnel]
GM[gossip-mesh]
HS --> RT
HS --> GM
PH[protocol-handshake] --> CP[connection-pool]
CP --> OT[overlay-topology]
LP[link-probe] --> AS[anycast-selector]
AS --> CS[congestion-signal]
BB[bandwidth-broker] --> FS[flow-shaper]
FS --> MF[multipath-fanout]
OT --> CL[circuit-loom]
RT --> CL
```
+3
View File
@@ -11,3 +11,6 @@
## v0.2.1 ## v0.2.1
- Production docs, input validation, third test, integration notes. - Production docs, input validation, third test, integration notes.
## v0.3.0
- Wave 6: presence-tier API tables, architecture wire section, validation test.
+23 -3
View File
@@ -18,15 +18,35 @@ const mod = new HyperP2PActivityQueue(opts)
## Methods ## Methods
See [`index.js`](../index.js) for the full method list. Core operations implement **distributed work queue with priority, DLQ, and optional vector-clock ordering**. | Method | Returns | Notes |
|--------|---------|-------|
| `ack(...)` | See source | — |
| `claim(...)` | See source | — |
| `close(...)` | See source | — |
| `enqueue(...)` | See source | — |
| `getDeadLetters(...)` | See source | — |
| `getQueueDepth(...)` | See source | — |
| `nack(...)` | See source | — |
| `ready(...)` | See source | — |
## Events ## Events
The instance extends `EventEmitter`. Common events: `closed`, plus module-specific events documented in source. | Event | Description |
|-------|-------------|
| `ack` | Module-specific |
| `claim` | Module-specific |
| `closed` | Module-specific |
| `enqueue` | Module-specific |
| `nack` | Module-specific |
| `remote-claim` | Module-specific |
## Metrics
Call `getStats()` when implemented for counters (Wave 6 network modules always expose stats).
## P2P ## P2P
When `topic` is set, `ready()` joins Hyperswarm and opens Protomux channel `activity-queue/v1` via [`_shared/p2p-bare.js`](../_shared/p2p-bare.js). When `topic` is set, `ready()` joins Hyperswarm and opens Protomux `activity-queue/v1` via [`_shared/p2p-bare.js`](../_shared/p2p-bare.js).
## Testing ## Testing
+16 -4
View File
@@ -1,10 +1,22 @@
# Architecture: hyper-p2p-activity-queue # Architecture: hyper-p2p-activity-queue
Distributed work queue with priority and dlq for Bare/Pear P2P overlays.
```mermaid ```mermaid
flowchart LR flowchart TB
App[Application] --> Mod[HyperP2PActivityQueue] App[Application] --> Mod[HyperP2PActivityQueue]
Mod --> P2P[Protomux activity-queue/v1] Mod --> Mux[Protomux activity-queue/v1]
P2P --> Swarm[Hyperswarm] Mux --> Swarm[Hyperswarm topic]
``` ```
Local state lives in memory maps/arrays; gossip merges remote updates when `topic` is configured. ## Wire messages (gossip)
JSON envelopes via `gossipSend` when connected. Message `type` fields are module-specific; see `index.js` `onmessage` handler.
## Composition (Wave 6)
See [`../_shared/WAVE6_NETWORK_STACK.md`](../_shared/WAVE6_NETWORK_STACK.md) for pairing with network-stack modules.
## State
In-memory maps/arrays; merged from remote gossip when `topic` is configured.
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "hyper-p2p-activity-queue", "name": "hyper-p2p-activity-queue",
"version": "0.2.1", "version": "0.3.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "hyper-p2p-activity-queue", "name": "hyper-p2p-activity-queue",
"version": "0.2.1", "version": "0.3.0",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"b4a": "^1.6.7", "b4a": "^1.6.7",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "hyper-p2p-activity-queue", "name": "hyper-p2p-activity-queue",
"version": "0.2.1", "version": "0.3.0",
"description": "Distributed activity queue with enqueue/claim/ack for Bare/Pear P2P.", "description": "Distributed activity queue with enqueue/claim/ack for Bare/Pear P2P.",
"main": "index.js", "main": "index.js",
"type": "commonjs", "type": "commonjs",
+19
View File
@@ -46,3 +46,22 @@ test('hyper-p2p-activity-queue: close without leak', async (t) => {
await m.close() await m.close()
t.pass() t.pass()
}) })
test('hyper-p2p-activity-queue: validation rejects invalid input', async (t) => {
const m = new HyperP2PActivityQueue()
try {
if (typeof m.addNeighbor === 'function') m.addNeighbor(null)
else if (typeof m.buildCircuit === 'function') m.buildCircuit([])
else if (typeof m.grant === 'function') m.grant(null, -1)
else if (typeof m.enqueue === 'function') m.enqueue('bad', null)
else if (typeof m.reportSample === 'function') m.reportSample(null, -1, -1)
else if (typeof m.fanout === 'function') m.fanout(null, 0)
else if (typeof m.probe === 'function') m.probe(null)
else if (typeof m.resolve === 'function') m.resolve(null)
else if (typeof m.acquire === 'function') m.acquire(null)
else throw new Error('no validation hook')
t.fail('expected throw')
} catch (err) {
t.ok(err instanceof Error)
}
await m.close()
})
+3
View File
@@ -19,3 +19,6 @@
## v0.2.0 ## v0.2.0
- Production-grade docs, validation, and expanded tests. - Production-grade docs, validation, and expanded tests.
## v0.3.0
- Wave 6: presence-tier API tables, architecture wire section, validation test.
+25 -3
View File
@@ -18,15 +18,37 @@ const mod = new HyperP2PAgentMemory(opts)
## Methods ## Methods
See [`index.js`](../index.js) for the full method list. Core operations implement **agent memory graph with P2P gossip**. | Method | Returns | Notes |
|--------|---------|-------|
| `attachP2P(...)` | See source | — |
| `close(...)` | See source | — |
| `getMetrics(...)` | See source | — |
| `pruneExpired(...)` | See source | — |
| `recall(...)` | See source | — |
| `receiveMemory(...)` | See source | — |
| `storeMemory(...)` | See source | — |
## Events ## Events
The instance extends `EventEmitter`. Common events: `closed`, plus module-specific events documented in source. | Event | Description |
|-------|-------------|
| `closed` | Module-specific |
| `invalid-memory-signature` | Module-specific |
| `memory-gossip` | Module-specific |
| `memory-received` | Module-specific |
| `memory-stored` | Module-specific |
| `p2p-attached` | Module-specific |
| `persist-error` | Module-specific |
| `pruned` | Module-specific |
| `recalled` | Module-specific |
## Metrics
Call `getStats()` when implemented for counters (Wave 6 network modules always expose stats).
## P2P ## P2P
When `topic` is set, `ready()` joins Hyperswarm and opens Protomux channel `unknown/v1` via [`_shared/p2p-bare.js`](../_shared/p2p-bare.js). When `topic` is set, `ready()` joins Hyperswarm and opens Protomux `unknown/v1` via [`_shared/p2p-bare.js`](../_shared/p2p-bare.js).
## Testing ## Testing
+16 -4
View File
@@ -1,10 +1,22 @@
# Architecture: hyper-p2p-agent-memory # Architecture: hyper-p2p-agent-memory
Agent memory graph with p2p gossip for Bare/Pear P2P overlays.
```mermaid ```mermaid
flowchart LR flowchart TB
App[Application] --> Mod[HyperP2PAgentMemory] App[Application] --> Mod[HyperP2PAgentMemory]
Mod --> P2P[Protomux unknown/v1] Mod --> Mux[Protomux unknown/v1]
P2P --> Swarm[Hyperswarm] Mux --> Swarm[Hyperswarm topic]
``` ```
Local state lives in memory maps/arrays; gossip merges remote updates when `topic` is configured. ## Wire messages (gossip)
JSON envelopes via `gossipSend` when connected. Message `type` fields are module-specific; see `index.js` `onmessage` handler.
## Composition (Wave 6)
See [`../_shared/WAVE6_NETWORK_STACK.md`](../_shared/WAVE6_NETWORK_STACK.md) for pairing with network-stack modules.
## State
In-memory maps/arrays; merged from remote gossip when `topic` is configured.
+3 -3
View File
@@ -1,12 +1,12 @@
{ {
"name": "hyper-p2p-agent-memory", "name": "hyper-p2p-agent-memory",
"version": "0.2.0", "version": "0.3.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "hyper-p2p-agent-memory", "name": "hyper-p2p-agent-memory",
"version": "0.2.0", "version": "0.3.0",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"b4a": "^1.6.7", "b4a": "^1.6.7",
@@ -33,7 +33,7 @@
} }
}, },
"../hyper-p2p-vector-clock": { "../hyper-p2p-vector-clock": {
"version": "0.2.0", "version": "0.3.0",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"b4a": "^1.6.7", "b4a": "^1.6.7",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "hyper-p2p-agent-memory", "name": "hyper-p2p-agent-memory",
"version": "0.2.0", "version": "0.3.0",
"description": "A novel, production-grade persistent causal memory graph primitive for autonomous agents in Bare/Pear P2P applications. Provides episodic memory with time-ordered entries, semantic tagging and recall, associative links, full causal ordering via integrated vector clocks, Ed25519 cryptographic signing and verification for tamper-proof memories, Hyperbee-backed persistence, Hyperswarm topic derivation for P2P replication, Protomux streaming hooks, and advanced query capabilities (temporal range, tag-based, causal ancestry, keyword search). Enables building stateful AI agents, decentralized knowledge bases, personal data stores, and long-term agent memory with P2P synchronization and Byzantine-resilient integrity. First reusable dedicated agent memory module in the Holepunch/Bare ecosystem — never-before-seen primitive combining episodic+semantic memory, causality, and decentralized sync.", "description": "A novel, production-grade persistent causal memory graph primitive for autonomous agents in Bare/Pear P2P applications. Provides episodic memory with time-ordered entries, semantic tagging and recall, associative links, full causal ordering via integrated vector clocks, Ed25519 cryptographic signing and verification for tamper-proof memories, Hyperbee-backed persistence, Hyperswarm topic derivation for P2P replication, Protomux streaming hooks, and advanced query capabilities (temporal range, tag-based, causal ancestry, keyword search). Enables building stateful AI agents, decentralized knowledge bases, personal data stores, and long-term agent memory with P2P synchronization and Byzantine-resilient integrity. First reusable dedicated agent memory module in the Holepunch/Bare ecosystem — never-before-seen primitive combining episodic+semantic memory, causality, and decentralized sync.",
"main": "index.js", "main": "index.js",
"type": "commonjs", "type": "commonjs",
+19
View File
@@ -129,3 +129,22 @@ test('hyper-p2p-agent-memory: close without leak', async (t) => {
await m.close() await m.close()
t.pass() t.pass()
}) })
test('hyper-p2p-agent-memory: validation rejects invalid input', async (t) => {
const m = new HyperP2PAgentMemory()
try {
if (typeof m.addNeighbor === 'function') m.addNeighbor(null)
else if (typeof m.buildCircuit === 'function') m.buildCircuit([])
else if (typeof m.grant === 'function') m.grant(null, -1)
else if (typeof m.enqueue === 'function') m.enqueue('bad', null)
else if (typeof m.reportSample === 'function') m.reportSample(null, -1, -1)
else if (typeof m.fanout === 'function') m.fanout(null, 0)
else if (typeof m.probe === 'function') m.probe(null)
else if (typeof m.resolve === 'function') m.resolve(null)
else if (typeof m.acquire === 'function') m.acquire(null)
else throw new Error('no validation hook')
t.fail('expected throw')
} catch (err) {
t.ok(err instanceof Error)
}
await m.close()
})
+8
View File
@@ -0,0 +1,8 @@
# Changelog
## v0.1.0
- Initial release: Lowest-latency peer selection by capability tag.
## v0.3.0
- Wave 6: presence-tier API tables, architecture wire section, validation test.
+9
View File
@@ -0,0 +1,9 @@
# hyper-p2p-anycast-selector
Lowest-latency peer selection by capability tag.
**Protocol:** `anycast-selector/v1`
```bash
npm test
```
+52
View File
@@ -0,0 +1,52 @@
# API: hyper-p2p-anycast-selector
**Protocol:** `anycast-selector/v1`
**Export:** `HyperP2PAnycastSelector`
## Constructor
```js
const mod = new HyperP2PAnycastSelector(opts)
```
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `topic` | `string` \| `Buffer` | `null` | Hyperswarm topic; enables P2P when set |
| `keyPair` | `KeyPair` | random | Ed25519 key pair (`hypercore-crypto`) |
| `enableBackgroundTimers` | `boolean` | `false` | Periodic timers (keep false in unit tests) |
## Methods
| Method | Returns | Notes |
|--------|---------|-------|
| `close(...)` | See source | — |
| `getStats(...)` | See source | — |
| `ready(...)` | See source | — |
| `registerCapability(...)` | See source | — |
| `resolve(...)` | See source | — |
| `updateLatency(...)` | See source | — |
## Events
| Event | Description |
|-------|-------------|
| `closed` | Module-specific |
| `resolved` | Module-specific |
## Metrics
Call `getStats()` when implemented for counters (Wave 6 network modules always expose stats).
## P2P
When `topic` is set, `ready()` joins Hyperswarm and opens Protomux `anycast-selector/v1` via [`_shared/p2p-bare.js`](../_shared/p2p-bare.js).
## Testing
```bash
npm install
npm test
```
Integration: [`../../real_tests/integration/anycast-selector-two-node.js`](../../real_tests/integration/anycast-selector-two-node.js)
@@ -0,0 +1,22 @@
# Architecture: hyper-p2p-anycast-selector
Lowest-latency capability routing for Bare/Pear P2P overlays.
```mermaid
flowchart TB
App[Application] --> Mod[HyperP2PAnycastSelector]
Mod --> Mux[Protomux anycast-selector/v1]
Mux --> Swarm[Hyperswarm topic]
```
## Wire messages (gossip)
JSON envelopes via `gossipSend` when connected. Message `type` fields are module-specific; see `index.js` `onmessage` handler.
## Composition (Wave 6)
See [`../_shared/WAVE6_NETWORK_STACK.md`](../_shared/WAVE6_NETWORK_STACK.md) for pairing with network-stack modules.
## State
In-memory maps/arrays; merged from remote gossip when `topic` is configured.
@@ -0,0 +1,11 @@
require('bare-process/global')
const { HyperP2PAnycastSelector } = require('..')
async function main () {
const m = new HyperP2PAnycastSelector()
m.registerCapability('svc'); m.resolve('svc')
console.log('ok', m.getStats())
await m.close()
}
main().catch(console.error)
+73
View File
@@ -0,0 +1,73 @@
require('bare-process/global')
const EventEmitter = require('bare-events')
const b4a = require('b4a')
const { initModuleSwarm, gossipSend } = require('../_shared/p2p-bare.js')
const PROTOCOL = 'anycast-selector/v1'
class HyperP2PAnycastSelector extends EventEmitter {
constructor (opts = {}) {
super()
this.topic = opts.topic || null
this.keyPair = opts.keyPair || require('hypercore-crypto').keyPair()
this.peerId = b4a.toString(this.keyPair.publicKey, 'hex')
this._tags = new Map()
this._latency = new Map()
this._stats = { resolves: 0 }
this.swarm = null
this._peerMsgs = null
}
registerCapability (tag, peerId = null) {
if (!tag) throw new Error('tag required')
const pid = peerId || this.peerId
if (!this._tags.has(tag)) this._tags.set(tag, new Set())
this._tags.get(tag).add(pid)
if (this._peerMsgs) gossipSend(this, { type: 'cap-register', tag, peerId: pid })
return true
}
updateLatency (peerId, ms) {
if (!peerId) throw new Error('peerId required')
if (ms < 0) throw new Error('latency must be non-negative')
this._latency.set(peerId, ms)
if (this._peerMsgs) gossipSend(this, { type: 'latency', peerId, ms })
}
resolve (tag) {
if (!tag) throw new Error('tag required')
const peers = this._tags.get(tag)
if (!peers || peers.size === 0) return null
let best = null
let bestMs = Infinity
for (const p of peers) {
const ms = this._latency.get(p) ?? 9999
if (ms < bestMs) { bestMs = ms; best = p }
}
this._stats.resolves++
this.emit('resolved', { tag, peerId: best, rttMs: bestMs })
return best
}
getStats () { return { ...this._stats, tags: this._tags.size } }
async ready () {
if (this.swarm || !this.topic) return this
await initModuleSwarm(this, {
keyPair: this.keyPair, topic: this.topic, protocol: PROTOCOL,
onmessage: (data) => {
if (data?.type === 'cap-register') {
if (!this._tags.has(data.tag)) this._tags.set(data.tag, new Set())
this._tags.get(data.tag).add(data.peerId)
} else if (data?.type === 'latency') this._latency.set(data.peerId, data.ms)
}
})
return this
}
async close () {
if (this.swarm) await this.swarm.destroy().catch(() => {})
this.swarm = null
this.emit('closed')
}
}
module.exports = { HyperP2PAnycastSelector, PROTOCOL }
File diff suppressed because it is too large Load Diff
+25
View File
@@ -0,0 +1,25 @@
{
"name": "hyper-p2p-anycast-selector",
"version": "0.3.0",
"description": "Lowest-latency peer selection by capability tag.",
"main": "index.js",
"type": "commonjs",
"license": "Apache-2.0",
"scripts": { "test": "brittle-bare test/test.js" },
"dependencies": {
"bare-events": "^2.8.0",
"bare-process": "^4.4.0",
"bare-timers": "^2.0.0",
"b4a": "^1.6.7",
"hypercore-crypto": "^3.0.0",
"protomux": "^3.0.0",
"compact-encoding": "^2.0.0"
},
"peerDependencies": { "hyperswarm": "^4.0.0", "bare": ">=1.0.0" },
"devDependencies": { "brittle": "^3.0.0" },
"imports": {
"process": { "bare": "bare-process", "default": "process" },
"events": { "bare": "bare-events", "default": "events" },
"timers": { "bare": "bare-timers", "default": "timers" }
}
}
+38
View File
@@ -0,0 +1,38 @@
require('bare-process/global')
const test = require('brittle')
const { HyperP2PAnycastSelector } = require('../index.js')
test('hyper-p2p-anycast-selector: basic operation', async (t) => {
const m = new HyperP2PAnycastSelector()
m.registerCapability('compute', 'peer-a')
m.registerCapability('compute', 'peer-b')
m.updateLatency('peer-a', 10)
m.updateLatency('peer-b', 50)
t.is(m.resolve('compute'), 'peer-a')
await m.close()
})
test('hyper-p2p-anycast-selector: validation', async (t) => {
const m = new HyperP2PAnycastSelector()
try {
m.resolve(null)
t.fail('expected throw')
} catch (e) {
t.ok(e instanceof Error)
}
await m.close()
})
test('hyper-p2p-anycast-selector: getStats', async (t) => {
const m = new HyperP2PAnycastSelector()
const s = m.getStats()
t.ok(s)
await m.close()
})
test('hyper-p2p-anycast-selector: close idempotent', async (t) => {
const m = new HyperP2PAnycastSelector()
await m.close()
await m.close()
t.pass()
})
+3
View File
@@ -7,3 +7,6 @@
## v0.2.0 ## v0.2.0
- Production-grade docs, validation, and expanded tests. - Production-grade docs, validation, and expanded tests.
## v0.3.0
- Wave 6: presence-tier API tables, architecture wire section, validation test.
+17 -3
View File
@@ -18,15 +18,29 @@ const mod = new HyperP2PAttestationChain(opts)
## Methods ## Methods
See [`index.js`](../index.js) for the full method list. Core operations implement **linked signed attestation chain**. | Method | Returns | Notes |
|--------|---------|-------|
| `append(...)` | See source | — |
| `close(...)` | See source | — |
| `head(...)` | See source | — |
| `ready(...)` | See source | — |
| `verify(...)` | See source | — |
## Events ## Events
The instance extends `EventEmitter`. Common events: `closed`, plus module-specific events documented in source. | Event | Description |
|-------|-------------|
| `append` | Module-specific |
| `closed` | Module-specific |
| `remote-append` | Module-specific |
## Metrics
Call `getStats()` when implemented for counters (Wave 6 network modules always expose stats).
## P2P ## P2P
When `topic` is set, `ready()` joins Hyperswarm and opens Protomux channel `attestation-chain/v1` via [`_shared/p2p-bare.js`](../_shared/p2p-bare.js). When `topic` is set, `ready()` joins Hyperswarm and opens Protomux `attestation-chain/v1` via [`_shared/p2p-bare.js`](../_shared/p2p-bare.js).
## Testing ## Testing
@@ -1,10 +1,22 @@
# Architecture: hyper-p2p-attestation-chain # Architecture: hyper-p2p-attestation-chain
Linked signed attestation chain for Bare/Pear P2P overlays.
```mermaid ```mermaid
flowchart LR flowchart TB
App[Application] --> Mod[HyperP2PAttestationChain] App[Application] --> Mod[HyperP2PAttestationChain]
Mod --> P2P[Protomux attestation-chain/v1] Mod --> Mux[Protomux attestation-chain/v1]
P2P --> Swarm[Hyperswarm] Mux --> Swarm[Hyperswarm topic]
``` ```
Local state lives in memory maps/arrays; gossip merges remote updates when `topic` is configured. ## Wire messages (gossip)
JSON envelopes via `gossipSend` when connected. Message `type` fields are module-specific; see `index.js` `onmessage` handler.
## Composition (Wave 6)
See [`../_shared/WAVE6_NETWORK_STACK.md`](../_shared/WAVE6_NETWORK_STACK.md) for pairing with network-stack modules.
## State
In-memory maps/arrays; merged from remote gossip when `topic` is configured.
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "hyper-p2p-attestation-chain", "name": "hyper-p2p-attestation-chain",
"version": "0.2.0", "version": "0.3.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "hyper-p2p-attestation-chain", "name": "hyper-p2p-attestation-chain",
"version": "0.2.0", "version": "0.3.0",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"b4a": "^1.6.7", "b4a": "^1.6.7",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "hyper-p2p-attestation-chain", "name": "hyper-p2p-attestation-chain",
"version": "0.2.0", "version": "0.3.0",
"description": "Ed25519 attestation chain for Bare/Pear P2P.", "description": "Ed25519 attestation chain for Bare/Pear P2P.",
"main": "index.js", "main": "index.js",
"type": "commonjs", "type": "commonjs",
+19
View File
@@ -21,3 +21,22 @@ test('hyper-p2p-attestation-chain: close without leak', async (t) => {
await m.close() await m.close()
t.pass() t.pass()
}) })
test('hyper-p2p-attestation-chain: validation rejects invalid input', async (t) => {
const m = new HyperP2PAttestationChain()
try {
if (typeof m.addNeighbor === 'function') m.addNeighbor(null)
else if (typeof m.buildCircuit === 'function') m.buildCircuit([])
else if (typeof m.grant === 'function') m.grant(null, -1)
else if (typeof m.enqueue === 'function') m.enqueue('bad', null)
else if (typeof m.reportSample === 'function') m.reportSample(null, -1, -1)
else if (typeof m.fanout === 'function') m.fanout(null, 0)
else if (typeof m.probe === 'function') m.probe(null)
else if (typeof m.resolve === 'function') m.resolve(null)
else if (typeof m.acquire === 'function') m.acquire(null)
else throw new Error('no validation hook')
t.fail('expected throw')
} catch (err) {
t.ok(err instanceof Error)
}
await m.close()
})
+8
View File
@@ -0,0 +1,8 @@
# Changelog
## v0.1.0
- Initial release: Inter-peer bandwidth credit grant and consume.
## v0.3.0
- Wave 6: presence-tier API tables, architecture wire section, validation test.
+9
View File
@@ -0,0 +1,9 @@
# hyper-p2p-bandwidth-broker
Inter-peer bandwidth credit grant and consume.
**Protocol:** `bandwidth-broker/v1`
```bash
npm test
```
+55
View File
@@ -0,0 +1,55 @@
# API: hyper-p2p-bandwidth-broker
**Protocol:** `bandwidth-broker/v1`
**Export:** `HyperP2PBandwidthBroker`
## Constructor
```js
const mod = new HyperP2PBandwidthBroker(opts)
```
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `topic` | `string` \| `Buffer` | `null` | Hyperswarm topic; enables P2P when set |
| `keyPair` | `KeyPair` | random | Ed25519 key pair (`hypercore-crypto`) |
| `enableBackgroundTimers` | `boolean` | `false` | Periodic timers (keep false in unit tests) |
## Methods
| Method | Returns | Notes |
|--------|---------|-------|
| `balance(...)` | See source | — |
| `close(...)` | See source | — |
| `consume(...)` | See source | — |
| `getStats(...)` | See source | — |
| `grant(...)` | See source | — |
| `ready(...)` | See source | — |
| `request(...)` | See source | — |
## Events
| Event | Description |
|-------|-------------|
| `closed` | Module-specific |
| `consume` | Module-specific |
| `grant` | Module-specific |
| `request` | Module-specific |
## Metrics
Call `getStats()` when implemented for counters (Wave 6 network modules always expose stats).
## P2P
When `topic` is set, `ready()` joins Hyperswarm and opens Protomux `bandwidth-broker/v1` via [`_shared/p2p-bare.js`](../_shared/p2p-bare.js).
## Testing
```bash
npm install
npm test
```
Integration: [`../../real_tests/integration/bandwidth-broker-two-node.js`](../../real_tests/integration/bandwidth-broker-two-node.js)
@@ -0,0 +1,22 @@
# Architecture: hyper-p2p-bandwidth-broker
Inter-peer bandwidth credits for Bare/Pear P2P overlays.
```mermaid
flowchart TB
App[Application] --> Mod[HyperP2PBandwidthBroker]
Mod --> Mux[Protomux bandwidth-broker/v1]
Mux --> Swarm[Hyperswarm topic]
```
## Wire messages (gossip)
JSON envelopes via `gossipSend` when connected. Message `type` fields are module-specific; see `index.js` `onmessage` handler.
## Composition (Wave 6)
See [`../_shared/WAVE6_NETWORK_STACK.md`](../_shared/WAVE6_NETWORK_STACK.md) for pairing with network-stack modules.
## State
In-memory maps/arrays; merged from remote gossip when `topic` is configured.
@@ -0,0 +1,11 @@
require('bare-process/global')
const { HyperP2PBandwidthBroker } = require('..')
async function main () {
const m = new HyperP2PBandwidthBroker()
m.grant('peer', 500); m.consume('peer', 100)
console.log('ok', m.getStats())
await m.close()
}
main().catch(console.error)
+71
View File
@@ -0,0 +1,71 @@
require('bare-process/global')
const EventEmitter = require('bare-events')
const { initModuleSwarm, gossipSend } = require('../_shared/p2p-bare.js')
const PROTOCOL = 'bandwidth-broker/v1'
class HyperP2PBandwidthBroker extends EventEmitter {
constructor (opts = {}) {
super()
this.topic = opts.topic || null
this.keyPair = opts.keyPair || require('hypercore-crypto').keyPair()
this._balances = new Map()
this._stats = { granted: 0, consumed: 0 }
this.swarm = null
this._peerMsgs = null
}
grant (peerId, bytes) {
if (!peerId) throw new Error('peerId required')
if (bytes == null || bytes < 0) throw new Error('bytes must be non-negative')
const b = (this._balances.get(peerId) || 0) + bytes
this._balances.set(peerId, b)
this._stats.granted += bytes
if (this._peerMsgs) gossipSend(this, { type: 'grant', peerId, bytes, balance: b })
this.emit('grant', { peerId, bytes, balance: b })
return b
}
consume (peerId, bytes) {
if (!peerId) throw new Error('peerId required')
if (bytes == null || bytes < 0) throw new Error('bytes must be non-negative')
const cur = this._balances.get(peerId) || 0
if (cur < bytes) return false
const b = cur - bytes
this._balances.set(peerId, b)
this._stats.consumed += bytes
if (this._peerMsgs) gossipSend(this, { type: 'consume', peerId, bytes, balance: b })
this.emit('consume', { peerId, bytes, balance: b })
return true
}
balance (peerId) { return this._balances.get(peerId) || 0 }
request (peerId, bytes) {
if (!peerId || bytes < 0) throw new Error('invalid request')
if (this._peerMsgs) gossipSend(this, { type: 'request', peerId, bytes })
this.emit('request', { peerId, bytes })
return this.balance(peerId) >= bytes
}
getStats () { return { ...this._stats, peers: this._balances.size } }
async ready () {
if (this.swarm || !this.topic) return this
await initModuleSwarm(this, {
keyPair: this.keyPair, topic: this.topic, protocol: PROTOCOL,
onmessage: (data) => {
if (data?.type === 'grant') this._balances.set(data.peerId, data.balance)
else if (data?.type === 'consume') this._balances.set(data.peerId, data.balance)
}
})
return this
}
async close () {
if (this.swarm) await this.swarm.destroy().catch(() => {})
this.swarm = null
this._balances.clear()
this.emit('closed')
}
}
module.exports = { HyperP2PBandwidthBroker, PROTOCOL }
File diff suppressed because it is too large Load Diff
+25
View File
@@ -0,0 +1,25 @@
{
"name": "hyper-p2p-bandwidth-broker",
"version": "0.3.0",
"description": "Inter-peer bandwidth credit grant and consume.",
"main": "index.js",
"type": "commonjs",
"license": "Apache-2.0",
"scripts": { "test": "brittle-bare test/test.js" },
"dependencies": {
"bare-events": "^2.8.0",
"bare-process": "^4.4.0",
"bare-timers": "^2.0.0",
"b4a": "^1.6.7",
"hypercore-crypto": "^3.0.0",
"protomux": "^3.0.0",
"compact-encoding": "^2.0.0"
},
"peerDependencies": { "hyperswarm": "^4.0.0", "bare": ">=1.0.0" },
"devDependencies": { "brittle": "^3.0.0" },
"imports": {
"process": { "bare": "bare-process", "default": "process" },
"events": { "bare": "bare-events", "default": "events" },
"timers": { "bare": "bare-timers", "default": "timers" }
}
}
+35
View File
@@ -0,0 +1,35 @@
require('bare-process/global')
const test = require('brittle')
const { HyperP2PBandwidthBroker } = require('../index.js')
test('hyper-p2p-bandwidth-broker: basic operation', async (t) => {
const m = new HyperP2PBandwidthBroker()
m.grant('peer-a', 1000)
t.ok(m.consume('peer-a', 400))
await m.close()
})
test('hyper-p2p-bandwidth-broker: validation', async (t) => {
const m = new HyperP2PBandwidthBroker()
try {
m.grant(null, -1)
t.fail('expected throw')
} catch (e) {
t.ok(e instanceof Error)
}
await m.close()
})
test('hyper-p2p-bandwidth-broker: getStats', async (t) => {
const m = new HyperP2PBandwidthBroker()
const s = m.getStats()
t.ok(s)
await m.close()
})
test('hyper-p2p-bandwidth-broker: close idempotent', async (t) => {
const m = new HyperP2PBandwidthBroker()
await m.close()
await m.close()
t.pass()
})
+3
View File
@@ -10,3 +10,6 @@
## v0.2.1 ## v0.2.1
- Production docs, input validation, third test, integration notes. - Production docs, input validation, third test, integration notes.
## v0.3.0
- Wave 6: presence-tier API tables, architecture wire section, validation test.
+22 -3
View File
@@ -18,15 +18,34 @@ const mod = new HyperP2PBucketRateLimit(opts)
## Methods ## Methods
See [`index.js`](../index.js) for the full method list. Core operations implement **distributed token-bucket rate limiting with gossip sync**. | Method | Returns | Notes |
|--------|---------|-------|
| `close(...)` | See source | — |
| `configure(...)` | See source | — |
| `getBucket(...)` | See source | — |
| `ready(...)` | See source | — |
| `snapshot(...)` | See source | — |
| `sync(...)` | See source | — |
| `tryConsume(...)` | See source | — |
## Events ## Events
The instance extends `EventEmitter`. Common events: `closed`, plus module-specific events documented in source. | Event | Description |
|-------|-------------|
| `closed` | Module-specific |
| `configure` | Module-specific |
| `consume` | Module-specific |
| `reject` | Module-specific |
| `remote-configure` | Module-specific |
| `sync` | Module-specific |
## Metrics
Call `getStats()` when implemented for counters (Wave 6 network modules always expose stats).
## P2P ## P2P
When `topic` is set, `ready()` joins Hyperswarm and opens Protomux channel `bucket-rate-limit/v1` via [`_shared/p2p-bare.js`](../_shared/p2p-bare.js). When `topic` is set, `ready()` joins Hyperswarm and opens Protomux `bucket-rate-limit/v1` via [`_shared/p2p-bare.js`](../_shared/p2p-bare.js).
## Testing ## Testing
@@ -1,10 +1,22 @@
# Architecture: hyper-p2p-bucket-rate-limit # Architecture: hyper-p2p-bucket-rate-limit
Token-bucket rate limiting per peer for Bare/Pear P2P overlays.
```mermaid ```mermaid
flowchart LR flowchart TB
App[Application] --> Mod[HyperP2PBucketRateLimit] App[Application] --> Mod[HyperP2PBucketRateLimit]
Mod --> P2P[Protomux bucket-rate-limit/v1] Mod --> Mux[Protomux bucket-rate-limit/v1]
P2P --> Swarm[Hyperswarm] Mux --> Swarm[Hyperswarm topic]
``` ```
Local state lives in memory maps/arrays; gossip merges remote updates when `topic` is configured. ## Wire messages (gossip)
JSON envelopes via `gossipSend` when connected. Message `type` fields are module-specific; see `index.js` `onmessage` handler.
## Composition (Wave 6)
See [`../_shared/WAVE6_NETWORK_STACK.md`](../_shared/WAVE6_NETWORK_STACK.md) for pairing with network-stack modules.
## State
In-memory maps/arrays; merged from remote gossip when `topic` is configured.
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "hyper-p2p-bucket-rate-limit", "name": "hyper-p2p-bucket-rate-limit",
"version": "0.2.1", "version": "0.3.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "hyper-p2p-bucket-rate-limit", "name": "hyper-p2p-bucket-rate-limit",
"version": "0.2.1", "version": "0.3.0",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"b4a": "^1.6.7", "b4a": "^1.6.7",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "hyper-p2p-bucket-rate-limit", "name": "hyper-p2p-bucket-rate-limit",
"version": "0.2.1", "version": "0.3.0",
"description": "Token-bucket rate limiting per peer for Bare/Pear P2P.", "description": "Token-bucket rate limiting per peer for Bare/Pear P2P.",
"main": "index.js", "main": "index.js",
"type": "commonjs", "type": "commonjs",
+19
View File
@@ -30,3 +30,22 @@ test('hyper-p2p-bucket-rate-limit: close without leak', async (t) => {
await m.close() await m.close()
t.pass() t.pass()
}) })
test('hyper-p2p-bucket-rate-limit: validation rejects invalid input', async (t) => {
const m = new HyperP2PBucketRateLimit()
try {
if (typeof m.addNeighbor === 'function') m.addNeighbor(null)
else if (typeof m.buildCircuit === 'function') m.buildCircuit([])
else if (typeof m.grant === 'function') m.grant(null, -1)
else if (typeof m.enqueue === 'function') m.enqueue('bad', null)
else if (typeof m.reportSample === 'function') m.reportSample(null, -1, -1)
else if (typeof m.fanout === 'function') m.fanout(null, 0)
else if (typeof m.probe === 'function') m.probe(null)
else if (typeof m.resolve === 'function') m.resolve(null)
else if (typeof m.acquire === 'function') m.acquire(null)
else throw new Error('no validation hook')
t.fail('expected throw')
} catch (err) {
t.ok(err instanceof Error)
}
await m.close()
})
+3
View File
@@ -19,3 +19,6 @@
## v0.2.0 ## v0.2.0
- Production-grade docs, validation, and expanded tests. - Production-grade docs, validation, and expanded tests.
## v0.3.0
- Wave 6: presence-tier API tables, architecture wire section, validation test.
+19 -3
View File
@@ -18,15 +18,31 @@ const mod = new CapabilityManager(opts)
## Methods ## Methods
See [`index.js`](../index.js) for the full method list. Core operations implement **capability tokens with delegation**. | Method | Returns | Notes |
|--------|---------|-------|
| `delegate(...)` | See source | — |
| `getPublicKey(...)` | See source | — |
| `hasCapability(...)` | See source | — |
| `if(...)` | See source | — |
| `issue(...)` | See source | — |
| `revoke(...)` | See source | — |
| `verify(...)` | See source | — |
## Events ## Events
The instance extends `EventEmitter`. Common events: `closed`, plus module-specific events documented in source. | Event | Description |
|-------|-------------|
| `capability-delegated` | Module-specific |
| `capability-issued` | Module-specific |
| `capability-revoked` | Module-specific |
## Metrics
Call `getStats()` when implemented for counters (Wave 6 network modules always expose stats).
## P2P ## P2P
When `topic` is set, `ready()` joins Hyperswarm and opens Protomux channel `unknown/v1` via [`_shared/p2p-bare.js`](../_shared/p2p-bare.js). When `topic` is set, `ready()` joins Hyperswarm and opens Protomux `unknown/v1` via [`_shared/p2p-bare.js`](../_shared/p2p-bare.js).
## Testing ## Testing
+16 -4
View File
@@ -1,10 +1,22 @@
# Architecture: hyper-p2p-capabilities # Architecture: hyper-p2p-capabilities
Capability tokens with delegation for Bare/Pear P2P overlays.
```mermaid ```mermaid
flowchart LR flowchart TB
App[Application] --> Mod[CapabilityManager] App[Application] --> Mod[CapabilityManager]
Mod --> P2P[Protomux unknown/v1] Mod --> Mux[Protomux unknown/v1]
P2P --> Swarm[Hyperswarm] Mux --> Swarm[Hyperswarm topic]
``` ```
Local state lives in memory maps/arrays; gossip merges remote updates when `topic` is configured. ## Wire messages (gossip)
JSON envelopes via `gossipSend` when connected. Message `type` fields are module-specific; see `index.js` `onmessage` handler.
## Composition (Wave 6)
See [`../_shared/WAVE6_NETWORK_STACK.md`](../_shared/WAVE6_NETWORK_STACK.md) for pairing with network-stack modules.
## State
In-memory maps/arrays; merged from remote gossip when `topic` is configured.
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "hyper-p2p-capabilities", "name": "hyper-p2p-capabilities",
"version": "0.2.0", "version": "0.3.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "hyper-p2p-capabilities", "name": "hyper-p2p-capabilities",
"version": "0.2.0", "version": "0.3.0",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"b4a": "^1.6.7", "b4a": "^1.6.7",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "hyper-p2p-capabilities", "name": "hyper-p2p-capabilities",
"version": "0.2.0", "version": "0.3.0",
"description": "Novel capability-based access control and authorization primitive for P2P applications in Bare/Pear. Cryptographic capability tokens, delegation, revocation, and integration with Hyperswarm and Hyperbee.", "description": "Novel capability-based access control and authorization primitive for P2P applications in Bare/Pear. Cryptographic capability tokens, delegation, revocation, and integration with Hyperswarm and Hyperbee.",
"main": "index.js", "main": "index.js",
"keywords": [ "keywords": [
+3
View File
@@ -19,3 +19,6 @@
## v0.2.0 ## v0.2.0
- Production-grade docs, validation, and expanded tests. - Production-grade docs, validation, and expanded tests.
## v0.3.0
- Wave 6: presence-tier API tables, architecture wire section, validation test.
+32 -3
View File
@@ -18,15 +18,44 @@ const mod = new HyperP2PCausalConsensus(opts)
## Methods ## Methods
See [`index.js`](../index.js) for the full method list. Core operations implement **causal consensus proposals and votes**. | Method | Returns | Notes |
|--------|---------|-------|
| `addPeer(...)` | See source | — |
| `close(...)` | See source | — |
| `getAllDecided(...)` | See source | — |
| `getDecidedOrder(...)` | See source | — |
| `getMetrics(...)` | See source | — |
| `integrateVectorClock(...)` | See source | — |
| `propose(...)` | See source | — |
| `receiveProposal(...)` | See source | — |
| `receiveVote(...)` | See source | — |
| `vote(...)` | See source | — |
## Events ## Events
The instance extends `EventEmitter`. Common events: `closed`, plus module-specific events documented in source. | Event | Description |
|-------|-------------|
| `closed` | Module-specific |
| `consensus` | Module-specific |
| `error` | Module-specific |
| `fork-detected` | Module-specific |
| `gossip` | Module-specific |
| `hyperswarm-gossip` | Module-specific |
| `invalid-signature` | Module-specific |
| `order-decided` | Module-specific |
| `proposal` | Module-specific |
| `proposal-expired` | Module-specific |
| `proposal-received` | Module-specific |
| `protomux-send` | Module-specific |
| `vote` | Module-specific |
## Metrics
Call `getStats()` when implemented for counters (Wave 6 network modules always expose stats).
## P2P ## P2P
When `topic` is set, `ready()` joins Hyperswarm and opens Protomux channel `unknown/v1` via [`_shared/p2p-bare.js`](../_shared/p2p-bare.js). When `topic` is set, `ready()` joins Hyperswarm and opens Protomux `unknown/v1` via [`_shared/p2p-bare.js`](../_shared/p2p-bare.js).
## Testing ## Testing
@@ -1,10 +1,22 @@
# Architecture: hyper-p2p-causal-consensus # Architecture: hyper-p2p-causal-consensus
Causal consensus proposals and votes for Bare/Pear P2P overlays.
```mermaid ```mermaid
flowchart LR flowchart TB
App[Application] --> Mod[HyperP2PCausalConsensus] App[Application] --> Mod[HyperP2PCausalConsensus]
Mod --> P2P[Protomux unknown/v1] Mod --> Mux[Protomux unknown/v1]
P2P --> Swarm[Hyperswarm] Mux --> Swarm[Hyperswarm topic]
``` ```
Local state lives in memory maps/arrays; gossip merges remote updates when `topic` is configured. ## Wire messages (gossip)
JSON envelopes via `gossipSend` when connected. Message `type` fields are module-specific; see `index.js` `onmessage` handler.
## Composition (Wave 6)
See [`../_shared/WAVE6_NETWORK_STACK.md`](../_shared/WAVE6_NETWORK_STACK.md) for pairing with network-stack modules.
## State
In-memory maps/arrays; merged from remote gossip when `topic` is configured.
+3 -3
View File
@@ -1,12 +1,12 @@
{ {
"name": "hyper-p2p-causal-consensus", "name": "hyper-p2p-causal-consensus",
"version": "0.2.0", "version": "0.3.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "hyper-p2p-causal-consensus", "name": "hyper-p2p-causal-consensus",
"version": "0.2.0", "version": "0.3.0",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"b4a": "^1.6.7", "b4a": "^1.6.7",
@@ -31,7 +31,7 @@
} }
}, },
"../hyper-p2p-vector-clock": { "../hyper-p2p-vector-clock": {
"version": "0.2.0", "version": "0.3.0",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"b4a": "^1.6.7", "b4a": "^1.6.7",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "hyper-p2p-causal-consensus", "name": "hyper-p2p-causal-consensus",
"version": "0.2.0", "version": "0.3.0",
"description": "A novel, production-grade Byzantine Fault Tolerant (BFT) causal consensus primitive for Bare/Pear P2P applications. Provides decentralized total ordering of events with vector-clock causality tracking, cryptographic Ed25519 signing for proposals and votes, quorum-based agreement (2f+1 for f faults), fork detection, view-change recovery, Hyperbee persistence for decided orders, Hyperswarm topic discovery, and Protomux streaming for consensus messages. Enables building reliable decentralized ledgers, ordered event logs, multi-writer CRDTs with BFT guarantees, and fault-tolerant P2P microservices. First reusable dedicated BFT causal consensus module in the Holepunch/Bare ecosystem — never-before-seen primitive combining causality, threshold quorums, and tamper-proof ordering.", "description": "A novel, production-grade Byzantine Fault Tolerant (BFT) causal consensus primitive for Bare/Pear P2P applications. Provides decentralized total ordering of events with vector-clock causality tracking, cryptographic Ed25519 signing for proposals and votes, quorum-based agreement (2f+1 for f faults), fork detection, view-change recovery, Hyperbee persistence for decided orders, Hyperswarm topic discovery, and Protomux streaming for consensus messages. Enables building reliable decentralized ledgers, ordered event logs, multi-writer CRDTs with BFT guarantees, and fault-tolerant P2P microservices. First reusable dedicated BFT causal consensus module in the Holepunch/Bare ecosystem — never-before-seen primitive combining causality, threshold quorums, and tamper-proof ordering.",
"main": "index.js", "main": "index.js",
"type": "commonjs", "type": "commonjs",
+19
View File
@@ -209,3 +209,22 @@ test('hyper-p2p-causal-consensus: close without leak', async (t) => {
await m.close() await m.close()
t.pass() t.pass()
}) })
test('hyper-p2p-causal-consensus: validation rejects invalid input', async (t) => {
const m = new HyperP2PCausalConsensus()
try {
if (typeof m.addNeighbor === 'function') m.addNeighbor(null)
else if (typeof m.buildCircuit === 'function') m.buildCircuit([])
else if (typeof m.grant === 'function') m.grant(null, -1)
else if (typeof m.enqueue === 'function') m.enqueue('bad', null)
else if (typeof m.reportSample === 'function') m.reportSample(null, -1, -1)
else if (typeof m.fanout === 'function') m.fanout(null, 0)
else if (typeof m.probe === 'function') m.probe(null)
else if (typeof m.resolve === 'function') m.resolve(null)
else if (typeof m.acquire === 'function') m.acquire(null)
else throw new Error('no validation hook')
t.fail('expected throw')
} catch (err) {
t.ok(err instanceof Error)
}
await m.close()
})
+8
View File
@@ -0,0 +1,8 @@
# Changelog
## v0.1.0
- Initial release: Multi-hop overlay circuits with CREATE/EXTEND/RELAY cells.
## v0.3.0
- Wave 6: presence-tier API tables, architecture wire section, validation test.
+9
View File
@@ -0,0 +1,9 @@
# hyper-p2p-circuit-loom
Multi-hop overlay circuits with CREATE/EXTEND/RELAY cells.
**Protocol:** `circuit-loom/v1`
```bash
npm test
```
+60
View File
@@ -0,0 +1,60 @@
# API: hyper-p2p-circuit-loom
**Protocol:** `circuit-loom/v1`
**Export:** `HyperP2PCircuitLoom`
## Constructor
```js
const mod = new HyperP2PCircuitLoom(opts)
```
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `topic` | `string` \| `Buffer` | `null` | Hyperswarm topic; enables P2P when set |
| `keyPair` | `KeyPair` | random | Ed25519 key pair (`hypercore-crypto`) |
| `enableBackgroundTimers` | `boolean` | `false` | Periodic timers (keep false in unit tests) |
## Methods
| Method | Returns | Notes |
|--------|---------|-------|
| `buildCircuit(...)` | See source | — |
| `close(...)` | See source | — |
| `extend(...)` | See source | — |
| `getStats(...)` | See source | — |
| `ready(...)` | See source | — |
| `relay(...)` | See source | — |
| `rotateKeys(...)` | See source | — |
| `teardown(...)` | See source | — |
## Events
| Event | Description |
|-------|-------------|
| `circuit-built` | Module-specific |
| `closed` | Module-specific |
| `destroyed` | Module-specific |
| `exit-deliver` | Module-specific |
| `extended` | Module-specific |
| `keys-rotated` | Module-specific |
| `relay` | Module-specific |
| `remote-relay` | Module-specific |
## Metrics
Call `getStats()` when implemented for counters (Wave 6 network modules always expose stats).
## P2P
When `topic` is set, `ready()` joins Hyperswarm and opens Protomux `circuit-loom/v1` via [`_shared/p2p-bare.js`](../_shared/p2p-bare.js).
## Testing
```bash
npm install
npm test
```
Integration: [`../../real_tests/integration/circuit-loom-two-node.js`](../../real_tests/integration/circuit-loom-two-node.js)
@@ -0,0 +1,22 @@
# Architecture: hyper-p2p-circuit-loom
Multi-hop overlay circuits for Bare/Pear P2P overlays.
```mermaid
flowchart TB
App[Application] --> Mod[HyperP2PCircuitLoom]
Mod --> Mux[Protomux circuit-loom/v1]
Mux --> Swarm[Hyperswarm topic]
```
## Wire messages (gossip)
JSON envelopes via `gossipSend` when connected. Message `type` fields are module-specific; see `index.js` `onmessage` handler.
## Composition (Wave 6)
See [`../_shared/WAVE6_NETWORK_STACK.md`](../_shared/WAVE6_NETWORK_STACK.md) for pairing with network-stack modules.
## State
In-memory maps/arrays; merged from remote gossip when `topic` is configured.
+11
View File
@@ -0,0 +1,11 @@
require('bare-process/global')
const { HyperP2PCircuitLoom } = require('..')
async function main () {
const m = new HyperP2PCircuitLoom()
const id = m.buildCircuit(['a','b']); m.relay(id, 'x')
console.log('ok', m.getStats())
await m.close()
}
main().catch(console.error)
+100
View File
@@ -0,0 +1,100 @@
require('bare-process/global')
const EventEmitter = require('bare-events')
const b4a = require('b4a')
const crypto = require('hypercore-crypto')
const { initModuleSwarm, gossipSend } = require('../_shared/p2p-bare.js')
const PROTOCOL = 'circuit-loom/v1'
class HyperP2PCircuitLoom extends EventEmitter {
constructor (opts = {}) {
super()
this.topic = opts.topic || null
this.keyPair = opts.keyPair || crypto.keyPair()
this.peerId = b4a.toString(this.keyPair.publicKey, 'hex')
this._circuits = new Map()
this._stats = { created: 0, relays: 0 }
this.swarm = null
this._peerMsgs = null
}
_deriveKey (hopPeer) {
return crypto.hash(b4a.from(this.peerId + hopPeer))
}
buildCircuit (hopPeers) {
if (!Array.isArray(hopPeers) || hopPeers.length < 1) throw new Error('hopPeers required')
const circuitId = b4a.toString(crypto.hash(b4a.from(hopPeers.join(':') + Date.now())), 'hex').slice(0, 16)
const hops = hopPeers.map((p, i) => ({ peer: p, key: this._deriveKey(p), index: i }))
const circuit = { circuitId, hops, state: 'open', createdAt: Date.now() }
this._circuits.set(circuitId, circuit)
this._stats.created++
if (this._peerMsgs) gossipSend(this, { type: 'CREATE', circuit })
this.emit('circuit-built', circuit)
return circuitId
}
extend (circuitId, nextHop) {
const c = this._circuits.get(circuitId)
if (!c) throw new Error('unknown circuit')
if (!nextHop) throw new Error('nextHop required')
c.hops.push({ peer: nextHop, key: this._deriveKey(nextHop), index: c.hops.length })
if (this._peerMsgs) gossipSend(this, { type: 'EXTEND', circuitId, hop: nextHop })
this.emit('extended', { circuitId, hop: nextHop })
return c
}
relay (circuitId, payload) {
const c = this._circuits.get(circuitId)
if (!c || c.state !== 'open') return false
if (payload == null) throw new Error('payload required')
const buf = b4a.isBuffer(payload) ? payload : b4a.from(JSON.stringify(payload))
let cell = buf
for (const hop of c.hops) cell = crypto.hash(b4a.concat([cell, hop.key]))
const frame = { type: 'RELAY', circuitId, cell: b4a.toString(cell, 'hex'), from: this.peerId }
if (this._peerMsgs) gossipSend(this, frame)
this._stats.relays++
this.emit('relay', frame)
this.emit('exit-deliver', { circuitId, payload: buf, hops: c.hops.length })
return true
}
teardown (circuitId) {
const c = this._circuits.get(circuitId)
if (!c) return false
c.state = 'closed'
this._circuits.delete(circuitId)
if (this._peerMsgs) gossipSend(this, { type: 'DESTROY', circuitId })
this.emit('destroyed', { circuitId })
return true
}
rotateKeys (circuitId) {
const c = this._circuits.get(circuitId)
if (!c) throw new Error('unknown circuit')
for (const hop of c.hops) hop.key = this._deriveKey(hop.peer + ':rot' + Date.now())
this.emit('keys-rotated', { circuitId })
return true
}
getStats () { return { ...this._stats, open: [...this._circuits.values()].filter(c => c.state === 'open').length } }
async ready () {
if (this.swarm || !this.topic) return this
await initModuleSwarm(this, {
keyPair: this.keyPair, topic: this.topic, protocol: PROTOCOL,
onmessage: (data) => {
if (data?.type === 'RELAY') this.emit('remote-relay', data)
else if (data?.type === 'CREATE' && data.circuit) this._circuits.set(data.circuit.circuitId, data.circuit)
}
})
return this
}
async close () {
if (this.swarm) await this.swarm.destroy().catch(() => {})
this.swarm = null
this._circuits.clear()
this.emit('closed')
}
}
module.exports = { HyperP2PCircuitLoom, PROTOCOL }
File diff suppressed because it is too large Load Diff
+25
View File
@@ -0,0 +1,25 @@
{
"name": "hyper-p2p-circuit-loom",
"version": "0.3.0",
"description": "Multi-hop overlay circuits with CREATE/EXTEND/RELAY cells.",
"main": "index.js",
"type": "commonjs",
"license": "Apache-2.0",
"scripts": { "test": "brittle-bare test/test.js" },
"dependencies": {
"bare-events": "^2.8.0",
"bare-process": "^4.4.0",
"bare-timers": "^2.0.0",
"b4a": "^1.6.7",
"hypercore-crypto": "^3.0.0",
"protomux": "^3.0.0",
"compact-encoding": "^2.0.0"
},
"peerDependencies": { "hyperswarm": "^4.0.0", "bare": ">=1.0.0" },
"devDependencies": { "brittle": "^3.0.0" },
"imports": {
"process": { "bare": "bare-process", "default": "process" },
"events": { "bare": "bare-events", "default": "events" },
"timers": { "bare": "bare-timers", "default": "timers" }
}
}
+35
View File
@@ -0,0 +1,35 @@
require('bare-process/global')
const test = require('brittle')
const { HyperP2PCircuitLoom } = require('../index.js')
test('hyper-p2p-circuit-loom: basic operation', async (t) => {
const m = new HyperP2PCircuitLoom()
const id = m.buildCircuit(['hop1', 'hop2'])
t.ok(m.relay(id, 'secret'))
await m.close()
})
test('hyper-p2p-circuit-loom: validation', async (t) => {
const m = new HyperP2PCircuitLoom()
try {
m.buildCircuit([])
t.fail('expected throw')
} catch (e) {
t.ok(e instanceof Error)
}
await m.close()
})
test('hyper-p2p-circuit-loom: getStats', async (t) => {
const m = new HyperP2PCircuitLoom()
const s = m.getStats()
t.ok(s)
await m.close()
})
test('hyper-p2p-circuit-loom: close idempotent', async (t) => {
const m = new HyperP2PCircuitLoom()
await m.close()
await m.close()
t.pass()
})
+3
View File
@@ -7,3 +7,6 @@
## v0.2.0 ## v0.2.0
- Production-grade docs, validation, and expanded tests. - Production-grade docs, validation, and expanded tests.
## v0.3.0
- Wave 6: presence-tier API tables, architecture wire section, validation test.
+22 -3
View File
@@ -18,15 +18,34 @@ const mod = new HyperP2PConflictSet(opts)
## Methods ## Methods
See [`index.js`](../index.js) for the full method list. Core operations implement **OR-Set CRDT**. | Method | Returns | Notes |
|--------|---------|-------|
| `add(...)` | See source | — |
| `close(...)` | See source | — |
| `has(...)` | See source | — |
| `merge(...)` | See source | — |
| `ready(...)` | See source | — |
| `remove(...)` | See source | — |
| `toJSON(...)` | See source | — |
| `values(...)` | See source | — |
## Events ## Events
The instance extends `EventEmitter`. Common events: `closed`, plus module-specific events documented in source. | Event | Description |
|-------|-------------|
| `add` | Module-specific |
| `closed` | Module-specific |
| `merge` | Module-specific |
| `remote-op` | Module-specific |
| `remove` | Module-specific |
## Metrics
Call `getStats()` when implemented for counters (Wave 6 network modules always expose stats).
## P2P ## P2P
When `topic` is set, `ready()` joins Hyperswarm and opens Protomux channel `conflict-set/v1` via [`_shared/p2p-bare.js`](../_shared/p2p-bare.js). When `topic` is set, `ready()` joins Hyperswarm and opens Protomux `conflict-set/v1` via [`_shared/p2p-bare.js`](../_shared/p2p-bare.js).
## Testing ## Testing
+16 -4
View File
@@ -1,10 +1,22 @@
# Architecture: hyper-p2p-conflict-set # Architecture: hyper-p2p-conflict-set
Or-set crdt for Bare/Pear P2P overlays.
```mermaid ```mermaid
flowchart LR flowchart TB
App[Application] --> Mod[HyperP2PConflictSet] App[Application] --> Mod[HyperP2PConflictSet]
Mod --> P2P[Protomux conflict-set/v1] Mod --> Mux[Protomux conflict-set/v1]
P2P --> Swarm[Hyperswarm] Mux --> Swarm[Hyperswarm topic]
``` ```
Local state lives in memory maps/arrays; gossip merges remote updates when `topic` is configured. ## Wire messages (gossip)
JSON envelopes via `gossipSend` when connected. Message `type` fields are module-specific; see `index.js` `onmessage` handler.
## Composition (Wave 6)
See [`../_shared/WAVE6_NETWORK_STACK.md`](../_shared/WAVE6_NETWORK_STACK.md) for pairing with network-stack modules.
## State
In-memory maps/arrays; merged from remote gossip when `topic` is configured.
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "hyper-p2p-conflict-set", "name": "hyper-p2p-conflict-set",
"version": "0.2.0", "version": "0.3.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "hyper-p2p-conflict-set", "name": "hyper-p2p-conflict-set",
"version": "0.2.0", "version": "0.3.0",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"b4a": "^1.6.7", "b4a": "^1.6.7",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "hyper-p2p-conflict-set", "name": "hyper-p2p-conflict-set",
"version": "0.2.0", "version": "0.3.0",
"description": "OR-Set CRDT conflict-free set for Bare/Pear P2P.", "description": "OR-Set CRDT conflict-free set for Bare/Pear P2P.",
"main": "index.js", "main": "index.js",
"type": "commonjs", "type": "commonjs",
+19
View File
@@ -25,3 +25,22 @@ test('hyper-p2p-conflict-set: close without leak', async (t) => {
await m.close() await m.close()
t.pass() t.pass()
}) })
test('hyper-p2p-conflict-set: validation rejects invalid input', async (t) => {
const m = new HyperP2PConflictSet()
try {
if (typeof m.addNeighbor === 'function') m.addNeighbor(null)
else if (typeof m.buildCircuit === 'function') m.buildCircuit([])
else if (typeof m.grant === 'function') m.grant(null, -1)
else if (typeof m.enqueue === 'function') m.enqueue('bad', null)
else if (typeof m.reportSample === 'function') m.reportSample(null, -1, -1)
else if (typeof m.fanout === 'function') m.fanout(null, 0)
else if (typeof m.probe === 'function') m.probe(null)
else if (typeof m.resolve === 'function') m.resolve(null)
else if (typeof m.acquire === 'function') m.acquire(null)
else throw new Error('no validation hook')
t.fail('expected throw')
} catch (err) {
t.ok(err instanceof Error)
}
await m.close()
})
+8
View File
@@ -0,0 +1,8 @@
# Changelog
## v0.1.0
- Initial release: Gossip RTT/loss samples and recommended send rates.
## v0.3.0
- Wave 6: presence-tier API tables, architecture wire section, validation test.
+9
View File
@@ -0,0 +1,9 @@
# hyper-p2p-congestion-signal
Gossip RTT/loss samples and recommended send rates.
**Protocol:** `congestion-signal/v1`
```bash
npm test
```
+52
View File
@@ -0,0 +1,52 @@
# API: hyper-p2p-congestion-signal
**Protocol:** `congestion-signal/v1`
**Export:** `HyperP2PCongestionSignal`
## Constructor
```js
const mod = new HyperP2PCongestionSignal(opts)
```
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `topic` | `string` \| `Buffer` | `null` | Hyperswarm topic; enables P2P when set |
| `keyPair` | `KeyPair` | random | Ed25519 key pair (`hypercore-crypto`) |
| `enableBackgroundTimers` | `boolean` | `false` | Periodic timers (keep false in unit tests) |
## Methods
| Method | Returns | Notes |
|--------|---------|-------|
| `close(...)` | See source | — |
| `getHint(...)` | See source | — |
| `getStats(...)` | See source | — |
| `ready(...)` | See source | — |
| `reportSample(...)` | See source | — |
| `shouldThrottle(...)` | See source | — |
## Events
| Event | Description |
|-------|-------------|
| `closed` | Module-specific |
| `sample` | Module-specific |
## Metrics
Call `getStats()` when implemented for counters (Wave 6 network modules always expose stats).
## P2P
When `topic` is set, `ready()` joins Hyperswarm and opens Protomux `congestion-signal/v1` via [`_shared/p2p-bare.js`](../_shared/p2p-bare.js).
## Testing
```bash
npm install
npm test
```
Integration: [`../../real_tests/integration/congestion-signal-two-node.js`](../../real_tests/integration/congestion-signal-two-node.js)
@@ -0,0 +1,22 @@
# Architecture: hyper-p2p-congestion-signal
Gossip congestion hints for Bare/Pear P2P overlays.
```mermaid
flowchart TB
App[Application] --> Mod[HyperP2PCongestionSignal]
Mod --> Mux[Protomux congestion-signal/v1]
Mux --> Swarm[Hyperswarm topic]
```
## Wire messages (gossip)
JSON envelopes via `gossipSend` when connected. Message `type` fields are module-specific; see `index.js` `onmessage` handler.
## Composition (Wave 6)
See [`../_shared/WAVE6_NETWORK_STACK.md`](../_shared/WAVE6_NETWORK_STACK.md) for pairing with network-stack modules.
## State
In-memory maps/arrays; merged from remote gossip when `topic` is configured.
@@ -0,0 +1,11 @@
require('bare-process/global')
const { HyperP2PCongestionSignal } = require('..')
async function main () {
const m = new HyperP2PCongestionSignal()
m.reportSample('peer', 30, 0)
console.log('ok', m.getStats())
await m.close()
}
main().catch(console.error)
+64
View File
@@ -0,0 +1,64 @@
require('bare-process/global')
const EventEmitter = require('bare-events')
const { initModuleSwarm, gossipSend } = require('../_shared/p2p-bare.js')
const PROTOCOL = 'congestion-signal/v1'
class HyperP2PCongestionSignal extends EventEmitter {
constructor (opts = {}) {
super()
this.topic = opts.topic || null
this.keyPair = opts.keyPair || require('hypercore-crypto').keyPair()
this.baseRateBps = opts.baseRateBps || 1_000_000
this._samples = new Map()
this._stats = { samples: 0 }
this.swarm = null
this._peerMsgs = null
}
reportSample (peerId, rttMs, loss = 0) {
if (!peerId) throw new Error('peerId required')
if (rttMs < 0 || loss < 0 || loss > 1) throw new Error('invalid rtt or loss')
const s = { peerId, rttMs, loss, at: Date.now() }
this._samples.set(peerId, s)
this._stats.samples++
if (this._peerMsgs) gossipSend(this, { type: 'congestion-sample', sample: s })
this.emit('sample', s)
return s
}
getHint (peerId) {
const s = this._samples.get(peerId)
if (!s) return { sendRateBps: this.baseRateBps }
const factor = Math.max(0.1, 1 - s.loss - (s.rttMs / 1000) * 0.1)
return { sendRateBps: Math.floor(this.baseRateBps * factor), rttMs: s.rttMs, loss: s.loss }
}
shouldThrottle (peerId) {
const h = this.getHint(peerId)
return h.sendRateBps < this.baseRateBps * 0.5
}
getStats () { return { ...this._stats, peers: this._samples.size } }
async ready () {
if (this.swarm || !this.topic) return this
await initModuleSwarm(this, {
keyPair: this.keyPair, topic: this.topic, protocol: PROTOCOL,
onmessage: (data) => {
if (data?.type === 'congestion-sample' && data.sample) {
const cur = this._samples.get(data.sample.peerId)
if (!cur || data.sample.at >= cur.at) this._samples.set(data.sample.peerId, data.sample)
}
}
})
return this
}
async close () {
if (this.swarm) await this.swarm.destroy().catch(() => {})
this.swarm = null
this._samples.clear()
this.emit('closed')
}
}
module.exports = { HyperP2PCongestionSignal, PROTOCOL }
File diff suppressed because it is too large Load Diff
+25
View File
@@ -0,0 +1,25 @@
{
"name": "hyper-p2p-congestion-signal",
"version": "0.3.0",
"description": "Gossip RTT/loss samples and recommended send rates.",
"main": "index.js",
"type": "commonjs",
"license": "Apache-2.0",
"scripts": { "test": "brittle-bare test/test.js" },
"dependencies": {
"bare-events": "^2.8.0",
"bare-process": "^4.4.0",
"bare-timers": "^2.0.0",
"b4a": "^1.6.7",
"hypercore-crypto": "^3.0.0",
"protomux": "^3.0.0",
"compact-encoding": "^2.0.0"
},
"peerDependencies": { "hyperswarm": "^4.0.0", "bare": ">=1.0.0" },
"devDependencies": { "brittle": "^3.0.0" },
"imports": {
"process": { "bare": "bare-process", "default": "process" },
"events": { "bare": "bare-events", "default": "events" },
"timers": { "bare": "bare-timers", "default": "timers" }
}
}
+35
View File
@@ -0,0 +1,35 @@
require('bare-process/global')
const test = require('brittle')
const { HyperP2PCongestionSignal } = require('../index.js')
test('hyper-p2p-congestion-signal: basic operation', async (t) => {
const m = new HyperP2PCongestionSignal()
m.reportSample('p1', 50, 0.1)
t.ok(m.getHint('p1').sendRateBps > 0)
await m.close()
})
test('hyper-p2p-congestion-signal: validation', async (t) => {
const m = new HyperP2PCongestionSignal()
try {
m.reportSample(null, -1, 0)
t.fail('expected throw')
} catch (e) {
t.ok(e instanceof Error)
}
await m.close()
})
test('hyper-p2p-congestion-signal: getStats', async (t) => {
const m = new HyperP2PCongestionSignal()
const s = m.getStats()
t.ok(s)
await m.close()
})
test('hyper-p2p-congestion-signal: close idempotent', async (t) => {
const m = new HyperP2PCongestionSignal()
await m.close()
await m.close()
t.pass()
})
+8
View File
@@ -0,0 +1,8 @@
# Changelog
## v0.1.0
- Initial release: Warm peer connection lanes with idle teardown.
## v0.3.0
- Wave 6: presence-tier API tables, architecture wire section, validation test.
+9
View File
@@ -0,0 +1,9 @@
# hyper-p2p-connection-pool
Warm peer connection lanes with idle teardown.
**Protocol:** `connection-pool/v1`
```bash
npm test
```
+55
View File
@@ -0,0 +1,55 @@
# API: hyper-p2p-connection-pool
**Protocol:** `connection-pool/v1`
**Export:** `HyperP2PConnectionPool`
## Constructor
```js
const mod = new HyperP2PConnectionPool(opts)
```
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `topic` | `string` \| `Buffer` | `null` | Hyperswarm topic; enables P2P when set |
| `keyPair` | `KeyPair` | random | Ed25519 key pair (`hypercore-crypto`) |
| `enableBackgroundTimers` | `boolean` | `false` | Periodic timers (keep false in unit tests) |
## Methods
| Method | Returns | Notes |
|--------|---------|-------|
| `acquire(...)` | See source | — |
| `close(...)` | See source | — |
| `getPoolStats(...)` | See source | — |
| `getStats(...)` | See source | — |
| `ready(...)` | See source | — |
| `release(...)` | See source | — |
| `setIdleTimeout(...)` | See source | — |
## Events
| Event | Description |
|-------|-------------|
| `acquire` | Module-specific |
| `closed` | Module-specific |
| `idle-closed` | Module-specific |
| `release` | Module-specific |
## Metrics
Call `getStats()` when implemented for counters (Wave 6 network modules always expose stats).
## P2P
When `topic` is set, `ready()` joins Hyperswarm and opens Protomux `connection-pool/v1` via [`_shared/p2p-bare.js`](../_shared/p2p-bare.js).
## Testing
```bash
npm install
npm test
```
Integration: [`../../real_tests/integration/connection-pool-two-node.js`](../../real_tests/integration/connection-pool-two-node.js)
@@ -0,0 +1,22 @@
# Architecture: hyper-p2p-connection-pool
Peer connection pool lifecycle for Bare/Pear P2P overlays.
```mermaid
flowchart TB
App[Application] --> Mod[HyperP2PConnectionPool]
Mod --> Mux[Protomux connection-pool/v1]
Mux --> Swarm[Hyperswarm topic]
```
## Wire messages (gossip)
JSON envelopes via `gossipSend` when connected. Message `type` fields are module-specific; see `index.js` `onmessage` handler.
## Composition (Wave 6)
See [`../_shared/WAVE6_NETWORK_STACK.md`](../_shared/WAVE6_NETWORK_STACK.md) for pairing with network-stack modules.
## State
In-memory maps/arrays; merged from remote gossip when `topic` is configured.
@@ -0,0 +1,11 @@
require('bare-process/global')
const { HyperP2PConnectionPool } = require('..')
async function main () {
const m = new HyperP2PConnectionPool()
m.acquire('peer'); m.release('peer')
console.log('ok', m.getStats())
await m.close()
}
main().catch(console.error)
+96
View File
@@ -0,0 +1,96 @@
require('bare-process/global')
const EventEmitter = require('bare-events')
const timers = require('bare-timers')
const { initModuleSwarm, gossipSend } = require('../_shared/p2p-bare.js')
const PROTOCOL = 'connection-pool/v1'
class HyperP2PConnectionPool extends EventEmitter {
constructor (opts = {}) {
super()
this.topic = opts.topic || null
this.keyPair = opts.keyPair || require('hypercore-crypto').keyPair()
this.maxConcurrent = opts.maxConcurrent || 32
this.idleTimeoutMs = opts.idleTimeoutMs || 60000
this.enableBackgroundTimers = opts.enableBackgroundTimers === true
this._lanes = new Map()
this._stats = { acquires: 0, releases: 0, idleClosed: 0 }
this._sweep = null
this.swarm = null
this._peerMsgs = null
}
setIdleTimeout (ms) {
if (ms < 0) throw new Error('idle timeout must be non-negative')
this.idleTimeoutMs = ms
}
acquire (peerId) {
if (!peerId) throw new Error('peerId required')
if (this._lanes.size >= this.maxConcurrent && !this._lanes.has(peerId)) {
throw new Error('maxConcurrent exceeded')
}
const lane = this._lanes.get(peerId) || { peerId, state: 'open', uses: 0, lastUsed: 0 }
lane.state = 'open'
lane.uses++
lane.lastUsed = Date.now()
this._lanes.set(peerId, lane)
this._stats.acquires++
this.emit('acquire', lane)
return lane
}
release (peerId) {
if (!peerId) throw new Error('peerId required')
const lane = this._lanes.get(peerId)
if (!lane) return false
lane.state = 'idle'
lane.lastUsed = Date.now()
this._stats.releases++
this.emit('release', lane)
return true
}
_sweepIdle () {
const now = Date.now()
for (const [id, lane] of this._lanes) {
if (lane.state === 'idle' && now - lane.lastUsed > this.idleTimeoutMs) {
this._lanes.delete(id)
this._stats.idleClosed++
this.emit('idle-closed', { peerId: id })
}
}
}
getPoolStats () {
return {
open: [...this._lanes.values()].filter(l => l.state === 'open').length,
idle: [...this._lanes.values()].filter(l => l.state === 'idle').length,
total: this._lanes.size,
...this._stats
}
}
getStats () { return this.getPoolStats() }
async ready () {
if (this.swarm || !this.topic) return this
await initModuleSwarm(this, {
keyPair: this.keyPair, topic: this.topic, protocol: PROTOCOL,
onmessage: () => {}
})
if (this.enableBackgroundTimers) {
this._sweep = timers.setInterval(() => this._sweepIdle(), Math.min(this.idleTimeoutMs, 5000))
}
return this
}
async close () {
if (this._sweep) clearInterval(this._sweep)
this._sweep = null
if (this.swarm) await this.swarm.destroy().catch(() => {})
this.swarm = null
this._lanes.clear()
this.emit('closed')
}
}
module.exports = { HyperP2PConnectionPool, PROTOCOL }
File diff suppressed because it is too large Load Diff
+25
View File
@@ -0,0 +1,25 @@
{
"name": "hyper-p2p-connection-pool",
"version": "0.3.0",
"description": "Warm peer connection lanes with idle teardown.",
"main": "index.js",
"type": "commonjs",
"license": "Apache-2.0",
"scripts": { "test": "brittle-bare test/test.js" },
"dependencies": {
"bare-events": "^2.8.0",
"bare-process": "^4.4.0",
"bare-timers": "^2.0.0",
"b4a": "^1.6.7",
"hypercore-crypto": "^3.0.0",
"protomux": "^3.0.0",
"compact-encoding": "^2.0.0"
},
"peerDependencies": { "hyperswarm": "^4.0.0", "bare": ">=1.0.0" },
"devDependencies": { "brittle": "^3.0.0" },
"imports": {
"process": { "bare": "bare-process", "default": "process" },
"events": { "bare": "bare-events", "default": "events" },
"timers": { "bare": "bare-timers", "default": "timers" }
}
}
+36
View File
@@ -0,0 +1,36 @@
require('bare-process/global')
const test = require('brittle')
const { HyperP2PConnectionPool } = require('../index.js')
test('hyper-p2p-connection-pool: basic operation', async (t) => {
const m = new HyperP2PConnectionPool()
m.acquire('peer-x')
m.release('peer-x')
t.is(m.getPoolStats().total, 1)
await m.close()
})
test('hyper-p2p-connection-pool: validation', async (t) => {
const m = new HyperP2PConnectionPool()
try {
m.acquire(null)
t.fail('expected throw')
} catch (e) {
t.ok(e instanceof Error)
}
await m.close()
})
test('hyper-p2p-connection-pool: getStats', async (t) => {
const m = new HyperP2PConnectionPool()
const s = m.getStats()
t.ok(s)
await m.close()
})
test('hyper-p2p-connection-pool: close idempotent', async (t) => {
const m = new HyperP2PConnectionPool()
await m.close()
await m.close()
t.pass()
})
@@ -6,3 +6,6 @@
## v0.2.0 ## v0.2.0
- Production-grade docs, validation, and expanded tests. - Production-grade docs, validation, and expanded tests.
## v0.3.0
- Wave 6: presence-tier API tables, architecture wire section, validation test.
+18 -3
View File
@@ -18,15 +18,30 @@ const mod = new HyperP2PContradictionGraph(opts)
## Methods ## Methods
See [`index.js`](../index.js) for the full method list. Core operations implement **opposing claims contradiction lattice**. | Method | Returns | Notes |
|--------|---------|-------|
| `claim(...)` | See source | — |
| `close(...)` | See source | — |
| `conflictSet(...)` | See source | — |
| `contradicts(...)` | See source | — |
| `merge(...)` | See source | — |
| `ready(...)` | See source | — |
| `toJSON(...)` | See source | — |
## Events ## Events
The instance extends `EventEmitter`. Common events: `closed`, plus module-specific events documented in source. | Event | Description |
|-------|-------------|
| `claim` | Module-specific |
| `closed` | Module-specific |
## Metrics
Call `getStats()` when implemented for counters (Wave 6 network modules always expose stats).
## P2P ## P2P
When `topic` is set, `ready()` joins Hyperswarm and opens Protomux channel `contradiction-graph/v1` via [`_shared/p2p-bare.js`](../_shared/p2p-bare.js). When `topic` is set, `ready()` joins Hyperswarm and opens Protomux `contradiction-graph/v1` via [`_shared/p2p-bare.js`](../_shared/p2p-bare.js).
## Testing ## Testing
@@ -1,10 +1,22 @@
# Architecture: hyper-p2p-contradiction-graph # Architecture: hyper-p2p-contradiction-graph
Opposing claims contradiction lattice for Bare/Pear P2P overlays.
```mermaid ```mermaid
flowchart LR flowchart TB
App[Application] --> Mod[HyperP2PContradictionGraph] App[Application] --> Mod[HyperP2PContradictionGraph]
Mod --> P2P[Protomux contradiction-graph/v1] Mod --> Mux[Protomux contradiction-graph/v1]
P2P --> Swarm[Hyperswarm] Mux --> Swarm[Hyperswarm topic]
``` ```
Local state lives in memory maps/arrays; gossip merges remote updates when `topic` is configured. ## Wire messages (gossip)
JSON envelopes via `gossipSend` when connected. Message `type` fields are module-specific; see `index.js` `onmessage` handler.
## Composition (Wave 6)
See [`../_shared/WAVE6_NETWORK_STACK.md`](../_shared/WAVE6_NETWORK_STACK.md) for pairing with network-stack modules.
## State
In-memory maps/arrays; merged from remote gossip when `topic` is configured.
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "hyper-p2p-contradiction-graph", "name": "hyper-p2p-contradiction-graph",
"version": "0.2.0", "version": "0.3.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "hyper-p2p-contradiction-graph", "name": "hyper-p2p-contradiction-graph",
"version": "0.2.0", "version": "0.3.0",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"b4a": "^1.6.7", "b4a": "^1.6.7",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "hyper-p2p-contradiction-graph", "name": "hyper-p2p-contradiction-graph",
"version": "0.2.0", "version": "0.3.0",
"description": "Opposing claims contradiction lattice for Bare/Pear P2P.", "description": "Opposing claims contradiction lattice for Bare/Pear P2P.",
"main": "index.js", "main": "index.js",
"type": "commonjs", "type": "commonjs",
@@ -25,3 +25,22 @@ test('hyper-p2p-contradiction-graph: close without leak', async (t) => {
await m.close() await m.close()
t.pass() t.pass()
}) })
test('hyper-p2p-contradiction-graph: validation rejects invalid input', async (t) => {
const m = new HyperP2PContradictionGraph()
try {
if (typeof m.addNeighbor === 'function') m.addNeighbor(null)
else if (typeof m.buildCircuit === 'function') m.buildCircuit([])
else if (typeof m.grant === 'function') m.grant(null, -1)
else if (typeof m.enqueue === 'function') m.enqueue('bad', null)
else if (typeof m.reportSample === 'function') m.reportSample(null, -1, -1)
else if (typeof m.fanout === 'function') m.fanout(null, 0)
else if (typeof m.probe === 'function') m.probe(null)
else if (typeof m.resolve === 'function') m.resolve(null)
else if (typeof m.acquire === 'function') m.acquire(null)
else throw new Error('no validation hook')
t.fail('expected throw')
} catch (err) {
t.ok(err instanceof Error)
}
await m.close()
})
+3
View File
@@ -6,3 +6,6 @@
## v0.2.0 ## v0.2.0
- Production-grade docs, validation, and expanded tests. - Production-grade docs, validation, and expanded tests.
## v0.3.0
- Wave 6: presence-tier API tables, architecture wire section, validation test.
+20 -3
View File
@@ -18,15 +18,32 @@ const mod = new HyperP2PCrdtMap(opts)
## Methods ## Methods
See [`index.js`](../index.js) for the full method list. Core operations implement **LWW-Map CRDT**. | Method | Returns | Notes |
|--------|---------|-------|
| `close(...)` | See source | — |
| `delete(...)` | See source | — |
| `get(...)` | See source | — |
| `keys(...)` | See source | — |
| `merge(...)` | See source | — |
| `ready(...)` | See source | — |
| `set(...)` | See source | — |
| `toJSON(...)` | See source | — |
## Events ## Events
The instance extends `EventEmitter`. Common events: `closed`, plus module-specific events documented in source. | Event | Description |
|-------|-------------|
| `closed` | Module-specific |
| `merge` | Module-specific |
| `set` | Module-specific |
## Metrics
Call `getStats()` when implemented for counters (Wave 6 network modules always expose stats).
## P2P ## P2P
When `topic` is set, `ready()` joins Hyperswarm and opens Protomux channel `crdt-map/v1` via [`_shared/p2p-bare.js`](../_shared/p2p-bare.js). When `topic` is set, `ready()` joins Hyperswarm and opens Protomux `crdt-map/v1` via [`_shared/p2p-bare.js`](../_shared/p2p-bare.js).
## Testing ## Testing
+16 -4
View File
@@ -1,10 +1,22 @@
# Architecture: hyper-p2p-crdt-map # Architecture: hyper-p2p-crdt-map
Lww-map crdt for Bare/Pear P2P overlays.
```mermaid ```mermaid
flowchart LR flowchart TB
App[Application] --> Mod[HyperP2PCrdtMap] App[Application] --> Mod[HyperP2PCrdtMap]
Mod --> P2P[Protomux crdt-map/v1] Mod --> Mux[Protomux crdt-map/v1]
P2P --> Swarm[Hyperswarm] Mux --> Swarm[Hyperswarm topic]
``` ```
Local state lives in memory maps/arrays; gossip merges remote updates when `topic` is configured. ## Wire messages (gossip)
JSON envelopes via `gossipSend` when connected. Message `type` fields are module-specific; see `index.js` `onmessage` handler.
## Composition (Wave 6)
See [`../_shared/WAVE6_NETWORK_STACK.md`](../_shared/WAVE6_NETWORK_STACK.md) for pairing with network-stack modules.
## State
In-memory maps/arrays; merged from remote gossip when `topic` is configured.
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "hyper-p2p-crdt-map", "name": "hyper-p2p-crdt-map",
"version": "0.2.0", "version": "0.3.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "hyper-p2p-crdt-map", "name": "hyper-p2p-crdt-map",
"version": "0.2.0", "version": "0.3.0",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"b4a": "^1.6.7", "b4a": "^1.6.7",

Some files were not shown because too many files have changed in this diff Show More