@ruvector/edge-net 0.4.2 → 0.4.4

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.
@@ -0,0 +1,187 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @ruvector/edge-net Genesis Node Health Check
4
+ *
5
+ * Standalone health check script for use with container orchestrators,
6
+ * load balancers, and monitoring systems.
7
+ *
8
+ * Usage:
9
+ * node deploy/health-check.js # Check localhost:8788
10
+ * node deploy/health-check.js --host 10.0.0.1 # Check specific host
11
+ * node deploy/health-check.js --port 9000 # Check specific port
12
+ * node deploy/health-check.js --endpoint ready # Check readiness
13
+ * node deploy/health-check.js --json # JSON output
14
+ *
15
+ * Exit codes:
16
+ * 0 - Healthy
17
+ * 1 - Unhealthy or error
18
+ *
19
+ * @module @ruvector/edge-net/deploy/health-check
20
+ */
21
+
22
+ import http from 'http';
23
+
24
+ // Parse arguments
25
+ const args = process.argv.slice(2);
26
+ const config = {
27
+ host: 'localhost',
28
+ port: 8788,
29
+ endpoint: 'health',
30
+ timeout: 5000,
31
+ json: false,
32
+ };
33
+
34
+ for (let i = 0; i < args.length; i++) {
35
+ switch (args[i]) {
36
+ case '--host':
37
+ case '-h':
38
+ config.host = args[++i];
39
+ break;
40
+ case '--port':
41
+ case '-p':
42
+ config.port = parseInt(args[++i]);
43
+ break;
44
+ case '--endpoint':
45
+ case '-e':
46
+ config.endpoint = args[++i];
47
+ break;
48
+ case '--timeout':
49
+ case '-t':
50
+ config.timeout = parseInt(args[++i]);
51
+ break;
52
+ case '--json':
53
+ case '-j':
54
+ config.json = true;
55
+ break;
56
+ case '--help':
57
+ console.log(`
58
+ Genesis Node Health Check
59
+
60
+ Usage: node health-check.js [options]
61
+
62
+ Options:
63
+ --host, -h <host> Host to check (default: localhost)
64
+ --port, -p <port> Port to check (default: 8788)
65
+ --endpoint, -e <path> Endpoint to check: health, ready, status, metrics (default: health)
66
+ --timeout, -t <ms> Request timeout in milliseconds (default: 5000)
67
+ --json, -j Output JSON format
68
+ --help Show this help
69
+
70
+ Examples:
71
+ node health-check.js
72
+ node health-check.js --host genesis.example.com --port 8788
73
+ node health-check.js --endpoint ready
74
+ node health-check.js --json
75
+
76
+ Exit Codes:
77
+ 0 - Healthy/Ready
78
+ 1 - Unhealthy/Not Ready/Error
79
+ `);
80
+ process.exit(0);
81
+ }
82
+ }
83
+
84
+ function checkHealth() {
85
+ return new Promise((resolve, reject) => {
86
+ const startTime = Date.now();
87
+
88
+ const req = http.get({
89
+ hostname: config.host,
90
+ port: config.port,
91
+ path: `/${config.endpoint}`,
92
+ timeout: config.timeout,
93
+ }, (res) => {
94
+ let data = '';
95
+
96
+ res.on('data', chunk => data += chunk);
97
+ res.on('end', () => {
98
+ const latency = Date.now() - startTime;
99
+
100
+ try {
101
+ const parsed = JSON.parse(data);
102
+ resolve({
103
+ healthy: res.statusCode === 200,
104
+ statusCode: res.statusCode,
105
+ latency,
106
+ data: parsed,
107
+ });
108
+ } catch {
109
+ resolve({
110
+ healthy: res.statusCode === 200,
111
+ statusCode: res.statusCode,
112
+ latency,
113
+ data: data,
114
+ });
115
+ }
116
+ });
117
+ });
118
+
119
+ req.on('error', (err) => {
120
+ reject({
121
+ healthy: false,
122
+ error: err.message,
123
+ latency: Date.now() - startTime,
124
+ });
125
+ });
126
+
127
+ req.on('timeout', () => {
128
+ req.destroy();
129
+ reject({
130
+ healthy: false,
131
+ error: 'Request timeout',
132
+ latency: config.timeout,
133
+ });
134
+ });
135
+ });
136
+ }
137
+
138
+ async function main() {
139
+ try {
140
+ const result = await checkHealth();
141
+
142
+ if (config.json) {
143
+ console.log(JSON.stringify({
144
+ ...result,
145
+ host: config.host,
146
+ port: config.port,
147
+ endpoint: config.endpoint,
148
+ timestamp: new Date().toISOString(),
149
+ }, null, 2));
150
+ } else {
151
+ if (result.healthy) {
152
+ console.log(`OK - ${config.host}:${config.port}/${config.endpoint} (${result.latency}ms)`);
153
+
154
+ if (result.data?.ready !== undefined) {
155
+ console.log(` Ready: ${result.data.ready}`);
156
+ }
157
+ if (result.data?.status) {
158
+ console.log(` Status: ${result.data.status}`);
159
+ }
160
+ } else {
161
+ console.log(`FAIL - ${config.host}:${config.port}/${config.endpoint}`);
162
+ console.log(` Status Code: ${result.statusCode}`);
163
+ }
164
+ }
165
+
166
+ process.exit(result.healthy ? 0 : 1);
167
+
168
+ } catch (error) {
169
+ if (config.json) {
170
+ console.log(JSON.stringify({
171
+ healthy: false,
172
+ host: config.host,
173
+ port: config.port,
174
+ endpoint: config.endpoint,
175
+ error: error.error || error.message,
176
+ timestamp: new Date().toISOString(),
177
+ }, null, 2));
178
+ } else {
179
+ console.log(`ERROR - ${config.host}:${config.port}/${config.endpoint}`);
180
+ console.log(` Error: ${error.error || error.message}`);
181
+ }
182
+
183
+ process.exit(1);
184
+ }
185
+ }
186
+
187
+ main();
@@ -0,0 +1,38 @@
1
+ # Prometheus configuration for Edge-Net Genesis Node monitoring
2
+ #
3
+ # Scrapes metrics from genesis nodes at /metrics endpoint
4
+
5
+ global:
6
+ scrape_interval: 15s
7
+ evaluation_interval: 15s
8
+ external_labels:
9
+ cluster: 'edge-net-local'
10
+
11
+ alerting:
12
+ alertmanagers: []
13
+
14
+ rule_files: []
15
+
16
+ scrape_configs:
17
+ # Primary Genesis Node
18
+ - job_name: 'genesis'
19
+ static_configs:
20
+ - targets: ['genesis:8788']
21
+ labels:
22
+ instance: 'genesis-primary'
23
+ metrics_path: /metrics
24
+ scrape_interval: 10s
25
+
26
+ # Secondary Genesis Node (if cluster profile enabled)
27
+ - job_name: 'genesis-cluster'
28
+ static_configs:
29
+ - targets: ['genesis-2:8788']
30
+ labels:
31
+ instance: 'genesis-secondary'
32
+ metrics_path: /metrics
33
+ scrape_interval: 10s
34
+
35
+ # Prometheus self-monitoring
36
+ - job_name: 'prometheus'
37
+ static_configs:
38
+ - targets: ['localhost:9090']
@@ -423,6 +423,22 @@ export class FirebaseSignaling extends EventEmitter {
423
423
  case 'ice-candidate':
424
424
  this.emit('ice-candidate', { from: signal.from, candidate: signal.data, verified: !!signal.signature });
425
425
  break;
426
+ // Task execution signal types
427
+ case 'task-assign':
428
+ case 'task-result':
429
+ case 'task-error':
430
+ case 'task-progress':
431
+ case 'task-cancel':
432
+ this.emit('signal', {
433
+ type: signal.type,
434
+ from: signal.from,
435
+ data: signal.data,
436
+ verified: !!signal.signature,
437
+ signature: signal.signature,
438
+ publicKey: signal.publicKey,
439
+ timestamp: signal.timestamp,
440
+ });
441
+ break;
426
442
  default:
427
443
  this.emit('signal', { ...signal, verified: !!signal.signature });
428
444
  }
@@ -449,6 +465,42 @@ export class FirebaseSignaling extends EventEmitter {
449
465
  return this.sendSignal(toPeerId, 'ice-candidate', candidate);
450
466
  }
451
467
 
468
+ /**
469
+ * Serialize WebRTC objects to plain JSON for Firebase storage
470
+ * RTCIceCandidate and RTCSessionDescription are not directly storable
471
+ */
472
+ _serializeWebRTCData(data) {
473
+ if (!data || typeof data !== 'object') {
474
+ return data;
475
+ }
476
+
477
+ // Handle RTCIceCandidate
478
+ if (data.candidate !== undefined && data.sdpMid !== undefined) {
479
+ return {
480
+ candidate: data.candidate,
481
+ sdpMid: data.sdpMid,
482
+ sdpMLineIndex: data.sdpMLineIndex,
483
+ usernameFragment: data.usernameFragment || null,
484
+ };
485
+ }
486
+
487
+ // Handle RTCSessionDescription (offer/answer)
488
+ if (data.type !== undefined && data.sdp !== undefined) {
489
+ return {
490
+ type: data.type,
491
+ sdp: data.sdp,
492
+ };
493
+ }
494
+
495
+ // Try to convert any object with toJSON method
496
+ if (typeof data.toJSON === 'function') {
497
+ return data.toJSON();
498
+ }
499
+
500
+ // Return as-is if already plain object
501
+ return data;
502
+ }
503
+
452
504
  /**
453
505
  * Send signal via Firebase with WASM signature
454
506
  */
@@ -462,12 +514,15 @@ export class FirebaseSignaling extends EventEmitter {
462
514
  const signalId = `${this.peerId}-${toPeerId}-${Date.now()}`;
463
515
  const signalRef = doc(this.db, SIGNALING_PATHS.signals, signalId);
464
516
 
517
+ // Serialize WebRTC objects to plain JSON
518
+ const serializedData = this._serializeWebRTCData(data);
519
+
465
520
  const timestamp = Date.now();
466
521
  const signalData = {
467
522
  from: this.peerId,
468
523
  to: toPeerId,
469
524
  type,
470
- data,
525
+ data: serializedData,
471
526
  timestamp,
472
527
  room: this.room,
473
528
  };
@@ -478,7 +533,7 @@ export class FirebaseSignaling extends EventEmitter {
478
533
  from: this.peerId,
479
534
  to: toPeerId,
480
535
  type,
481
- data: typeof data === 'object' ? JSON.stringify(data) : data,
536
+ data: typeof serializedData === 'object' ? JSON.stringify(serializedData) : serializedData,
482
537
  timestamp
483
538
  });
484
539
  signalData.signature = signed.signature;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ruvector/edge-net",
3
- "version": "0.4.2",
3
+ "version": "0.4.4",
4
4
  "type": "module",
5
5
  "description": "Distributed compute intelligence network with WASM cryptographic security - contribute browser compute, spawn distributed AI agents, earn credits. Features Ed25519 signing, PiKey identity, Time Crystal coordination, Neural DAG attention, P2P swarm intelligence, ONNX inference, WebRTC signaling, CRDT ledger, and multi-agent workflows.",
6
6
  "main": "ruvector_edge_net.js",
@@ -77,6 +77,8 @@
77
77
  "ruvector_edge_net.d.ts",
78
78
  "ruvector_edge_net_bg.wasm.d.ts",
79
79
  "node/",
80
+ "deploy/",
81
+ "tests/",
80
82
  "index.js",
81
83
  "cli.js",
82
84
  "join.js",
@@ -189,6 +191,11 @@
189
191
  "signaling": "node -e \"import('./signaling.js').then(m => new m.SignalingServer().start())\"",
190
192
  "genesis": "node genesis.js",
191
193
  "genesis:start": "node genesis.js --port 8787",
194
+ "genesis:prod": "NODE_ENV=production node deploy/genesis-prod.js",
195
+ "genesis:docker": "docker-compose -f deploy/docker-compose.yml up -d",
196
+ "genesis:docker:logs": "docker-compose -f deploy/docker-compose.yml logs -f genesis",
197
+ "genesis:docker:stop": "docker-compose -f deploy/docker-compose.yml down",
198
+ "genesis:docker:build": "docker build -t ruvector/edge-net-genesis:latest -f deploy/Dockerfile .",
192
199
  "p2p": "node -e \"import('./p2p.js').then(m => m.createP2PNetwork({ nodeId: 'test' }))\"",
193
200
  "monitor": "node -e \"import('./monitor.js').then(m => { const mon = new m.Monitor(); mon.start(); setInterval(() => console.log(JSON.stringify(mon.generateReport(), null, 2)), 5000); })\"",
194
201
  "firebase:setup": "node firebase-setup.js",
package/real-workers.js CHANGED
@@ -295,6 +295,7 @@ export class RealWorkerPool extends EventEmitter {
295
295
  status: 'idle',
296
296
  tasksCompleted: 0,
297
297
  currentTask: null,
298
+ terminated: false, // Track intentional termination
298
299
  };
299
300
 
300
301
  worker.on('message', (msg) => {
@@ -307,13 +308,16 @@ export class RealWorkerPool extends EventEmitter {
307
308
  });
308
309
 
309
310
  worker.on('exit', (code) => {
310
- if (code !== 0) {
311
- console.error(`[Worker ${index}] Exited with code ${code}`);
311
+ // Only respawn if worker crashed unexpectedly (not terminated intentionally)
312
+ if (!workerInfo.terminated && this.status === 'ready') {
313
+ console.error(`[Worker ${index}] Exited unexpectedly with code ${code}, respawning...`);
312
314
  // Respawn worker
313
315
  const idx = this.workers.indexOf(workerInfo);
314
- if (idx >= 0 && this.status === 'ready') {
316
+ if (idx >= 0) {
315
317
  this.spawnWorker(index).then(w => {
316
318
  this.workers[idx] = w;
319
+ }).catch(err => {
320
+ console.error(`[Worker ${index}] Failed to respawn:`, err.message);
317
321
  });
318
322
  }
319
323
  }
@@ -537,8 +541,9 @@ export class RealWorkerPool extends EventEmitter {
537
541
  await new Promise(r => setTimeout(r, 100));
538
542
  }
539
543
 
540
- // Terminate workers
544
+ // Terminate workers (mark as intentionally terminated first)
541
545
  for (const workerInfo of this.workers) {
546
+ workerInfo.terminated = true;
542
547
  await workerInfo.worker.terminate();
543
548
  }
544
549
 
package/scheduler.js CHANGED
@@ -596,11 +596,15 @@ export class TaskScheduler extends EventEmitter {
596
596
  worker.allocate(task);
597
597
  this.running.set(task.id, task);
598
598
 
599
- // Calculate wait time
599
+ // Calculate wait time using running average
600
600
  const waitTime = task.startedAt - task.queuedAt;
601
- this.stats.avgWaitTime =
602
- (this.stats.avgWaitTime * this.stats.submitted + waitTime) /
603
- (this.stats.submitted + 1);
601
+ const assignedCount = this.stats.completed + this.running.size;
602
+ if (assignedCount <= 1) {
603
+ this.stats.avgWaitTime = waitTime;
604
+ } else {
605
+ this.stats.avgWaitTime =
606
+ (this.stats.avgWaitTime * (assignedCount - 1) + waitTime) / assignedCount;
607
+ }
604
608
 
605
609
  this.emit('task-assigned', {
606
610
  taskId: task.id,