yakmesh 2.8.2 → 3.0.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.
Files changed (232) hide show
  1. package/CHANGELOG.md +637 -0
  2. package/CONTRIBUTING.md +42 -0
  3. package/Caddyfile +77 -0
  4. package/README.md +119 -29
  5. package/adapters/adapter-mlv-bible/README.md +124 -0
  6. package/adapters/adapter-mlv-bible/index.js +400 -0
  7. package/adapters/chat-mod-adapter.js +532 -0
  8. package/adapters/content-adapter.js +273 -0
  9. package/content/api.js +50 -41
  10. package/content/index.js +2 -2
  11. package/content/store.js +355 -173
  12. package/dashboard/index.html +19 -3
  13. package/database/replication.js +117 -37
  14. package/docs/CRYPTO-AGILITY.md +204 -0
  15. package/docs/MTLS-RESEARCH.md +367 -0
  16. package/docs/NAMCHE-SPEC.md +681 -0
  17. package/docs/PEERQUANTA-YAKMESH-INTEGRATION.md +407 -0
  18. package/docs/PRECISION-DISCLOSURE.md +96 -0
  19. package/docs/README.md +76 -0
  20. package/docs/ROADMAP-2.4.0.md +447 -0
  21. package/docs/ROADMAP-2.5.0.md +244 -0
  22. package/docs/SECURITY-AUDIT-REPORT.md +306 -0
  23. package/docs/SST-INTEGRATION.md +712 -0
  24. package/docs/STEADYWATCH-IMPLEMENTATION.md +303 -0
  25. package/docs/TERNARY-AUDIT-REPORT.md +247 -0
  26. package/docs/TME-FAQ.md +221 -0
  27. package/docs/WHITEPAPER.md +623 -0
  28. package/docs/adapters.html +1001 -0
  29. package/docs/advanced-systems.html +1045 -0
  30. package/docs/annex.html +1046 -0
  31. package/docs/api.html +970 -0
  32. package/docs/business/response-templates.md +160 -0
  33. package/docs/c2c.html +1225 -0
  34. package/docs/cli.html +1332 -0
  35. package/docs/configuration.html +1248 -0
  36. package/docs/darshan.html +1085 -0
  37. package/docs/dharma.html +966 -0
  38. package/docs/docs-bundle.html +1075 -0
  39. package/docs/docs.css +3120 -0
  40. package/docs/docs.js +556 -0
  41. package/docs/doko.html +969 -0
  42. package/docs/geo-proof.html +858 -0
  43. package/docs/getting-started.html +840 -0
  44. package/docs/gumba-tutorial.html +1144 -0
  45. package/docs/gumba.html +1098 -0
  46. package/docs/index.html +914 -0
  47. package/docs/jhilke.html +1312 -0
  48. package/docs/karma.html +1100 -0
  49. package/docs/katha.html +1037 -0
  50. package/docs/lama.html +978 -0
  51. package/docs/mandala.html +1067 -0
  52. package/docs/mani.html +964 -0
  53. package/docs/mantra.html +967 -0
  54. package/docs/mesh.html +1409 -0
  55. package/docs/nakpak.html +869 -0
  56. package/docs/namche.html +928 -0
  57. package/docs/nav-order.json +53 -0
  58. package/docs/prahari.html +1043 -0
  59. package/docs/prism-bash.min.js +1 -0
  60. package/docs/prism-javascript.min.js +1 -0
  61. package/docs/prism-json.min.js +1 -0
  62. package/docs/prism-tomorrow.min.css +1 -0
  63. package/docs/prism.min.js +1 -0
  64. package/docs/privacy.html +699 -0
  65. package/docs/quick-reference.html +1181 -0
  66. package/docs/sakshi.html +1402 -0
  67. package/docs/sandboxing.md +386 -0
  68. package/docs/seva.html +911 -0
  69. package/docs/sherpa.html +871 -0
  70. package/docs/studio.html +860 -0
  71. package/docs/stupa.html +995 -0
  72. package/docs/tailwind.min.css +2 -0
  73. package/docs/tattva.html +1332 -0
  74. package/docs/terms.html +686 -0
  75. package/docs/time-server-deployment.md +166 -0
  76. package/docs/time-sources.html +1392 -0
  77. package/docs/tivra.html +1127 -0
  78. package/docs/trademark-policy.html +686 -0
  79. package/docs/tribhuj.html +1183 -0
  80. package/docs/trust-security.html +1029 -0
  81. package/docs/tutorials/backup-recovery.html +654 -0
  82. package/docs/tutorials/dashboard.html +604 -0
  83. package/docs/tutorials/domain-setup.html +605 -0
  84. package/docs/tutorials/host-website.html +456 -0
  85. package/docs/tutorials/mesh-network.html +505 -0
  86. package/docs/tutorials/mobile-access.html +445 -0
  87. package/docs/tutorials/privacy.html +467 -0
  88. package/docs/tutorials/raspberry-pi.html +600 -0
  89. package/docs/tutorials/security-basics.html +539 -0
  90. package/docs/tutorials/share-files.html +431 -0
  91. package/docs/tutorials/troubleshooting.html +637 -0
  92. package/docs/tutorials/trust-karma.html +419 -0
  93. package/docs/tutorials/yak-protocol.html +456 -0
  94. package/docs/tutorials.html +1034 -0
  95. package/docs/vani.html +1270 -0
  96. package/docs/webserver.html +809 -0
  97. package/docs/yak-protocol.html +940 -0
  98. package/docs/yak-timeserver-design.md +475 -0
  99. package/docs/yakapp.html +1015 -0
  100. package/docs/ypc27.html +1069 -0
  101. package/docs/yurt.html +1344 -0
  102. package/embedded-docs/bundle.js +334 -74
  103. package/gossip/protocol.js +247 -27
  104. package/identity/key-resolver.js +262 -0
  105. package/identity/machine-seed.js +632 -0
  106. package/identity/node-key.js +669 -368
  107. package/identity/tribhuj-ratchet.js +506 -0
  108. package/knowledge-base.js +37 -8
  109. package/launcher/yakmesh.bat +62 -0
  110. package/launcher/yakmesh.sh +70 -0
  111. package/mesh/annex.js +462 -108
  112. package/mesh/beacon-broadcast.js +113 -1
  113. package/mesh/darshan.js +1718 -0
  114. package/mesh/gumba.js +1567 -0
  115. package/mesh/jhilke.js +651 -0
  116. package/mesh/katha.js +1012 -0
  117. package/mesh/nakpak-routing.js +8 -5
  118. package/mesh/network.js +724 -34
  119. package/mesh/pulse-sync.js +4 -1
  120. package/mesh/rate-limiter.js +127 -15
  121. package/mesh/seva.js +526 -0
  122. package/mesh/sherpa-discovery.js +89 -8
  123. package/mesh/sybil-defense.js +19 -5
  124. package/mesh/temporal-encoder.js +4 -3
  125. package/mesh/vani.js +1364 -0
  126. package/mesh/yurt.js +1340 -0
  127. package/models/entropy-sentinel.onnx +0 -0
  128. package/models/karma-trust.onnx +0 -0
  129. package/models/manifest.json +43 -0
  130. package/models/sakshi-anomaly.onnx +0 -0
  131. package/oracle/code-proof-protocol.js +7 -6
  132. package/oracle/codebase-lock.js +257 -28
  133. package/oracle/index.js +74 -15
  134. package/oracle/ma902-snmp.js +678 -0
  135. package/oracle/module-sealer.js +5 -3
  136. package/oracle/network-identity.js +16 -0
  137. package/oracle/packet-checksum.js +201 -0
  138. package/oracle/sst.js +579 -0
  139. package/oracle/ternary-144t.js +714 -0
  140. package/oracle/ternary-ml.js +481 -0
  141. package/oracle/time-api.js +239 -0
  142. package/oracle/time-source.js +137 -47
  143. package/oracle/validation-oracle-hardened.js +1111 -1071
  144. package/oracle/validation-oracle.js +4 -2
  145. package/oracle/ypc27.js +211 -0
  146. package/package.json +20 -3
  147. package/protocol/yak-handler.js +35 -9
  148. package/protocol/yak-protocol.js +28 -13
  149. package/reference/cpp/yakmesh_mceliece_shard.cpp +168 -0
  150. package/reference/cpp/yakmesh_ypc27.cpp +179 -0
  151. package/sbom.json +87 -0
  152. package/scripts/security-audit.mjs +264 -0
  153. package/scripts/update-docs-nav.js +194 -0
  154. package/scripts/update-docs-sidebar.cjs +164 -0
  155. package/security/crypto-config.js +4 -3
  156. package/security/dharma-moderation.js +517 -0
  157. package/security/doko-identity.js +193 -143
  158. package/security/domain-consensus.js +86 -85
  159. package/security/fs-hardening.js +620 -0
  160. package/security/hardware-attestation.js +5 -3
  161. package/security/hybrid-trust.js +227 -87
  162. package/security/karma-rate-limiter.js +692 -0
  163. package/security/khata-protocol.js +22 -21
  164. package/security/khata-trust-integration.js +277 -150
  165. package/security/memory-safety.js +635 -0
  166. package/security/mesh-auth.js +11 -10
  167. package/security/mesh-revocation.js +373 -5
  168. package/security/namche-gateway.js +298 -69
  169. package/security/sakshi.js +460 -3
  170. package/security/sangha.js +770 -0
  171. package/security/secure-config.js +473 -0
  172. package/security/silicon-parity.js +13 -10
  173. package/security/steadywatch.js +1142 -0
  174. package/security/strike-system.js +32 -3
  175. package/security/temporal-signing.js +488 -0
  176. package/security/trit-commitment.js +464 -0
  177. package/server/crypto/annex.js +247 -0
  178. package/server/darshan-api.js +343 -0
  179. package/server/index.js +3259 -362
  180. package/server/komm-api.js +668 -0
  181. package/utils/accel.js +2273 -0
  182. package/utils/ternary-id.js +79 -0
  183. package/utils/verify-worker.js +57 -0
  184. package/webserver/index.js +95 -5
  185. package/assets/yakmesh-logo.png +0 -0
  186. package/assets/yakmesh-logo.svg +0 -80
  187. package/assets/yakmesh-logo2.png +0 -0
  188. package/assets/yakmesh-logo2sm.png +0 -0
  189. package/assets/ymsm.png +0 -0
  190. package/website/assets/silhouettes/adapters.svg +0 -107
  191. package/website/assets/silhouettes/api-endpoints.svg +0 -115
  192. package/website/assets/silhouettes/atomic-clock.svg +0 -83
  193. package/website/assets/silhouettes/base-camp.svg +0 -81
  194. package/website/assets/silhouettes/bridge.svg +0 -69
  195. package/website/assets/silhouettes/docs-bundle.svg +0 -113
  196. package/website/assets/silhouettes/doko-basket.svg +0 -70
  197. package/website/assets/silhouettes/fortress.svg +0 -93
  198. package/website/assets/silhouettes/gateway.svg +0 -54
  199. package/website/assets/silhouettes/gears.svg +0 -93
  200. package/website/assets/silhouettes/globe-satellite.svg +0 -67
  201. package/website/assets/silhouettes/karma-wheel.svg +0 -137
  202. package/website/assets/silhouettes/lama-council.svg +0 -141
  203. package/website/assets/silhouettes/mandala-network.svg +0 -169
  204. package/website/assets/silhouettes/mani-stones.svg +0 -149
  205. package/website/assets/silhouettes/mantra-wheel.svg +0 -116
  206. package/website/assets/silhouettes/mesh-nodes.svg +0 -113
  207. package/website/assets/silhouettes/nakpak.svg +0 -56
  208. package/website/assets/silhouettes/peak-lightning.svg +0 -73
  209. package/website/assets/silhouettes/sherpa.svg +0 -69
  210. package/website/assets/silhouettes/stupa-tower.svg +0 -119
  211. package/website/assets/silhouettes/tattva-eye.svg +0 -78
  212. package/website/assets/silhouettes/terminal.svg +0 -74
  213. package/website/assets/silhouettes/webserver.svg +0 -145
  214. package/website/assets/silhouettes/yak.svg +0 -78
  215. package/website/assets/yakmesh-logo.png +0 -0
  216. package/website/assets/yakmesh-logo.webp +0 -0
  217. package/website/assets/yakmesh-logo128x140.webp +0 -0
  218. package/website/assets/yakmesh-logo2.png +0 -0
  219. package/website/assets/yakmesh-logo2.svg +0 -51
  220. package/website/assets/yakmesh-logo40x44.webp +0 -0
  221. package/website/assets/yakmesh.gif +0 -0
  222. package/website/assets/yakmesh.ico +0 -0
  223. package/website/assets/yakmesh.jpg +0 -0
  224. package/website/assets/yakmesh.pdf +0 -0
  225. package/website/assets/yakmesh.png +0 -0
  226. package/website/assets/yakmesh.svg +0 -70
  227. package/website/assets/yakmesh128.webp +0 -0
  228. package/website/assets/yakmesh32.png +0 -0
  229. package/website/assets/yakmesh32.svg +0 -65
  230. package/website/assets/yakmesh32o.ico +0 -2
  231. package/website/assets/yakmesh32o.svg +0 -65
  232. package/website/assets/yakmesh32o.svgz +0 -0
package/mesh/seva.js ADDED
@@ -0,0 +1,526 @@
1
+ /**
2
+ * SEVA Mesh Handler — NPU Compute Sharing for Yakmesh Mesh
3
+ *
4
+ * सेवा (seva) = selfless service
5
+ *
6
+ * Enables nodes with NPU hardware to serve compute requests from
7
+ * nodes without it. Work is math-only — no executable code, no files.
8
+ * All transport via ANNEX-encrypted mesh channels.
9
+ *
10
+ * PROTOCOL FLOW:
11
+ * 1. Node with NPU opts in: config.seva.enabled = true
12
+ * 2. Node broadcasts capability ad via GOSSIP (seva:capability)
13
+ * 3. Requesting node sends seva:request to a capable peer
14
+ * 4. Receiving node validates (math-only), executes, returns result
15
+ * 5. Optional: third-party verification via re-execution
16
+ *
17
+ * MESSAGE TYPES:
18
+ * - seva:capability — broadcast: what this node can compute
19
+ * - seva:request — directed: work request (math-only params)
20
+ * - seva:response — directed: work result (numbers only)
21
+ * - seva:verify — directed: request verification of a result
22
+ * - seva:verify:ack — directed: verification result (match/mismatch)
23
+ *
24
+ * SECURITY:
25
+ * - All params validated to be numbers-only (no strings, no code)
26
+ * - Pre-defined model slots only (no custom computation)
27
+ * - Rate-limited per requesting peer
28
+ * - Reputation-gated: low-karma peers get lower priority
29
+ * - Results are deterministic → verifiable by any third peer
30
+ *
31
+ * @module mesh/seva
32
+ * @license MIT
33
+ * @copyright 2026 YAKMESH™ Contributors
34
+ */
35
+
36
+ import { createLogger } from '../utils/logger.js';
37
+ import EventEmitter from 'events';
38
+
39
+ const log = createLogger('mesh:seva');
40
+
41
+ // ═══════════════════════════════════════════════════════════════════════════════
42
+ // CONFIGURATION
43
+ // ═══════════════════════════════════════════════════════════════════════════════
44
+
45
+ export const SEVA_CONFIG = Object.freeze({
46
+ version: 1,
47
+
48
+ // Capacity management
49
+ defaultMaxConcurrent: 10,
50
+ maxRequestsPerPeerPerMinute: 30,
51
+ requestTimeout: 5000, // 5s timeout for work responses
52
+
53
+ // Verification
54
+ verifyProbability: 0.05, // 5% of results get spot-checked
55
+ verifyTimeout: 10000, // 10s for verification round-trip
56
+
57
+ // Capability broadcast
58
+ capabilityBroadcastInterval: 60000, // Advertise every 60s
59
+ capabilityTTL: 180000, // Expire after 3 minutes
60
+
61
+ // Model slots (must match c2c server SEVA_SLOTS)
62
+ validSlots: new Set([
63
+ 'planet-variation',
64
+ 'planet-heightmap',
65
+ 'planet-superres',
66
+ 'faction-brain',
67
+ 'combat-predict',
68
+ ]),
69
+
70
+ // Message types
71
+ messageTypes: {
72
+ CAPABILITY: 'seva:capability',
73
+ REQUEST: 'seva:request',
74
+ RESPONSE: 'seva:response',
75
+ VERIFY: 'seva:verify',
76
+ VERIFY_ACK: 'seva:verify:ack',
77
+ },
78
+ });
79
+
80
+ // ═══════════════════════════════════════════════════════════════════════════════
81
+ // SEVA MESH HANDLER
82
+ // ═══════════════════════════════════════════════════════════════════════════════
83
+
84
+ export class SevaMeshHandler extends EventEmitter {
85
+ /**
86
+ * @param {Object} opts
87
+ * @param {Object} opts.identity — node identity (nodeId, publicKey)
88
+ * @param {Object} opts.network — MandalaNetwork instance for sending messages
89
+ * @param {boolean} opts.enabled — whether this node offers NPU compute (opt-in)
90
+ * @param {Object} opts.hardware — { npu: bool, npuTops: number, gpu: bool, ... }
91
+ * @param {Function} opts.executor — async function(slot, params) => result
92
+ * @param {number} opts.maxConcurrent — max parallel jobs (default 10)
93
+ */
94
+ constructor(opts = {}) {
95
+ super();
96
+ this.identity = opts.identity;
97
+ this.network = opts.network;
98
+ this.enabled = opts.enabled ?? false;
99
+ this.hardware = opts.hardware || {};
100
+ this.executor = opts.executor || null;
101
+ this.maxConcurrent = opts.maxConcurrent || SEVA_CONFIG.defaultMaxConcurrent;
102
+
103
+ // State
104
+ this.activeJobs = 0;
105
+ this.totalServed = 0;
106
+ this.peerCapabilities = new Map(); // peerId → { slots, capacity, lastSeen }
107
+ this.peerRateLimits = new Map(); // peerId → { timestamps: number[] }
108
+ this.pendingRequests = new Map(); // requestId → { resolve, reject, timer }
109
+
110
+ // Broadcast timer
111
+ this._capBroadcastTimer = null;
112
+ }
113
+
114
+ // ─── LIFECYCLE ────────────────────────────────────────────────────────────
115
+
116
+ start() {
117
+ if (this.enabled) {
118
+ // Broadcast our capabilities immediately, then periodically
119
+ this._broadcastCapability();
120
+ this._capBroadcastTimer = setInterval(
121
+ () => this._broadcastCapability(),
122
+ SEVA_CONFIG.capabilityBroadcastInterval
123
+ );
124
+ log.info({
125
+ npu: !!this.hardware.npu,
126
+ npuTops: this.hardware.npuTops || 0,
127
+ maxConcurrent: this.maxConcurrent,
128
+ }, 'SEVA mesh handler started (serving NPU compute)');
129
+ } else {
130
+ log.info('SEVA mesh handler started (consumer only — no NPU sharing)');
131
+ }
132
+
133
+ // Always listen for capabilities and responses (even if not serving)
134
+ // The network adapter will call handleMessage() for seva: messages
135
+ }
136
+
137
+ stop() {
138
+ if (this._capBroadcastTimer) clearInterval(this._capBroadcastTimer);
139
+ this._capBroadcastTimer = null;
140
+
141
+ // Reject all pending requests
142
+ for (const [id, pending] of this.pendingRequests) {
143
+ clearTimeout(pending.timer);
144
+ pending.reject(new Error('SEVA handler stopped'));
145
+ }
146
+ this.pendingRequests.clear();
147
+
148
+ log.info('SEVA mesh handler stopped');
149
+ }
150
+
151
+ // ─── INCOMING MESSAGE HANDLER ─────────────────────────────────────────────
152
+
153
+ /**
154
+ * Handle an incoming SEVA mesh message.
155
+ * Called by MandalaNetwork when a seva: message arrives.
156
+ *
157
+ * @param {string} type — message type (seva:*)
158
+ * @param {Object} payload — message payload
159
+ * @param {string} peerId — sender peer ID
160
+ */
161
+ async handleMessage(type, payload, peerId) {
162
+ switch (type) {
163
+ case SEVA_CONFIG.messageTypes.CAPABILITY:
164
+ this._handleCapability(payload, peerId);
165
+ break;
166
+ case SEVA_CONFIG.messageTypes.REQUEST:
167
+ await this._handleRequest(payload, peerId);
168
+ break;
169
+ case SEVA_CONFIG.messageTypes.RESPONSE:
170
+ this._handleResponse(payload, peerId);
171
+ break;
172
+ case SEVA_CONFIG.messageTypes.VERIFY:
173
+ await this._handleVerify(payload, peerId);
174
+ break;
175
+ case SEVA_CONFIG.messageTypes.VERIFY_ACK:
176
+ this._handleVerifyAck(payload, peerId);
177
+ break;
178
+ default:
179
+ log.debug({ type }, 'Unknown SEVA message type');
180
+ }
181
+ }
182
+
183
+ // ─── CAPABILITY MANAGEMENT ────────────────────────────────────────────────
184
+
185
+ _broadcastCapability() {
186
+ if (!this.enabled || !this.network) return;
187
+
188
+ const ad = {
189
+ nodeId: this.identity?.nodeId,
190
+ version: SEVA_CONFIG.version,
191
+ slots: this._getActiveSlots(),
192
+ capacity: {
193
+ maxConcurrent: this.maxConcurrent,
194
+ currentLoad: this.activeJobs,
195
+ accelerated: !!this.hardware.npu,
196
+ npuTops: this.hardware.npuTops || 0,
197
+ },
198
+ ts: Date.now(),
199
+ };
200
+
201
+ this.network.broadcast({ type: SEVA_CONFIG.messageTypes.CAPABILITY, ...ad });
202
+ }
203
+
204
+ _getActiveSlots() {
205
+ const slots = [];
206
+ for (const slotId of SEVA_CONFIG.validSlots) {
207
+ slots.push({
208
+ id: slotId,
209
+ accelerated: !!this.hardware.npu,
210
+ });
211
+ }
212
+ return slots;
213
+ }
214
+
215
+ _handleCapability(payload, peerId) {
216
+ if (!payload || !payload.slots) return;
217
+
218
+ this.peerCapabilities.set(peerId, {
219
+ slots: new Set(payload.slots.map(s => s.id)),
220
+ capacity: payload.capacity || {},
221
+ accelerated: payload.capacity?.accelerated || false,
222
+ lastSeen: Date.now(),
223
+ });
224
+
225
+ this.emit('peerCapability', peerId, payload);
226
+
227
+ // Evict stale peer capabilities
228
+ this._cleanStalePeers();
229
+ }
230
+
231
+ _cleanStalePeers() {
232
+ const now = Date.now();
233
+ for (const [peerId, cap] of this.peerCapabilities) {
234
+ if (now - cap.lastSeen > SEVA_CONFIG.capabilityTTL) {
235
+ this.peerCapabilities.delete(peerId);
236
+ }
237
+ }
238
+ }
239
+
240
+ // ─── WORK REQUEST (CONSUMER SIDE) ─────────────────────────────────────────
241
+
242
+ /**
243
+ * Submit a work request to the mesh.
244
+ * Finds the best capable peer and sends the request.
245
+ *
246
+ * @param {string} slot — model slot ID
247
+ * @param {Object} params — numeric parameters
248
+ * @returns {Promise<Object>} — work result
249
+ */
250
+ async requestWork(slot, params) {
251
+ if (!SEVA_CONFIG.validSlots.has(slot)) {
252
+ throw new Error(`Invalid SEVA slot: ${slot}`);
253
+ }
254
+
255
+ // Validate params are math-only
256
+ if (!this._validateNumericOnly(params)) {
257
+ throw new Error('SEVA params must be numbers only');
258
+ }
259
+
260
+ // Find best peer for this slot
261
+ const peer = this._findBestPeer(slot);
262
+ if (!peer) {
263
+ throw new Error('No SEVA-capable peers available');
264
+ }
265
+
266
+ // Generate request ID
267
+ const reqId = this._generateId();
268
+
269
+ // Send request and wait for response
270
+ return new Promise((resolve, reject) => {
271
+ const timer = setTimeout(() => {
272
+ this.pendingRequests.delete(reqId);
273
+ reject(new Error('SEVA request timeout'));
274
+ }, SEVA_CONFIG.requestTimeout);
275
+
276
+ this.pendingRequests.set(reqId, { resolve, reject, timer, slot, params });
277
+
278
+ this.network.sendTo(peer, {
279
+ type: SEVA_CONFIG.messageTypes.REQUEST,
280
+ id: reqId,
281
+ slot,
282
+ params,
283
+ ts: Date.now(),
284
+ });
285
+ });
286
+ }
287
+
288
+ /**
289
+ * Find the best peer for a given slot.
290
+ * Prefers: accelerated > lowest load > most recent capability ad.
291
+ */
292
+ _findBestPeer(slot) {
293
+ let bestPeer = null;
294
+ let bestScore = -1;
295
+
296
+ for (const [peerId, cap] of this.peerCapabilities) {
297
+ if (!cap.slots.has(slot)) continue;
298
+
299
+ // Score: NPU acceleration = 100, low load bonus = 0-50, freshness = 0-10
300
+ let score = 0;
301
+ if (cap.accelerated) score += 100;
302
+ const loadRatio = (cap.capacity.currentLoad || 0) / (cap.capacity.maxConcurrent || 10);
303
+ score += (1 - loadRatio) * 50;
304
+ const ageMs = Date.now() - cap.lastSeen;
305
+ score += Math.max(0, 10 - ageMs / 10000);
306
+
307
+ if (score > bestScore) {
308
+ bestScore = score;
309
+ bestPeer = peerId;
310
+ }
311
+ }
312
+
313
+ return bestPeer;
314
+ }
315
+
316
+ // ─── WORK REQUEST (SERVER SIDE) ───────────────────────────────────────────
317
+
318
+ async _handleRequest(payload, peerId) {
319
+ if (!this.enabled || !this.executor) {
320
+ this.network.sendTo(peerId, {
321
+ type: SEVA_CONFIG.messageTypes.RESPONSE,
322
+ id: payload.id,
323
+ status: 'rejected',
324
+ error: 'SEVA not enabled on this node',
325
+ });
326
+ return;
327
+ }
328
+
329
+ // Rate limit check
330
+ if (!this._checkPeerRateLimit(peerId)) {
331
+ this.network.sendTo(peerId, {
332
+ type: SEVA_CONFIG.messageTypes.RESPONSE,
333
+ id: payload.id,
334
+ status: 'rejected',
335
+ error: 'Rate limit exceeded',
336
+ });
337
+ return;
338
+ }
339
+
340
+ // Validate slot
341
+ if (!SEVA_CONFIG.validSlots.has(payload.slot)) {
342
+ this.network.sendTo(peerId, {
343
+ type: SEVA_CONFIG.messageTypes.RESPONSE,
344
+ id: payload.id,
345
+ status: 'error',
346
+ error: 'Invalid slot',
347
+ });
348
+ return;
349
+ }
350
+
351
+ // Validate math-only params
352
+ if (!this._validateNumericOnly(payload.params)) {
353
+ this.network.sendTo(peerId, {
354
+ type: SEVA_CONFIG.messageTypes.RESPONSE,
355
+ id: payload.id,
356
+ status: 'error',
357
+ error: 'Params must be numbers only',
358
+ });
359
+ return;
360
+ }
361
+
362
+ // Concurrency check
363
+ if (this.activeJobs >= this.maxConcurrent) {
364
+ this.network.sendTo(peerId, {
365
+ type: SEVA_CONFIG.messageTypes.RESPONSE,
366
+ id: payload.id,
367
+ status: 'rejected',
368
+ error: 'Node at capacity',
369
+ });
370
+ return;
371
+ }
372
+
373
+ // Execute
374
+ this.activeJobs++;
375
+ try {
376
+ const t0 = performance.now();
377
+ const result = await this.executor(payload.slot, payload.params);
378
+ const computeMs = Math.round(performance.now() - t0);
379
+ this.totalServed++;
380
+
381
+ this.network.sendTo(peerId, {
382
+ type: SEVA_CONFIG.messageTypes.RESPONSE,
383
+ id: payload.id,
384
+ slot: payload.slot,
385
+ status: 'ok',
386
+ result,
387
+ source: this.hardware.npu ? 'npu' : 'cpu',
388
+ computeMs,
389
+ });
390
+
391
+ log.debug({ slot: payload.slot, computeMs, peer: peerId.slice(0, 8) }, 'SEVA work served');
392
+ } catch (err) {
393
+ this.network.sendTo(peerId, {
394
+ type: SEVA_CONFIG.messageTypes.RESPONSE,
395
+ id: payload.id,
396
+ status: 'error',
397
+ error: 'Execution failed',
398
+ });
399
+ } finally {
400
+ this.activeJobs--;
401
+ }
402
+ }
403
+
404
+ _handleResponse(payload, peerId) {
405
+ const pending = this.pendingRequests.get(payload.id);
406
+ if (!pending) return;
407
+
408
+ clearTimeout(pending.timer);
409
+ this.pendingRequests.delete(payload.id);
410
+
411
+ if (payload.status === 'ok') {
412
+ pending.resolve(payload);
413
+ } else {
414
+ pending.reject(new Error(payload.error || 'SEVA response error'));
415
+ }
416
+ }
417
+
418
+ // ─── VERIFICATION ─────────────────────────────────────────────────────────
419
+
420
+ async _handleVerify(payload, peerId) {
421
+ if (!this.enabled || !this.executor) return;
422
+
423
+ // Re-execute the computation
424
+ try {
425
+ const result = await this.executor(payload.slot, payload.params);
426
+
427
+ // Compare with claimed result using canonical JSON comparison
428
+ const match = JSON.stringify(result) === JSON.stringify(payload.claimedResult);
429
+
430
+ this.network.sendTo(peerId, {
431
+ type: SEVA_CONFIG.messageTypes.VERIFY_ACK,
432
+ id: payload.id,
433
+ match,
434
+ slot: payload.slot,
435
+ });
436
+ } catch {
437
+ // Can't verify — skip
438
+ }
439
+ }
440
+
441
+ _handleVerifyAck(payload, peerId) {
442
+ this.emit('verifyResult', payload.id, payload.match, peerId);
443
+
444
+ if (!payload.match) {
445
+ log.warn({ id: payload.id, peer: peerId.slice(0, 8) }, 'SEVA verification MISMATCH');
446
+ this.emit('verifyMismatch', payload.id, peerId);
447
+ }
448
+ }
449
+
450
+ // ─── RATE LIMITING ────────────────────────────────────────────────────────
451
+
452
+ _checkPeerRateLimit(peerId) {
453
+ const now = Date.now();
454
+ let state = this.peerRateLimits.get(peerId);
455
+ if (!state) {
456
+ state = { timestamps: [] };
457
+ this.peerRateLimits.set(peerId, state);
458
+ }
459
+
460
+ // Remove timestamps older than 60s
461
+ state.timestamps = state.timestamps.filter(t => now - t < 60000);
462
+ state.timestamps.push(now);
463
+
464
+ return state.timestamps.length <= SEVA_CONFIG.maxRequestsPerPeerPerMinute;
465
+ }
466
+
467
+ // ─── UTILITIES ────────────────────────────────────────────────────────────
468
+
469
+ _validateNumericOnly(obj, depth = 0) {
470
+ if (depth > 3) return false;
471
+ if (typeof obj === 'number' && isFinite(obj)) return true;
472
+ if (Array.isArray(obj)) {
473
+ if (obj.length > 100000) return false;
474
+ return obj.every(v => this._validateNumericOnly(v, depth + 1));
475
+ }
476
+ if (typeof obj === 'object' && obj !== null) {
477
+ const keys = Object.keys(obj);
478
+ if (keys.length > 20) return false;
479
+ return keys.every(k => this._validateNumericOnly(obj[k], depth + 1));
480
+ }
481
+ return false;
482
+ }
483
+
484
+ _generateId() {
485
+ const chars = '0123456789abcdef';
486
+ let id = '';
487
+ for (let i = 0; i < 16; i++) {
488
+ id += chars[Math.floor(Math.random() * 16)];
489
+ }
490
+ return id;
491
+ }
492
+
493
+ // ─── PUBLIC API ───────────────────────────────────────────────────────────
494
+
495
+ /**
496
+ * Get all known SEVA-capable peers.
497
+ */
498
+ getCapablePeers(slot = null) {
499
+ const peers = [];
500
+ for (const [peerId, cap] of this.peerCapabilities) {
501
+ if (slot && !cap.slots.has(slot)) continue;
502
+ peers.push({ peerId, ...cap });
503
+ }
504
+ return peers;
505
+ }
506
+
507
+ /**
508
+ * Get this node's current SEVA stats.
509
+ */
510
+ getStats() {
511
+ return {
512
+ enabled: this.enabled,
513
+ activeJobs: this.activeJobs,
514
+ totalServed: this.totalServed,
515
+ maxConcurrent: this.maxConcurrent,
516
+ knownPeers: this.peerCapabilities.size,
517
+ hardware: {
518
+ npu: !!this.hardware.npu,
519
+ npuTops: this.hardware.npuTops || 0,
520
+ gpu: !!this.hardware.gpu,
521
+ },
522
+ };
523
+ }
524
+ }
525
+
526
+ export default SevaMeshHandler;