yakmesh 1.7.1 β†’ 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,7 +2,61 @@
2
2
 
3
3
  All notable changes to YAKMESH will be documented in this file.
4
4
 
5
- ## [1.7.0] - 2026-01-18
5
+ ## [1.8.0] - 2026-01-18
6
+
7
+ ### πŸ”οΈ SHERPA: Decentralized Peer Discovery
8
+
9
+ This release implements SHERPA, a novel peer discovery mechanism that uses the public web as a decentralized DHT.
10
+
11
+ #### New Feature: SHERPA Discovery
12
+
13
+ ##### The Innovation: "The Web IS the DHT"
14
+ - Each node exposes `/.well-known/yakmesh/beacon` with its peer list
15
+ - Discovery crawls known endpoints to find new peers
16
+ - No central authority - truly decentralized bootstrap
17
+ - Works with existing CDN infrastructure
18
+
19
+ ##### New Module: `mesh/sherpa-discovery.js`
20
+ - `SherpaDiscovery` - Main discovery engine with peer crawling
21
+ - `BeaconMessage` - Signed beacon format for peer advertisement
22
+ - `PeerRegistry` - Scored peer management with decay
23
+ - `createBeaconMiddleware` - Express middleware for beacon endpoint
24
+
25
+ ##### New Endpoints
26
+ - `GET /.well-known/yakmesh/beacon` - Advertise this node and known peers
27
+ - `GET /sherpa/status` - Discovery statistics
28
+ - `GET /sherpa/candidates` - Get connection candidates
29
+
30
+ ##### Configuration
31
+ ```javascript
32
+ // yakmesh.config.js
33
+ export default {
34
+ sherpa: {
35
+ enabled: true,
36
+ selfEndpoint: 'https://mynode.example.com',
37
+ wsEndpoint: 'wss://mynode.example.com:9001',
38
+ seeds: ['https://peer1.example.com', 'https://peer2.example.com'],
39
+ },
40
+ };
41
+ ```
42
+
43
+ ##### Beacon Response Format
44
+ ```json
45
+ {
46
+ "version": "1.0",
47
+ "nodeId": "abc123...",
48
+ "networkName": "mobius-rabi-junction",
49
+ "timestamp": 1737225600000,
50
+ "capabilities": { "wsPort": 9001, "supportsAnnex": true },
51
+ "peers": [{ "nodeId": "...", "endpoint": "https://..." }],
52
+ "publicKey": "...",
53
+ "signature": "..."
54
+ }
55
+ ```
56
+
57
+ ---
58
+
59
+ ## [1.7.1] - 2026-01-18
6
60
 
7
61
  ### 🦬 NAKPAK & SHERPA: Yak-Themed Protocol Naming
8
62
 
@@ -0,0 +1,655 @@
1
+ /**
2
+ * SHERPA - Secure Hidden Endpoint Resolution Path Architecture
3
+ *
4
+ * A novel peer discovery mechanism using public web endpoints as a decentralized DHT.
5
+ * Instead of centralized bootstrap nodes, SHERPA leverages the public-facing portion
6
+ * of yakmesh nodes (Caddy/Abyss web servers) to create a self-organizing peer registry.
7
+ *
8
+ * Key Innovation: "The web IS the DHT"
9
+ * - Each node exposes /.well-known/yakmesh/beacon with its peer list
10
+ * - Discovery crawls known endpoints to find new peers
11
+ * - No central authority - truly decentralized bootstrap
12
+ * - Works with existing CDN infrastructure
13
+ *
14
+ * Etymology: Sherpas guide travelers through hidden mountain paths,
15
+ * just like SHERPA guides nodes to discover each other.
16
+ *
17
+ * @module mesh/sherpa-discovery
18
+ * @license MIT
19
+ * @copyright 2026 YAKMESHβ„’ Contributors
20
+ */
21
+
22
+ import { sha3_256 } from '@noble/hashes/sha3.js';
23
+ import { bytesToHex, randomBytes, utf8ToBytes } from '@noble/hashes/utils.js';
24
+ import { EventEmitter } from 'events';
25
+
26
+ // ============================================================
27
+ // SHERPA CONFIGURATION
28
+ // ============================================================
29
+
30
+ const SHERPA_CONFIG = {
31
+ // Beacon endpoint
32
+ beaconPath: '/.well-known/yakmesh/beacon',
33
+
34
+ // Discovery settings
35
+ maxPeersPerBeacon: 50, // Max peers to advertise in beacon
36
+ maxPeersToReturn: 20, // Max peers to return per request
37
+ maxCrawlDepth: 3, // How many hops to crawl
38
+ crawlTimeout: 5000, // Timeout for each beacon fetch (ms)
39
+ crawlInterval: 300000, // Re-crawl interval (5 minutes)
40
+
41
+ // Peer scoring
42
+ minPeerScore: 0.1, // Minimum score to keep peer
43
+ scoreDecay: 0.95, // Score decay per interval
44
+ successBonus: 0.2, // Score bonus for successful contact
45
+ failurePenalty: 0.3, // Score penalty for failed contact
46
+
47
+ // Security
48
+ maxBeaconSize: 65536, // Max beacon response size (64KB)
49
+ signatureRequired: true, // Require signed beacons
50
+
51
+ // Rate limiting
52
+ maxCrawlsPerMinute: 10, // Prevent crawl storms
53
+
54
+ // Version
55
+ protocolVersion: '1.0',
56
+ };
57
+
58
+ // ============================================================
59
+ // BEACON MESSAGE FORMAT
60
+ // ============================================================
61
+
62
+ /**
63
+ * Beacon message format for /.well-known/yakmesh/beacon
64
+ * This is what nodes advertise to help others discover peers.
65
+ */
66
+ class BeaconMessage {
67
+ constructor(options = {}) {
68
+ this.version = SHERPA_CONFIG.protocolVersion;
69
+ this.nodeId = options.nodeId;
70
+ this.networkName = options.networkName;
71
+ this.timestamp = options.timestamp || Date.now();
72
+ this.ttl = options.ttl || 3600; // 1 hour default TTL
73
+
74
+ // Node capabilities
75
+ this.capabilities = {
76
+ wsPort: options.wsPort || null,
77
+ httpPort: options.httpPort || null,
78
+ supportsAnnex: options.supportsAnnex ?? true,
79
+ supportsNakpak: options.supportsNakpak ?? true,
80
+ supportsGossip: options.supportsGossip ?? true,
81
+ };
82
+
83
+ // Known peers (other nodes we know about)
84
+ this.peers = options.peers || [];
85
+
86
+ // Cryptographic proof
87
+ this.publicKey = options.publicKey || null;
88
+ this.signature = options.signature || null;
89
+ }
90
+
91
+ /**
92
+ * Add a peer to the beacon
93
+ */
94
+ addPeer(peerInfo) {
95
+ if (this.peers.length >= SHERPA_CONFIG.maxPeersPerBeacon) {
96
+ // Remove lowest-scored peer
97
+ this.peers.sort((a, b) => b.score - a.score);
98
+ this.peers.pop();
99
+ }
100
+
101
+ this.peers.push({
102
+ nodeId: peerInfo.nodeId,
103
+ endpoint: peerInfo.endpoint, // e.g., "https://example.com"
104
+ wsEndpoint: peerInfo.wsEndpoint, // e.g., "wss://example.com:9001"
105
+ lastSeen: peerInfo.lastSeen || Date.now(),
106
+ score: peerInfo.score || 1.0,
107
+ networkName: peerInfo.networkName,
108
+ });
109
+ }
110
+
111
+ /**
112
+ * Get peers for discovery response (limited subset)
113
+ */
114
+ getPeersForDiscovery() {
115
+ // Sort by score, return top N
116
+ return [...this.peers]
117
+ .sort((a, b) => b.score - a.score)
118
+ .slice(0, SHERPA_CONFIG.maxPeersToReturn)
119
+ .map(p => ({
120
+ nodeId: p.nodeId,
121
+ endpoint: p.endpoint,
122
+ wsEndpoint: p.wsEndpoint,
123
+ lastSeen: p.lastSeen,
124
+ networkName: p.networkName,
125
+ }));
126
+ }
127
+
128
+ /**
129
+ * Serialize beacon for HTTP response
130
+ */
131
+ serialize() {
132
+ return {
133
+ version: this.version,
134
+ nodeId: this.nodeId,
135
+ networkName: this.networkName,
136
+ timestamp: this.timestamp,
137
+ ttl: this.ttl,
138
+ capabilities: this.capabilities,
139
+ peers: this.getPeersForDiscovery(),
140
+ publicKey: this.publicKey,
141
+ signature: this.signature,
142
+ };
143
+ }
144
+
145
+ /**
146
+ * Create data to sign
147
+ */
148
+ getSignableData() {
149
+ return JSON.stringify({
150
+ version: this.version,
151
+ nodeId: this.nodeId,
152
+ networkName: this.networkName,
153
+ timestamp: this.timestamp,
154
+ capabilities: this.capabilities,
155
+ });
156
+ }
157
+
158
+ /**
159
+ * Deserialize beacon from HTTP response
160
+ */
161
+ static deserialize(data) {
162
+ const beacon = new BeaconMessage({
163
+ nodeId: data.nodeId,
164
+ networkName: data.networkName,
165
+ timestamp: data.timestamp,
166
+ ttl: data.ttl,
167
+ wsPort: data.capabilities?.wsPort,
168
+ httpPort: data.capabilities?.httpPort,
169
+ supportsAnnex: data.capabilities?.supportsAnnex,
170
+ supportsNakpak: data.capabilities?.supportsNakpak,
171
+ supportsGossip: data.capabilities?.supportsGossip,
172
+ publicKey: data.publicKey,
173
+ signature: data.signature,
174
+ });
175
+ beacon.version = data.version;
176
+ beacon.peers = data.peers || [];
177
+ return beacon;
178
+ }
179
+ }
180
+
181
+ // ============================================================
182
+ // PEER REGISTRY
183
+ // ============================================================
184
+
185
+ /**
186
+ * Maintains a registry of known peers with scoring
187
+ */
188
+ class PeerRegistry {
189
+ constructor(options = {}) {
190
+ this.peers = new Map(); // nodeId -> PeerInfo
191
+ this.networkFilter = options.networkFilter || null; // Only accept peers from this network
192
+ this.maxPeers = options.maxPeers || 1000;
193
+ }
194
+
195
+ /**
196
+ * Add or update a peer
197
+ */
198
+ upsert(peerInfo) {
199
+ const existing = this.peers.get(peerInfo.nodeId);
200
+
201
+ // Filter by network if configured
202
+ if (this.networkFilter && peerInfo.networkName !== this.networkFilter) {
203
+ return false;
204
+ }
205
+
206
+ if (existing) {
207
+ // Update existing peer
208
+ existing.endpoint = peerInfo.endpoint || existing.endpoint;
209
+ existing.wsEndpoint = peerInfo.wsEndpoint || existing.wsEndpoint;
210
+ existing.lastSeen = Math.max(existing.lastSeen, peerInfo.lastSeen || Date.now());
211
+ existing.score = Math.min(1.0, existing.score + SHERPA_CONFIG.successBonus);
212
+ existing.capabilities = peerInfo.capabilities || existing.capabilities;
213
+ } else {
214
+ // Add new peer (evict lowest scored if at capacity)
215
+ if (this.peers.size >= this.maxPeers) {
216
+ this._evictLowest();
217
+ }
218
+
219
+ this.peers.set(peerInfo.nodeId, {
220
+ nodeId: peerInfo.nodeId,
221
+ endpoint: peerInfo.endpoint,
222
+ wsEndpoint: peerInfo.wsEndpoint,
223
+ lastSeen: peerInfo.lastSeen || Date.now(),
224
+ score: peerInfo.score || 1.0,
225
+ networkName: peerInfo.networkName,
226
+ capabilities: peerInfo.capabilities || {},
227
+ discoveredAt: Date.now(),
228
+ failureCount: 0,
229
+ });
230
+ }
231
+
232
+ return true;
233
+ }
234
+
235
+ /**
236
+ * Mark a peer as failed (decrease score)
237
+ */
238
+ markFailed(nodeId) {
239
+ const peer = this.peers.get(nodeId);
240
+ if (peer) {
241
+ peer.score = Math.max(0, peer.score - SHERPA_CONFIG.failurePenalty);
242
+ peer.failureCount++;
243
+
244
+ // Remove if score too low
245
+ if (peer.score < SHERPA_CONFIG.minPeerScore) {
246
+ this.peers.delete(nodeId);
247
+ return false;
248
+ }
249
+ }
250
+ return true;
251
+ }
252
+
253
+ /**
254
+ * Get best peers for connection
255
+ */
256
+ getBestPeers(count = 10) {
257
+ return [...this.peers.values()]
258
+ .filter(p => p.score >= SHERPA_CONFIG.minPeerScore)
259
+ .sort((a, b) => b.score - a.score)
260
+ .slice(0, count);
261
+ }
262
+
263
+ /**
264
+ * Get peers for beacon advertisement
265
+ */
266
+ getForBeacon() {
267
+ return [...this.peers.values()]
268
+ .filter(p => p.score >= SHERPA_CONFIG.minPeerScore && p.endpoint)
269
+ .sort((a, b) => b.score - a.score)
270
+ .slice(0, SHERPA_CONFIG.maxPeersPerBeacon);
271
+ }
272
+
273
+ /**
274
+ * Apply score decay to all peers
275
+ */
276
+ decayScores() {
277
+ for (const peer of this.peers.values()) {
278
+ peer.score *= SHERPA_CONFIG.scoreDecay;
279
+
280
+ if (peer.score < SHERPA_CONFIG.minPeerScore) {
281
+ this.peers.delete(peer.nodeId);
282
+ }
283
+ }
284
+ }
285
+
286
+ /**
287
+ * Evict lowest-scored peer
288
+ */
289
+ _evictLowest() {
290
+ let lowest = null;
291
+ let lowestScore = Infinity;
292
+
293
+ for (const [nodeId, peer] of this.peers) {
294
+ if (peer.score < lowestScore) {
295
+ lowestScore = peer.score;
296
+ lowest = nodeId;
297
+ }
298
+ }
299
+
300
+ if (lowest) {
301
+ this.peers.delete(lowest);
302
+ }
303
+ }
304
+
305
+ size() {
306
+ return this.peers.size;
307
+ }
308
+
309
+ has(nodeId) {
310
+ return this.peers.has(nodeId);
311
+ }
312
+
313
+ get(nodeId) {
314
+ return this.peers.get(nodeId);
315
+ }
316
+ }
317
+
318
+ // ============================================================
319
+ // SHERPA DISCOVERY ENGINE
320
+ // ============================================================
321
+
322
+ /**
323
+ * Main SHERPA discovery engine
324
+ * Crawls beacon endpoints to discover peers in a decentralized manner
325
+ */
326
+ class SherpaDiscovery extends EventEmitter {
327
+ constructor(options = {}) {
328
+ super();
329
+
330
+ this.nodeId = options.nodeId;
331
+ this.networkName = options.networkName;
332
+ this.publicKey = options.publicKey;
333
+ this.signFn = options.signFn; // Function to sign beacon data
334
+ this.verifyFn = options.verifyFn; // Function to verify signatures
335
+
336
+ // Our own endpoint info
337
+ this.selfEndpoint = options.selfEndpoint || null; // e.g., "https://mynode.com"
338
+ this.wsEndpoint = options.wsEndpoint || null;
339
+ this.capabilities = options.capabilities || {};
340
+
341
+ // Peer registry
342
+ this.registry = new PeerRegistry({
343
+ networkFilter: options.networkFilter || this.networkName,
344
+ maxPeers: options.maxPeers || 1000,
345
+ });
346
+
347
+ // Seed endpoints (initial known beacons to crawl)
348
+ this.seedEndpoints = new Set(options.seedEndpoints || []);
349
+
350
+ // Crawl state
351
+ this.crawlInProgress = false;
352
+ this.lastCrawl = 0;
353
+ this.crawlTimer = null;
354
+
355
+ // Stats
356
+ this.stats = {
357
+ crawlsCompleted: 0,
358
+ beaconsFetched: 0,
359
+ beaconsFailed: 0,
360
+ peersDiscovered: 0,
361
+ peersEvicted: 0,
362
+ };
363
+ }
364
+
365
+ /**
366
+ * Start periodic discovery
367
+ */
368
+ start() {
369
+ if (this.crawlTimer) return;
370
+
371
+ // Initial crawl
372
+ this.crawl();
373
+
374
+ // Periodic crawl
375
+ this.crawlTimer = setInterval(() => {
376
+ this.crawl();
377
+ this.registry.decayScores();
378
+ }, SHERPA_CONFIG.crawlInterval);
379
+
380
+ this.emit('started');
381
+ }
382
+
383
+ /**
384
+ * Stop discovery
385
+ */
386
+ stop() {
387
+ if (this.crawlTimer) {
388
+ clearInterval(this.crawlTimer);
389
+ this.crawlTimer = null;
390
+ }
391
+ this.emit('stopped');
392
+ }
393
+
394
+ /**
395
+ * Add a seed endpoint for initial discovery
396
+ */
397
+ addSeed(endpoint) {
398
+ this.seedEndpoints.add(endpoint);
399
+ }
400
+
401
+ /**
402
+ * Generate our beacon response
403
+ */
404
+ generateBeacon() {
405
+ const beacon = new BeaconMessage({
406
+ nodeId: this.nodeId,
407
+ networkName: this.networkName,
408
+ wsPort: this.capabilities.wsPort,
409
+ httpPort: this.capabilities.httpPort,
410
+ supportsAnnex: this.capabilities.supportsAnnex ?? true,
411
+ supportsNakpak: this.capabilities.supportsNakpak ?? true,
412
+ supportsGossip: this.capabilities.supportsGossip ?? true,
413
+ publicKey: this.publicKey,
414
+ });
415
+
416
+ // Add known peers
417
+ for (const peer of this.registry.getForBeacon()) {
418
+ beacon.addPeer(peer);
419
+ }
420
+
421
+ // Sign beacon
422
+ if (this.signFn && this.publicKey) {
423
+ beacon.signature = this.signFn(beacon.getSignableData());
424
+ }
425
+
426
+ return beacon.serialize();
427
+ }
428
+
429
+ /**
430
+ * Crawl known endpoints to discover peers
431
+ */
432
+ async crawl() {
433
+ if (this.crawlInProgress) return;
434
+ this.crawlInProgress = true;
435
+
436
+ try {
437
+ const visited = new Set();
438
+ const toVisit = new Set([
439
+ ...this.seedEndpoints,
440
+ ...this.registry.getForBeacon().map(p => p.endpoint).filter(Boolean),
441
+ ]);
442
+
443
+ let depth = 0;
444
+
445
+ while (toVisit.size > 0 && depth < SHERPA_CONFIG.maxCrawlDepth) {
446
+ const batch = [...toVisit].slice(0, SHERPA_CONFIG.maxCrawlsPerMinute);
447
+ toVisit.clear();
448
+
449
+ const results = await Promise.allSettled(
450
+ batch
451
+ .filter(endpoint => endpoint && !visited.has(endpoint))
452
+ .map(endpoint => this._fetchBeacon(endpoint))
453
+ );
454
+
455
+ for (let i = 0; i < results.length; i++) {
456
+ const endpoint = batch[i];
457
+ visited.add(endpoint);
458
+
459
+ if (results[i].status === 'fulfilled') {
460
+ const beacon = results[i].value;
461
+
462
+ // Add the beacon source as a peer
463
+ if (beacon.nodeId && beacon.nodeId !== this.nodeId) {
464
+ this.registry.upsert({
465
+ nodeId: beacon.nodeId,
466
+ endpoint: endpoint,
467
+ wsEndpoint: beacon.capabilities?.wsPort
468
+ ? `wss://${new URL(endpoint).hostname}:${beacon.capabilities.wsPort}`
469
+ : null,
470
+ networkName: beacon.networkName,
471
+ capabilities: beacon.capabilities,
472
+ });
473
+ this.stats.peersDiscovered++;
474
+ }
475
+
476
+ // Queue peers for next depth
477
+ for (const peer of beacon.peers || []) {
478
+ if (peer.endpoint && !visited.has(peer.endpoint)) {
479
+ toVisit.add(peer.endpoint);
480
+ }
481
+
482
+ // Also add these peers to our registry
483
+ if (peer.nodeId && peer.nodeId !== this.nodeId) {
484
+ this.registry.upsert({
485
+ nodeId: peer.nodeId,
486
+ endpoint: peer.endpoint,
487
+ wsEndpoint: peer.wsEndpoint,
488
+ networkName: peer.networkName,
489
+ lastSeen: peer.lastSeen,
490
+ });
491
+ }
492
+ }
493
+
494
+ this.stats.beaconsFetched++;
495
+ } else {
496
+ this.stats.beaconsFailed++;
497
+ // Mark the peer as failed if we have them
498
+ const peer = [...this.registry.peers.values()]
499
+ .find(p => p.endpoint === endpoint);
500
+ if (peer) {
501
+ this.registry.markFailed(peer.nodeId);
502
+ }
503
+ }
504
+ }
505
+
506
+ depth++;
507
+ }
508
+
509
+ this.stats.crawlsCompleted++;
510
+ this.lastCrawl = Date.now();
511
+ this.emit('crawl-complete', {
512
+ peersFound: this.registry.size(),
513
+ depth,
514
+ });
515
+
516
+ } finally {
517
+ this.crawlInProgress = false;
518
+ }
519
+ }
520
+
521
+ /**
522
+ * Fetch a beacon from an endpoint
523
+ */
524
+ async _fetchBeacon(endpoint) {
525
+ const url = new URL(SHERPA_CONFIG.beaconPath, endpoint).toString();
526
+
527
+ const controller = new AbortController();
528
+ const timeout = setTimeout(() => controller.abort(), SHERPA_CONFIG.crawlTimeout);
529
+
530
+ try {
531
+ const response = await fetch(url, {
532
+ headers: {
533
+ 'Accept': 'application/json',
534
+ 'User-Agent': `SHERPA/${SHERPA_CONFIG.protocolVersion}`,
535
+ },
536
+ signal: controller.signal,
537
+ });
538
+
539
+ if (!response.ok) {
540
+ throw new Error(`HTTP ${response.status}`);
541
+ }
542
+
543
+ const text = await response.text();
544
+ if (text.length > SHERPA_CONFIG.maxBeaconSize) {
545
+ throw new Error('Beacon too large');
546
+ }
547
+
548
+ const data = JSON.parse(text);
549
+ const beacon = BeaconMessage.deserialize(data);
550
+
551
+ // Verify signature if required
552
+ if (SHERPA_CONFIG.signatureRequired && this.verifyFn) {
553
+ if (!beacon.publicKey || !beacon.signature) {
554
+ throw new Error('Missing signature');
555
+ }
556
+
557
+ const valid = this.verifyFn(
558
+ beacon.getSignableData(),
559
+ beacon.signature,
560
+ beacon.publicKey
561
+ );
562
+
563
+ if (!valid) {
564
+ throw new Error('Invalid signature');
565
+ }
566
+ }
567
+
568
+ // Check timestamp freshness
569
+ const age = Date.now() - beacon.timestamp;
570
+ if (age > beacon.ttl * 1000) {
571
+ throw new Error('Beacon expired');
572
+ }
573
+
574
+ return beacon;
575
+
576
+ } finally {
577
+ clearTimeout(timeout);
578
+ }
579
+ }
580
+
581
+ /**
582
+ * Get connection candidates for mesh networking
583
+ */
584
+ getConnectionCandidates(count = 5) {
585
+ return this.registry.getBestPeers(count)
586
+ .filter(p => p.wsEndpoint)
587
+ .map(p => ({
588
+ nodeId: p.nodeId,
589
+ wsEndpoint: p.wsEndpoint,
590
+ score: p.score,
591
+ }));
592
+ }
593
+
594
+ /**
595
+ * Notify that we successfully connected to a peer
596
+ */
597
+ markConnected(nodeId) {
598
+ const peer = this.registry.get(nodeId);
599
+ if (peer) {
600
+ peer.score = Math.min(1.0, peer.score + SHERPA_CONFIG.successBonus);
601
+ peer.lastSeen = Date.now();
602
+ peer.failureCount = 0;
603
+ }
604
+ }
605
+
606
+ /**
607
+ * Notify that connection to a peer failed
608
+ */
609
+ markDisconnected(nodeId) {
610
+ this.registry.markFailed(nodeId);
611
+ }
612
+
613
+ getStats() {
614
+ return {
615
+ ...this.stats,
616
+ registrySize: this.registry.size(),
617
+ seedCount: this.seedEndpoints.size,
618
+ lastCrawl: this.lastCrawl,
619
+ crawlInProgress: this.crawlInProgress,
620
+ };
621
+ }
622
+ }
623
+
624
+ // ============================================================
625
+ // EXPRESS MIDDLEWARE
626
+ // ============================================================
627
+
628
+ /**
629
+ * Express middleware to serve the beacon endpoint
630
+ */
631
+ function createBeaconMiddleware(sherpa) {
632
+ return (req, res) => {
633
+ try {
634
+ const beacon = sherpa.generateBeacon();
635
+ res.setHeader('Content-Type', 'application/json');
636
+ res.setHeader('Cache-Control', 'public, max-age=60');
637
+ res.setHeader('X-Sherpa-Version', SHERPA_CONFIG.protocolVersion);
638
+ res.json(beacon);
639
+ } catch (err) {
640
+ res.status(500).json({ error: 'Failed to generate beacon' });
641
+ }
642
+ };
643
+ }
644
+
645
+ // ============================================================
646
+ // EXPORTS
647
+ // ============================================================
648
+
649
+ export {
650
+ SHERPA_CONFIG,
651
+ BeaconMessage,
652
+ PeerRegistry,
653
+ SherpaDiscovery,
654
+ createBeaconMiddleware,
655
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yakmesh",
3
- "version": "1.7.1",
3
+ "version": "1.8.0",
4
4
  "description": "YAKMESH: Yielding Atomic Kernel Modular Encryption Secured Hub - Post-quantum secure P2P mesh network for the 2026 threat landscape",
5
5
  "type": "module",
6
6
  "main": "server/index.js",
@@ -26,7 +26,8 @@
26
26
  "./mesh/echo-ranging": "./mesh/echo-ranging.js",
27
27
  "./mesh/pulse-sync": "./mesh/pulse-sync.js",
28
28
  "./mesh/nakpak-routing": "./mesh/nakpak-routing.js",
29
- "./mesh/beacon-broadcast": "./mesh/beacon-broadcast.js"
29
+ "./mesh/beacon-broadcast": "./mesh/beacon-broadcast.js",
30
+ "./mesh/sherpa-discovery": "./mesh/sherpa-discovery.js"
30
31
  },
31
32
  "bin": {
32
33
  "yakmesh": "cli/index.js"
@@ -67,6 +68,7 @@
67
68
  "onion-routing",
68
69
  "echo-ranging",
69
70
  "nakpak-routing",
71
+ "sherpa-discovery",
70
72
  "pulse-sync",
71
73
  "beacon-broadcast",
72
74
  "kyber",
package/server/index.js CHANGED
@@ -25,6 +25,9 @@ import { ContentStore, createContentAPI } from '../content/index.js';
25
25
  // Annex - Autonomous Network Negotiated Encrypted eXchange
26
26
  import { Annex } from '../mesh/annex.js';
27
27
 
28
+ // SHERPA - Secure Hidden Endpoint Resolution Path Architecture
29
+ import { SherpaDiscovery, createBeaconMiddleware } from '../mesh/sherpa-discovery.js';
30
+
28
31
  // Oracle system imports
29
32
  import {
30
33
  getOracle,
@@ -271,6 +274,31 @@ export class YakmeshNode {
271
274
  });
272
275
  console.log('βœ“ Annex channel initialized (encrypted P2P messaging)');
273
276
 
277
+ // 5c. Initialize SHERPA for decentralized peer discovery
278
+ this.sherpa = new SherpaDiscovery({
279
+ nodeId: this.identity.identity.nodeId,
280
+ networkName: this.genesisNetwork?.networkName,
281
+ publicKey: this.identity.identity.publicKey,
282
+ signFn: (data) => this.identity.sign(data),
283
+ verifyFn: (data, sig, pubKey) => this.identity.verify(data, sig, pubKey),
284
+ selfEndpoint: this.config.sherpa?.selfEndpoint || null,
285
+ wsEndpoint: this.config.sherpa?.wsEndpoint || null,
286
+ capabilities: {
287
+ wsPort: this.config.network.wsPort,
288
+ httpPort: this.config.network.httpPort,
289
+ supportsAnnex: true,
290
+ supportsNakpak: true,
291
+ supportsGossip: true,
292
+ },
293
+ seedEndpoints: this.config.sherpa?.seeds || [],
294
+ });
295
+
296
+ // Start SHERPA if seeds are configured or selfEndpoint is set
297
+ if (this.config.sherpa?.enabled !== false) {
298
+ this.sherpa.start();
299
+ console.log('βœ“ SHERPA discovery initialized (decentralized peer discovery)');
300
+ }
301
+
274
302
  // 6. Start HTTP server
275
303
  await this._startHttpServer();
276
304
 
@@ -298,6 +326,9 @@ export class YakmeshNode {
298
326
  if (this.annex) {
299
327
  console.log(` Annex: βœ“ Encrypted P2P ready`);
300
328
  }
329
+ if (this.sherpa) {
330
+ console.log(` SHERPA: βœ“ Beacon at /.well-known/yakmesh/beacon`);
331
+ }
301
332
  if (this.adapter) {
302
333
  console.log(` Adapter: βœ“ Enabled`);
303
334
  }
@@ -606,6 +637,32 @@ export class YakmeshNode {
606
637
  res.json(this.mesh.getPeers());
607
638
  });
608
639
 
640
+ // =========================================
641
+ // SHERPA: Decentralized Peer Discovery
642
+ // =========================================
643
+
644
+ // Beacon endpoint for SHERPA peer discovery
645
+ // This allows other nodes to discover us and our known peers
646
+ if (this.sherpa) {
647
+ app.get('/.well-known/yakmesh/beacon', createBeaconMiddleware(this.sherpa));
648
+ }
649
+
650
+ // SHERPA discovery stats
651
+ app.get('/sherpa/status', (req, res) => {
652
+ if (!this.sherpa) {
653
+ return res.status(503).json({ error: 'SHERPA not initialized' });
654
+ }
655
+ res.json(this.sherpa.getStats());
656
+ });
657
+
658
+ // Get connection candidates from SHERPA
659
+ app.get('/sherpa/candidates', (req, res) => {
660
+ if (!this.sherpa) {
661
+ return res.status(503).json({ error: 'SHERPA not initialized' });
662
+ }
663
+ res.json(this.sherpa.getConnectionCandidates(10));
664
+ });
665
+
609
666
  // Replication stats
610
667
  app.get('/replication', (req, res) => {
611
668
  res.json(this.replication.getStats());