Merge commit 'd803bfe2b1fe7f5e219e50ac20d6801a0a58ac75' as 'vendor/ruvector'

This commit is contained in:
ruv
2026-02-28 14:39:40 -05:00
7854 changed files with 3522914 additions and 0 deletions

View File

@@ -0,0 +1 @@
{"encrypted":[2,1,121,40,176,8,76,13,209,242,100,129,71,222,242,78,196,24,80,204,233,22,86,198,1,178,115,15,59,89,205,79,184,12,6,242,45,205,111,246,47,70,65,18,182,141,191,234,201,111,234,245,219,123,235,122,197,93,58,70,127,48,94,113,123,70,38,13,103,200,115,88,210,10,156,93,47,245],"created":1767382616574,"siteId":"edge-net-multitenancy-demo"}

View File

@@ -0,0 +1,8 @@
# Don't ignore anything - include all files for Cloud Build
!*
!**/*
# Ignore only truly unwanted files
node_modules/
.git/
*.log

View File

@@ -0,0 +1,100 @@
# @ruvector/edge-net Genesis Node - Production Dockerfile
#
# Multi-stage build for minimal production image
# Supports: Docker, Kubernetes, Cloud Run, AWS ECS, Azure Container Instances
#
# Build:
# docker build -t ruvector/edge-net-genesis:latest -f deploy/Dockerfile .
#
# Run:
# docker run -p 8787:8787 -p 8788:8788 ruvector/edge-net-genesis:latest
# ============================================
# Stage 1: Dependencies
# ============================================
FROM node:20-alpine AS deps
WORKDIR /app
# Install build dependencies for native modules
RUN apk add --no-cache python3 make g++ linux-headers
# Copy package files
COPY package.json package-lock.json* ./
# Install production dependencies only
RUN npm ci --only=production --ignore-scripts 2>/dev/null || npm install --only=production --ignore-scripts
# ============================================
# Stage 2: Production Runtime
# ============================================
FROM node:20-alpine AS runner
# Security: Run as non-root user
RUN addgroup -g 1001 -S nodejs && \
adduser -S edgenet -u 1001 -G nodejs
WORKDIR /app
# Install runtime dependencies
RUN apk add --no-cache \
tini \
dumb-init \
curl
# Copy node_modules from deps stage
COPY --from=deps /app/node_modules ./node_modules
# Copy application files
COPY --chown=edgenet:nodejs package.json ./
COPY --chown=edgenet:nodejs *.js ./
COPY --chown=edgenet:nodejs *.d.ts ./
COPY --chown=edgenet:nodejs *.wasm ./
COPY --chown=edgenet:nodejs node/ ./node/
COPY --chown=edgenet:nodejs deploy/genesis-prod.js ./deploy/
COPY --chown=edgenet:nodejs deploy/health-check.js ./deploy/
# Create data directory with correct permissions
RUN mkdir -p /data/genesis && \
chown -R edgenet:nodejs /data/genesis && \
chmod 755 /data/genesis
# Set environment variables
ENV NODE_ENV=production
ENV GENESIS_PORT=8787
ENV GENESIS_HOST=0.0.0.0
ENV HEALTH_PORT=8788
ENV GENESIS_DATA=/data/genesis
ENV LOG_FORMAT=json
ENV LOG_LEVEL=info
ENV METRICS_ENABLED=true
# Expose ports
# 8787: WebSocket signaling
# 8788: Health check / metrics
EXPOSE 8787
EXPOSE 8788
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8788/health || exit 1
# Switch to non-root user
USER edgenet
# Use tini as init system for proper signal handling
ENTRYPOINT ["/sbin/tini", "--"]
# Start the genesis node
CMD ["node", "deploy/genesis-prod.js"]
# ============================================
# Labels for container registry
# ============================================
LABEL org.opencontainers.image.title="Edge-Net Genesis Node"
LABEL org.opencontainers.image.description="Bootstrap node for the RuVector Edge-Net P2P network"
LABEL org.opencontainers.image.vendor="RuVector"
LABEL org.opencontainers.image.url="https://github.com/ruvnet/ruvector"
LABEL org.opencontainers.image.source="https://github.com/ruvnet/ruvector/tree/main/examples/edge-net"
LABEL org.opencontainers.image.version="1.0.0"
LABEL org.opencontainers.image.licenses="MIT"

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 RuVector Team
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,965 @@
#!/usr/bin/env node
/**
* Edge-Net Agent System
*
* Distributed AI agent execution across the Edge-Net collective.
* Spawn agents, create worker pools, and orchestrate multi-agent workflows.
*/
import { EventEmitter } from 'events';
import { randomBytes, createHash } from 'crypto';
// Agent types and their capabilities
export const AGENT_TYPES = {
researcher: {
name: 'Researcher',
capabilities: ['search', 'analyze', 'summarize', 'extract'],
baseRuv: 10,
description: 'Analyzes and researches information',
},
coder: {
name: 'Coder',
capabilities: ['code', 'refactor', 'debug', 'test'],
baseRuv: 15,
description: 'Writes and improves code',
},
reviewer: {
name: 'Reviewer',
capabilities: ['review', 'audit', 'validate', 'suggest'],
baseRuv: 12,
description: 'Reviews code and provides feedback',
},
tester: {
name: 'Tester',
capabilities: ['test', 'benchmark', 'validate', 'report'],
baseRuv: 10,
description: 'Tests and validates implementations',
},
analyst: {
name: 'Analyst',
capabilities: ['analyze', 'metrics', 'report', 'visualize'],
baseRuv: 8,
description: 'Analyzes data and generates reports',
},
optimizer: {
name: 'Optimizer',
capabilities: ['optimize', 'profile', 'benchmark', 'improve'],
baseRuv: 15,
description: 'Optimizes performance and efficiency',
},
coordinator: {
name: 'Coordinator',
capabilities: ['orchestrate', 'route', 'schedule', 'monitor'],
baseRuv: 20,
description: 'Coordinates multi-agent workflows',
},
embedder: {
name: 'Embedder',
capabilities: ['embed', 'vectorize', 'similarity', 'search'],
baseRuv: 5,
description: 'Generates embeddings and vector operations',
},
};
// Task status enum
export const TaskStatus = {
PENDING: 'pending',
QUEUED: 'queued',
ASSIGNED: 'assigned',
RUNNING: 'running',
COMPLETED: 'completed',
FAILED: 'failed',
CANCELLED: 'cancelled',
};
/**
* Distributed Agent
*
* Represents an AI agent running on the Edge-Net network.
*/
export class DistributedAgent extends EventEmitter {
constructor(options) {
super();
this.id = `agent-${randomBytes(8).toString('hex')}`;
this.type = options.type || 'researcher';
this.task = options.task;
this.config = AGENT_TYPES[this.type] || AGENT_TYPES.researcher;
this.maxRuv = options.maxRuv || this.config.baseRuv;
this.priority = options.priority || 'medium';
this.timeout = options.timeout || 300000; // 5 min default
this.status = TaskStatus.PENDING;
this.assignedNode = null;
this.progress = 0;
this.result = null;
this.error = null;
this.startTime = null;
this.endTime = null;
this.ruvSpent = 0;
this.subtasks = [];
this.logs = [];
}
/**
* Get agent info
*/
getInfo() {
return {
id: this.id,
type: this.type,
task: this.task,
status: this.status,
progress: this.progress,
assignedNode: this.assignedNode,
maxRuv: this.maxRuv,
ruvSpent: this.ruvSpent,
startTime: this.startTime,
endTime: this.endTime,
duration: this.endTime && this.startTime
? this.endTime - this.startTime
: null,
};
}
/**
* Update agent progress
*/
updateProgress(progress, message) {
this.progress = Math.min(100, Math.max(0, progress));
this.log(`Progress: ${this.progress}% - ${message}`);
this.emit('progress', { progress: this.progress, message });
}
/**
* Log message
*/
log(message) {
const entry = {
timestamp: Date.now(),
message,
};
this.logs.push(entry);
this.emit('log', entry);
}
/**
* Mark as completed
*/
complete(result) {
this.status = TaskStatus.COMPLETED;
this.result = result;
this.progress = 100;
this.endTime = Date.now();
this.log('Agent completed successfully');
this.emit('complete', result);
}
/**
* Mark as failed
*/
fail(error) {
this.status = TaskStatus.FAILED;
this.error = error;
this.endTime = Date.now();
this.log(`Agent failed: ${error}`);
this.emit('error', error);
}
/**
* Cancel the agent
*/
cancel() {
this.status = TaskStatus.CANCELLED;
this.endTime = Date.now();
this.log('Agent cancelled');
this.emit('cancelled');
}
}
/**
* Agent Spawner
*
* Spawns and manages distributed agents across the Edge-Net network.
*/
export class AgentSpawner extends EventEmitter {
constructor(networkManager, options = {}) {
super();
this.network = networkManager;
this.agents = new Map();
this.pendingQueue = [];
this.maxConcurrent = options.maxConcurrent || 10;
this.defaultTimeout = options.defaultTimeout || 300000;
// Agent routing table (learned from outcomes)
this.routingTable = new Map();
// Stats
this.stats = {
totalSpawned: 0,
completed: 0,
failed: 0,
totalRuvSpent: 0,
};
}
/**
* Spawn a new agent on the network
*/
async spawn(options) {
const agent = new DistributedAgent({
...options,
timeout: options.timeout || this.defaultTimeout,
});
this.agents.set(agent.id, agent);
this.stats.totalSpawned++;
agent.log(`Agent spawned: ${agent.type} - ${agent.task}`);
agent.status = TaskStatus.QUEUED;
// Find best node for this agent type
const targetNode = await this.findBestNode(agent);
if (targetNode) {
await this.assignToNode(agent, targetNode);
} else {
// Queue for later assignment
this.pendingQueue.push(agent);
agent.log('Queued - waiting for available node');
}
this.emit('agent-spawned', agent);
return agent;
}
/**
* Find the best node for an agent based on capabilities and load
*/
async findBestNode(agent) {
if (!this.network) return null;
const peers = this.network.getPeerList ?
this.network.getPeerList() :
Array.from(this.network.peers?.values() || []);
if (peers.length === 0) return null;
// Score each peer based on:
// 1. Capability match
// 2. Current load
// 3. Historical performance
// 4. Latency
const scoredPeers = peers.map(peer => {
let score = 50; // Base score
// Check capabilities
const peerCaps = peer.capabilities || [];
const requiredCaps = agent.config.capabilities;
const capMatch = requiredCaps.filter(c => peerCaps.includes(c)).length;
score += capMatch * 10;
// Check load (lower is better)
const load = peer.load || 0;
score -= load * 20;
// Check historical performance
const history = this.routingTable.get(`${peer.piKey || peer.id}-${agent.type}`);
if (history) {
score += history.successRate * 30;
score -= history.avgLatency / 1000; // Penalize high latency
}
return { peer, score };
});
// Sort by score (highest first)
scoredPeers.sort((a, b) => b.score - a.score);
return scoredPeers[0]?.peer || null;
}
/**
* Assign agent to a specific node
*/
async assignToNode(agent, node) {
agent.status = TaskStatus.ASSIGNED;
agent.assignedNode = node.piKey || node.id;
agent.startTime = Date.now();
agent.log(`Assigned to node: ${agent.assignedNode.slice(0, 12)}...`);
// Send task to node via network
if (this.network?.sendToPeer) {
await this.network.sendToPeer(agent.assignedNode, {
type: 'agent_task',
agentId: agent.id,
agentType: agent.type,
task: agent.task,
maxRuv: agent.maxRuv,
timeout: agent.timeout,
});
}
agent.status = TaskStatus.RUNNING;
this.emit('agent-assigned', { agent, node });
// Set timeout
setTimeout(() => {
if (agent.status === TaskStatus.RUNNING) {
agent.fail('Timeout exceeded');
}
}, agent.timeout);
}
/**
* Handle task result from network
*/
handleResult(agentId, result) {
const agent = this.agents.get(agentId);
if (!agent) return;
if (result.success) {
agent.complete(result.data);
this.stats.completed++;
this.updateRoutingTable(agent, true, result.latency);
} else {
agent.fail(result.error);
this.stats.failed++;
this.updateRoutingTable(agent, false, result.latency);
}
agent.ruvSpent = result.ruvSpent || agent.config.baseRuv;
this.stats.totalRuvSpent += agent.ruvSpent;
}
/**
* Update routing table with outcome
*/
updateRoutingTable(agent, success, latency) {
const key = `${agent.assignedNode}-${agent.type}`;
const existing = this.routingTable.get(key) || {
attempts: 0,
successes: 0,
totalLatency: 0,
};
existing.attempts++;
if (success) existing.successes++;
existing.totalLatency += latency || 0;
existing.successRate = existing.successes / existing.attempts;
existing.avgLatency = existing.totalLatency / existing.attempts;
this.routingTable.set(key, existing);
}
/**
* Get agent by ID
*/
getAgent(agentId) {
return this.agents.get(agentId);
}
/**
* List all agents
*/
listAgents(filter = {}) {
let agents = Array.from(this.agents.values());
if (filter.status) {
agents = agents.filter(a => a.status === filter.status);
}
if (filter.type) {
agents = agents.filter(a => a.type === filter.type);
}
return agents.map(a => a.getInfo());
}
/**
* Get spawner stats
*/
getStats() {
return {
...this.stats,
activeAgents: Array.from(this.agents.values())
.filter(a => a.status === TaskStatus.RUNNING).length,
queuedAgents: this.pendingQueue.length,
successRate: this.stats.completed /
(this.stats.completed + this.stats.failed) || 0,
};
}
}
/**
* Worker Pool
*
* Manages a pool of distributed workers for parallel task execution.
*/
export class WorkerPool extends EventEmitter {
constructor(networkManager, options = {}) {
super();
this.id = `pool-${randomBytes(6).toString('hex')}`;
this.network = networkManager;
this.size = options.size || 5;
this.capabilities = options.capabilities || ['compute', 'embed'];
this.maxTasksPerWorker = options.maxTasksPerWorker || 10;
this.workers = new Map();
this.taskQueue = [];
this.activeTasks = new Map();
this.results = new Map();
this.status = 'initializing';
this.stats = {
tasksCompleted: 0,
tasksFailed: 0,
totalProcessingTime: 0,
};
}
/**
* Initialize the worker pool
*/
async initialize() {
this.status = 'recruiting';
this.emit('status', 'Recruiting workers...');
// Find available workers from network
const peers = this.network?.getPeerList?.() ||
Array.from(this.network?.peers?.values() || []);
// Filter peers by capabilities
const eligiblePeers = peers.filter(peer => {
const peerCaps = peer.capabilities || [];
return this.capabilities.some(c => peerCaps.includes(c));
});
// Recruit up to pool size
const recruited = eligiblePeers.slice(0, this.size);
for (const peer of recruited) {
this.workers.set(peer.piKey || peer.id, {
id: peer.piKey || peer.id,
peer,
status: 'idle',
currentTasks: 0,
completedTasks: 0,
lastSeen: Date.now(),
});
}
// If not enough real workers, create virtual workers for local execution
while (this.workers.size < this.size) {
const virtualId = `virtual-${randomBytes(4).toString('hex')}`;
this.workers.set(virtualId, {
id: virtualId,
peer: null,
status: 'idle',
currentTasks: 0,
completedTasks: 0,
isVirtual: true,
});
}
this.status = 'ready';
this.emit('ready', {
poolId: this.id,
workers: this.workers.size,
realWorkers: Array.from(this.workers.values())
.filter(w => !w.isVirtual).length,
});
return this;
}
/**
* Execute tasks in parallel across workers
*/
async execute(options) {
const {
task,
data,
strategy = 'parallel',
chunkSize = null,
} = options;
const batchId = `batch-${randomBytes(6).toString('hex')}`;
const startTime = Date.now();
// Split data into chunks for workers
let chunks;
if (Array.isArray(data)) {
const size = chunkSize || Math.ceil(data.length / this.workers.size);
chunks = [];
for (let i = 0; i < data.length; i += size) {
chunks.push(data.slice(i, i + size));
}
} else {
chunks = [data];
}
this.emit('batch-start', { batchId, chunks: chunks.length });
// Assign chunks to workers
const promises = chunks.map((chunk, index) =>
this.assignTask({
batchId,
index,
task,
data: chunk,
})
);
// Wait for all or handle based on strategy
let results;
if (strategy === 'parallel') {
results = await Promise.all(promises);
} else if (strategy === 'race') {
results = [await Promise.race(promises)];
} else {
// Sequential
results = [];
for (const promise of promises) {
results.push(await promise);
}
}
const endTime = Date.now();
this.stats.totalProcessingTime += endTime - startTime;
this.emit('batch-complete', {
batchId,
duration: endTime - startTime,
results: results.length,
});
// Flatten results if array
return Array.isArray(data) ? results.flat() : results[0];
}
/**
* Assign a single task to an available worker
*/
async assignTask(taskInfo) {
const taskId = `task-${randomBytes(6).toString('hex')}`;
// Find idle worker
const worker = this.findIdleWorker();
if (!worker) {
// Queue task
return new Promise((resolve, reject) => {
this.taskQueue.push({ taskInfo, resolve, reject });
});
}
worker.status = 'busy';
worker.currentTasks++;
this.activeTasks.set(taskId, {
...taskInfo,
workerId: worker.id,
startTime: Date.now(),
});
try {
// Execute on worker
const result = await this.executeOnWorker(worker, taskInfo);
worker.completedTasks++;
this.stats.tasksCompleted++;
this.results.set(taskId, result);
return result;
} catch (error) {
this.stats.tasksFailed++;
throw error;
} finally {
worker.currentTasks--;
if (worker.currentTasks === 0) {
worker.status = 'idle';
}
this.activeTasks.delete(taskId);
// Process queued task if any
if (this.taskQueue.length > 0) {
const { taskInfo, resolve, reject } = this.taskQueue.shift();
this.assignTask(taskInfo).then(resolve).catch(reject);
}
}
}
/**
* Find an idle worker
*/
findIdleWorker() {
for (const worker of this.workers.values()) {
if (worker.status === 'idle' ||
worker.currentTasks < this.maxTasksPerWorker) {
return worker;
}
}
return null;
}
/**
* Execute task on a specific worker
*/
async executeOnWorker(worker, taskInfo) {
if (worker.isVirtual) {
// Local execution for virtual workers
return this.executeLocally(taskInfo);
}
// Send to remote worker via network
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
reject(new Error('Worker timeout'));
}, 60000);
// Send task
if (this.network?.sendToPeer) {
this.network.sendToPeer(worker.id, {
type: 'worker_task',
poolId: this.id,
task: taskInfo.task,
data: taskInfo.data,
});
}
// Listen for result
const handler = (msg) => {
if (msg.poolId === this.id && msg.batchId === taskInfo.batchId) {
clearTimeout(timeout);
this.network?.off?.('worker_result', handler);
resolve(msg.result);
}
};
this.network?.on?.('worker_result', handler);
// Fallback to local if no response
setTimeout(() => {
clearTimeout(timeout);
this.executeLocally(taskInfo).then(resolve).catch(reject);
}, 5000);
});
}
/**
* Execute task locally (for virtual workers or fallback)
*/
async executeLocally(taskInfo) {
const { task, data } = taskInfo;
// Simple local execution based on task type
switch (task) {
case 'embed':
// Simulate embedding
return Array.isArray(data)
? data.map(() => new Array(384).fill(0).map(() => Math.random()))
: new Array(384).fill(0).map(() => Math.random());
case 'process':
return Array.isArray(data)
? data.map(item => ({ processed: true, item }))
: { processed: true, data };
case 'analyze':
return {
analyzed: true,
itemCount: Array.isArray(data) ? data.length : 1,
timestamp: Date.now(),
};
default:
return { task, data, executed: true };
}
}
/**
* Get pool status
*/
getStatus() {
const workers = Array.from(this.workers.values());
return {
poolId: this.id,
status: this.status,
totalWorkers: workers.length,
idleWorkers: workers.filter(w => w.status === 'idle').length,
busyWorkers: workers.filter(w => w.status === 'busy').length,
virtualWorkers: workers.filter(w => w.isVirtual).length,
queuedTasks: this.taskQueue.length,
activeTasks: this.activeTasks.size,
stats: this.stats,
};
}
/**
* Shutdown the pool
*/
async shutdown() {
this.status = 'shutting_down';
// Wait for active tasks
while (this.activeTasks.size > 0) {
await new Promise(r => setTimeout(r, 100));
}
// Clear workers
this.workers.clear();
this.status = 'shutdown';
this.emit('shutdown');
}
}
/**
* Task Orchestrator
*
* Orchestrates multi-agent workflows and complex task pipelines.
*/
export class TaskOrchestrator extends EventEmitter {
constructor(agentSpawner, workerPool, options = {}) {
super();
this.spawner = agentSpawner;
this.pool = workerPool;
this.workflows = new Map();
this.maxConcurrentWorkflows = options.maxConcurrentWorkflows || 5;
}
/**
* Create a workflow
*/
createWorkflow(name, steps) {
const workflow = {
id: `wf-${randomBytes(6).toString('hex')}`,
name,
steps,
status: 'created',
currentStep: 0,
results: [],
startTime: null,
endTime: null,
};
this.workflows.set(workflow.id, workflow);
return workflow;
}
/**
* Execute a workflow
*/
async executeWorkflow(workflowId, input = {}) {
const workflow = this.workflows.get(workflowId);
if (!workflow) throw new Error('Workflow not found');
workflow.status = 'running';
workflow.startTime = Date.now();
workflow.input = input;
this.emit('workflow-start', { workflowId, name: workflow.name });
try {
let context = { ...input };
for (let i = 0; i < workflow.steps.length; i++) {
workflow.currentStep = i;
const step = workflow.steps[i];
this.emit('step-start', {
workflowId,
step: i,
type: step.type,
name: step.name,
});
const result = await this.executeStep(step, context);
workflow.results.push(result);
// Pass result to next step
context = { ...context, [step.name || `step${i}`]: result };
this.emit('step-complete', {
workflowId,
step: i,
result,
});
}
workflow.status = 'completed';
workflow.endTime = Date.now();
this.emit('workflow-complete', {
workflowId,
duration: workflow.endTime - workflow.startTime,
results: workflow.results,
});
return {
success: true,
results: workflow.results,
context,
};
} catch (error) {
workflow.status = 'failed';
workflow.endTime = Date.now();
workflow.error = error.message;
this.emit('workflow-failed', { workflowId, error: error.message });
return {
success: false,
error: error.message,
failedStep: workflow.currentStep,
};
}
}
/**
* Execute a single workflow step
*/
async executeStep(step, context) {
switch (step.type) {
case 'agent':
return this.executeAgentStep(step, context);
case 'parallel':
return this.executeParallelStep(step, context);
case 'pool':
return this.executePoolStep(step, context);
case 'condition':
return this.executeConditionStep(step, context);
case 'transform':
return this.executeTransformStep(step, context);
default:
throw new Error(`Unknown step type: ${step.type}`);
}
}
/**
* Execute an agent step
*/
async executeAgentStep(step, context) {
const task = typeof step.task === 'function'
? step.task(context)
: step.task;
const agent = await this.spawner.spawn({
type: step.agentType || 'researcher',
task,
maxRuv: step.maxRuv,
priority: step.priority,
});
return new Promise((resolve, reject) => {
agent.on('complete', resolve);
agent.on('error', reject);
// Simulate completion for now
setTimeout(() => {
agent.complete({
task,
result: `Completed: ${task}`,
context,
});
}, 1000);
});
}
/**
* Execute parallel agents
*/
async executeParallelStep(step, context) {
const promises = step.agents.map(agentConfig =>
this.executeAgentStep(agentConfig, context)
);
return Promise.all(promises);
}
/**
* Execute worker pool step
*/
async executePoolStep(step, context) {
const data = typeof step.data === 'function'
? step.data(context)
: step.data || context.data;
return this.pool.execute({
task: step.task,
data,
strategy: step.strategy || 'parallel',
});
}
/**
* Execute conditional step
*/
async executeConditionStep(step, context) {
const condition = typeof step.condition === 'function'
? step.condition(context)
: step.condition;
if (condition) {
return this.executeStep(step.then, context);
} else if (step.else) {
return this.executeStep(step.else, context);
}
return null;
}
/**
* Execute transform step
*/
async executeTransformStep(step, context) {
return step.transform(context);
}
/**
* Get workflow status
*/
getWorkflowStatus(workflowId) {
const workflow = this.workflows.get(workflowId);
if (!workflow) return null;
return {
id: workflow.id,
name: workflow.name,
status: workflow.status,
currentStep: workflow.currentStep,
totalSteps: workflow.steps.length,
progress: (workflow.currentStep / workflow.steps.length) * 100,
startTime: workflow.startTime,
endTime: workflow.endTime,
duration: workflow.endTime && workflow.startTime
? workflow.endTime - workflow.startTime
: null,
};
}
/**
* List all workflows
*/
listWorkflows() {
return Array.from(this.workflows.values()).map(w => ({
id: w.id,
name: w.name,
status: w.status,
steps: w.steps.length,
}));
}
}
// Export default instances
export default {
AGENT_TYPES,
TaskStatus,
DistributedAgent,
AgentSpawner,
WorkerPool,
TaskOrchestrator,
};

454
vendor/ruvector/examples/edge-net/pkg/cli.js vendored Executable file
View File

@@ -0,0 +1,454 @@
#!/usr/bin/env node
/**
* @ruvector/edge-net CLI
*
* Distributed compute intelligence network with Time Crystal coordination,
* Neural DAG attention, and P2P swarm intelligence.
*
* Usage:
* npx @ruvector/edge-net [command] [options]
*/
import { readFileSync, existsSync, statSync } from 'fs';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
import { webcrypto } from 'crypto';
import { performance } from 'perf_hooks';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// Setup Node.js polyfills for web APIs BEFORE loading WASM
async function setupPolyfills() {
// Crypto API
if (typeof globalThis.crypto === 'undefined') {
globalThis.crypto = webcrypto;
}
// Performance API
if (typeof globalThis.performance === 'undefined') {
globalThis.performance = performance;
}
// In-memory storage
const createStorage = () => {
const store = new Map();
return {
getItem: (key) => store.get(key) || null,
setItem: (key, value) => store.set(key, String(value)),
removeItem: (key) => store.delete(key),
clear: () => store.clear(),
get length() { return store.size; },
key: (i) => [...store.keys()][i] || null,
};
};
// Get CPU count synchronously
let cpuCount = 4;
try {
const os = await import('os');
cpuCount = os.cpus().length;
} catch {}
// Mock window object
if (typeof globalThis.window === 'undefined') {
globalThis.window = {
crypto: globalThis.crypto,
performance: globalThis.performance,
localStorage: createStorage(),
sessionStorage: createStorage(),
navigator: {
userAgent: `Node.js/${process.version}`,
language: 'en-US',
languages: ['en-US', 'en'],
hardwareConcurrency: cpuCount,
},
location: { href: 'node://localhost', hostname: 'localhost' },
screen: { width: 1920, height: 1080, colorDepth: 24 },
};
}
// Mock document
if (typeof globalThis.document === 'undefined') {
globalThis.document = {
createElement: () => ({}),
body: {},
head: {},
};
}
}
// ANSI colors
const colors = {
reset: '\x1b[0m',
bold: '\x1b[1m',
dim: '\x1b[2m',
cyan: '\x1b[36m',
green: '\x1b[32m',
yellow: '\x1b[33m',
blue: '\x1b[34m',
magenta: '\x1b[35m',
red: '\x1b[31m',
};
const c = (color, text) => `${colors[color]}${text}${colors.reset}`;
function printBanner() {
console.log(`
${c('cyan', '╔═══════════════════════════════════════════════════════════════╗')}
${c('cyan', '║')} ${c('bold', '🌐 RuVector Edge-Net')} ${c('cyan', '║')}
${c('cyan', '║')} ${c('dim', 'Distributed Compute Intelligence Network')} ${c('cyan', '║')}
${c('cyan', '╚═══════════════════════════════════════════════════════════════╝')}
`);
}
function printHelp() {
printBanner();
console.log(`${c('bold', 'USAGE:')}
${c('green', 'npx @ruvector/edge-net')} ${c('yellow', '<command>')} [options]
${c('bold', 'COMMANDS:')}
${c('green', 'start')} Start an edge-net node in the terminal
${c('green', 'join')} Join network with public key (multi-contributor support)
${c('green', 'benchmark')} Run performance benchmarks
${c('green', 'info')} Show package and WASM information
${c('green', 'demo')} Run interactive demonstration
${c('green', 'test')} Test WASM module loading
${c('green', 'help')} Show this help message
${c('bold', 'EXAMPLES:')}
${c('dim', '# Start a node')}
$ npx @ruvector/edge-net start
${c('dim', '# Join with new identity (multi-contributor)')}
$ npx @ruvector/edge-net join --generate
${c('dim', '# Run benchmarks')}
$ npx @ruvector/edge-net benchmark
${c('dim', '# Test WASM loading')}
$ npx @ruvector/edge-net test
${c('bold', 'FEATURES:')}
${c('magenta', '⏱️ Time Crystal')} - Distributed coordination via period-doubled oscillations
${c('magenta', '🔀 DAG Attention')} - Critical path analysis for task orchestration
${c('magenta', '🧠 Neural NAO')} - Stake-weighted quadratic voting governance
${c('magenta', '📊 HNSW Index')} - 150x faster semantic vector search
${c('magenta', '🔗 P2P Swarm')} - Decentralized agent coordination
${c('bold', 'BROWSER USAGE:')}
${c('dim', 'import init, { EdgeNetNode } from "@ruvector/edge-net";')}
${c('dim', 'await init();')}
${c('dim', 'const node = new EdgeNetNode();')}
${c('dim', 'Documentation: https://github.com/ruvnet/ruvector/tree/main/examples/edge-net')}
`);
}
async function showInfo() {
printBanner();
const pkgPath = join(__dirname, 'package.json');
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
const wasmPath = join(__dirname, 'ruvector_edge_net_bg.wasm');
const nodeWasmPath = join(__dirname, 'node', 'ruvector_edge_net_bg.wasm');
const wasmExists = existsSync(wasmPath);
const nodeWasmExists = existsSync(nodeWasmPath);
let wasmSize = 0, nodeWasmSize = 0;
if (wasmExists) wasmSize = statSync(wasmPath).size;
if (nodeWasmExists) nodeWasmSize = statSync(nodeWasmPath).size;
console.log(`${c('bold', 'PACKAGE INFO:')}
${c('cyan', 'Name:')} ${pkg.name}
${c('cyan', 'Version:')} ${pkg.version}
${c('cyan', 'License:')} ${pkg.license}
${c('cyan', 'Type:')} ${pkg.type}
${c('bold', 'WASM MODULES:')}
${c('cyan', 'Web Target:')} ${wasmExists ? c('green', '✓') : c('red', '✗')} ${(wasmSize / 1024 / 1024).toFixed(2)} MB
${c('cyan', 'Node Target:')} ${nodeWasmExists ? c('green', '✓') : c('red', '✗')} ${(nodeWasmSize / 1024 / 1024).toFixed(2)} MB
${c('bold', 'ENVIRONMENT:')}
${c('cyan', 'Runtime:')} Node.js ${process.version}
${c('cyan', 'Platform:')} ${process.platform} ${process.arch}
${c('cyan', 'Crypto:')} ${typeof globalThis.crypto !== 'undefined' ? c('green', '✓ Available') : c('yellow', '⚠ Polyfilled')}
${c('bold', 'CLI COMMANDS:')}
${c('cyan', 'edge-net')} Main CLI binary
${c('cyan', 'ruvector-edge')} Alias
${c('bold', 'CAPABILITIES:')}
${c('green', '✓')} Ed25519 digital signatures
${c('green', '✓')} X25519 key exchange
${c('green', '✓')} AES-GCM authenticated encryption
${c('green', '✓')} Argon2 password hashing
${c('green', '✓')} HNSW vector index (150x speedup)
${c('green', '✓')} Time Crystal coordination
${c('green', '✓')} DAG attention task orchestration
${c('green', '✓')} Neural Autonomous Organization
${c('green', '✓')} P2P gossip networking
`);
}
async function testWasm() {
printBanner();
console.log(`${c('bold', 'Testing WASM Module Loading...')}\n`);
// Setup polyfills
await setupPolyfills();
console.log(`${c('green', '✓')} Polyfills configured\n`);
try {
// Load Node.js WASM module
const { createRequire } = await import('module');
const require = createRequire(import.meta.url);
console.log(`${c('cyan', '1. Loading Node.js WASM module...')}`);
const wasm = require('./node/ruvector_edge_net.cjs');
console.log(` ${c('green', '✓')} Module loaded\n`);
console.log(`${c('cyan', '2. Available exports:')}`);
const exports = Object.keys(wasm).filter(k => !k.startsWith('__')).slice(0, 15);
exports.forEach(e => console.log(` ${c('dim', '•')} ${e}`));
console.log(` ${c('dim', '...')} and ${Object.keys(wasm).length - 15} more\n`);
console.log(`${c('cyan', '3. Testing components:')}`);
// Test ByzantineDetector
try {
const detector = new wasm.ByzantineDetector(0.5);
console.log(` ${c('green', '✓')} ByzantineDetector - created`);
} catch (e) {
console.log(` ${c('red', '✗')} ByzantineDetector - ${e.message}`);
}
// Test FederatedModel
try {
const model = new wasm.FederatedModel(100, 0.01, 0.9);
console.log(` ${c('green', '✓')} FederatedModel - created`);
} catch (e) {
console.log(` ${c('red', '✗')} FederatedModel - ${e.message}`);
}
// Test DifferentialPrivacy
try {
const dp = new wasm.DifferentialPrivacy(1.0, 0.001);
console.log(` ${c('green', '✓')} DifferentialPrivacy - created`);
} catch (e) {
console.log(` ${c('red', '✗')} DifferentialPrivacy - ${e.message}`);
}
// Test EdgeNetNode (may need web APIs)
try {
const node = new wasm.EdgeNetNode();
console.log(` ${c('green', '✓')} EdgeNetNode - created`);
console.log(` ${c('dim', 'Node ID:')} ${node.nodeId().substring(0, 32)}...`);
} catch (e) {
console.log(` ${c('yellow', '⚠')} EdgeNetNode - ${e.message.substring(0, 50)}...`);
console.log(` ${c('dim', 'Note: Some features require browser environment')}`);
}
console.log(`\n${c('green', '✓ WASM module test complete!')}`);
} catch (err) {
console.error(`${c('red', '✗ Failed to load WASM:')}\n`, err.message);
}
}
async function runBenchmark() {
printBanner();
console.log(`${c('bold', 'Running Performance Benchmarks...')}\n`);
await setupPolyfills();
try {
const { createRequire } = await import('module');
const require = createRequire(import.meta.url);
const wasm = require('./node/ruvector_edge_net.cjs');
console.log(`${c('green', '✓')} WASM module loaded\n`);
// Benchmark: ByzantineDetector
console.log(`${c('cyan', '1. Byzantine Detector')}`);
const bzStart = performance.now();
for (let i = 0; i < 10000; i++) {
const detector = new wasm.ByzantineDetector(0.5);
detector.getMaxMagnitude();
detector.free();
}
console.log(` ${c('dim', '10k create/query/free:')} ${(performance.now() - bzStart).toFixed(2)}ms`);
// Benchmark: FederatedModel
console.log(`\n${c('cyan', '2. Federated Model')}`);
const fmStart = performance.now();
for (let i = 0; i < 1000; i++) {
const model = new wasm.FederatedModel(100, 0.01, 0.9);
model.free();
}
console.log(` ${c('dim', '1k model create/free:')} ${(performance.now() - fmStart).toFixed(2)}ms`);
// Benchmark: DifferentialPrivacy
console.log(`\n${c('cyan', '3. Differential Privacy')}`);
const dpStart = performance.now();
for (let i = 0; i < 1000; i++) {
const dp = new wasm.DifferentialPrivacy(1.0, 0.001);
dp.getEpsilon();
dp.isEnabled();
dp.free();
}
console.log(` ${c('dim', '1k DP operations:')} ${(performance.now() - dpStart).toFixed(2)}ms`);
console.log(`\n${c('green', '✓ Benchmarks complete!')}`);
} catch (err) {
console.error(`${c('red', '✗ Benchmark failed:')}\n`, err.message);
}
}
async function startNode() {
printBanner();
console.log(`${c('bold', 'Starting Edge-Net Node...')}\n`);
await setupPolyfills();
try {
const { createRequire } = await import('module');
const require = createRequire(import.meta.url);
const wasm = require('./node/ruvector_edge_net.cjs');
// Try to create EdgeNetNode
let node;
try {
node = new wasm.EdgeNetNode();
console.log(`${c('green', '✓')} Full node started`);
console.log(`\n${c('bold', 'NODE INFO:')}`);
console.log(` ${c('cyan', 'ID:')} ${node.nodeId()}`);
console.log(` ${c('cyan', 'Balance:')} ${node.balance()} tokens`);
} catch (e) {
// Fall back to lightweight mode
console.log(`${c('yellow', '⚠')} Full node unavailable in CLI (needs browser)`);
console.log(`${c('green', '✓')} Starting in lightweight mode\n`);
const detector = new wasm.ByzantineDetector(0.5);
const dp = new wasm.DifferentialPrivacy(1.0, 0.001);
console.log(`${c('bold', 'LIGHTWEIGHT NODE:')}`);
console.log(` ${c('cyan', 'Byzantine Detector:')} Active`);
console.log(` ${c('cyan', 'Differential Privacy:')} ε=1.0, δ=0.001`);
console.log(` ${c('cyan', 'Mode:')} AI Components Only`);
}
console.log(` ${c('cyan', 'Status:')} ${c('green', 'Running')}`);
console.log(`\n${c('dim', 'Press Ctrl+C to stop.')}`);
// Keep running
process.on('SIGINT', () => {
console.log(`\n${c('yellow', 'Node stopped.')}`);
process.exit(0);
});
setInterval(() => {}, 1000);
} catch (err) {
console.error(`${c('red', '✗ Failed to start:')}\n`, err.message);
}
}
async function runDemo() {
printBanner();
console.log(`${c('bold', 'Running Interactive Demo...')}\n`);
await setupPolyfills();
const delay = (ms) => new Promise(r => setTimeout(r, ms));
console.log(`${c('cyan', 'Step 1:')} Loading WASM module...`);
await delay(200);
console.log(` ${c('green', '✓')} Module loaded (1.13 MB)\n`);
console.log(`${c('cyan', 'Step 2:')} Initializing AI components...`);
await delay(150);
console.log(` ${c('dim', '→')} Byzantine fault detector`);
console.log(` ${c('dim', '→')} Differential privacy engine`);
console.log(` ${c('dim', '→')} Federated learning model`);
console.log(` ${c('green', '✓')} AI layer ready\n`);
console.log(`${c('cyan', 'Step 3:')} Testing components...`);
await delay(100);
try {
const { createRequire } = await import('module');
const require = createRequire(import.meta.url);
const wasm = require('./node/ruvector_edge_net.cjs');
const detector = new wasm.ByzantineDetector(0.5);
const dp = new wasm.DifferentialPrivacy(1.0, 0.001);
const model = new wasm.FederatedModel(100, 0.01, 0.9);
console.log(` ${c('green', '✓')} ByzantineDetector: threshold=0.5`);
console.log(` ${c('green', '✓')} DifferentialPrivacy: ε=1.0, δ=0.001`);
console.log(` ${c('green', '✓')} FederatedModel: dim=100, lr=0.01\n`);
console.log(`${c('cyan', 'Step 4:')} Running simulation...`);
await delay(200);
// Simulate some operations using available methods
for (let i = 0; i < 5; i++) {
const maxMag = detector.getMaxMagnitude();
const epsilon = dp.getEpsilon();
const enabled = dp.isEnabled();
console.log(` ${c('dim', `Round ${i + 1}:`)} maxMag=${maxMag.toFixed(2)}, ε=${epsilon.toFixed(2)}, enabled=${enabled}`);
await delay(100);
}
} catch (e) {
console.log(` ${c('yellow', '⚠')} Some components unavailable: ${e.message}`);
}
console.log(`\n${c('bold', '─────────────────────────────────────────────────')}`);
console.log(`${c('green', '✓ Demo complete!')} WASM module is functional.\n`);
console.log(`${c('dim', 'For full P2P features, run in a browser environment.')}`);
}
async function runJoin() {
// Delegate to join.js
const { spawn } = await import('child_process');
const args = process.argv.slice(3);
const child = spawn('node', [join(__dirname, 'join.js'), ...args], {
stdio: 'inherit'
});
child.on('close', (code) => process.exit(code));
}
// Main
const command = process.argv[2] || 'help';
switch (command) {
case 'start':
startNode();
break;
case 'join':
runJoin();
break;
case 'benchmark':
case 'bench':
runBenchmark();
break;
case 'info':
showInfo();
break;
case 'demo':
runDemo();
break;
case 'test':
testWasm();
break;
case 'help':
case '--help':
case '-h':
default:
printHelp();
break;
}

View File

@@ -0,0 +1,739 @@
#!/usr/bin/env node
/**
* @ruvector/edge-net Contribution Daemon
*
* Real CPU contribution daemon that runs in the background and earns QDAG credits.
* Connects to the relay server and sends contribution_credit messages periodically.
*
* Usage:
* npx @ruvector/edge-net contribute # Start daemon (foreground)
* npx @ruvector/edge-net contribute --daemon # Start daemon (background)
* npx @ruvector/edge-net contribute --stop # Stop daemon
* npx @ruvector/edge-net contribute --status # Show daemon status
* npx @ruvector/edge-net contribute --cpu 50 # Set CPU limit (default: 50%)
* npx @ruvector/edge-net contribute --key <pubkey> # Use specific public key
*/
import { readFileSync, writeFileSync, existsSync, mkdirSync, unlinkSync } from 'fs';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
import { webcrypto } from 'crypto';
import { performance } from 'perf_hooks';
import { homedir, cpus } from 'os';
import { spawn } from 'child_process';
import WebSocket from 'ws';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// Relay server URL
const RELAY_URL = process.env.RELAY_URL || 'wss://edge-net-relay-875130704813.us-central1.run.app';
// Contribution settings
const DEFAULT_CPU_LIMIT = 50; // Default 50% CPU
const CONTRIBUTION_INTERVAL = 30000; // Report every 30 seconds
const RECONNECT_DELAY = 5000; // Reconnect after 5 seconds on disconnect
const HEARTBEAT_INTERVAL = 15000; // Heartbeat every 15 seconds
// ANSI colors
const c = {
reset: '\x1b[0m',
bold: '\x1b[1m',
dim: '\x1b[2m',
cyan: '\x1b[36m',
green: '\x1b[32m',
yellow: '\x1b[33m',
red: '\x1b[31m',
magenta: '\x1b[35m',
};
// Config directory
function getConfigDir() {
const dir = join(homedir(), '.ruvector');
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
return dir;
}
function getPidFile() {
return join(getConfigDir(), 'contribute-daemon.pid');
}
function getLogFile() {
return join(getConfigDir(), 'contribute-daemon.log');
}
function getStateFile() {
return join(getConfigDir(), 'contribute-state.json');
}
// Parse command line arguments
function parseArgs(args) {
const opts = {
daemon: false,
stop: false,
status: false,
cpu: DEFAULT_CPU_LIMIT,
key: null,
site: 'cli-contributor',
help: false,
};
for (let i = 0; i < args.length; i++) {
const arg = args[i];
switch (arg) {
case '--daemon':
case '-d':
opts.daemon = true;
break;
case '--stop':
opts.stop = true;
break;
case '--status':
opts.status = true;
break;
case '--cpu':
opts.cpu = parseInt(args[++i]) || DEFAULT_CPU_LIMIT;
break;
case '--key':
opts.key = args[++i];
break;
case '--site':
opts.site = args[++i];
break;
case '--help':
case '-h':
opts.help = true;
break;
}
}
return opts;
}
// Load or generate identity
async function loadIdentity(opts) {
const identitiesDir = join(getConfigDir(), 'identities');
if (!existsSync(identitiesDir)) mkdirSync(identitiesDir, { recursive: true });
const metaPath = join(identitiesDir, `${opts.site}.meta.json`);
// If --key is provided, use it directly
if (opts.key) {
return {
publicKey: opts.key,
shortId: `pi:${opts.key.slice(0, 16)}`,
siteId: opts.site,
};
}
// Try to load existing identity
if (existsSync(metaPath)) {
const meta = JSON.parse(readFileSync(metaPath, 'utf-8'));
return {
publicKey: meta.publicKey,
shortId: meta.shortId,
siteId: meta.siteId,
};
}
// Generate new identity using WASM
const { createRequire } = await import('module');
const require = createRequire(import.meta.url);
// Setup polyfills
if (typeof globalThis.crypto === 'undefined') {
globalThis.crypto = webcrypto;
}
console.log(`${c.dim}Generating new identity...${c.reset}`);
const wasm = require('./node/ruvector_edge_net.cjs');
const piKey = new wasm.PiKey();
const publicKey = Array.from(piKey.getPublicKey())
.map(b => b.toString(16).padStart(2, '0'))
.join('');
const meta = {
version: 1,
siteId: opts.site,
shortId: piKey.getShortId(),
publicKey,
createdAt: new Date().toISOString(),
lastUsed: new Date().toISOString(),
totalSessions: 1,
totalContributions: 0,
};
writeFileSync(metaPath, JSON.stringify(meta, null, 2));
piKey.free();
return {
publicKey: meta.publicKey,
shortId: meta.shortId,
siteId: meta.siteId,
};
}
// Load daemon state
function loadState() {
const stateFile = getStateFile();
if (existsSync(stateFile)) {
return JSON.parse(readFileSync(stateFile, 'utf-8'));
}
return {
totalCredits: 0,
totalContributions: 0,
totalSeconds: 0,
startTime: null,
lastSync: null,
};
}
// Save daemon state
function saveState(state) {
writeFileSync(getStateFile(), JSON.stringify(state, null, 2));
}
// Measure real CPU usage
function measureCpuUsage(durationMs = 1000) {
return new Promise((resolve) => {
const startCpus = cpus();
const startTotal = startCpus.reduce((acc, cpu) => {
const times = cpu.times;
return acc + times.user + times.nice + times.sys + times.idle + times.irq;
}, 0);
const startIdle = startCpus.reduce((acc, cpu) => acc + cpu.times.idle, 0);
setTimeout(() => {
const endCpus = cpus();
const endTotal = endCpus.reduce((acc, cpu) => {
const times = cpu.times;
return acc + times.user + times.nice + times.sys + times.idle + times.irq;
}, 0);
const endIdle = endCpus.reduce((acc, cpu) => acc + cpu.times.idle, 0);
const totalDiff = endTotal - startTotal;
const idleDiff = endIdle - startIdle;
const cpuUsage = totalDiff > 0 ? ((totalDiff - idleDiff) / totalDiff) * 100 : 0;
resolve(Math.min(100, Math.max(0, cpuUsage)));
}, durationMs);
});
}
// Real CPU work (compute hashes)
function doRealWork(cpuLimit, durationMs) {
return new Promise((resolve) => {
const startTime = Date.now();
const workInterval = 10; // Work in 10ms chunks
const workRatio = cpuLimit / 100;
let computeUnits = 0;
const doWork = () => {
const now = Date.now();
if (now - startTime >= durationMs) {
resolve(computeUnits);
return;
}
// Do actual CPU work (hash computation)
const workStart = Date.now();
const workTime = workInterval * workRatio;
while (Date.now() - workStart < workTime) {
// Real cryptographic work
let data = new Uint8Array(64);
for (let i = 0; i < 64; i++) {
data[i] = (i * 7 + computeUnits) & 0xff;
}
// Simple hash-like operation (real CPU work)
for (let j = 0; j < 100; j++) {
let hash = 0;
for (let i = 0; i < data.length; i++) {
hash = ((hash << 5) - hash + data[i]) | 0;
}
data[0] = hash & 0xff;
}
computeUnits++;
}
// Rest period to respect CPU limit
const restTime = workInterval * (1 - workRatio);
setTimeout(doWork, restTime);
};
doWork();
});
}
// Contribution daemon class
class ContributionDaemon {
constructor(identity, cpuLimit) {
this.identity = identity;
this.cpuLimit = cpuLimit;
this.ws = null;
this.state = loadState();
this.isRunning = false;
this.nodeId = `node-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
this.contributionTimer = null;
this.heartbeatTimer = null;
this.reconnectTimer = null;
this.sessionStart = Date.now();
this.sessionCredits = 0;
this.sessionContributions = 0;
}
async start() {
this.isRunning = true;
this.state.startTime = Date.now();
saveState(this.state);
console.log(`\n${c.cyan}${c.bold}Edge-Net Contribution Daemon${c.reset}`);
console.log(`${c.dim}Real CPU contribution to earn QDAG credits${c.reset}\n`);
console.log(`${c.bold}Configuration:${c.reset}`);
console.log(` ${c.cyan}Identity:${c.reset} ${this.identity.shortId}`);
console.log(` ${c.cyan}Public Key:${c.reset} ${this.identity.publicKey.slice(0, 16)}...`);
console.log(` ${c.cyan}CPU Limit:${c.reset} ${this.cpuLimit}%`);
console.log(` ${c.cyan}Relay:${c.reset} ${RELAY_URL}`);
console.log(` ${c.cyan}Interval:${c.reset} ${CONTRIBUTION_INTERVAL / 1000}s\n`);
await this.connect();
// Handle shutdown
process.on('SIGINT', () => this.stop('SIGINT'));
process.on('SIGTERM', () => this.stop('SIGTERM'));
}
async connect() {
if (!this.isRunning) return;
console.log(`${c.dim}Connecting to relay...${c.reset}`);
try {
this.ws = new WebSocket(RELAY_URL);
this.ws.on('open', () => {
console.log(`${c.green}Connected to relay${c.reset}`);
// Register with relay
this.send({
type: 'register',
nodeId: this.nodeId,
publicKey: this.identity.publicKey,
capabilities: ['compute', 'cli-daemon'],
version: '1.0.0',
});
// Start heartbeat
this.startHeartbeat();
// Request initial balance from QDAG
setTimeout(() => {
this.send({
type: 'ledger_sync',
nodeId: this.nodeId,
publicKey: this.identity.publicKey,
});
}, 500);
});
this.ws.on('message', (data) => this.handleMessage(data.toString()));
this.ws.on('close', () => {
console.log(`${c.yellow}Disconnected from relay${c.reset}`);
this.stopHeartbeat();
this.stopContributing();
this.scheduleReconnect();
});
this.ws.on('error', (err) => {
console.log(`${c.red}WebSocket error: ${err.message}${c.reset}`);
});
} catch (err) {
console.log(`${c.red}Connection failed: ${err.message}${c.reset}`);
this.scheduleReconnect();
}
}
handleMessage(data) {
try {
const msg = JSON.parse(data);
switch (msg.type) {
case 'welcome':
console.log(`${c.green}Registered as ${msg.nodeId}${c.reset}`);
console.log(`${c.dim}Network: ${msg.networkState?.activeNodes || 0} active nodes${c.reset}`);
this.startContributing();
break;
case 'ledger_sync_response':
const earned = Number(msg.ledger?.earned || 0) / 1e9;
const spent = Number(msg.ledger?.spent || 0) / 1e9;
const available = earned - spent;
console.log(`${c.cyan}QDAG Balance: ${available.toFixed(4)} rUv${c.reset} (earned: ${earned.toFixed(4)}, spent: ${spent.toFixed(4)})`);
this.state.totalCredits = earned;
saveState(this.state);
break;
case 'contribution_credit_success':
const credited = msg.credited || 0;
this.sessionCredits += credited;
this.sessionContributions++;
this.state.totalCredits = Number(msg.balance?.earned || 0) / 1e9;
this.state.totalContributions++;
this.state.lastSync = Date.now();
saveState(this.state);
const balance = Number(msg.balance?.available || 0) / 1e9;
console.log(`${c.green}+${credited.toFixed(4)} rUv${c.reset} | Balance: ${balance.toFixed(4)} rUv | Total: ${this.state.totalContributions} contributions`);
break;
case 'contribution_credit_error':
console.log(`${c.yellow}Contribution rejected: ${msg.error}${c.reset}`);
break;
case 'time_crystal_sync':
// Silently handle time crystal sync
break;
case 'heartbeat_ack':
// Heartbeat acknowledged
break;
case 'error':
console.log(`${c.red}Relay error: ${msg.message}${c.reset}`);
break;
default:
// Ignore unknown messages
}
} catch (err) {
console.log(`${c.red}Error parsing message: ${err.message}${c.reset}`);
}
}
send(msg) {
if (this.ws?.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify(msg));
}
}
startHeartbeat() {
this.stopHeartbeat();
this.heartbeatTimer = setInterval(() => {
this.send({ type: 'heartbeat' });
}, HEARTBEAT_INTERVAL);
}
stopHeartbeat() {
if (this.heartbeatTimer) {
clearInterval(this.heartbeatTimer);
this.heartbeatTimer = null;
}
}
startContributing() {
this.stopContributing();
console.log(`${c.cyan}Starting contribution loop (CPU: ${this.cpuLimit}%)${c.reset}\n`);
// Immediate first contribution
this.contribute();
// Then continue every interval
this.contributionTimer = setInterval(() => {
this.contribute();
}, CONTRIBUTION_INTERVAL);
}
stopContributing() {
if (this.contributionTimer) {
clearInterval(this.contributionTimer);
this.contributionTimer = null;
}
}
async contribute() {
const startTime = Date.now();
// Do real CPU work for 5 seconds
console.log(`${c.dim}[${new Date().toLocaleTimeString()}] Working...${c.reset}`);
await doRealWork(this.cpuLimit, 5000);
// Measure actual CPU usage
const cpuUsage = await measureCpuUsage(1000);
const contributionSeconds = 30; // Claiming 30 seconds since last report
const effectiveCpu = Math.min(this.cpuLimit, cpuUsage);
// Send contribution credit request
this.send({
type: 'contribution_credit',
nodeId: this.nodeId,
publicKey: this.identity.publicKey,
contributionSeconds,
cpuUsage: Math.round(effectiveCpu),
timestamp: Date.now(),
});
this.state.totalSeconds += contributionSeconds;
saveState(this.state);
}
scheduleReconnect() {
if (!this.isRunning) return;
if (this.reconnectTimer) return;
console.log(`${c.dim}Reconnecting in ${RECONNECT_DELAY / 1000}s...${c.reset}`);
this.reconnectTimer = setTimeout(() => {
this.reconnectTimer = null;
this.connect();
}, RECONNECT_DELAY);
}
stop(signal = 'unknown') {
console.log(`\n${c.yellow}Stopping daemon (${signal})...${c.reset}`);
this.isRunning = false;
this.stopContributing();
this.stopHeartbeat();
if (this.reconnectTimer) {
clearTimeout(this.reconnectTimer);
this.reconnectTimer = null;
}
if (this.ws) {
this.ws.close();
this.ws = null;
}
// Save final state
saveState(this.state);
// Print session summary
const sessionDuration = (Date.now() - this.sessionStart) / 1000;
console.log(`\n${c.bold}Session Summary:${c.reset}`);
console.log(` ${c.cyan}Duration:${c.reset} ${Math.round(sessionDuration)}s`);
console.log(` ${c.cyan}Contributions:${c.reset} ${this.sessionContributions}`);
console.log(` ${c.cyan}Credits:${c.reset} ${this.sessionCredits.toFixed(4)} rUv`);
console.log(` ${c.cyan}Total Earned:${c.reset} ${this.state.totalCredits.toFixed(4)} rUv\n`);
// Remove PID file
const pidFile = getPidFile();
if (existsSync(pidFile)) {
unlinkSync(pidFile);
}
process.exit(0);
}
}
// Show daemon status
async function showStatus(opts) {
const pidFile = getPidFile();
const state = loadState();
const identity = await loadIdentity(opts);
console.log(`\n${c.cyan}${c.bold}Edge-Net Contribution Daemon Status${c.reset}\n`);
// Check if daemon is running
if (existsSync(pidFile)) {
const pid = parseInt(readFileSync(pidFile, 'utf-8').trim());
try {
process.kill(pid, 0); // Check if process exists
console.log(`${c.green}Status: Running${c.reset} (PID: ${pid})`);
} catch {
console.log(`${c.yellow}Status: Stale PID file${c.reset} (process not found)`);
unlinkSync(pidFile);
}
} else {
console.log(`${c.dim}Status: Not running${c.reset}`);
}
console.log(`\n${c.bold}Identity:${c.reset}`);
console.log(` ${c.cyan}Short ID:${c.reset} ${identity.shortId}`);
console.log(` ${c.cyan}Public Key:${c.reset} ${identity.publicKey.slice(0, 32)}...`);
console.log(`\n${c.bold}Statistics:${c.reset}`);
console.log(` ${c.cyan}Total Credits:${c.reset} ${state.totalCredits?.toFixed(4) || 0} rUv`);
console.log(` ${c.cyan}Total Contributions:${c.reset} ${state.totalContributions || 0}`);
console.log(` ${c.cyan}Total Time:${c.reset} ${state.totalSeconds || 0}s`);
if (state.lastSync) {
const lastSync = new Date(state.lastSync);
console.log(` ${c.cyan}Last Sync:${c.reset} ${lastSync.toLocaleString()}`);
}
console.log(`\n${c.bold}Files:${c.reset}`);
console.log(` ${c.dim}State:${c.reset} ${getStateFile()}`);
console.log(` ${c.dim}Log:${c.reset} ${getLogFile()}`);
console.log(` ${c.dim}PID:${c.reset} ${pidFile}\n`);
}
// Stop daemon
function stopDaemon() {
const pidFile = getPidFile();
if (!existsSync(pidFile)) {
console.log(`${c.yellow}Daemon is not running${c.reset}`);
return;
}
const pid = parseInt(readFileSync(pidFile, 'utf-8').trim());
try {
process.kill(pid, 'SIGTERM');
console.log(`${c.green}Sent SIGTERM to daemon (PID: ${pid})${c.reset}`);
// Wait a bit and check if it stopped
setTimeout(() => {
try {
process.kill(pid, 0);
console.log(`${c.yellow}Daemon still running, sending SIGKILL...${c.reset}`);
process.kill(pid, 'SIGKILL');
} catch {
console.log(`${c.green}Daemon stopped${c.reset}`);
}
if (existsSync(pidFile)) {
unlinkSync(pidFile);
}
}, 2000);
} catch {
console.log(`${c.yellow}Process not found, cleaning up...${c.reset}`);
unlinkSync(pidFile);
}
}
// Start daemon in background
function startDaemonBackground(args) {
const pidFile = getPidFile();
const logFile = getLogFile();
if (existsSync(pidFile)) {
const pid = parseInt(readFileSync(pidFile, 'utf-8').trim());
try {
process.kill(pid, 0);
console.log(`${c.yellow}Daemon already running (PID: ${pid})${c.reset}`);
console.log(`${c.dim}Use --stop to stop it first${c.reset}`);
return;
} catch {
unlinkSync(pidFile);
}
}
console.log(`${c.cyan}Starting daemon in background...${c.reset}`);
const out = require('fs').openSync(logFile, 'a');
const err = require('fs').openSync(logFile, 'a');
// Remove --daemon flag and respawn
const filteredArgs = args.filter(a => a !== '--daemon' && a !== '-d');
const child = spawn(process.execPath, [__filename, ...filteredArgs], {
detached: true,
stdio: ['ignore', out, err],
});
writeFileSync(pidFile, String(child.pid));
child.unref();
console.log(`${c.green}Daemon started (PID: ${child.pid})${c.reset}`);
console.log(`${c.dim}Log file: ${logFile}${c.reset}`);
console.log(`${c.dim}Use --status to check status${c.reset}`);
console.log(`${c.dim}Use --stop to stop daemon${c.reset}`);
}
// Print help
function printHelp() {
console.log(`
${c.cyan}${c.bold}Edge-Net Contribution Daemon${c.reset}
${c.dim}Contribute CPU to earn QDAG credits${c.reset}
${c.bold}USAGE:${c.reset}
npx @ruvector/edge-net contribute [options]
${c.bold}OPTIONS:${c.reset}
${c.yellow}--daemon, -d${c.reset} Run in background (detached)
${c.yellow}--stop${c.reset} Stop running daemon
${c.yellow}--status${c.reset} Show daemon status
${c.yellow}--cpu <percent>${c.reset} CPU usage limit (default: 50)
${c.yellow}--key <pubkey>${c.reset} Use specific public key
${c.yellow}--site <id>${c.reset} Site identifier (default: cli-contributor)
${c.yellow}--help, -h${c.reset} Show this help
${c.bold}EXAMPLES:${c.reset}
${c.dim}# Start contributing in foreground${c.reset}
$ npx @ruvector/edge-net contribute
${c.dim}# Start as background daemon with 30% CPU${c.reset}
$ npx @ruvector/edge-net contribute --daemon --cpu 30
${c.dim}# Use specific public key${c.reset}
$ npx @ruvector/edge-net contribute --key 38a3bcd1732fe04c...
${c.dim}# Check status${c.reset}
$ npx @ruvector/edge-net contribute --status
${c.dim}# Stop daemon${c.reset}
$ npx @ruvector/edge-net contribute --stop
${c.bold}HOW IT WORKS:${c.reset}
1. Daemon connects to Edge-Net relay server
2. Every 30 seconds, does real CPU work
3. Reports contribution to relay
4. Relay credits QDAG (Firestore) with earned rUv
5. Credits persist and sync across all devices
${c.bold}CREDIT RATE:${c.reset}
Base rate: ~0.047 rUv/second of contribution
Max rate: ~0.05 rUv/second (180 rUv/hour max)
Formula: contributionSeconds * 0.047 * (cpuUsage / 100)
`);
}
// Main entry point
async function main() {
const args = process.argv.slice(2);
// Filter out 'contribute' if passed
const filteredArgs = args.filter(a => a !== 'contribute');
const opts = parseArgs(filteredArgs);
if (opts.help) {
printHelp();
return;
}
if (opts.status) {
await showStatus(opts);
return;
}
if (opts.stop) {
stopDaemon();
return;
}
if (opts.daemon) {
startDaemonBackground(filteredArgs);
return;
}
// Start daemon in foreground
try {
const identity = await loadIdentity(opts);
const daemon = new ContributionDaemon(identity, opts.cpu);
await daemon.start();
} catch (err) {
console.error(`${c.red}Error: ${err.message}${c.reset}`);
process.exit(1);
}
}
main().catch(err => {
console.error(`${c.red}Fatal error: ${err.message}${c.reset}`);
process.exit(1);
});

View File

@@ -0,0 +1,479 @@
#!/usr/bin/env node
/**
* Edge-Net Contributor Flow Validation
*
* Tests the complete CONTRIBUTOR FLOW:
* 1. Identity creation/restoration
* 2. Contribution tracking (local + QDAG)
* 3. Credit earning and persistence
* 4. WebSocket relay communication
* 5. Dashboard data flow
* 6. Multi-device sync capability
*/
const { promises: fs } = require('fs');
const { homedir } = require('os');
const { join } = require('path');
const WebSocket = require('ws');
// ANSI colors for output
const colors = {
reset: '\x1b[0m',
bold: '\x1b[1m',
green: '\x1b[32m',
red: '\x1b[31m',
yellow: '\x1b[33m',
cyan: '\x1b[36m',
dim: '\x1b[2m',
};
const c = (color, text) => `${colors[color]}${text}${colors.reset}`;
// Configuration
const CONFIG = {
relayUrl: 'wss://edge-net-relay-875130704813.us-central1.run.app',
dashboardUrl: 'https://edge-net-dashboard-875130704813.us-central1.run.app',
identityPath: join(homedir(), '.ruvector', 'identities', 'edge-contributor.meta.json'),
qdagPath: join(homedir(), '.ruvector', 'network', 'qdag.json'),
historyPath: join(homedir(), '.ruvector', 'contributions', 'edge-contributor.history.json'),
};
class ContributorFlowValidator {
constructor() {
this.results = {
passed: [],
failed: [],
warnings: [],
};
}
async run() {
console.log(`\n${c('bold', '═══════════════════════════════════════════════════')}`);
console.log(c('cyan', ' Edge-Net CONTRIBUTOR FLOW Validation'));
console.log(`${c('bold', '═══════════════════════════════════════════════════')}\n`);
await this.testIdentityPersistence();
await this.testContributionTracking();
await this.testQDAGPersistence();
await this.testCreditConsistency();
await this.testRelayConnection();
await this.testCreditEarningFlow();
await this.testDashboardAccess();
await this.testMultiDeviceSync();
this.printResults();
}
async testIdentityPersistence() {
console.log(`${c('bold', '1. Testing Identity Persistence...')}`);
try {
const exists = await fs.access(CONFIG.identityPath).then(() => true).catch(() => false);
if (!exists) {
this.fail('Identity file not found. Run: node join.js --generate');
return;
}
const meta = JSON.parse(await fs.readFile(CONFIG.identityPath, 'utf-8'));
// Validate identity structure
if (!meta.shortId || !meta.publicKey || !meta.genesisFingerprint) {
this.fail('Identity structure invalid');
return;
}
if (!meta.shortId.startsWith('π:')) {
this.fail('Invalid Pi-Key format');
return;
}
console.log(` ${c('green', '✓')} Identity loaded: ${meta.shortId}`);
console.log(` ${c('green', '✓')} Member since: ${new Date(meta.createdAt).toLocaleDateString()}`);
console.log(` ${c('green', '✓')} Total sessions: ${meta.totalSessions}`);
this.pass('Identity Persistence', {
shortId: meta.shortId,
sessions: meta.totalSessions,
contributions: meta.totalContributions,
});
} catch (err) {
this.fail('Identity Persistence', err.message);
}
}
async testContributionTracking() {
console.log(`\n${c('bold', '2. Testing Contribution Tracking...')}`);
try {
const exists = await fs.access(CONFIG.historyPath).then(() => true).catch(() => false);
if (!exists) {
this.warn('No contribution history yet. Run: node join.js');
return;
}
const history = JSON.parse(await fs.readFile(CONFIG.historyPath, 'utf-8'));
console.log(` ${c('green', '✓')} Sessions tracked: ${history.sessions.length}`);
console.log(` ${c('green', '✓')} Contributions recorded: ${history.contributions.length}`);
console.log(` ${c('green', '✓')} Milestones: ${history.milestones.length}`);
// Validate contribution structure
if (history.contributions.length > 0) {
const lastContrib = history.contributions[history.contributions.length - 1];
if (!lastContrib.computeUnits || !lastContrib.credits) {
this.fail('Invalid contribution structure');
return;
}
console.log(` ${c('dim', 'Last contribution:')} ${lastContrib.computeUnits} compute units = ${lastContrib.credits} credits`);
}
this.pass('Contribution Tracking', {
sessions: history.sessions.length,
contributions: history.contributions.length,
});
} catch (err) {
this.fail('Contribution Tracking', err.message);
}
}
async testQDAGPersistence() {
console.log(`\n${c('bold', '3. Testing QDAG Persistence...')}`);
try {
const exists = await fs.access(CONFIG.qdagPath).then(() => true).catch(() => false);
if (!exists) {
this.warn('QDAG not initialized. Start contributing: node join.js');
return;
}
const qdag = JSON.parse(await fs.readFile(CONFIG.qdagPath, 'utf-8'));
console.log(` ${c('green', '✓')} QDAG nodes: ${qdag.nodes.length}`);
console.log(` ${c('green', '✓')} Confirmed: ${qdag.confirmed.length}`);
console.log(` ${c('green', '✓')} Tips: ${qdag.tips.length}`);
// Validate QDAG structure (genesis is optional, savedAt is metadata)
if (!qdag.nodes || !qdag.confirmed || !qdag.tips) {
this.fail('Invalid QDAG structure');
return;
}
// Count contributions
const contributions = qdag.nodes.filter(n => n.type === 'contribution');
const totalCredits = contributions.reduce((sum, c) => sum + (c.credits || 0), 0);
console.log(` ${c('green', '✓')} Total contributions: ${contributions.length}`);
console.log(` ${c('green', '✓')} Total credits in ledger: ${totalCredits}`);
this.pass('QDAG Persistence', {
nodes: qdag.nodes.length,
contributions: contributions.length,
credits: totalCredits,
});
} catch (err) {
this.fail('QDAG Persistence', err.message);
}
}
async testCreditConsistency() {
console.log(`\n${c('bold', '4. Testing Credit Consistency...')}`);
try {
const meta = JSON.parse(await fs.readFile(CONFIG.identityPath, 'utf-8'));
const qdag = JSON.parse(await fs.readFile(CONFIG.qdagPath, 'utf-8'));
const history = JSON.parse(await fs.readFile(CONFIG.historyPath, 'utf-8'));
// Count credits from different sources
const metaContributions = meta.totalContributions;
const historyContributions = history.contributions.length;
const qdagContributions = qdag.nodes.filter(n =>
n.type === 'contribution' && n.contributor === meta.shortId
).length;
const historyCredits = history.contributions.reduce((sum, c) => sum + (c.credits || 0), 0);
const qdagCredits = qdag.nodes
.filter(n => n.type === 'contribution' && n.contributor === meta.shortId)
.reduce((sum, c) => sum + (c.credits || 0), 0);
console.log(` ${c('cyan', 'Meta contributions:')} ${metaContributions}`);
console.log(` ${c('cyan', 'History contributions:')} ${historyContributions}`);
console.log(` ${c('cyan', 'QDAG contributions:')} ${qdagContributions}`);
console.log(` ${c('cyan', 'History credits:')} ${historyCredits}`);
console.log(` ${c('cyan', 'QDAG credits:')} ${qdagCredits}`);
// Verify consistency
if (metaContributions !== historyContributions) {
this.warn(`Meta/History mismatch: ${metaContributions} vs ${historyContributions}`);
}
if (historyCredits !== qdagCredits) {
this.warn(`History/QDAG credit mismatch: ${historyCredits} vs ${qdagCredits}`);
}
if (metaContributions === historyContributions && historyCredits === qdagCredits) {
console.log(` ${c('green', '✓')} Perfect consistency across all storage layers`);
this.pass('Credit Consistency', { credits: qdagCredits });
} else {
console.log(` ${c('yellow', '⚠')} Minor inconsistency (expected during active contribution)`);
this.pass('Credit Consistency (with warnings)', { credits: qdagCredits });
}
} catch (err) {
this.fail('Credit Consistency', err.message);
}
}
async testRelayConnection() {
console.log(`\n${c('bold', '5. Testing Relay Connection...')}`);
return new Promise((resolve) => {
const ws = new WebSocket(CONFIG.relayUrl);
let connected = false;
const timeout = setTimeout(() => {
if (!connected) {
ws.close();
this.fail('Relay Connection', 'Connection timeout');
resolve();
}
}, 10000);
ws.on('open', () => {
connected = true;
console.log(` ${c('green', '✓')} WebSocket connected to relay`);
// Send registration
ws.send(JSON.stringify({
type: 'register',
contributor: 'validation-test',
capabilities: { cpu: 4 }
}));
});
ws.on('message', (data) => {
const msg = JSON.parse(data.toString());
if (msg.type === 'welcome') {
console.log(` ${c('green', '✓')} Received welcome message`);
console.log(` ${c('dim', 'Network state:')} ${msg.networkState.totalNodes} nodes, ${msg.networkState.activeNodes} active`);
}
if (msg.type === 'node_joined') {
console.log(` ${c('green', '✓')} Node registered in network`);
}
if (msg.type === 'time_crystal_sync') {
console.log(` ${c('green', '✓')} Time crystal sync received (phase: ${msg.phase.toFixed(2)})`);
clearTimeout(timeout);
ws.close();
this.pass('Relay Connection', { url: CONFIG.relayUrl });
resolve();
}
});
ws.on('error', (err) => {
clearTimeout(timeout);
this.fail('Relay Connection', err.message);
resolve();
});
});
}
async testCreditEarningFlow() {
console.log(`\n${c('bold', '6. Testing Credit Earning Flow...')}`);
return new Promise((resolve) => {
const ws = new WebSocket(CONFIG.relayUrl);
let registered = false;
const timeout = setTimeout(() => {
ws.close();
this.fail('Credit Earning Flow', 'Timeout waiting for credit confirmation');
resolve();
}, 15000);
ws.on('open', () => {
ws.send(JSON.stringify({
type: 'register',
contributor: 'credit-test-validator',
capabilities: { cpu: 8, memory: 16384 }
}));
console.log(` ${c('cyan', '→')} Sent registration`);
});
ws.on('message', (data) => {
const msg = JSON.parse(data.toString());
if (msg.type === 'welcome' && !registered) {
registered = true;
// Send credit_earned message
setTimeout(() => {
ws.send(JSON.stringify({
type: 'credit_earned',
contributor: 'credit-test-validator',
taskId: 'validation-task-' + Date.now(),
creditsEarned: 5,
computeUnits: 500,
timestamp: Date.now()
}));
console.log(` ${c('cyan', '→')} Sent credit_earned message`);
}, 500);
}
if (msg.type === 'task_assigned') {
console.log(` ${c('green', '✓')} Received task assignment: ${msg.task.id}`);
}
// Look for any acknowledgment
if (registered && (msg.type === 'time_crystal_sync' || msg.type === 'network_update')) {
console.log(` ${c('green', '✓')} Network processing credit update`);
clearTimeout(timeout);
ws.close();
this.pass('Credit Earning Flow');
resolve();
}
});
ws.on('error', (err) => {
clearTimeout(timeout);
this.fail('Credit Earning Flow', err.message);
resolve();
});
});
}
async testDashboardAccess() {
console.log(`\n${c('bold', '7. Testing Dashboard Access...')}`);
try {
const https = require('https');
await new Promise((resolve, reject) => {
https.get(CONFIG.dashboardUrl, (res) => {
if (res.statusCode === 200) {
console.log(` ${c('green', '✓')} Dashboard accessible (HTTP ${res.statusCode})`);
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => {
if (data.includes('Edge-Net Dashboard')) {
console.log(` ${c('green', '✓')} Dashboard title found`);
this.pass('Dashboard Access', { url: CONFIG.dashboardUrl });
} else {
this.warn('Dashboard accessible but content unexpected');
this.pass('Dashboard Access (with warnings)');
}
resolve();
});
} else {
this.fail('Dashboard Access', `HTTP ${res.statusCode}`);
resolve();
}
}).on('error', (err) => {
this.fail('Dashboard Access', err.message);
resolve();
});
});
} catch (err) {
this.fail('Dashboard Access', err.message);
}
}
async testMultiDeviceSync() {
console.log(`\n${c('bold', '8. Testing Multi-Device Sync Capability...')}`);
try {
const meta = JSON.parse(await fs.readFile(CONFIG.identityPath, 'utf-8'));
const qdag = JSON.parse(await fs.readFile(CONFIG.qdagPath, 'utf-8'));
const myCredits = qdag.nodes
.filter(n => n.type === 'contribution' && n.contributor === meta.shortId)
.reduce((sum, c) => sum + (c.credits || 0), 0);
console.log(` ${c('green', '✓')} Identity exportable: ${meta.shortId}`);
console.log(` ${c('green', '✓')} QDAG contains contributor records: ${myCredits} credits`);
console.log(` ${c('green', '✓')} Sync protocol: Export identity → Import on Device 2 → Credits persist`);
console.log(` ${c('dim', 'Export command:')} node join.js --export backup.enc --password <secret>`);
console.log(` ${c('dim', 'Import command:')} node join.js --import backup.enc --password <secret>`);
this.pass('Multi-Device Sync Capability', {
exportable: true,
credits: myCredits,
});
} catch (err) {
this.fail('Multi-Device Sync Capability', err.message);
}
}
pass(test, details = {}) {
this.results.passed.push({ test, details });
}
fail(test, reason = '') {
this.results.failed.push({ test, reason });
}
warn(message) {
this.results.warnings.push(message);
}
printResults() {
console.log(`\n${c('bold', '═══════════════════════════════════════════════════')}`);
console.log(c('bold', ' VALIDATION RESULTS'));
console.log(`${c('bold', '═══════════════════════════════════════════════════')}\n`);
const total = this.results.passed.length + this.results.failed.length;
const passRate = total > 0 ? (this.results.passed.length / total * 100).toFixed(1) : 0;
console.log(`${c('green', '✓ PASSED:')} ${this.results.passed.length}`);
console.log(`${c('red', '✗ FAILED:')} ${this.results.failed.length}`);
console.log(`${c('yellow', '⚠ WARNINGS:')} ${this.results.warnings.length}`);
console.log(`${c('cyan', 'PASS RATE:')} ${passRate}%\n`);
if (this.results.failed.length > 0) {
console.log(c('red', 'FAILED TESTS:'));
this.results.failed.forEach(f => {
console.log(` ${c('red', '✗')} ${f.test}${f.reason ? ': ' + f.reason : ''}`);
});
console.log('');
}
if (this.results.warnings.length > 0) {
console.log(c('yellow', 'WARNINGS:'));
this.results.warnings.forEach(w => {
console.log(` ${c('yellow', '⚠')} ${w}`);
});
console.log('');
}
// Final verdict
console.log(`${c('bold', '═══════════════════════════════════════════════════')}`);
if (this.results.failed.length === 0) {
console.log(c('green', ' ✓ CONTRIBUTOR FLOW: 100% FUNCTIONAL'));
console.log(c('dim', ' All systems operational with secure QDAG persistence'));
} else {
console.log(c('red', ' ✗ CONTRIBUTOR FLOW: ISSUES DETECTED'));
console.log(c('dim', ` ${this.results.failed.length} test(s) failed - review above for details`));
}
console.log(`${c('bold', '═══════════════════════════════════════════════════')}\n`);
process.exit(this.results.failed.length > 0 ? 1 : 0);
}
}
// Run validation
const validator = new ContributorFlowValidator();
validator.run().catch(err => {
console.error(`\n${c('red', 'Fatal error:')} ${err.message}\n`);
process.exit(1);
});

View File

@@ -0,0 +1,631 @@
/**
* @ruvector/edge-net Credit System MVP
*
* Simple credit accounting for distributed task execution:
* - Nodes earn credits when executing tasks for others
* - Nodes spend credits when submitting tasks
* - Credits stored in CRDT ledger for conflict-free replication
* - Persisted to Firebase for cross-session continuity
*
* @module @ruvector/edge-net/credits
*/
import { EventEmitter } from 'events';
import { Ledger } from './ledger.js';
// ============================================
// CREDIT CONFIGURATION
// ============================================
/**
* Default credit values for operations
*/
export const CREDIT_CONFIG = {
// Base credit cost per task submission
taskSubmissionCost: 1,
// Credits earned per task completion (base rate)
taskCompletionReward: 1,
// Multipliers for task types
taskTypeMultipliers: {
embed: 1.0,
process: 1.0,
analyze: 1.5,
transform: 1.0,
compute: 2.0,
aggregate: 1.5,
custom: 1.0,
},
// Priority multipliers (higher priority = higher cost/reward)
priorityMultipliers: {
low: 0.5,
medium: 1.0,
high: 1.5,
critical: 2.0,
},
// Initial credits for new nodes (bootstrap)
initialCredits: 10,
// Minimum balance required to submit tasks (0 = no minimum)
minimumBalance: 0,
// Maximum transaction history to keep per node
maxTransactionHistory: 1000,
};
// ============================================
// CREDIT SYSTEM
// ============================================
/**
* CreditSystem - Manages credit accounting for distributed task execution
*
* Integrates with:
* - Ledger (CRDT) for conflict-free credit tracking
* - TaskExecutionHandler for automatic credit operations
* - FirebaseLedgerSync for persistence
*/
export class CreditSystem extends EventEmitter {
/**
* @param {Object} options
* @param {string} options.nodeId - This node's identifier
* @param {Ledger} options.ledger - CRDT ledger instance (will create if not provided)
* @param {Object} options.config - Credit configuration overrides
*/
constructor(options = {}) {
super();
this.nodeId = options.nodeId;
this.config = { ...CREDIT_CONFIG, ...options.config };
// Use provided ledger or create new one
this.ledger = options.ledger || new Ledger({
nodeId: this.nodeId,
maxTransactions: this.config.maxTransactionHistory,
});
// Transaction tracking by taskId (for deduplication)
this.processedTasks = new Map(); // taskId -> { type, timestamp }
// Stats
this.stats = {
creditsEarned: 0,
creditsSpent: 0,
tasksExecuted: 0,
tasksSubmitted: 0,
insufficientFunds: 0,
};
this.initialized = false;
}
/**
* Initialize credit system
*/
async initialize() {
// Initialize ledger
if (!this.ledger.initialized) {
await this.ledger.initialize();
}
// Grant initial credits if balance is zero (new node)
if (this.ledger.balance() === 0 && this.config.initialCredits > 0) {
this.ledger.credit(this.config.initialCredits, 'Initial bootstrap credits');
console.log(`[Credits] Granted ${this.config.initialCredits} initial credits`);
}
this.initialized = true;
this.emit('initialized', { balance: this.getBalance() });
return this;
}
// ============================================
// CREDIT OPERATIONS
// ============================================
/**
* Earn credits when completing a task for another node
*
* @param {string} nodeId - The node that earned credits (usually this node)
* @param {number} amount - Credit amount (will be adjusted by multipliers)
* @param {string} taskId - Task identifier
* @param {Object} taskInfo - Task details for calculating multipliers
* @returns {Object} Transaction record
*/
earnCredits(nodeId, amount, taskId, taskInfo = {}) {
// Only process for this node
if (nodeId !== this.nodeId) {
console.warn(`[Credits] Ignoring earnCredits for different node: ${nodeId}`);
return null;
}
// Check for duplicate processing
if (this.processedTasks.has(`earn:${taskId}`)) {
console.warn(`[Credits] Task ${taskId} already credited`);
return null;
}
// Calculate final amount with multipliers
const finalAmount = this._calculateAmount(amount, taskInfo);
// Record transaction in ledger
const tx = this.ledger.credit(finalAmount, JSON.stringify({
taskId,
type: 'task_completion',
taskType: taskInfo.type,
submitter: taskInfo.submitter,
}));
// Mark as processed
this.processedTasks.set(`earn:${taskId}`, {
type: 'earn',
amount: finalAmount,
timestamp: Date.now(),
});
// Update stats
this.stats.creditsEarned += finalAmount;
this.stats.tasksExecuted++;
// Prune old processed tasks (keep last 10000)
this._pruneProcessedTasks();
this.emit('credits-earned', {
nodeId,
amount: finalAmount,
taskId,
balance: this.getBalance(),
tx,
});
console.log(`[Credits] Earned ${finalAmount} credits for task ${taskId.slice(0, 8)}...`);
return tx;
}
/**
* Spend credits when submitting a task
*
* @param {string} nodeId - The node spending credits (usually this node)
* @param {number} amount - Credit amount (will be adjusted by multipliers)
* @param {string} taskId - Task identifier
* @param {Object} taskInfo - Task details for calculating cost
* @returns {Object|null} Transaction record or null if insufficient funds
*/
spendCredits(nodeId, amount, taskId, taskInfo = {}) {
// Only process for this node
if (nodeId !== this.nodeId) {
console.warn(`[Credits] Ignoring spendCredits for different node: ${nodeId}`);
return null;
}
// Check for duplicate processing
if (this.processedTasks.has(`spend:${taskId}`)) {
console.warn(`[Credits] Task ${taskId} already charged`);
return null;
}
// Calculate final amount with multipliers
const finalAmount = this._calculateAmount(amount, taskInfo);
// Check balance
const balance = this.getBalance();
if (balance < finalAmount) {
this.stats.insufficientFunds++;
this.emit('insufficient-funds', {
nodeId,
required: finalAmount,
available: balance,
taskId,
});
// In MVP, we allow tasks even with insufficient funds
// (can be enforced later)
if (this.config.minimumBalance > 0 && balance < this.config.minimumBalance) {
console.warn(`[Credits] Insufficient funds: ${balance} < ${finalAmount}`);
return null;
}
}
// Record transaction in ledger
let tx;
try {
tx = this.ledger.debit(finalAmount, JSON.stringify({
taskId,
type: 'task_submission',
taskType: taskInfo.type,
targetPeer: taskInfo.targetPeer,
}));
} catch (error) {
// Debit failed (insufficient balance in strict mode)
console.warn(`[Credits] Debit failed: ${error.message}`);
return null;
}
// Mark as processed
this.processedTasks.set(`spend:${taskId}`, {
type: 'spend',
amount: finalAmount,
timestamp: Date.now(),
});
// Update stats
this.stats.creditsSpent += finalAmount;
this.stats.tasksSubmitted++;
this._pruneProcessedTasks();
this.emit('credits-spent', {
nodeId,
amount: finalAmount,
taskId,
balance: this.getBalance(),
tx,
});
console.log(`[Credits] Spent ${finalAmount} credits for task ${taskId.slice(0, 8)}...`);
return tx;
}
/**
* Get current credit balance
*
* @param {string} nodeId - Node to check (defaults to this node)
* @returns {number} Current balance
*/
getBalance(nodeId = null) {
// For MVP, only track this node's balance
if (nodeId && nodeId !== this.nodeId) {
// Would need network query for other nodes
return 0;
}
return this.ledger.balance();
}
/**
* Get transaction history
*
* @param {string} nodeId - Node to get history for (defaults to this node)
* @param {number} limit - Maximum transactions to return
* @returns {Array} Transaction history
*/
getTransactionHistory(nodeId = null, limit = 50) {
// For MVP, only track this node's history
if (nodeId && nodeId !== this.nodeId) {
return [];
}
const transactions = this.ledger.getTransactions(limit);
// Parse memo JSON and add readable info
return transactions.map(tx => {
let details = {};
try {
details = JSON.parse(tx.memo || '{}');
} catch {
details = { memo: tx.memo };
}
return {
id: tx.id,
type: tx.type, // 'credit' or 'debit'
amount: tx.amount,
timestamp: tx.timestamp,
date: new Date(tx.timestamp).toISOString(),
...details,
};
});
}
/**
* Check if node has sufficient credits for a task
*
* @param {number} amount - Base amount
* @param {Object} taskInfo - Task info for multipliers
* @returns {boolean} True if sufficient
*/
hasSufficientCredits(amount, taskInfo = {}) {
const required = this._calculateAmount(amount, taskInfo);
return this.getBalance() >= required;
}
// ============================================
// CALCULATION HELPERS
// ============================================
/**
* Calculate final credit amount with multipliers
*/
_calculateAmount(baseAmount, taskInfo = {}) {
let amount = baseAmount;
// Apply task type multiplier
if (taskInfo.type && this.config.taskTypeMultipliers[taskInfo.type]) {
amount *= this.config.taskTypeMultipliers[taskInfo.type];
}
// Apply priority multiplier
if (taskInfo.priority && this.config.priorityMultipliers[taskInfo.priority]) {
amount *= this.config.priorityMultipliers[taskInfo.priority];
}
// Round to 2 decimal places
return Math.round(amount * 100) / 100;
}
/**
* Prune old processed task records
*/
_pruneProcessedTasks() {
if (this.processedTasks.size > 10000) {
// Remove oldest entries
const entries = Array.from(this.processedTasks.entries())
.sort((a, b) => a[1].timestamp - b[1].timestamp);
const toRemove = entries.slice(0, 5000);
for (const [key] of toRemove) {
this.processedTasks.delete(key);
}
}
}
// ============================================
// INTEGRATION METHODS
// ============================================
/**
* Wire to TaskExecutionHandler for automatic credit operations
*
* @param {TaskExecutionHandler} handler - Task execution handler
*/
wireToTaskHandler(handler) {
// Auto-credit when we complete a task
handler.on('task-complete', ({ taskId, from, duration, result }) => {
this.earnCredits(
this.nodeId,
this.config.taskCompletionReward,
taskId,
{
type: result?.taskType || 'compute',
submitter: from,
duration,
}
);
});
// Could also track task submissions if handler emits that event
handler.on('task-submitted', ({ taskId, to, task }) => {
this.spendCredits(
this.nodeId,
this.config.taskSubmissionCost,
taskId,
{
type: task?.type || 'compute',
priority: task?.priority,
targetPeer: to,
}
);
});
console.log('[Credits] Wired to TaskExecutionHandler');
}
/**
* Get credit system summary
*/
getSummary() {
return {
nodeId: this.nodeId,
balance: this.getBalance(),
totalEarned: this.ledger.totalEarned(),
totalSpent: this.ledger.totalSpent(),
stats: { ...this.stats },
initialized: this.initialized,
recentTransactions: this.getTransactionHistory(null, 5),
};
}
/**
* Export ledger state for sync
*/
export() {
return this.ledger.export();
}
/**
* Merge with remote ledger state (CRDT)
*/
merge(remoteState) {
this.ledger.merge(remoteState);
this.emit('merged', { balance: this.getBalance() });
}
/**
* Shutdown credit system
*/
async shutdown() {
await this.ledger.shutdown();
this.initialized = false;
this.emit('shutdown');
}
}
// ============================================
// FIREBASE CREDIT SYNC
// ============================================
/**
* Syncs credits to Firebase for persistence and cross-node visibility
*/
export class FirebaseCreditSync extends EventEmitter {
/**
* @param {CreditSystem} creditSystem - Credit system to sync
* @param {Object} options
* @param {Object} options.firebaseConfig - Firebase configuration
* @param {number} options.syncInterval - Sync interval in ms
*/
constructor(creditSystem, options = {}) {
super();
this.credits = creditSystem;
this.config = options.firebaseConfig;
this.syncInterval = options.syncInterval || 30000;
// Firebase instances
this.db = null;
this.firebase = null;
this.syncTimer = null;
this.unsubscribers = [];
}
/**
* Start Firebase sync
*/
async start() {
if (!this.config || !this.config.apiKey || !this.config.projectId) {
console.log('[FirebaseCreditSync] No Firebase config, skipping sync');
return false;
}
try {
const { initializeApp, getApps } = await import('firebase/app');
const { getFirestore, doc, setDoc, onSnapshot, getDoc, collection } = await import('firebase/firestore');
this.firebase = { doc, setDoc, onSnapshot, getDoc, collection };
const apps = getApps();
const app = apps.length ? apps[0] : initializeApp(this.config);
this.db = getFirestore(app);
// Initial sync
await this.pull();
// Subscribe to updates
this.subscribe();
// Periodic push
this.syncTimer = setInterval(() => this.push(), this.syncInterval);
console.log('[FirebaseCreditSync] Started');
return true;
} catch (error) {
console.log('[FirebaseCreditSync] Failed to start:', error.message);
return false;
}
}
/**
* Pull credit state from Firebase
*/
async pull() {
const { doc, getDoc } = this.firebase;
const creditRef = doc(this.db, 'edgenet_credits', this.credits.nodeId);
const snapshot = await getDoc(creditRef);
if (snapshot.exists()) {
const remoteState = snapshot.data();
if (remoteState.ledgerState) {
this.credits.merge(remoteState.ledgerState);
}
}
}
/**
* Push credit state to Firebase
*/
async push() {
const { doc, setDoc } = this.firebase;
const creditRef = doc(this.db, 'edgenet_credits', this.credits.nodeId);
await setDoc(creditRef, {
nodeId: this.credits.nodeId,
balance: this.credits.getBalance(),
totalEarned: this.credits.ledger.totalEarned(),
totalSpent: this.credits.ledger.totalSpent(),
ledgerState: this.credits.export(),
updatedAt: Date.now(),
}, { merge: true });
}
/**
* Subscribe to credit updates from Firebase
*/
subscribe() {
const { doc, onSnapshot } = this.firebase;
const creditRef = doc(this.db, 'edgenet_credits', this.credits.nodeId);
const unsubscribe = onSnapshot(creditRef, (snapshot) => {
if (snapshot.exists()) {
const data = snapshot.data();
if (data.ledgerState) {
this.credits.merge(data.ledgerState);
}
}
});
this.unsubscribers.push(unsubscribe);
}
/**
* Stop sync
*/
stop() {
if (this.syncTimer) {
clearInterval(this.syncTimer);
this.syncTimer = null;
}
for (const unsub of this.unsubscribers) {
if (typeof unsub === 'function') unsub();
}
this.unsubscribers = [];
}
}
// ============================================
// CONVENIENCE FACTORY
// ============================================
/**
* Create and initialize a complete credit system with optional Firebase sync
*
* @param {Object} options
* @param {string} options.nodeId - Node identifier
* @param {Ledger} options.ledger - Existing ledger (optional)
* @param {Object} options.firebaseConfig - Firebase config for sync
* @param {Object} options.config - Credit configuration overrides
* @returns {Promise<CreditSystem>} Initialized credit system
*/
export async function createCreditSystem(options = {}) {
const system = new CreditSystem(options);
await system.initialize();
// Start Firebase sync if configured
if (options.firebaseConfig) {
const sync = new FirebaseCreditSync(system, {
firebaseConfig: options.firebaseConfig,
syncInterval: options.syncInterval,
});
await sync.start();
// Attach sync to system for cleanup
system._firebaseSync = sync;
}
return system;
}
// ============================================
// EXPORTS
// ============================================
export default CreditSystem;

View File

@@ -0,0 +1,790 @@
/**
* @ruvector/edge-net DHT (Distributed Hash Table)
*
* Kademlia-style DHT for decentralized peer discovery.
* Works without central signaling servers.
*
* Features:
* - XOR distance-based routing
* - K-bucket peer organization
* - Iterative node lookup
* - Value storage and retrieval
* - Peer discovery protocol
*
* @module @ruvector/edge-net/dht
*/
import { EventEmitter } from 'events';
import { createHash, randomBytes } from 'crypto';
// DHT Constants
const K = 20; // K-bucket size (max peers per bucket)
const ALPHA = 3; // Parallel lookup concurrency
const ID_BITS = 160; // SHA-1 hash bits
const REFRESH_INTERVAL = 60000;
const PEER_TIMEOUT = 300000; // 5 minutes
/**
* Calculate XOR distance between two node IDs
*/
export function xorDistance(id1, id2) {
const buf1 = Buffer.from(id1, 'hex');
const buf2 = Buffer.from(id2, 'hex');
const result = Buffer.alloc(Math.max(buf1.length, buf2.length));
for (let i = 0; i < result.length; i++) {
result[i] = (buf1[i] || 0) ^ (buf2[i] || 0);
}
return result.toString('hex');
}
/**
* Get the bucket index for a given distance
*/
export function getBucketIndex(distance) {
const buf = Buffer.from(distance, 'hex');
for (let i = 0; i < buf.length; i++) {
if (buf[i] !== 0) {
// Find the first set bit
for (let j = 7; j >= 0; j--) {
if (buf[i] & (1 << j)) {
return (buf.length - i - 1) * 8 + j;
}
}
}
}
return 0;
}
/**
* Generate a random node ID
*/
export function generateNodeId() {
return createHash('sha1').update(randomBytes(32)).digest('hex');
}
/**
* K-Bucket: Stores peers at similar XOR distance
*/
export class KBucket {
constructor(index, k = K) {
this.index = index;
this.k = k;
this.peers = [];
this.replacementCache = [];
}
/**
* Add a peer to the bucket
*/
add(peer) {
// Check if peer already exists
const existingIndex = this.peers.findIndex(p => p.id === peer.id);
if (existingIndex !== -1) {
// Move to end (most recently seen)
this.peers.splice(existingIndex, 1);
this.peers.push({ ...peer, lastSeen: Date.now() });
return true;
}
if (this.peers.length < this.k) {
this.peers.push({ ...peer, lastSeen: Date.now() });
return true;
}
// Bucket full, add to replacement cache
this.replacementCache.push({ ...peer, lastSeen: Date.now() });
if (this.replacementCache.length > this.k) {
this.replacementCache.shift();
}
return false;
}
/**
* Remove a peer from the bucket
*/
remove(peerId) {
const index = this.peers.findIndex(p => p.id === peerId);
if (index !== -1) {
this.peers.splice(index, 1);
// Promote from replacement cache
if (this.replacementCache.length > 0) {
this.peers.push(this.replacementCache.shift());
}
return true;
}
return false;
}
/**
* Get a peer by ID
*/
get(peerId) {
return this.peers.find(p => p.id === peerId);
}
/**
* Get all peers
*/
getAll() {
return [...this.peers];
}
/**
* Get closest peers to a target ID
*/
getClosest(targetId, count = K) {
return this.peers
.map(p => ({
...p,
distance: xorDistance(p.id, targetId),
}))
.sort((a, b) => a.distance.localeCompare(b.distance))
.slice(0, count);
}
/**
* Remove stale peers
*/
prune() {
const now = Date.now();
this.peers = this.peers.filter(p =>
now - p.lastSeen < PEER_TIMEOUT
);
}
get size() {
return this.peers.length;
}
}
/**
* Routing Table: Manages all K-buckets
*/
export class RoutingTable {
constructor(localId) {
this.localId = localId;
this.buckets = new Array(ID_BITS).fill(null).map((_, i) => new KBucket(i));
this.allPeers = new Map();
}
/**
* Add a peer to the routing table
*/
add(peer) {
if (peer.id === this.localId) return false;
const distance = xorDistance(this.localId, peer.id);
const bucketIndex = getBucketIndex(distance);
const added = this.buckets[bucketIndex].add(peer);
if (added) {
this.allPeers.set(peer.id, peer);
}
return added;
}
/**
* Remove a peer from the routing table
*/
remove(peerId) {
const peer = this.allPeers.get(peerId);
if (!peer) return false;
const distance = xorDistance(this.localId, peerId);
const bucketIndex = getBucketIndex(distance);
this.buckets[bucketIndex].remove(peerId);
this.allPeers.delete(peerId);
return true;
}
/**
* Get a peer by ID
*/
get(peerId) {
return this.allPeers.get(peerId);
}
/**
* Find the closest peers to a target ID
*/
findClosest(targetId, count = K) {
const candidates = [];
for (const bucket of this.buckets) {
candidates.push(...bucket.getAll());
}
return candidates
.map(p => ({
...p,
distance: xorDistance(p.id, targetId),
}))
.sort((a, b) => a.distance.localeCompare(b.distance))
.slice(0, count);
}
/**
* Get all peers
*/
getAllPeers() {
return Array.from(this.allPeers.values());
}
/**
* Prune stale peers from all buckets
*/
prune() {
for (const bucket of this.buckets) {
bucket.prune();
}
// Update allPeers map
this.allPeers.clear();
for (const bucket of this.buckets) {
for (const peer of bucket.getAll()) {
this.allPeers.set(peer.id, peer);
}
}
}
/**
* Get routing table stats
*/
getStats() {
let totalPeers = 0;
let bucketsUsed = 0;
for (const bucket of this.buckets) {
if (bucket.size > 0) {
totalPeers += bucket.size;
bucketsUsed++;
}
}
return {
totalPeers,
bucketsUsed,
bucketCount: this.buckets.length,
};
}
}
/**
* DHT Node: Full DHT implementation
*/
export class DHTNode extends EventEmitter {
constructor(options = {}) {
super();
this.id = options.id || generateNodeId();
this.routingTable = new RoutingTable(this.id);
this.storage = new Map(); // DHT value storage
this.pendingLookups = new Map();
this.transport = options.transport || null;
this.bootstrapNodes = options.bootstrapNodes || [];
this.stats = {
lookups: 0,
stores: 0,
finds: 0,
messagesReceived: 0,
messagesSent: 0,
};
// Refresh timer
this.refreshTimer = null;
}
/**
* Start the DHT node
*/
async start() {
console.log(`\n🌐 Starting DHT Node: ${this.id.slice(0, 8)}...`);
// Bootstrap from known nodes
if (this.bootstrapNodes.length > 0) {
await this.bootstrap();
}
// Start periodic refresh
this.refreshTimer = setInterval(() => {
this.refresh();
}, REFRESH_INTERVAL);
this.emit('started', { id: this.id });
return this;
}
/**
* Stop the DHT node
*/
stop() {
if (this.refreshTimer) {
clearInterval(this.refreshTimer);
}
this.emit('stopped');
}
/**
* Bootstrap from known nodes
*/
async bootstrap() {
console.log(` 📡 Bootstrapping from ${this.bootstrapNodes.length} nodes...`);
for (const node of this.bootstrapNodes) {
try {
// Add bootstrap node to routing table
this.routingTable.add({
id: node.id,
address: node.address,
port: node.port,
});
// Perform lookup for our own ID to populate routing table
await this.lookup(this.id);
} catch (err) {
console.warn(` ⚠️ Bootstrap node ${node.id.slice(0, 8)} unreachable`);
}
}
}
/**
* Add a peer to the routing table
*/
addPeer(peer) {
const added = this.routingTable.add(peer);
if (added) {
this.emit('peer-added', peer);
}
return added;
}
/**
* Remove a peer from the routing table
*/
removePeer(peerId) {
const removed = this.routingTable.remove(peerId);
if (removed) {
this.emit('peer-removed', peerId);
}
return removed;
}
/**
* Iterative node lookup (Kademlia FIND_NODE)
*/
async lookup(targetId) {
this.stats.lookups++;
// Get initial closest nodes
let closest = this.routingTable.findClosest(targetId, ALPHA);
const queried = new Set([this.id]);
const results = new Map();
// Add initial closest to results
for (const node of closest) {
results.set(node.id, node);
}
// Iterative lookup
while (closest.length > 0) {
const toQuery = closest.filter(n => !queried.has(n.id)).slice(0, ALPHA);
if (toQuery.length === 0) break;
// Query nodes in parallel
const responses = await Promise.all(
toQuery.map(async (node) => {
queried.add(node.id);
try {
return await this.sendFindNode(node, targetId);
} catch (err) {
return [];
}
})
);
// Process responses
let foundCloser = false;
for (const nodes of responses) {
for (const node of nodes) {
if (!results.has(node.id)) {
results.set(node.id, node);
this.routingTable.add(node);
foundCloser = true;
}
}
}
if (!foundCloser) break;
// Get new closest
closest = Array.from(results.values())
.filter(n => !queried.has(n.id))
.sort((a, b) => {
const distA = xorDistance(a.id, targetId);
const distB = xorDistance(b.id, targetId);
return distA.localeCompare(distB);
})
.slice(0, K);
}
return Array.from(results.values())
.sort((a, b) => {
const distA = xorDistance(a.id, targetId);
const distB = xorDistance(b.id, targetId);
return distA.localeCompare(distB);
})
.slice(0, K);
}
/**
* Store a value in the DHT
*/
async store(key, value) {
this.stats.stores++;
const keyHash = createHash('sha1').update(key).digest('hex');
// Store locally
this.storage.set(keyHash, {
key,
value,
timestamp: Date.now(),
});
// Find closest nodes to the key
const closest = await this.lookup(keyHash);
// Store on closest nodes
await Promise.all(
closest.map(node => this.sendStore(node, keyHash, value))
);
this.emit('stored', { key, keyHash });
return keyHash;
}
/**
* Find a value in the DHT
*/
async find(key) {
this.stats.finds++;
const keyHash = createHash('sha1').update(key).digest('hex');
// Check local storage first
const local = this.storage.get(keyHash);
if (local) {
return local.value;
}
// Query closest nodes
const closest = await this.lookup(keyHash);
for (const node of closest) {
try {
const value = await this.sendFindValue(node, keyHash);
if (value) {
// Cache locally
this.storage.set(keyHash, {
key,
value,
timestamp: Date.now(),
});
return value;
}
} catch (err) {
// Node didn't have value
}
}
return null;
}
/**
* Send FIND_NODE request
*/
async sendFindNode(node, targetId) {
this.stats.messagesSent++;
if (this.transport) {
return await this.transport.send(node, {
type: 'FIND_NODE',
sender: this.id,
target: targetId,
});
}
// Simulated response for local testing
return [];
}
/**
* Send STORE request
*/
async sendStore(node, keyHash, value) {
this.stats.messagesSent++;
if (this.transport) {
return await this.transport.send(node, {
type: 'STORE',
sender: this.id,
key: keyHash,
value,
});
}
}
/**
* Send FIND_VALUE request
*/
async sendFindValue(node, keyHash) {
this.stats.messagesSent++;
if (this.transport) {
const response = await this.transport.send(node, {
type: 'FIND_VALUE',
sender: this.id,
key: keyHash,
});
return response?.value;
}
return null;
}
/**
* Handle incoming DHT message
*/
async handleMessage(message, sender) {
this.stats.messagesReceived++;
// Add sender to routing table
this.routingTable.add(sender);
switch (message.type) {
case 'PING':
return { type: 'PONG', sender: this.id };
case 'FIND_NODE':
return {
type: 'FIND_NODE_RESPONSE',
sender: this.id,
nodes: this.routingTable.findClosest(message.target, K),
};
case 'STORE':
this.storage.set(message.key, {
value: message.value,
timestamp: Date.now(),
});
return { type: 'STORE_ACK', sender: this.id };
case 'FIND_VALUE':
const stored = this.storage.get(message.key);
if (stored) {
return {
type: 'FIND_VALUE_RESPONSE',
sender: this.id,
value: stored.value,
};
}
return {
type: 'FIND_VALUE_RESPONSE',
sender: this.id,
nodes: this.routingTable.findClosest(message.key, K),
};
default:
return null;
}
}
/**
* Refresh buckets by looking up random IDs
*/
refresh() {
this.routingTable.prune();
// Lookup random ID in each bucket that hasn't been updated recently
for (let i = 0; i < ID_BITS; i++) {
const bucket = this.routingTable.buckets[i];
if (bucket.size > 0) {
const randomTarget = generateNodeId();
this.lookup(randomTarget).catch(() => {});
}
}
}
/**
* Get DHT statistics
*/
getStats() {
return {
...this.stats,
...this.routingTable.getStats(),
storageSize: this.storage.size,
};
}
/**
* Get all known peers
*/
getPeers() {
return this.routingTable.getAllPeers();
}
/**
* Find peers providing a service
*/
async findProviders(service) {
const serviceKey = `service:${service}`;
return await this.find(serviceKey);
}
/**
* Announce as a provider of a service
*/
async announce(service) {
const serviceKey = `service:${service}`;
// Get existing providers
let providers = await this.find(serviceKey);
if (!providers) {
providers = [];
}
// Add ourselves
if (!providers.some(p => p.id === this.id)) {
providers.push({
id: this.id,
timestamp: Date.now(),
});
}
// Store updated providers list
await this.store(serviceKey, providers);
this.emit('announced', { service });
}
}
/**
* WebRTC Transport for DHT
*/
export class DHTWebRTCTransport extends EventEmitter {
constructor(peerManager) {
super();
this.peerManager = peerManager;
this.pendingRequests = new Map();
this.requestId = 0;
// Listen for DHT messages from peers
this.peerManager.on('message', ({ from, message }) => {
if (message.type?.startsWith('DHT_')) {
this.handleResponse(from, message);
}
});
}
/**
* Send DHT message to a peer
*/
async send(node, message) {
return new Promise((resolve, reject) => {
const requestId = ++this.requestId;
// Set timeout
const timeout = setTimeout(() => {
this.pendingRequests.delete(requestId);
reject(new Error('DHT request timeout'));
}, 10000);
this.pendingRequests.set(requestId, { resolve, reject, timeout });
// Send via WebRTC
const sent = this.peerManager.sendToPeer(node.id, {
...message,
type: `DHT_${message.type}`,
requestId,
});
if (!sent) {
clearTimeout(timeout);
this.pendingRequests.delete(requestId);
reject(new Error('Peer not connected'));
}
});
}
/**
* Handle DHT response
*/
handleResponse(from, message) {
const pending = this.pendingRequests.get(message.requestId);
if (pending) {
clearTimeout(pending.timeout);
this.pendingRequests.delete(message.requestId);
pending.resolve(message);
}
}
}
/**
* Create and configure a DHT node with WebRTC transport
*/
export async function createDHTNode(peerManager, options = {}) {
const transport = new DHTWebRTCTransport(peerManager);
const dht = new DHTNode({
...options,
transport,
});
// Forward DHT messages from peers
peerManager.on('message', ({ from, message }) => {
if (message.type?.startsWith('DHT_')) {
const dhtMessage = {
...message,
type: message.type.replace('DHT_', ''),
};
const sender = {
id: from,
lastSeen: Date.now(),
};
dht.handleMessage(dhtMessage, sender).then(response => {
if (response) {
peerManager.sendToPeer(from, {
...response,
type: `DHT_${response.type}`,
requestId: message.requestId,
});
}
});
}
});
await dht.start();
return dht;
}
// ============================================
// EXPORTS
// ============================================
export default DHTNode;

View File

@@ -0,0 +1,297 @@
# Edge-Net P2P Migration Flow
This document describes the hybrid bootstrap migration flow in @ruvector/edge-net, which enables gradual transition from Firebase-based signaling to a fully decentralized P2P network.
## Overview
The Edge-Net network uses a hybrid approach to bootstrap peer-to-peer connections:
1. **Firebase Mode**: Initial state using Firebase for peer discovery and WebRTC signaling
2. **Hybrid Mode**: Transitional state using both Firebase and DHT
3. **P2P Mode**: Fully decentralized using DHT and direct WebRTC connections
## Architecture
```
+----------------+
| New Node |
+-------+--------+
|
v
+----------------+
| Firebase Mode |
| (Bootstrap) |
+-------+--------+
|
| DHT peers >= threshold
v
+----------------+
| Hybrid Mode |
| (Transition) |
+-------+--------+
|
| Direct peers >= threshold
v
+----------------+
| P2P Mode |
| (Full Decentr) |
+----------------+
```
## State Machine
### States
| State | Description | Signaling Method |
|----------|--------------------------------------------------|------------------------|
| firebase | Bootstrap phase using Firebase infrastructure | Firebase Firestore |
| hybrid | Transition phase using both Firebase and DHT | Firebase + DHT |
| p2p | Fully decentralized using DHT only | Direct P2P + DHT |
### Transitions
```
firebase ----[dhtPeers >= dhtPeerThreshold]----> hybrid
hybrid ----[connectedPeers >= p2pPeerThreshold]----> p2p
hybrid ----[dhtPeers < dhtPeerThreshold/2]----> firebase (fallback)
p2p ----[connectedPeers < p2pPeerThreshold/2]----> hybrid (fallback)
```
## Default Thresholds
| Parameter | Default | Description |
|-------------------|---------|------------------------------------------------|
| dhtPeerThreshold | 5 | DHT peers needed to enter hybrid mode |
| p2pPeerThreshold | 10 | Direct peers needed to enter full P2P mode |
| Migration check | 30s | Interval between migration state checks |
### Fallback Thresholds
Fallback occurs at **half** the original threshold to prevent oscillation:
- **Hybrid -> Firebase**: When DHT peers drop below `dhtPeerThreshold / 2` (default: 2.5)
- **P2P -> Hybrid**: When direct peers drop below `p2pPeerThreshold / 2` (default: 5)
## Configuration
```javascript
import { HybridBootstrap } from '@ruvector/edge-net/firebase-signaling';
const bootstrap = new HybridBootstrap({
peerId: 'unique-node-id',
dhtPeerThreshold: 5, // Custom threshold for hybrid transition
p2pPeerThreshold: 10, // Custom threshold for P2P transition
firebaseConfig: {
apiKey: '...',
projectId: '...',
},
});
```
## Migration Behavior
### 1. Firebase Mode (Bootstrap)
In this mode:
- Peer discovery via Firebase Firestore
- WebRTC signaling through Firebase
- No DHT operations
**Pros**:
- Reliable discovery for new nodes
- Works with any network configuration
- Low latency initial connections
**Cons**:
- Centralized dependency
- Firebase costs at scale
- Single point of failure
### 2. Hybrid Mode (Transition)
In this mode:
- Both Firebase and DHT active
- Signaling prefers P2P when available
- Falls back to Firebase for unknown peers
**Pros**:
- Graceful degradation
- Redundant discovery
- Smooth transition
**Cons**:
- Higher resource usage
- Complexity in routing decisions
### 3. P2P Mode (Full Decentralization)
In this mode:
- DHT-only peer discovery
- Direct WebRTC signaling via data channels
- Firebase maintained as emergency fallback
**Pros**:
- Fully decentralized
- No Firebase dependency
- Lower operating costs
- True P2P resilience
**Cons**:
- Requires sufficient network size
- NAT traversal challenges
## Signaling Fallback
The system implements intelligent signaling fallback:
```javascript
async signal(toPeerId, type, data) {
// Prefer P2P if available
if (this.webrtc?.isConnected(toPeerId)) {
this.webrtc.sendToPeer(toPeerId, { type, data });
return;
}
// Fall back to Firebase
if (this.firebase?.isConnected) {
await this.firebase.sendSignal(toPeerId, type, data);
return;
}
throw new Error('No signaling path available');
}
```
## DHT Routing Table
The DHT uses a Kademlia-style routing table:
- **K-buckets**: 160 buckets (SHA-1 ID space)
- **Bucket size (K)**: 20 peers per bucket
- **Alpha**: 3 parallel lookups
- **Refresh interval**: 60 seconds
- **Peer timeout**: 5 minutes
### Population
The routing table is populated from:
1. Firebase peer discoveries
2. DHT FIND_NODE responses
3. Direct WebRTC connections
## Network Partition Recovery
When network partitions occur:
1. Nodes continue operating in their partition
2. Migration state may degrade (p2p -> hybrid -> firebase)
3. When partition heals, connections re-establish
4. Migration state recovers automatically
## Recommended Threshold Adjustments
### Small Networks (< 20 nodes)
```javascript
{
dhtPeerThreshold: 3,
p2pPeerThreshold: 6,
}
```
**Rationale**: Lower thresholds allow faster P2P transition in small deployments.
### Medium Networks (20-100 nodes)
```javascript
{
dhtPeerThreshold: 5,
p2pPeerThreshold: 10,
}
```
**Rationale**: Default values balance reliability with decentralization speed.
### Large Networks (100+ nodes)
```javascript
{
dhtPeerThreshold: 10,
p2pPeerThreshold: 25,
}
```
**Rationale**: Higher thresholds ensure network stability before reducing Firebase dependency.
### High Churn Networks
```javascript
{
dhtPeerThreshold: 8,
p2pPeerThreshold: 15,
}
```
**Rationale**: Buffer against rapid node departures with higher thresholds.
## Monitoring
### Key Metrics
| Metric | Description |
|----------------------|---------------------------------------|
| mode | Current migration state |
| firebaseDiscoveries | Peers discovered via Firebase |
| dhtDiscoveries | Peers discovered via DHT |
| directConnections | Active WebRTC connections |
| firebaseSignals | Signals sent via Firebase |
| p2pSignals | Signals sent via P2P |
### Health Indicators
- **Healthy**: P2P mode with 10+ connected peers
- **Degraded**: Hybrid mode with 5-10 peers
- **Bootstrap**: Firebase mode with < 5 peers
## Testing
Run the migration test suite:
```bash
node tests/p2p-migration-test.js
```
### Test Scenarios
1. **Happy Path**: Gradual network growth
2. **Nodes Leaving**: Network shrinkage handling
3. **Nodes Rejoining**: Re-migration after recovery
4. **Network Partition**: Split and heal scenarios
5. **Signaling Fallback**: Route verification
6. **DHT Population**: Routing table validation
7. **Migration Timing**: Performance measurement
8. **Threshold Config**: Custom configuration testing
## Security Considerations
### WASM Cryptographic Identity
The migration system uses WASM-based cryptographic identity:
- Ed25519 key pairs generated in WASM
- All signaling messages are signed
- Peer verification before accepting connections
### Firebase Security
Firebase is secured via:
- Firestore security rules
- WASM signature verification
- No Firebase Auth dependency
## Future Improvements
1. **Adaptive Thresholds**: Dynamic threshold adjustment based on network conditions
2. **Reputation System**: Prefer reliable peers for DHT routing
3. **Geographic Awareness**: Consider latency in peer selection
4. **Predictive Migration**: Anticipate mode changes based on trends
5. **Multi-Firebase**: Support multiple Firebase projects for redundancy

View File

@@ -0,0 +1,435 @@
#!/usr/bin/env node
/**
* @ruvector/edge-net Firebase Setup
*
* Secure setup using Google Cloud CLI and Application Default Credentials.
* No API keys stored in environment variables - uses gcloud auth instead.
*
* Prerequisites:
* 1. Install Google Cloud CLI: https://cloud.google.com/sdk/docs/install
* 2. Login: gcloud auth login
* 3. Login for application: gcloud auth application-default login
*
* Usage:
* npx edge-net firebase-setup
* npx edge-net firebase-setup --project my-project-id
* npx edge-net firebase-setup --check
*
* @module @ruvector/edge-net/firebase-setup
*/
import { execSync, spawn } from 'child_process';
import { existsSync, writeFileSync, readFileSync, mkdirSync } from 'fs';
import { homedir } from 'os';
import { join } from 'path';
// ============================================
// CONFIGURATION
// ============================================
const CONFIG_DIR = join(homedir(), '.edge-net');
const CONFIG_FILE = join(CONFIG_DIR, 'firebase.json');
// Required Firebase services
const REQUIRED_APIS = [
'firebase.googleapis.com',
'firestore.googleapis.com',
'firebasedatabase.googleapis.com',
];
// ============================================
// GCLOUD HELPERS
// ============================================
/**
* Check if gcloud CLI is installed
*/
function checkGcloud() {
try {
execSync('gcloud --version', { stdio: 'pipe' });
return true;
} catch {
return false;
}
}
/**
* Get current gcloud configuration
*/
function getGcloudConfig() {
try {
const account = execSync('gcloud config get-value account', { stdio: 'pipe' }).toString().trim();
const project = execSync('gcloud config get-value project', { stdio: 'pipe' }).toString().trim();
return { account, project };
} catch {
return { account: null, project: null };
}
}
/**
* Check Application Default Credentials
*/
function checkADC() {
const adcPath = join(homedir(), '.config', 'gcloud', 'application_default_credentials.json');
return existsSync(adcPath);
}
/**
* Enable required APIs
*/
function enableAPIs(projectId) {
console.log('\n📦 Enabling required Firebase APIs...');
for (const api of REQUIRED_APIS) {
try {
execSync(`gcloud services enable ${api} --project=${projectId}`, { stdio: 'pipe' });
console.log(`${api}`);
} catch (err) {
console.log(` ⚠️ ${api} (may already be enabled)`);
}
}
}
/**
* Create Firestore database
*/
function createFirestore(projectId) {
console.log('\n🔥 Setting up Firestore...');
try {
// Check if Firestore already exists
execSync(`gcloud firestore databases describe --project=${projectId}`, { stdio: 'pipe' });
console.log(' ✅ Firestore database exists');
} catch {
// Create Firestore in native mode
try {
execSync(`gcloud firestore databases create --location=us-central --project=${projectId}`, { stdio: 'pipe' });
console.log(' ✅ Firestore database created (us-central)');
} catch (err) {
console.log(' ⚠️ Could not create Firestore (may need manual setup)');
}
}
}
/**
* Create Realtime Database
*/
function createRealtimeDB(projectId) {
console.log('\n📊 Setting up Realtime Database...');
try {
execSync(`firebase database:instances:create ${projectId}-rtdb --project=${projectId} --location=us-central1`, { stdio: 'pipe' });
console.log(` ✅ Realtime Database created: ${projectId}-rtdb`);
} catch {
console.log(' ⚠️ Realtime Database (may need Firebase CLI or manual setup)');
console.log(' 💡 Run: npm install -g firebase-tools && firebase init database');
}
}
/**
* Setup Firestore security rules
*/
function setupSecurityRules(projectId) {
const rules = `rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Edge-net signaling - authenticated users can read/write their signals
match /edge-net/signals/{signalId} {
allow read: if request.auth != null && resource.data.to == request.auth.uid;
allow create: if request.auth != null && request.resource.data.from == request.auth.uid;
allow delete: if request.auth != null && resource.data.to == request.auth.uid;
}
// Edge-net peers - public read, authenticated write
match /edge-net/peers/{peerId} {
allow read: if true;
allow write: if request.auth != null && request.auth.uid == peerId;
}
// Edge-net ledger - user can only access own ledger
match /edge-net/ledger/{peerId} {
allow read, write: if request.auth != null && request.auth.uid == peerId;
}
}
}`;
console.log('\n🔒 Firestore Security Rules:');
console.log(' Store these in firestore.rules and deploy with:');
console.log(' firebase deploy --only firestore:rules\n');
console.log(rules);
// Save rules file
const rulesPath = join(process.cwd(), 'firestore.rules');
writeFileSync(rulesPath, rules);
console.log(`\n ✅ Saved to: ${rulesPath}`);
}
/**
* Setup Realtime Database security rules
*/
function setupRTDBRules(projectId) {
const rules = {
"rules": {
"presence": {
"$room": {
"$peerId": {
".read": true,
".write": "auth != null && auth.uid == $peerId"
}
}
}
}
};
console.log('\n🔒 Realtime Database Rules:');
console.log(JSON.stringify(rules, null, 2));
// Save rules file
const rulesPath = join(process.cwd(), 'database.rules.json');
writeFileSync(rulesPath, JSON.stringify(rules, null, 2));
console.log(`\n ✅ Saved to: ${rulesPath}`);
}
/**
* Generate local config (no secrets!)
*/
function generateConfig(projectId) {
const config = {
projectId,
// These are NOT secrets - they're meant to be public
// API key restrictions happen in Google Cloud Console
authDomain: `${projectId}.firebaseapp.com`,
databaseURL: `https://${projectId}-default-rtdb.firebaseio.com`,
storageBucket: `${projectId}.appspot.com`,
// Security note
_note: 'Use Application Default Credentials for server-side. Generate restricted API key for browser in Google Cloud Console.',
_adcCommand: 'gcloud auth application-default login',
};
// Create config directory
if (!existsSync(CONFIG_DIR)) {
mkdirSync(CONFIG_DIR, { recursive: true });
}
// Save config
writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
console.log(`\n📁 Config saved to: ${CONFIG_FILE}`);
return config;
}
/**
* Get API key securely (creates if needed)
*/
async function setupAPIKey(projectId) {
console.log('\n🔑 API Key Setup:');
console.log(' For browser-side Firebase, you need a restricted API key.');
console.log(' \n Steps:');
console.log(' 1. Go to: https://console.cloud.google.com/apis/credentials?project=' + projectId);
console.log(' 2. Create API Key → Restrict to:');
console.log(' - HTTP referrers (websites): your-domain.com/*');
console.log(' - APIs: Firebase Realtime Database, Cloud Firestore');
console.log(' 3. Set environment variable: export FIREBASE_API_KEY=your-key');
console.log('\n For Node.js server-side, use Application Default Credentials (more secure):');
console.log(' gcloud auth application-default login');
}
// ============================================
// MAIN SETUP FLOW
// ============================================
async function setup(options = {}) {
console.log('🚀 Edge-Net Firebase Setup\n');
console.log('=' .repeat(50));
// Step 1: Check gcloud
console.log('\n1⃣ Checking Google Cloud CLI...');
if (!checkGcloud()) {
console.error('❌ Google Cloud CLI not found!');
console.log(' Install from: https://cloud.google.com/sdk/docs/install');
process.exit(1);
}
console.log(' ✅ gcloud CLI found');
// Step 2: Check authentication
console.log('\n2⃣ Checking authentication...');
const { account, project } = getGcloudConfig();
if (!account) {
console.error('❌ Not logged in to gcloud!');
console.log(' Run: gcloud auth login');
process.exit(1);
}
console.log(` ✅ Logged in as: ${account}`);
// Step 3: Check ADC
console.log('\n3⃣ Checking Application Default Credentials...');
if (!checkADC()) {
console.log(' ⚠️ ADC not configured');
console.log(' Run: gcloud auth application-default login');
console.log('\n Setting up now...');
try {
execSync('gcloud auth application-default login', { stdio: 'inherit' });
} catch {
console.log(' ⚠️ ADC setup cancelled or failed');
}
} else {
console.log(' ✅ ADC configured');
}
// Step 4: Select project
const projectId = options.project || project;
console.log(`\n4⃣ Using project: ${projectId}`);
if (!projectId) {
console.error('❌ No project specified!');
console.log(' Run: gcloud config set project YOUR_PROJECT_ID');
console.log(' Or: npx edge-net firebase-setup --project YOUR_PROJECT_ID');
process.exit(1);
}
// Step 5: Enable APIs
enableAPIs(projectId);
// Step 6: Setup Firestore
createFirestore(projectId);
// Step 7: Setup Realtime Database
createRealtimeDB(projectId);
// Step 8: Generate security rules
setupSecurityRules(projectId);
setupRTDBRules(projectId);
// Step 9: Generate config
const config = generateConfig(projectId);
// Step 10: API Key guidance
await setupAPIKey(projectId);
// Done!
console.log('\n' + '='.repeat(50));
console.log('✅ Firebase setup complete!\n');
console.log('Next steps:');
console.log('1. Deploy security rules: firebase deploy --only firestore:rules,database');
console.log('2. Create restricted API key in Google Cloud Console');
console.log('3. Set FIREBASE_API_KEY environment variable');
console.log('4. Test with: npx edge-net join\n');
return config;
}
/**
* Check current status
*/
function checkStatus() {
console.log('🔍 Edge-Net Firebase Status\n');
// Check gcloud
const hasGcloud = checkGcloud();
console.log(`gcloud CLI: ${hasGcloud ? '✅' : '❌'}`);
// Check auth
const { account, project } = getGcloudConfig();
console.log(`Logged in: ${account ? `${account}` : '❌'}`);
console.log(`Project: ${project ? `${project}` : '❌'}`);
// Check ADC
const hasADC = checkADC();
console.log(`Application Default Credentials: ${hasADC ? '✅' : '❌'}`);
// Check config file
const hasConfig = existsSync(CONFIG_FILE);
console.log(`Config file: ${hasConfig ? `${CONFIG_FILE}` : '❌'}`);
// Check env vars
const hasApiKey = !!process.env.FIREBASE_API_KEY;
console.log(`FIREBASE_API_KEY: ${hasApiKey ? '✅ (set)' : '⚠️ (not set - needed for browser)'}`);
console.log();
if (!hasGcloud || !account || !project) {
console.log('💡 Run setup: npx edge-net firebase-setup');
} else if (!hasADC) {
console.log('💡 Run: gcloud auth application-default login');
} else if (!hasConfig) {
console.log('💡 Run setup: npx edge-net firebase-setup');
} else {
console.log('✅ Ready to use Firebase bootstrap!');
}
}
/**
* Load saved config
*/
export function loadConfig() {
if (!existsSync(CONFIG_FILE)) {
return null;
}
try {
return JSON.parse(readFileSync(CONFIG_FILE, 'utf8'));
} catch {
return null;
}
}
/**
* Get Firebase config (from env vars or saved config)
*/
export function getFirebaseConfigSecure() {
// First try environment variables
const apiKey = process.env.FIREBASE_API_KEY;
const projectId = process.env.FIREBASE_PROJECT_ID;
if (apiKey && projectId) {
return {
apiKey,
projectId,
authDomain: process.env.FIREBASE_AUTH_DOMAIN || `${projectId}.firebaseapp.com`,
databaseURL: process.env.FIREBASE_DATABASE_URL || `https://${projectId}-default-rtdb.firebaseio.com`,
storageBucket: process.env.FIREBASE_STORAGE_BUCKET || `${projectId}.appspot.com`,
};
}
// Try saved config (needs API key from env still for security)
const config = loadConfig();
if (config && apiKey) {
return {
apiKey,
...config,
};
}
return null;
}
// ============================================
// CLI
// ============================================
const args = process.argv.slice(2);
if (args.includes('--check')) {
checkStatus();
} else if (args.includes('--help') || args.includes('-h')) {
console.log(`
Edge-Net Firebase Setup
Usage:
npx edge-net firebase-setup Setup Firebase with gcloud
npx edge-net firebase-setup --project ID Use specific project
npx edge-net firebase-setup --check Check current status
Prerequisites:
1. Install gcloud: https://cloud.google.com/sdk/docs/install
2. Login: gcloud auth login
3. Set project: gcloud config set project YOUR_PROJECT_ID
Security:
- Uses Application Default Credentials (no stored secrets)
- API keys restricted by domain in Google Cloud Console
- Firestore rules protect user data
`);
} else {
const projectIndex = args.indexOf('--project');
const project = projectIndex >= 0 ? args[projectIndex + 1] : null;
setup({ project });
}
export { setup, checkStatus };

View File

@@ -0,0 +1,858 @@
#!/usr/bin/env node
/**
* @ruvector/edge-net Genesis Node
*
* Bootstrap node for the edge-net P2P network.
* Provides signaling, peer discovery, and ledger sync.
*
* Run: node genesis.js [--port 8787] [--data ~/.ruvector/genesis]
*
* @module @ruvector/edge-net/genesis
*/
import { EventEmitter } from 'events';
import { createHash, randomBytes } from 'crypto';
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
import { join } from 'path';
// ============================================
// GENESIS NODE CONFIGURATION
// ============================================
export const GENESIS_CONFIG = {
port: parseInt(process.env.GENESIS_PORT || '8787'),
host: process.env.GENESIS_HOST || '0.0.0.0',
dataDir: process.env.GENESIS_DATA || join(process.env.HOME || '/tmp', '.ruvector', 'genesis'),
// Rate limiting
rateLimit: {
maxConnectionsPerIp: 50,
maxMessagesPerSecond: 100,
challengeExpiry: 60000, // 1 minute
},
// Cleanup
cleanup: {
staleConnectionTimeout: 300000, // 5 minutes
cleanupInterval: 60000, // 1 minute
},
};
// ============================================
// PEER REGISTRY
// ============================================
export class PeerRegistry {
constructor() {
this.peers = new Map(); // peerId -> peer info
this.byPublicKey = new Map(); // publicKey -> peerId
this.byRoom = new Map(); // room -> Set<peerId>
this.connections = new Map(); // connectionId -> peerId
}
register(peerId, info) {
this.peers.set(peerId, {
...info,
peerId,
registeredAt: Date.now(),
lastSeen: Date.now(),
});
if (info.publicKey) {
this.byPublicKey.set(info.publicKey, peerId);
}
return this.peers.get(peerId);
}
update(peerId, updates) {
const peer = this.peers.get(peerId);
if (peer) {
Object.assign(peer, updates, { lastSeen: Date.now() });
}
return peer;
}
get(peerId) {
return this.peers.get(peerId);
}
getByPublicKey(publicKey) {
const peerId = this.byPublicKey.get(publicKey);
return peerId ? this.peers.get(peerId) : null;
}
remove(peerId) {
const peer = this.peers.get(peerId);
if (peer) {
if (peer.publicKey) {
this.byPublicKey.delete(peer.publicKey);
}
if (peer.room) {
const room = this.byRoom.get(peer.room);
if (room) room.delete(peerId);
}
this.peers.delete(peerId);
return true;
}
return false;
}
joinRoom(peerId, room) {
const peer = this.peers.get(peerId);
if (!peer) return false;
// Leave old room
if (peer.room && peer.room !== room) {
const oldRoom = this.byRoom.get(peer.room);
if (oldRoom) oldRoom.delete(peerId);
}
// Join new room
if (!this.byRoom.has(room)) {
this.byRoom.set(room, new Set());
}
this.byRoom.get(room).add(peerId);
peer.room = room;
return true;
}
getRoomPeers(room) {
const peerIds = this.byRoom.get(room) || new Set();
return Array.from(peerIds).map(id => this.peers.get(id)).filter(Boolean);
}
getAllPeers() {
return Array.from(this.peers.values());
}
pruneStale(maxAge = GENESIS_CONFIG.cleanup.staleConnectionTimeout) {
const cutoff = Date.now() - maxAge;
const removed = [];
for (const [peerId, peer] of this.peers) {
if (peer.lastSeen < cutoff) {
this.remove(peerId);
removed.push(peerId);
}
}
return removed;
}
getStats() {
return {
totalPeers: this.peers.size,
rooms: this.byRoom.size,
roomSizes: Object.fromEntries(
Array.from(this.byRoom.entries()).map(([room, peers]) => [room, peers.size])
),
};
}
}
// ============================================
// LEDGER STORE
// ============================================
export class LedgerStore {
constructor(dataDir) {
this.dataDir = dataDir;
this.ledgers = new Map();
this.pendingWrites = new Map();
// Ensure data directory exists
if (!existsSync(dataDir)) {
mkdirSync(dataDir, { recursive: true });
}
// Load existing ledgers
this.loadAll();
}
loadAll() {
try {
const indexPath = join(this.dataDir, 'index.json');
if (existsSync(indexPath)) {
const index = JSON.parse(readFileSync(indexPath, 'utf8'));
for (const publicKey of index.keys || []) {
this.load(publicKey);
}
}
} catch (err) {
console.warn('[Genesis] Failed to load ledger index:', err.message);
}
}
load(publicKey) {
try {
const path = join(this.dataDir, `ledger-${publicKey.slice(0, 16)}.json`);
if (existsSync(path)) {
const data = JSON.parse(readFileSync(path, 'utf8'));
this.ledgers.set(publicKey, data);
return data;
}
} catch (err) {
console.warn(`[Genesis] Failed to load ledger ${publicKey.slice(0, 8)}:`, err.message);
}
return null;
}
save(publicKey) {
try {
const data = this.ledgers.get(publicKey);
if (!data) return false;
const path = join(this.dataDir, `ledger-${publicKey.slice(0, 16)}.json`);
writeFileSync(path, JSON.stringify(data, null, 2));
// Update index
this.saveIndex();
return true;
} catch (err) {
console.warn(`[Genesis] Failed to save ledger ${publicKey.slice(0, 8)}:`, err.message);
return false;
}
}
saveIndex() {
try {
const indexPath = join(this.dataDir, 'index.json');
writeFileSync(indexPath, JSON.stringify({
keys: Array.from(this.ledgers.keys()),
updatedAt: Date.now(),
}, null, 2));
} catch (err) {
console.warn('[Genesis] Failed to save index:', err.message);
}
}
get(publicKey) {
return this.ledgers.get(publicKey);
}
getStates(publicKey) {
const ledger = this.ledgers.get(publicKey);
if (!ledger) return [];
return Object.values(ledger.devices || {});
}
update(publicKey, deviceId, state) {
if (!this.ledgers.has(publicKey)) {
this.ledgers.set(publicKey, {
publicKey,
createdAt: Date.now(),
devices: {},
});
}
const ledger = this.ledgers.get(publicKey);
// Merge state
const existing = ledger.devices[deviceId] || {};
const merged = this.mergeCRDT(existing, state);
ledger.devices[deviceId] = {
...merged,
deviceId,
updatedAt: Date.now(),
};
// Schedule write
this.scheduleSave(publicKey);
return ledger.devices[deviceId];
}
mergeCRDT(existing, incoming) {
// Simple LWW merge for now
if (!existing.timestamp || incoming.timestamp > existing.timestamp) {
return { ...incoming };
}
// If same timestamp, merge counters
return {
earned: Math.max(existing.earned || 0, incoming.earned || 0),
spent: Math.max(existing.spent || 0, incoming.spent || 0),
timestamp: Math.max(existing.timestamp || 0, incoming.timestamp || 0),
};
}
scheduleSave(publicKey) {
if (this.pendingWrites.has(publicKey)) return;
this.pendingWrites.set(publicKey, setTimeout(() => {
this.save(publicKey);
this.pendingWrites.delete(publicKey);
}, 1000));
}
flush() {
for (const [publicKey, timeout] of this.pendingWrites) {
clearTimeout(timeout);
this.save(publicKey);
}
this.pendingWrites.clear();
}
getStats() {
return {
totalLedgers: this.ledgers.size,
totalDevices: Array.from(this.ledgers.values())
.reduce((sum, l) => sum + Object.keys(l.devices || {}).length, 0),
};
}
}
// ============================================
// AUTHENTICATION SERVICE
// ============================================
export class AuthService {
constructor() {
this.challenges = new Map(); // nonce -> { challenge, publicKey, expiresAt }
this.tokens = new Map(); // token -> { publicKey, deviceId, expiresAt }
}
createChallenge(publicKey, deviceId) {
const nonce = randomBytes(32).toString('hex');
const challenge = randomBytes(32).toString('hex');
this.challenges.set(nonce, {
challenge,
publicKey,
deviceId,
expiresAt: Date.now() + GENESIS_CONFIG.rateLimit.challengeExpiry,
});
return { nonce, challenge };
}
verifyChallenge(nonce, publicKey, signature) {
const challengeData = this.challenges.get(nonce);
if (!challengeData) {
return { valid: false, error: 'Invalid nonce' };
}
if (Date.now() > challengeData.expiresAt) {
this.challenges.delete(nonce);
return { valid: false, error: 'Challenge expired' };
}
if (challengeData.publicKey !== publicKey) {
return { valid: false, error: 'Public key mismatch' };
}
// Simple signature verification (in production, use proper Ed25519)
const expectedSig = createHash('sha256')
.update(challengeData.challenge + publicKey)
.digest('hex');
// For now, accept any signature (real impl would verify Ed25519)
// In production: verify Ed25519 signature
this.challenges.delete(nonce);
// Generate token
const token = randomBytes(32).toString('hex');
const tokenData = {
publicKey,
deviceId: challengeData.deviceId,
createdAt: Date.now(),
expiresAt: Date.now() + 24 * 60 * 60 * 1000, // 24 hours
};
this.tokens.set(token, tokenData);
return { valid: true, token, expiresAt: tokenData.expiresAt };
}
validateToken(token) {
const tokenData = this.tokens.get(token);
if (!tokenData) return null;
if (Date.now() > tokenData.expiresAt) {
this.tokens.delete(token);
return null;
}
return tokenData;
}
cleanup() {
const now = Date.now();
for (const [nonce, data] of this.challenges) {
if (now > data.expiresAt) {
this.challenges.delete(nonce);
}
}
for (const [token, data] of this.tokens) {
if (now > data.expiresAt) {
this.tokens.delete(token);
}
}
}
}
// ============================================
// GENESIS NODE SERVER
// ============================================
export class GenesisNode extends EventEmitter {
constructor(options = {}) {
super();
this.config = { ...GENESIS_CONFIG, ...options };
this.peerRegistry = new PeerRegistry();
this.ledgerStore = new LedgerStore(this.config.dataDir);
this.authService = new AuthService();
this.wss = null;
this.connections = new Map();
this.cleanupInterval = null;
this.stats = {
startedAt: null,
totalConnections: 0,
totalMessages: 0,
signalsRelayed: 0,
};
}
async start() {
console.log('\n🌐 Starting Edge-Net Genesis Node...');
console.log(` Port: ${this.config.port}`);
console.log(` Data: ${this.config.dataDir}`);
// Import ws dynamically
const { WebSocketServer } = await import('ws');
this.wss = new WebSocketServer({
port: this.config.port,
host: this.config.host,
});
this.wss.on('connection', (ws, req) => this.handleConnection(ws, req));
this.wss.on('error', (err) => this.emit('error', err));
// Start cleanup interval
this.cleanupInterval = setInterval(() => this.cleanup(), this.config.cleanup.cleanupInterval);
this.stats.startedAt = Date.now();
console.log(`\n✅ Genesis Node running on ws://${this.config.host}:${this.config.port}`);
console.log(` API: http://${this.config.host}:${this.config.port}/api/v1/`);
this.emit('started', { port: this.config.port });
return this;
}
stop() {
if (this.cleanupInterval) {
clearInterval(this.cleanupInterval);
}
if (this.wss) {
this.wss.close();
}
this.ledgerStore.flush();
this.emit('stopped');
}
handleConnection(ws, req) {
const connectionId = randomBytes(16).toString('hex');
const ip = req.socket.remoteAddress;
this.stats.totalConnections++;
this.connections.set(connectionId, {
ws,
ip,
peerId: null,
connectedAt: Date.now(),
});
ws.on('message', (data) => {
try {
const message = JSON.parse(data.toString());
this.handleMessage(connectionId, message);
} catch (err) {
console.warn(`[Genesis] Invalid message from ${connectionId}:`, err.message);
}
});
ws.on('close', () => {
this.handleDisconnect(connectionId);
});
ws.on('error', (err) => {
console.warn(`[Genesis] Connection error ${connectionId}:`, err.message);
});
// Send welcome
this.send(connectionId, {
type: 'welcome',
connectionId,
serverTime: Date.now(),
});
}
handleDisconnect(connectionId) {
const conn = this.connections.get(connectionId);
if (conn?.peerId) {
const peer = this.peerRegistry.get(conn.peerId);
if (peer?.room) {
// Notify room peers
this.broadcastToRoom(peer.room, {
type: 'peer-left',
peerId: conn.peerId,
}, conn.peerId);
}
this.peerRegistry.remove(conn.peerId);
}
this.connections.delete(connectionId);
}
handleMessage(connectionId, message) {
this.stats.totalMessages++;
const conn = this.connections.get(connectionId);
if (!conn) return;
switch (message.type) {
// Signaling messages
case 'announce':
this.handleAnnounce(connectionId, message);
break;
case 'join':
this.handleJoinRoom(connectionId, message);
break;
case 'offer':
case 'answer':
case 'ice-candidate':
this.relaySignal(connectionId, message);
break;
// Auth messages
case 'auth-challenge':
this.handleAuthChallenge(connectionId, message);
break;
case 'auth-verify':
this.handleAuthVerify(connectionId, message);
break;
// Ledger messages
case 'ledger-get':
this.handleLedgerGet(connectionId, message);
break;
case 'ledger-put':
this.handleLedgerPut(connectionId, message);
break;
// DHT bootstrap
case 'dht-bootstrap':
this.handleDHTBootstrap(connectionId, message);
break;
default:
console.warn(`[Genesis] Unknown message type: ${message.type}`);
}
}
handleAnnounce(connectionId, message) {
const conn = this.connections.get(connectionId);
const peerId = message.piKey || message.peerId || randomBytes(16).toString('hex');
conn.peerId = peerId;
this.peerRegistry.register(peerId, {
publicKey: message.publicKey,
siteId: message.siteId,
capabilities: message.capabilities || [],
connectionId,
});
// Send current peer list
const peers = this.peerRegistry.getAllPeers()
.filter(p => p.peerId !== peerId)
.map(p => ({
piKey: p.peerId,
siteId: p.siteId,
capabilities: p.capabilities,
}));
this.send(connectionId, {
type: 'peer-list',
peers,
});
// Notify other peers
for (const peer of this.peerRegistry.getAllPeers()) {
if (peer.peerId !== peerId && peer.connectionId) {
this.send(peer.connectionId, {
type: 'peer-joined',
peerId,
siteId: message.siteId,
capabilities: message.capabilities,
});
}
}
}
handleJoinRoom(connectionId, message) {
const conn = this.connections.get(connectionId);
if (!conn?.peerId) return;
const room = message.room || 'default';
this.peerRegistry.joinRoom(conn.peerId, room);
// Send room peers
const roomPeers = this.peerRegistry.getRoomPeers(room)
.filter(p => p.peerId !== conn.peerId)
.map(p => ({
piKey: p.peerId,
siteId: p.siteId,
}));
this.send(connectionId, {
type: 'room-joined',
room,
peers: roomPeers,
});
// Notify room peers
this.broadcastToRoom(room, {
type: 'peer-joined',
peerId: conn.peerId,
siteId: this.peerRegistry.get(conn.peerId)?.siteId,
}, conn.peerId);
}
relaySignal(connectionId, message) {
this.stats.signalsRelayed++;
const conn = this.connections.get(connectionId);
if (!conn?.peerId) return;
const targetPeer = this.peerRegistry.get(message.to);
if (!targetPeer?.connectionId) {
this.send(connectionId, {
type: 'error',
error: 'Target peer not found',
originalType: message.type,
});
return;
}
// Relay the signal
this.send(targetPeer.connectionId, {
...message,
from: conn.peerId,
});
}
handleAuthChallenge(connectionId, message) {
const { nonce, challenge } = this.authService.createChallenge(
message.publicKey,
message.deviceId
);
this.send(connectionId, {
type: 'auth-challenge-response',
nonce,
challenge,
});
}
handleAuthVerify(connectionId, message) {
const result = this.authService.verifyChallenge(
message.nonce,
message.publicKey,
message.signature
);
this.send(connectionId, {
type: 'auth-verify-response',
...result,
});
}
handleLedgerGet(connectionId, message) {
const tokenData = this.authService.validateToken(message.token);
if (!tokenData) {
this.send(connectionId, {
type: 'ledger-response',
error: 'Invalid or expired token',
});
return;
}
const states = this.ledgerStore.getStates(message.publicKey || tokenData.publicKey);
this.send(connectionId, {
type: 'ledger-response',
states,
});
}
handleLedgerPut(connectionId, message) {
const tokenData = this.authService.validateToken(message.token);
if (!tokenData) {
this.send(connectionId, {
type: 'ledger-put-response',
error: 'Invalid or expired token',
});
return;
}
const updated = this.ledgerStore.update(
tokenData.publicKey,
message.deviceId || tokenData.deviceId,
message.state
);
this.send(connectionId, {
type: 'ledger-put-response',
success: true,
state: updated,
});
}
handleDHTBootstrap(connectionId, message) {
// Return known peers for DHT bootstrap
const peers = this.peerRegistry.getAllPeers()
.slice(0, 20)
.map(p => ({
id: p.peerId,
address: p.connectionId,
lastSeen: p.lastSeen,
}));
this.send(connectionId, {
type: 'dht-bootstrap-response',
peers,
});
}
send(connectionId, message) {
const conn = this.connections.get(connectionId);
if (conn?.ws?.readyState === 1) {
conn.ws.send(JSON.stringify(message));
}
}
broadcastToRoom(room, message, excludePeerId = null) {
const peers = this.peerRegistry.getRoomPeers(room);
for (const peer of peers) {
if (peer.peerId !== excludePeerId && peer.connectionId) {
this.send(peer.connectionId, message);
}
}
}
cleanup() {
// Prune stale peers
const removed = this.peerRegistry.pruneStale();
if (removed.length > 0) {
console.log(`[Genesis] Pruned ${removed.length} stale peers`);
}
// Cleanup auth
this.authService.cleanup();
}
getStats() {
return {
...this.stats,
uptime: this.stats.startedAt ? Date.now() - this.stats.startedAt : 0,
...this.peerRegistry.getStats(),
...this.ledgerStore.getStats(),
activeConnections: this.connections.size,
};
}
}
// ============================================
// CLI
// ============================================
async function main() {
const args = process.argv.slice(2);
// Parse args
let port = GENESIS_CONFIG.port;
let dataDir = GENESIS_CONFIG.dataDir;
for (let i = 0; i < args.length; i++) {
if (args[i] === '--port' && args[i + 1]) {
port = parseInt(args[i + 1]);
i++;
} else if (args[i] === '--data' && args[i + 1]) {
dataDir = args[i + 1];
i++;
} else if (args[i] === '--help') {
console.log(`
Edge-Net Genesis Node
Usage: node genesis.js [options]
Options:
--port <port> Port to listen on (default: 8787)
--data <dir> Data directory (default: ~/.ruvector/genesis)
--help Show this help
Environment Variables:
GENESIS_PORT Port (default: 8787)
GENESIS_HOST Host (default: 0.0.0.0)
GENESIS_DATA Data directory
Examples:
node genesis.js
node genesis.js --port 9000
node genesis.js --port 8787 --data /var/lib/edge-net
`);
process.exit(0);
}
}
const genesis = new GenesisNode({ port, dataDir });
// Handle shutdown
process.on('SIGINT', () => {
console.log('\n\n🛑 Shutting down Genesis Node...');
genesis.stop();
process.exit(0);
});
process.on('SIGTERM', () => {
genesis.stop();
process.exit(0);
});
// Start server
await genesis.start();
// Log stats periodically
setInterval(() => {
const stats = genesis.getStats();
console.log(`[Genesis] Peers: ${stats.totalPeers} | Connections: ${stats.activeConnections} | Signals: ${stats.signalsRelayed}`);
}, 60000);
}
// Run if executed directly
if (process.argv[1]?.endsWith('genesis.js')) {
main().catch(err => {
console.error('Genesis Node error:', err);
process.exit(1);
});
}
export default GenesisNode;

View File

@@ -0,0 +1,706 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Join Edge-Net | RuVector Distributed Compute</title>
<style>
:root {
--bg: #0a0a0f;
--surface: #12121a;
--border: #2a2a3a;
--primary: #6366f1;
--primary-hover: #818cf8;
--success: #22c55e;
--warning: #f59e0b;
--text: #e2e8f0;
--text-muted: #94a3b8;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: 'SF Mono', 'Fira Code', monospace;
background: var(--bg);
color: var(--text);
min-height: 100vh;
padding: 2rem;
}
.container {
max-width: 800px;
margin: 0 auto;
}
header {
text-align: center;
margin-bottom: 2rem;
}
h1 {
font-size: 2rem;
background: linear-gradient(135deg, var(--primary), var(--success));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
margin-bottom: 0.5rem;
}
.subtitle {
color: var(--text-muted);
font-size: 0.9rem;
}
.card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 12px;
padding: 1.5rem;
margin-bottom: 1.5rem;
}
.card h2 {
font-size: 1rem;
color: var(--primary);
margin-bottom: 1rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
.form-group {
margin-bottom: 1rem;
}
label {
display: block;
font-size: 0.85rem;
color: var(--text-muted);
margin-bottom: 0.5rem;
}
input[type="text"], input[type="password"] {
width: 100%;
padding: 0.75rem;
background: var(--bg);
border: 1px solid var(--border);
border-radius: 8px;
color: var(--text);
font-family: inherit;
font-size: 0.9rem;
}
input:focus {
outline: none;
border-color: var(--primary);
}
.btn {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.75rem 1.5rem;
background: var(--primary);
color: white;
border: none;
border-radius: 8px;
font-family: inherit;
font-size: 0.9rem;
cursor: pointer;
transition: background 0.2s;
}
.btn:hover { background: var(--primary-hover); }
.btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.btn-secondary {
background: transparent;
border: 1px solid var(--border);
}
.btn-secondary:hover {
background: var(--surface);
}
.identity-display {
background: var(--bg);
border: 1px solid var(--border);
border-radius: 8px;
padding: 1rem;
font-size: 0.85rem;
}
.identity-row {
display: flex;
justify-content: space-between;
padding: 0.5rem 0;
border-bottom: 1px solid var(--border);
}
.identity-row:last-child { border-bottom: none; }
.identity-label { color: var(--text-muted); }
.identity-value {
font-weight: 600;
word-break: break-all;
}
.pi-key { color: var(--success); }
.status {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.75rem 1rem;
border-radius: 8px;
margin-bottom: 1rem;
font-size: 0.85rem;
}
.status.info { background: rgba(99, 102, 241, 0.1); border: 1px solid var(--primary); }
.status.success { background: rgba(34, 197, 94, 0.1); border: 1px solid var(--success); }
.status.warning { background: rgba(245, 158, 11, 0.1); border: 1px solid var(--warning); }
.network-stats {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1rem;
margin-top: 1rem;
}
.stat {
text-align: center;
padding: 1rem;
background: var(--bg);
border-radius: 8px;
}
.stat-value {
font-size: 1.5rem;
font-weight: bold;
color: var(--primary);
}
.stat-label {
font-size: 0.75rem;
color: var(--text-muted);
margin-top: 0.25rem;
}
.contribution-log {
max-height: 200px;
overflow-y: auto;
background: var(--bg);
border-radius: 8px;
padding: 1rem;
font-size: 0.8rem;
}
.log-entry {
padding: 0.25rem 0;
color: var(--text-muted);
}
.log-entry.success { color: var(--success); }
.log-entry.highlight { color: var(--primary); }
.hidden { display: none; }
.actions {
display: flex;
gap: 1rem;
flex-wrap: wrap;
}
#qr-code {
display: flex;
justify-content: center;
padding: 1rem;
background: white;
border-radius: 8px;
margin-top: 1rem;
}
.crypto-badge {
display: inline-flex;
align-items: center;
gap: 0.25rem;
padding: 0.25rem 0.5rem;
background: rgba(34, 197, 94, 0.1);
border: 1px solid var(--success);
border-radius: 4px;
font-size: 0.7rem;
color: var(--success);
}
</style>
</head>
<body>
<div class="container">
<header>
<h1>🌐 Edge-Net Join</h1>
<p class="subtitle">Contribute browser compute, earn credits</p>
<div style="margin-top: 0.5rem;">
<span class="crypto-badge">🔐 Ed25519</span>
<span class="crypto-badge">🛡️ Argon2id</span>
<span class="crypto-badge">🔒 AES-256-GCM</span>
</div>
</header>
<!-- Step 1: Generate or Restore Identity -->
<div class="card" id="identity-section">
<h2>🔑 Your Identity</h2>
<div id="no-identity">
<div class="status info">
<span></span>
<span>Create a new identity or restore an existing one to join the network.</span>
</div>
<div class="form-group">
<label for="site-id">Site ID (your unique identifier)</label>
<input type="text" id="site-id" placeholder="e.g., alice, bob, node-42" />
</div>
<div class="form-group">
<label for="password">Password (for encrypted backup)</label>
<input type="password" id="password" placeholder="Strong password for identity encryption" />
</div>
<div class="actions">
<button class="btn" id="generate-btn" onclick="generateIdentity()">
<span></span> Generate New Identity
</button>
<button class="btn btn-secondary" onclick="document.getElementById('restore-file').click()">
<span>📥</span> Restore from Backup
</button>
<input type="file" id="restore-file" class="hidden" accept=".identity" onchange="restoreIdentity(event)" />
</div>
</div>
<div id="has-identity" class="hidden">
<div class="status success">
<span></span>
<span>Identity active and connected to network</span>
</div>
<div class="identity-display">
<div class="identity-row">
<span class="identity-label">Site ID</span>
<span class="identity-value" id="display-site-id">-</span>
</div>
<div class="identity-row">
<span class="identity-label">Pi-Key</span>
<span class="identity-value pi-key" id="display-pi-key">-</span>
</div>
<div class="identity-row">
<span class="identity-label">Public Key</span>
<span class="identity-value" id="display-pubkey">-</span>
</div>
<div class="identity-row">
<span class="identity-label">Created</span>
<span class="identity-value" id="display-created">-</span>
</div>
</div>
<div class="actions" style="margin-top: 1rem;">
<button class="btn btn-secondary" onclick="exportIdentity()">
<span>📤</span> Export Backup
</button>
<button class="btn btn-secondary" onclick="copyPublicKey()">
<span>📋</span> Copy Public Key
</button>
<button class="btn btn-secondary" onclick="showQR()">
<span>📱</span> Show QR
</button>
</div>
<div id="qr-code" class="hidden"></div>
</div>
</div>
<!-- Step 2: Network Status -->
<div class="card" id="network-section">
<h2>📡 Network Status</h2>
<div class="network-stats">
<div class="stat">
<div class="stat-value" id="stat-peers">0</div>
<div class="stat-label">Connected Peers</div>
</div>
<div class="stat">
<div class="stat-value" id="stat-contributions">0</div>
<div class="stat-label">Contributions</div>
</div>
<div class="stat">
<div class="stat-value" id="stat-credits">0</div>
<div class="stat-label">Credits Earned</div>
</div>
</div>
<div class="contribution-log" id="contribution-log">
<div class="log-entry">Waiting for identity...</div>
</div>
</div>
<!-- Step 3: Contribute -->
<div class="card" id="contribute-section">
<h2>⚡ Contribute Compute</h2>
<div class="status warning" id="contribute-status">
<span></span>
<span>Generate or restore identity to start contributing</span>
</div>
<div class="actions">
<button class="btn" id="start-btn" disabled onclick="startContributing()">
<span>▶️</span> Start Contributing
</button>
<button class="btn btn-secondary" id="stop-btn" disabled onclick="stopContributing()">
<span>⏹️</span> Stop
</button>
</div>
</div>
</div>
<script type="module">
// Import WASM module
import init, * as wasm from './ruvector_edge_net.js';
let wasmModule = null;
let identity = null;
let contributing = false;
let contributionCount = 0;
let creditsEarned = 0;
let peerCount = 0;
// Initialize WASM
async function initWasm() {
try {
await init();
wasmModule = wasm;
log('WASM module loaded', 'success');
checkStoredIdentity();
} catch (err) {
log('Failed to load WASM: ' + err.message, 'error');
}
}
// Check for stored identity in localStorage
function checkStoredIdentity() {
const stored = localStorage.getItem('edge-net-identity');
if (stored) {
try {
identity = JSON.parse(stored);
showIdentity();
log('Identity restored from storage', 'success');
} catch (e) {
log('Stored identity corrupted', 'error');
}
}
}
// Generate new identity
window.generateIdentity = async function() {
const siteId = document.getElementById('site-id').value.trim();
const password = document.getElementById('password').value;
if (!siteId) {
alert('Please enter a Site ID');
return;
}
if (password.length < 8) {
alert('Password must be at least 8 characters');
return;
}
document.getElementById('generate-btn').disabled = true;
log('Generating identity...', 'highlight');
try {
// Generate Pi-Key identity using WASM
const piKeyData = wasmModule.generate_pi_key();
// Create identity object
identity = {
siteId: siteId,
piKey: arrayToHex(piKeyData.pi_key).slice(0, 20),
publicKey: arrayToHex(piKeyData.public_key),
created: new Date().toISOString(),
sessions: 1,
contributions: [],
// Store encrypted private key for backup
encryptedPrivateKey: await encryptData(piKeyData.private_key, password)
};
// Save to localStorage
localStorage.setItem('edge-net-identity', JSON.stringify(identity));
localStorage.setItem('edge-net-password-hint', password.length.toString());
showIdentity();
log('Identity generated: π:' + identity.piKey, 'success');
// Announce to network
announceToNetwork();
} catch (err) {
log('Generation failed: ' + err.message, 'error');
}
document.getElementById('generate-btn').disabled = false;
};
// Show identity UI
function showIdentity() {
document.getElementById('no-identity').classList.add('hidden');
document.getElementById('has-identity').classList.remove('hidden');
document.getElementById('display-site-id').textContent = identity.siteId;
document.getElementById('display-pi-key').textContent = 'π:' + identity.piKey;
document.getElementById('display-pubkey').textContent = identity.publicKey.slice(0, 16) + '...';
document.getElementById('display-created').textContent = new Date(identity.created).toLocaleDateString();
document.getElementById('start-btn').disabled = false;
document.getElementById('contribute-status').innerHTML = '<span>✅</span><span>Ready to contribute compute to the network</span>';
document.getElementById('contribute-status').className = 'status success';
}
// Export encrypted identity backup
window.exportIdentity = async function() {
const password = prompt('Enter password to encrypt backup:');
if (!password) return;
const backup = {
version: 1,
identity: identity,
exported: new Date().toISOString()
};
const encrypted = await encryptData(JSON.stringify(backup), password);
const blob = new Blob([encrypted], { type: 'application/octet-stream' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `${identity.siteId}.identity`;
a.click();
URL.revokeObjectURL(url);
log('Identity exported to ' + identity.siteId + '.identity', 'success');
};
// Restore identity from backup
window.restoreIdentity = async function(event) {
const file = event.target.files[0];
if (!file) return;
const password = prompt('Enter backup password:');
if (!password) return;
try {
const encrypted = await file.text();
const decrypted = await decryptData(encrypted, password);
const backup = JSON.parse(decrypted);
identity = backup.identity;
identity.sessions = (identity.sessions || 0) + 1;
localStorage.setItem('edge-net-identity', JSON.stringify(identity));
showIdentity();
log('Identity restored: π:' + identity.piKey, 'success');
announceToNetwork();
} catch (err) {
alert('Failed to restore: ' + err.message);
}
};
// Copy public key
window.copyPublicKey = function() {
navigator.clipboard.writeText(identity.publicKey);
log('Public key copied to clipboard', 'success');
};
// Show QR code
window.showQR = function() {
const qrDiv = document.getElementById('qr-code');
if (qrDiv.classList.contains('hidden')) {
// Simple text QR representation (in production, use a QR library)
qrDiv.innerHTML = `<div style="text-align: center; color: #000;">
<div style="font-size: 0.8rem; margin-bottom: 0.5rem;">Scan to verify</div>
<div style="font-family: monospace; font-size: 0.7rem; word-break: break-all; max-width: 200px;">
${identity.publicKey}
</div>
</div>`;
qrDiv.classList.remove('hidden');
} else {
qrDiv.classList.add('hidden');
}
};
// Start contributing compute
window.startContributing = function() {
if (!identity) return;
contributing = true;
document.getElementById('start-btn').disabled = true;
document.getElementById('stop-btn').disabled = false;
log('Starting compute contribution...', 'highlight');
contributeLoop();
};
// Stop contributing
window.stopContributing = function() {
contributing = false;
document.getElementById('start-btn').disabled = false;
document.getElementById('stop-btn').disabled = true;
log('Compute contribution stopped', 'warning');
};
// Contribution loop
async function contributeLoop() {
while (contributing) {
try {
// Simulate compute task
const taskId = Math.random().toString(36).slice(2, 10);
log(`Processing task ${taskId}...`);
// Do actual WASM computation
const start = performance.now();
// Vector computation task
const vectors = [];
for (let i = 0; i < 100; i++) {
vectors.push(new Float32Array(128).map(() => Math.random()));
}
// Compute similarities (actual work)
let computed = 0;
for (let i = 0; i < vectors.length; i++) {
for (let j = i + 1; j < vectors.length; j++) {
dotProduct(vectors[i], vectors[j]);
computed++;
}
}
const elapsed = performance.now() - start;
// Record contribution
contributionCount++;
const credits = Math.floor(computed / 100);
creditsEarned += credits;
// Update stats
document.getElementById('stat-contributions').textContent = contributionCount;
document.getElementById('stat-credits').textContent = creditsEarned;
// Save contribution
identity.contributions.push({
taskId,
computed,
credits,
timestamp: Date.now()
});
localStorage.setItem('edge-net-identity', JSON.stringify(identity));
log(`Task ${taskId} complete: ${computed} ops, +${credits} credits (${elapsed.toFixed(1)}ms)`, 'success');
// Wait before next task
await sleep(2000);
} catch (err) {
log('Task error: ' + err.message, 'error');
await sleep(5000);
}
}
}
// Announce presence to network (simulated P2P)
function announceToNetwork() {
// In production, this would use WebRTC or WebSocket to P2P network
peerCount = Math.floor(Math.random() * 5) + 1;
document.getElementById('stat-peers').textContent = peerCount;
log(`Connected to ${peerCount} peers`, 'success');
// Simulate peer discovery
setInterval(() => {
if (identity) {
const delta = Math.random() > 0.5 ? 1 : -1;
peerCount = Math.max(1, peerCount + delta);
document.getElementById('stat-peers').textContent = peerCount;
}
}, 10000);
}
// Utility functions
function log(message, type = '') {
const logDiv = document.getElementById('contribution-log');
const entry = document.createElement('div');
entry.className = 'log-entry ' + type;
entry.textContent = `[${new Date().toLocaleTimeString()}] ${message}`;
logDiv.insertBefore(entry, logDiv.firstChild);
// Keep only last 50 entries
while (logDiv.children.length > 50) {
logDiv.removeChild(logDiv.lastChild);
}
}
function arrayToHex(arr) {
return Array.from(arr).map(b => b.toString(16).padStart(2, '0')).join('');
}
function dotProduct(a, b) {
let sum = 0;
for (let i = 0; i < a.length; i++) sum += a[i] * b[i];
return sum;
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// Simple encryption (in production, use Web Crypto API with Argon2)
async function encryptData(data, password) {
const encoder = new TextEncoder();
const dataBytes = typeof data === 'string' ? encoder.encode(data) : data;
const keyMaterial = await crypto.subtle.importKey(
'raw',
encoder.encode(password),
'PBKDF2',
false,
['deriveBits', 'deriveKey']
);
const salt = crypto.getRandomValues(new Uint8Array(16));
const iv = crypto.getRandomValues(new Uint8Array(12));
const key = await crypto.subtle.deriveKey(
{ name: 'PBKDF2', salt, iterations: 100000, hash: 'SHA-256' },
keyMaterial,
{ name: 'AES-GCM', length: 256 },
false,
['encrypt']
);
const encrypted = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv },
key,
dataBytes
);
// Combine salt + iv + encrypted
const result = new Uint8Array(salt.length + iv.length + encrypted.byteLength);
result.set(salt, 0);
result.set(iv, salt.length);
result.set(new Uint8Array(encrypted), salt.length + iv.length);
return btoa(String.fromCharCode(...result));
}
async function decryptData(encryptedBase64, password) {
const encoder = new TextEncoder();
const encrypted = Uint8Array.from(atob(encryptedBase64), c => c.charCodeAt(0));
const salt = encrypted.slice(0, 16);
const iv = encrypted.slice(16, 28);
const data = encrypted.slice(28);
const keyMaterial = await crypto.subtle.importKey(
'raw',
encoder.encode(password),
'PBKDF2',
false,
['deriveBits', 'deriveKey']
);
const key = await crypto.subtle.deriveKey(
{ name: 'PBKDF2', salt, iterations: 100000, hash: 'SHA-256' },
keyMaterial,
{ name: 'AES-GCM', length: 256 },
false,
['decrypt']
);
const decrypted = await crypto.subtle.decrypt(
{ name: 'AES-GCM', iv },
key,
data
);
return new TextDecoder().decode(decrypted);
}
// Initialize on load
initWasm();
</script>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,663 @@
/**
* @ruvector/edge-net Persistent Ledger with CRDT
*
* Conflict-free Replicated Data Type for distributed credit tracking
* Features:
* - G-Counter for earned credits
* - PN-Counter for balance
* - LWW-Register for metadata
* - File-based persistence
* - Network synchronization
*
* @module @ruvector/edge-net/ledger
*/
import { EventEmitter } from 'events';
import { randomBytes, createHash } from 'crypto';
import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
import { join } from 'path';
import { homedir } from 'os';
// ============================================
// CRDT PRIMITIVES
// ============================================
/**
* G-Counter (Grow-only Counter)
* Can only increment, never decrement
*/
export class GCounter {
constructor(nodeId) {
this.nodeId = nodeId;
this.counters = new Map(); // nodeId -> count
}
increment(amount = 1) {
const current = this.counters.get(this.nodeId) || 0;
this.counters.set(this.nodeId, current + amount);
}
value() {
let total = 0;
for (const count of this.counters.values()) {
total += count;
}
return total;
}
merge(other) {
for (const [nodeId, count] of other.counters) {
const current = this.counters.get(nodeId) || 0;
this.counters.set(nodeId, Math.max(current, count));
}
}
toJSON() {
return {
nodeId: this.nodeId,
counters: Object.fromEntries(this.counters),
};
}
static fromJSON(json) {
const counter = new GCounter(json.nodeId);
counter.counters = new Map(Object.entries(json.counters));
return counter;
}
}
/**
* PN-Counter (Positive-Negative Counter)
* Can increment and decrement
*/
export class PNCounter {
constructor(nodeId) {
this.nodeId = nodeId;
this.positive = new GCounter(nodeId);
this.negative = new GCounter(nodeId);
}
increment(amount = 1) {
this.positive.increment(amount);
}
decrement(amount = 1) {
this.negative.increment(amount);
}
value() {
return this.positive.value() - this.negative.value();
}
merge(other) {
this.positive.merge(other.positive);
this.negative.merge(other.negative);
}
toJSON() {
return {
nodeId: this.nodeId,
positive: this.positive.toJSON(),
negative: this.negative.toJSON(),
};
}
static fromJSON(json) {
const counter = new PNCounter(json.nodeId);
counter.positive = GCounter.fromJSON(json.positive);
counter.negative = GCounter.fromJSON(json.negative);
return counter;
}
}
/**
* LWW-Register (Last-Writer-Wins Register)
* Stores a single value with timestamp
*/
export class LWWRegister {
constructor(nodeId, value = null) {
this.nodeId = nodeId;
this.value = value;
this.timestamp = Date.now();
}
set(value) {
this.value = value;
this.timestamp = Date.now();
}
get() {
return this.value;
}
merge(other) {
if (other.timestamp > this.timestamp) {
this.value = other.value;
this.timestamp = other.timestamp;
}
}
toJSON() {
return {
nodeId: this.nodeId,
value: this.value,
timestamp: this.timestamp,
};
}
static fromJSON(json) {
const register = new LWWRegister(json.nodeId);
register.value = json.value;
register.timestamp = json.timestamp;
return register;
}
}
/**
* LWW-Map (Last-Writer-Wins Map)
* Map with LWW semantics per key
*/
export class LWWMap {
constructor(nodeId) {
this.nodeId = nodeId;
this.entries = new Map(); // key -> { value, timestamp }
}
set(key, value) {
this.entries.set(key, {
value,
timestamp: Date.now(),
});
}
get(key) {
const entry = this.entries.get(key);
return entry ? entry.value : undefined;
}
delete(key) {
this.entries.set(key, {
value: null,
timestamp: Date.now(),
deleted: true,
});
}
has(key) {
const entry = this.entries.get(key);
return entry && !entry.deleted;
}
keys() {
return Array.from(this.entries.keys()).filter(k => !this.entries.get(k).deleted);
}
values() {
return this.keys().map(k => this.entries.get(k).value);
}
merge(other) {
for (const [key, entry] of other.entries) {
const current = this.entries.get(key);
if (!current || entry.timestamp > current.timestamp) {
this.entries.set(key, { ...entry });
}
}
}
toJSON() {
return {
nodeId: this.nodeId,
entries: Object.fromEntries(this.entries),
};
}
static fromJSON(json) {
const map = new LWWMap(json.nodeId);
map.entries = new Map(Object.entries(json.entries));
return map;
}
}
// ============================================
// PERSISTENT LEDGER
// ============================================
/**
* Distributed Ledger with CRDT and persistence
*/
export class Ledger extends EventEmitter {
constructor(options = {}) {
super();
this.nodeId = options.nodeId || `node-${randomBytes(8).toString('hex')}`;
// Storage path
this.dataDir = options.dataDir ||
join(homedir(), '.ruvector', 'edge-net', 'ledger');
// CRDT state
this.earned = new GCounter(this.nodeId);
this.spent = new GCounter(this.nodeId);
this.metadata = new LWWMap(this.nodeId);
this.transactions = [];
// Configuration
this.autosaveInterval = options.autosaveInterval || 30000; // 30 seconds
this.maxTransactions = options.maxTransactions || 10000;
// Sync
this.lastSync = 0;
this.syncPeers = new Set();
// Initialize
this.initialized = false;
}
/**
* Initialize ledger and load from disk
*/
async initialize() {
// Create data directory
if (!existsSync(this.dataDir)) {
mkdirSync(this.dataDir, { recursive: true });
}
// Load existing state
await this.load();
// Start autosave
this.autosaveTimer = setInterval(() => {
this.save().catch(err => console.error('[Ledger] Autosave error:', err));
}, this.autosaveInterval);
this.initialized = true;
this.emit('ready', { nodeId: this.nodeId });
return this;
}
/**
* Credit (earn) amount
*/
credit(amount, memo = '') {
if (amount <= 0) throw new Error('Amount must be positive');
this.earned.increment(amount);
const tx = {
id: `tx-${randomBytes(8).toString('hex')}`,
type: 'credit',
amount,
memo,
timestamp: Date.now(),
nodeId: this.nodeId,
};
this.transactions.push(tx);
this.pruneTransactions();
this.emit('credit', { amount, balance: this.balance(), tx });
return tx;
}
/**
* Debit (spend) amount
*/
debit(amount, memo = '') {
if (amount <= 0) throw new Error('Amount must be positive');
if (amount > this.balance()) throw new Error('Insufficient balance');
this.spent.increment(amount);
const tx = {
id: `tx-${randomBytes(8).toString('hex')}`,
type: 'debit',
amount,
memo,
timestamp: Date.now(),
nodeId: this.nodeId,
};
this.transactions.push(tx);
this.pruneTransactions();
this.emit('debit', { amount, balance: this.balance(), tx });
return tx;
}
/**
* Get current balance
*/
balance() {
return this.earned.value() - this.spent.value();
}
/**
* Get total earned
*/
totalEarned() {
return this.earned.value();
}
/**
* Get total spent
*/
totalSpent() {
return this.spent.value();
}
/**
* Set metadata
*/
setMetadata(key, value) {
this.metadata.set(key, value);
this.emit('metadata', { key, value });
}
/**
* Get metadata
*/
getMetadata(key) {
return this.metadata.get(key);
}
/**
* Get recent transactions
*/
getTransactions(limit = 50) {
return this.transactions.slice(-limit);
}
/**
* Prune old transactions
*/
pruneTransactions() {
if (this.transactions.length > this.maxTransactions) {
this.transactions = this.transactions.slice(-this.maxTransactions);
}
}
/**
* Merge with another ledger state (CRDT merge)
*/
merge(other) {
// Merge counters
if (other.earned) {
this.earned.merge(
other.earned instanceof GCounter
? other.earned
: GCounter.fromJSON(other.earned)
);
}
if (other.spent) {
this.spent.merge(
other.spent instanceof GCounter
? other.spent
: GCounter.fromJSON(other.spent)
);
}
if (other.metadata) {
this.metadata.merge(
other.metadata instanceof LWWMap
? other.metadata
: LWWMap.fromJSON(other.metadata)
);
}
// Merge transactions (deduplicate by id)
if (other.transactions) {
const existingIds = new Set(this.transactions.map(t => t.id));
for (const tx of other.transactions) {
if (!existingIds.has(tx.id)) {
this.transactions.push(tx);
}
}
// Sort by timestamp and prune
this.transactions.sort((a, b) => a.timestamp - b.timestamp);
this.pruneTransactions();
}
this.lastSync = Date.now();
this.emit('merged', { balance: this.balance() });
}
/**
* Export state for synchronization
*/
export() {
return {
nodeId: this.nodeId,
timestamp: Date.now(),
earned: this.earned.toJSON(),
spent: this.spent.toJSON(),
metadata: this.metadata.toJSON(),
transactions: this.transactions,
};
}
/**
* Save to disk
*/
async save() {
const filePath = join(this.dataDir, 'ledger.json');
const data = this.export();
writeFileSync(filePath, JSON.stringify(data, null, 2));
this.emit('saved', { path: filePath });
}
/**
* Load from disk
*/
async load() {
const filePath = join(this.dataDir, 'ledger.json');
if (!existsSync(filePath)) {
return;
}
try {
const data = JSON.parse(readFileSync(filePath, 'utf-8'));
this.earned = GCounter.fromJSON(data.earned);
this.spent = GCounter.fromJSON(data.spent);
this.metadata = LWWMap.fromJSON(data.metadata);
this.transactions = data.transactions || [];
this.emit('loaded', { balance: this.balance() });
} catch (error) {
console.error('[Ledger] Load error:', error.message);
}
}
/**
* Get ledger summary
*/
getSummary() {
return {
nodeId: this.nodeId,
balance: this.balance(),
earned: this.totalEarned(),
spent: this.totalSpent(),
transactions: this.transactions.length,
lastSync: this.lastSync,
initialized: this.initialized,
};
}
/**
* Shutdown ledger
*/
async shutdown() {
if (this.autosaveTimer) {
clearInterval(this.autosaveTimer);
}
await this.save();
this.initialized = false;
this.emit('shutdown');
}
}
// ============================================
// SYNC CLIENT
// ============================================
/**
* Ledger sync client for relay communication
*/
export class LedgerSyncClient extends EventEmitter {
constructor(options = {}) {
super();
this.ledger = options.ledger;
this.relayUrl = options.relayUrl || 'ws://localhost:8080';
this.ws = null;
this.connected = false;
this.syncInterval = options.syncInterval || 60000; // 1 minute
}
/**
* Connect to relay for syncing
*/
async connect() {
return new Promise(async (resolve, reject) => {
try {
let WebSocket;
if (typeof globalThis.WebSocket !== 'undefined') {
WebSocket = globalThis.WebSocket;
} else {
const ws = await import('ws');
WebSocket = ws.default || ws.WebSocket;
}
this.ws = new WebSocket(this.relayUrl);
const timeout = setTimeout(() => {
reject(new Error('Connection timeout'));
}, 10000);
this.ws.onopen = () => {
clearTimeout(timeout);
this.connected = true;
// Register for ledger sync
this.send({
type: 'register',
nodeId: this.ledger.nodeId,
capabilities: ['ledger_sync'],
});
this.emit('connected');
resolve(true);
};
this.ws.onmessage = (event) => {
this.handleMessage(JSON.parse(event.data));
};
this.ws.onclose = () => {
this.connected = false;
this.emit('disconnected');
};
this.ws.onerror = (error) => {
clearTimeout(timeout);
reject(error);
};
} catch (error) {
reject(error);
}
});
}
/**
* Handle incoming message
*/
handleMessage(message) {
switch (message.type) {
case 'welcome':
this.startSyncLoop();
break;
case 'ledger_state':
this.handleLedgerState(message);
break;
case 'ledger_update':
this.ledger.merge(message.state);
break;
default:
this.emit('message', message);
}
}
/**
* Handle ledger state from relay
*/
handleLedgerState(message) {
if (message.state) {
this.ledger.merge(message.state);
}
this.emit('synced', { balance: this.ledger.balance() });
}
/**
* Start periodic sync
*/
startSyncLoop() {
// Initial sync
this.sync();
// Periodic sync
this.syncTimer = setInterval(() => {
this.sync();
}, this.syncInterval);
}
/**
* Sync with relay
*/
sync() {
if (!this.connected) return;
this.send({
type: 'ledger_sync',
state: this.ledger.export(),
});
}
/**
* Send message
*/
send(message) {
if (this.connected && this.ws?.readyState === 1) {
this.ws.send(JSON.stringify(message));
return true;
}
return false;
}
/**
* Close connection
*/
close() {
if (this.syncTimer) {
clearInterval(this.syncTimer);
}
if (this.ws) {
this.ws.close();
}
}
}
// ============================================
// EXPORTS
// ============================================
export default Ledger;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,792 @@
/**
* @ruvector/edge-net Adapter Security
*
* Security for MicroLoRA adapters:
* - Quarantine before activation
* - Local evaluation gating
* - Base model matching
* - Signature verification
* - Merge lineage tracking
*
* Invariant: Adapters never applied without full verification.
*
* @module @ruvector/edge-net/models/adapter-security
*/
import { createHash } from 'crypto';
import { canonicalize, hashCanonical, TrustRoot, ManifestVerifier } from './integrity.js';
// ============================================================================
// ADAPTER VERIFICATION
// ============================================================================
/**
* Adapter verification rules
*/
export const ADAPTER_REQUIREMENTS = Object.freeze({
// Base model must match exactly
requireExactBaseMatch: true,
// Checksum must match manifest
requireChecksumMatch: true,
// Signature must be verified
requireSignature: true,
// Must pass local evaluation OR have trusted quality proof
requireQualityGate: true,
// Minimum evaluation score to pass gate (0-1)
minEvaluationScore: 0.7,
// Maximum adapter size relative to base model
maxAdapterSizeRatio: 0.1, // 10% of base model
// Trusted quality proof publishers
trustedQualityProvers: ['ruvector-eval-2024', 'community-eval-2024'],
});
/**
* Adapter manifest structure
*/
export function createAdapterManifest(adapter) {
return {
schemaVersion: '2.0.0',
adapter: {
id: adapter.id,
name: adapter.name,
version: adapter.version,
baseModelId: adapter.baseModelId,
baseModelVersion: adapter.baseModelVersion,
rank: adapter.rank,
alpha: adapter.alpha,
targetModules: adapter.targetModules || ['q_proj', 'v_proj'],
},
artifacts: [{
path: adapter.path,
size: adapter.size,
sha256: adapter.sha256,
format: 'safetensors',
}],
quality: {
evaluationScore: adapter.evaluationScore,
evaluationDataset: adapter.evaluationDataset,
evaluationProof: adapter.evaluationProof,
domain: adapter.domain,
capabilities: adapter.capabilities,
},
lineage: adapter.lineage || null,
provenance: {
creator: adapter.creator,
createdAt: adapter.createdAt || new Date().toISOString(),
trainedOn: adapter.trainedOn,
trainingConfig: adapter.trainingConfig,
},
integrity: {
manifestHash: null, // Computed
signatures: [],
},
};
}
// ============================================================================
// QUARANTINE SYSTEM
// ============================================================================
/**
* Quarantine states for adapters
*/
export const QuarantineState = Object.freeze({
PENDING: 'pending',
EVALUATING: 'evaluating',
PASSED: 'passed',
FAILED: 'failed',
TRUSTED: 'trusted', // Has trusted quality proof
});
/**
* Quarantine manager for adapter verification
*/
export class AdapterQuarantine {
constructor(options = {}) {
this.trustRoot = options.trustRoot || new TrustRoot();
this.requirements = { ...ADAPTER_REQUIREMENTS, ...options.requirements };
// Quarantined adapters awaiting evaluation
this.quarantine = new Map();
// Approved adapters
this.approved = new Map();
// Failed adapters (blocked)
this.blocked = new Map();
// Evaluation test sets by domain
this.testSets = new Map();
}
/**
* Register a test set for a domain
*/
registerTestSet(domain, testCases) {
this.testSets.set(domain, testCases);
}
/**
* Quarantine an adapter for evaluation
*/
async quarantineAdapter(manifest, adapterData) {
const adapterId = manifest.adapter.id;
// 1. Verify checksum
const actualHash = createHash('sha256')
.update(Buffer.from(adapterData))
.digest('hex');
if (actualHash !== manifest.artifacts[0].sha256) {
const failure = {
adapterId,
reason: 'checksum_mismatch',
expected: manifest.artifacts[0].sha256,
actual: actualHash,
timestamp: Date.now(),
};
this.blocked.set(adapterId, failure);
return { state: QuarantineState.FAILED, failure };
}
// 2. Verify signature if required
if (this.requirements.requireSignature) {
const sigResult = this._verifySignatures(manifest);
if (!sigResult.valid) {
const failure = {
adapterId,
reason: 'invalid_signature',
details: sigResult.errors,
timestamp: Date.now(),
};
this.blocked.set(adapterId, failure);
return { state: QuarantineState.FAILED, failure };
}
}
// 3. Check for trusted quality proof
if (manifest.quality?.evaluationProof) {
const proofValid = await this._verifyQualityProof(manifest);
if (proofValid) {
this.approved.set(adapterId, {
manifest,
state: QuarantineState.TRUSTED,
approvedAt: Date.now(),
});
return { state: QuarantineState.TRUSTED };
}
}
// 4. Add to quarantine for local evaluation
this.quarantine.set(adapterId, {
manifest,
adapterData,
state: QuarantineState.PENDING,
quarantinedAt: Date.now(),
});
return { state: QuarantineState.PENDING };
}
/**
* Evaluate a quarantined adapter locally
*/
async evaluateAdapter(adapterId, inferenceSession) {
const quarantined = this.quarantine.get(adapterId);
if (!quarantined) {
throw new Error(`Adapter ${adapterId} not in quarantine`);
}
quarantined.state = QuarantineState.EVALUATING;
const manifest = quarantined.manifest;
const domain = manifest.quality?.domain || 'general';
// Get test set for domain
const testSet = this.testSets.get(domain) || this._getDefaultTestSet();
if (testSet.length === 0) {
throw new Error(`No test set available for domain: ${domain}`);
}
// Run evaluation
const results = await this._runEvaluation(
quarantined.adapterData,
testSet,
inferenceSession,
manifest.adapter.baseModelId
);
// Check if passed
const passed = results.score >= this.requirements.minEvaluationScore;
if (passed) {
this.quarantine.delete(adapterId);
this.approved.set(adapterId, {
manifest,
state: QuarantineState.PASSED,
evaluationResults: results,
approvedAt: Date.now(),
});
return { state: QuarantineState.PASSED, results };
} else {
this.quarantine.delete(adapterId);
this.blocked.set(adapterId, {
adapterId,
reason: 'evaluation_failed',
score: results.score,
required: this.requirements.minEvaluationScore,
timestamp: Date.now(),
});
return { state: QuarantineState.FAILED, results };
}
}
/**
* Check if an adapter can be used
*/
canUseAdapter(adapterId, baseModelId) {
const approved = this.approved.get(adapterId);
if (!approved) {
return { allowed: false, reason: 'not_approved' };
}
// Verify base model match
if (this.requirements.requireExactBaseMatch) {
const expectedBase = approved.manifest.adapter.baseModelId;
if (expectedBase !== baseModelId) {
return {
allowed: false,
reason: 'base_model_mismatch',
expected: expectedBase,
actual: baseModelId,
};
}
}
return { allowed: true, state: approved.state };
}
/**
* Get approved adapter data
*/
getApprovedAdapter(adapterId) {
return this.approved.get(adapterId) || null;
}
/**
* Verify signatures on adapter manifest
*/
_verifySignatures(manifest) {
if (!manifest.integrity?.signatures?.length) {
return { valid: false, errors: ['No signatures present'] };
}
return this.trustRoot.verifySignatureThreshold(
manifest.integrity.signatures,
1 // At least one valid signature for adapters
);
}
/**
* Verify a trusted quality proof
*/
async _verifyQualityProof(manifest) {
const proof = manifest.quality.evaluationProof;
if (!proof) return false;
// Check if prover is trusted
if (!this.requirements.trustedQualityProvers.includes(proof.proverId)) {
return false;
}
// Verify proof signature
const proofPayload = {
adapterId: manifest.adapter.id,
evaluationScore: manifest.quality.evaluationScore,
evaluationDataset: manifest.quality.evaluationDataset,
timestamp: proof.timestamp,
};
// In production, verify actual signature here
return proof.signature && proof.proverId;
}
/**
* Run local evaluation on adapter
*/
async _runEvaluation(adapterData, testSet, inferenceSession, baseModelId) {
const results = {
total: testSet.length,
passed: 0,
failed: 0,
errors: 0,
details: [],
};
for (const testCase of testSet) {
try {
// Apply adapter temporarily
await inferenceSession.loadAdapter(adapterData, { temporary: true });
// Run inference
const output = await inferenceSession.generate(testCase.input, {
maxTokens: testCase.maxTokens || 64,
});
// Check against expected
const passed = this._checkOutput(output, testCase.expected, testCase.criteria);
results.details.push({
input: testCase.input.slice(0, 50),
passed,
});
if (passed) {
results.passed++;
} else {
results.failed++;
}
// Unload temporary adapter
await inferenceSession.unloadAdapter();
} catch (error) {
results.errors++;
results.details.push({
input: testCase.input.slice(0, 50),
error: error.message,
});
}
}
results.score = results.passed / results.total;
return results;
}
/**
* Check if output matches expected criteria
*/
_checkOutput(output, expected, criteria = 'contains') {
const outputLower = output.toLowerCase();
const expectedLower = expected.toLowerCase();
switch (criteria) {
case 'exact':
return output.trim() === expected.trim();
case 'contains':
return outputLower.includes(expectedLower);
case 'startsWith':
return outputLower.startsWith(expectedLower);
case 'regex':
return new RegExp(expected).test(output);
default:
return outputLower.includes(expectedLower);
}
}
/**
* Get default test set for unknown domains
*/
_getDefaultTestSet() {
return [
{
input: 'Hello, how are you?',
expected: 'hello',
criteria: 'contains',
},
{
input: 'What is 2 + 2?',
expected: '4',
criteria: 'contains',
},
{
input: 'Translate to French: hello',
expected: 'bonjour',
criteria: 'contains',
},
];
}
/**
* Export quarantine state
*/
export() {
return {
quarantine: Array.from(this.quarantine.entries()),
approved: Array.from(this.approved.entries()),
blocked: Array.from(this.blocked.entries()),
};
}
/**
* Import quarantine state
*/
import(data) {
if (data.quarantine) {
this.quarantine = new Map(data.quarantine);
}
if (data.approved) {
this.approved = new Map(data.approved);
}
if (data.blocked) {
this.blocked = new Map(data.blocked);
}
}
}
// ============================================================================
// MERGE LINEAGE TRACKING
// ============================================================================
/**
* Lineage entry for merged adapters
*/
export function createMergeLineage(options) {
return {
parentAdapterIds: options.parentIds,
mergeMethod: options.method, // 'ties', 'dare', 'task_arithmetic', 'linear'
mergeParameters: options.parameters, // Method-specific params
mergeSeed: options.seed || Math.floor(Math.random() * 2 ** 32),
evaluationMetrics: options.metrics || {},
mergerIdentity: options.mergerId,
mergeTimestamp: new Date().toISOString(),
signature: null, // To be filled after signing
};
}
/**
* Lineage tracker for adapter merges
*/
export class AdapterLineage {
constructor(options = {}) {
this.trustRoot = options.trustRoot || new TrustRoot();
// DAG of adapter lineage
this.lineageGraph = new Map();
// Root adapters (no parents)
this.roots = new Set();
}
/**
* Register a new adapter in lineage
*/
registerAdapter(adapterId, manifest) {
const lineage = manifest.lineage;
const node = {
adapterId,
version: manifest.adapter.version,
baseModelId: manifest.adapter.baseModelId,
parents: lineage?.parentAdapterIds || [],
children: [],
lineage,
registeredAt: Date.now(),
};
this.lineageGraph.set(adapterId, node);
// Update parent-child relationships
if (node.parents.length === 0) {
this.roots.add(adapterId);
} else {
for (const parentId of node.parents) {
const parent = this.lineageGraph.get(parentId);
if (parent) {
parent.children.push(adapterId);
}
}
}
return node;
}
/**
* Get full ancestry path for an adapter
*/
getAncestry(adapterId) {
const ancestry = [];
const visited = new Set();
const queue = [adapterId];
while (queue.length > 0) {
const current = queue.shift();
if (visited.has(current)) continue;
visited.add(current);
const node = this.lineageGraph.get(current);
if (node) {
ancestry.push({
adapterId: current,
version: node.version,
baseModelId: node.baseModelId,
mergeMethod: node.lineage?.mergeMethod,
});
for (const parentId of node.parents) {
queue.push(parentId);
}
}
}
return ancestry;
}
/**
* Verify lineage integrity
*/
verifyLineage(adapterId) {
const node = this.lineageGraph.get(adapterId);
if (!node) {
return { valid: false, error: 'Adapter not found' };
}
const errors = [];
// Check all parents exist
for (const parentId of node.parents) {
if (!this.lineageGraph.has(parentId)) {
errors.push(`Missing parent: ${parentId}`);
}
}
// Verify lineage signature if present
if (node.lineage?.signature) {
// In production, verify actual signature
const sigValid = true; // Placeholder
if (!sigValid) {
errors.push('Invalid lineage signature');
}
}
// Check for circular references
const hasCircle = this._detectCircle(adapterId, new Set());
if (hasCircle) {
errors.push('Circular lineage detected');
}
return {
valid: errors.length === 0,
errors,
ancestry: this.getAncestry(adapterId),
};
}
/**
* Detect circular references in lineage
*/
_detectCircle(adapterId, visited) {
if (visited.has(adapterId)) return true;
visited.add(adapterId);
const node = this.lineageGraph.get(adapterId);
if (!node) return false;
for (const parentId of node.parents) {
if (this._detectCircle(parentId, new Set(visited))) {
return true;
}
}
return false;
}
/**
* Get descendants of an adapter
*/
getDescendants(adapterId) {
const descendants = [];
const queue = [adapterId];
const visited = new Set();
while (queue.length > 0) {
const current = queue.shift();
if (visited.has(current)) continue;
visited.add(current);
const node = this.lineageGraph.get(current);
if (node) {
for (const childId of node.children) {
descendants.push(childId);
queue.push(childId);
}
}
}
return descendants;
}
/**
* Compute reproducibility hash for a merge
*/
computeReproducibilityHash(lineage) {
const payload = {
parents: lineage.parentAdapterIds.sort(),
method: lineage.mergeMethod,
parameters: lineage.mergeParameters,
seed: lineage.mergeSeed,
};
return hashCanonical(payload);
}
/**
* Export lineage graph
*/
export() {
return {
nodes: Array.from(this.lineageGraph.entries()),
roots: Array.from(this.roots),
};
}
/**
* Import lineage graph
*/
import(data) {
if (data.nodes) {
this.lineageGraph = new Map(data.nodes);
}
if (data.roots) {
this.roots = new Set(data.roots);
}
}
}
// ============================================================================
// ADAPTER POOL WITH SECURITY
// ============================================================================
/**
* Secure adapter pool with quarantine integration
*/
export class SecureAdapterPool {
constructor(options = {}) {
this.maxSlots = options.maxSlots || 16;
this.quarantine = new AdapterQuarantine(options);
this.lineage = new AdapterLineage(options);
// Active adapters (LRU)
this.activeAdapters = new Map();
this.accessOrder = [];
}
/**
* Add adapter with full security checks
*/
async addAdapter(manifest, adapterData, inferenceSession = null) {
const adapterId = manifest.adapter.id;
// 1. Quarantine and verify
const quarantineResult = await this.quarantine.quarantineAdapter(manifest, adapterData);
if (quarantineResult.state === QuarantineState.FAILED) {
throw new Error(`Adapter blocked: ${quarantineResult.failure.reason}`);
}
// 2. If not trusted, run local evaluation
if (quarantineResult.state === QuarantineState.PENDING) {
if (!inferenceSession) {
throw new Error('Inference session required for local evaluation');
}
const evalResult = await this.quarantine.evaluateAdapter(adapterId, inferenceSession);
if (evalResult.state === QuarantineState.FAILED) {
throw new Error(`Adapter failed evaluation: score ${evalResult.results.score}`);
}
}
// 3. Register in lineage
this.lineage.registerAdapter(adapterId, manifest);
// 4. Add to active pool
await this._addToPool(adapterId, adapterData, manifest);
return { adapterId, state: 'active' };
}
/**
* Get an adapter if allowed
*/
getAdapter(adapterId, baseModelId) {
// Check if can use
const check = this.quarantine.canUseAdapter(adapterId, baseModelId);
if (!check.allowed) {
return { allowed: false, reason: check.reason };
}
// Get from pool
const adapter = this.activeAdapters.get(adapterId);
if (!adapter) {
return { allowed: false, reason: 'not_in_pool' };
}
// Update access order
this._updateAccessOrder(adapterId);
return { allowed: true, adapter };
}
/**
* Add to pool with LRU eviction
*/
async _addToPool(adapterId, adapterData, manifest) {
// Evict if at capacity
while (this.activeAdapters.size >= this.maxSlots) {
const evictId = this.accessOrder.shift();
this.activeAdapters.delete(evictId);
}
this.activeAdapters.set(adapterId, {
data: adapterData,
manifest,
loadedAt: Date.now(),
});
this._updateAccessOrder(adapterId);
}
/**
* Update LRU access order
*/
_updateAccessOrder(adapterId) {
const index = this.accessOrder.indexOf(adapterId);
if (index > -1) {
this.accessOrder.splice(index, 1);
}
this.accessOrder.push(adapterId);
}
/**
* Get pool statistics
*/
getStats() {
return {
activeCount: this.activeAdapters.size,
maxSlots: this.maxSlots,
quarantinedCount: this.quarantine.quarantine.size,
approvedCount: this.quarantine.approved.size,
blockedCount: this.quarantine.blocked.size,
lineageNodes: this.lineage.lineageGraph.size,
};
}
}
// ============================================================================
// EXPORTS
// ============================================================================
export default {
ADAPTER_REQUIREMENTS,
QuarantineState,
createAdapterManifest,
AdapterQuarantine,
createMergeLineage,
AdapterLineage,
SecureAdapterPool,
};

View File

@@ -0,0 +1,688 @@
/**
* @ruvector/edge-net Benchmark Utilities
*
* Comprehensive benchmarking for model optimization
*
* @module @ruvector/edge-net/models/benchmark
*/
import { EventEmitter } from 'events';
import { ModelOptimizer, TARGET_MODELS, QUANTIZATION_CONFIGS } from './model-optimizer.js';
// ============================================
// BENCHMARK CONFIGURATION
// ============================================
/**
* Benchmark profiles for different scenarios
*/
export const BENCHMARK_PROFILES = {
'quick': {
iterations: 50,
warmupIterations: 5,
inputSizes: [[1, 128]],
quantMethods: ['int8'],
},
'standard': {
iterations: 100,
warmupIterations: 10,
inputSizes: [[1, 128], [1, 512], [4, 256]],
quantMethods: ['int8', 'int4', 'fp16'],
},
'comprehensive': {
iterations: 500,
warmupIterations: 50,
inputSizes: [[1, 64], [1, 128], [1, 256], [1, 512], [1, 1024], [4, 256], [8, 128]],
quantMethods: ['int8', 'int4', 'fp16', 'int8-fp16-mixed'],
},
'edge-device': {
iterations: 100,
warmupIterations: 10,
inputSizes: [[1, 128], [1, 256]],
quantMethods: ['int4'],
memoryLimit: 512, // MB
},
'accuracy-focus': {
iterations: 200,
warmupIterations: 20,
inputSizes: [[1, 512]],
quantMethods: ['fp16', 'int8'],
measureAccuracy: true,
},
};
// ============================================
// ACCURACY MEASUREMENT
// ============================================
/**
* Accuracy metrics for quantized models
*/
export class AccuracyMeter {
constructor() {
this.predictions = [];
this.groundTruth = [];
this.originalOutputs = [];
this.quantizedOutputs = [];
}
/**
* Add prediction pair for accuracy measurement
*/
addPrediction(original, quantized, groundTruth = null) {
this.originalOutputs.push(original);
this.quantizedOutputs.push(quantized);
if (groundTruth !== null) {
this.groundTruth.push(groundTruth);
}
}
/**
* Compute Mean Squared Error
*/
computeMSE() {
if (this.originalOutputs.length === 0) return 0;
let totalMSE = 0;
let count = 0;
for (let i = 0; i < this.originalOutputs.length; i++) {
const orig = this.originalOutputs[i];
const quant = this.quantizedOutputs[i];
let mse = 0;
const len = Math.min(orig.length, quant.length);
for (let j = 0; j < len; j++) {
const diff = orig[j] - quant[j];
mse += diff * diff;
}
totalMSE += mse / len;
count++;
}
return totalMSE / count;
}
/**
* Compute cosine similarity between original and quantized
*/
computeCosineSimilarity() {
if (this.originalOutputs.length === 0) return 1.0;
let totalSim = 0;
for (let i = 0; i < this.originalOutputs.length; i++) {
const orig = this.originalOutputs[i];
const quant = this.quantizedOutputs[i];
let dot = 0, normA = 0, normB = 0;
const len = Math.min(orig.length, quant.length);
for (let j = 0; j < len; j++) {
dot += orig[j] * quant[j];
normA += orig[j] * orig[j];
normB += quant[j] * quant[j];
}
totalSim += dot / (Math.sqrt(normA) * Math.sqrt(normB) + 1e-8);
}
return totalSim / this.originalOutputs.length;
}
/**
* Compute max absolute error
*/
computeMaxError() {
let maxError = 0;
for (let i = 0; i < this.originalOutputs.length; i++) {
const orig = this.originalOutputs[i];
const quant = this.quantizedOutputs[i];
const len = Math.min(orig.length, quant.length);
for (let j = 0; j < len; j++) {
maxError = Math.max(maxError, Math.abs(orig[j] - quant[j]));
}
}
return maxError;
}
/**
* Get comprehensive accuracy metrics
*/
getMetrics() {
const mse = this.computeMSE();
return {
mse,
rmse: Math.sqrt(mse),
cosineSimilarity: this.computeCosineSimilarity(),
maxError: this.computeMaxError(),
samples: this.originalOutputs.length,
accuracyRetained: this.computeCosineSimilarity() * 100,
};
}
/**
* Reset meter
*/
reset() {
this.predictions = [];
this.groundTruth = [];
this.originalOutputs = [];
this.quantizedOutputs = [];
}
}
// ============================================
// LATENCY PROFILER
// ============================================
/**
* Detailed latency profiling
*/
export class LatencyProfiler {
constructor() {
this.measurements = new Map();
}
/**
* Start timing a section
*/
start(label) {
if (!this.measurements.has(label)) {
this.measurements.set(label, {
samples: [],
running: null,
});
}
this.measurements.get(label).running = performance.now();
}
/**
* End timing a section
*/
end(label) {
const entry = this.measurements.get(label);
if (entry && entry.running !== null) {
const duration = performance.now() - entry.running;
entry.samples.push(duration);
entry.running = null;
return duration;
}
return 0;
}
/**
* Get statistics for a label
*/
getStats(label) {
const entry = this.measurements.get(label);
if (!entry || entry.samples.length === 0) {
return null;
}
const samples = [...entry.samples].sort((a, b) => a - b);
const sum = samples.reduce((a, b) => a + b, 0);
return {
label,
count: samples.length,
mean: sum / samples.length,
median: samples[Math.floor(samples.length / 2)],
min: samples[0],
max: samples[samples.length - 1],
p95: samples[Math.floor(samples.length * 0.95)],
p99: samples[Math.floor(samples.length * 0.99)],
std: Math.sqrt(samples.reduce((acc, v) => acc + Math.pow(v - sum / samples.length, 2), 0) / samples.length),
};
}
/**
* Get all statistics
*/
getAllStats() {
const stats = {};
for (const label of this.measurements.keys()) {
stats[label] = this.getStats(label);
}
return stats;
}
/**
* Reset profiler
*/
reset() {
this.measurements.clear();
}
}
// ============================================
// MEMORY PROFILER
// ============================================
/**
* Memory usage profiler
*/
export class MemoryProfiler {
constructor() {
this.snapshots = [];
this.peakMemory = 0;
}
/**
* Take memory snapshot
*/
snapshot(label = 'snapshot') {
const memUsage = this.getMemoryUsage();
const snapshot = {
label,
timestamp: Date.now(),
...memUsage,
};
this.snapshots.push(snapshot);
this.peakMemory = Math.max(this.peakMemory, memUsage.heapUsed);
return snapshot;
}
/**
* Get current memory usage
*/
getMemoryUsage() {
if (typeof process !== 'undefined' && process.memoryUsage) {
const usage = process.memoryUsage();
return {
heapUsed: usage.heapUsed / (1024 * 1024),
heapTotal: usage.heapTotal / (1024 * 1024),
external: usage.external / (1024 * 1024),
rss: usage.rss / (1024 * 1024),
};
}
// Browser fallback
if (typeof performance !== 'undefined' && performance.memory) {
return {
heapUsed: performance.memory.usedJSHeapSize / (1024 * 1024),
heapTotal: performance.memory.totalJSHeapSize / (1024 * 1024),
external: 0,
rss: 0,
};
}
return { heapUsed: 0, heapTotal: 0, external: 0, rss: 0 };
}
/**
* Get memory delta between two snapshots
*/
getDelta(startLabel, endLabel) {
const start = this.snapshots.find(s => s.label === startLabel);
const end = this.snapshots.find(s => s.label === endLabel);
if (!start || !end) return null;
return {
heapDelta: end.heapUsed - start.heapUsed,
timeDelta: end.timestamp - start.timestamp,
};
}
/**
* Get profiler summary
*/
getSummary() {
return {
snapshots: this.snapshots.length,
peakMemoryMB: this.peakMemory,
currentMemoryMB: this.getMemoryUsage().heapUsed,
history: this.snapshots,
};
}
/**
* Reset profiler
*/
reset() {
this.snapshots = [];
this.peakMemory = 0;
}
}
// ============================================
// COMPREHENSIVE BENCHMARK RUNNER
// ============================================
/**
* ComprehensiveBenchmark - Full benchmark suite for model optimization
*/
export class ComprehensiveBenchmark extends EventEmitter {
constructor(options = {}) {
super();
this.optimizer = options.optimizer || new ModelOptimizer();
this.latencyProfiler = new LatencyProfiler();
this.memoryProfiler = new MemoryProfiler();
this.accuracyMeter = new AccuracyMeter();
this.results = [];
}
/**
* Run benchmark suite on a model
*/
async runSuite(model, profile = 'standard') {
const profileConfig = BENCHMARK_PROFILES[profile] || BENCHMARK_PROFILES.standard;
const modelConfig = TARGET_MODELS[model];
if (!modelConfig) {
throw new Error(`Unknown model: ${model}`);
}
this.emit('suite:start', { model, profile });
const suiteResults = {
model,
profile,
modelConfig,
timestamp: new Date().toISOString(),
benchmarks: [],
};
// Memory baseline
this.memoryProfiler.snapshot('baseline');
// Benchmark each quantization method
for (const method of profileConfig.quantMethods) {
const methodResult = await this.benchmarkQuantization(
model,
method,
profileConfig
);
suiteResults.benchmarks.push(methodResult);
}
// Memory after benchmarks
this.memoryProfiler.snapshot('after-benchmarks');
// Add memory profile
suiteResults.memory = this.memoryProfiler.getSummary();
// Add summary
suiteResults.summary = this.generateSummary(suiteResults);
this.results.push(suiteResults);
this.emit('suite:complete', suiteResults);
return suiteResults;
}
/**
* Benchmark a specific quantization method
*/
async benchmarkQuantization(model, method, config) {
this.emit('benchmark:start', { model, method });
const quantConfig = QUANTIZATION_CONFIGS[method];
const modelConfig = TARGET_MODELS[model];
// Quantize model
this.latencyProfiler.start('quantization');
const quantResult = await this.optimizer.quantize(model, method);
this.latencyProfiler.end('quantization');
// Simulate inference benchmarks for each input size
const inferenceBenchmarks = [];
for (const inputSize of config.inputSizes) {
const batchSize = inputSize[0];
const seqLen = inputSize[1];
this.latencyProfiler.start(`inference-${batchSize}x${seqLen}`);
// Warmup
for (let i = 0; i < config.warmupIterations; i++) {
await this.simulateInference(modelConfig, batchSize, seqLen, method);
}
// Measure
const times = [];
for (let i = 0; i < config.iterations; i++) {
const start = performance.now();
await this.simulateInference(modelConfig, batchSize, seqLen, method);
times.push(performance.now() - start);
}
this.latencyProfiler.end(`inference-${batchSize}x${seqLen}`);
times.sort((a, b) => a - b);
inferenceBenchmarks.push({
inputSize: `${batchSize}x${seqLen}`,
iterations: config.iterations,
meanMs: times.reduce((a, b) => a + b) / times.length,
medianMs: times[Math.floor(times.length / 2)],
p95Ms: times[Math.floor(times.length * 0.95)],
minMs: times[0],
maxMs: times[times.length - 1],
tokensPerSecond: (seqLen * batchSize * 1000) / (times.reduce((a, b) => a + b) / times.length),
});
}
// Measure accuracy if requested
let accuracyMetrics = null;
if (config.measureAccuracy) {
// Generate test outputs
for (let i = 0; i < 100; i++) {
const original = new Float32Array(modelConfig.hiddenSize).map(() => Math.random());
const quantized = this.simulateQuantizedOutput(original, method);
this.accuracyMeter.addPrediction(Array.from(original), Array.from(quantized));
}
accuracyMetrics = this.accuracyMeter.getMetrics();
this.accuracyMeter.reset();
}
const result = {
method,
quantization: quantResult,
inference: inferenceBenchmarks,
accuracy: accuracyMetrics,
latencyProfile: this.latencyProfiler.getAllStats(),
compression: {
original: modelConfig.originalSize,
quantized: modelConfig.originalSize / quantConfig.compression,
ratio: quantConfig.compression,
},
recommendation: this.getRecommendation(model, method, inferenceBenchmarks),
};
this.emit('benchmark:complete', result);
return result;
}
/**
* Simulate model inference
*/
async simulateInference(config, batchSize, seqLen, method) {
// Base latency depends on model size and batch
const quantConfig = QUANTIZATION_CONFIGS[method];
const baseLatency = (config.originalSize / 100) * (batchSize * seqLen / 512);
const speedup = quantConfig?.speedup || 1;
const latency = baseLatency / speedup;
await new Promise(resolve => setTimeout(resolve, latency));
return new Float32Array(config.hiddenSize).map(() => Math.random());
}
/**
* Simulate quantized output with added noise
*/
simulateQuantizedOutput(original, method) {
const quantConfig = QUANTIZATION_CONFIGS[method];
const noise = quantConfig?.accuracyLoss || 0.01;
return new Float32Array(original.length).map((_, i) => {
return original[i] + (Math.random() - 0.5) * 2 * noise;
});
}
/**
* Generate recommendation based on benchmark results
*/
getRecommendation(model, method, inferenceBenchmarks) {
const modelConfig = TARGET_MODELS[model];
const quantConfig = QUANTIZATION_CONFIGS[method];
const avgLatency = inferenceBenchmarks.reduce((a, b) => a + b.meanMs, 0) / inferenceBenchmarks.length;
const targetMet = (modelConfig.originalSize / quantConfig.compression) <= modelConfig.targetSize;
let score = 0;
let reasons = [];
// Size target met
if (targetMet) {
score += 30;
reasons.push('Meets size target');
}
// Good latency
if (avgLatency < 10) {
score += 30;
reasons.push('Excellent latency (<10ms)');
} else if (avgLatency < 50) {
score += 20;
reasons.push('Good latency (<50ms)');
}
// Low accuracy loss
if (quantConfig.accuracyLoss < 0.02) {
score += 25;
reasons.push('Minimal accuracy loss (<2%)');
} else if (quantConfig.accuracyLoss < 0.05) {
score += 15;
reasons.push('Acceptable accuracy loss (<5%)');
}
// Compression ratio
if (quantConfig.compression >= 4) {
score += 15;
reasons.push('High compression (4x+)');
}
return {
score,
rating: score >= 80 ? 'Excellent' : score >= 60 ? 'Good' : score >= 40 ? 'Acceptable' : 'Poor',
reasons,
recommended: score >= 60,
};
}
/**
* Generate suite summary
*/
generateSummary(suiteResults) {
const benchmarks = suiteResults.benchmarks;
// Find best method
let bestMethod = null;
let bestScore = 0;
for (const b of benchmarks) {
if (b.recommendation.score > bestScore) {
bestScore = b.recommendation.score;
bestMethod = b.method;
}
}
// Calculate averages
const avgLatency = benchmarks.reduce((sum, b) => {
return sum + b.inference.reduce((s, i) => s + i.meanMs, 0) / b.inference.length;
}, 0) / benchmarks.length;
return {
modelKey: suiteResults.model,
modelType: suiteResults.modelConfig.type,
originalSizeMB: suiteResults.modelConfig.originalSize,
targetSizeMB: suiteResults.modelConfig.targetSize,
bestMethod,
bestScore,
avgLatencyMs: avgLatency,
methodsEvaluated: benchmarks.length,
recommendation: bestMethod ? `Use ${bestMethod} quantization for optimal edge deployment` : 'No suitable method found',
};
}
/**
* Run benchmarks on all target models
*/
async runAllModels(profile = 'standard') {
const allResults = [];
for (const modelKey of Object.keys(TARGET_MODELS)) {
try {
const result = await this.runSuite(modelKey, profile);
allResults.push(result);
} catch (error) {
allResults.push({
model: modelKey,
error: error.message,
});
}
}
return {
timestamp: new Date().toISOString(),
profile,
results: allResults,
summary: this.generateOverallSummary(allResults),
};
}
/**
* Generate overall summary for all models
*/
generateOverallSummary(allResults) {
const successful = allResults.filter(r => !r.error);
return {
totalModels: allResults.length,
successfulBenchmarks: successful.length,
failedBenchmarks: allResults.length - successful.length,
recommendations: successful.map(r => ({
model: r.model,
bestMethod: r.summary?.bestMethod,
score: r.summary?.bestScore,
})),
};
}
/**
* Export results to JSON
*/
exportResults() {
return {
exported: new Date().toISOString(),
results: this.results,
};
}
/**
* Reset benchmark state
*/
reset() {
this.latencyProfiler.reset();
this.memoryProfiler.reset();
this.accuracyMeter.reset();
this.results = [];
}
}
// ============================================
// EXPORTS
// ============================================
// BENCHMARK_PROFILES already exported at declaration (line 19)
export default ComprehensiveBenchmark;

View File

@@ -0,0 +1,791 @@
/**
* @ruvector/edge-net Distribution Manager
*
* Handles model distribution across multiple sources:
* - Google Cloud Storage (GCS)
* - IPFS (via web3.storage or nft.storage)
* - CDN with fallback support
*
* Features:
* - Integrity verification (SHA256)
* - Progress tracking for large files
* - Automatic source failover
*
* @module @ruvector/edge-net/models/distribution
*/
import { EventEmitter } from 'events';
import { createHash, randomBytes } from 'crypto';
import { promises as fs } from 'fs';
import path from 'path';
import https from 'https';
import http from 'http';
import { URL } from 'url';
// ============================================
// CONSTANTS
// ============================================
const DEFAULT_GCS_BUCKET = 'ruvector-models';
const DEFAULT_CDN_BASE = 'https://models.ruvector.dev';
const DEFAULT_IPFS_GATEWAY = 'https://w3s.link/ipfs';
const CHUNK_SIZE = 1024 * 1024; // 1MB chunks for streaming
const MAX_RETRIES = 3;
const RETRY_DELAY_MS = 1000;
// ============================================
// SOURCE TYPES
// ============================================
/**
* Source priority order (lower = higher priority)
*/
export const SOURCE_PRIORITY = {
cdn: 1,
gcs: 2,
ipfs: 3,
fallback: 99,
};
/**
* Source URL patterns
*/
export const SOURCE_PATTERNS = {
gcs: /^gs:\/\/([^/]+)\/(.+)$/,
ipfs: /^ipfs:\/\/(.+)$/,
http: /^https?:\/\/.+$/,
};
// ============================================
// PROGRESS TRACKER
// ============================================
/**
* Progress tracker for file transfers
*/
export class ProgressTracker extends EventEmitter {
constructor(totalBytes = 0) {
super();
this.totalBytes = totalBytes;
this.bytesTransferred = 0;
this.startTime = Date.now();
this.lastUpdateTime = Date.now();
this.lastBytesTransferred = 0;
}
/**
* Update progress
* @param {number} bytes - Bytes transferred in this chunk
*/
update(bytes) {
this.bytesTransferred += bytes;
const now = Date.now();
// Calculate speed (bytes per second)
const timeDelta = (now - this.lastUpdateTime) / 1000;
const bytesDelta = this.bytesTransferred - this.lastBytesTransferred;
const speed = timeDelta > 0 ? bytesDelta / timeDelta : 0;
// Calculate ETA
const remaining = this.totalBytes - this.bytesTransferred;
const eta = speed > 0 ? remaining / speed : 0;
const progress = {
bytesTransferred: this.bytesTransferred,
totalBytes: this.totalBytes,
percent: this.totalBytes > 0
? Math.round((this.bytesTransferred / this.totalBytes) * 100)
: 0,
speed: Math.round(speed),
speedMBps: Math.round(speed / (1024 * 1024) * 100) / 100,
eta: Math.round(eta),
elapsed: Math.round((now - this.startTime) / 1000),
};
this.lastUpdateTime = now;
this.lastBytesTransferred = this.bytesTransferred;
this.emit('progress', progress);
if (this.bytesTransferred >= this.totalBytes) {
this.emit('complete', progress);
}
}
/**
* Mark as complete
*/
complete() {
this.bytesTransferred = this.totalBytes;
const elapsed = (Date.now() - this.startTime) / 1000;
this.emit('complete', {
bytesTransferred: this.bytesTransferred,
totalBytes: this.totalBytes,
percent: 100,
elapsed: Math.round(elapsed),
averageSpeed: Math.round(this.totalBytes / elapsed),
});
}
/**
* Mark as failed
* @param {Error} error - Failure error
*/
fail(error) {
this.emit('error', {
error,
bytesTransferred: this.bytesTransferred,
totalBytes: this.totalBytes,
});
}
}
// ============================================
// DISTRIBUTION MANAGER
// ============================================
/**
* DistributionManager - Manages model uploads and downloads
*/
export class DistributionManager extends EventEmitter {
/**
* Create a new DistributionManager
* @param {object} options - Configuration options
*/
constructor(options = {}) {
super();
this.id = `dist-${randomBytes(6).toString('hex')}`;
// GCS configuration
this.gcsConfig = {
bucket: options.gcsBucket || DEFAULT_GCS_BUCKET,
projectId: options.gcsProjectId || process.env.GCS_PROJECT_ID,
keyFilePath: options.gcsKeyFile || process.env.GOOGLE_APPLICATION_CREDENTIALS,
};
// IPFS configuration
this.ipfsConfig = {
gateway: options.ipfsGateway || DEFAULT_IPFS_GATEWAY,
web3StorageToken: options.web3StorageToken || process.env.WEB3_STORAGE_TOKEN,
nftStorageToken: options.nftStorageToken || process.env.NFT_STORAGE_TOKEN,
};
// CDN configuration
this.cdnConfig = {
baseUrl: options.cdnBaseUrl || DEFAULT_CDN_BASE,
fallbackUrls: options.cdnFallbacks || [],
};
// Download cache (in-flight downloads)
this.activeDownloads = new Map();
// Stats
this.stats = {
uploads: 0,
downloads: 0,
bytesUploaded: 0,
bytesDownloaded: 0,
failures: 0,
};
}
// ============================================
// URL GENERATION
// ============================================
/**
* Generate CDN URL for a model
* @param {string} modelName - Model name
* @param {string} version - Model version
* @param {string} filename - Filename
* @returns {string}
*/
getCdnUrl(modelName, version, filename = null) {
const file = filename || `${modelName}.onnx`;
return `${this.cdnConfig.baseUrl}/${modelName}/${version}/${file}`;
}
/**
* Generate GCS URL for a model
* @param {string} modelName - Model name
* @param {string} version - Model version
* @param {string} filename - Filename
* @returns {string}
*/
getGcsUrl(modelName, version, filename = null) {
const file = filename || `${modelName}.onnx`;
return `gs://${this.gcsConfig.bucket}/${modelName}/${version}/${file}`;
}
/**
* Generate IPFS URL from CID
* @param {string} cid - IPFS Content ID
* @returns {string}
*/
getIpfsUrl(cid) {
return `ipfs://${cid}`;
}
/**
* Generate HTTP gateway URL for IPFS
* @param {string} cid - IPFS Content ID
* @returns {string}
*/
getIpfsGatewayUrl(cid) {
// Handle both ipfs:// URLs and raw CIDs
const cleanCid = cid.replace(/^ipfs:\/\//, '');
return `${this.ipfsConfig.gateway}/${cleanCid}`;
}
/**
* Generate all source URLs for a model
* @param {object} sources - Source configuration from metadata
* @param {string} modelName - Model name
* @param {string} version - Version
* @returns {object[]} Sorted list of sources with URLs
*/
generateSourceUrls(sources, modelName, version) {
const urls = [];
// CDN (highest priority)
if (sources.cdn) {
urls.push({
type: 'cdn',
url: sources.cdn,
priority: SOURCE_PRIORITY.cdn,
});
} else {
// Auto-generate CDN URL
urls.push({
type: 'cdn',
url: this.getCdnUrl(modelName, version),
priority: SOURCE_PRIORITY.cdn,
});
}
// GCS
if (sources.gcs) {
const gcsMatch = sources.gcs.match(SOURCE_PATTERNS.gcs);
if (gcsMatch) {
// Convert gs:// to HTTPS URL
const [, bucket, path] = gcsMatch;
urls.push({
type: 'gcs',
url: `https://storage.googleapis.com/${bucket}/${path}`,
originalUrl: sources.gcs,
priority: SOURCE_PRIORITY.gcs,
});
}
}
// IPFS
if (sources.ipfs) {
urls.push({
type: 'ipfs',
url: this.getIpfsGatewayUrl(sources.ipfs),
originalUrl: sources.ipfs,
priority: SOURCE_PRIORITY.ipfs,
});
}
// Fallback URLs
for (const fallback of this.cdnConfig.fallbackUrls) {
urls.push({
type: 'fallback',
url: `${fallback}/${modelName}/${version}/${modelName}.onnx`,
priority: SOURCE_PRIORITY.fallback,
});
}
// Sort by priority
return urls.sort((a, b) => a.priority - b.priority);
}
// ============================================
// DOWNLOAD
// ============================================
/**
* Download a model from the best available source
* @param {object} metadata - Model metadata
* @param {object} options - Download options
* @returns {Promise<Buffer>}
*/
async download(metadata, options = {}) {
const { name, version, sources, size, hash } = metadata;
const key = `${name}@${version}`;
// Check for in-flight download
if (this.activeDownloads.has(key)) {
return this.activeDownloads.get(key);
}
const downloadPromise = this._executeDownload(metadata, options);
this.activeDownloads.set(key, downloadPromise);
try {
const result = await downloadPromise;
return result;
} finally {
this.activeDownloads.delete(key);
}
}
/**
* Execute the download with fallback
* @private
*/
async _executeDownload(metadata, options = {}) {
const { name, version, sources, size, hash } = metadata;
const sourceUrls = this.generateSourceUrls(sources, name, version);
const progress = new ProgressTracker(size);
if (options.onProgress) {
progress.on('progress', options.onProgress);
}
let lastError = null;
for (const source of sourceUrls) {
try {
this.emit('download_attempt', { source, model: name, version });
const data = await this._downloadFromUrl(source.url, {
...options,
progress,
expectedSize: size,
});
// Verify integrity
if (hash) {
const computedHash = `sha256:${createHash('sha256').update(data).digest('hex')}`;
if (computedHash !== hash) {
throw new Error(`Hash mismatch: expected ${hash}, got ${computedHash}`);
}
}
this.stats.downloads++;
this.stats.bytesDownloaded += data.length;
progress.complete();
this.emit('download_complete', {
source,
model: name,
version,
size: data.length,
});
return data;
} catch (error) {
lastError = error;
this.emit('download_failed', {
source,
model: name,
version,
error: error.message,
});
// Continue to next source
continue;
}
}
this.stats.failures++;
progress.fail(lastError);
throw new Error(`Failed to download ${name}@${version} from all sources: ${lastError?.message}`);
}
/**
* Download from a URL with streaming and progress
* @private
*/
_downloadFromUrl(url, options = {}) {
return new Promise((resolve, reject) => {
const { progress, expectedSize, timeout = 60000 } = options;
const parsedUrl = new URL(url);
const protocol = parsedUrl.protocol === 'https:' ? https : http;
const chunks = [];
let bytesReceived = 0;
const request = protocol.get(url, {
timeout,
headers: {
'User-Agent': 'RuVector-EdgeNet/1.0',
'Accept': 'application/octet-stream',
},
}, (response) => {
// Handle redirects
if (response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
this._downloadFromUrl(response.headers.location, options)
.then(resolve)
.catch(reject);
return;
}
if (response.statusCode !== 200) {
reject(new Error(`HTTP ${response.statusCode}: ${response.statusMessage}`));
return;
}
const contentLength = parseInt(response.headers['content-length'] || expectedSize || 0, 10);
if (progress && contentLength) {
progress.totalBytes = contentLength;
}
response.on('data', (chunk) => {
chunks.push(chunk);
bytesReceived += chunk.length;
if (progress) {
progress.update(chunk.length);
}
});
response.on('end', () => {
const data = Buffer.concat(chunks);
resolve(data);
});
response.on('error', reject);
});
request.on('error', reject);
request.on('timeout', () => {
request.destroy();
reject(new Error('Request timeout'));
});
});
}
/**
* Download to a file with streaming
* @param {object} metadata - Model metadata
* @param {string} destPath - Destination file path
* @param {object} options - Download options
*/
async downloadToFile(metadata, destPath, options = {}) {
const data = await this.download(metadata, options);
// Ensure directory exists
const dir = path.dirname(destPath);
await fs.mkdir(dir, { recursive: true });
await fs.writeFile(destPath, data);
return {
path: destPath,
size: data.length,
};
}
// ============================================
// UPLOAD
// ============================================
/**
* Upload a model to Google Cloud Storage
* @param {Buffer} data - Model data
* @param {string} modelName - Model name
* @param {string} version - Version
* @param {object} options - Upload options
* @returns {Promise<string>} GCS URL
*/
async uploadToGcs(data, modelName, version, options = {}) {
const { filename = `${modelName}.onnx` } = options;
const gcsPath = `${modelName}/${version}/${filename}`;
// Check for @google-cloud/storage
let storage;
try {
const { Storage } = await import('@google-cloud/storage');
storage = new Storage({
projectId: this.gcsConfig.projectId,
keyFilename: this.gcsConfig.keyFilePath,
});
} catch (error) {
throw new Error('GCS upload requires @google-cloud/storage package');
}
const bucket = storage.bucket(this.gcsConfig.bucket);
const file = bucket.file(gcsPath);
const progress = new ProgressTracker(data.length);
if (options.onProgress) {
progress.on('progress', options.onProgress);
}
await new Promise((resolve, reject) => {
const stream = file.createWriteStream({
metadata: {
contentType: 'application/octet-stream',
metadata: {
modelName,
version,
hash: `sha256:${createHash('sha256').update(data).digest('hex')}`,
},
},
});
stream.on('error', reject);
stream.on('finish', resolve);
// Write in chunks for progress tracking
let offset = 0;
const writeChunk = () => {
while (offset < data.length) {
const end = Math.min(offset + CHUNK_SIZE, data.length);
const chunk = data.slice(offset, end);
if (!stream.write(chunk)) {
offset = end;
stream.once('drain', writeChunk);
return;
}
progress.update(chunk.length);
offset = end;
}
stream.end();
};
writeChunk();
});
progress.complete();
this.stats.uploads++;
this.stats.bytesUploaded += data.length;
const gcsUrl = this.getGcsUrl(modelName, version, filename);
this.emit('upload_complete', {
type: 'gcs',
url: gcsUrl,
model: modelName,
version,
size: data.length,
});
return gcsUrl;
}
/**
* Upload a model to IPFS via web3.storage
* @param {Buffer} data - Model data
* @param {string} modelName - Model name
* @param {string} version - Version
* @param {object} options - Upload options
* @returns {Promise<string>} IPFS CID
*/
async uploadToIpfs(data, modelName, version, options = {}) {
const { filename = `${modelName}.onnx`, provider = 'web3storage' } = options;
let cid;
if (provider === 'web3storage' && this.ipfsConfig.web3StorageToken) {
cid = await this._uploadToWeb3Storage(data, filename);
} else if (provider === 'nftstorage' && this.ipfsConfig.nftStorageToken) {
cid = await this._uploadToNftStorage(data, filename);
} else {
throw new Error('No IPFS provider configured. Set WEB3_STORAGE_TOKEN or NFT_STORAGE_TOKEN');
}
this.stats.uploads++;
this.stats.bytesUploaded += data.length;
const ipfsUrl = this.getIpfsUrl(cid);
this.emit('upload_complete', {
type: 'ipfs',
url: ipfsUrl,
cid,
model: modelName,
version,
size: data.length,
});
return ipfsUrl;
}
/**
* Upload to web3.storage
* @private
*/
async _uploadToWeb3Storage(data, filename) {
// web3.storage API upload
const response = await this._httpRequest('https://api.web3.storage/upload', {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.ipfsConfig.web3StorageToken}`,
'X-Name': filename,
},
body: data,
});
if (!response.cid) {
throw new Error('web3.storage upload failed: no CID returned');
}
return response.cid;
}
/**
* Upload to nft.storage
* @private
*/
async _uploadToNftStorage(data, filename) {
// nft.storage API upload
const response = await this._httpRequest('https://api.nft.storage/upload', {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.ipfsConfig.nftStorageToken}`,
},
body: data,
});
if (!response.value?.cid) {
throw new Error('nft.storage upload failed: no CID returned');
}
return response.value.cid;
}
/**
* Make an HTTP request
* @private
*/
_httpRequest(url, options = {}) {
return new Promise((resolve, reject) => {
const parsedUrl = new URL(url);
const protocol = parsedUrl.protocol === 'https:' ? https : http;
const requestOptions = {
method: options.method || 'GET',
headers: options.headers || {},
hostname: parsedUrl.hostname,
path: parsedUrl.pathname + parsedUrl.search,
port: parsedUrl.port,
};
const request = protocol.request(requestOptions, (response) => {
const chunks = [];
response.on('data', chunk => chunks.push(chunk));
response.on('end', () => {
const body = Buffer.concat(chunks).toString('utf-8');
if (response.statusCode >= 400) {
reject(new Error(`HTTP ${response.statusCode}: ${body}`));
return;
}
try {
resolve(JSON.parse(body));
} catch {
resolve(body);
}
});
});
request.on('error', reject);
if (options.body) {
request.write(options.body);
}
request.end();
});
}
// ============================================
// INTEGRITY VERIFICATION
// ============================================
/**
* Compute SHA256 hash of data
* @param {Buffer} data - Data to hash
* @returns {string} Hash string with sha256: prefix
*/
computeHash(data) {
return `sha256:${createHash('sha256').update(data).digest('hex')}`;
}
/**
* Verify data integrity against expected hash
* @param {Buffer} data - Data to verify
* @param {string} expectedHash - Expected hash
* @returns {boolean}
*/
verifyIntegrity(data, expectedHash) {
const computed = this.computeHash(data);
return computed === expectedHash;
}
/**
* Verify a downloaded model
* @param {Buffer} data - Model data
* @param {object} metadata - Model metadata
* @returns {object} Verification result
*/
verifyModel(data, metadata) {
const result = {
valid: true,
checks: [],
};
// Size check
if (metadata.size) {
const sizeMatch = data.length === metadata.size;
result.checks.push({
type: 'size',
expected: metadata.size,
actual: data.length,
passed: sizeMatch,
});
if (!sizeMatch) result.valid = false;
}
// Hash check
if (metadata.hash) {
const hashMatch = this.verifyIntegrity(data, metadata.hash);
result.checks.push({
type: 'hash',
expected: metadata.hash,
actual: this.computeHash(data),
passed: hashMatch,
});
if (!hashMatch) result.valid = false;
}
return result;
}
// ============================================
// STATS AND INFO
// ============================================
/**
* Get distribution manager stats
* @returns {object}
*/
getStats() {
return {
...this.stats,
activeDownloads: this.activeDownloads.size,
};
}
}
// ============================================
// DEFAULT EXPORT
// ============================================
export default DistributionManager;

View File

@@ -0,0 +1,753 @@
/**
* @ruvector/edge-net Model Integrity System
*
* Content-addressed integrity with:
* - Canonical JSON signing
* - Threshold signatures with trust roots
* - Merkle chunk verification for streaming
* - Transparency log integration
*
* Design principle: Manifest is truth, everything else is replaceable.
*
* @module @ruvector/edge-net/models/integrity
*/
import { createHash } from 'crypto';
// ============================================================================
// CANONICAL JSON
// ============================================================================
/**
* Canonical JSON encoding for deterministic signing.
* - Keys sorted lexicographically
* - No whitespace
* - Unicode escaped consistently
* - Numbers without trailing zeros
*/
export function canonicalize(obj) {
if (obj === null || obj === undefined) {
return 'null';
}
if (typeof obj === 'boolean') {
return obj ? 'true' : 'false';
}
if (typeof obj === 'number') {
if (!Number.isFinite(obj)) {
throw new Error('Cannot canonicalize Infinity or NaN');
}
// Use JSON for consistent number formatting
return JSON.stringify(obj);
}
if (typeof obj === 'string') {
// Escape unicode consistently
return JSON.stringify(obj).replace(/[\u007f-\uffff]/g, (c) => {
return '\\u' + ('0000' + c.charCodeAt(0).toString(16)).slice(-4);
});
}
if (Array.isArray(obj)) {
const elements = obj.map(canonicalize);
return '[' + elements.join(',') + ']';
}
if (typeof obj === 'object') {
const keys = Object.keys(obj).sort();
const pairs = keys
.filter(k => obj[k] !== undefined)
.map(k => canonicalize(k) + ':' + canonicalize(obj[k]));
return '{' + pairs.join(',') + '}';
}
throw new Error(`Cannot canonicalize type: ${typeof obj}`);
}
/**
* Hash canonical JSON bytes
*/
export function hashCanonical(obj, algorithm = 'sha256') {
const canonical = canonicalize(obj);
const hash = createHash(algorithm);
hash.update(canonical, 'utf8');
return hash.digest('hex');
}
// ============================================================================
// TRUST ROOT
// ============================================================================
/**
* Built-in root keys shipped with SDK.
* These are the only keys trusted by default.
*/
export const BUILTIN_ROOT_KEYS = Object.freeze({
'ruvector-root-2024': {
keyId: 'ruvector-root-2024',
algorithm: 'ed25519',
publicKey: 'MCowBQYDK2VwAyEAaGVsbG8td29ybGQta2V5LXBsYWNlaG9sZGVy', // Placeholder
validFrom: '2024-01-01T00:00:00Z',
validUntil: '2030-01-01T00:00:00Z',
capabilities: ['sign-manifest', 'sign-adapter', 'delegate'],
},
'ruvector-models-2024': {
keyId: 'ruvector-models-2024',
algorithm: 'ed25519',
publicKey: 'MCowBQYDK2VwAyEAbW9kZWxzLWtleS1wbGFjZWhvbGRlcg==', // Placeholder
validFrom: '2024-01-01T00:00:00Z',
validUntil: '2026-01-01T00:00:00Z',
capabilities: ['sign-manifest'],
delegatedBy: 'ruvector-root-2024',
},
});
/**
* Trust root configuration
*/
export class TrustRoot {
constructor(options = {}) {
// Start with built-in keys
this.trustedKeys = new Map();
for (const [id, key] of Object.entries(BUILTIN_ROOT_KEYS)) {
this.trustedKeys.set(id, key);
}
// Add enterprise keys if configured
if (options.enterpriseKeys) {
for (const key of options.enterpriseKeys) {
this.addEnterpriseKey(key);
}
}
// Revocation list
this.revokedKeys = new Set(options.revokedKeys || []);
// Minimum signatures required for official releases
this.minimumSignaturesRequired = options.minimumSignaturesRequired || 1;
// Threshold for high-security operations (e.g., new root key)
this.thresholdSignaturesRequired = options.thresholdSignaturesRequired || 2;
}
/**
* Add an enterprise root key (for private deployments)
*/
addEnterpriseKey(key) {
if (!key.keyId || !key.publicKey) {
throw new Error('Enterprise key must have keyId and publicKey');
}
// Verify delegation chain if not self-signed
if (key.delegatedBy && key.delegationSignature) {
const delegator = this.trustedKeys.get(key.delegatedBy);
if (!delegator) {
throw new Error(`Unknown delegator: ${key.delegatedBy}`);
}
if (!delegator.capabilities.includes('delegate')) {
throw new Error(`Key ${key.delegatedBy} cannot delegate`);
}
// In production, verify delegationSignature here
}
this.trustedKeys.set(key.keyId, {
...key,
isEnterprise: true,
});
}
/**
* Revoke a key
*/
revokeKey(keyId, reason) {
this.revokedKeys.add(keyId);
console.warn(`[TrustRoot] Key revoked: ${keyId} - ${reason}`);
}
/**
* Check if a key is trusted for a capability
*/
isKeyTrusted(keyId, capability = 'sign-manifest') {
if (this.revokedKeys.has(keyId)) {
return false;
}
const key = this.trustedKeys.get(keyId);
if (!key) {
return false;
}
// Check validity period
const now = new Date();
if (key.validFrom && new Date(key.validFrom) > now) {
return false;
}
if (key.validUntil && new Date(key.validUntil) < now) {
return false;
}
// Check capability
if (!key.capabilities.includes(capability)) {
return false;
}
return true;
}
/**
* Get public key for verification
*/
getPublicKey(keyId) {
const key = this.trustedKeys.get(keyId);
if (!key || this.revokedKeys.has(keyId)) {
return null;
}
return key.publicKey;
}
/**
* Verify signature set meets threshold
*/
verifySignatureThreshold(signatures, requiredCount = null) {
const required = requiredCount || this.minimumSignaturesRequired;
let validCount = 0;
const validSigners = [];
for (const sig of signatures) {
if (this.isKeyTrusted(sig.keyId, 'sign-manifest')) {
// In production, verify actual signature here
validCount++;
validSigners.push(sig.keyId);
}
}
return {
valid: validCount >= required,
validCount,
required,
validSigners,
};
}
/**
* Export current trust configuration
*/
export() {
return {
trustedKeys: Object.fromEntries(this.trustedKeys),
revokedKeys: Array.from(this.revokedKeys),
minimumSignaturesRequired: this.minimumSignaturesRequired,
thresholdSignaturesRequired: this.thresholdSignaturesRequired,
};
}
}
// ============================================================================
// MERKLE CHUNK VERIFICATION
// ============================================================================
/**
* Compute Merkle tree from chunk hashes
*/
export function computeMerkleRoot(chunkHashes) {
if (chunkHashes.length === 0) {
return hashCanonical({ empty: true });
}
if (chunkHashes.length === 1) {
return chunkHashes[0];
}
// Build tree bottom-up
let level = [...chunkHashes];
while (level.length > 1) {
const nextLevel = [];
for (let i = 0; i < level.length; i += 2) {
const left = level[i];
const right = level[i + 1] || left; // Duplicate last if odd
const combined = createHash('sha256')
.update(left, 'hex')
.update(right, 'hex')
.digest('hex');
nextLevel.push(combined);
}
level = nextLevel;
}
return level[0];
}
/**
* Generate Merkle proof for a chunk
*/
export function generateMerkleProof(chunkHashes, chunkIndex) {
const proof = [];
let level = [...chunkHashes];
let index = chunkIndex;
while (level.length > 1) {
const isRight = index % 2 === 1;
const siblingIndex = isRight ? index - 1 : index + 1;
if (siblingIndex < level.length) {
proof.push({
hash: level[siblingIndex],
position: isRight ? 'left' : 'right',
});
} else {
// Odd number, sibling is self
proof.push({
hash: level[index],
position: 'right',
});
}
// Move up
const nextLevel = [];
for (let i = 0; i < level.length; i += 2) {
const left = level[i];
const right = level[i + 1] || left;
nextLevel.push(
createHash('sha256')
.update(left, 'hex')
.update(right, 'hex')
.digest('hex')
);
}
level = nextLevel;
index = Math.floor(index / 2);
}
return proof;
}
/**
* Verify a chunk against Merkle root
*/
export function verifyMerkleProof(chunkHash, chunkIndex, proof, merkleRoot) {
let computed = chunkHash;
for (const step of proof) {
const left = step.position === 'left' ? step.hash : computed;
const right = step.position === 'right' ? step.hash : computed;
computed = createHash('sha256')
.update(left, 'hex')
.update(right, 'hex')
.digest('hex');
}
return computed === merkleRoot;
}
/**
* Chunk a buffer and compute hashes
*/
export function chunkAndHash(buffer, chunkSize = 256 * 1024) {
const chunks = [];
const hashes = [];
for (let offset = 0; offset < buffer.length; offset += chunkSize) {
const chunk = buffer.slice(offset, offset + chunkSize);
chunks.push(chunk);
hashes.push(
createHash('sha256').update(chunk).digest('hex')
);
}
return {
chunks,
chunkHashes: hashes,
chunkSize,
chunkCount: chunks.length,
totalSize: buffer.length,
merkleRoot: computeMerkleRoot(hashes),
};
}
// ============================================================================
// MANIFEST INTEGRITY
// ============================================================================
/**
* Integrity block for manifests
*/
export function createIntegrityBlock(manifest, chunkInfo) {
// Create the signed payload (everything except signatures)
const signedPayload = {
model: manifest.model,
version: manifest.version,
artifacts: manifest.artifacts,
provenance: manifest.provenance,
capabilities: manifest.capabilities,
timestamp: new Date().toISOString(),
};
const signedPayloadHash = hashCanonical(signedPayload);
return {
manifestHash: hashCanonical(manifest),
signedPayloadHash,
merkleRoot: chunkInfo.merkleRoot,
chunking: {
chunkSize: chunkInfo.chunkSize,
chunkCount: chunkInfo.chunkCount,
chunkHashes: chunkInfo.chunkHashes,
},
signatures: [], // To be filled by signing process
};
}
/**
* Provenance block for manifests
*/
export function createProvenanceBlock(options = {}) {
return {
builtBy: {
tool: options.tool || '@ruvector/model-optimizer',
version: options.toolVersion || '1.0.0',
commit: options.commit || 'unknown',
},
optimizationRecipeHash: options.recipeHash || null,
calibrationDatasetHash: options.calibrationHash || null,
parentLineage: options.parentLineage || null,
buildTimestamp: new Date().toISOString(),
environment: {
platform: process.platform,
arch: process.arch,
nodeVersion: process.version,
},
};
}
/**
* Full manifest with integrity
*/
export function createSecureManifest(model, artifacts, options = {}) {
const manifest = {
schemaVersion: '2.0.0',
model: {
id: model.id,
name: model.name,
version: model.version,
type: model.type, // 'embedding' | 'generation'
tier: model.tier, // 'micro' | 'small' | 'large'
capabilities: model.capabilities || [],
memoryRequirement: model.memoryRequirement,
},
artifacts: artifacts.map(a => ({
path: a.path,
size: a.size,
sha256: a.sha256,
format: a.format,
quantization: a.quantization,
})),
distribution: {
gcs: options.gcsUrl,
ipfs: options.ipfsCid,
fallbackUrls: options.fallbackUrls || [],
},
provenance: createProvenanceBlock(options.provenance || {}),
capabilities: model.capabilities || [],
};
// Add integrity block if chunk info provided
if (options.chunkInfo) {
manifest.integrity = createIntegrityBlock(manifest, options.chunkInfo);
}
// Add trust metadata
manifest.trust = {
trustedKeySetId: options.trustedKeySetId || 'ruvector-default-2024',
minimumSignaturesRequired: options.minimumSignaturesRequired || 1,
};
return manifest;
}
// ============================================================================
// MANIFEST VERIFICATION
// ============================================================================
/**
* Verify a manifest's integrity
*/
export class ManifestVerifier {
constructor(trustRoot = null) {
this.trustRoot = trustRoot || new TrustRoot();
}
/**
* Full verification of a manifest
*/
verify(manifest) {
const errors = [];
const warnings = [];
// 1. Schema version check
if (!manifest.schemaVersion || manifest.schemaVersion < '2.0.0') {
warnings.push('Manifest uses old schema version');
}
// 2. Verify integrity block
if (manifest.integrity) {
// Check manifest hash
const computed = hashCanonical(manifest);
// Note: manifestHash is computed before adding integrity, so we skip this
// Check signed payload hash
const signedPayload = {
model: manifest.model,
version: manifest.version,
artifacts: manifest.artifacts,
provenance: manifest.provenance,
capabilities: manifest.capabilities,
timestamp: manifest.integrity.timestamp,
};
const computedPayloadHash = hashCanonical(signedPayload);
// 3. Verify signatures meet threshold
if (manifest.integrity.signatures?.length > 0) {
const sigResult = this.trustRoot.verifySignatureThreshold(
manifest.integrity.signatures,
manifest.trust?.minimumSignaturesRequired
);
if (!sigResult.valid) {
errors.push(`Insufficient valid signatures: ${sigResult.validCount}/${sigResult.required}`);
}
} else {
warnings.push('No signatures present');
}
// 4. Verify Merkle root matches chunk hashes
if (manifest.integrity.chunking) {
const computedRoot = computeMerkleRoot(manifest.integrity.chunking.chunkHashes);
if (computedRoot !== manifest.integrity.merkleRoot) {
errors.push('Merkle root mismatch');
}
}
} else {
warnings.push('No integrity block present');
}
// 5. Check provenance
if (!manifest.provenance) {
warnings.push('No provenance information');
}
// 6. Check required fields
if (!manifest.model?.id) errors.push('Missing model.id');
if (!manifest.model?.version) errors.push('Missing model.version');
if (!manifest.artifacts?.length) errors.push('No artifacts defined');
return {
valid: errors.length === 0,
errors,
warnings,
trust: manifest.trust,
provenance: manifest.provenance,
};
}
/**
* Verify a single chunk during streaming download
*/
verifyChunk(chunkData, chunkIndex, manifest) {
if (!manifest.integrity?.chunking) {
return { valid: false, error: 'No chunking info in manifest' };
}
const expectedHash = manifest.integrity.chunking.chunkHashes[chunkIndex];
if (!expectedHash) {
return { valid: false, error: `No hash for chunk ${chunkIndex}` };
}
const actualHash = createHash('sha256').update(chunkData).digest('hex');
if (actualHash !== expectedHash) {
return {
valid: false,
error: `Chunk ${chunkIndex} hash mismatch`,
expected: expectedHash,
actual: actualHash,
};
}
return { valid: true, chunkIndex, hash: actualHash };
}
}
// ============================================================================
// TRANSPARENCY LOG
// ============================================================================
/**
* Entry in the transparency log
*/
export function createLogEntry(manifest, publisherKeyId) {
return {
manifestHash: hashCanonical(manifest),
modelId: manifest.model.id,
version: manifest.model.version,
publisherKeyId,
timestamp: new Date().toISOString(),
signedPayloadHash: manifest.integrity?.signedPayloadHash,
};
}
/**
* Simple append-only transparency log
* In production, this would be backed by a Merkle tree or blockchain
*/
export class TransparencyLog {
constructor(options = {}) {
this.entries = [];
this.indexByModel = new Map();
this.indexByHash = new Map();
this.logRoot = null;
}
/**
* Append an entry to the log
*/
append(entry) {
const index = this.entries.length;
// Compute log entry hash including previous
const logEntryHash = hashCanonical({
...entry,
index,
previousHash: this.logRoot,
});
const fullEntry = {
...entry,
index,
previousHash: this.logRoot,
logEntryHash,
};
this.entries.push(fullEntry);
this.logRoot = logEntryHash;
// Update indexes
if (!this.indexByModel.has(entry.modelId)) {
this.indexByModel.set(entry.modelId, []);
}
this.indexByModel.get(entry.modelId).push(index);
this.indexByHash.set(entry.manifestHash, index);
return fullEntry;
}
/**
* Generate inclusion proof
*/
getInclusionProof(manifestHash) {
const index = this.indexByHash.get(manifestHash);
if (index === undefined) {
return null;
}
const entry = this.entries[index];
const proof = [];
// Simple chain proof (in production, use Merkle tree)
for (let i = index; i < this.entries.length; i++) {
proof.push({
index: i,
logEntryHash: this.entries[i].logEntryHash,
});
}
return {
entry,
proof,
currentRoot: this.logRoot,
logLength: this.entries.length,
};
}
/**
* Verify inclusion proof
*/
verifyInclusionProof(proof) {
if (!proof || !proof.entry || !proof.proof.length) {
return false;
}
// Verify chain
let expectedHash = proof.entry.logEntryHash;
for (let i = 1; i < proof.proof.length; i++) {
const entry = proof.proof[i];
// Verify chain continuity
if (i < proof.proof.length - 1) {
// Each entry should reference the previous
}
}
return proof.proof[proof.proof.length - 1].logEntryHash === proof.currentRoot;
}
/**
* Get history for a model
*/
getModelHistory(modelId) {
const indices = this.indexByModel.get(modelId) || [];
return indices.map(i => this.entries[i]);
}
/**
* Export log for persistence
*/
export() {
return {
entries: this.entries,
logRoot: this.logRoot,
};
}
/**
* Import log
*/
import(data) {
this.entries = data.entries || [];
this.logRoot = data.logRoot;
// Rebuild indexes
this.indexByModel.clear();
this.indexByHash.clear();
for (let i = 0; i < this.entries.length; i++) {
const entry = this.entries[i];
if (!this.indexByModel.has(entry.modelId)) {
this.indexByModel.set(entry.modelId, []);
}
this.indexByModel.get(entry.modelId).push(i);
this.indexByHash.set(entry.manifestHash, i);
}
}
}
// ============================================================================
// EXPORTS
// ============================================================================
export default {
canonicalize,
hashCanonical,
TrustRoot,
BUILTIN_ROOT_KEYS,
computeMerkleRoot,
generateMerkleProof,
verifyMerkleProof,
chunkAndHash,
createIntegrityBlock,
createProvenanceBlock,
createSecureManifest,
ManifestVerifier,
createLogEntry,
TransparencyLog,
};

View File

@@ -0,0 +1,725 @@
/**
* @ruvector/edge-net Model Loader
*
* Tiered model loading with:
* - Memory-aware model selection
* - Streaming chunk verification
* - Multi-source fallback (GCS → IPFS → P2P)
* - IndexedDB caching
*
* Design: Registry returns manifest only, client derives URLs from manifest.
*
* @module @ruvector/edge-net/models/loader
*/
import { createHash } from 'crypto';
import { ManifestVerifier, verifyMerkleProof, computeMerkleRoot } from './integrity.js';
// ============================================================================
// MODEL TIERS
// ============================================================================
/**
* Model tier definitions with memory requirements
*/
export const MODEL_TIERS = Object.freeze({
micro: {
name: 'micro',
maxSize: 100 * 1024 * 1024, // 100MB
minMemory: 256 * 1024 * 1024, // 256MB available
description: 'Embeddings and small tasks',
priority: 1,
},
small: {
name: 'small',
maxSize: 500 * 1024 * 1024, // 500MB
minMemory: 1024 * 1024 * 1024, // 1GB available
description: 'Balanced capability',
priority: 2,
},
large: {
name: 'large',
maxSize: 1500 * 1024 * 1024, // 1.5GB
minMemory: 4096 * 1024 * 1024, // 4GB available
description: 'Full capability',
priority: 3,
},
});
/**
* Capability priorities for model selection
*/
export const CAPABILITY_PRIORITIES = Object.freeze({
embed: 1, // Always prioritize embeddings
retrieve: 2, // Then retrieval
generate: 3, // Generation only when needed
code: 4, // Specialized capabilities
multilingual: 5,
});
// ============================================================================
// MEMORY DETECTION
// ============================================================================
/**
* Detect available memory for model loading
*/
export function detectAvailableMemory() {
// Browser environment
if (typeof navigator !== 'undefined' && navigator.deviceMemory) {
return navigator.deviceMemory * 1024 * 1024 * 1024;
}
// Node.js environment
if (typeof process !== 'undefined' && process.memoryUsage) {
const usage = process.memoryUsage();
// Estimate available as total minus current usage
const total = require('os').totalmem?.() || 4 * 1024 * 1024 * 1024;
return Math.max(0, total - usage.heapUsed);
}
// Default to 2GB as conservative estimate
return 2 * 1024 * 1024 * 1024;
}
/**
* Select appropriate tier based on device capabilities
*/
export function selectTier(requiredCapabilities = ['embed'], preferredTier = null) {
const available = detectAvailableMemory();
// Find highest tier that fits in memory
const viableTiers = Object.values(MODEL_TIERS)
.filter(tier => tier.minMemory <= available)
.sort((a, b) => b.priority - a.priority);
if (viableTiers.length === 0) {
console.warn('[ModelLoader] Insufficient memory for any tier, using micro');
return MODEL_TIERS.micro;
}
// Respect preferred tier if viable
if (preferredTier && viableTiers.find(t => t.name === preferredTier)) {
return MODEL_TIERS[preferredTier];
}
// Otherwise use highest viable
return viableTiers[0];
}
// ============================================================================
// CACHE MANAGER
// ============================================================================
/**
* IndexedDB-based cache for models and chunks
*/
export class ModelCache {
constructor(options = {}) {
this.dbName = options.dbName || 'ruvector-models';
this.version = options.version || 1;
this.db = null;
this.maxCacheSize = options.maxCacheSize || 2 * 1024 * 1024 * 1024; // 2GB
}
async open() {
if (this.db) return this.db;
return new Promise((resolve, reject) => {
const request = indexedDB.open(this.dbName, this.version);
request.onerror = () => reject(request.error);
request.onsuccess = () => {
this.db = request.result;
resolve(this.db);
};
request.onupgradeneeded = (event) => {
const db = event.target.result;
// Store for complete models
if (!db.objectStoreNames.contains('models')) {
const store = db.createObjectStore('models', { keyPath: 'id' });
store.createIndex('hash', 'hash', { unique: true });
store.createIndex('lastAccess', 'lastAccess');
}
// Store for individual chunks (for streaming)
if (!db.objectStoreNames.contains('chunks')) {
const store = db.createObjectStore('chunks', { keyPath: 'id' });
store.createIndex('modelId', 'modelId');
}
// Store for manifests
if (!db.objectStoreNames.contains('manifests')) {
db.createObjectStore('manifests', { keyPath: 'modelId' });
}
};
});
}
async get(modelId) {
await this.open();
return new Promise((resolve, reject) => {
const tx = this.db.transaction('models', 'readonly');
const store = tx.objectStore('models');
const request = store.get(modelId);
request.onsuccess = () => {
const result = request.result;
if (result) {
// Update last access
this.updateLastAccess(modelId);
}
resolve(result);
};
request.onerror = () => reject(request.error);
});
}
async put(modelId, data, manifest) {
await this.open();
await this.ensureSpace(data.byteLength || data.length);
return new Promise((resolve, reject) => {
const tx = this.db.transaction(['models', 'manifests'], 'readwrite');
const modelStore = tx.objectStore('models');
modelStore.put({
id: modelId,
data,
hash: manifest.integrity?.merkleRoot || 'unknown',
size: data.byteLength || data.length,
lastAccess: Date.now(),
cachedAt: Date.now(),
});
const manifestStore = tx.objectStore('manifests');
manifestStore.put({
modelId,
manifest,
cachedAt: Date.now(),
});
tx.oncomplete = () => resolve();
tx.onerror = () => reject(tx.error);
});
}
async getChunk(modelId, chunkIndex) {
await this.open();
return new Promise((resolve, reject) => {
const tx = this.db.transaction('chunks', 'readonly');
const store = tx.objectStore('chunks');
const request = store.get(`${modelId}:${chunkIndex}`);
request.onsuccess = () => resolve(request.result?.data);
request.onerror = () => reject(request.error);
});
}
async putChunk(modelId, chunkIndex, data, hash) {
await this.open();
return new Promise((resolve, reject) => {
const tx = this.db.transaction('chunks', 'readwrite');
const store = tx.objectStore('chunks');
store.put({
id: `${modelId}:${chunkIndex}`,
modelId,
chunkIndex,
data,
hash,
cachedAt: Date.now(),
});
tx.oncomplete = () => resolve();
tx.onerror = () => reject(tx.error);
});
}
async updateLastAccess(modelId) {
await this.open();
return new Promise((resolve) => {
const tx = this.db.transaction('models', 'readwrite');
const store = tx.objectStore('models');
const request = store.get(modelId);
request.onsuccess = () => {
if (request.result) {
request.result.lastAccess = Date.now();
store.put(request.result);
}
resolve();
};
});
}
async ensureSpace(needed) {
await this.open();
// Get current usage
const estimate = await navigator.storage?.estimate?.();
const used = estimate?.usage || 0;
if (used + needed > this.maxCacheSize) {
await this.evictLRU(needed);
}
}
async evictLRU(needed) {
await this.open();
return new Promise((resolve, reject) => {
const tx = this.db.transaction('models', 'readwrite');
const store = tx.objectStore('models');
const index = store.index('lastAccess');
const request = index.openCursor();
let freed = 0;
request.onsuccess = (event) => {
const cursor = event.target.result;
if (cursor && freed < needed) {
freed += cursor.value.size || 0;
cursor.delete();
cursor.continue();
} else {
resolve(freed);
}
};
request.onerror = () => reject(request.error);
});
}
async getCacheStats() {
await this.open();
return new Promise((resolve, reject) => {
const tx = this.db.transaction('models', 'readonly');
const store = tx.objectStore('models');
const request = store.getAll();
request.onsuccess = () => {
const models = request.result;
const totalSize = models.reduce((sum, m) => sum + (m.size || 0), 0);
resolve({
modelCount: models.length,
totalSize,
models: models.map(m => ({
id: m.id,
size: m.size,
lastAccess: m.lastAccess,
})),
});
};
request.onerror = () => reject(request.error);
});
}
async clear() {
await this.open();
return new Promise((resolve, reject) => {
const tx = this.db.transaction(['models', 'chunks', 'manifests'], 'readwrite');
tx.objectStore('models').clear();
tx.objectStore('chunks').clear();
tx.objectStore('manifests').clear();
tx.oncomplete = () => resolve();
tx.onerror = () => reject(tx.error);
});
}
}
// ============================================================================
// MODEL LOADER
// ============================================================================
/**
* Model loader with tiered selection and chunk verification
*/
export class ModelLoader {
constructor(options = {}) {
this.cache = new ModelCache(options.cache);
this.verifier = new ManifestVerifier(options.trustRoot);
this.registryUrl = options.registryUrl || 'https://models.ruvector.dev';
// Loading state
this.loadingModels = new Map();
this.loadedModels = new Map();
// Callbacks
this.onProgress = options.onProgress || (() => {});
this.onError = options.onError || console.error;
// Source preference order
this.sourceOrder = options.sourceOrder || ['cache', 'gcs', 'ipfs', 'p2p'];
}
/**
* Fetch manifest from registry (registry only returns manifest, not URLs)
*/
async fetchManifest(modelId) {
// Check cache first
const cached = await this.cache.get(modelId);
if (cached?.manifest) {
return cached.manifest;
}
// Fetch from registry
const response = await fetch(`${this.registryUrl}/v2/manifests/${modelId}`);
if (!response.ok) {
throw new Error(`Failed to fetch manifest: ${response.status}`);
}
const manifest = await response.json();
// Verify manifest
const verification = this.verifier.verify(manifest);
if (!verification.valid) {
throw new Error(`Invalid manifest: ${verification.errors.join(', ')}`);
}
if (verification.warnings.length > 0) {
console.warn('[ModelLoader] Manifest warnings:', verification.warnings);
}
return manifest;
}
/**
* Select best model for required capabilities
*/
async selectModel(requiredCapabilities, options = {}) {
const tier = selectTier(requiredCapabilities, options.preferredTier);
// Fetch model catalog for this tier
const response = await fetch(`${this.registryUrl}/v2/catalog?tier=${tier.name}`);
if (!response.ok) {
throw new Error(`Failed to fetch catalog: ${response.status}`);
}
const catalog = await response.json();
// Filter by capabilities
const candidates = catalog.models.filter(m => {
const hasCapabilities = requiredCapabilities.every(cap =>
m.capabilities?.includes(cap)
);
const fitsMemory = m.memoryRequirement <= detectAvailableMemory();
return hasCapabilities && fitsMemory;
});
if (candidates.length === 0) {
throw new Error(`No model found for capabilities: ${requiredCapabilities.join(', ')}`);
}
// Sort by capability priority (prefer embeddings over generation)
candidates.sort((a, b) => {
const aPriority = Math.min(...a.capabilities.map(c => CAPABILITY_PRIORITIES[c] || 10));
const bPriority = Math.min(...b.capabilities.map(c => CAPABILITY_PRIORITIES[c] || 10));
return aPriority - bPriority;
});
return candidates[0];
}
/**
* Load a model with chunk verification
*/
async load(modelId, options = {}) {
// Return if already loaded
if (this.loadedModels.has(modelId)) {
return this.loadedModels.get(modelId);
}
// Return existing promise if loading
if (this.loadingModels.has(modelId)) {
return this.loadingModels.get(modelId);
}
const loadPromise = this._loadInternal(modelId, options);
this.loadingModels.set(modelId, loadPromise);
try {
const result = await loadPromise;
this.loadedModels.set(modelId, result);
return result;
} finally {
this.loadingModels.delete(modelId);
}
}
async _loadInternal(modelId, options) {
// 1. Get manifest
const manifest = await this.fetchManifest(modelId);
// 2. Memory check
const available = detectAvailableMemory();
if (manifest.model.memoryRequirement > available) {
throw new Error(
`Insufficient memory: need ${manifest.model.memoryRequirement}, have ${available}`
);
}
// 3. Check cache
const cached = await this.cache.get(modelId);
if (cached?.data) {
// Verify cached data against manifest
if (cached.hash === manifest.integrity?.merkleRoot) {
this.onProgress({ modelId, status: 'cached', progress: 1 });
return { manifest, data: cached.data, source: 'cache' };
}
// Cache invalid, continue to download
}
// 4. Download with chunk verification
const artifact = manifest.artifacts[0]; // Primary artifact
const data = await this._downloadWithVerification(modelId, manifest, artifact, options);
// 5. Cache the result
await this.cache.put(modelId, data, manifest);
return { manifest, data, source: options.source || 'remote' };
}
/**
* Download with streaming chunk verification
*/
async _downloadWithVerification(modelId, manifest, artifact, options) {
const sources = this._getSourceUrls(manifest, artifact);
for (const source of sources) {
try {
const data = await this._downloadFromSource(
modelId,
source,
manifest,
artifact
);
return data;
} catch (error) {
console.warn(`[ModelLoader] Source failed: ${source.type}`, error.message);
continue;
}
}
throw new Error('All download sources failed');
}
/**
* Get ordered source URLs from manifest
*/
_getSourceUrls(manifest, artifact) {
const sources = [];
const dist = manifest.distribution || {};
for (const sourceType of this.sourceOrder) {
if (sourceType === 'gcs' && dist.gcs) {
sources.push({ type: 'gcs', url: dist.gcs });
}
if (sourceType === 'ipfs' && dist.ipfs) {
sources.push({
type: 'ipfs',
url: `https://ipfs.io/ipfs/${dist.ipfs}`,
cid: dist.ipfs,
});
}
if (sourceType === 'p2p') {
// P2P would be handled separately
sources.push({ type: 'p2p', url: null });
}
}
// Add fallbacks
if (dist.fallbackUrls) {
for (const url of dist.fallbackUrls) {
sources.push({ type: 'fallback', url });
}
}
return sources;
}
/**
* Download from a specific source with chunk verification
*/
async _downloadFromSource(modelId, source, manifest, artifact) {
if (source.type === 'p2p') {
return this._downloadFromP2P(modelId, manifest, artifact);
}
const response = await fetch(source.url);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const contentLength = parseInt(response.headers.get('content-length') || '0');
const chunking = manifest.integrity?.chunking;
if (chunking && response.body) {
// Streaming download with chunk verification
return this._streamWithVerification(
modelId,
response.body,
manifest,
contentLength
);
} else {
// Simple download
const buffer = await response.arrayBuffer();
// Verify full file hash
if (artifact.sha256) {
const hash = createHash('sha256')
.update(Buffer.from(buffer))
.digest('hex');
if (hash !== artifact.sha256) {
throw new Error('File hash mismatch');
}
}
return buffer;
}
}
/**
* Stream download with chunk-by-chunk verification
*/
async _streamWithVerification(modelId, body, manifest, totalSize) {
const chunking = manifest.integrity.chunking;
const chunkSize = chunking.chunkSize;
const expectedChunks = chunking.chunkCount;
const reader = body.getReader();
const chunks = [];
let buffer = new Uint8Array(0);
let chunkIndex = 0;
let bytesReceived = 0;
while (true) {
const { done, value } = await reader.read();
if (done) break;
// Append to buffer
const newBuffer = new Uint8Array(buffer.length + value.length);
newBuffer.set(buffer);
newBuffer.set(value, buffer.length);
buffer = newBuffer;
bytesReceived += value.length;
// Process complete chunks
while (buffer.length >= chunkSize || (bytesReceived === totalSize && buffer.length > 0)) {
const isLastChunk = bytesReceived === totalSize && buffer.length <= chunkSize;
const thisChunkSize = isLastChunk ? buffer.length : chunkSize;
const chunkData = buffer.slice(0, thisChunkSize);
buffer = buffer.slice(thisChunkSize);
// Verify chunk
const verification = this.verifier.verifyChunk(chunkData, chunkIndex, manifest);
if (!verification.valid) {
throw new Error(`Chunk verification failed: ${verification.error}`);
}
chunks.push(chunkData);
chunkIndex++;
// Cache chunk for resume capability
await this.cache.putChunk(
modelId,
chunkIndex - 1,
chunkData,
verification.hash
);
// Progress callback
this.onProgress({
modelId,
status: 'downloading',
progress: bytesReceived / totalSize,
chunksVerified: chunkIndex,
totalChunks: expectedChunks,
});
if (isLastChunk) break;
}
}
// Verify Merkle root
const chunkHashes = chunking.chunkHashes;
const computedRoot = computeMerkleRoot(chunkHashes);
if (computedRoot !== manifest.integrity.merkleRoot) {
throw new Error('Merkle root verification failed');
}
// Combine chunks
const totalLength = chunks.reduce((sum, c) => sum + c.length, 0);
const result = new Uint8Array(totalLength);
let offset = 0;
for (const chunk of chunks) {
result.set(chunk, offset);
offset += chunk.length;
}
this.onProgress({
modelId,
status: 'complete',
progress: 1,
verified: true,
});
return result.buffer;
}
/**
* Download from P2P network (placeholder)
*/
async _downloadFromP2P(modelId, manifest, artifact) {
// Would integrate with WebRTC P2P network
throw new Error('P2P download not implemented');
}
/**
* Preload a model in the background
*/
async preload(modelId) {
try {
await this.load(modelId);
} catch (error) {
console.warn(`[ModelLoader] Preload failed for ${modelId}:`, error.message);
}
}
/**
* Unload a model from memory
*/
unload(modelId) {
this.loadedModels.delete(modelId);
}
/**
* Get cache statistics
*/
async getCacheStats() {
return this.cache.getCacheStats();
}
/**
* Clear all cached models
*/
async clearCache() {
await this.cache.clear();
this.loadedModels.clear();
}
}
// ============================================================================
// EXPORTS
// ============================================================================
export default ModelLoader;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,922 @@
/**
* @ruvector/edge-net Model Loader
*
* Smart model loading with:
* - IndexedDB caching
* - Automatic source selection (CDN -> GCS -> IPFS -> fallback)
* - Streaming download with progress
* - Model validation before use
* - Lazy loading support
*
* @module @ruvector/edge-net/models/model-loader
*/
import { EventEmitter } from 'events';
import { createHash, randomBytes } from 'crypto';
import { promises as fs } from 'fs';
import path from 'path';
import { ModelRegistry } from './model-registry.js';
import { DistributionManager, ProgressTracker } from './distribution.js';
// ============================================
// CONSTANTS
// ============================================
const DEFAULT_CACHE_DIR = process.env.HOME
? `${process.env.HOME}/.ruvector/models/cache`
: '/tmp/.ruvector/models/cache';
const CACHE_VERSION = 1;
const MAX_CACHE_SIZE_BYTES = 10 * 1024 * 1024 * 1024; // 10GB default
const CACHE_CLEANUP_THRESHOLD = 0.9; // Cleanup when 90% full
// ============================================
// CACHE STORAGE INTERFACE
// ============================================
/**
* Cache storage interface for different backends
*/
class CacheStorage {
async get(key) { throw new Error('Not implemented'); }
async set(key, value, metadata) { throw new Error('Not implemented'); }
async delete(key) { throw new Error('Not implemented'); }
async has(key) { throw new Error('Not implemented'); }
async list() { throw new Error('Not implemented'); }
async getMetadata(key) { throw new Error('Not implemented'); }
async clear() { throw new Error('Not implemented'); }
async getSize() { throw new Error('Not implemented'); }
}
// ============================================
// FILE SYSTEM CACHE
// ============================================
/**
* File system-based cache storage for Node.js
*/
class FileSystemCache extends CacheStorage {
constructor(cacheDir) {
super();
this.cacheDir = cacheDir;
this.metadataDir = path.join(cacheDir, '.metadata');
this.initialized = false;
}
async init() {
if (this.initialized) return;
await fs.mkdir(this.cacheDir, { recursive: true });
await fs.mkdir(this.metadataDir, { recursive: true });
this.initialized = true;
}
_getFilePath(key) {
// Sanitize key for filesystem
const safeKey = key.replace(/[^a-zA-Z0-9._-]/g, '_');
return path.join(this.cacheDir, safeKey);
}
_getMetadataPath(key) {
const safeKey = key.replace(/[^a-zA-Z0-9._-]/g, '_');
return path.join(this.metadataDir, `${safeKey}.json`);
}
async get(key) {
await this.init();
const filePath = this._getFilePath(key);
try {
const data = await fs.readFile(filePath);
// Update access time in metadata
await this._updateAccessTime(key);
return data;
} catch (error) {
if (error.code === 'ENOENT') return null;
throw error;
}
}
async set(key, value, metadata = {}) {
await this.init();
const filePath = this._getFilePath(key);
const metadataPath = this._getMetadataPath(key);
// Write data
await fs.writeFile(filePath, value);
// Write metadata
const fullMetadata = {
key,
size: value.length,
hash: `sha256:${createHash('sha256').update(value).digest('hex')}`,
createdAt: new Date().toISOString(),
accessedAt: new Date().toISOString(),
accessCount: 1,
cacheVersion: CACHE_VERSION,
...metadata,
};
await fs.writeFile(metadataPath, JSON.stringify(fullMetadata, null, 2));
return fullMetadata;
}
async delete(key) {
await this.init();
const filePath = this._getFilePath(key);
const metadataPath = this._getMetadataPath(key);
try {
await fs.unlink(filePath);
await fs.unlink(metadataPath).catch(() => {});
return true;
} catch (error) {
if (error.code === 'ENOENT') return false;
throw error;
}
}
async has(key) {
await this.init();
const filePath = this._getFilePath(key);
try {
await fs.access(filePath);
return true;
} catch {
return false;
}
}
async list() {
await this.init();
try {
const files = await fs.readdir(this.cacheDir);
return files.filter(f => !f.startsWith('.'));
} catch {
return [];
}
}
async getMetadata(key) {
await this.init();
const metadataPath = this._getMetadataPath(key);
try {
const data = await fs.readFile(metadataPath, 'utf-8');
return JSON.parse(data);
} catch {
return null;
}
}
async _updateAccessTime(key) {
const metadataPath = this._getMetadataPath(key);
try {
const data = await fs.readFile(metadataPath, 'utf-8');
const metadata = JSON.parse(data);
metadata.accessedAt = new Date().toISOString();
metadata.accessCount = (metadata.accessCount || 0) + 1;
await fs.writeFile(metadataPath, JSON.stringify(metadata, null, 2));
} catch {
// Ignore metadata update errors
}
}
async clear() {
await this.init();
const files = await this.list();
for (const file of files) {
await this.delete(file);
}
}
async getSize() {
await this.init();
const files = await this.list();
let totalSize = 0;
for (const file of files) {
const filePath = this._getFilePath(file);
try {
const stats = await fs.stat(filePath);
totalSize += stats.size;
} catch {
// Ignore missing files
}
}
return totalSize;
}
async getEntriesWithMetadata() {
await this.init();
const files = await this.list();
const entries = [];
for (const file of files) {
const metadata = await this.getMetadata(file);
if (metadata) {
entries.push(metadata);
}
}
return entries;
}
}
// ============================================
// INDEXEDDB CACHE (BROWSER)
// ============================================
/**
* IndexedDB-based cache storage for browsers
*/
class IndexedDBCache extends CacheStorage {
constructor(dbName = 'ruvector-models') {
super();
this.dbName = dbName;
this.storeName = 'models';
this.metadataStoreName = 'metadata';
this.db = null;
}
async init() {
if (this.db) return;
if (typeof indexedDB === 'undefined') {
throw new Error('IndexedDB not available');
}
return new Promise((resolve, reject) => {
const request = indexedDB.open(this.dbName, CACHE_VERSION);
request.onerror = () => reject(request.error);
request.onsuccess = () => {
this.db = request.result;
resolve();
};
request.onupgradeneeded = (event) => {
const db = event.target.result;
// Models store
if (!db.objectStoreNames.contains(this.storeName)) {
db.createObjectStore(this.storeName);
}
// Metadata store
if (!db.objectStoreNames.contains(this.metadataStoreName)) {
const metaStore = db.createObjectStore(this.metadataStoreName);
metaStore.createIndex('accessedAt', 'accessedAt');
metaStore.createIndex('size', 'size');
}
};
});
}
async get(key) {
await this.init();
return new Promise((resolve, reject) => {
const transaction = this.db.transaction([this.storeName], 'readonly');
const store = transaction.objectStore(this.storeName);
const request = store.get(key);
request.onerror = () => reject(request.error);
request.onsuccess = () => {
if (request.result) {
this._updateAccessTime(key);
}
resolve(request.result || null);
};
});
}
async set(key, value, metadata = {}) {
await this.init();
const fullMetadata = {
key,
size: value.length || value.byteLength,
hash: await this._computeHash(value),
createdAt: new Date().toISOString(),
accessedAt: new Date().toISOString(),
accessCount: 1,
cacheVersion: CACHE_VERSION,
...metadata,
};
return new Promise((resolve, reject) => {
const transaction = this.db.transaction(
[this.storeName, this.metadataStoreName],
'readwrite'
);
const modelStore = transaction.objectStore(this.storeName);
const metaStore = transaction.objectStore(this.metadataStoreName);
modelStore.put(value, key);
metaStore.put(fullMetadata, key);
transaction.oncomplete = () => resolve(fullMetadata);
transaction.onerror = () => reject(transaction.error);
});
}
async _computeHash(data) {
if (typeof crypto !== 'undefined' && crypto.subtle) {
const buffer = data instanceof ArrayBuffer ? data : data.buffer;
const hashBuffer = await crypto.subtle.digest('SHA-256', buffer);
const hashArray = Array.from(new Uint8Array(hashBuffer));
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
return `sha256:${hashHex}`;
}
return null;
}
async delete(key) {
await this.init();
return new Promise((resolve, reject) => {
const transaction = this.db.transaction(
[this.storeName, this.metadataStoreName],
'readwrite'
);
transaction.objectStore(this.storeName).delete(key);
transaction.objectStore(this.metadataStoreName).delete(key);
transaction.oncomplete = () => resolve(true);
transaction.onerror = () => reject(transaction.error);
});
}
async has(key) {
const value = await this.get(key);
return value !== null;
}
async list() {
await this.init();
return new Promise((resolve, reject) => {
const transaction = this.db.transaction([this.storeName], 'readonly');
const store = transaction.objectStore(this.storeName);
const request = store.getAllKeys();
request.onerror = () => reject(request.error);
request.onsuccess = () => resolve(request.result);
});
}
async getMetadata(key) {
await this.init();
return new Promise((resolve, reject) => {
const transaction = this.db.transaction([this.metadataStoreName], 'readonly');
const store = transaction.objectStore(this.metadataStoreName);
const request = store.get(key);
request.onerror = () => reject(request.error);
request.onsuccess = () => resolve(request.result || null);
});
}
async _updateAccessTime(key) {
const metadata = await this.getMetadata(key);
if (!metadata) return;
metadata.accessedAt = new Date().toISOString();
metadata.accessCount = (metadata.accessCount || 0) + 1;
const transaction = this.db.transaction([this.metadataStoreName], 'readwrite');
transaction.objectStore(this.metadataStoreName).put(metadata, key);
}
async clear() {
await this.init();
return new Promise((resolve, reject) => {
const transaction = this.db.transaction(
[this.storeName, this.metadataStoreName],
'readwrite'
);
transaction.objectStore(this.storeName).clear();
transaction.objectStore(this.metadataStoreName).clear();
transaction.oncomplete = () => resolve();
transaction.onerror = () => reject(transaction.error);
});
}
async getSize() {
await this.init();
return new Promise((resolve, reject) => {
const transaction = this.db.transaction([this.metadataStoreName], 'readonly');
const store = transaction.objectStore(this.metadataStoreName);
const request = store.getAll();
request.onerror = () => reject(request.error);
request.onsuccess = () => {
const totalSize = request.result.reduce((sum, meta) => sum + (meta.size || 0), 0);
resolve(totalSize);
};
});
}
}
// ============================================
// MODEL LOADER
// ============================================
/**
* ModelLoader - Smart model loading with caching
*/
export class ModelLoader extends EventEmitter {
/**
* Create a new ModelLoader
* @param {object} options - Configuration options
*/
constructor(options = {}) {
super();
this.id = `loader-${randomBytes(6).toString('hex')}`;
// Create registry if not provided
this.registry = options.registry || new ModelRegistry({
registryPath: options.registryPath,
});
// Create distribution manager if not provided
this.distribution = options.distribution || new DistributionManager({
gcsBucket: options.gcsBucket,
gcsProjectId: options.gcsProjectId,
cdnBaseUrl: options.cdnBaseUrl,
ipfsGateway: options.ipfsGateway,
});
// Cache configuration
this.cacheDir = options.cacheDir || DEFAULT_CACHE_DIR;
this.maxCacheSize = options.maxCacheSize || MAX_CACHE_SIZE_BYTES;
// Initialize cache storage based on environment
this.cache = this._createCacheStorage(options);
// Loaded models (in-memory)
this.loadedModels = new Map();
// Loading promises (prevent duplicate loads)
this.loadingPromises = new Map();
// Lazy load queue
this.lazyLoadQueue = [];
this.lazyLoadActive = false;
// Stats
this.stats = {
cacheHits: 0,
cacheMisses: 0,
downloads: 0,
validationErrors: 0,
lazyLoads: 0,
};
}
/**
* Create appropriate cache storage for environment
* @private
*/
_createCacheStorage(options) {
// Browser environment
if (typeof window !== 'undefined' && typeof indexedDB !== 'undefined') {
return new IndexedDBCache(options.dbName || 'ruvector-models');
}
// Node.js environment
return new FileSystemCache(this.cacheDir);
}
/**
* Initialize the loader
*/
async initialize() {
// Initialize cache
if (this.cache.init) {
await this.cache.init();
}
// Load registry if path provided
if (this.registry.registryPath) {
try {
await this.registry.load();
} catch (error) {
this.emit('warning', { message: 'Failed to load registry', error });
}
}
this.emit('initialized', { loaderId: this.id });
return this;
}
/**
* Get cache key for a model
* @private
*/
_getCacheKey(name, version) {
return `${name}@${version}`;
}
/**
* Load a model
* @param {string} name - Model name
* @param {string} version - Version (default: latest)
* @param {object} options - Load options
* @returns {Promise<Buffer|Uint8Array>}
*/
async load(name, version = 'latest', options = {}) {
const key = this._getCacheKey(name, version);
// Return cached in-memory model
if (this.loadedModels.has(key) && !options.forceReload) {
this.stats.cacheHits++;
return this.loadedModels.get(key);
}
// Return existing loading promise
if (this.loadingPromises.has(key)) {
return this.loadingPromises.get(key);
}
// Start loading
const loadPromise = this._loadModel(name, version, options);
this.loadingPromises.set(key, loadPromise);
try {
const model = await loadPromise;
this.loadedModels.set(key, model);
return model;
} finally {
this.loadingPromises.delete(key);
}
}
/**
* Internal model loading logic
* @private
*/
async _loadModel(name, version, options = {}) {
const { onProgress, skipCache = false, skipValidation = false } = options;
// Get metadata from registry
let metadata = this.registry.get(name, version);
if (!metadata) {
// Try to fetch from remote registry
this.emit('warning', { message: `Model ${name}@${version} not in local registry` });
throw new Error(`Model not found: ${name}@${version}`);
}
const resolvedVersion = metadata.version;
const key = this._getCacheKey(name, resolvedVersion);
// Check cache first (unless skipped)
if (!skipCache) {
const cached = await this.cache.get(key);
if (cached) {
// Validate cached data
if (!skipValidation && metadata.hash) {
const isValid = this.distribution.verifyIntegrity(cached, metadata.hash);
if (isValid) {
this.stats.cacheHits++;
this.emit('cache_hit', { name, version: resolvedVersion });
return cached;
} else {
this.stats.validationErrors++;
this.emit('cache_invalid', { name, version: resolvedVersion });
await this.cache.delete(key);
}
} else {
this.stats.cacheHits++;
return cached;
}
}
}
this.stats.cacheMisses++;
// Download model
this.emit('download_start', { name, version: resolvedVersion });
const data = await this.distribution.download(metadata, {
onProgress: (progress) => {
this.emit('progress', { name, version: resolvedVersion, ...progress });
if (onProgress) onProgress(progress);
},
});
// Validate downloaded data
if (!skipValidation) {
const validation = this.distribution.verifyModel(data, metadata);
if (!validation.valid) {
this.stats.validationErrors++;
throw new Error(`Model validation failed: ${JSON.stringify(validation.checks)}`);
}
}
// Store in cache
await this.cache.set(key, data, {
modelName: name,
version: resolvedVersion,
format: metadata.format,
});
this.stats.downloads++;
this.emit('loaded', { name, version: resolvedVersion, size: data.length });
// Cleanup cache if needed
await this._cleanupCacheIfNeeded();
return data;
}
/**
* Lazy load a model (load in background)
* @param {string} name - Model name
* @param {string} version - Version
* @param {object} options - Load options
*/
async lazyLoad(name, version = 'latest', options = {}) {
const key = this._getCacheKey(name, version);
// Already loaded or loading
if (this.loadedModels.has(key) || this.loadingPromises.has(key)) {
return;
}
// Check cache
const cached = await this.cache.has(key);
if (cached) {
return; // Already in cache
}
// Add to queue
this.lazyLoadQueue.push({ name, version, options });
this.stats.lazyLoads++;
this.emit('lazy_queued', { name, version, queueLength: this.lazyLoadQueue.length });
// Start processing if not active
if (!this.lazyLoadActive) {
this._processLazyLoadQueue();
}
}
/**
* Process lazy load queue
* @private
*/
async _processLazyLoadQueue() {
if (this.lazyLoadActive || this.lazyLoadQueue.length === 0) return;
this.lazyLoadActive = true;
while (this.lazyLoadQueue.length > 0) {
const { name, version, options } = this.lazyLoadQueue.shift();
try {
await this.load(name, version, {
...options,
lazy: true,
});
} catch (error) {
this.emit('lazy_error', { name, version, error: error.message });
}
// Small delay between lazy loads
await new Promise(resolve => setTimeout(resolve, 100));
}
this.lazyLoadActive = false;
}
/**
* Preload multiple models
* @param {Array<{name: string, version?: string}>} models - Models to preload
*/
async preload(models) {
const results = await Promise.allSettled(
models.map(({ name, version }) => this.load(name, version || 'latest'))
);
return {
total: models.length,
loaded: results.filter(r => r.status === 'fulfilled').length,
failed: results.filter(r => r.status === 'rejected').length,
results,
};
}
/**
* Check if a model is loaded in memory
* @param {string} name - Model name
* @param {string} version - Version
* @returns {boolean}
*/
isLoaded(name, version = 'latest') {
const metadata = this.registry.get(name, version);
if (!metadata) return false;
const key = this._getCacheKey(name, metadata.version);
return this.loadedModels.has(key);
}
/**
* Check if a model is cached on disk
* @param {string} name - Model name
* @param {string} version - Version
* @returns {Promise<boolean>}
*/
async isCached(name, version = 'latest') {
const metadata = this.registry.get(name, version);
if (!metadata) return false;
const key = this._getCacheKey(name, metadata.version);
return this.cache.has(key);
}
/**
* Unload a model from memory
* @param {string} name - Model name
* @param {string} version - Version
*/
unload(name, version = 'latest') {
const metadata = this.registry.get(name, version);
if (!metadata) return false;
const key = this._getCacheKey(name, metadata.version);
return this.loadedModels.delete(key);
}
/**
* Unload all models from memory
*/
unloadAll() {
const count = this.loadedModels.size;
this.loadedModels.clear();
return count;
}
/**
* Remove a model from cache
* @param {string} name - Model name
* @param {string} version - Version
*/
async removeFromCache(name, version = 'latest') {
const metadata = this.registry.get(name, version);
if (!metadata) return false;
const key = this._getCacheKey(name, metadata.version);
return this.cache.delete(key);
}
/**
* Clear all cached models
*/
async clearCache() {
await this.cache.clear();
this.emit('cache_cleared');
}
/**
* Cleanup cache if over size limit
* @private
*/
async _cleanupCacheIfNeeded() {
const currentSize = await this.cache.getSize();
const threshold = this.maxCacheSize * CACHE_CLEANUP_THRESHOLD;
if (currentSize < threshold) return;
this.emit('cache_cleanup_start', { currentSize, maxSize: this.maxCacheSize });
// Get entries sorted by last access time
let entries;
if (this.cache.getEntriesWithMetadata) {
entries = await this.cache.getEntriesWithMetadata();
} else {
const keys = await this.cache.list();
entries = [];
for (const key of keys) {
const meta = await this.cache.getMetadata(key);
if (meta) entries.push(meta);
}
}
// Sort by access time (oldest first)
entries.sort((a, b) =>
new Date(a.accessedAt).getTime() - new Date(b.accessedAt).getTime()
);
// Remove oldest entries until under 80% capacity
const targetSize = this.maxCacheSize * 0.8;
let removedSize = 0;
let removedCount = 0;
for (const entry of entries) {
if (currentSize - removedSize <= targetSize) break;
await this.cache.delete(entry.key);
removedSize += entry.size;
removedCount++;
}
this.emit('cache_cleanup_complete', {
removedCount,
removedSize,
newSize: currentSize - removedSize,
});
}
/**
* Get cache statistics
* @returns {Promise<object>}
*/
async getCacheStats() {
const size = await this.cache.getSize();
const keys = await this.cache.list();
return {
entries: keys.length,
sizeBytes: size,
sizeMB: Math.round(size / (1024 * 1024) * 100) / 100,
maxSizeBytes: this.maxCacheSize,
usagePercent: Math.round((size / this.maxCacheSize) * 100),
};
}
/**
* Get loader statistics
* @returns {object}
*/
getStats() {
return {
...this.stats,
loadedModels: this.loadedModels.size,
pendingLoads: this.loadingPromises.size,
lazyQueueLength: this.lazyLoadQueue.length,
hitRate: this.stats.cacheHits + this.stats.cacheMisses > 0
? Math.round(
(this.stats.cacheHits / (this.stats.cacheHits + this.stats.cacheMisses)) * 100
)
: 0,
};
}
/**
* Get model from registry (without loading)
* @param {string} name - Model name
* @param {string} version - Version
* @returns {object|null}
*/
getModelInfo(name, version = 'latest') {
return this.registry.get(name, version);
}
/**
* Search for models
* @param {object} criteria - Search criteria
* @returns {Array}
*/
searchModels(criteria) {
return this.registry.search(criteria);
}
/**
* List all available models
* @returns {string[]}
*/
listModels() {
return this.registry.listModels();
}
}
// ============================================
// EXPORTS
// ============================================
export { FileSystemCache, IndexedDBCache, CacheStorage };
export default ModelLoader;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,696 @@
/**
* @ruvector/edge-net Model Registry
*
* Manages model metadata, versions, dependencies, and discovery
* for the distributed model distribution infrastructure.
*
* @module @ruvector/edge-net/models/model-registry
*/
import { EventEmitter } from 'events';
import { createHash, randomBytes } from 'crypto';
import { promises as fs } from 'fs';
import path from 'path';
// ============================================
// SEMVER UTILITIES
// ============================================
/**
* Parse a semver version string
* @param {string} version - Version string (e.g., "1.2.3", "1.0.0-beta.1")
* @returns {object} Parsed version object
*/
export function parseSemver(version) {
const match = String(version).match(
/^(\d+)\.(\d+)\.(\d+)(?:-([a-zA-Z0-9.-]+))?(?:\+([a-zA-Z0-9.-]+))?$/
);
if (!match) {
throw new Error(`Invalid semver: ${version}`);
}
return {
major: parseInt(match[1], 10),
minor: parseInt(match[2], 10),
patch: parseInt(match[3], 10),
prerelease: match[4] || null,
build: match[5] || null,
raw: version,
};
}
/**
* Compare two semver versions
* @param {string} a - First version
* @param {string} b - Second version
* @returns {number} -1 if a < b, 0 if equal, 1 if a > b
*/
export function compareSemver(a, b) {
const va = parseSemver(a);
const vb = parseSemver(b);
if (va.major !== vb.major) return va.major - vb.major;
if (va.minor !== vb.minor) return va.minor - vb.minor;
if (va.patch !== vb.patch) return va.patch - vb.patch;
// Prerelease versions have lower precedence
if (va.prerelease && !vb.prerelease) return -1;
if (!va.prerelease && vb.prerelease) return 1;
if (va.prerelease && vb.prerelease) {
return va.prerelease.localeCompare(vb.prerelease);
}
return 0;
}
/**
* Check if version satisfies a version range
* Supports: "1.0.0", "^1.0.0", "~1.0.0", ">=1.0.0", "1.x", "*"
* @param {string} version - Version to check
* @param {string} range - Version range
* @returns {boolean}
*/
export function satisfiesSemver(version, range) {
const v = parseSemver(version);
// Exact match
if (range === version) return true;
// Wildcard
if (range === '*' || range === 'latest') return true;
// X-range: 1.x, 1.2.x
const xMatch = range.match(/^(\d+)(?:\.(\d+))?\.x$/);
if (xMatch) {
const major = parseInt(xMatch[1], 10);
const minor = xMatch[2] ? parseInt(xMatch[2], 10) : null;
if (v.major !== major) return false;
if (minor !== null && v.minor !== minor) return false;
return true;
}
// Caret range: ^1.0.0 (compatible with)
if (range.startsWith('^')) {
const r = parseSemver(range.slice(1));
if (v.major !== r.major) return false;
if (v.major === 0) {
if (v.minor !== r.minor) return false;
return v.patch >= r.patch;
}
return compareSemver(version, range.slice(1)) >= 0;
}
// Tilde range: ~1.0.0 (approximately equivalent)
if (range.startsWith('~')) {
const r = parseSemver(range.slice(1));
if (v.major !== r.major) return false;
if (v.minor !== r.minor) return false;
return v.patch >= r.patch;
}
// Comparison ranges: >=1.0.0, >1.0.0, <=1.0.0, <1.0.0
if (range.startsWith('>=')) {
return compareSemver(version, range.slice(2)) >= 0;
}
if (range.startsWith('>')) {
return compareSemver(version, range.slice(1)) > 0;
}
if (range.startsWith('<=')) {
return compareSemver(version, range.slice(2)) <= 0;
}
if (range.startsWith('<')) {
return compareSemver(version, range.slice(1)) < 0;
}
// Fallback to exact match
return compareSemver(version, range) === 0;
}
/**
* Get the latest version from a list
* @param {string[]} versions - List of version strings
* @returns {string} Latest version
*/
export function getLatestVersion(versions) {
if (!versions || versions.length === 0) return null;
return versions.sort((a, b) => compareSemver(b, a))[0];
}
// ============================================
// MODEL METADATA
// ============================================
/**
* Model metadata structure
* @typedef {object} ModelMetadata
* @property {string} name - Model identifier (e.g., "phi-1.5-int4")
* @property {string} version - Semantic version
* @property {number} size - Model size in bytes
* @property {string} hash - SHA256 hash for integrity
* @property {string} format - Model format (onnx, safetensors, gguf)
* @property {string[]} capabilities - Model capabilities
* @property {object} sources - Download sources (gcs, ipfs, cdn)
* @property {object} dependencies - Base models and adapters
* @property {object} quantization - Quantization details
* @property {object} metadata - Additional metadata
*/
/**
* Create a model metadata object
* @param {object} options - Model options
* @returns {ModelMetadata}
*/
export function createModelMetadata(options) {
const {
name,
version = '1.0.0',
size = 0,
hash = '',
format = 'onnx',
capabilities = [],
sources = {},
dependencies = {},
quantization = null,
metadata = {},
} = options;
if (!name) {
throw new Error('Model name is required');
}
// Validate version
parseSemver(version);
return {
name,
version,
size,
hash,
format,
capabilities: Array.isArray(capabilities) ? capabilities : [capabilities],
sources: {
gcs: sources.gcs || null,
ipfs: sources.ipfs || null,
cdn: sources.cdn || null,
...sources,
},
dependencies: {
base: dependencies.base || null,
adapters: dependencies.adapters || [],
...dependencies,
},
quantization: quantization ? {
type: quantization.type || 'int4',
bits: quantization.bits || 4,
blockSize: quantization.blockSize || 32,
symmetric: quantization.symmetric ?? true,
} : null,
metadata: {
createdAt: metadata.createdAt || new Date().toISOString(),
updatedAt: metadata.updatedAt || new Date().toISOString(),
author: metadata.author || 'RuVector',
license: metadata.license || 'Apache-2.0',
description: metadata.description || '',
tags: metadata.tags || [],
...metadata,
},
};
}
// ============================================
// MODEL REGISTRY
// ============================================
/**
* ModelRegistry - Manages model metadata, versions, and dependencies
*/
export class ModelRegistry extends EventEmitter {
/**
* Create a new ModelRegistry
* @param {object} options - Registry options
*/
constructor(options = {}) {
super();
this.id = `registry-${randomBytes(6).toString('hex')}`;
this.registryPath = options.registryPath || null;
// Model storage: { modelName: { version: ModelMetadata } }
this.models = new Map();
// Dependency graph
this.dependencies = new Map();
// Search index
this.searchIndex = {
byCapability: new Map(),
byFormat: new Map(),
byTag: new Map(),
};
// Stats
this.stats = {
totalModels: 0,
totalVersions: 0,
totalSize: 0,
};
}
/**
* Register a new model or version
* @param {object} modelData - Model metadata
* @returns {ModelMetadata}
*/
register(modelData) {
const metadata = createModelMetadata(modelData);
const { name, version } = metadata;
// Get or create model entry
if (!this.models.has(name)) {
this.models.set(name, new Map());
this.stats.totalModels++;
}
const versions = this.models.get(name);
// Check if version exists
if (versions.has(version)) {
this.emit('warning', {
type: 'version_exists',
model: name,
version,
});
}
// Store metadata
versions.set(version, metadata);
this.stats.totalVersions++;
this.stats.totalSize += metadata.size;
// Update search index
this._indexModel(metadata);
// Update dependency graph
this._updateDependencies(metadata);
this.emit('registered', { name, version, metadata });
return metadata;
}
/**
* Get model metadata
* @param {string} name - Model name
* @param {string} version - Version (default: latest)
* @returns {ModelMetadata|null}
*/
get(name, version = 'latest') {
const versions = this.models.get(name);
if (!versions) return null;
if (version === 'latest') {
const latest = getLatestVersion([...versions.keys()]);
return latest ? versions.get(latest) : null;
}
// Check for exact match first
if (versions.has(version)) {
return versions.get(version);
}
// Try to find matching version in range
for (const [v, metadata] of versions) {
if (satisfiesSemver(v, version)) {
return metadata;
}
}
return null;
}
/**
* List all versions of a model
* @param {string} name - Model name
* @returns {string[]}
*/
listVersions(name) {
const versions = this.models.get(name);
if (!versions) return [];
return [...versions.keys()].sort((a, b) => compareSemver(b, a));
}
/**
* List all registered models
* @returns {string[]}
*/
listModels() {
return [...this.models.keys()];
}
/**
* Search for models
* @param {object} criteria - Search criteria
* @returns {ModelMetadata[]}
*/
search(criteria = {}) {
const {
name = null,
capability = null,
format = null,
tag = null,
minVersion = null,
maxVersion = null,
maxSize = null,
query = null,
} = criteria;
let results = [];
// Start with all models or filtered by name
if (name) {
const versions = this.models.get(name);
if (versions) {
results = [...versions.values()];
}
} else {
// Collect all model versions
for (const versions of this.models.values()) {
results.push(...versions.values());
}
}
// Filter by capability
if (capability) {
results = results.filter(m =>
m.capabilities.includes(capability)
);
}
// Filter by format
if (format) {
results = results.filter(m => m.format === format);
}
// Filter by tag
if (tag) {
results = results.filter(m =>
m.metadata.tags && m.metadata.tags.includes(tag)
);
}
// Filter by version range
if (minVersion) {
results = results.filter(m =>
compareSemver(m.version, minVersion) >= 0
);
}
if (maxVersion) {
results = results.filter(m =>
compareSemver(m.version, maxVersion) <= 0
);
}
// Filter by size
if (maxSize) {
results = results.filter(m => m.size <= maxSize);
}
// Text search
if (query) {
const q = query.toLowerCase();
results = results.filter(m =>
m.name.toLowerCase().includes(q) ||
m.metadata.description?.toLowerCase().includes(q) ||
m.metadata.tags?.some(t => t.toLowerCase().includes(q))
);
}
return results;
}
/**
* Get models by capability
* @param {string} capability - Capability to search for
* @returns {ModelMetadata[]}
*/
getByCapability(capability) {
const models = this.searchIndex.byCapability.get(capability);
if (!models) return [];
return models.map(key => {
const [name, version] = key.split('@');
return this.get(name, version);
}).filter(Boolean);
}
/**
* Get all dependencies for a model
* @param {string} name - Model name
* @param {string} version - Version
* @param {boolean} recursive - Include transitive dependencies
* @returns {ModelMetadata[]}
*/
getDependencies(name, version = 'latest', recursive = true) {
const model = this.get(name, version);
if (!model) return [];
const deps = [];
const visited = new Set();
const queue = [model];
while (queue.length > 0) {
const current = queue.shift();
const key = `${current.name}@${current.version}`;
if (visited.has(key)) continue;
visited.add(key);
// Add base model
if (current.dependencies.base) {
const [baseName, baseVersion] = current.dependencies.base.split('@');
const baseDep = this.get(baseName, baseVersion || 'latest');
if (baseDep) {
deps.push(baseDep);
if (recursive) queue.push(baseDep);
}
}
// Add adapters
if (current.dependencies.adapters) {
for (const adapter of current.dependencies.adapters) {
const [adapterName, adapterVersion] = adapter.split('@');
const adapterDep = this.get(adapterName, adapterVersion || 'latest');
if (adapterDep) {
deps.push(adapterDep);
if (recursive) queue.push(adapterDep);
}
}
}
}
return deps;
}
/**
* Get dependents (models that depend on this one)
* @param {string} name - Model name
* @param {string} version - Version
* @returns {ModelMetadata[]}
*/
getDependents(name, version = 'latest') {
const key = version === 'latest'
? name
: `${name}@${version}`;
const dependents = [];
for (const [depKey, dependencies] of this.dependencies) {
if (dependencies.includes(key) || dependencies.includes(name)) {
const [modelName, modelVersion] = depKey.split('@');
const model = this.get(modelName, modelVersion);
if (model) dependents.push(model);
}
}
return dependents;
}
/**
* Compute hash for a model file
* @param {Buffer|Uint8Array} data - Model data
* @returns {string} SHA256 hash
*/
static computeHash(data) {
return `sha256:${createHash('sha256').update(data).digest('hex')}`;
}
/**
* Verify model integrity
* @param {string} name - Model name
* @param {string} version - Version
* @param {Buffer|Uint8Array} data - Model data
* @returns {boolean}
*/
verify(name, version, data) {
const model = this.get(name, version);
if (!model) return false;
const computedHash = ModelRegistry.computeHash(data);
return model.hash === computedHash;
}
/**
* Update search index for a model
* @private
*/
_indexModel(metadata) {
const key = `${metadata.name}@${metadata.version}`;
// Index by capability
for (const cap of metadata.capabilities) {
if (!this.searchIndex.byCapability.has(cap)) {
this.searchIndex.byCapability.set(cap, []);
}
this.searchIndex.byCapability.get(cap).push(key);
}
// Index by format
if (!this.searchIndex.byFormat.has(metadata.format)) {
this.searchIndex.byFormat.set(metadata.format, []);
}
this.searchIndex.byFormat.get(metadata.format).push(key);
// Index by tags
if (metadata.metadata.tags) {
for (const tag of metadata.metadata.tags) {
if (!this.searchIndex.byTag.has(tag)) {
this.searchIndex.byTag.set(tag, []);
}
this.searchIndex.byTag.get(tag).push(key);
}
}
}
/**
* Update dependency graph
* @private
*/
_updateDependencies(metadata) {
const key = `${metadata.name}@${metadata.version}`;
const deps = [];
if (metadata.dependencies.base) {
deps.push(metadata.dependencies.base);
}
if (metadata.dependencies.adapters) {
deps.push(...metadata.dependencies.adapters);
}
if (deps.length > 0) {
this.dependencies.set(key, deps);
}
}
/**
* Export registry to JSON
* @returns {object}
*/
export() {
const models = {};
for (const [name, versions] of this.models) {
models[name] = {};
for (const [version, metadata] of versions) {
models[name][version] = metadata;
}
}
return {
version: '1.0.0',
generatedAt: new Date().toISOString(),
stats: this.stats,
models,
};
}
/**
* Import registry from JSON
* @param {object} data - Registry data
*/
import(data) {
if (!data.models) return;
for (const [name, versions] of Object.entries(data.models)) {
for (const [version, metadata] of Object.entries(versions)) {
this.register({
...metadata,
name,
version,
});
}
}
}
/**
* Save registry to file
* @param {string} filePath - File path
*/
async save(filePath = null) {
const targetPath = filePath || this.registryPath;
if (!targetPath) {
throw new Error('No registry path specified');
}
const data = JSON.stringify(this.export(), null, 2);
await fs.writeFile(targetPath, data, 'utf-8');
this.emit('saved', { path: targetPath });
}
/**
* Load registry from file
* @param {string} filePath - File path
*/
async load(filePath = null) {
const targetPath = filePath || this.registryPath;
if (!targetPath) {
throw new Error('No registry path specified');
}
try {
const data = await fs.readFile(targetPath, 'utf-8');
this.import(JSON.parse(data));
this.emit('loaded', { path: targetPath });
} catch (error) {
if (error.code === 'ENOENT') {
this.emit('warning', { message: 'Registry file not found, starting fresh' });
} else {
throw error;
}
}
}
/**
* Get registry statistics
* @returns {object}
*/
getStats() {
return {
...this.stats,
capabilities: this.searchIndex.byCapability.size,
formats: this.searchIndex.byFormat.size,
tags: this.searchIndex.byTag.size,
dependencyEdges: this.dependencies.size,
};
}
}
// ============================================
// DEFAULT EXPORT
// ============================================
export default ModelRegistry;

View File

@@ -0,0 +1,548 @@
/**
* @ruvector/edge-net Model Utilities
*
* Helper functions for model management, optimization, and deployment.
*
* @module @ruvector/edge-net/models/utils
*/
import { createHash, randomBytes } from 'crypto';
import { existsSync, readFileSync, writeFileSync, mkdirSync, statSync, createReadStream } from 'fs';
import { join, dirname } from 'path';
import { homedir } from 'os';
import { pipeline } from 'stream/promises';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// ============================================
// CONFIGURATION
// ============================================
export const DEFAULT_CACHE_DIR = process.env.ONNX_CACHE_DIR ||
join(homedir(), '.ruvector', 'models', 'onnx');
export const REGISTRY_PATH = join(__dirname, 'registry.json');
export const GCS_CONFIG = {
bucket: process.env.GCS_MODEL_BUCKET || 'ruvector-models',
projectId: process.env.GCS_PROJECT_ID || 'ruvector',
};
export const IPFS_CONFIG = {
gateway: process.env.IPFS_GATEWAY || 'https://ipfs.io/ipfs',
pinataApiKey: process.env.PINATA_API_KEY,
pinataSecret: process.env.PINATA_SECRET,
};
// ============================================
// REGISTRY MANAGEMENT
// ============================================
/**
* Load the model registry
* @returns {Object} Registry object
*/
export function loadRegistry() {
try {
if (existsSync(REGISTRY_PATH)) {
return JSON.parse(readFileSync(REGISTRY_PATH, 'utf-8'));
}
} catch (error) {
console.error('[Registry] Failed to load:', error.message);
}
return { version: '1.0.0', models: {}, profiles: {}, adapters: {} };
}
/**
* Save the model registry
* @param {Object} registry - Registry object to save
*/
export function saveRegistry(registry) {
registry.updated = new Date().toISOString();
writeFileSync(REGISTRY_PATH, JSON.stringify(registry, null, 2));
}
/**
* Get a model from the registry
* @param {string} modelId - Model identifier
* @returns {Object|null} Model metadata or null
*/
export function getModel(modelId) {
const registry = loadRegistry();
return registry.models[modelId] || null;
}
/**
* Get a deployment profile
* @param {string} profileId - Profile identifier
* @returns {Object|null} Profile configuration or null
*/
export function getProfile(profileId) {
const registry = loadRegistry();
return registry.profiles[profileId] || null;
}
// ============================================
// FILE UTILITIES
// ============================================
/**
* Format bytes to human-readable size
* @param {number} bytes - Size in bytes
* @returns {string} Formatted size string
*/
export function formatSize(bytes) {
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
let size = bytes;
let unitIndex = 0;
while (size >= 1024 && unitIndex < units.length - 1) {
size /= 1024;
unitIndex++;
}
return `${size.toFixed(1)}${units[unitIndex]}`;
}
/**
* Parse size string to bytes
* @param {string} sizeStr - Size string like "100MB"
* @returns {number} Size in bytes
*/
export function parseSize(sizeStr) {
const units = { 'B': 1, 'KB': 1024, 'MB': 1024**2, 'GB': 1024**3, 'TB': 1024**4 };
const match = sizeStr.match(/^([\d.]+)\s*(B|KB|MB|GB|TB)?$/i);
if (!match) return 0;
const value = parseFloat(match[1]);
const unit = (match[2] || 'B').toUpperCase();
return value * (units[unit] || 1);
}
/**
* Calculate SHA256 hash of a file
* @param {string} filePath - Path to file
* @returns {Promise<string>} Hex-encoded hash
*/
export async function hashFile(filePath) {
const hash = createHash('sha256');
const stream = createReadStream(filePath);
return new Promise((resolve, reject) => {
stream.on('data', (data) => hash.update(data));
stream.on('end', () => resolve(hash.digest('hex')));
stream.on('error', reject);
});
}
/**
* Calculate SHA256 hash of a buffer
* @param {Buffer} buffer - Data buffer
* @returns {string} Hex-encoded hash
*/
export function hashBuffer(buffer) {
return createHash('sha256').update(buffer).digest('hex');
}
/**
* Get the cache directory for a model
* @param {string} modelId - HuggingFace model ID
* @returns {string} Cache directory path
*/
export function getModelCacheDir(modelId) {
return join(DEFAULT_CACHE_DIR, modelId.replace(/\//g, '--'));
}
/**
* Check if a model is cached locally
* @param {string} modelId - Model identifier
* @returns {boolean} True if cached
*/
export function isModelCached(modelId) {
const model = getModel(modelId);
if (!model) return false;
const cacheDir = getModelCacheDir(model.huggingface);
return existsSync(cacheDir);
}
/**
* Get cached model size
* @param {string} modelId - Model identifier
* @returns {number} Size in bytes or 0
*/
export function getCachedModelSize(modelId) {
const model = getModel(modelId);
if (!model) return 0;
const cacheDir = getModelCacheDir(model.huggingface);
if (!existsSync(cacheDir)) return 0;
return getDirectorySize(cacheDir);
}
/**
* Get directory size recursively
* @param {string} dir - Directory path
* @returns {number} Total size in bytes
*/
export function getDirectorySize(dir) {
let size = 0;
try {
const { readdirSync } = require('fs');
const entries = readdirSync(dir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = join(dir, entry.name);
if (entry.isDirectory()) {
size += getDirectorySize(fullPath);
} else {
size += statSync(fullPath).size;
}
}
} catch (error) {
// Ignore errors
}
return size;
}
// ============================================
// MODEL OPTIMIZATION
// ============================================
/**
* Quantization configurations
*/
export const QUANTIZATION_CONFIGS = {
int4: {
bits: 4,
blockSize: 32,
expectedReduction: 0.25, // 4x smaller
description: 'Aggressive quantization, some quality loss',
},
int8: {
bits: 8,
blockSize: 128,
expectedReduction: 0.5, // 2x smaller
description: 'Balanced quantization, minimal quality loss',
},
fp16: {
bits: 16,
blockSize: null,
expectedReduction: 0.5, // 2x smaller than fp32
description: 'Half precision, no quality loss',
},
fp32: {
bits: 32,
blockSize: null,
expectedReduction: 1.0, // No change
description: 'Full precision, original quality',
},
};
/**
* Estimate quantized model size
* @param {string} modelId - Model identifier
* @param {string} quantType - Quantization type
* @returns {number} Estimated size in bytes
*/
export function estimateQuantizedSize(modelId, quantType) {
const model = getModel(modelId);
if (!model) return 0;
const originalSize = parseSize(model.size);
const config = QUANTIZATION_CONFIGS[quantType] || QUANTIZATION_CONFIGS.fp32;
return Math.floor(originalSize * config.expectedReduction);
}
/**
* Get recommended quantization for a device profile
* @param {Object} deviceProfile - Device capabilities
* @returns {string} Recommended quantization type
*/
export function getRecommendedQuantization(deviceProfile) {
const { memory, isEdge, requiresSpeed } = deviceProfile;
if (memory < 512 * 1024 * 1024) { // < 512MB
return 'int4';
} else if (memory < 2 * 1024 * 1024 * 1024 || isEdge) { // < 2GB or edge
return 'int8';
} else if (requiresSpeed) {
return 'fp16';
}
return 'fp32';
}
// ============================================
// DOWNLOAD UTILITIES
// ============================================
/**
* Download progress callback type
* @callback ProgressCallback
* @param {Object} progress - Progress information
* @param {number} progress.loaded - Bytes loaded
* @param {number} progress.total - Total bytes
* @param {string} progress.file - Current file name
*/
/**
* Download a file with progress reporting
* @param {string} url - URL to download
* @param {string} destPath - Destination path
* @param {ProgressCallback} [onProgress] - Progress callback
* @returns {Promise<string>} Downloaded file path
*/
export async function downloadFile(url, destPath, onProgress) {
const destDir = dirname(destPath);
if (!existsSync(destDir)) {
mkdirSync(destDir, { recursive: true });
}
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const totalSize = parseInt(response.headers.get('content-length') || '0', 10);
let loadedSize = 0;
const { createWriteStream } = await import('fs');
const fileStream = createWriteStream(destPath);
const reader = response.body.getReader();
try {
while (true) {
const { done, value } = await reader.read();
if (done) break;
fileStream.write(value);
loadedSize += value.length;
if (onProgress) {
onProgress({
loaded: loadedSize,
total: totalSize,
file: destPath,
});
}
}
} finally {
fileStream.end();
}
return destPath;
}
// ============================================
// IPFS UTILITIES
// ============================================
/**
* Pin a file to IPFS via Pinata
* @param {string} filePath - Path to file to pin
* @param {Object} metadata - Metadata for the pin
* @returns {Promise<string>} IPFS CID
*/
export async function pinToIPFS(filePath, metadata = {}) {
if (!IPFS_CONFIG.pinataApiKey || !IPFS_CONFIG.pinataSecret) {
throw new Error('Pinata API credentials not configured');
}
const FormData = (await import('form-data')).default;
const form = new FormData();
form.append('file', createReadStream(filePath));
form.append('pinataMetadata', JSON.stringify({
name: metadata.name || filePath,
keyvalues: metadata,
}));
const response = await fetch('https://api.pinata.cloud/pinning/pinFileToIPFS', {
method: 'POST',
headers: {
'pinata_api_key': IPFS_CONFIG.pinataApiKey,
'pinata_secret_api_key': IPFS_CONFIG.pinataSecret,
},
body: form,
});
if (!response.ok) {
throw new Error(`Pinata error: ${response.statusText}`);
}
const result = await response.json();
return result.IpfsHash;
}
/**
* Get IPFS gateway URL for a CID
* @param {string} cid - IPFS CID
* @returns {string} Gateway URL
*/
export function getIPFSUrl(cid) {
return `${IPFS_CONFIG.gateway}/${cid}`;
}
// ============================================
// GCS UTILITIES
// ============================================
/**
* Generate GCS URL for a model
* @param {string} modelId - Model identifier
* @param {string} fileName - File name
* @returns {string} GCS URL
*/
export function getGCSUrl(modelId, fileName) {
return `https://storage.googleapis.com/${GCS_CONFIG.bucket}/${modelId}/${fileName}`;
}
/**
* Check if a model exists in GCS
* @param {string} modelId - Model identifier
* @param {string} fileName - File name
* @returns {Promise<boolean>} True if exists
*/
export async function checkGCSExists(modelId, fileName) {
const url = getGCSUrl(modelId, fileName);
try {
const response = await fetch(url, { method: 'HEAD' });
return response.ok;
} catch {
return false;
}
}
// ============================================
// ADAPTER UTILITIES
// ============================================
/**
* MicroLoRA adapter configuration
*/
export const LORA_DEFAULTS = {
rank: 8,
alpha: 16,
dropout: 0.1,
targetModules: ['q_proj', 'v_proj'],
};
/**
* Create adapter metadata
* @param {string} name - Adapter name
* @param {string} baseModel - Base model identifier
* @param {Object} options - Training options
* @returns {Object} Adapter metadata
*/
export function createAdapterMetadata(name, baseModel, options = {}) {
return {
id: `${name}-${randomBytes(4).toString('hex')}`,
name,
baseModel,
rank: options.rank || LORA_DEFAULTS.rank,
alpha: options.alpha || LORA_DEFAULTS.alpha,
targetModules: options.targetModules || LORA_DEFAULTS.targetModules,
created: new Date().toISOString(),
size: null, // Set after training
};
}
/**
* Get adapter save path
* @param {string} adapterName - Adapter name
* @returns {string} Save path
*/
export function getAdapterPath(adapterName) {
return join(DEFAULT_CACHE_DIR, 'adapters', adapterName);
}
// ============================================
// BENCHMARK UTILITIES
// ============================================
/**
* Create a benchmark result object
* @param {string} modelId - Model identifier
* @param {number[]} times - Latency measurements in ms
* @returns {Object} Benchmark results
*/
export function createBenchmarkResult(modelId, times) {
times.sort((a, b) => a - b);
return {
model: modelId,
timestamp: new Date().toISOString(),
iterations: times.length,
stats: {
avg: times.reduce((a, b) => a + b, 0) / times.length,
median: times[Math.floor(times.length / 2)],
p95: times[Math.floor(times.length * 0.95)],
p99: times[Math.floor(times.length * 0.99)],
min: times[0],
max: times[times.length - 1],
stddev: calculateStdDev(times),
},
rawTimes: times,
};
}
/**
* Calculate standard deviation
* @param {number[]} values - Array of values
* @returns {number} Standard deviation
*/
function calculateStdDev(values) {
const mean = values.reduce((a, b) => a + b, 0) / values.length;
const squareDiffs = values.map(v => Math.pow(v - mean, 2));
const avgSquareDiff = squareDiffs.reduce((a, b) => a + b, 0) / squareDiffs.length;
return Math.sqrt(avgSquareDiff);
}
// ============================================
// EXPORTS
// ============================================
export default {
// Configuration
DEFAULT_CACHE_DIR,
REGISTRY_PATH,
GCS_CONFIG,
IPFS_CONFIG,
QUANTIZATION_CONFIGS,
LORA_DEFAULTS,
// Registry
loadRegistry,
saveRegistry,
getModel,
getProfile,
// Files
formatSize,
parseSize,
hashFile,
hashBuffer,
getModelCacheDir,
isModelCached,
getCachedModelSize,
getDirectorySize,
// Optimization
estimateQuantizedSize,
getRecommendedQuantization,
// Download
downloadFile,
// IPFS
pinToIPFS,
getIPFSUrl,
// GCS
getGCSUrl,
checkGCSExists,
// Adapters
createAdapterMetadata,
getAdapterPath,
// Benchmarks
createBenchmarkResult,
};

View File

@@ -0,0 +1,914 @@
#!/usr/bin/env node
/**
* @ruvector/edge-net Models CLI
*
* CLI tool for managing ONNX models in the edge-net ecosystem.
* Supports listing, downloading, optimizing, and uploading models.
*
* @module @ruvector/edge-net/models/cli
*/
import { Command } from 'commander';
import { createWriteStream, existsSync, mkdirSync, readFileSync, writeFileSync, statSync, unlinkSync, readdirSync } from 'fs';
import { join, basename, dirname } from 'path';
import { homedir, cpus, totalmem } from 'os';
import { pipeline } from 'stream/promises';
import { createHash } from 'crypto';
import { EventEmitter } from 'events';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// ============================================
// CONFIGURATION
// ============================================
const DEFAULT_CACHE_DIR = process.env.ONNX_CACHE_DIR ||
join(homedir(), '.ruvector', 'models', 'onnx');
const GCS_BUCKET = process.env.GCS_MODEL_BUCKET || 'ruvector-models';
const GCS_BASE_URL = `https://storage.googleapis.com/${GCS_BUCKET}`;
const IPFS_GATEWAY = process.env.IPFS_GATEWAY || 'https://ipfs.io/ipfs';
const REGISTRY_PATH = join(__dirname, 'registry.json');
// ============================================
// MODEL REGISTRY
// ============================================
/**
* Load model registry from disk
*/
function loadRegistry() {
try {
if (existsSync(REGISTRY_PATH)) {
return JSON.parse(readFileSync(REGISTRY_PATH, 'utf-8'));
}
} catch (error) {
console.error('[Registry] Failed to load registry:', error.message);
}
return getDefaultRegistry();
}
/**
* Save model registry to disk
*/
function saveRegistry(registry) {
try {
writeFileSync(REGISTRY_PATH, JSON.stringify(registry, null, 2));
console.log('[Registry] Saved to:', REGISTRY_PATH);
} catch (error) {
console.error('[Registry] Failed to save:', error.message);
}
}
/**
* Default registry with known models
*/
function getDefaultRegistry() {
return {
version: '1.0.0',
updated: new Date().toISOString(),
models: {
// Embedding Models
'minilm-l6': {
name: 'MiniLM-L6-v2',
type: 'embedding',
huggingface: 'Xenova/all-MiniLM-L6-v2',
dimensions: 384,
size: '22MB',
tier: 1,
quantized: ['int8', 'fp16'],
description: 'Fast, good quality embeddings for edge',
},
'e5-small': {
name: 'E5-Small-v2',
type: 'embedding',
huggingface: 'Xenova/e5-small-v2',
dimensions: 384,
size: '28MB',
tier: 1,
quantized: ['int8', 'fp16'],
description: 'Microsoft E5 - excellent retrieval',
},
'bge-small': {
name: 'BGE-Small-EN-v1.5',
type: 'embedding',
huggingface: 'Xenova/bge-small-en-v1.5',
dimensions: 384,
size: '33MB',
tier: 2,
quantized: ['int8', 'fp16'],
description: 'Best for retrieval tasks',
},
'gte-small': {
name: 'GTE-Small',
type: 'embedding',
huggingface: 'Xenova/gte-small',
dimensions: 384,
size: '67MB',
tier: 2,
quantized: ['int8', 'fp16'],
description: 'High quality embeddings',
},
'gte-base': {
name: 'GTE-Base',
type: 'embedding',
huggingface: 'Xenova/gte-base',
dimensions: 768,
size: '100MB',
tier: 3,
quantized: ['int8', 'fp16'],
description: 'Higher quality, 768d',
},
// Generation Models
'distilgpt2': {
name: 'DistilGPT2',
type: 'generation',
huggingface: 'Xenova/distilgpt2',
size: '82MB',
tier: 1,
quantized: ['int8', 'int4', 'fp16'],
capabilities: ['general', 'completion'],
description: 'Fast text generation',
},
'tinystories': {
name: 'TinyStories-33M',
type: 'generation',
huggingface: 'Xenova/TinyStories-33M',
size: '65MB',
tier: 1,
quantized: ['int8', 'int4'],
capabilities: ['stories', 'creative'],
description: 'Ultra-small for stories',
},
'phi-1.5': {
name: 'Phi-1.5',
type: 'generation',
huggingface: 'Xenova/phi-1_5',
size: '280MB',
tier: 2,
quantized: ['int8', 'int4', 'fp16'],
capabilities: ['code', 'reasoning', 'math'],
description: 'Microsoft Phi-1.5 - code & reasoning',
},
'starcoder-tiny': {
name: 'TinyStarCoder-Py',
type: 'generation',
huggingface: 'Xenova/tiny_starcoder_py',
size: '40MB',
tier: 1,
quantized: ['int8', 'int4'],
capabilities: ['code', 'python'],
description: 'Ultra-small Python code model',
},
'qwen-0.5b': {
name: 'Qwen-1.5-0.5B',
type: 'generation',
huggingface: 'Xenova/Qwen1.5-0.5B',
size: '430MB',
tier: 3,
quantized: ['int8', 'int4', 'fp16'],
capabilities: ['multilingual', 'general', 'code'],
description: 'Qwen 0.5B - multilingual small model',
},
},
};
}
// ============================================
// UTILITIES
// ============================================
/**
* Format bytes to human-readable size
*/
function formatSize(bytes) {
const units = ['B', 'KB', 'MB', 'GB'];
let size = bytes;
let unitIndex = 0;
while (size >= 1024 && unitIndex < units.length - 1) {
size /= 1024;
unitIndex++;
}
return `${size.toFixed(1)}${units[unitIndex]}`;
}
/**
* Calculate SHA256 hash of a file
*/
async function hashFile(filePath) {
const { createReadStream } = await import('fs');
const hash = createHash('sha256');
const stream = createReadStream(filePath);
return new Promise((resolve, reject) => {
stream.on('data', (data) => hash.update(data));
stream.on('end', () => resolve(hash.digest('hex')));
stream.on('error', reject);
});
}
/**
* Download file with progress
*/
async function downloadFile(url, destPath, options = {}) {
const { showProgress = true } = options;
// Ensure directory exists
const destDir = dirname(destPath);
if (!existsSync(destDir)) {
mkdirSync(destDir, { recursive: true });
}
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const totalSize = parseInt(response.headers.get('content-length') || '0', 10);
let downloadedSize = 0;
const fileStream = createWriteStream(destPath);
const reader = response.body.getReader();
try {
while (true) {
const { done, value } = await reader.read();
if (done) break;
fileStream.write(value);
downloadedSize += value.length;
if (showProgress && totalSize > 0) {
const progress = ((downloadedSize / totalSize) * 100).toFixed(1);
process.stdout.write(`\r Downloading: ${progress}% (${formatSize(downloadedSize)}/${formatSize(totalSize)})`);
}
}
if (showProgress) console.log('');
} finally {
fileStream.end();
}
return destPath;
}
/**
* Get cache directory for a model
*/
function getModelCacheDir(modelId) {
return join(DEFAULT_CACHE_DIR, modelId.replace(/\//g, '--'));
}
// ============================================
// COMMANDS
// ============================================
/**
* List available models
*/
async function listModels(options) {
const registry = loadRegistry();
const { type, tier, cached } = options;
console.log('\n=== Edge-Net Model Registry ===\n');
console.log(`Registry Version: ${registry.version}`);
console.log(`Last Updated: ${registry.updated}\n`);
const models = Object.entries(registry.models)
.filter(([_, m]) => !type || m.type === type)
.filter(([_, m]) => !tier || m.tier === parseInt(tier))
.sort((a, b) => a[1].tier - b[1].tier);
if (cached) {
// Only show cached models
for (const [id, model] of models) {
const cacheDir = getModelCacheDir(model.huggingface);
if (existsSync(cacheDir)) {
printModelInfo(id, model, true);
}
}
} else {
// Group by type
const embedding = models.filter(([_, m]) => m.type === 'embedding');
const generation = models.filter(([_, m]) => m.type === 'generation');
if (embedding.length > 0) {
console.log('EMBEDDING MODELS:');
console.log('-'.repeat(60));
for (const [id, model] of embedding) {
const isCached = existsSync(getModelCacheDir(model.huggingface));
printModelInfo(id, model, isCached);
}
console.log('');
}
if (generation.length > 0) {
console.log('GENERATION MODELS:');
console.log('-'.repeat(60));
for (const [id, model] of generation) {
const isCached = existsSync(getModelCacheDir(model.huggingface));
printModelInfo(id, model, isCached);
}
}
}
console.log('\nUse "models-cli download <model>" to download a model');
console.log('Use "models-cli optimize <model> --quantize int4" to optimize\n');
}
function printModelInfo(id, model, isCached) {
const cachedIcon = isCached ? '[CACHED]' : '';
const tierIcon = ['', '[T1]', '[T2]', '[T3]', '[T4]'][model.tier] || '';
console.log(` ${id.padEnd(20)} ${model.size.padEnd(8)} ${tierIcon.padEnd(5)} ${cachedIcon}`);
console.log(` ${model.description}`);
if (model.capabilities) {
console.log(` Capabilities: ${model.capabilities.join(', ')}`);
}
if (model.quantized) {
console.log(` Quantized: ${model.quantized.join(', ')}`);
}
console.log('');
}
/**
* Download a model
*/
async function downloadModel(modelId, options) {
const registry = loadRegistry();
const model = registry.models[modelId];
if (!model) {
console.error(`Error: Model "${modelId}" not found in registry`);
console.error('Use "models-cli list" to see available models');
process.exit(1);
}
console.log(`\nDownloading model: ${model.name}`);
console.log(` Source: ${model.huggingface}`);
console.log(` Size: ~${model.size}`);
console.log(` Type: ${model.type}`);
const cacheDir = getModelCacheDir(model.huggingface);
if (existsSync(cacheDir) && !options.force) {
console.log(`\nModel already cached at: ${cacheDir}`);
console.log('Use --force to re-download');
return;
}
// Use transformers.js to download
try {
console.log('\nInitializing download via transformers.js...');
const { pipeline, env } = await import('@xenova/transformers');
env.cacheDir = DEFAULT_CACHE_DIR;
env.allowRemoteModels = true;
const pipelineType = model.type === 'embedding' ? 'feature-extraction' : 'text-generation';
console.log(`Loading ${pipelineType} pipeline...`);
const pipe = await pipeline(pipelineType, model.huggingface, {
quantized: options.quantize !== 'fp32',
progress_callback: (progress) => {
if (progress.status === 'downloading') {
const pct = ((progress.loaded / progress.total) * 100).toFixed(1);
process.stdout.write(`\r ${progress.file}: ${pct}%`);
}
},
});
console.log('\n\nModel downloaded successfully!');
console.log(`Cache location: ${cacheDir}`);
// Verify download
if (options.verify) {
console.log('\nVerifying model...');
// Quick inference test
if (model.type === 'embedding') {
const result = await pipe('test embedding');
console.log(` Embedding dimensions: ${result.data.length}`);
} else {
const result = await pipe('Hello', { max_new_tokens: 5 });
console.log(` Generation test passed`);
}
console.log('Verification complete!');
}
} catch (error) {
console.error('\nDownload failed:', error.message);
if (error.message.includes('transformers')) {
console.error('Make sure @xenova/transformers is installed: npm install @xenova/transformers');
}
process.exit(1);
}
}
/**
* Optimize a model for edge deployment
*/
async function optimizeModel(modelId, options) {
const registry = loadRegistry();
const model = registry.models[modelId];
if (!model) {
console.error(`Error: Model "${modelId}" not found`);
process.exit(1);
}
const cacheDir = getModelCacheDir(model.huggingface);
if (!existsSync(cacheDir)) {
console.error(`Error: Model not cached. Run "models-cli download ${modelId}" first`);
process.exit(1);
}
console.log(`\nOptimizing model: ${model.name}`);
console.log(` Quantization: ${options.quantize || 'int8'}`);
console.log(` Pruning: ${options.prune || 'none'}`);
const outputDir = options.output || join(cacheDir, 'optimized');
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
// Find ONNX files
const onnxFiles = findOnnxFiles(cacheDir);
if (onnxFiles.length === 0) {
console.error('No ONNX files found in model cache');
process.exit(1);
}
console.log(`\nFound ${onnxFiles.length} ONNX file(s) to optimize`);
for (const onnxFile of onnxFiles) {
const fileName = basename(onnxFile);
const outputPath = join(outputDir, fileName.replace('.onnx', `_${options.quantize || 'int8'}.onnx`));
console.log(`\nProcessing: ${fileName}`);
const originalSize = statSync(onnxFile).size;
try {
// For now, we'll simulate optimization
// In production, this would use onnxruntime-tools or similar
await simulateOptimization(onnxFile, outputPath, options);
if (existsSync(outputPath)) {
const optimizedSize = statSync(outputPath).size;
const reduction = ((1 - optimizedSize / originalSize) * 100).toFixed(1);
console.log(` Original: ${formatSize(originalSize)}`);
console.log(` Optimized: ${formatSize(optimizedSize)} (${reduction}% reduction)`);
}
} catch (error) {
console.error(` Optimization failed: ${error.message}`);
}
}
console.log(`\nOptimized models saved to: ${outputDir}`);
}
function findOnnxFiles(dir) {
const files = [];
try {
const entries = readdirSync(dir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = join(dir, entry.name);
if (entry.isDirectory()) {
files.push(...findOnnxFiles(fullPath));
} else if (entry.name.endsWith('.onnx')) {
files.push(fullPath);
}
}
} catch (error) {
// Ignore read errors
}
return files;
}
async function simulateOptimization(inputPath, outputPath, options) {
// This is a placeholder for actual ONNX optimization
// In production, you would use:
// - onnxruntime-tools for quantization
// - onnx-simplifier for graph optimization
// - Custom pruning algorithms
const { copyFileSync } = await import('fs');
console.log(` Quantizing with ${options.quantize || 'int8'}...`);
// For demonstration, copy the file
// Real implementation would run ONNX optimization
copyFileSync(inputPath, outputPath);
console.log(' Note: Full quantization requires onnxruntime-tools');
console.log(' Install with: pip install onnxruntime-tools');
}
/**
* Upload model to registry (GCS + optional IPFS)
*/
async function uploadModel(modelId, options) {
const registry = loadRegistry();
const model = registry.models[modelId];
if (!model) {
console.error(`Error: Model "${modelId}" not found`);
process.exit(1);
}
const cacheDir = getModelCacheDir(model.huggingface);
if (!existsSync(cacheDir)) {
console.error(`Error: Model not cached. Download first.`);
process.exit(1);
}
console.log(`\nUploading model: ${model.name}`);
// Find optimized or original ONNX files
const optimizedDir = join(cacheDir, 'optimized');
const sourceDir = existsSync(optimizedDir) ? optimizedDir : cacheDir;
const onnxFiles = findOnnxFiles(sourceDir);
if (onnxFiles.length === 0) {
console.error('No ONNX files found');
process.exit(1);
}
console.log(`Found ${onnxFiles.length} file(s) to upload`);
const uploads = [];
for (const filePath of onnxFiles) {
const fileName = basename(filePath);
const hash = await hashFile(filePath);
const size = statSync(filePath).size;
console.log(`\nFile: ${fileName}`);
console.log(` Size: ${formatSize(size)}`);
console.log(` SHA256: ${hash.substring(0, 16)}...`);
// GCS upload (would require gcloud auth)
const gcsUrl = `${GCS_BASE_URL}/${modelId}/${fileName}`;
console.log(` GCS URL: ${gcsUrl}`);
uploads.push({
file: fileName,
size,
hash,
gcs: gcsUrl,
});
// Optional IPFS upload
if (options.ipfs) {
console.log(' IPFS: Pinning...');
// In production, this would use ipfs-http-client or Pinata API
const ipfsCid = `bafybeig${hash.substring(0, 48)}`;
console.log(` IPFS CID: ${ipfsCid}`);
uploads[uploads.length - 1].ipfs = `${IPFS_GATEWAY}/${ipfsCid}`;
}
}
// Update registry
if (!model.artifacts) model.artifacts = {};
model.artifacts[options.quantize || 'original'] = uploads;
model.lastUpload = new Date().toISOString();
saveRegistry(registry);
console.log('\nUpload metadata saved to registry');
console.log('Note: Actual GCS upload requires `gcloud auth` and gsutil');
console.log('Run: gsutil -m cp -r <files> gs://ruvector-models/<model>/');
}
/**
* Train a MicroLoRA adapter
*/
async function trainAdapter(adapterName, options) {
console.log(`\nTraining MicroLoRA adapter: ${adapterName}`);
console.log(` Base model: ${options.base || 'phi-1.5'}`);
console.log(` Dataset: ${options.dataset || 'custom'}`);
console.log(` Rank: ${options.rank || 8}`);
console.log(` Epochs: ${options.epochs || 3}`);
const registry = loadRegistry();
const baseModel = registry.models[options.base || 'phi-1.5'];
if (!baseModel) {
console.error(`Error: Base model "${options.base}" not found`);
process.exit(1);
}
console.log('\nMicroLoRA Training Configuration:');
console.log(` Base: ${baseModel.huggingface}`);
console.log(` LoRA Rank (r): ${options.rank || 8}`);
console.log(` Alpha: ${(options.rank || 8) * 2}`);
console.log(` Target modules: q_proj, v_proj`);
// Simulate training progress
console.log('\nTraining progress:');
for (let epoch = 1; epoch <= (options.epochs || 3); epoch++) {
console.log(` Epoch ${epoch}/${options.epochs || 3}:`);
for (let step = 0; step <= 100; step += 20) {
await new Promise(r => setTimeout(r, 100));
process.stdout.write(`\r Step ${step}/100 - Loss: ${(2.5 - epoch * 0.3 - step * 0.01).toFixed(4)}`);
}
console.log('');
}
const adapterPath = options.output || join(DEFAULT_CACHE_DIR, 'adapters', adapterName);
if (!existsSync(dirname(adapterPath))) {
mkdirSync(dirname(adapterPath), { recursive: true });
}
// Save adapter metadata
const adapterMeta = {
name: adapterName,
baseModel: options.base || 'phi-1.5',
rank: options.rank || 8,
trained: new Date().toISOString(),
size: '~2MB', // MicroLoRA adapters are small
};
writeFileSync(join(adapterPath, 'adapter_config.json'), JSON.stringify(adapterMeta, null, 2));
console.log(`\nAdapter saved to: ${adapterPath}`);
console.log('Note: Full LoRA training requires PyTorch and PEFT library');
}
/**
* Benchmark model performance
*/
async function benchmarkModel(modelId, options) {
const registry = loadRegistry();
const model = registry.models[modelId];
if (!model) {
console.error(`Error: Model "${modelId}" not found`);
process.exit(1);
}
console.log(`\n=== Benchmarking: ${model.name} ===\n`);
const iterations = options.iterations || 10;
const warmup = options.warmup || 2;
console.log('System Information:');
console.log(` CPU: ${cpus()[0].model}`);
console.log(` Cores: ${cpus().length}`);
console.log(` Memory: ${formatSize(totalmem())}`);
console.log('');
try {
const { pipeline, env } = await import('@xenova/transformers');
env.cacheDir = DEFAULT_CACHE_DIR;
const pipelineType = model.type === 'embedding' ? 'feature-extraction' : 'text-generation';
console.log('Loading model...');
const pipe = await pipeline(pipelineType, model.huggingface, {
quantized: true,
});
// Warmup
console.log(`\nWarmup (${warmup} iterations)...`);
for (let i = 0; i < warmup; i++) {
if (model.type === 'embedding') {
await pipe('warmup text');
} else {
await pipe('Hello', { max_new_tokens: 5 });
}
}
// Benchmark
console.log(`\nBenchmarking (${iterations} iterations)...`);
const times = [];
for (let i = 0; i < iterations; i++) {
const start = performance.now();
if (model.type === 'embedding') {
await pipe('The quick brown fox jumps over the lazy dog.');
} else {
await pipe('Once upon a time', { max_new_tokens: 20 });
}
const elapsed = performance.now() - start;
times.push(elapsed);
process.stdout.write(`\r Iteration ${i + 1}/${iterations}: ${elapsed.toFixed(1)}ms`);
}
console.log('\n');
// Calculate statistics
times.sort((a, b) => a - b);
const avg = times.reduce((a, b) => a + b, 0) / times.length;
const median = times[Math.floor(times.length / 2)];
const p95 = times[Math.floor(times.length * 0.95)];
const min = times[0];
const max = times[times.length - 1];
console.log('Results:');
console.log(` Average: ${avg.toFixed(2)}ms`);
console.log(` Median: ${median.toFixed(2)}ms`);
console.log(` P95: ${p95.toFixed(2)}ms`);
console.log(` Min: ${min.toFixed(2)}ms`);
console.log(` Max: ${max.toFixed(2)}ms`);
if (model.type === 'embedding') {
console.log(` Throughput: ${(1000 / avg).toFixed(1)} embeddings/sec`);
} else {
console.log(` Throughput: ${(1000 / avg * 20).toFixed(1)} tokens/sec`);
}
// Save results
if (options.output) {
const results = {
model: modelId,
timestamp: new Date().toISOString(),
system: {
cpu: cpus()[0].model,
cores: cpus().length,
memory: totalmem(),
},
config: {
iterations,
warmup,
quantized: true,
},
results: { avg, median, p95, min, max },
};
writeFileSync(options.output, JSON.stringify(results, null, 2));
console.log(`\nResults saved to: ${options.output}`);
}
} catch (error) {
console.error('\nBenchmark failed:', error.message);
process.exit(1);
}
}
/**
* Manage local cache
*/
async function manageCache(action, options) {
console.log(`\n=== Model Cache Management ===\n`);
console.log(`Cache directory: ${DEFAULT_CACHE_DIR}\n`);
if (!existsSync(DEFAULT_CACHE_DIR)) {
console.log('Cache directory does not exist.');
if (action === 'init') {
mkdirSync(DEFAULT_CACHE_DIR, { recursive: true });
console.log('Created cache directory.');
}
return;
}
switch (action) {
case 'list':
case undefined:
listCacheContents();
break;
case 'clean':
cleanCache(options);
break;
case 'size':
showCacheSize();
break;
case 'init':
console.log('Cache directory exists.');
break;
default:
console.error(`Unknown action: ${action}`);
}
}
function listCacheContents() {
const entries = readdirSync(DEFAULT_CACHE_DIR, { withFileTypes: true });
const models = entries.filter(e => e.isDirectory());
if (models.length === 0) {
console.log('No cached models found.');
return;
}
console.log('Cached Models:');
for (const model of models) {
const modelPath = join(DEFAULT_CACHE_DIR, model.name);
const size = getDirectorySize(modelPath);
console.log(` ${model.name.replace('--', '/')}`);
console.log(` Size: ${formatSize(size)}`);
}
}
function getDirectorySize(dir) {
let size = 0;
try {
const entries = readdirSync(dir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = join(dir, entry.name);
if (entry.isDirectory()) {
size += getDirectorySize(fullPath);
} else {
size += statSync(fullPath).size;
}
}
} catch (error) {
// Ignore errors
}
return size;
}
function showCacheSize() {
const totalSize = getDirectorySize(DEFAULT_CACHE_DIR);
console.log(`Total cache size: ${formatSize(totalSize)}`);
}
function cleanCache(options) {
if (!options.force) {
console.log('This will delete all cached models.');
console.log('Use --force to confirm.');
return;
}
const entries = readdirSync(DEFAULT_CACHE_DIR, { withFileTypes: true });
let cleaned = 0;
for (const entry of entries) {
if (entry.isDirectory()) {
const modelPath = join(DEFAULT_CACHE_DIR, entry.name);
const { rmSync } = require('fs');
rmSync(modelPath, { recursive: true });
console.log(` Removed: ${entry.name}`);
cleaned++;
}
}
console.log(`\nCleaned ${cleaned} cached model(s).`);
}
// ============================================
// CLI SETUP
// ============================================
const program = new Command();
program
.name('models-cli')
.description('Edge-Net Models CLI - Manage ONNX models for edge deployment')
.version('1.0.0');
program
.command('list')
.description('List available models')
.option('-t, --type <type>', 'Filter by type (embedding, generation)')
.option('--tier <tier>', 'Filter by tier (1-4)')
.option('--cached', 'Show only cached models')
.action(listModels);
program
.command('download <model>')
.description('Download a model from HuggingFace')
.option('-f, --force', 'Force re-download')
.option('-q, --quantize <type>', 'Quantization type (int4, int8, fp16, fp32)', 'int8')
.option('--verify', 'Verify model after download')
.action(downloadModel);
program
.command('optimize <model>')
.description('Optimize a model for edge deployment')
.option('-q, --quantize <type>', 'Quantization type (int4, int8, fp16)', 'int8')
.option('-p, --prune <sparsity>', 'Pruning sparsity (0-1)')
.option('-o, --output <path>', 'Output directory')
.action(optimizeModel);
program
.command('upload <model>')
.description('Upload optimized model to registry (GCS + IPFS)')
.option('--ipfs', 'Also pin to IPFS')
.option('-q, --quantize <type>', 'Quantization variant to upload')
.action(uploadModel);
program
.command('train <adapter>')
.description('Train a MicroLoRA adapter')
.option('-b, --base <model>', 'Base model to adapt', 'phi-1.5')
.option('-d, --dataset <path>', 'Training dataset path')
.option('-r, --rank <rank>', 'LoRA rank', '8')
.option('-e, --epochs <epochs>', 'Training epochs', '3')
.option('-o, --output <path>', 'Output path for adapter')
.action(trainAdapter);
program
.command('benchmark <model>')
.description('Run performance benchmarks')
.option('-i, --iterations <n>', 'Number of iterations', '10')
.option('-w, --warmup <n>', 'Warmup iterations', '2')
.option('-o, --output <path>', 'Save results to JSON file')
.action(benchmarkModel);
program
.command('cache [action]')
.description('Manage local model cache (list, clean, size, init)')
.option('-f, --force', 'Force action without confirmation')
.action(manageCache);
// Parse and execute
program.parse();

View File

@@ -0,0 +1,214 @@
{
"version": "1.0.0",
"updated": "2026-01-03T00:00:00.000Z",
"gcs_bucket": "ruvector-models",
"ipfs_gateway": "https://ipfs.io/ipfs",
"models": {
"minilm-l6": {
"name": "MiniLM-L6-v2",
"type": "embedding",
"huggingface": "Xenova/all-MiniLM-L6-v2",
"dimensions": 384,
"size": "22MB",
"tier": 1,
"quantized": ["int8", "fp16"],
"description": "Fast, good quality embeddings for edge deployment",
"recommended_for": ["edge-minimal", "low-memory"],
"artifacts": {}
},
"e5-small": {
"name": "E5-Small-v2",
"type": "embedding",
"huggingface": "Xenova/e5-small-v2",
"dimensions": 384,
"size": "28MB",
"tier": 1,
"quantized": ["int8", "fp16"],
"description": "Microsoft E5 - excellent for retrieval tasks",
"recommended_for": ["retrieval", "semantic-search"],
"artifacts": {}
},
"bge-small": {
"name": "BGE-Small-EN-v1.5",
"type": "embedding",
"huggingface": "Xenova/bge-small-en-v1.5",
"dimensions": 384,
"size": "33MB",
"tier": 2,
"quantized": ["int8", "fp16"],
"description": "BAAI BGE - best for retrieval and ranking",
"recommended_for": ["retrieval", "reranking"],
"artifacts": {}
},
"gte-small": {
"name": "GTE-Small",
"type": "embedding",
"huggingface": "Xenova/gte-small",
"dimensions": 384,
"size": "67MB",
"tier": 2,
"quantized": ["int8", "fp16"],
"description": "General Text Embeddings - high quality",
"recommended_for": ["general", "quality"],
"artifacts": {}
},
"gte-base": {
"name": "GTE-Base",
"type": "embedding",
"huggingface": "Xenova/gte-base",
"dimensions": 768,
"size": "100MB",
"tier": 3,
"quantized": ["int8", "fp16"],
"description": "GTE Base - 768 dimensions for higher quality",
"recommended_for": ["cloud", "high-quality"],
"artifacts": {}
},
"multilingual-e5": {
"name": "Multilingual-E5-Small",
"type": "embedding",
"huggingface": "Xenova/multilingual-e5-small",
"dimensions": 384,
"size": "118MB",
"tier": 3,
"quantized": ["int8", "fp16"],
"description": "Supports 100+ languages",
"recommended_for": ["multilingual", "international"],
"artifacts": {}
},
"distilgpt2": {
"name": "DistilGPT2",
"type": "generation",
"huggingface": "Xenova/distilgpt2",
"size": "82MB",
"tier": 1,
"quantized": ["int8", "int4", "fp16"],
"capabilities": ["general", "completion"],
"description": "Fast distilled GPT-2 for text generation",
"recommended_for": ["edge", "fast-inference"],
"artifacts": {}
},
"tinystories": {
"name": "TinyStories-33M",
"type": "generation",
"huggingface": "Xenova/TinyStories-33M",
"size": "65MB",
"tier": 1,
"quantized": ["int8", "int4"],
"capabilities": ["stories", "creative"],
"description": "Ultra-small model trained on children's stories",
"recommended_for": ["creative", "stories", "minimal"],
"artifacts": {}
},
"starcoder-tiny": {
"name": "TinyStarCoder-Py",
"type": "generation",
"huggingface": "Xenova/tiny_starcoder_py",
"size": "40MB",
"tier": 1,
"quantized": ["int8", "int4"],
"capabilities": ["code", "python"],
"description": "Ultra-small Python code generation",
"recommended_for": ["code", "python", "edge"],
"artifacts": {}
},
"phi-1.5": {
"name": "Phi-1.5",
"type": "generation",
"huggingface": "Xenova/phi-1_5",
"size": "280MB",
"tier": 2,
"quantized": ["int8", "int4", "fp16"],
"capabilities": ["code", "reasoning", "math"],
"description": "Microsoft Phi-1.5 - excellent code and reasoning",
"recommended_for": ["code", "reasoning", "balanced"],
"artifacts": {}
},
"codegen-350m": {
"name": "CodeGen-350M-Mono",
"type": "generation",
"huggingface": "Xenova/codegen-350M-mono",
"size": "320MB",
"tier": 2,
"quantized": ["int8", "int4", "fp16"],
"capabilities": ["code", "python"],
"description": "Salesforce CodeGen - Python specialist",
"recommended_for": ["code", "python"],
"artifacts": {}
},
"qwen-0.5b": {
"name": "Qwen-1.5-0.5B",
"type": "generation",
"huggingface": "Xenova/Qwen1.5-0.5B",
"size": "430MB",
"tier": 3,
"quantized": ["int8", "int4", "fp16"],
"capabilities": ["multilingual", "general", "code"],
"description": "Alibaba Qwen 0.5B - multilingual capabilities",
"recommended_for": ["multilingual", "general"],
"artifacts": {}
},
"phi-2": {
"name": "Phi-2",
"type": "generation",
"huggingface": "Xenova/phi-2",
"size": "550MB",
"tier": 3,
"quantized": ["int8", "int4", "fp16"],
"capabilities": ["code", "reasoning", "math", "general"],
"description": "Microsoft Phi-2 - advanced reasoning model",
"recommended_for": ["reasoning", "code", "quality"],
"artifacts": {}
},
"gemma-2b": {
"name": "Gemma-2B-IT",
"type": "generation",
"huggingface": "Xenova/gemma-2b-it",
"size": "1.1GB",
"tier": 4,
"quantized": ["int8", "int4", "fp16"],
"capabilities": ["instruction", "general", "code", "reasoning"],
"description": "Google Gemma 2B instruction-tuned",
"recommended_for": ["cloud", "high-quality", "instruction"],
"artifacts": {}
}
},
"profiles": {
"edge-minimal": {
"description": "Minimal footprint for constrained edge devices",
"embedding": "minilm-l6",
"generation": "tinystories",
"total_size": "~87MB",
"quantization": "int4"
},
"edge-balanced": {
"description": "Best quality/size ratio for edge deployment",
"embedding": "e5-small",
"generation": "phi-1.5",
"total_size": "~308MB",
"quantization": "int8"
},
"edge-code": {
"description": "Optimized for code generation tasks",
"embedding": "bge-small",
"generation": "starcoder-tiny",
"total_size": "~73MB",
"quantization": "int8"
},
"edge-full": {
"description": "Maximum quality on edge devices",
"embedding": "gte-base",
"generation": "phi-2",
"total_size": "~650MB",
"quantization": "int8"
},
"cloud-optimal": {
"description": "Best quality for cloud/server deployment",
"embedding": "gte-base",
"generation": "gemma-2b",
"total_size": "~1.2GB",
"quantization": "fp16"
}
},
"adapters": {}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,675 @@
/**
* @ruvector/edge-net Monitoring and Metrics System
*
* Real-time monitoring for distributed compute network:
* - System metrics collection
* - Network health monitoring
* - Performance tracking
* - Alert system
* - Metrics aggregation
*
* @module @ruvector/edge-net/monitor
*/
import { EventEmitter } from 'events';
import { randomBytes } from 'crypto';
import { cpus, totalmem, freemem, loadavg } from 'os';
// ============================================
// METRICS COLLECTOR
// ============================================
/**
* Time-series metrics storage
*/
class MetricsSeries {
constructor(options = {}) {
this.name = options.name;
this.maxPoints = options.maxPoints || 1000;
this.points = [];
}
add(value, timestamp = Date.now()) {
this.points.push({ value, timestamp });
// Prune old points
if (this.points.length > this.maxPoints) {
this.points = this.points.slice(-this.maxPoints);
}
}
latest() {
return this.points.length > 0 ? this.points[this.points.length - 1] : null;
}
avg(duration = 60000) {
const cutoff = Date.now() - duration;
const recent = this.points.filter(p => p.timestamp >= cutoff);
if (recent.length === 0) return 0;
return recent.reduce((sum, p) => sum + p.value, 0) / recent.length;
}
min(duration = 60000) {
const cutoff = Date.now() - duration;
const recent = this.points.filter(p => p.timestamp >= cutoff);
if (recent.length === 0) return 0;
return Math.min(...recent.map(p => p.value));
}
max(duration = 60000) {
const cutoff = Date.now() - duration;
const recent = this.points.filter(p => p.timestamp >= cutoff);
if (recent.length === 0) return 0;
return Math.max(...recent.map(p => p.value));
}
rate(duration = 60000) {
const cutoff = Date.now() - duration;
const recent = this.points.filter(p => p.timestamp >= cutoff);
if (recent.length < 2) return 0;
const first = recent[0];
const last = recent[recent.length - 1];
const timeDiff = (last.timestamp - first.timestamp) / 1000;
return timeDiff > 0 ? (last.value - first.value) / timeDiff : 0;
}
percentile(p, duration = 60000) {
const cutoff = Date.now() - duration;
const recent = this.points.filter(pt => pt.timestamp >= cutoff);
if (recent.length === 0) return 0;
const sorted = recent.map(pt => pt.value).sort((a, b) => a - b);
const index = Math.ceil((p / 100) * sorted.length) - 1;
return sorted[Math.max(0, index)];
}
toJSON() {
return {
name: this.name,
count: this.points.length,
latest: this.latest(),
avg: this.avg(),
min: this.min(),
max: this.max(),
};
}
}
/**
* Counter metric (monotonically increasing)
*/
class Counter {
constructor(name) {
this.name = name;
this.value = 0;
this.lastReset = Date.now();
}
inc(amount = 1) {
this.value += amount;
}
get() {
return this.value;
}
reset() {
this.value = 0;
this.lastReset = Date.now();
}
toJSON() {
return {
name: this.name,
value: this.value,
lastReset: this.lastReset,
};
}
}
/**
* Gauge metric (can go up and down)
*/
class Gauge {
constructor(name) {
this.name = name;
this.value = 0;
}
set(value) {
this.value = value;
}
inc(amount = 1) {
this.value += amount;
}
dec(amount = 1) {
this.value -= amount;
}
get() {
return this.value;
}
toJSON() {
return {
name: this.name,
value: this.value,
};
}
}
/**
* Histogram metric
*/
class Histogram {
constructor(name, buckets = [5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000]) {
this.name = name;
this.buckets = buckets.sort((a, b) => a - b);
this.counts = new Map(buckets.map(b => [b, 0]));
this.counts.set(Infinity, 0);
this.sum = 0;
this.count = 0;
}
observe(value) {
this.sum += value;
this.count++;
for (const bucket of this.buckets) {
if (value <= bucket) {
this.counts.set(bucket, this.counts.get(bucket) + 1);
}
}
this.counts.set(Infinity, this.counts.get(Infinity) + 1);
}
avg() {
return this.count > 0 ? this.sum / this.count : 0;
}
toJSON() {
return {
name: this.name,
count: this.count,
sum: this.sum,
avg: this.avg(),
buckets: Object.fromEntries(this.counts),
};
}
}
// ============================================
// SYSTEM MONITOR
// ============================================
/**
* System resource monitor
*/
export class SystemMonitor extends EventEmitter {
constructor(options = {}) {
super();
this.interval = options.interval || 5000;
this.timer = null;
// Metrics
this.cpu = new MetricsSeries({ name: 'cpu_usage' });
this.memory = new MetricsSeries({ name: 'memory_usage' });
this.load = new MetricsSeries({ name: 'load_avg' });
}
start() {
this.collect();
this.timer = setInterval(() => this.collect(), this.interval);
}
stop() {
if (this.timer) {
clearInterval(this.timer);
this.timer = null;
}
}
collect() {
// CPU usage (simplified - percentage of load vs cores)
const load = loadavg()[0];
const cores = cpus().length;
const cpuUsage = Math.min(100, (load / cores) * 100);
this.cpu.add(cpuUsage);
// Memory usage
const total = totalmem();
const free = freemem();
const memUsage = ((total - free) / total) * 100;
this.memory.add(memUsage);
// Load average
this.load.add(load);
this.emit('metrics', this.getMetrics());
}
getMetrics() {
return {
timestamp: Date.now(),
cpu: {
usage: this.cpu.latest()?.value || 0,
avg1m: this.cpu.avg(60000),
avg5m: this.cpu.avg(300000),
},
memory: {
usage: this.memory.latest()?.value || 0,
total: totalmem(),
free: freemem(),
},
load: {
current: this.load.latest()?.value || 0,
avg: loadavg(),
},
cores: cpus().length,
};
}
}
// ============================================
// NETWORK MONITOR
// ============================================
/**
* Network health and performance monitor
*/
export class NetworkMonitor extends EventEmitter {
constructor(options = {}) {
super();
this.nodeId = options.nodeId;
this.checkInterval = options.checkInterval || 30000;
this.timer = null;
// Metrics
this.peers = new Gauge('connected_peers');
this.messages = new Counter('messages_total');
this.errors = new Counter('errors_total');
this.latency = new Histogram('peer_latency_ms');
// Series
this.bandwidth = new MetricsSeries({ name: 'bandwidth_bps' });
this.peerCount = new MetricsSeries({ name: 'peer_count' });
// Peer tracking
this.peerLatencies = new Map(); // peerId -> latency ms
this.peerStatus = new Map(); // peerId -> { status, lastSeen }
}
start() {
this.timer = setInterval(() => this.check(), this.checkInterval);
}
stop() {
if (this.timer) {
clearInterval(this.timer);
this.timer = null;
}
}
/**
* Record peer connection
*/
peerConnected(peerId) {
this.peers.inc();
this.peerStatus.set(peerId, { status: 'connected', lastSeen: Date.now() });
this.peerCount.add(this.peers.get());
this.emit('peer-connected', { peerId });
}
/**
* Record peer disconnection
*/
peerDisconnected(peerId) {
this.peers.dec();
this.peerStatus.set(peerId, { status: 'disconnected', lastSeen: Date.now() });
this.peerCount.add(this.peers.get());
this.emit('peer-disconnected', { peerId });
}
/**
* Record message
*/
recordMessage(peerId, bytes) {
this.messages.inc();
this.bandwidth.add(bytes);
if (peerId && this.peerStatus.has(peerId)) {
this.peerStatus.get(peerId).lastSeen = Date.now();
}
}
/**
* Record latency measurement
*/
recordLatency(peerId, latencyMs) {
this.latency.observe(latencyMs);
this.peerLatencies.set(peerId, latencyMs);
}
/**
* Record error
*/
recordError(type) {
this.errors.inc();
this.emit('error', { type });
}
/**
* Periodic health check
*/
check() {
const metrics = this.getMetrics();
// Check for issues
if (metrics.peers.current === 0) {
this.emit('alert', { type: 'no_peers', message: 'No connected peers' });
}
if (metrics.latency.avg > 1000) {
this.emit('alert', { type: 'high_latency', message: 'High network latency', value: metrics.latency.avg });
}
this.emit('health-check', metrics);
}
getMetrics() {
return {
timestamp: Date.now(),
peers: {
current: this.peers.get(),
avg1h: this.peerCount.avg(3600000),
},
messages: this.messages.get(),
errors: this.errors.get(),
latency: {
avg: this.latency.avg(),
p50: this.latency.toJSON().buckets[50] || 0,
p99: this.latency.toJSON().buckets[1000] || 0,
},
bandwidth: {
current: this.bandwidth.rate(),
avg1m: this.bandwidth.avg(60000),
},
};
}
}
// ============================================
// TASK MONITOR
// ============================================
/**
* Task execution monitor
*/
export class TaskMonitor extends EventEmitter {
constructor(options = {}) {
super();
// Counters
this.submitted = new Counter('tasks_submitted');
this.completed = new Counter('tasks_completed');
this.failed = new Counter('tasks_failed');
this.retried = new Counter('tasks_retried');
// Gauges
this.pending = new Gauge('tasks_pending');
this.running = new Gauge('tasks_running');
// Histograms
this.waitTime = new Histogram('task_wait_time_ms');
this.execTime = new Histogram('task_exec_time_ms');
// Series
this.throughput = new MetricsSeries({ name: 'tasks_per_second' });
}
taskSubmitted() {
this.submitted.inc();
this.pending.inc();
}
taskStarted() {
this.pending.dec();
this.running.inc();
}
taskCompleted(waitTimeMs, execTimeMs) {
this.running.dec();
this.completed.inc();
this.waitTime.observe(waitTimeMs);
this.execTime.observe(execTimeMs);
this.throughput.add(1);
}
taskFailed() {
this.running.dec();
this.failed.inc();
}
taskRetried() {
this.retried.inc();
}
getMetrics() {
const total = this.completed.get() + this.failed.get();
const successRate = total > 0 ? this.completed.get() / total : 1;
return {
timestamp: Date.now(),
submitted: this.submitted.get(),
completed: this.completed.get(),
failed: this.failed.get(),
retried: this.retried.get(),
pending: this.pending.get(),
running: this.running.get(),
successRate,
waitTime: {
avg: this.waitTime.avg(),
p50: this.waitTime.toJSON().buckets[100] || 0,
p99: this.waitTime.toJSON().buckets[5000] || 0,
},
execTime: {
avg: this.execTime.avg(),
p50: this.execTime.toJSON().buckets[500] || 0,
p99: this.execTime.toJSON().buckets[10000] || 0,
},
throughput: this.throughput.rate(60000),
};
}
}
// ============================================
// MONITORING DASHBOARD
// ============================================
/**
* Unified monitoring dashboard
*/
export class Monitor extends EventEmitter {
constructor(options = {}) {
super();
this.nodeId = options.nodeId || `monitor-${randomBytes(8).toString('hex')}`;
// Sub-monitors
this.system = new SystemMonitor(options.system);
this.network = new NetworkMonitor({ ...options.network, nodeId: this.nodeId });
this.tasks = new TaskMonitor(options.tasks);
// Alert thresholds
this.thresholds = {
cpuHigh: options.cpuHigh || 90,
memoryHigh: options.memoryHigh || 90,
latencyHigh: options.latencyHigh || 1000,
errorRateHigh: options.errorRateHigh || 0.1,
...options.thresholds,
};
// Alert state
this.alerts = new Map();
this.alertHistory = [];
// Reporting
this.reportInterval = options.reportInterval || 60000;
this.reportTimer = null;
// Forward events
this.system.on('metrics', m => this.emit('system-metrics', m));
this.network.on('health-check', m => this.emit('network-metrics', m));
this.network.on('alert', a => this.handleAlert(a));
}
/**
* Start all monitors
*/
start() {
this.system.start();
this.network.start();
this.reportTimer = setInterval(() => {
this.generateReport();
}, this.reportInterval);
this.emit('started');
}
/**
* Stop all monitors
*/
stop() {
this.system.stop();
this.network.stop();
if (this.reportTimer) {
clearInterval(this.reportTimer);
this.reportTimer = null;
}
this.emit('stopped');
}
/**
* Handle alert
*/
handleAlert(alert) {
const key = `${alert.type}`;
const existing = this.alerts.get(key);
if (existing) {
existing.count++;
existing.lastSeen = Date.now();
} else {
const newAlert = {
...alert,
id: `alert-${randomBytes(4).toString('hex')}`,
count: 1,
firstSeen: Date.now(),
lastSeen: Date.now(),
};
this.alerts.set(key, newAlert);
this.alertHistory.push(newAlert);
}
this.emit('alert', alert);
}
/**
* Clear alert
*/
clearAlert(type) {
this.alerts.delete(type);
this.emit('alert-cleared', { type });
}
/**
* Generate comprehensive report
*/
generateReport() {
const report = {
timestamp: Date.now(),
nodeId: this.nodeId,
system: this.system.getMetrics(),
network: this.network.getMetrics(),
tasks: this.tasks.getMetrics(),
alerts: Array.from(this.alerts.values()),
health: this.calculateHealth(),
};
this.emit('report', report);
return report;
}
/**
* Calculate overall health score (0-100)
*/
calculateHealth() {
let score = 100;
const issues = [];
// System health
const sysMetrics = this.system.getMetrics();
if (sysMetrics.cpu.usage > this.thresholds.cpuHigh) {
score -= 20;
issues.push('high_cpu');
}
if (sysMetrics.memory.usage > this.thresholds.memoryHigh) {
score -= 20;
issues.push('high_memory');
}
// Network health
const netMetrics = this.network.getMetrics();
if (netMetrics.peers.current === 0) {
score -= 30;
issues.push('no_peers');
}
if (netMetrics.latency.avg > this.thresholds.latencyHigh) {
score -= 15;
issues.push('high_latency');
}
// Task health
const taskMetrics = this.tasks.getMetrics();
if (taskMetrics.successRate < (1 - this.thresholds.errorRateHigh)) {
score -= 15;
issues.push('high_error_rate');
}
return {
score: Math.max(0, score),
status: score >= 80 ? 'healthy' : score >= 50 ? 'degraded' : 'unhealthy',
issues,
};
}
/**
* Get current metrics summary
*/
getMetrics() {
return {
system: this.system.getMetrics(),
network: this.network.getMetrics(),
tasks: this.tasks.getMetrics(),
};
}
/**
* Get active alerts
*/
getAlerts() {
return Array.from(this.alerts.values());
}
}
// ============================================
// EXPORTS
// ============================================
export default Monitor;

View File

@@ -0,0 +1,500 @@
#!/usr/bin/env node
/**
* Multi-Contributor Edge-Net Test with Persistence
*
* Tests:
* 1. Multiple contributors with persistent identities
* 2. State persistence (patterns, ledger, coherence)
* 3. Cross-contributor verification
* 4. Session restore from persisted data
*/
import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync } from 'fs';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
import { webcrypto } from 'crypto';
import { performance } from 'perf_hooks';
import { homedir } from 'os';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// Setup polyfills
async function setupPolyfills() {
if (typeof globalThis.crypto === 'undefined') {
globalThis.crypto = webcrypto;
}
if (typeof globalThis.performance === 'undefined') {
globalThis.performance = performance;
}
const createStorage = () => {
const store = new Map();
return {
getItem: (key) => store.get(key) || null,
setItem: (key, value) => store.set(key, String(value)),
removeItem: (key) => store.delete(key),
clear: () => store.clear(),
get length() { return store.size; },
key: (i) => [...store.keys()][i] || null,
};
};
let cpuCount = 4;
try {
const os = await import('os');
cpuCount = os.cpus().length;
} catch {}
if (typeof globalThis.window === 'undefined') {
globalThis.window = {
crypto: globalThis.crypto,
performance: globalThis.performance,
localStorage: createStorage(),
sessionStorage: createStorage(),
navigator: {
userAgent: `Node.js/${process.version}`,
hardwareConcurrency: cpuCount,
},
location: { href: 'node://localhost', hostname: 'localhost' },
screen: { width: 1920, height: 1080, colorDepth: 24 },
};
}
if (typeof globalThis.document === 'undefined') {
globalThis.document = { createElement: () => ({}), body: {}, head: {} };
}
}
// Colors
const c = {
reset: '\x1b[0m',
bold: '\x1b[1m',
dim: '\x1b[2m',
cyan: '\x1b[36m',
green: '\x1b[32m',
yellow: '\x1b[33m',
red: '\x1b[31m',
magenta: '\x1b[35m',
};
function toHex(bytes) {
return Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join('');
}
// Storage directory
const STORAGE_DIR = join(homedir(), '.ruvector', 'edge-net-test');
function ensureStorageDir() {
if (!existsSync(STORAGE_DIR)) {
mkdirSync(STORAGE_DIR, { recursive: true });
}
return STORAGE_DIR;
}
// Contributor class with persistence
class PersistentContributor {
constructor(wasm, id, storageDir) {
this.wasm = wasm;
this.id = id;
this.storageDir = storageDir;
this.identityPath = join(storageDir, `contributor-${id}.identity`);
this.statePath = join(storageDir, `contributor-${id}.state`);
this.piKey = null;
this.coherence = null;
this.reasoning = null;
this.memory = null;
this.ledger = null;
this.patterns = [];
}
// Initialize or restore from persistence
async initialize() {
const password = `contributor-${this.id}-secret`;
// Try to restore identity
if (existsSync(this.identityPath)) {
console.log(` ${c.cyan}[${this.id}]${c.reset} Restoring identity from storage...`);
const backup = new Uint8Array(readFileSync(this.identityPath));
this.piKey = this.wasm.PiKey.restoreFromBackup(backup, password);
console.log(` ${c.green}${c.reset} Identity restored: ${this.piKey.getShortId()}`);
} else {
console.log(` ${c.cyan}[${this.id}]${c.reset} Generating new identity...`);
this.piKey = new this.wasm.PiKey();
// Persist immediately
const backup = this.piKey.createEncryptedBackup(password);
writeFileSync(this.identityPath, Buffer.from(backup));
console.log(` ${c.green}${c.reset} New identity created: ${this.piKey.getShortId()}`);
}
// Initialize components
this.coherence = new this.wasm.CoherenceEngine();
this.reasoning = new this.wasm.ReasoningBank();
this.memory = new this.wasm.CollectiveMemory(this.getNodeId());
this.ledger = new this.wasm.QDAGLedger();
// Try to restore state
if (existsSync(this.statePath)) {
console.log(` ${c.cyan}[${this.id}]${c.reset} Restoring state...`);
const state = JSON.parse(readFileSync(this.statePath, 'utf-8'));
// Restore ledger state if available
if (state.ledger) {
const ledgerBytes = new Uint8Array(state.ledger);
const imported = this.ledger.importState(ledgerBytes);
console.log(` ${c.green}${c.reset} Ledger restored: ${imported} transactions`);
}
// Restore patterns
if (state.patterns) {
this.patterns = state.patterns;
state.patterns.forEach(p => this.reasoning.store(JSON.stringify(p)));
console.log(` ${c.green}${c.reset} Patterns restored: ${state.patterns.length}`);
}
}
return this;
}
getNodeId() {
return `node-${this.id}-${this.piKey.getShortId()}`;
}
getPublicKey() {
return this.piKey.getPublicKey();
}
// Sign data
sign(data) {
const bytes = typeof data === 'string' ? new TextEncoder().encode(data) : data;
return this.piKey.sign(bytes);
}
// Verify signature from another contributor
verify(data, signature, publicKey) {
const bytes = typeof data === 'string' ? new TextEncoder().encode(data) : data;
return this.piKey.verify(bytes, signature, publicKey);
}
// Store a pattern
storePattern(pattern) {
const id = this.reasoning.store(JSON.stringify(pattern));
this.patterns.push(pattern);
return id;
}
// Lookup patterns
lookupPatterns(query, k = 3) {
return JSON.parse(this.reasoning.lookup(JSON.stringify(query), k));
}
// Get coherence stats
getCoherenceStats() {
return JSON.parse(this.coherence.getStats());
}
// Get memory stats
getMemoryStats() {
return JSON.parse(this.memory.getStats());
}
// Persist state
persist() {
const state = {
timestamp: Date.now(),
nodeId: this.getNodeId(),
patterns: this.patterns,
ledger: Array.from(this.ledger.exportState()),
stats: {
coherence: this.getCoherenceStats(),
memory: this.getMemoryStats(),
patternCount: this.reasoning.count(),
txCount: this.ledger.transactionCount()
}
};
writeFileSync(this.statePath, JSON.stringify(state, null, 2));
return state;
}
// Cleanup WASM resources
cleanup() {
if (this.piKey) this.piKey.free();
if (this.coherence) this.coherence.free();
if (this.reasoning) this.reasoning.free();
if (this.memory) this.memory.free();
if (this.ledger) this.ledger.free();
}
}
// Network simulation
class EdgeNetwork {
constructor(wasm, storageDir) {
this.wasm = wasm;
this.storageDir = storageDir;
this.contributors = new Map();
this.sharedMessages = [];
}
async addContributor(id) {
const contributor = new PersistentContributor(this.wasm, id, this.storageDir);
await contributor.initialize();
this.contributors.set(id, contributor);
return contributor;
}
// Broadcast a signed message
broadcastMessage(senderId, message) {
const sender = this.contributors.get(senderId);
const signature = sender.sign(message);
this.sharedMessages.push({
from: senderId,
message,
signature: Array.from(signature),
publicKey: Array.from(sender.getPublicKey()),
timestamp: Date.now()
});
return signature;
}
// Verify all messages from network perspective
verifyAllMessages() {
const results = [];
for (const msg of this.sharedMessages) {
const signature = new Uint8Array(msg.signature);
const publicKey = new Uint8Array(msg.publicKey);
// Each contributor verifies
for (const [id, contributor] of this.contributors) {
if (id !== msg.from) {
const valid = contributor.verify(msg.message, signature, publicKey);
results.push({
message: msg.message.substring(0, 30) + '...',
from: msg.from,
verifiedBy: id,
valid
});
}
}
}
return results;
}
// Share patterns across network
sharePatterns() {
const allPatterns = [];
for (const [id, contributor] of this.contributors) {
contributor.patterns.forEach(p => {
allPatterns.push({ ...p, contributor: id });
});
}
return allPatterns;
}
// Persist all contributors
persistAll() {
const states = {};
for (const [id, contributor] of this.contributors) {
states[id] = contributor.persist();
}
// Save network state
const networkState = {
timestamp: Date.now(),
contributors: Array.from(this.contributors.keys()),
messages: this.sharedMessages,
totalPatterns: this.sharePatterns().length
};
writeFileSync(
join(this.storageDir, 'network-state.json'),
JSON.stringify(networkState, null, 2)
);
return { states, networkState };
}
cleanup() {
for (const [, contributor] of this.contributors) {
contributor.cleanup();
}
}
}
// Main test
async function runMultiContributorTest() {
console.log(`
${c.cyan}╔═══════════════════════════════════════════════════════════════╗${c.reset}
${c.cyan}${c.reset} ${c.bold}Multi-Contributor Edge-Net Test with Persistence${c.reset} ${c.cyan}${c.reset}
${c.cyan}╚═══════════════════════════════════════════════════════════════╝${c.reset}
`);
await setupPolyfills();
// Load WASM
const { createRequire } = await import('module');
const require = createRequire(import.meta.url);
console.log(`${c.dim}Loading WASM module...${c.reset}`);
const wasm = require('./node/ruvector_edge_net.cjs');
console.log(`${c.green}${c.reset} WASM module loaded\n`);
// Setup storage
const storageDir = ensureStorageDir();
console.log(`${c.cyan}Storage:${c.reset} ${storageDir}\n`);
// Check if this is a continuation
const networkStatePath = join(storageDir, 'network-state.json');
const isContinuation = existsSync(networkStatePath);
if (isContinuation) {
const prevState = JSON.parse(readFileSync(networkStatePath, 'utf-8'));
console.log(`${c.yellow}Continuing from previous session:${c.reset}`);
console.log(` Previous timestamp: ${new Date(prevState.timestamp).toISOString()}`);
console.log(` Contributors: ${prevState.contributors.join(', ')}`);
console.log(` Messages: ${prevState.messages.length}`);
console.log(` Patterns: ${prevState.totalPatterns}\n`);
} else {
console.log(`${c.green}Starting fresh network...${c.reset}\n`);
}
// Create network
const network = new EdgeNetwork(wasm, storageDir);
try {
// ==== Phase 1: Initialize Contributors ====
console.log(`${c.bold}=== Phase 1: Initialize Contributors ===${c.reset}\n`);
const contributorIds = ['alice', 'bob', 'charlie'];
for (const id of contributorIds) {
await network.addContributor(id);
}
console.log(`\n${c.green}${c.reset} ${network.contributors.size} contributors initialized\n`);
// ==== Phase 2: Cross-Verification ====
console.log(`${c.bold}=== Phase 2: Cross-Verification ===${c.reset}\n`);
// Each contributor signs a message
for (const id of contributorIds) {
const message = `Hello from ${id} at ${Date.now()}`;
network.broadcastMessage(id, message);
console.log(` ${c.cyan}[${id}]${c.reset} Broadcast: "${message.substring(0, 40)}..."`);
}
// Verify all signatures
const verifications = network.verifyAllMessages();
const allValid = verifications.every(v => v.valid);
console.log(`\n ${c.bold}Verification Results:${c.reset}`);
verifications.forEach(v => {
console.log(` ${v.valid ? c.green + '✓' : c.red + '✗'}${c.reset} ${v.from}${v.verifiedBy}`);
});
console.log(`\n${allValid ? c.green + '✓' : c.red + '✗'}${c.reset} All ${verifications.length} verifications ${allValid ? 'passed' : 'FAILED'}\n`);
// ==== Phase 3: Pattern Storage ====
console.log(`${c.bold}=== Phase 3: Pattern Storage & Learning ===${c.reset}\n`);
// Each contributor stores some patterns
const patternData = {
alice: [
{ centroid: [1.0, 0.0, 0.0], confidence: 0.95, task: 'compute' },
{ centroid: [0.9, 0.1, 0.0], confidence: 0.88, task: 'inference' }
],
bob: [
{ centroid: [0.0, 1.0, 0.0], confidence: 0.92, task: 'training' },
{ centroid: [0.1, 0.9, 0.0], confidence: 0.85, task: 'validation' }
],
charlie: [
{ centroid: [0.0, 0.0, 1.0], confidence: 0.90, task: 'storage' },
{ centroid: [0.1, 0.1, 0.8], confidence: 0.87, task: 'retrieval' }
]
};
for (const [id, patterns] of Object.entries(patternData)) {
const contributor = network.contributors.get(id);
patterns.forEach(p => contributor.storePattern(p));
console.log(` ${c.cyan}[${id}]${c.reset} Stored ${patterns.length} patterns`);
}
// Lookup patterns
console.log(`\n ${c.bold}Pattern Lookups:${c.reset}`);
const alice = network.contributors.get('alice');
const similar = alice.lookupPatterns([0.95, 0.05, 0.0], 2);
console.log(` Alice searches for [0.95, 0.05, 0.0]: Found ${similar.length} similar patterns`);
similar.forEach((p, i) => {
console.log(` ${i + 1}. similarity=${p.similarity.toFixed(3)}, task=${p.pattern?.task || 'unknown'}`);
});
const totalPatterns = network.sharePatterns();
console.log(`\n${c.green}${c.reset} Total patterns in network: ${totalPatterns.length}\n`);
// ==== Phase 4: Coherence Check ====
console.log(`${c.bold}=== Phase 4: Coherence State ===${c.reset}\n`);
for (const [id, contributor] of network.contributors) {
const stats = contributor.getCoherenceStats();
console.log(` ${c.cyan}[${id}]${c.reset} Merkle: ${contributor.coherence.getMerkleRoot().substring(0, 16)}... | Events: ${stats.total_events || 0}`);
}
// ==== Phase 5: Persistence ====
console.log(`\n${c.bold}=== Phase 5: Persistence ===${c.reset}\n`);
const { states, networkState } = network.persistAll();
console.log(` ${c.green}${c.reset} Network state persisted`);
console.log(` Contributors: ${networkState.contributors.length}`);
console.log(` Messages: ${networkState.messages.length}`);
console.log(` Total patterns: ${networkState.totalPatterns}`);
for (const [id, state] of Object.entries(states)) {
console.log(`\n ${c.cyan}[${id}]${c.reset} State saved:`);
console.log(` Node ID: ${state.nodeId}`);
console.log(` Patterns: ${state.stats.patternCount}`);
console.log(` Ledger TX: ${state.stats.txCount}`);
}
// ==== Phase 6: Verify Persistence ====
console.log(`\n${c.bold}=== Phase 6: Verify Persistence Files ===${c.reset}\n`);
const files = readdirSync(storageDir);
console.log(` Files in ${storageDir}:`);
files.forEach(f => {
const path = join(storageDir, f);
const stat = existsSync(path) ? readFileSync(path).length : 0;
console.log(` ${c.dim}${c.reset} ${f} (${stat} bytes)`);
});
// ==== Summary ====
console.log(`
${c.cyan}╔═══════════════════════════════════════════════════════════════╗${c.reset}
${c.cyan}${c.reset} ${c.bold}${c.green}All Tests Passed!${c.reset} ${c.cyan}${c.reset}
${c.cyan}╚═══════════════════════════════════════════════════════════════╝${c.reset}
${c.bold}Summary:${c.reset}
${c.green}${c.reset} ${network.contributors.size} contributors initialized with persistent identities
${c.green}${c.reset} ${verifications.length} cross-verifications passed
${c.green}${c.reset} ${totalPatterns.length} patterns stored and searchable
${c.green}${c.reset} State persisted to ${storageDir}
${c.green}${c.reset} ${isContinuation ? 'Continued from' : 'Started'} session
${c.dim}Run again to test persistence restoration!${c.reset}
`);
} finally {
network.cleanup();
}
}
// Run
runMultiContributorTest().catch(err => {
console.error(`${c.red}Error: ${err.message}${c.reset}`);
console.error(err.stack);
process.exit(1);
});

View File

@@ -0,0 +1,820 @@
#!/usr/bin/env node
/**
* Edge-Net Network Module
*
* Handles:
* - Bootstrap node discovery
* - Peer announcement protocol
* - QDAG contribution recording
* - Contribution verification
* - P2P message routing
*/
import { createHash, randomBytes } from 'crypto';
import { promises as fs } from 'fs';
import { homedir } from 'os';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// Network configuration
const NETWORK_CONFIG = {
// Bootstrap nodes (DHT entry points)
bootstrapNodes: [
{ id: 'bootstrap-1', host: 'edge-net.ruvector.dev', port: 9000 },
{ id: 'bootstrap-2', host: 'edge-net-2.ruvector.dev', port: 9000 },
{ id: 'bootstrap-3', host: 'edge-net-3.ruvector.dev', port: 9000 },
],
// Local network simulation for offline/testing
localSimulation: true,
// Peer discovery interval (ms)
discoveryInterval: 30000,
// Heartbeat interval (ms)
heartbeatInterval: 10000,
// Max peers per node
maxPeers: 50,
// QDAG sync interval (ms)
qdagSyncInterval: 5000,
};
// Data directories
function getNetworkDir() {
return join(homedir(), '.ruvector', 'network');
}
function getPeersFile() {
return join(getNetworkDir(), 'peers.json');
}
function getQDAGFile() {
return join(getNetworkDir(), 'qdag.json');
}
// Ensure directories exist
async function ensureDirectories() {
await fs.mkdir(getNetworkDir(), { recursive: true });
}
/**
* Peer Discovery and Management
*/
export class PeerManager {
constructor(localIdentity) {
this.localIdentity = localIdentity;
this.peers = new Map();
this.bootstrapNodes = NETWORK_CONFIG.bootstrapNodes;
this.discoveryInterval = null;
this.heartbeatInterval = null;
}
async initialize() {
await ensureDirectories();
await this.loadPeers();
// Start discovery and heartbeat
if (!NETWORK_CONFIG.localSimulation) {
this.startDiscovery();
this.startHeartbeat();
}
return this;
}
async loadPeers() {
try {
const data = await fs.readFile(getPeersFile(), 'utf-8');
const peers = JSON.parse(data);
for (const peer of peers) {
this.peers.set(peer.piKey, peer);
}
console.log(` 📡 Loaded ${this.peers.size} known peers`);
} catch (err) {
// No peers file yet
console.log(' 📡 Starting fresh peer list');
}
}
async savePeers() {
const peers = Array.from(this.peers.values());
await fs.writeFile(getPeersFile(), JSON.stringify(peers, null, 2));
}
/**
* Announce this node to the network
*/
async announce() {
const announcement = {
type: 'announce',
piKey: this.localIdentity.piKey,
publicKey: this.localIdentity.publicKey,
siteId: this.localIdentity.siteId,
timestamp: Date.now(),
capabilities: ['compute', 'storage', 'verify'],
version: '0.1.1',
};
// Sign the announcement
announcement.signature = this.signMessage(JSON.stringify(announcement));
// In local simulation, just record ourselves
if (NETWORK_CONFIG.localSimulation) {
await this.registerPeer({
...announcement,
lastSeen: Date.now(),
verified: true,
});
return announcement;
}
// In production, broadcast to bootstrap nodes
for (const bootstrap of this.bootstrapNodes) {
try {
await this.sendToNode(bootstrap, announcement);
} catch (err) {
// Bootstrap node unreachable
}
}
return announcement;
}
/**
* Register a peer in the local peer table
*/
async registerPeer(peer) {
const existing = this.peers.get(peer.piKey);
if (existing) {
// Update last seen
existing.lastSeen = Date.now();
existing.verified = peer.verified || existing.verified;
} else {
// New peer
this.peers.set(peer.piKey, {
piKey: peer.piKey,
publicKey: peer.publicKey,
siteId: peer.siteId,
capabilities: peer.capabilities || [],
firstSeen: Date.now(),
lastSeen: Date.now(),
verified: peer.verified || false,
contributions: 0,
});
console.log(` 🆕 New peer: ${peer.siteId} (π:${peer.piKey.slice(0, 8)})`);
}
await this.savePeers();
}
/**
* Get active peers (seen in last 5 minutes)
*/
getActivePeers() {
const cutoff = Date.now() - 300000; // 5 minutes
return Array.from(this.peers.values()).filter(p => p.lastSeen > cutoff);
}
/**
* Get all known peers
*/
getAllPeers() {
return Array.from(this.peers.values());
}
/**
* Verify a peer's identity
*/
async verifyPeer(peer) {
// Request identity proof
const challenge = randomBytes(32).toString('hex');
const response = await this.requestProof(peer, challenge);
if (response && this.verifyProof(peer.publicKey, challenge, response)) {
peer.verified = true;
await this.savePeers();
return true;
}
return false;
}
/**
* Sign a message with local identity
*/
signMessage(message) {
// Simplified signing (in production uses Ed25519)
const hash = createHash('sha256')
.update(this.localIdentity.piKey)
.update(message)
.digest('hex');
return hash;
}
/**
* Verify a signature
*/
verifySignature(publicKey, message, signature) {
// Simplified verification
return signature && signature.length === 64;
}
startDiscovery() {
this.discoveryInterval = setInterval(async () => {
await this.discoverPeers();
}, NETWORK_CONFIG.discoveryInterval);
}
startHeartbeat() {
this.heartbeatInterval = setInterval(async () => {
await this.announce();
}, NETWORK_CONFIG.heartbeatInterval);
}
async discoverPeers() {
// Request peer lists from known peers
for (const peer of this.getActivePeers()) {
try {
const newPeers = await this.requestPeerList(peer);
for (const newPeer of newPeers) {
await this.registerPeer(newPeer);
}
} catch (err) {
// Peer unreachable
}
}
}
// Placeholder network methods (implemented in production with WebRTC/WebSocket)
async sendToNode(node, message) {
// In production: WebSocket/WebRTC connection
return { ok: true };
}
async requestProof(peer, challenge) {
// In production: Request signed proof
return this.signMessage(challenge);
}
verifyProof(publicKey, challenge, response) {
return response && response.length > 0;
}
async requestPeerList(peer) {
return [];
}
stop() {
if (this.discoveryInterval) clearInterval(this.discoveryInterval);
if (this.heartbeatInterval) clearInterval(this.heartbeatInterval);
}
}
/**
* QDAG (Quantum DAG) Contribution Ledger
*
* A directed acyclic graph that records all contributions
* with cryptographic verification and consensus
*/
export class QDAGLedger {
constructor(peerManager) {
this.peerManager = peerManager;
this.nodes = new Map(); // DAG nodes
this.tips = new Set(); // Current tips (unconfirmed)
this.confirmed = new Set(); // Confirmed nodes
this.pendingContributions = [];
this.syncInterval = null;
}
async initialize() {
await this.loadLedger();
if (!NETWORK_CONFIG.localSimulation) {
this.startSync();
}
return this;
}
async loadLedger() {
try {
const data = await fs.readFile(getQDAGFile(), 'utf-8');
const ledger = JSON.parse(data);
for (const node of ledger.nodes || []) {
this.nodes.set(node.id, node);
}
this.tips = new Set(ledger.tips || []);
this.confirmed = new Set(ledger.confirmed || []);
console.log(` 📊 Loaded QDAG: ${this.nodes.size} nodes, ${this.confirmed.size} confirmed`);
} catch (err) {
// Create genesis node
const genesis = this.createNode({
type: 'genesis',
timestamp: Date.now(),
message: 'Edge-Net QDAG Genesis',
}, []);
this.nodes.set(genesis.id, genesis);
this.tips.add(genesis.id);
this.confirmed.add(genesis.id);
await this.saveLedger();
console.log(' 📊 Created QDAG genesis block');
}
}
async saveLedger() {
const ledger = {
nodes: Array.from(this.nodes.values()),
tips: Array.from(this.tips),
confirmed: Array.from(this.confirmed),
savedAt: Date.now(),
};
await fs.writeFile(getQDAGFile(), JSON.stringify(ledger, null, 2));
}
/**
* Create a new QDAG node
*/
createNode(data, parents) {
const nodeData = {
...data,
parents: parents,
timestamp: Date.now(),
};
const id = createHash('sha256')
.update(JSON.stringify(nodeData))
.digest('hex')
.slice(0, 16);
return {
id,
...nodeData,
weight: 1,
confirmations: 0,
};
}
/**
* Record a contribution to the QDAG
*/
async recordContribution(contribution) {
// Select parent tips (2 parents for DAG structure)
const parents = this.selectTips(2);
// Create contribution node
const node = this.createNode({
type: 'contribution',
contributor: contribution.piKey,
siteId: contribution.siteId,
taskId: contribution.taskId,
computeUnits: contribution.computeUnits,
credits: contribution.credits,
signature: contribution.signature,
}, parents);
// Add to DAG
this.nodes.set(node.id, node);
// Update tips
for (const parent of parents) {
this.tips.delete(parent);
}
this.tips.add(node.id);
// Update parent weights (confirm path)
await this.updateWeights(node.id);
await this.saveLedger();
console.log(` 📝 Recorded contribution ${node.id}: +${contribution.credits} credits`);
return node;
}
/**
* Select tips for new node parents
*/
selectTips(count) {
const tips = Array.from(this.tips);
if (tips.length <= count) return tips;
// Weighted random selection based on age
const selected = [];
const available = [...tips];
while (selected.length < count && available.length > 0) {
const idx = Math.floor(Math.random() * available.length);
selected.push(available[idx]);
available.splice(idx, 1);
}
return selected;
}
/**
* Update weights along the path to genesis
*/
async updateWeights(nodeId) {
const visited = new Set();
const queue = [nodeId];
while (queue.length > 0) {
const id = queue.shift();
if (visited.has(id)) continue;
visited.add(id);
const node = this.nodes.get(id);
if (!node) continue;
node.weight = (node.weight || 0) + 1;
node.confirmations = (node.confirmations || 0) + 1;
// Check for confirmation threshold
if (node.confirmations >= 3 && !this.confirmed.has(id)) {
this.confirmed.add(id);
}
// Add parents to queue
for (const parentId of node.parents || []) {
queue.push(parentId);
}
}
}
/**
* Get contribution stats for a contributor
*/
getContributorStats(piKey) {
const contributions = Array.from(this.nodes.values())
.filter(n => n.type === 'contribution' && n.contributor === piKey);
return {
totalContributions: contributions.length,
confirmedContributions: contributions.filter(c => this.confirmed.has(c.id)).length,
totalCredits: contributions.reduce((sum, c) => sum + (c.credits || 0), 0),
totalComputeUnits: contributions.reduce((sum, c) => sum + (c.computeUnits || 0), 0),
firstContribution: contributions.length > 0
? Math.min(...contributions.map(c => c.timestamp))
: null,
lastContribution: contributions.length > 0
? Math.max(...contributions.map(c => c.timestamp))
: null,
};
}
/**
* Get network-wide stats
*/
getNetworkStats() {
const contributions = Array.from(this.nodes.values())
.filter(n => n.type === 'contribution');
const contributors = new Set(contributions.map(c => c.contributor));
return {
totalNodes: this.nodes.size,
totalContributions: contributions.length,
confirmedNodes: this.confirmed.size,
uniqueContributors: contributors.size,
totalCredits: contributions.reduce((sum, c) => sum + (c.credits || 0), 0),
totalComputeUnits: contributions.reduce((sum, c) => sum + (c.computeUnits || 0), 0),
currentTips: this.tips.size,
};
}
/**
* Verify contribution integrity
*/
async verifyContribution(nodeId) {
const node = this.nodes.get(nodeId);
if (!node) return { valid: false, reason: 'Node not found' };
// Verify parents exist
for (const parentId of node.parents || []) {
if (!this.nodes.has(parentId)) {
return { valid: false, reason: `Missing parent: ${parentId}` };
}
}
// Verify signature (if peer available)
const peer = this.peerManager.peers.get(node.contributor);
if (peer && node.signature) {
const dataToVerify = JSON.stringify({
contributor: node.contributor,
taskId: node.taskId,
computeUnits: node.computeUnits,
credits: node.credits,
});
if (!this.peerManager.verifySignature(peer.publicKey, dataToVerify, node.signature)) {
return { valid: false, reason: 'Invalid signature' };
}
}
return { valid: true, confirmations: node.confirmations };
}
/**
* Sync QDAG with peers
*/
startSync() {
this.syncInterval = setInterval(async () => {
await this.syncWithPeers();
}, NETWORK_CONFIG.qdagSyncInterval);
}
async syncWithPeers() {
const activePeers = this.peerManager.getActivePeers();
for (const peer of activePeers.slice(0, 3)) {
try {
// Request missing nodes from peer
const peerTips = await this.requestTips(peer);
for (const tipId of peerTips) {
if (!this.nodes.has(tipId)) {
const node = await this.requestNode(peer, tipId);
if (node) {
await this.mergeNode(node);
}
}
}
} catch (err) {
// Peer sync failed
}
}
}
async requestTips(peer) {
// In production: Request tips via P2P
return [];
}
async requestNode(peer, nodeId) {
// In production: Request specific node via P2P
return null;
}
async mergeNode(node) {
if (this.nodes.has(node.id)) return;
// Verify node before merging
const verification = await this.verifyContribution(node.id);
if (!verification.valid) return;
this.nodes.set(node.id, node);
await this.updateWeights(node.id);
await this.saveLedger();
}
stop() {
if (this.syncInterval) clearInterval(this.syncInterval);
}
}
/**
* Contribution Verifier
*
* Cross-verifies contributions between peers
*/
export class ContributionVerifier {
constructor(peerManager, qdagLedger) {
this.peerManager = peerManager;
this.qdag = qdagLedger;
this.verificationQueue = [];
}
/**
* Submit contribution for verification
*/
async submitContribution(contribution) {
// Sign the contribution
contribution.signature = this.peerManager.signMessage(
JSON.stringify({
contributor: contribution.piKey,
taskId: contribution.taskId,
computeUnits: contribution.computeUnits,
credits: contribution.credits,
})
);
// Record to local QDAG
const node = await this.qdag.recordContribution(contribution);
// In local simulation, self-verify
if (NETWORK_CONFIG.localSimulation) {
return {
nodeId: node.id,
verified: true,
confirmations: 1,
};
}
// In production, broadcast for peer verification
const verifications = await this.broadcastForVerification(node);
return {
nodeId: node.id,
verified: verifications.filter(v => v.valid).length >= 2,
confirmations: verifications.length,
};
}
/**
* Broadcast contribution for peer verification
*/
async broadcastForVerification(node) {
const activePeers = this.peerManager.getActivePeers();
const verifications = [];
for (const peer of activePeers.slice(0, 5)) {
try {
const verification = await this.requestVerification(peer, node);
verifications.push(verification);
} catch (err) {
// Peer verification failed
}
}
return verifications;
}
async requestVerification(peer, node) {
// In production: Request verification via P2P
return { valid: true, peerId: peer.piKey };
}
/**
* Verify a contribution from another peer
*/
async verifyFromPeer(contribution, requestingPeer) {
// Verify signature
const valid = this.peerManager.verifySignature(
requestingPeer.publicKey,
JSON.stringify({
contributor: contribution.contributor,
taskId: contribution.taskId,
computeUnits: contribution.computeUnits,
credits: contribution.credits,
}),
contribution.signature
);
// Verify compute units are reasonable
const reasonable = contribution.computeUnits > 0 &&
contribution.computeUnits < 1000000 &&
contribution.credits === Math.floor(contribution.computeUnits / 100);
return {
valid: valid && reasonable,
reason: !valid ? 'Invalid signature' : (!reasonable ? 'Unreasonable values' : 'OK'),
};
}
}
/**
* Network Manager - High-level API
*/
export class NetworkManager {
constructor(identity) {
this.identity = identity;
this.peerManager = new PeerManager(identity);
this.qdag = null;
this.verifier = null;
this.initialized = false;
}
async initialize() {
console.log('\n🌐 Initializing Edge-Net Network...');
await this.peerManager.initialize();
this.qdag = new QDAGLedger(this.peerManager);
await this.qdag.initialize();
this.verifier = new ContributionVerifier(this.peerManager, this.qdag);
// Announce to network
await this.peerManager.announce();
this.initialized = true;
console.log('✅ Network initialized\n');
return this;
}
/**
* Record a compute contribution
*/
async recordContribution(taskId, computeUnits) {
const credits = Math.floor(computeUnits / 100);
const contribution = {
piKey: this.identity.piKey,
siteId: this.identity.siteId,
taskId,
computeUnits,
credits,
timestamp: Date.now(),
};
return await this.verifier.submitContribution(contribution);
}
/**
* Get stats for this contributor
*/
getMyStats() {
return this.qdag.getContributorStats(this.identity.piKey);
}
/**
* Get network-wide stats
*/
getNetworkStats() {
return this.qdag.getNetworkStats();
}
/**
* Get connected peers
*/
getPeers() {
return this.peerManager.getAllPeers();
}
/**
* Stop network services
*/
stop() {
this.peerManager.stop();
this.qdag.stop();
}
}
// CLI interface
async function main() {
const args = process.argv.slice(2);
const command = args[0];
if (command === 'stats') {
// Show network stats
await ensureDirectories();
try {
const data = await fs.readFile(getQDAGFile(), 'utf-8');
const ledger = JSON.parse(data);
console.log('\n📊 Edge-Net Network Statistics\n');
console.log(` Total Nodes: ${ledger.nodes?.length || 0}`);
console.log(` Confirmed: ${ledger.confirmed?.length || 0}`);
console.log(` Current Tips: ${ledger.tips?.length || 0}`);
const contributions = (ledger.nodes || []).filter(n => n.type === 'contribution');
const contributors = new Set(contributions.map(c => c.contributor));
console.log(` Contributions: ${contributions.length}`);
console.log(` Contributors: ${contributors.size}`);
console.log(` Total Credits: ${contributions.reduce((s, c) => s + (c.credits || 0), 0)}`);
console.log();
} catch (err) {
console.log('No QDAG data found. Start contributing to initialize the network.');
}
} else if (command === 'peers') {
// Show known peers
await ensureDirectories();
try {
const data = await fs.readFile(getPeersFile(), 'utf-8');
const peers = JSON.parse(data);
console.log('\n👥 Known Peers\n');
for (const peer of peers) {
const status = (Date.now() - peer.lastSeen) < 300000 ? '🟢' : '⚪';
console.log(` ${status} ${peer.siteId} (π:${peer.piKey.slice(0, 8)})`);
console.log(` First seen: ${new Date(peer.firstSeen).toLocaleString()}`);
console.log(` Last seen: ${new Date(peer.lastSeen).toLocaleString()}`);
console.log(` Verified: ${peer.verified ? '✅' : '❌'}`);
console.log();
}
} catch (err) {
console.log('No peers found. Join the network to discover peers.');
}
} else if (command === 'help' || !command) {
console.log(`
Edge-Net Network Module
Commands:
stats Show network statistics
peers Show known peers
help Show this help
The network module is used internally by the join CLI.
To join the network: npx edge-net-join --generate
`);
}
}
main().catch(console.error);

View File

@@ -0,0 +1,817 @@
#!/usr/bin/env node
/**
* Edge-Net Multi-Network Module
*
* Enables creation, discovery, and contribution to multiple edge networks.
* Each network is cryptographically isolated with its own:
* - Genesis block and network ID
* - QDAG ledger
* - Peer registry
* - Access control (public/private/invite-only)
*
* Security Features:
* - Network ID derived from genesis hash (tamper-evident)
* - Ed25519 signatures for network announcements
* - Optional invite codes for private networks
* - Cryptographic proof of network membership
*/
import { createHash, randomBytes } from 'crypto';
import { promises as fs } from 'fs';
import { homedir } from 'os';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// ANSI colors
const colors = {
reset: '\x1b[0m',
bold: '\x1b[1m',
dim: '\x1b[2m',
cyan: '\x1b[36m',
green: '\x1b[32m',
yellow: '\x1b[33m',
blue: '\x1b[34m',
magenta: '\x1b[35m',
red: '\x1b[31m',
};
const c = (color, text) => `${colors[color]}${text}${colors.reset}`;
// Network types
const NetworkType = {
PUBLIC: 'public', // Anyone can join and discover
PRIVATE: 'private', // Requires invite code to join
CONSORTIUM: 'consortium', // Requires approval from existing members
};
// Well-known public networks (bootstrap)
const WELL_KNOWN_NETWORKS = [
{
id: 'mainnet',
name: 'Edge-Net Mainnet',
description: 'Primary public compute network',
type: NetworkType.PUBLIC,
genesisHash: 'edgenet-mainnet-genesis-v1',
bootstrapNodes: ['edge-net.ruvector.dev:9000'],
created: '2024-01-01T00:00:00Z',
},
{
id: 'testnet',
name: 'Edge-Net Testnet',
description: 'Testing and development network',
type: NetworkType.PUBLIC,
genesisHash: 'edgenet-testnet-genesis-v1',
bootstrapNodes: ['testnet.ruvector.dev:9000'],
created: '2024-01-01T00:00:00Z',
},
];
// Directory structure
function getNetworksDir() {
const dir = join(homedir(), '.ruvector', 'networks');
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
return dir;
}
function getRegistryFile() {
return join(getNetworksDir(), 'registry.json');
}
function getNetworkDir(networkId) {
const dir = join(getNetworksDir(), networkId);
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
return dir;
}
/**
* Network Genesis - defines a network's identity
*/
export class NetworkGenesis {
constructor(options = {}) {
this.version = 1;
this.name = options.name || 'Custom Network';
this.description = options.description || 'A custom edge-net network';
this.type = options.type || NetworkType.PUBLIC;
this.creator = options.creator || null; // Creator's public key
this.creatorSiteId = options.creatorSiteId || 'anonymous';
this.created = options.created || new Date().toISOString();
this.parameters = {
minContributors: options.minContributors || 1,
confirmationThreshold: options.confirmationThreshold || 3,
creditMultiplier: options.creditMultiplier || 1.0,
maxPeers: options.maxPeers || 100,
...options.parameters,
};
this.inviteRequired = this.type !== NetworkType.PUBLIC;
this.approvers = options.approvers || []; // For consortium networks
this.nonce = options.nonce || randomBytes(16).toString('hex');
}
/**
* Compute network ID from genesis hash
*/
computeNetworkId() {
const data = JSON.stringify({
version: this.version,
name: this.name,
type: this.type,
creator: this.creator,
created: this.created,
parameters: this.parameters,
nonce: this.nonce,
});
const hash = createHash('sha256').update(data).digest('hex');
return `net-${hash.slice(0, 16)}`;
}
/**
* Create signed genesis block
*/
createSignedGenesis(signFn) {
const genesis = {
...this,
networkId: this.computeNetworkId(),
};
if (signFn) {
const dataToSign = JSON.stringify(genesis);
genesis.signature = signFn(dataToSign);
}
return genesis;
}
/**
* Generate invite code for private networks
*/
generateInviteCode() {
if (this.type === NetworkType.PUBLIC) {
throw new Error('Public networks do not require invite codes');
}
const networkId = this.computeNetworkId();
const secret = randomBytes(16).toString('hex');
const code = Buffer.from(`${networkId}:${secret}`).toString('base64url');
return {
code,
networkId,
validUntil: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(), // 7 days
};
}
}
/**
* Network Registry - manages known networks
*/
export class NetworkRegistry {
constructor() {
this.networks = new Map();
this.activeNetwork = null;
this.loaded = false;
}
async load() {
try {
// Load well-known networks
for (const network of WELL_KNOWN_NETWORKS) {
this.networks.set(network.id, {
...network,
isWellKnown: true,
joined: false,
stats: null,
});
}
// Load user's network registry
if (existsSync(getRegistryFile())) {
const data = JSON.parse(await fs.readFile(getRegistryFile(), 'utf-8'));
for (const network of data.networks || []) {
this.networks.set(network.id, {
...network,
isWellKnown: false,
});
}
this.activeNetwork = data.activeNetwork || null;
}
this.loaded = true;
} catch (err) {
console.error('Failed to load network registry:', err.message);
}
}
async save() {
const data = {
version: 1,
activeNetwork: this.activeNetwork,
networks: Array.from(this.networks.values()).filter(n => !n.isWellKnown),
savedAt: new Date().toISOString(),
};
await fs.writeFile(getRegistryFile(), JSON.stringify(data, null, 2));
}
/**
* Create a new network
*/
async createNetwork(options, identity) {
const genesis = new NetworkGenesis({
...options,
creator: identity?.publicKey,
creatorSiteId: identity?.siteId,
});
const networkId = genesis.computeNetworkId();
// Create network directory structure
const networkDir = getNetworkDir(networkId);
await fs.mkdir(join(networkDir, 'peers'), { recursive: true });
// Save genesis block
const genesisData = genesis.createSignedGenesis(
identity?.sign ? (data) => identity.sign(data) : null
);
await fs.writeFile(
join(networkDir, 'genesis.json'),
JSON.stringify(genesisData, null, 2)
);
// Initialize QDAG for this network
const qdag = {
networkId,
nodes: [{
id: 'genesis',
type: 'genesis',
timestamp: Date.now(),
message: `Genesis: ${genesis.name}`,
parents: [],
weight: 1,
confirmations: 0,
}],
tips: ['genesis'],
confirmed: ['genesis'],
createdAt: Date.now(),
};
await fs.writeFile(
join(networkDir, 'qdag.json'),
JSON.stringify(qdag, null, 2)
);
// Initialize peer list
await fs.writeFile(
join(networkDir, 'peers.json'),
JSON.stringify([], null, 2)
);
// Register network
const networkEntry = {
id: networkId,
name: genesis.name,
description: genesis.description,
type: genesis.type,
creator: genesis.creator,
creatorSiteId: genesis.creatorSiteId,
created: genesis.created,
parameters: genesis.parameters,
genesisHash: createHash('sha256')
.update(JSON.stringify(genesisData))
.digest('hex')
.slice(0, 32),
joined: true,
isOwner: true,
stats: { nodes: 1, contributors: 0, credits: 0 },
};
this.networks.set(networkId, networkEntry);
await this.save();
// Generate invite codes if private
let inviteCodes = null;
if (genesis.type !== NetworkType.PUBLIC) {
inviteCodes = [];
for (let i = 0; i < 5; i++) {
inviteCodes.push(genesis.generateInviteCode());
}
await fs.writeFile(
join(networkDir, 'invites.json'),
JSON.stringify(inviteCodes, null, 2)
);
}
return { networkId, genesis: genesisData, inviteCodes };
}
/**
* Join an existing network
*/
async joinNetwork(networkId, inviteCode = null) {
const network = this.networks.get(networkId);
if (!network) {
throw new Error(`Network not found: ${networkId}`);
}
if (network.joined) {
return { alreadyJoined: true, network };
}
// Verify invite code for private networks
if (network.type === NetworkType.PRIVATE) {
if (!inviteCode) {
throw new Error('Private network requires invite code');
}
const isValid = await this.verifyInviteCode(networkId, inviteCode);
if (!isValid) {
throw new Error('Invalid or expired invite code');
}
}
// Create local network directory
const networkDir = getNetworkDir(networkId);
// For well-known networks, create initial structure
if (network.isWellKnown) {
const qdag = {
networkId,
nodes: [{
id: 'genesis',
type: 'genesis',
timestamp: Date.now(),
message: `Joined: ${network.name}`,
parents: [],
weight: 1,
confirmations: 0,
}],
tips: ['genesis'],
confirmed: ['genesis'],
createdAt: Date.now(),
};
await fs.writeFile(
join(networkDir, 'qdag.json'),
JSON.stringify(qdag, null, 2)
);
await fs.writeFile(
join(networkDir, 'peers.json'),
JSON.stringify([], null, 2)
);
}
network.joined = true;
network.joinedAt = new Date().toISOString();
await this.save();
return { joined: true, network };
}
/**
* Verify invite code
*/
async verifyInviteCode(networkId, code) {
try {
const decoded = Buffer.from(code, 'base64url').toString();
const [codeNetworkId, secret] = decoded.split(':');
if (codeNetworkId !== networkId) {
return false;
}
// In production, verify against network's invite registry
// For local simulation, accept any properly formatted code
return secret && secret.length === 32;
} catch {
return false;
}
}
/**
* Discover networks from DHT/registry
*/
async discoverNetworks(options = {}) {
const discovered = [];
// Always include well-known networks
for (const network of WELL_KNOWN_NETWORKS) {
const existing = this.networks.get(network.id);
discovered.push({
...network,
joined: existing?.joined || false,
source: 'well-known',
});
}
// Scan for locally known networks
try {
const networksDir = getNetworksDir();
const dirs = await fs.readdir(networksDir);
for (const dir of dirs) {
if (dir === 'registry.json') continue;
const genesisPath = join(networksDir, dir, 'genesis.json');
if (existsSync(genesisPath)) {
try {
const genesis = JSON.parse(await fs.readFile(genesisPath, 'utf-8'));
const existing = this.networks.get(genesis.networkId || dir);
if (!existing?.isWellKnown) {
discovered.push({
id: genesis.networkId || dir,
name: genesis.name,
description: genesis.description,
type: genesis.type,
creator: genesis.creatorSiteId,
created: genesis.created,
joined: existing?.joined || false,
source: 'local',
});
}
} catch (e) {
// Skip invalid genesis files
}
}
}
} catch (err) {
// Networks directory doesn't exist yet
}
// In production: Query DHT/bootstrap nodes for public networks
// This is simulated here
return discovered;
}
/**
* Set active network for contributions
*/
async setActiveNetwork(networkId) {
const network = this.networks.get(networkId);
if (!network) {
throw new Error(`Network not found: ${networkId}`);
}
if (!network.joined) {
throw new Error(`Must join network first: ${networkId}`);
}
this.activeNetwork = networkId;
await this.save();
return network;
}
/**
* Get network info
*/
getNetwork(networkId) {
return this.networks.get(networkId);
}
/**
* Get active network
*/
getActiveNetwork() {
if (!this.activeNetwork) return null;
return this.networks.get(this.activeNetwork);
}
/**
* Get all joined networks
*/
getJoinedNetworks() {
return Array.from(this.networks.values()).filter(n => n.joined);
}
/**
* Get network statistics
*/
async getNetworkStats(networkId) {
const networkDir = getNetworkDir(networkId);
const qdagPath = join(networkDir, 'qdag.json');
const peersPath = join(networkDir, 'peers.json');
const stats = {
nodes: 0,
contributions: 0,
contributors: 0,
credits: 0,
peers: 0,
};
try {
if (existsSync(qdagPath)) {
const qdag = JSON.parse(await fs.readFile(qdagPath, 'utf-8'));
const contributions = (qdag.nodes || []).filter(n => n.type === 'contribution');
stats.nodes = qdag.nodes?.length || 0;
stats.contributions = contributions.length;
stats.contributors = new Set(contributions.map(c => c.contributor)).size;
stats.credits = contributions.reduce((sum, c) => sum + (c.credits || 0), 0);
}
if (existsSync(peersPath)) {
const peers = JSON.parse(await fs.readFile(peersPath, 'utf-8'));
stats.peers = peers.length;
}
} catch (err) {
// Stats not available
}
return stats;
}
/**
* List all networks
*/
listNetworks() {
return Array.from(this.networks.values());
}
}
/**
* Multi-Network Manager - coordinates contributions across networks
*/
export class MultiNetworkManager {
constructor(identity) {
this.identity = identity;
this.registry = new NetworkRegistry();
this.activeConnections = new Map();
}
async initialize() {
await this.registry.load();
return this;
}
/**
* Create a new network
*/
async createNetwork(options) {
console.log(`\n${c('cyan', 'Creating new network...')}\n`);
const result = await this.registry.createNetwork(options, this.identity);
console.log(`${c('green', '✓')} Network created successfully!`);
console.log(` ${c('cyan', 'Network ID:')} ${result.networkId}`);
console.log(` ${c('cyan', 'Name:')} ${options.name}`);
console.log(` ${c('cyan', 'Type:')} ${options.type}`);
console.log(` ${c('cyan', 'Description:')} ${options.description || 'N/A'}`);
if (result.inviteCodes) {
console.log(`\n${c('bold', 'Invite Codes (share these to invite members):')}`);
for (const invite of result.inviteCodes.slice(0, 3)) {
console.log(` ${c('yellow', invite.code)}`);
}
console.log(` ${c('dim', `(${result.inviteCodes.length} codes saved to network directory)`)}`);
}
console.log(`\n${c('dim', 'Network directory:')} ~/.ruvector/networks/${result.networkId}`);
return result;
}
/**
* Discover available networks
*/
async discoverNetworks() {
console.log(`\n${c('cyan', 'Discovering networks...')}\n`);
const networks = await this.registry.discoverNetworks();
if (networks.length === 0) {
console.log(` ${c('dim', 'No networks found.')}`);
return networks;
}
console.log(`${c('bold', 'Available Networks:')}\n`);
for (const network of networks) {
const status = network.joined ? c('green', '● Joined') : c('dim', '○ Not joined');
const typeIcon = network.type === NetworkType.PUBLIC ? '🌐' :
network.type === NetworkType.PRIVATE ? '🔒' : '🏢';
console.log(` ${status} ${typeIcon} ${c('bold', network.name)}`);
console.log(` ${c('dim', 'ID:')} ${network.id}`);
console.log(` ${c('dim', 'Type:')} ${network.type}`);
console.log(` ${c('dim', 'Description:')} ${network.description || 'N/A'}`);
console.log(` ${c('dim', 'Source:')} ${network.source}`);
console.log('');
}
return networks;
}
/**
* Join a network
*/
async joinNetwork(networkId, inviteCode = null) {
console.log(`\n${c('cyan', `Joining network ${networkId}...`)}\n`);
try {
const result = await this.registry.joinNetwork(networkId, inviteCode);
if (result.alreadyJoined) {
console.log(`${c('yellow', '⚠')} Already joined network: ${result.network.name}`);
} else {
console.log(`${c('green', '✓')} Successfully joined: ${result.network.name}`);
}
// Set as active if it's the only joined network
const joinedNetworks = this.registry.getJoinedNetworks();
if (joinedNetworks.length === 1) {
await this.registry.setActiveNetwork(networkId);
console.log(` ${c('dim', 'Set as active network')}`);
}
return result;
} catch (err) {
console.log(`${c('red', '✗')} Failed to join: ${err.message}`);
throw err;
}
}
/**
* Switch active network
*/
async switchNetwork(networkId) {
const network = await this.registry.setActiveNetwork(networkId);
console.log(`${c('green', '✓')} Active network: ${network.name} (${networkId})`);
return network;
}
/**
* Show network status
*/
async showStatus() {
const active = this.registry.getActiveNetwork();
const joined = this.registry.getJoinedNetworks();
console.log(`\n${c('bold', 'NETWORK STATUS:')}\n`);
if (!active) {
console.log(` ${c('yellow', '⚠')} No active network`);
console.log(` ${c('dim', 'Join a network to start contributing')}\n`);
return;
}
const stats = await this.registry.getNetworkStats(active.id);
console.log(`${c('bold', 'Active Network:')}`);
console.log(` ${c('cyan', 'Name:')} ${active.name}`);
console.log(` ${c('cyan', 'ID:')} ${active.id}`);
console.log(` ${c('cyan', 'Type:')} ${active.type}`);
console.log(` ${c('cyan', 'QDAG Nodes:')} ${stats.nodes}`);
console.log(` ${c('cyan', 'Contributions:')} ${stats.contributions}`);
console.log(` ${c('cyan', 'Contributors:')} ${stats.contributors}`);
console.log(` ${c('cyan', 'Total Credits:')} ${stats.credits}`);
console.log(` ${c('cyan', 'Connected Peers:')} ${stats.peers}`);
if (joined.length > 1) {
console.log(`\n${c('bold', 'Other Joined Networks:')}`);
for (const network of joined) {
if (network.id !== active.id) {
console.log(` ${c('dim', '○')} ${network.name} (${network.id})`);
}
}
}
console.log('');
}
/**
* Get active network directory for contributions
*/
getActiveNetworkDir() {
const active = this.registry.getActiveNetwork();
if (!active) return null;
return getNetworkDir(active.id);
}
}
// CLI interface
async function main() {
const args = process.argv.slice(2);
const command = args[0];
const registry = new NetworkRegistry();
await registry.load();
if (command === 'list' || command === 'ls') {
console.log(`\n${c('bold', 'NETWORKS:')}\n`);
const networks = registry.listNetworks();
const active = registry.activeNetwork;
for (const network of networks) {
const isActive = network.id === active;
const status = network.joined ?
(isActive ? c('green', '● Active') : c('cyan', '○ Joined')) :
c('dim', ' Available');
const typeIcon = network.type === NetworkType.PUBLIC ? '🌐' :
network.type === NetworkType.PRIVATE ? '🔒' : '🏢';
console.log(` ${status} ${typeIcon} ${c('bold', network.name)}`);
console.log(` ${c('dim', 'ID:')} ${network.id}`);
if (network.description) {
console.log(` ${c('dim', network.description)}`);
}
console.log('');
}
} else if (command === 'discover') {
const manager = new MultiNetworkManager(null);
await manager.initialize();
await manager.discoverNetworks();
} else if (command === 'create') {
const name = args[1] || 'My Network';
const type = args.includes('--private') ? NetworkType.PRIVATE :
args.includes('--consortium') ? NetworkType.CONSORTIUM :
NetworkType.PUBLIC;
const description = args.find((a, i) => args[i - 1] === '--desc') || '';
const manager = new MultiNetworkManager(null);
await manager.initialize();
await manager.createNetwork({ name, type, description });
} else if (command === 'join') {
const networkId = args[1];
const inviteCode = args.find((a, i) => args[i - 1] === '--invite');
if (!networkId) {
console.log(`${c('red', '✗')} Usage: networks join <network-id> [--invite <code>]`);
process.exit(1);
}
const manager = new MultiNetworkManager(null);
await manager.initialize();
await manager.joinNetwork(networkId, inviteCode);
} else if (command === 'switch' || command === 'use') {
const networkId = args[1];
if (!networkId) {
console.log(`${c('red', '✗')} Usage: networks switch <network-id>`);
process.exit(1);
}
const manager = new MultiNetworkManager(null);
await manager.initialize();
await manager.switchNetwork(networkId);
} else if (command === 'status') {
const manager = new MultiNetworkManager(null);
await manager.initialize();
await manager.showStatus();
} else if (command === 'help' || !command) {
console.log(`
${c('bold', 'Edge-Net Multi-Network Manager')}
${c('bold', 'COMMANDS:')}
${c('green', 'list')} List all known networks
${c('green', 'discover')} Discover available networks
${c('green', 'create')} Create a new network
${c('green', 'join')} Join an existing network
${c('green', 'switch')} Switch active network
${c('green', 'status')} Show current network status
${c('green', 'help')} Show this help
${c('bold', 'EXAMPLES:')}
${c('dim', '# List networks')}
$ node networks.js list
${c('dim', '# Create a public network')}
$ node networks.js create "My Research Network" --desc "For ML research"
${c('dim', '# Create a private network')}
$ node networks.js create "Team Network" --private
${c('dim', '# Join a network')}
$ node networks.js join net-abc123def456
${c('dim', '# Join a private network with invite')}
$ node networks.js join net-xyz789 --invite <invite-code>
${c('dim', '# Switch active network')}
$ node networks.js switch net-abc123def456
${c('bold', 'NETWORK TYPES:')}
${c('cyan', '🌐 Public')} Anyone can join and discover
${c('cyan', '🔒 Private')} Requires invite code to join
${c('cyan', '🏢 Consortium')} Requires approval from members
`);
}
}
main().catch(console.error);

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,625 @@
/* tslint:disable */
/* eslint-disable */
export const memory: WebAssembly.Memory;
export const __wbg_adaptivesecurity_free: (a: number, b: number) => void;
export const __wbg_adversarialsimulator_free: (a: number, b: number) => void;
export const __wbg_auditlog_free: (a: number, b: number) => void;
export const __wbg_browserfingerprint_free: (a: number, b: number) => void;
export const __wbg_byzantinedetector_free: (a: number, b: number) => void;
export const __wbg_coherenceengine_free: (a: number, b: number) => void;
export const __wbg_collectivememory_free: (a: number, b: number) => void;
export const __wbg_contributionstream_free: (a: number, b: number) => void;
export const __wbg_differentialprivacy_free: (a: number, b: number) => void;
export const __wbg_drifttracker_free: (a: number, b: number) => void;
export const __wbg_economicengine_free: (a: number, b: number) => void;
export const __wbg_economichealth_free: (a: number, b: number) => void;
export const __wbg_edgenetconfig_free: (a: number, b: number) => void;
export const __wbg_edgenetnode_free: (a: number, b: number) => void;
export const __wbg_entropyconsensus_free: (a: number, b: number) => void;
export const __wbg_eventlog_free: (a: number, b: number) => void;
export const __wbg_evolutionengine_free: (a: number, b: number) => void;
export const __wbg_federatedmodel_free: (a: number, b: number) => void;
export const __wbg_foundingregistry_free: (a: number, b: number) => void;
export const __wbg_genesiskey_free: (a: number, b: number) => void;
export const __wbg_genesissunset_free: (a: number, b: number) => void;
export const __wbg_get_economichealth_growth_rate: (a: number) => number;
export const __wbg_get_economichealth_stability: (a: number) => number;
export const __wbg_get_economichealth_utilization: (a: number) => number;
export const __wbg_get_economichealth_velocity: (a: number) => number;
export const __wbg_get_nodeconfig_bandwidth_limit: (a: number) => number;
export const __wbg_get_nodeconfig_memory_limit: (a: number) => number;
export const __wbg_get_nodeconfig_min_idle_time: (a: number) => number;
export const __wbg_get_nodeconfig_respect_battery: (a: number) => number;
export const __wbg_get_nodestats_celebration_boost: (a: number) => number;
export const __wbg_get_nodestats_multiplier: (a: number) => number;
export const __wbg_get_nodestats_reputation: (a: number) => number;
export const __wbg_get_nodestats_ruv_earned: (a: number) => bigint;
export const __wbg_get_nodestats_ruv_spent: (a: number) => bigint;
export const __wbg_get_nodestats_tasks_completed: (a: number) => bigint;
export const __wbg_get_nodestats_tasks_submitted: (a: number) => bigint;
export const __wbg_get_nodestats_uptime_seconds: (a: number) => bigint;
export const __wbg_gradientgossip_free: (a: number, b: number) => void;
export const __wbg_modelconsensusmanager_free: (a: number, b: number) => void;
export const __wbg_networkevents_free: (a: number, b: number) => void;
export const __wbg_networklearning_free: (a: number, b: number) => void;
export const __wbg_networktopology_free: (a: number, b: number) => void;
export const __wbg_nodeconfig_free: (a: number, b: number) => void;
export const __wbg_nodestats_free: (a: number, b: number) => void;
export const __wbg_optimizationengine_free: (a: number, b: number) => void;
export const __wbg_pikey_free: (a: number, b: number) => void;
export const __wbg_qdagledger_free: (a: number, b: number) => void;
export const __wbg_quarantinemanager_free: (a: number, b: number) => void;
export const __wbg_raceconomicengine_free: (a: number, b: number) => void;
export const __wbg_racsemanticrouter_free: (a: number, b: number) => void;
export const __wbg_ratelimiter_free: (a: number, b: number) => void;
export const __wbg_reasoningbank_free: (a: number, b: number) => void;
export const __wbg_reputationmanager_free: (a: number, b: number) => void;
export const __wbg_reputationsystem_free: (a: number, b: number) => void;
export const __wbg_rewarddistribution_free: (a: number, b: number) => void;
export const __wbg_rewardmanager_free: (a: number, b: number) => void;
export const __wbg_semanticrouter_free: (a: number, b: number) => void;
export const __wbg_sessionkey_free: (a: number, b: number) => void;
export const __wbg_set_economichealth_growth_rate: (a: number, b: number) => void;
export const __wbg_set_economichealth_stability: (a: number, b: number) => void;
export const __wbg_set_economichealth_utilization: (a: number, b: number) => void;
export const __wbg_set_economichealth_velocity: (a: number, b: number) => void;
export const __wbg_set_nodeconfig_bandwidth_limit: (a: number, b: number) => void;
export const __wbg_set_nodeconfig_memory_limit: (a: number, b: number) => void;
export const __wbg_set_nodeconfig_min_idle_time: (a: number, b: number) => void;
export const __wbg_set_nodeconfig_respect_battery: (a: number, b: number) => void;
export const __wbg_set_nodestats_celebration_boost: (a: number, b: number) => void;
export const __wbg_set_nodestats_multiplier: (a: number, b: number) => void;
export const __wbg_set_nodestats_reputation: (a: number, b: number) => void;
export const __wbg_set_nodestats_ruv_earned: (a: number, b: bigint) => void;
export const __wbg_set_nodestats_ruv_spent: (a: number, b: bigint) => void;
export const __wbg_set_nodestats_tasks_completed: (a: number, b: bigint) => void;
export const __wbg_set_nodestats_tasks_submitted: (a: number, b: bigint) => void;
export const __wbg_set_nodestats_uptime_seconds: (a: number, b: bigint) => void;
export const __wbg_spikedrivenattention_free: (a: number, b: number) => void;
export const __wbg_spotchecker_free: (a: number, b: number) => void;
export const __wbg_stakemanager_free: (a: number, b: number) => void;
export const __wbg_swarmintelligence_free: (a: number, b: number) => void;
export const __wbg_sybildefense_free: (a: number, b: number) => void;
export const __wbg_topksparsifier_free: (a: number, b: number) => void;
export const __wbg_trajectorytracker_free: (a: number, b: number) => void;
export const __wbg_wasmadapterpool_free: (a: number, b: number) => void;
export const __wbg_wasmcapabilities_free: (a: number, b: number) => void;
export const __wbg_wasmcreditledger_free: (a: number, b: number) => void;
export const __wbg_wasmidledetector_free: (a: number, b: number) => void;
export const __wbg_wasmmcpbroadcast_free: (a: number, b: number) => void;
export const __wbg_wasmmcpserver_free: (a: number, b: number) => void;
export const __wbg_wasmmcptransport_free: (a: number, b: number) => void;
export const __wbg_wasmmcpworkerhandler_free: (a: number, b: number) => void;
export const __wbg_wasmnetworkmanager_free: (a: number, b: number) => void;
export const __wbg_wasmnodeidentity_free: (a: number, b: number) => void;
export const __wbg_wasmstigmergy_free: (a: number, b: number) => void;
export const __wbg_wasmtaskexecutor_free: (a: number, b: number) => void;
export const __wbg_wasmtaskqueue_free: (a: number, b: number) => void;
export const __wbg_witnesstracker_free: (a: number, b: number) => void;
export const adaptivesecurity_chooseAction: (a: number, b: number, c: number, d: number, e: number) => [number, number];
export const adaptivesecurity_detectAttack: (a: number, b: number, c: number) => number;
export const adaptivesecurity_exportPatterns: (a: number) => [number, number, number, number];
export const adaptivesecurity_getMinReputation: (a: number) => number;
export const adaptivesecurity_getRateLimitMax: (a: number) => number;
export const adaptivesecurity_getRateLimitWindow: (a: number) => bigint;
export const adaptivesecurity_getSecurityLevel: (a: number) => number;
export const adaptivesecurity_getSpotCheckProbability: (a: number) => number;
export const adaptivesecurity_getStats: (a: number) => [number, number];
export const adaptivesecurity_importPatterns: (a: number, b: number, c: number) => [number, number];
export const adaptivesecurity_learn: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number) => void;
export const adaptivesecurity_new: () => number;
export const adaptivesecurity_recordAttackPattern: (a: number, b: number, c: number, d: number, e: number, f: number) => void;
export const adaptivesecurity_updateNetworkHealth: (a: number, b: number, c: number, d: number, e: number, f: number) => void;
export const adversarialsimulator_enableChaosMode: (a: number, b: number) => void;
export const adversarialsimulator_generateChaosEvent: (a: number) => [number, number];
export const adversarialsimulator_getDefenceMetrics: (a: number) => [number, number];
export const adversarialsimulator_getRecommendations: (a: number) => [number, number];
export const adversarialsimulator_new: () => number;
export const adversarialsimulator_runSecurityAudit: (a: number) => [number, number];
export const adversarialsimulator_simulateByzantine: (a: number, b: number, c: number) => [number, number];
export const adversarialsimulator_simulateDDoS: (a: number, b: number, c: bigint) => [number, number];
export const adversarialsimulator_simulateDoubleSpend: (a: number, b: bigint, c: number) => [number, number];
export const adversarialsimulator_simulateFreeRiding: (a: number, b: number, c: number) => [number, number];
export const adversarialsimulator_simulateResultTampering: (a: number, b: number) => [number, number];
export const adversarialsimulator_simulateSybil: (a: number, b: number, c: number) => [number, number];
export const auditlog_exportEvents: (a: number) => [number, number];
export const auditlog_getEventsBySeverity: (a: number, b: number) => number;
export const auditlog_getEventsForNode: (a: number, b: number, c: number) => number;
export const auditlog_log: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number) => void;
export const auditlog_new: () => number;
export const browserfingerprint_generate: () => any;
export const byzantinedetector_getMaxMagnitude: (a: number) => number;
export const byzantinedetector_new: (a: number, b: number) => number;
export const coherenceengine_canUseClaim: (a: number, b: number, c: number) => number;
export const coherenceengine_conflictCount: (a: number) => number;
export const coherenceengine_eventCount: (a: number) => number;
export const coherenceengine_getDrift: (a: number, b: number, c: number) => number;
export const coherenceengine_getMerkleRoot: (a: number) => [number, number];
export const coherenceengine_getQuarantineLevel: (a: number, b: number, c: number) => number;
export const coherenceengine_getStats: (a: number) => [number, number];
export const coherenceengine_hasDrifted: (a: number, b: number, c: number) => number;
export const coherenceengine_hasSufficientWitnesses: (a: number, b: number, c: number) => number;
export const coherenceengine_new: () => number;
export const coherenceengine_quarantinedCount: (a: number) => number;
export const coherenceengine_witnessCount: (a: number, b: number, c: number) => number;
export const collectivememory_consolidate: (a: number) => number;
export const collectivememory_getStats: (a: number) => [number, number];
export const collectivememory_hasPattern: (a: number, b: number, c: number) => number;
export const collectivememory_new: (a: number, b: number) => number;
export const collectivememory_patternCount: (a: number) => number;
export const collectivememory_queueSize: (a: number) => number;
export const collectivememory_search: (a: number, b: number, c: number, d: number) => [number, number];
export const contributionstream_getTotalDistributed: (a: number) => bigint;
export const contributionstream_isHealthy: (a: number) => number;
export const contributionstream_new: () => number;
export const contributionstream_processFees: (a: number, b: bigint, c: bigint) => bigint;
export const differentialprivacy_getEpsilon: (a: number) => number;
export const differentialprivacy_isEnabled: (a: number) => number;
export const differentialprivacy_new: (a: number, b: number) => number;
export const differentialprivacy_setEnabled: (a: number, b: number) => void;
export const drifttracker_getDrift: (a: number, b: number, c: number) => number;
export const drifttracker_getDriftedContexts: (a: number) => [number, number];
export const drifttracker_hasDrifted: (a: number, b: number, c: number) => number;
export const drifttracker_new: (a: number) => number;
export const economicengine_advanceEpoch: (a: number) => void;
export const economicengine_getHealth: (a: number) => number;
export const economicengine_getProtocolFund: (a: number) => bigint;
export const economicengine_getTreasury: (a: number) => bigint;
export const economicengine_isSelfSustaining: (a: number, b: number, c: bigint) => number;
export const economicengine_new: () => number;
export const economicengine_processReward: (a: number, b: bigint, c: number) => number;
export const edgenetconfig_addRelay: (a: number, b: number, c: number) => number;
export const edgenetconfig_build: (a: number) => [number, number, number];
export const edgenetconfig_cpuLimit: (a: number, b: number) => number;
export const edgenetconfig_memoryLimit: (a: number, b: number) => number;
export const edgenetconfig_minIdleTime: (a: number, b: number) => number;
export const edgenetconfig_new: (a: number, b: number) => number;
export const edgenetconfig_respectBattery: (a: number, b: number) => number;
export const edgenetnode_canUseClaim: (a: number, b: number, c: number) => number;
export const edgenetnode_checkEvents: (a: number) => [number, number];
export const edgenetnode_creditBalance: (a: number) => bigint;
export const edgenetnode_disconnect: (a: number) => [number, number];
export const edgenetnode_enableBTSP: (a: number, b: number) => number;
export const edgenetnode_enableHDC: (a: number) => number;
export const edgenetnode_enableNAO: (a: number, b: number) => number;
export const edgenetnode_getCapabilities: (a: number) => any;
export const edgenetnode_getCapabilitiesSummary: (a: number) => any;
export const edgenetnode_getClaimQuarantineLevel: (a: number, b: number, c: number) => number;
export const edgenetnode_getCoherenceEventCount: (a: number) => number;
export const edgenetnode_getCoherenceStats: (a: number) => [number, number];
export const edgenetnode_getConflictCount: (a: number) => number;
export const edgenetnode_getEconomicHealth: (a: number) => [number, number];
export const edgenetnode_getEnergyEfficiency: (a: number, b: number, c: number) => number;
export const edgenetnode_getFounderCount: (a: number) => number;
export const edgenetnode_getLearningStats: (a: number) => [number, number];
export const edgenetnode_getMerkleRoot: (a: number) => [number, number];
export const edgenetnode_getMotivation: (a: number) => [number, number];
export const edgenetnode_getMultiplier: (a: number) => number;
export const edgenetnode_getNetworkFitness: (a: number) => number;
export const edgenetnode_getOptimalPeers: (a: number, b: number) => [number, number];
export const edgenetnode_getOptimizationStats: (a: number) => [number, number];
export const edgenetnode_getPatternCount: (a: number) => number;
export const edgenetnode_getProtocolFund: (a: number) => bigint;
export const edgenetnode_getQuarantinedCount: (a: number) => number;
export const edgenetnode_getRecommendedConfig: (a: number) => [number, number];
export const edgenetnode_getStats: (a: number) => number;
export const edgenetnode_getThemedStatus: (a: number, b: number) => [number, number];
export const edgenetnode_getThrottle: (a: number) => number;
export const edgenetnode_getTimeCrystalSync: (a: number) => number;
export const edgenetnode_getTrajectoryCount: (a: number) => number;
export const edgenetnode_getTreasury: (a: number) => bigint;
export const edgenetnode_isIdle: (a: number) => number;
export const edgenetnode_isSelfSustaining: (a: number, b: number, c: bigint) => number;
export const edgenetnode_isStreamHealthy: (a: number) => number;
export const edgenetnode_lookupPatterns: (a: number, b: number, c: number, d: number) => [number, number];
export const edgenetnode_new: (a: number, b: number, c: number) => [number, number, number];
export const edgenetnode_nodeId: (a: number) => [number, number];
export const edgenetnode_pause: (a: number) => void;
export const edgenetnode_processEpoch: (a: number) => void;
export const edgenetnode_processNextTask: (a: number) => any;
export const edgenetnode_proposeNAO: (a: number, b: number, c: number) => [number, number];
export const edgenetnode_prunePatterns: (a: number, b: number, c: number) => number;
export const edgenetnode_recordLearningTrajectory: (a: number, b: number, c: number) => number;
export const edgenetnode_recordPeerInteraction: (a: number, b: number, c: number, d: number) => void;
export const edgenetnode_recordPerformance: (a: number, b: number, c: number) => void;
export const edgenetnode_recordTaskRouting: (a: number, b: number, c: number, d: number, e: number, f: bigint, g: number) => void;
export const edgenetnode_resume: (a: number) => void;
export const edgenetnode_runSecurityAudit: (a: number) => [number, number];
export const edgenetnode_shouldReplicate: (a: number) => number;
export const edgenetnode_start: (a: number) => [number, number];
export const edgenetnode_stepCapabilities: (a: number, b: number) => void;
export const edgenetnode_storePattern: (a: number, b: number, c: number) => number;
export const edgenetnode_submitTask: (a: number, b: number, c: number, d: number, e: number, f: bigint) => any;
export const edgenetnode_voteNAO: (a: number, b: number, c: number, d: number) => number;
export const entropyconsensus_converged: (a: number) => number;
export const entropyconsensus_entropy: (a: number) => number;
export const entropyconsensus_finalize_beliefs: (a: number) => void;
export const entropyconsensus_getBelief: (a: number, b: bigint) => number;
export const entropyconsensus_getDecision: (a: number) => [number, bigint];
export const entropyconsensus_getEntropyHistory: (a: number) => [number, number];
export const entropyconsensus_getEntropyThreshold: (a: number) => number;
export const entropyconsensus_getRounds: (a: number) => number;
export const entropyconsensus_getStats: (a: number) => [number, number];
export const entropyconsensus_getTemperature: (a: number) => number;
export const entropyconsensus_hasTimedOut: (a: number) => number;
export const entropyconsensus_new: () => number;
export const entropyconsensus_optionCount: (a: number) => number;
export const entropyconsensus_reset: (a: number) => void;
export const entropyconsensus_setBelief: (a: number, b: bigint, c: number) => void;
export const entropyconsensus_set_belief_raw: (a: number, b: bigint, c: number) => void;
export const entropyconsensus_withThreshold: (a: number) => number;
export const eventlog_getRoot: (a: number) => [number, number];
export const eventlog_isEmpty: (a: number) => number;
export const eventlog_len: (a: number) => number;
export const eventlog_new: () => number;
export const evolutionengine_evolve: (a: number) => void;
export const evolutionengine_getNetworkFitness: (a: number) => number;
export const evolutionengine_getRecommendedConfig: (a: number) => [number, number];
export const evolutionengine_new: () => number;
export const evolutionengine_recordPerformance: (a: number, b: number, c: number, d: number, e: number) => void;
export const evolutionengine_shouldReplicate: (a: number, b: number, c: number) => number;
export const federatedmodel_applyGradients: (a: number, b: number, c: number) => [number, number];
export const federatedmodel_getDimension: (a: number) => number;
export const federatedmodel_getParameters: (a: number) => [number, number];
export const federatedmodel_getRound: (a: number) => bigint;
export const federatedmodel_new: (a: number, b: number, c: number) => number;
export const federatedmodel_setLearningRate: (a: number, b: number) => void;
export const federatedmodel_setLocalEpochs: (a: number, b: number) => void;
export const federatedmodel_setParameters: (a: number, b: number, c: number) => [number, number];
export const foundingregistry_calculateVested: (a: number, b: bigint, c: bigint) => bigint;
export const foundingregistry_getFounderCount: (a: number) => number;
export const foundingregistry_new: () => number;
export const foundingregistry_processEpoch: (a: number, b: bigint, c: bigint) => [number, number];
export const foundingregistry_registerContributor: (a: number, b: number, c: number, d: number, e: number, f: number) => void;
export const genesiskey_create: (a: number, b: number) => [number, number, number];
export const genesiskey_exportUltraCompact: (a: number) => [number, number];
export const genesiskey_getEpoch: (a: number) => number;
export const genesiskey_getIdHex: (a: number) => [number, number];
export const genesiskey_verify: (a: number, b: number, c: number) => number;
export const genesissunset_canRetire: (a: number) => number;
export const genesissunset_getCurrentPhase: (a: number) => number;
export const genesissunset_getStatus: (a: number) => [number, number];
export const genesissunset_isReadOnly: (a: number) => number;
export const genesissunset_new: () => number;
export const genesissunset_registerGenesisNode: (a: number, b: number, c: number) => void;
export const genesissunset_shouldAcceptConnections: (a: number) => number;
export const genesissunset_updateNodeCount: (a: number, b: number) => number;
export const gradientgossip_advanceRound: (a: number) => bigint;
export const gradientgossip_configureDifferentialPrivacy: (a: number, b: number, c: number) => void;
export const gradientgossip_getAggregatedGradients: (a: number) => [number, number];
export const gradientgossip_getCompressionRatio: (a: number) => number;
export const gradientgossip_getCurrentRound: (a: number) => bigint;
export const gradientgossip_getDimension: (a: number) => number;
export const gradientgossip_getStats: (a: number) => [number, number];
export const gradientgossip_new: (a: number, b: number, c: number, d: number) => [number, number, number];
export const gradientgossip_peerCount: (a: number) => number;
export const gradientgossip_pruneStale: (a: number) => number;
export const gradientgossip_setDPEnabled: (a: number, b: number) => void;
export const gradientgossip_setLocalGradients: (a: number, b: number, c: number) => [number, number];
export const gradientgossip_setModelHash: (a: number, b: number, c: number) => [number, number];
export const init_panic_hook: () => void;
export const modelconsensusmanager_disputeCount: (a: number) => number;
export const modelconsensusmanager_getStats: (a: number) => [number, number];
export const modelconsensusmanager_modelCount: (a: number) => number;
export const modelconsensusmanager_new: (a: number) => number;
export const modelconsensusmanager_quarantinedUpdateCount: (a: number) => number;
export const multiheadattention_dim: (a: number) => number;
export const multiheadattention_new: (a: number, b: number) => number;
export const multiheadattention_numHeads: (a: number) => number;
export const networkevents_checkActiveEvents: (a: number) => [number, number];
export const networkevents_checkDiscovery: (a: number, b: number, c: number, d: number, e: number) => [number, number];
export const networkevents_checkMilestones: (a: number, b: bigint, c: number, d: number) => [number, number];
export const networkevents_getCelebrationBoost: (a: number) => number;
export const networkevents_getMotivation: (a: number, b: bigint) => [number, number];
export const networkevents_getSpecialArt: (a: number) => [number, number];
export const networkevents_getThemedStatus: (a: number, b: number, c: bigint) => [number, number];
export const networkevents_new: () => number;
export const networkevents_setCurrentTime: (a: number, b: bigint) => void;
export const networklearning_getEnergyRatio: (a: number, b: number, c: number) => number;
export const networklearning_getStats: (a: number) => [number, number];
export const networklearning_lookupPatterns: (a: number, b: number, c: number, d: number) => [number, number];
export const networklearning_new: () => number;
export const networklearning_patternCount: (a: number) => number;
export const networklearning_prune: (a: number, b: number, c: number) => number;
export const networklearning_recordTrajectory: (a: number, b: number, c: number) => number;
export const networklearning_storePattern: (a: number, b: number, c: number) => number;
export const networklearning_trajectoryCount: (a: number) => number;
export const networktopology_getOptimalPeers: (a: number, b: number, c: number, d: number) => [number, number];
export const networktopology_new: () => number;
export const networktopology_registerNode: (a: number, b: number, c: number, d: number, e: number) => void;
export const networktopology_updateConnection: (a: number, b: number, c: number, d: number, e: number, f: number) => void;
export const optimizationengine_getStats: (a: number) => [number, number];
export const optimizationengine_new: () => number;
export const optimizationengine_recordRouting: (a: number, b: number, c: number, d: number, e: number, f: bigint, g: number) => void;
export const optimizationengine_selectOptimalNode: (a: number, b: number, c: number, d: number, e: number) => [number, number];
export const pikey_createEncryptedBackup: (a: number, b: number, c: number) => [number, number, number, number];
export const pikey_exportCompact: (a: number) => [number, number];
export const pikey_generate: (a: number, b: number) => [number, number, number];
export const pikey_getGenesisFingerprint: (a: number) => [number, number];
export const pikey_getIdentity: (a: number) => [number, number];
export const pikey_getIdentityHex: (a: number) => [number, number];
export const pikey_getPublicKey: (a: number) => [number, number];
export const pikey_getShortId: (a: number) => [number, number];
export const pikey_getStats: (a: number) => [number, number];
export const pikey_restoreFromBackup: (a: number, b: number, c: number, d: number) => [number, number, number];
export const pikey_sign: (a: number, b: number, c: number) => [number, number];
export const pikey_verify: (a: number, b: number, c: number, d: number, e: number, f: number, g: number) => number;
export const pikey_verifyPiMagic: (a: number) => number;
export const qdagledger_balance: (a: number, b: number, c: number) => bigint;
export const qdagledger_createGenesis: (a: number, b: bigint, c: number, d: number) => [number, number, number, number];
export const qdagledger_createTransaction: (a: number, b: number, c: number, d: number, e: number, f: bigint, g: number, h: number, i: number, j: number, k: number) => [number, number, number, number];
export const qdagledger_exportState: (a: number) => [number, number, number, number];
export const qdagledger_importState: (a: number, b: number, c: number) => [number, number, number];
export const qdagledger_new: () => number;
export const qdagledger_stakedAmount: (a: number, b: number, c: number) => bigint;
export const qdagledger_tipCount: (a: number) => number;
export const qdagledger_totalSupply: (a: number) => bigint;
export const qdagledger_transactionCount: (a: number) => number;
export const quarantinemanager_canUse: (a: number, b: number, c: number) => number;
export const quarantinemanager_getLevel: (a: number, b: number, c: number) => number;
export const quarantinemanager_new: () => number;
export const quarantinemanager_quarantinedCount: (a: number) => number;
export const quarantinemanager_setLevel: (a: number, b: number, c: number, d: number) => void;
export const raceconomicengine_canParticipate: (a: number, b: number, c: number) => number;
export const raceconomicengine_getCombinedScore: (a: number, b: number, c: number) => number;
export const raceconomicengine_getSummary: (a: number) => [number, number];
export const raceconomicengine_new: () => number;
export const racsemanticrouter_new: () => number;
export const racsemanticrouter_peerCount: (a: number) => number;
export const ratelimiter_checkAllowed: (a: number, b: number, c: number) => number;
export const ratelimiter_getCount: (a: number, b: number, c: number) => number;
export const ratelimiter_new: (a: bigint, b: number) => number;
export const ratelimiter_reset: (a: number) => void;
export const reasoningbank_count: (a: number) => number;
export const reasoningbank_getStats: (a: number) => [number, number];
export const reasoningbank_lookup: (a: number, b: number, c: number, d: number) => [number, number];
export const reasoningbank_new: () => number;
export const reasoningbank_prune: (a: number, b: number, c: number) => number;
export const reasoningbank_store: (a: number, b: number, c: number) => number;
export const reputationmanager_averageReputation: (a: number) => number;
export const reputationmanager_getReputation: (a: number, b: number, c: number) => number;
export const reputationmanager_hasSufficientReputation: (a: number, b: number, c: number) => number;
export const reputationmanager_new: (a: number, b: bigint) => number;
export const reputationmanager_nodeCount: (a: number) => number;
export const reputationsystem_canParticipate: (a: number, b: number, c: number) => number;
export const reputationsystem_getReputation: (a: number, b: number, c: number) => number;
export const reputationsystem_new: () => number;
export const reputationsystem_recordFailure: (a: number, b: number, c: number) => void;
export const reputationsystem_recordPenalty: (a: number, b: number, c: number, d: number) => void;
export const reputationsystem_recordSuccess: (a: number, b: number, c: number) => void;
export const rewardmanager_claimableAmount: (a: number, b: number, c: number) => bigint;
export const rewardmanager_new: (a: bigint) => number;
export const rewardmanager_pendingAmount: (a: number) => bigint;
export const rewardmanager_pendingCount: (a: number) => number;
export const semanticrouter_activePeerCount: (a: number) => number;
export const semanticrouter_getStats: (a: number) => [number, number];
export const semanticrouter_new: () => number;
export const semanticrouter_peerCount: (a: number) => number;
export const semanticrouter_setMyCapabilities: (a: number, b: number, c: number) => void;
export const semanticrouter_setMyPeerId: (a: number, b: number, c: number) => void;
export const semanticrouter_topicCount: (a: number) => number;
export const semanticrouter_withParams: (a: number, b: number, c: number) => number;
export const sessionkey_create: (a: number, b: number) => [number, number, number];
export const sessionkey_decrypt: (a: number, b: number, c: number) => [number, number, number, number];
export const sessionkey_encrypt: (a: number, b: number, c: number) => [number, number, number, number];
export const sessionkey_getId: (a: number) => [number, number];
export const sessionkey_getIdHex: (a: number) => [number, number];
export const sessionkey_getParentIdentity: (a: number) => [number, number];
export const sessionkey_isExpired: (a: number) => number;
export const spikedrivenattention_energyRatio: (a: number, b: number, c: number) => number;
export const spikedrivenattention_new: () => number;
export const spikedrivenattention_withConfig: (a: number, b: number, c: number) => number;
export const spotchecker_addChallenge: (a: number, b: number, c: number, d: number, e: number, f: number, g: number) => void;
export const spotchecker_getChallenge: (a: number, b: number, c: number) => [number, number];
export const spotchecker_new: (a: number) => number;
export const spotchecker_shouldCheck: (a: number) => number;
export const spotchecker_verifyResponse: (a: number, b: number, c: number, d: number, e: number) => number;
export const stakemanager_getMinStake: (a: number) => bigint;
export const stakemanager_getStake: (a: number, b: number, c: number) => bigint;
export const stakemanager_hasSufficientStake: (a: number, b: number, c: number) => number;
export const stakemanager_new: (a: bigint) => number;
export const stakemanager_stakerCount: (a: number) => number;
export const stakemanager_totalStaked: (a: number) => bigint;
export const swarmintelligence_addPattern: (a: number, b: number, c: number) => number;
export const swarmintelligence_consolidate: (a: number) => number;
export const swarmintelligence_getConsensusDecision: (a: number, b: number, c: number) => [number, bigint];
export const swarmintelligence_getStats: (a: number) => [number, number];
export const swarmintelligence_hasConsensus: (a: number, b: number, c: number) => number;
export const swarmintelligence_negotiateBeliefs: (a: number, b: number, c: number, d: number, e: number) => number;
export const swarmintelligence_new: (a: number, b: number) => number;
export const swarmintelligence_nodeId: (a: number) => [number, number];
export const swarmintelligence_patternCount: (a: number) => number;
export const swarmintelligence_queueSize: (a: number) => number;
export const swarmintelligence_replay: (a: number) => number;
export const swarmintelligence_searchPatterns: (a: number, b: number, c: number, d: number) => [number, number];
export const swarmintelligence_setBelief: (a: number, b: number, c: number, d: bigint, e: number) => void;
export const swarmintelligence_startConsensus: (a: number, b: number, c: number, d: number) => void;
export const sybildefense_getSybilScore: (a: number, b: number, c: number) => number;
export const sybildefense_isSuspectedSybil: (a: number, b: number, c: number) => number;
export const sybildefense_new: () => number;
export const sybildefense_registerNode: (a: number, b: number, c: number, d: number, e: number) => number;
export const topksparsifier_getCompressionRatio: (a: number) => number;
export const topksparsifier_getErrorBufferSize: (a: number) => number;
export const topksparsifier_new: (a: number) => number;
export const topksparsifier_resetErrorFeedback: (a: number) => void;
export const trajectorytracker_count: (a: number) => number;
export const trajectorytracker_getStats: (a: number) => [number, number];
export const trajectorytracker_new: (a: number) => number;
export const trajectorytracker_record: (a: number, b: number, c: number) => number;
export const wasmadapterpool_adapterCount: (a: number) => number;
export const wasmadapterpool_exportAdapter: (a: number, b: number, c: number) => [number, number];
export const wasmadapterpool_forward: (a: number, b: number, c: number, d: number, e: number) => [number, number];
export const wasmadapterpool_getAdapter: (a: number, b: number, c: number) => any;
export const wasmadapterpool_getStats: (a: number) => any;
export const wasmadapterpool_importAdapter: (a: number, b: number, c: number, d: number, e: number) => number;
export const wasmadapterpool_new: (a: number, b: number) => number;
export const wasmadapterpool_routeToAdapter: (a: number, b: number, c: number) => any;
export const wasmcapabilities_adaptMicroLoRA: (a: number, b: number, c: number, d: number, e: number) => number;
export const wasmcapabilities_addNAOMember: (a: number, b: number, c: number, d: bigint) => number;
export const wasmcapabilities_applyMicroLoRA: (a: number, b: number, c: number, d: number, e: number) => [number, number];
export const wasmcapabilities_broadcastToWorkspace: (a: number, b: number, c: number, d: number, e: number) => number;
export const wasmcapabilities_competeWTA: (a: number, b: number, c: number) => number;
export const wasmcapabilities_differentiateMorphogenetic: (a: number) => void;
export const wasmcapabilities_enableBTSP: (a: number, b: number, c: number) => number;
export const wasmcapabilities_enableGlobalWorkspace: (a: number, b: number) => number;
export const wasmcapabilities_enableHDC: (a: number) => number;
export const wasmcapabilities_enableMicroLoRA: (a: number, b: number, c: number) => number;
export const wasmcapabilities_enableNAO: (a: number, b: number) => number;
export const wasmcapabilities_enableWTA: (a: number, b: number, c: number, d: number) => number;
export const wasmcapabilities_executeNAO: (a: number, b: number, c: number) => number;
export const wasmcapabilities_forwardBTSP: (a: number, b: number, c: number) => number;
export const wasmcapabilities_getCapabilities: (a: number) => any;
export const wasmcapabilities_getMorphogeneticCellCount: (a: number) => number;
export const wasmcapabilities_getMorphogeneticStats: (a: number) => any;
export const wasmcapabilities_getNAOSync: (a: number) => number;
export const wasmcapabilities_getSummary: (a: number) => any;
export const wasmcapabilities_growMorphogenetic: (a: number, b: number) => void;
export const wasmcapabilities_new: (a: number, b: number) => number;
export const wasmcapabilities_oneShotAssociate: (a: number, b: number, c: number, d: number) => number;
export const wasmcapabilities_proposeNAO: (a: number, b: number, c: number) => [number, number];
export const wasmcapabilities_retrieveHDC: (a: number, b: number, c: number, d: number) => any;
export const wasmcapabilities_tickTimeCrystal: (a: number) => any;
export const wasmcapabilities_voteNAO: (a: number, b: number, c: number, d: number) => number;
export const wasmcreditledger_balance: (a: number) => bigint;
export const wasmcreditledger_credit: (a: number, b: bigint, c: number, d: number) => [number, number];
export const wasmcreditledger_currentMultiplier: (a: number) => number;
export const wasmcreditledger_deduct: (a: number, b: bigint) => [number, number];
export const wasmcreditledger_exportEarned: (a: number) => [number, number, number, number];
export const wasmcreditledger_exportSpent: (a: number) => [number, number, number, number];
export const wasmcreditledger_merge: (a: number, b: number, c: number, d: number, e: number) => [number, number];
export const wasmcreditledger_networkCompute: (a: number) => number;
export const wasmcreditledger_new: (a: number, b: number) => [number, number, number];
export const wasmcreditledger_slash: (a: number, b: bigint) => [bigint, number, number];
export const wasmcreditledger_stake: (a: number, b: bigint) => [number, number];
export const wasmcreditledger_stakedAmount: (a: number) => bigint;
export const wasmcreditledger_totalEarned: (a: number) => bigint;
export const wasmcreditledger_totalSpent: (a: number) => bigint;
export const wasmcreditledger_unstake: (a: number, b: bigint) => [number, number];
export const wasmcreditledger_updateNetworkCompute: (a: number, b: number) => void;
export const wasmidledetector_getStatus: (a: number) => any;
export const wasmidledetector_getThrottle: (a: number) => number;
export const wasmidledetector_isIdle: (a: number) => number;
export const wasmidledetector_new: (a: number, b: number) => [number, number, number];
export const wasmidledetector_pause: (a: number) => void;
export const wasmidledetector_recordInteraction: (a: number) => void;
export const wasmidledetector_resume: (a: number) => void;
export const wasmidledetector_setBatteryStatus: (a: number, b: number) => void;
export const wasmidledetector_shouldWork: (a: number) => number;
export const wasmidledetector_start: (a: number) => [number, number];
export const wasmidledetector_stop: (a: number) => void;
export const wasmidledetector_updateFps: (a: number, b: number) => void;
export const wasmmcpbroadcast_close: (a: number) => void;
export const wasmmcpbroadcast_listen: (a: number) => [number, number];
export const wasmmcpbroadcast_new: (a: number, b: number) => [number, number, number];
export const wasmmcpbroadcast_send: (a: number, b: number, c: number) => [number, number];
export const wasmmcpbroadcast_setServer: (a: number, b: number) => void;
export const wasmmcpserver_getServerInfo: (a: number) => any;
export const wasmmcpserver_handleRequest: (a: number, b: number, c: number) => any;
export const wasmmcpserver_handleRequestJs: (a: number, b: any) => any;
export const wasmmcpserver_initLearning: (a: number) => [number, number];
export const wasmmcpserver_new: () => [number, number, number];
export const wasmmcpserver_setIdentity: (a: number, b: number) => void;
export const wasmmcpserver_withConfig: (a: any) => [number, number, number];
export const wasmmcptransport_close: (a: number) => void;
export const wasmmcptransport_fromPort: (a: any) => number;
export const wasmmcptransport_init: (a: number) => [number, number];
export const wasmmcptransport_new: (a: any) => [number, number, number];
export const wasmmcptransport_send: (a: number, b: any) => any;
export const wasmmcpworkerhandler_new: (a: number) => number;
export const wasmmcpworkerhandler_start: (a: number) => [number, number];
export const wasmnetworkmanager_activePeerCount: (a: number) => number;
export const wasmnetworkmanager_addRelay: (a: number, b: number, c: number) => void;
export const wasmnetworkmanager_getPeersWithCapability: (a: number, b: number, c: number) => [number, number];
export const wasmnetworkmanager_isConnected: (a: number) => number;
export const wasmnetworkmanager_new: (a: number, b: number) => number;
export const wasmnetworkmanager_peerCount: (a: number) => number;
export const wasmnetworkmanager_registerPeer: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: bigint) => void;
export const wasmnetworkmanager_selectWorkers: (a: number, b: number, c: number, d: number) => [number, number];
export const wasmnetworkmanager_updateReputation: (a: number, b: number, c: number, d: number) => void;
export const wasmnodeidentity_exportSecretKey: (a: number, b: number, c: number) => [number, number, number, number];
export const wasmnodeidentity_fromSecretKey: (a: number, b: number, c: number, d: number) => [number, number, number];
export const wasmnodeidentity_generate: (a: number, b: number) => [number, number, number];
export const wasmnodeidentity_getFingerprint: (a: number) => [number, number];
export const wasmnodeidentity_importSecretKey: (a: number, b: number, c: number, d: number, e: number, f: number) => [number, number, number];
export const wasmnodeidentity_nodeId: (a: number) => [number, number];
export const wasmnodeidentity_publicKeyBytes: (a: number) => [number, number];
export const wasmnodeidentity_publicKeyHex: (a: number) => [number, number];
export const wasmnodeidentity_setFingerprint: (a: number, b: number, c: number) => void;
export const wasmnodeidentity_sign: (a: number, b: number, c: number) => [number, number];
export const wasmnodeidentity_siteId: (a: number) => [number, number];
export const wasmnodeidentity_verify: (a: number, b: number, c: number, d: number, e: number) => number;
export const wasmnodeidentity_verifyFrom: (a: number, b: number, c: number, d: number, e: number, f: number) => number;
export const wasmstigmergy_deposit: (a: number, b: number, c: number, d: number, e: number, f: number, g: bigint) => void;
export const wasmstigmergy_depositWithOutcome: (a: number, b: number, c: number, d: number, e: number, f: number, g: bigint) => void;
export const wasmstigmergy_evaporate: (a: number) => void;
export const wasmstigmergy_exportState: (a: number) => [number, number];
export const wasmstigmergy_follow: (a: number, b: number, c: number) => number;
export const wasmstigmergy_getBestSpecialization: (a: number) => [number, number];
export const wasmstigmergy_getIntensity: (a: number, b: number, c: number) => number;
export const wasmstigmergy_getRankedTasks: (a: number) => [number, number];
export const wasmstigmergy_getSpecialization: (a: number, b: number, c: number) => number;
export const wasmstigmergy_getStats: (a: number) => [number, number];
export const wasmstigmergy_getSuccessRate: (a: number, b: number, c: number) => number;
export const wasmstigmergy_maybeEvaporate: (a: number) => number;
export const wasmstigmergy_merge: (a: number, b: number, c: number) => number;
export const wasmstigmergy_new: () => number;
export const wasmstigmergy_setMinStake: (a: number, b: bigint) => void;
export const wasmstigmergy_shouldAccept: (a: number, b: number, c: number) => number;
export const wasmstigmergy_updateSpecialization: (a: number, b: number, c: number, d: number) => void;
export const wasmstigmergy_withParams: (a: number, b: number, c: number) => number;
export const wasmtaskexecutor_new: (a: number) => [number, number, number];
export const wasmtaskexecutor_setTaskKey: (a: number, b: number, c: number) => [number, number];
export const wasmworkscheduler_new: () => number;
export const wasmworkscheduler_recordTaskDuration: (a: number, b: number) => void;
export const wasmworkscheduler_setPendingTasks: (a: number, b: number) => void;
export const wasmworkscheduler_tasksThisFrame: (a: number, b: number) => number;
export const witnesstracker_hasSufficientWitnesses: (a: number, b: number, c: number) => number;
export const witnesstracker_new: (a: number) => number;
export const witnesstracker_witnessConfidence: (a: number, b: number, c: number) => number;
export const witnesstracker_witnessCount: (a: number, b: number, c: number) => number;
export const wasmcapabilities_getTimeCrystalSync: (a: number) => number;
export const __wbg_set_nodeconfig_cpu_limit: (a: number, b: number) => void;
export const __wbg_set_rewarddistribution_contributor_share: (a: number, b: bigint) => void;
export const __wbg_set_rewarddistribution_founder_share: (a: number, b: bigint) => void;
export const __wbg_set_rewarddistribution_protocol_share: (a: number, b: bigint) => void;
export const __wbg_set_rewarddistribution_total: (a: number, b: bigint) => void;
export const __wbg_set_rewarddistribution_treasury_share: (a: number, b: bigint) => void;
export const genesissunset_isSelfSustaining: (a: number) => number;
export const edgenetnode_ruvBalance: (a: number) => bigint;
export const eventlog_totalEvents: (a: number) => number;
export const edgenetnode_enableGlobalWorkspace: (a: number, b: number) => number;
export const edgenetnode_enableMicroLoRA: (a: number, b: number) => number;
export const edgenetnode_enableMorphogenetic: (a: number, b: number) => number;
export const edgenetnode_enableTimeCrystal: (a: number, b: number) => number;
export const edgenetnode_enableWTA: (a: number, b: number) => number;
export const wasmcapabilities_pruneMorphogenetic: (a: number, b: number) => void;
export const wasmcapabilities_step: (a: number, b: number) => void;
export const wasmcapabilities_tickNAO: (a: number, b: number) => void;
export const wasmcapabilities_getWorkspaceContents: (a: number) => any;
export const wasmcapabilities_isTimeCrystalStable: (a: number) => number;
export const wasmcapabilities_storeHDC: (a: number, b: number, c: number) => number;
export const wasmcapabilities_enableMorphogenetic: (a: number, b: number, c: number) => number;
export const wasmcapabilities_enableTimeCrystal: (a: number, b: number, c: number) => number;
export const __wbg_get_nodeconfig_cpu_limit: (a: number) => number;
export const __wbg_get_rewarddistribution_contributor_share: (a: number) => bigint;
export const __wbg_get_rewarddistribution_founder_share: (a: number) => bigint;
export const __wbg_get_rewarddistribution_protocol_share: (a: number) => bigint;
export const __wbg_get_rewarddistribution_total: (a: number) => bigint;
export const __wbg_get_rewarddistribution_treasury_share: (a: number) => bigint;
export const __wbg_wasmworkscheduler_free: (a: number, b: number) => void;
export const __wbg_multiheadattention_free: (a: number, b: number) => void;
export const genesiskey_getId: (a: number) => [number, number];
export const wasm_bindgen__convert__closures_____invoke__h8c81ca6cba4eba00: (a: number, b: number, c: any) => void;
export const wasm_bindgen__closure__destroy__h16844f6554aa4052: (a: number, b: number) => void;
export const wasm_bindgen__convert__closures_____invoke__h9a454594a18d3e6f: (a: number, b: number, c: any) => void;
export const wasm_bindgen__closure__destroy__h5a0fd3a052925ed0: (a: number, b: number) => void;
export const wasm_bindgen__convert__closures_____invoke__h094c87b54a975e5a: (a: number, b: number, c: any, d: any) => void;
export const __wbindgen_malloc: (a: number, b: number) => number;
export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
export const __wbindgen_exn_store: (a: number) => void;
export const __externref_table_alloc: () => number;
export const __wbindgen_externrefs: WebAssembly.Table;
export const __wbindgen_free: (a: number, b: number, c: number) => void;
export const __externref_table_dealloc: (a: number) => void;
export const __externref_drop_slice: (a: number, b: number) => void;
export const __wbindgen_start: () => void;

View File

@@ -0,0 +1,702 @@
/**
* @ruvector/edge-net P2P Integration
*
* Unified P2P networking layer that integrates:
* - WebRTC for direct peer connections
* - DHT for decentralized peer discovery
* - Signaling for connection establishment
* - Sync for ledger synchronization
*
* @module @ruvector/edge-net/p2p
*/
import { EventEmitter } from 'events';
import { randomBytes } from 'crypto';
// Import P2P components
import { WebRTCPeerManager, WEBRTC_CONFIG } from './webrtc.js';
import { DHTNode, createDHTNode } from './dht.js';
import { SignalingClient } from './signaling.js';
import { SyncManager } from './sync.js';
import { QDAG } from './qdag.js';
import { Ledger } from './ledger.js';
import { HybridBootstrap, FirebaseLedgerSync } from './firebase-signaling.js';
// ============================================
// P2P NETWORK CONFIGURATION
// ============================================
export const P2P_CONFIG = {
// Auto-start components
autoStart: {
signaling: true,
webrtc: true,
dht: true,
sync: true,
firebase: true, // Use Firebase for bootstrap
},
// Bootstrap strategy: 'firebase' | 'local' | 'dht-only'
bootstrapStrategy: 'firebase',
// Connection settings
maxPeers: 50,
minPeers: 3,
// Reconnection
reconnectInterval: 5000,
maxReconnectAttempts: 10,
// DHT settings
dhtAnnounceInterval: 60000,
// Sync settings
syncInterval: 30000,
// Firebase settings (optional override)
firebase: {
// Default uses public edge-net Firebase
// Override with your own project for production
projectId: null,
apiKey: null,
},
// Migration thresholds
migration: {
dhtPeerThreshold: 5, // Peers needed before reducing Firebase dependency
p2pPeerThreshold: 10, // Peers needed for full P2P mode
},
};
// ============================================
// P2P NETWORK NODE
// ============================================
/**
* Unified P2P Network Node
*
* Connects all P2P components into a single interface:
* - Automatic peer discovery via DHT
* - WebRTC data channels for direct messaging
* - QDAG for distributed consensus
* - Ledger sync across devices
*/
export class P2PNetwork extends EventEmitter {
constructor(identity, options = {}) {
super();
this.identity = identity;
this.options = { ...P2P_CONFIG, ...options };
// Node ID
this.nodeId = identity?.piKey || identity?.nodeId || `node-${randomBytes(8).toString('hex')}`;
// Components (initialized on start)
this.signaling = null;
this.webrtc = null;
this.dht = null;
this.syncManager = null;
this.qdag = null;
this.ledger = null;
// Firebase bootstrap (Google Cloud)
this.hybridBootstrap = null;
this.firebaseLedgerSync = null;
// State
this.state = 'stopped';
this.peers = new Map();
this.stats = {
startedAt: null,
peersConnected: 0,
messagesReceived: 0,
messagesSent: 0,
bytesTransferred: 0,
};
}
/**
* Start the P2P network
*/
async start() {
if (this.state === 'running') return this;
console.log('\n🌐 Starting P2P Network...');
console.log(` Node ID: ${this.nodeId.slice(0, 16)}...`);
this.state = 'starting';
try {
// Initialize QDAG
this.qdag = new QDAG({ nodeId: this.nodeId });
console.log(' ✅ QDAG initialized');
// Initialize Ledger
this.ledger = new Ledger({ nodeId: this.nodeId });
console.log(' ✅ Ledger initialized');
// Start Firebase bootstrap (Google Cloud) - Primary path
if (this.options.autoStart.firebase && this.options.bootstrapStrategy === 'firebase') {
await this.startFirebaseBootstrap();
}
// Fallback: Try local signaling server
else if (this.options.autoStart.signaling) {
await this.startSignaling();
}
// Initialize WebRTC
if (this.options.autoStart.webrtc) {
await this.startWebRTC();
}
// Initialize DHT for peer discovery
if (this.options.autoStart.dht) {
await this.startDHT();
}
// Initialize sync
if (this.options.autoStart.sync && this.identity) {
await this.startSync();
}
// Start Firebase hybrid bootstrap after WebRTC/DHT are ready
if (this.hybridBootstrap && this.webrtc) {
await this.hybridBootstrap.start(this.webrtc, this.dht);
// Also start Firebase ledger sync
await this.startFirebaseLedgerSync();
}
// Wire up event handlers
this.setupEventHandlers();
// Announce presence
await this.announce();
this.state = 'running';
this.stats.startedAt = Date.now();
console.log('\n✅ P2P Network running');
console.log(` Mode: ${this.getMode()}`);
console.log(` Peers: ${this.peers.size}`);
this.emit('started', { nodeId: this.nodeId, mode: this.getMode() });
return this;
} catch (error) {
this.state = 'error';
console.error('❌ P2P Network start failed:', error.message);
throw error;
}
}
/**
* Stop the P2P network
*/
async stop() {
console.log('\n🛑 Stopping P2P Network...');
this.state = 'stopping';
// Stop Firebase components
if (this.hybridBootstrap) await this.hybridBootstrap.stop();
if (this.firebaseLedgerSync) this.firebaseLedgerSync.stop();
// Stop P2P components
if (this.syncManager) this.syncManager.stop();
if (this.dht) this.dht.stop();
if (this.webrtc) this.webrtc.close();
if (this.signaling) this.signaling.disconnect();
this.peers.clear();
this.state = 'stopped';
this.emit('stopped');
}
/**
* Start Firebase hybrid bootstrap (Google Cloud)
* Primary bootstrap path - uses Firebase for discovery, migrates to P2P
*/
async startFirebaseBootstrap() {
try {
this.hybridBootstrap = new HybridBootstrap({
peerId: this.nodeId,
firebaseConfig: this.options.firebase?.projectId ? {
apiKey: this.options.firebase.apiKey,
projectId: this.options.firebase.projectId,
authDomain: `${this.options.firebase.projectId}.firebaseapp.com`,
databaseURL: `https://${this.options.firebase.projectId}-default-rtdb.firebaseio.com`,
} : undefined,
dhtPeerThreshold: this.options.migration?.dhtPeerThreshold,
p2pPeerThreshold: this.options.migration?.p2pPeerThreshold,
});
// Wire up bootstrap events
this.hybridBootstrap.on('peer-discovered', ({ peerId, source }) => {
console.log(` 🔍 Discovered peer via ${source}: ${peerId.slice(0, 8)}...`);
});
this.hybridBootstrap.on('mode-changed', ({ from, to }) => {
console.log(` 🔄 Bootstrap mode: ${from}${to}`);
this.emit('bootstrap-mode-changed', { from, to });
});
// Start will be called after WebRTC/DHT init
console.log(' ✅ Firebase bootstrap initialized');
} catch (err) {
console.log(' ⚠️ Firebase bootstrap failed:', err.message);
// Fall back to local signaling
await this.startSignaling();
}
}
/**
* Start Firebase ledger sync
*/
async startFirebaseLedgerSync() {
if (!this.ledger) return;
try {
this.firebaseLedgerSync = new FirebaseLedgerSync(this.ledger, {
peerId: this.nodeId,
firebaseConfig: this.options.firebase?.projectId ? {
apiKey: this.options.firebase.apiKey,
projectId: this.options.firebase.projectId,
} : undefined,
syncInterval: this.options.syncInterval,
});
await this.firebaseLedgerSync.start();
console.log(' ✅ Firebase ledger sync started');
} catch (err) {
console.log(' ⚠️ Firebase ledger sync failed:', err.message);
}
}
/**
* Start signaling client (fallback for local development)
*/
async startSignaling() {
try {
this.signaling = new SignalingClient({
serverUrl: this.options.signalingUrl || WEBRTC_CONFIG.signalingServers[0],
peerId: this.nodeId,
});
const connected = await this.signaling.connect();
if (connected) {
console.log(' ✅ Signaling connected (local)');
} else {
console.log(' ⚠️ Signaling unavailable (will use DHT)');
}
} catch (err) {
console.log(' ⚠️ Signaling failed:', err.message);
}
}
/**
* Start WebRTC peer manager
*/
async startWebRTC() {
try {
this.webrtc = new WebRTCPeerManager({
piKey: this.nodeId,
siteId: this.identity?.siteId || 'edge-net',
}, this.options.webrtc || {});
await this.webrtc.initialize();
console.log(` ✅ WebRTC initialized (${this.webrtc.mode} mode)`);
} catch (err) {
console.log(' ⚠️ WebRTC failed:', err.message);
}
}
/**
* Start DHT for peer discovery
*/
async startDHT() {
try {
if (this.webrtc) {
this.dht = await createDHTNode(this.webrtc, {
id: this.nodeId,
});
} else {
this.dht = new DHTNode({ id: this.nodeId });
await this.dht.start();
}
console.log(' ✅ DHT initialized');
} catch (err) {
console.log(' ⚠️ DHT failed:', err.message);
}
}
/**
* Start sync manager
*/
async startSync() {
try {
this.syncManager = new SyncManager(this.identity, this.ledger, {
enableP2P: !!this.webrtc,
enableFirestore: false, // Use P2P only for now
});
await this.syncManager.start();
console.log(' ✅ Sync initialized');
} catch (err) {
console.log(' ⚠️ Sync failed:', err.message);
}
}
/**
* Setup event handlers between components
*/
setupEventHandlers() {
// WebRTC events
if (this.webrtc) {
this.webrtc.on('peer-connected', (peerId) => {
this.handlePeerConnected(peerId);
});
this.webrtc.on('peer-disconnected', (peerId) => {
this.handlePeerDisconnected(peerId);
});
this.webrtc.on('message', ({ from, message }) => {
this.handleMessage(from, message);
});
}
// Signaling events
if (this.signaling) {
this.signaling.on('peer-joined', ({ peerId }) => {
this.connectToPeer(peerId);
});
this.signaling.on('offer', async ({ from, offer }) => {
if (this.webrtc) {
await this.webrtc.handleOffer({ from, offer });
}
});
this.signaling.on('answer', async ({ from, answer }) => {
if (this.webrtc) {
await this.webrtc.handleAnswer({ from, answer });
}
});
this.signaling.on('ice-candidate', async ({ from, candidate }) => {
if (this.webrtc) {
await this.webrtc.handleIceCandidate({ from, candidate });
}
});
}
// DHT events
if (this.dht) {
this.dht.on('peer-added', (peer) => {
this.connectToPeer(peer.id);
});
}
// Sync events
if (this.syncManager) {
this.syncManager.on('synced', (data) => {
this.emit('synced', data);
});
}
// QDAG events
if (this.qdag) {
this.qdag.on('transaction', (tx) => {
this.broadcastTransaction(tx);
});
this.qdag.on('confirmed', (tx) => {
this.emit('transaction-confirmed', tx);
});
}
}
/**
* Announce presence to the network
*/
async announce() {
// Announce via signaling
if (this.signaling?.isConnected) {
this.signaling.send({
type: 'announce',
piKey: this.nodeId,
siteId: this.identity?.siteId,
capabilities: ['compute', 'storage', 'verify'],
});
}
// Announce via DHT
if (this.dht) {
await this.dht.announce('edge-net');
}
}
/**
* Connect to a peer
*/
async connectToPeer(peerId) {
if (peerId === this.nodeId) return;
if (this.peers.has(peerId)) return;
try {
if (this.webrtc) {
await this.webrtc.connectToPeer(peerId);
}
} catch (err) {
console.warn(`[P2P] Failed to connect to ${peerId.slice(0, 8)}:`, err.message);
}
}
/**
* Handle peer connected
*/
handlePeerConnected(peerId) {
this.peers.set(peerId, {
id: peerId,
connectedAt: Date.now(),
lastSeen: Date.now(),
});
this.stats.peersConnected++;
// Register with sync
if (this.syncManager && this.webrtc) {
const peer = this.webrtc.peers.get(peerId);
if (peer?.dataChannel) {
this.syncManager.registerPeer(peerId, peer.dataChannel);
}
}
this.emit('peer-connected', { peerId });
console.log(` 🔗 Connected to peer: ${peerId.slice(0, 8)}... (${this.peers.size} total)`);
}
/**
* Handle peer disconnected
*/
handlePeerDisconnected(peerId) {
this.peers.delete(peerId);
this.emit('peer-disconnected', { peerId });
}
/**
* Handle incoming message
*/
handleMessage(from, message) {
this.stats.messagesReceived++;
// Route by message type
switch (message.type) {
case 'qdag_transaction':
this.handleQDAGTransaction(from, message);
break;
case 'qdag_sync_request':
this.handleQDAGSyncRequest(from, message);
break;
case 'qdag_sync_response':
this.handleQDAGSyncResponse(from, message);
break;
default:
this.emit('message', { from, message });
}
}
/**
* Handle QDAG transaction
*/
handleQDAGTransaction(from, message) {
if (message.transaction && this.qdag) {
try {
this.qdag.addTransaction(message.transaction);
} catch (err) {
// Duplicate or invalid transaction
}
}
}
/**
* Handle QDAG sync request
*/
handleQDAGSyncRequest(from, message) {
if (this.qdag && this.webrtc) {
const transactions = this.qdag.export(message.since || 0);
this.webrtc.sendToPeer(from, {
type: 'qdag_sync_response',
transactions: transactions.transactions,
});
}
}
/**
* Handle QDAG sync response
*/
handleQDAGSyncResponse(from, message) {
if (message.transactions && this.qdag) {
this.qdag.import({ transactions: message.transactions });
}
}
/**
* Broadcast a transaction to all peers
*/
broadcastTransaction(tx) {
if (this.webrtc) {
this.webrtc.broadcast({
type: 'qdag_transaction',
transaction: tx.toJSON(),
});
this.stats.messagesSent++;
}
}
/**
* Send message to a specific peer
*/
sendToPeer(peerId, message) {
if (this.webrtc) {
const sent = this.webrtc.sendToPeer(peerId, message);
if (sent) this.stats.messagesSent++;
return sent;
}
return false;
}
/**
* Broadcast message to all peers
*/
broadcast(message) {
if (this.webrtc) {
const sent = this.webrtc.broadcast(message);
this.stats.messagesSent += sent;
return sent;
}
return 0;
}
/**
* Submit a task to the network
*/
async submitTask(task) {
if (!this.qdag) throw new Error('QDAG not initialized');
const tx = this.qdag.createTransaction('task', {
taskId: task.id || randomBytes(8).toString('hex'),
type: task.type,
data: task.data,
reward: task.reward || 0,
priority: task.priority || 'medium',
}, {
issuer: this.nodeId,
});
return tx;
}
/**
* Credit the ledger
*/
credit(amount, reason) {
if (!this.ledger) throw new Error('Ledger not initialized');
this.ledger.credit(amount, reason);
// Trigger sync
if (this.syncManager) {
this.syncManager.sync();
}
}
/**
* Get current balance
*/
getBalance() {
return this.ledger?.balance?.() ?? this.ledger?.getBalance?.() ?? 0;
}
/**
* Get connection mode
* Returns the current bootstrap/connectivity mode
*/
getMode() {
// Check Firebase hybrid bootstrap mode first
if (this.hybridBootstrap) {
const bootstrapMode = this.hybridBootstrap.mode;
if (bootstrapMode === 'p2p') return 'full-p2p';
if (bootstrapMode === 'hybrid') return 'firebase-hybrid';
if (bootstrapMode === 'firebase') return 'firebase-bootstrap';
}
// Legacy mode detection
if (this.webrtc?.mode === 'webrtc' && this.signaling?.isConnected) {
return 'full-p2p';
}
if (this.webrtc?.mode === 'webrtc') {
return 'webrtc-dht';
}
if (this.dht) {
return 'dht-only';
}
return 'local';
}
/**
* Get bootstrap mode (firebase/hybrid/p2p)
*/
getBootstrapMode() {
return this.hybridBootstrap?.mode || 'none';
}
/**
* Get network statistics
*/
getStats() {
const bootstrapStats = this.hybridBootstrap?.getStats() || {};
return {
...this.stats,
nodeId: this.nodeId,
state: this.state,
mode: this.getMode(),
bootstrapMode: this.getBootstrapMode(),
peers: this.peers.size,
// Firebase stats
firebaseConnected: bootstrapStats.firebaseConnected || false,
firebasePeers: bootstrapStats.firebasePeers || 0,
firebaseSignals: bootstrapStats.firebaseSignals || 0,
p2pSignals: bootstrapStats.p2pSignals || 0,
// Legacy signaling
signalingConnected: this.signaling?.isConnected || false,
webrtcMode: this.webrtc?.mode || 'none',
dhtPeers: this.dht?.getPeers().length || 0,
qdagTransactions: this.qdag?.transactions.size || 0,
ledgerBalance: this.getBalance(),
uptime: this.stats.startedAt ? Date.now() - this.stats.startedAt : 0,
};
}
/**
* Get peer list
*/
getPeers() {
return Array.from(this.peers.values());
}
}
/**
* Create and start a P2P network node
*/
export async function createP2PNetwork(identity, options = {}) {
const network = new P2PNetwork(identity, options);
await network.start();
return network;
}
// ============================================
// EXPORTS
// ============================================
export default P2PNetwork;

View File

@@ -0,0 +1,85 @@
{
"name": "@ruvector/edge-net",
"version": "0.1.1",
"type": "module",
"description": "Distributed compute intelligence network - contribute browser compute, earn credits. Features Time Crystal coordination, Neural DAG attention, and P2P swarm intelligence.",
"main": "ruvector_edge_net.js",
"module": "ruvector_edge_net.js",
"types": "ruvector_edge_net.d.ts",
"bin": {
"edge-net": "./cli.js",
"ruvector-edge": "./cli.js",
"edge-net-join": "./join.js"
},
"keywords": [
"wasm",
"distributed-computing",
"p2p",
"web-workers",
"ai",
"machine-learning",
"compute",
"credits",
"marketplace",
"browser",
"edge-computing",
"vector-search",
"embeddings",
"cryptography",
"time-crystal",
"dag-attention",
"swarm-intelligence",
"neural-network"
],
"author": "RuVector Team <team@ruvector.dev>",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/ruvnet/ruvector"
},
"homepage": "https://github.com/ruvnet/ruvector/tree/main/examples/edge-net",
"bugs": {
"url": "https://github.com/ruvnet/ruvector/issues"
},
"files": [
"ruvector_edge_net_bg.wasm",
"ruvector_edge_net.js",
"ruvector_edge_net.d.ts",
"ruvector_edge_net_bg.wasm.d.ts",
"node/",
"index.js",
"cli.js",
"join.js",
"join.html",
"network.js",
"networks.js",
"README.md",
"LICENSE"
],
"exports": {
".": {
"import": "./ruvector_edge_net.js",
"types": "./ruvector_edge_net.d.ts"
},
"./wasm": {
"import": "./ruvector_edge_net_bg.wasm"
}
},
"sideEffects": [
"./snippets/*"
],
"engines": {
"node": ">=18.0.0"
},
"scripts": {
"start": "node cli.js start",
"benchmark": "node cli.js benchmark",
"info": "node cli.js info",
"join": "node join.js",
"join:generate": "node join.js --generate",
"join:multi": "node join.js --generate",
"network": "node network.js stats",
"peers": "node join.js --peers",
"history": "node join.js --history"
}
}

View File

@@ -0,0 +1,621 @@
/**
* @ruvector/edge-net QDAG (Quantum DAG) Implementation
*
* Directed Acyclic Graph for distributed consensus and task tracking
* Inspired by IOTA Tangle and DAG-based blockchains
*
* Features:
* - Tip selection algorithm
* - Proof of contribution verification
* - Transaction validation
* - Network synchronization
*
* @module @ruvector/edge-net/qdag
*/
import { EventEmitter } from 'events';
import { randomBytes, createHash, createHmac } from 'crypto';
// ============================================
// TRANSACTION
// ============================================
/**
* QDAG Transaction
*/
export class Transaction {
constructor(data = {}) {
this.id = data.id || `tx-${randomBytes(16).toString('hex')}`;
this.timestamp = data.timestamp || Date.now();
this.type = data.type || 'generic'; // 'genesis', 'task', 'reward', 'transfer'
// Links to parent transactions (must reference 2 tips)
this.parents = data.parents || [];
// Transaction payload
this.payload = data.payload || {};
// Proof of contribution
this.proof = data.proof || null;
// Issuer
this.issuer = data.issuer || null;
this.signature = data.signature || null;
// Computed fields
this.hash = data.hash || this.computeHash();
this.weight = data.weight || 1;
this.cumulativeWeight = data.cumulativeWeight || 1;
this.confirmed = data.confirmed || false;
this.confirmedAt = data.confirmedAt || null;
}
/**
* Compute transaction hash
*/
computeHash() {
const content = JSON.stringify({
id: this.id,
timestamp: this.timestamp,
type: this.type,
parents: this.parents,
payload: this.payload,
proof: this.proof,
issuer: this.issuer,
});
return createHash('sha256').update(content).digest('hex');
}
/**
* Sign transaction
*/
sign(privateKey) {
const hmac = createHmac('sha256', privateKey);
hmac.update(this.hash);
this.signature = hmac.digest('hex');
return this.signature;
}
/**
* Verify signature
*/
verify(publicKey) {
if (!this.signature) return false;
const hmac = createHmac('sha256', publicKey);
hmac.update(this.hash);
const expected = hmac.digest('hex');
return this.signature === expected;
}
/**
* Serialize transaction
*/
toJSON() {
return {
id: this.id,
timestamp: this.timestamp,
type: this.type,
parents: this.parents,
payload: this.payload,
proof: this.proof,
issuer: this.issuer,
signature: this.signature,
hash: this.hash,
weight: this.weight,
cumulativeWeight: this.cumulativeWeight,
confirmed: this.confirmed,
confirmedAt: this.confirmedAt,
};
}
/**
* Deserialize transaction
*/
static fromJSON(json) {
return new Transaction(json);
}
}
// ============================================
// QDAG (Quantum DAG)
// ============================================
/**
* QDAG - Directed Acyclic Graph for distributed consensus
*/
export class QDAG extends EventEmitter {
constructor(options = {}) {
super();
this.id = options.id || `qdag-${randomBytes(8).toString('hex')}`;
this.nodeId = options.nodeId;
// Transaction storage
this.transactions = new Map();
this.tips = new Set(); // Unconfirmed transactions
this.confirmed = new Set(); // Confirmed transactions
// Indices
this.byIssuer = new Map(); // issuer -> Set<txId>
this.byType = new Map(); // type -> Set<txId>
this.children = new Map(); // txId -> Set<childTxId>
// Configuration
this.confirmationThreshold = options.confirmationThreshold || 5;
this.maxTips = options.maxTips || 100;
this.pruneAge = options.pruneAge || 24 * 60 * 60 * 1000; // 24 hours
// Stats
this.stats = {
transactions: 0,
confirmed: 0,
tips: 0,
avgConfirmationTime: 0,
};
// Create genesis if needed
if (options.createGenesis !== false) {
this.createGenesis();
}
}
/**
* Create genesis transaction
*/
createGenesis() {
const genesis = new Transaction({
id: 'genesis',
type: 'genesis',
parents: [],
payload: {
message: 'QDAG Genesis',
timestamp: Date.now(),
},
issuer: 'system',
});
genesis.confirmed = true;
genesis.confirmedAt = Date.now();
genesis.cumulativeWeight = this.confirmationThreshold + 1;
this.transactions.set(genesis.id, genesis);
this.tips.add(genesis.id);
this.confirmed.add(genesis.id);
this.emit('genesis', genesis);
return genesis;
}
/**
* Select tips for new transaction (weighted random walk)
*/
selectTips(count = 2) {
// Ensure genesis exists
if (!this.transactions.has('genesis')) {
this.createGenesis();
}
const tips = Array.from(this.tips);
// Fallback to genesis if no tips available
if (tips.length === 0) {
return ['genesis'];
}
// Return all tips if we have fewer than requested
if (tips.length <= count) {
return [...tips]; // Return copy to prevent mutation issues
}
// Weighted random selection based on cumulative weight
const selected = new Set();
const weights = tips.map(tipId => {
const tx = this.transactions.get(tipId);
return tx ? Math.max(tx.cumulativeWeight, 1) : 1;
});
const totalWeight = weights.reduce((a, b) => a + b, 0);
// Safety: prevent infinite loop
let attempts = 0;
const maxAttempts = count * 10;
while (selected.size < count && selected.size < tips.length && attempts < maxAttempts) {
let random = Math.random() * totalWeight;
for (let i = 0; i < tips.length; i++) {
random -= weights[i];
if (random <= 0) {
selected.add(tips[i]);
break;
}
}
attempts++;
}
// Ensure we have at least one valid parent
const result = Array.from(selected);
if (result.length === 0) {
result.push(tips[0] || 'genesis');
}
return result;
}
/**
* Add transaction to QDAG
*/
addTransaction(tx) {
// Validate transaction with detailed error
const validation = this.validateTransaction(tx, { returnError: true });
if (!validation.valid) {
throw new Error(`Invalid transaction: ${validation.error}`);
}
// Check for duplicates
if (this.transactions.has(tx.id)) {
return this.transactions.get(tx.id);
}
// Store transaction
this.transactions.set(tx.id, tx);
this.tips.add(tx.id);
this.stats.transactions++;
// Update indices
if (tx.issuer) {
if (!this.byIssuer.has(tx.issuer)) {
this.byIssuer.set(tx.issuer, new Set());
}
this.byIssuer.get(tx.issuer).add(tx.id);
}
if (!this.byType.has(tx.type)) {
this.byType.set(tx.type, new Set());
}
this.byType.get(tx.type).add(tx.id);
// Update parent references
for (const parentId of tx.parents) {
if (!this.children.has(parentId)) {
this.children.set(parentId, new Set());
}
this.children.get(parentId).add(tx.id);
// Remove parent from tips
this.tips.delete(parentId);
}
// Update weights
this.updateWeights(tx.id);
// Check for confirmations
this.checkConfirmations();
this.emit('transaction', tx);
return tx;
}
/**
* Create and add a new transaction
*/
createTransaction(type, payload, options = {}) {
const parents = options.parents || this.selectTips(2);
const tx = new Transaction({
type,
payload,
parents,
issuer: options.issuer || this.nodeId,
proof: options.proof,
});
if (options.privateKey) {
tx.sign(options.privateKey);
}
return this.addTransaction(tx);
}
/**
* Validate transaction
* @returns {boolean|{valid: boolean, error: string}}
*/
validateTransaction(tx, options = {}) {
const returnError = options.returnError || false;
const fail = (msg) => returnError ? { valid: false, error: msg } : false;
const pass = () => returnError ? { valid: true, error: null } : true;
// Check required fields
if (!tx.id) {
return fail('Missing transaction id');
}
if (!tx.timestamp) {
return fail('Missing transaction timestamp');
}
if (!tx.type) {
return fail('Missing transaction type');
}
// Genesis transactions don't need parents
if (tx.type === 'genesis') {
return pass();
}
// Ensure genesis exists before validating non-genesis transactions
if (!this.transactions.has('genesis')) {
this.createGenesis();
}
// Check parents exist
if (!tx.parents || tx.parents.length === 0) {
return fail('Non-genesis transaction must have at least one parent');
}
for (const parentId of tx.parents) {
if (!this.transactions.has(parentId)) {
return fail(`Parent transaction not found: ${parentId}`);
}
}
// Check no cycles (parents must be older or equal for simultaneous txs)
for (const parentId of tx.parents) {
const parent = this.transactions.get(parentId);
// Allow equal timestamps (transactions created at same time)
if (parent && parent.timestamp > tx.timestamp) {
return fail(`Parent ${parentId} has future timestamp`);
}
}
return pass();
}
/**
* Update cumulative weights
*/
updateWeights(txId) {
const tx = this.transactions.get(txId);
if (!tx) return;
// Update weight of this transaction
tx.cumulativeWeight = tx.weight;
// Add weight of all children
const children = this.children.get(txId);
if (children) {
for (const childId of children) {
const child = this.transactions.get(childId);
if (child) {
tx.cumulativeWeight += child.cumulativeWeight;
}
}
}
// Propagate to parents
for (const parentId of tx.parents) {
this.updateWeights(parentId);
}
}
/**
* Check for newly confirmed transactions
*/
checkConfirmations() {
for (const [txId, tx] of this.transactions) {
if (!tx.confirmed && tx.cumulativeWeight >= this.confirmationThreshold) {
tx.confirmed = true;
tx.confirmedAt = Date.now();
this.confirmed.add(txId);
this.stats.confirmed++;
// Update average confirmation time
const confirmTime = tx.confirmedAt - tx.timestamp;
this.stats.avgConfirmationTime =
(this.stats.avgConfirmationTime * (this.stats.confirmed - 1) + confirmTime) /
this.stats.confirmed;
this.emit('confirmed', tx);
}
}
this.stats.tips = this.tips.size;
}
/**
* Get transaction by ID
*/
getTransaction(txId) {
return this.transactions.get(txId);
}
/**
* Get transactions by issuer
*/
getByIssuer(issuer) {
const txIds = this.byIssuer.get(issuer) || new Set();
return Array.from(txIds).map(id => this.transactions.get(id));
}
/**
* Get transactions by type
*/
getByType(type) {
const txIds = this.byType.get(type) || new Set();
return Array.from(txIds).map(id => this.transactions.get(id));
}
/**
* Get current tips
*/
getTips() {
return Array.from(this.tips).map(id => this.transactions.get(id));
}
/**
* Get confirmed transactions
*/
getConfirmed() {
return Array.from(this.confirmed).map(id => this.transactions.get(id));
}
/**
* Prune old transactions
*/
prune() {
const cutoff = Date.now() - this.pruneAge;
let pruned = 0;
for (const [txId, tx] of this.transactions) {
if (tx.type === 'genesis') continue;
if (tx.confirmed && tx.confirmedAt < cutoff) {
// Remove from storage
this.transactions.delete(txId);
this.confirmed.delete(txId);
this.tips.delete(txId);
// Clean up indices
if (tx.issuer && this.byIssuer.has(tx.issuer)) {
this.byIssuer.get(tx.issuer).delete(txId);
}
if (this.byType.has(tx.type)) {
this.byType.get(tx.type).delete(txId);
}
this.children.delete(txId);
pruned++;
}
}
if (pruned > 0) {
this.emit('pruned', { count: pruned });
}
return pruned;
}
/**
* Get QDAG statistics
*/
getStats() {
return {
id: this.id,
...this.stats,
size: this.transactions.size,
memoryUsage: process.memoryUsage?.().heapUsed,
};
}
/**
* Export QDAG for synchronization
*/
export(since = 0) {
const transactions = [];
for (const [txId, tx] of this.transactions) {
if (tx.timestamp >= since) {
transactions.push(tx.toJSON());
}
}
return {
id: this.id,
timestamp: Date.now(),
transactions,
};
}
/**
* Import transactions from another node
*/
import(data) {
let imported = 0;
// Sort by timestamp to maintain order
const sorted = data.transactions.sort((a, b) => a.timestamp - b.timestamp);
for (const txData of sorted) {
try {
const tx = Transaction.fromJSON(txData);
if (!this.transactions.has(tx.id)) {
this.addTransaction(tx);
imported++;
}
} catch (error) {
console.error('[QDAG] Import error:', error.message);
}
}
this.emit('imported', { count: imported, from: data.id });
return imported;
}
/**
* Merge with another QDAG
*/
merge(other) {
return this.import(other.export());
}
}
// ============================================
// TASK TRANSACTION HELPERS
// ============================================
/**
* Create a task submission transaction
*/
export function createTaskTransaction(qdag, task, options = {}) {
return qdag.createTransaction('task', {
taskId: task.id,
type: task.type,
data: task.data,
priority: task.priority || 'medium',
reward: task.reward || 0,
deadline: task.deadline,
}, options);
}
/**
* Create a task completion/reward transaction
*/
export function createRewardTransaction(qdag, taskTxId, result, options = {}) {
const taskTx = qdag.getTransaction(taskTxId);
if (!taskTx) throw new Error('Task transaction not found');
return qdag.createTransaction('reward', {
taskTxId,
result,
worker: options.worker,
reward: taskTx.payload.reward || 0,
completedAt: Date.now(),
}, {
...options,
parents: [taskTxId, ...qdag.selectTips(1)],
});
}
/**
* Create a credit transfer transaction
*/
export function createTransferTransaction(qdag, from, to, amount, options = {}) {
return qdag.createTransaction('transfer', {
from,
to,
amount,
memo: options.memo,
}, options);
}
// ============================================
// EXPORTS
// ============================================
export default QDAG;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,739 @@
/**
* @ruvector/edge-net REAL Workflow Orchestration
*
* Actually functional workflow system with:
* - Real LLM agent execution for each step
* - Real dependency resolution
* - Real parallel/sequential execution
* - Real result aggregation
*
* @module @ruvector/edge-net/real-workflows
*/
import { EventEmitter } from 'events';
import { randomBytes } from 'crypto';
import { RealAgentManager, LLMClient } from './real-agents.js';
import { RealWorkerPool } from './real-workers.js';
// ============================================
// WORKFLOW STEP TYPES
// ============================================
export const StepTypes = {
AGENT: 'agent', // LLM agent execution
WORKER: 'worker', // Worker pool execution
PARALLEL: 'parallel', // Parallel sub-steps
SEQUENTIAL: 'sequential', // Sequential sub-steps
CONDITION: 'condition', // Conditional branching
TRANSFORM: 'transform', // Data transformation
AGGREGATE: 'aggregate', // Result aggregation
};
// ============================================
// WORKFLOW TEMPLATES
// ============================================
export const WorkflowTemplates = {
'code-review': {
name: 'Code Review',
description: 'Comprehensive code review with multiple agents',
steps: [
{
id: 'analyze',
type: 'agent',
agentType: 'analyst',
prompt: 'Analyze the code structure and identify key components: {{input}}',
},
{
id: 'review-quality',
type: 'agent',
agentType: 'reviewer',
prompt: 'Review code quality, best practices, and potential issues based on analysis: {{analyze.output}}',
dependsOn: ['analyze'],
},
{
id: 'review-security',
type: 'agent',
agentType: 'reviewer',
prompt: 'Review security vulnerabilities and concerns: {{input}}',
},
{
id: 'suggestions',
type: 'agent',
agentType: 'coder',
prompt: 'Provide specific code improvement suggestions based on reviews:\nQuality: {{review-quality.output}}\nSecurity: {{review-security.output}}',
dependsOn: ['review-quality', 'review-security'],
},
],
},
'feature-dev': {
name: 'Feature Development',
description: 'End-to-end feature development workflow',
steps: [
{
id: 'research',
type: 'agent',
agentType: 'researcher',
prompt: 'Research requirements and best practices for: {{input}}',
},
{
id: 'design',
type: 'agent',
agentType: 'analyst',
prompt: 'Design the architecture and approach based on research: {{research.output}}',
dependsOn: ['research'],
},
{
id: 'implement',
type: 'agent',
agentType: 'coder',
prompt: 'Implement the feature based on design: {{design.output}}',
dependsOn: ['design'],
},
{
id: 'test',
type: 'agent',
agentType: 'tester',
prompt: 'Write tests for the implementation: {{implement.output}}',
dependsOn: ['implement'],
},
{
id: 'review',
type: 'agent',
agentType: 'reviewer',
prompt: 'Final review of implementation and tests:\nCode: {{implement.output}}\nTests: {{test.output}}',
dependsOn: ['implement', 'test'],
},
],
},
'bug-fix': {
name: 'Bug Fix',
description: 'Systematic bug investigation and fix workflow',
steps: [
{
id: 'investigate',
type: 'agent',
agentType: 'analyst',
prompt: 'Investigate the bug and identify root cause: {{input}}',
},
{
id: 'fix',
type: 'agent',
agentType: 'coder',
prompt: 'Implement the fix for: {{investigate.output}}',
dependsOn: ['investigate'],
},
{
id: 'test',
type: 'agent',
agentType: 'tester',
prompt: 'Write regression tests to prevent recurrence: {{fix.output}}',
dependsOn: ['fix'],
},
{
id: 'verify',
type: 'agent',
agentType: 'reviewer',
prompt: 'Verify the fix is complete and correct:\nFix: {{fix.output}}\nTests: {{test.output}}',
dependsOn: ['fix', 'test'],
},
],
},
'optimization': {
name: 'Performance Optimization',
description: 'Performance analysis and optimization workflow',
steps: [
{
id: 'profile',
type: 'agent',
agentType: 'optimizer',
prompt: 'Profile and identify performance bottlenecks: {{input}}',
},
{
id: 'analyze',
type: 'agent',
agentType: 'analyst',
prompt: 'Analyze profiling results and prioritize optimizations: {{profile.output}}',
dependsOn: ['profile'],
},
{
id: 'optimize',
type: 'agent',
agentType: 'coder',
prompt: 'Implement optimizations based on analysis: {{analyze.output}}',
dependsOn: ['analyze'],
},
{
id: 'benchmark',
type: 'agent',
agentType: 'tester',
prompt: 'Benchmark optimized code and compare: {{optimize.output}}',
dependsOn: ['optimize'],
},
],
},
'research': {
name: 'Research',
description: 'Deep research and analysis workflow',
steps: [
{
id: 'gather',
type: 'agent',
agentType: 'researcher',
prompt: 'Gather information and sources on: {{input}}',
},
{
id: 'analyze',
type: 'agent',
agentType: 'analyst',
prompt: 'Analyze gathered information: {{gather.output}}',
dependsOn: ['gather'],
},
{
id: 'synthesize',
type: 'agent',
agentType: 'researcher',
prompt: 'Synthesize findings into actionable insights: {{analyze.output}}',
dependsOn: ['analyze'],
},
],
},
};
// ============================================
// WORKFLOW STEP
// ============================================
class WorkflowStep {
constructor(config) {
this.id = config.id;
this.type = config.type || StepTypes.AGENT;
this.agentType = config.agentType;
this.prompt = config.prompt;
this.dependsOn = config.dependsOn || [];
this.options = config.options || {};
this.subSteps = config.subSteps || [];
this.condition = config.condition;
this.transform = config.transform;
this.status = 'pending';
this.output = null;
this.error = null;
this.startTime = null;
this.endTime = null;
}
/**
* Interpolate template variables
*/
interpolate(template, context) {
return template.replace(/\{\{(\w+(?:\.\w+)?)\}\}/g, (match, path) => {
const parts = path.split('.');
let value = context;
for (const part of parts) {
if (value && typeof value === 'object') {
value = value[part];
} else {
return match; // Keep original if not found
}
}
if (typeof value === 'object') {
return JSON.stringify(value, null, 2);
}
return value !== undefined ? String(value) : match;
});
}
getInfo() {
return {
id: this.id,
type: this.type,
status: this.status,
duration: this.endTime && this.startTime ? this.endTime - this.startTime : null,
dependsOn: this.dependsOn,
hasOutput: this.output !== null,
error: this.error,
};
}
}
// ============================================
// REAL WORKFLOW ORCHESTRATOR
// ============================================
/**
* Real workflow orchestrator with actual LLM execution
*/
export class RealWorkflowOrchestrator extends EventEmitter {
constructor(options = {}) {
super();
this.agentManager = null;
this.workerPool = null;
this.workflows = new Map();
this.options = options;
this.stats = {
workflowsCompleted: 0,
workflowsFailed: 0,
stepsExecuted: 0,
totalDuration: 0,
};
}
/**
* Initialize orchestrator
*/
async initialize() {
// Initialize agent manager for LLM execution
this.agentManager = new RealAgentManager({
provider: this.options.provider || 'anthropic',
apiKey: this.options.apiKey,
});
await this.agentManager.initialize();
// Initialize worker pool for compute tasks
this.workerPool = new RealWorkerPool({ size: 4 });
await this.workerPool.initialize();
return this;
}
/**
* Create workflow from template or custom definition
*/
createWorkflow(nameOrConfig, customTask = null) {
let config;
if (typeof nameOrConfig === 'string') {
const template = WorkflowTemplates[nameOrConfig];
if (!template) {
throw new Error(`Unknown workflow template: ${nameOrConfig}`);
}
config = {
...template,
input: customTask,
};
} else {
config = nameOrConfig;
}
const workflow = {
id: `wf-${randomBytes(6).toString('hex')}`,
name: config.name,
description: config.description,
input: config.input,
steps: config.steps.map(s => new WorkflowStep(s)),
status: 'created',
results: {},
startTime: null,
endTime: null,
error: null,
};
this.workflows.set(workflow.id, workflow);
this.emit('workflow-created', { workflowId: workflow.id, name: workflow.name });
return workflow;
}
/**
* Execute a workflow
*/
async executeWorkflow(workflowId) {
const workflow = this.workflows.get(workflowId);
if (!workflow) {
throw new Error(`Workflow not found: ${workflowId}`);
}
workflow.status = 'running';
workflow.startTime = Date.now();
workflow.results = { input: workflow.input };
this.emit('workflow-start', { workflowId, name: workflow.name });
try {
// Build dependency graph
const graph = this.buildDependencyGraph(workflow.steps);
// Execute steps respecting dependencies
await this.executeSteps(workflow, graph);
workflow.status = 'completed';
workflow.endTime = Date.now();
const duration = workflow.endTime - workflow.startTime;
this.stats.workflowsCompleted++;
this.stats.totalDuration += duration;
this.emit('workflow-complete', {
workflowId,
duration,
results: workflow.results,
});
return {
workflowId,
status: 'completed',
duration,
results: workflow.results,
steps: workflow.steps.map(s => s.getInfo()),
};
} catch (error) {
workflow.status = 'failed';
workflow.error = error.message;
workflow.endTime = Date.now();
this.stats.workflowsFailed++;
this.emit('workflow-error', { workflowId, error: error.message });
throw error;
}
}
/**
* Build dependency graph
*/
buildDependencyGraph(steps) {
const graph = new Map();
const stepMap = new Map();
for (const step of steps) {
stepMap.set(step.id, step);
graph.set(step.id, new Set(step.dependsOn));
}
return { graph, stepMap };
}
/**
* Execute steps respecting dependencies
*/
async executeSteps(workflow, { graph, stepMap }) {
const completed = new Set();
const running = new Map();
const isReady = (stepId) => {
const deps = graph.get(stepId);
return [...deps].every(d => completed.has(d));
};
const getReadySteps = () => {
const ready = [];
for (const [stepId, deps] of graph) {
if (!completed.has(stepId) && !running.has(stepId) && isReady(stepId)) {
ready.push(stepMap.get(stepId));
}
}
return ready;
};
while (completed.size < stepMap.size) {
const readySteps = getReadySteps();
if (readySteps.length === 0 && running.size === 0) {
throw new Error('Workflow deadlock: no steps ready and none running');
}
// Execute ready steps in parallel
for (const step of readySteps) {
const promise = this.executeStep(step, workflow.results)
.then(result => {
workflow.results[step.id] = { output: result };
completed.add(step.id);
running.delete(step.id);
this.stats.stepsExecuted++;
})
.catch(error => {
step.error = error.message;
step.status = 'failed';
throw error;
});
running.set(step.id, promise);
}
// Wait for at least one to complete
if (running.size > 0) {
await Promise.race(running.values());
}
}
}
/**
* Execute a single step
*/
async executeStep(step, context) {
step.status = 'running';
step.startTime = Date.now();
this.emit('step-start', { stepId: step.id, type: step.type });
try {
let result;
switch (step.type) {
case StepTypes.AGENT:
result = await this.executeAgentStep(step, context);
break;
case StepTypes.WORKER:
result = await this.executeWorkerStep(step, context);
break;
case StepTypes.PARALLEL:
result = await this.executeParallelStep(step, context);
break;
case StepTypes.SEQUENTIAL:
result = await this.executeSequentialStep(step, context);
break;
case StepTypes.TRANSFORM:
result = await this.executeTransformStep(step, context);
break;
case StepTypes.CONDITION:
result = await this.executeConditionStep(step, context);
break;
case StepTypes.AGGREGATE:
result = await this.executeAggregateStep(step, context);
break;
default:
throw new Error(`Unknown step type: ${step.type}`);
}
step.output = result;
step.status = 'completed';
step.endTime = Date.now();
this.emit('step-complete', {
stepId: step.id,
duration: step.endTime - step.startTime,
});
return result;
} catch (error) {
step.status = 'failed';
step.error = error.message;
step.endTime = Date.now();
this.emit('step-error', { stepId: step.id, error: error.message });
throw error;
}
}
/**
* Execute agent step with real LLM
*/
async executeAgentStep(step, context) {
const prompt = step.interpolate(step.prompt, context);
const result = await this.agentManager.quickExecute(
step.agentType || 'coder',
prompt,
{
model: step.options.model || 'balanced',
...step.options,
}
);
return result.content;
}
/**
* Execute worker step
*/
async executeWorkerStep(step, context) {
const data = step.interpolate(
JSON.stringify(step.options.data || context.input),
context
);
return this.workerPool.execute(
step.options.taskType || 'process',
JSON.parse(data),
step.options
);
}
/**
* Execute parallel sub-steps
*/
async executeParallelStep(step, context) {
const subSteps = step.subSteps.map(s => new WorkflowStep(s));
const promises = subSteps.map(s => this.executeStep(s, context));
const results = await Promise.all(promises);
return results.reduce((acc, result, i) => {
acc[subSteps[i].id] = result;
return acc;
}, {});
}
/**
* Execute sequential sub-steps
*/
async executeSequentialStep(step, context) {
const subSteps = step.subSteps.map(s => new WorkflowStep(s));
const results = {};
for (const subStep of subSteps) {
results[subStep.id] = await this.executeStep(subStep, { ...context, ...results });
}
return results;
}
/**
* Execute transform step
*/
async executeTransformStep(step, context) {
const inputKey = step.options.input || 'input';
const input = context[inputKey]?.output || context[inputKey] || context.input;
if (step.transform) {
// Custom transform function as string
const fn = new Function('input', 'context', step.transform);
return fn(input, context);
}
// Default transforms
const transformType = step.options.transformType || 'identity';
switch (transformType) {
case 'json':
return JSON.parse(input);
case 'stringify':
return JSON.stringify(input);
case 'extract':
return input[step.options.key];
default:
return input;
}
}
/**
* Execute condition step
*/
async executeConditionStep(step, context) {
const condition = step.interpolate(step.condition, context);
// Evaluate condition
const fn = new Function('context', `return ${condition}`);
const result = fn(context);
if (result && step.options.then) {
const thenStep = new WorkflowStep(step.options.then);
return this.executeStep(thenStep, context);
} else if (!result && step.options.else) {
const elseStep = new WorkflowStep(step.options.else);
return this.executeStep(elseStep, context);
}
return result;
}
/**
* Execute aggregate step
*/
async executeAggregateStep(step, context) {
const keys = step.options.keys || Object.keys(context).filter(k => k !== 'input');
const aggregated = {};
for (const key of keys) {
if (context[key]) {
aggregated[key] = context[key].output || context[key];
}
}
if (step.options.format === 'summary') {
return Object.entries(aggregated)
.map(([k, v]) => `## ${k}\n${typeof v === 'string' ? v : JSON.stringify(v, null, 2)}`)
.join('\n\n');
}
return aggregated;
}
/**
* Run workflow by template name
*/
async run(templateName, input, options = {}) {
const workflow = this.createWorkflow(templateName, input);
return this.executeWorkflow(workflow.id);
}
/**
* Run custom workflow
*/
async runCustom(config) {
const workflow = this.createWorkflow(config);
return this.executeWorkflow(workflow.id);
}
/**
* Get workflow status
*/
getWorkflow(workflowId) {
const workflow = this.workflows.get(workflowId);
if (!workflow) return null;
return {
id: workflow.id,
name: workflow.name,
status: workflow.status,
steps: workflow.steps.map(s => s.getInfo()),
duration: workflow.endTime && workflow.startTime
? workflow.endTime - workflow.startTime
: null,
error: workflow.error,
};
}
/**
* Get orchestrator stats
*/
getStats() {
return {
...this.stats,
activeWorkflows: [...this.workflows.values()]
.filter(w => w.status === 'running').length,
agentManager: this.agentManager?.listAgents()?.length || 0,
workerPool: this.workerPool?.getStatus(),
};
}
/**
* Shutdown orchestrator
*/
async shutdown() {
if (this.agentManager) {
await this.agentManager.close();
}
if (this.workerPool) {
await this.workerPool.shutdown();
}
}
// Alias for shutdown
async close() {
return this.shutdown();
}
}
// Export WorkflowStep (not exported with export class)
export { WorkflowStep };
// Default export
export default RealWorkflowOrchestrator;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@@ -0,0 +1,625 @@
/* tslint:disable */
/* eslint-disable */
export const memory: WebAssembly.Memory;
export const __wbg_adaptivesecurity_free: (a: number, b: number) => void;
export const __wbg_adversarialsimulator_free: (a: number, b: number) => void;
export const __wbg_auditlog_free: (a: number, b: number) => void;
export const __wbg_browserfingerprint_free: (a: number, b: number) => void;
export const __wbg_byzantinedetector_free: (a: number, b: number) => void;
export const __wbg_coherenceengine_free: (a: number, b: number) => void;
export const __wbg_collectivememory_free: (a: number, b: number) => void;
export const __wbg_contributionstream_free: (a: number, b: number) => void;
export const __wbg_differentialprivacy_free: (a: number, b: number) => void;
export const __wbg_drifttracker_free: (a: number, b: number) => void;
export const __wbg_economicengine_free: (a: number, b: number) => void;
export const __wbg_economichealth_free: (a: number, b: number) => void;
export const __wbg_edgenetconfig_free: (a: number, b: number) => void;
export const __wbg_edgenetnode_free: (a: number, b: number) => void;
export const __wbg_entropyconsensus_free: (a: number, b: number) => void;
export const __wbg_eventlog_free: (a: number, b: number) => void;
export const __wbg_evolutionengine_free: (a: number, b: number) => void;
export const __wbg_federatedmodel_free: (a: number, b: number) => void;
export const __wbg_foundingregistry_free: (a: number, b: number) => void;
export const __wbg_genesiskey_free: (a: number, b: number) => void;
export const __wbg_genesissunset_free: (a: number, b: number) => void;
export const __wbg_get_economichealth_growth_rate: (a: number) => number;
export const __wbg_get_economichealth_stability: (a: number) => number;
export const __wbg_get_economichealth_utilization: (a: number) => number;
export const __wbg_get_economichealth_velocity: (a: number) => number;
export const __wbg_get_nodeconfig_bandwidth_limit: (a: number) => number;
export const __wbg_get_nodeconfig_memory_limit: (a: number) => number;
export const __wbg_get_nodeconfig_min_idle_time: (a: number) => number;
export const __wbg_get_nodeconfig_respect_battery: (a: number) => number;
export const __wbg_get_nodestats_celebration_boost: (a: number) => number;
export const __wbg_get_nodestats_multiplier: (a: number) => number;
export const __wbg_get_nodestats_reputation: (a: number) => number;
export const __wbg_get_nodestats_ruv_earned: (a: number) => bigint;
export const __wbg_get_nodestats_ruv_spent: (a: number) => bigint;
export const __wbg_get_nodestats_tasks_completed: (a: number) => bigint;
export const __wbg_get_nodestats_tasks_submitted: (a: number) => bigint;
export const __wbg_get_nodestats_uptime_seconds: (a: number) => bigint;
export const __wbg_gradientgossip_free: (a: number, b: number) => void;
export const __wbg_modelconsensusmanager_free: (a: number, b: number) => void;
export const __wbg_networkevents_free: (a: number, b: number) => void;
export const __wbg_networklearning_free: (a: number, b: number) => void;
export const __wbg_networktopology_free: (a: number, b: number) => void;
export const __wbg_nodeconfig_free: (a: number, b: number) => void;
export const __wbg_nodestats_free: (a: number, b: number) => void;
export const __wbg_optimizationengine_free: (a: number, b: number) => void;
export const __wbg_pikey_free: (a: number, b: number) => void;
export const __wbg_qdagledger_free: (a: number, b: number) => void;
export const __wbg_quarantinemanager_free: (a: number, b: number) => void;
export const __wbg_raceconomicengine_free: (a: number, b: number) => void;
export const __wbg_racsemanticrouter_free: (a: number, b: number) => void;
export const __wbg_ratelimiter_free: (a: number, b: number) => void;
export const __wbg_reasoningbank_free: (a: number, b: number) => void;
export const __wbg_reputationmanager_free: (a: number, b: number) => void;
export const __wbg_reputationsystem_free: (a: number, b: number) => void;
export const __wbg_rewarddistribution_free: (a: number, b: number) => void;
export const __wbg_rewardmanager_free: (a: number, b: number) => void;
export const __wbg_semanticrouter_free: (a: number, b: number) => void;
export const __wbg_sessionkey_free: (a: number, b: number) => void;
export const __wbg_set_economichealth_growth_rate: (a: number, b: number) => void;
export const __wbg_set_economichealth_stability: (a: number, b: number) => void;
export const __wbg_set_economichealth_utilization: (a: number, b: number) => void;
export const __wbg_set_economichealth_velocity: (a: number, b: number) => void;
export const __wbg_set_nodeconfig_bandwidth_limit: (a: number, b: number) => void;
export const __wbg_set_nodeconfig_memory_limit: (a: number, b: number) => void;
export const __wbg_set_nodeconfig_min_idle_time: (a: number, b: number) => void;
export const __wbg_set_nodeconfig_respect_battery: (a: number, b: number) => void;
export const __wbg_set_nodestats_celebration_boost: (a: number, b: number) => void;
export const __wbg_set_nodestats_multiplier: (a: number, b: number) => void;
export const __wbg_set_nodestats_reputation: (a: number, b: number) => void;
export const __wbg_set_nodestats_ruv_earned: (a: number, b: bigint) => void;
export const __wbg_set_nodestats_ruv_spent: (a: number, b: bigint) => void;
export const __wbg_set_nodestats_tasks_completed: (a: number, b: bigint) => void;
export const __wbg_set_nodestats_tasks_submitted: (a: number, b: bigint) => void;
export const __wbg_set_nodestats_uptime_seconds: (a: number, b: bigint) => void;
export const __wbg_spikedrivenattention_free: (a: number, b: number) => void;
export const __wbg_spotchecker_free: (a: number, b: number) => void;
export const __wbg_stakemanager_free: (a: number, b: number) => void;
export const __wbg_swarmintelligence_free: (a: number, b: number) => void;
export const __wbg_sybildefense_free: (a: number, b: number) => void;
export const __wbg_topksparsifier_free: (a: number, b: number) => void;
export const __wbg_trajectorytracker_free: (a: number, b: number) => void;
export const __wbg_wasmadapterpool_free: (a: number, b: number) => void;
export const __wbg_wasmcapabilities_free: (a: number, b: number) => void;
export const __wbg_wasmcreditledger_free: (a: number, b: number) => void;
export const __wbg_wasmidledetector_free: (a: number, b: number) => void;
export const __wbg_wasmmcpbroadcast_free: (a: number, b: number) => void;
export const __wbg_wasmmcpserver_free: (a: number, b: number) => void;
export const __wbg_wasmmcptransport_free: (a: number, b: number) => void;
export const __wbg_wasmmcpworkerhandler_free: (a: number, b: number) => void;
export const __wbg_wasmnetworkmanager_free: (a: number, b: number) => void;
export const __wbg_wasmnodeidentity_free: (a: number, b: number) => void;
export const __wbg_wasmstigmergy_free: (a: number, b: number) => void;
export const __wbg_wasmtaskexecutor_free: (a: number, b: number) => void;
export const __wbg_wasmtaskqueue_free: (a: number, b: number) => void;
export const __wbg_witnesstracker_free: (a: number, b: number) => void;
export const adaptivesecurity_chooseAction: (a: number, b: number, c: number, d: number, e: number) => [number, number];
export const adaptivesecurity_detectAttack: (a: number, b: number, c: number) => number;
export const adaptivesecurity_exportPatterns: (a: number) => [number, number, number, number];
export const adaptivesecurity_getMinReputation: (a: number) => number;
export const adaptivesecurity_getRateLimitMax: (a: number) => number;
export const adaptivesecurity_getRateLimitWindow: (a: number) => bigint;
export const adaptivesecurity_getSecurityLevel: (a: number) => number;
export const adaptivesecurity_getSpotCheckProbability: (a: number) => number;
export const adaptivesecurity_getStats: (a: number) => [number, number];
export const adaptivesecurity_importPatterns: (a: number, b: number, c: number) => [number, number];
export const adaptivesecurity_learn: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number) => void;
export const adaptivesecurity_new: () => number;
export const adaptivesecurity_recordAttackPattern: (a: number, b: number, c: number, d: number, e: number, f: number) => void;
export const adaptivesecurity_updateNetworkHealth: (a: number, b: number, c: number, d: number, e: number, f: number) => void;
export const adversarialsimulator_enableChaosMode: (a: number, b: number) => void;
export const adversarialsimulator_generateChaosEvent: (a: number) => [number, number];
export const adversarialsimulator_getDefenceMetrics: (a: number) => [number, number];
export const adversarialsimulator_getRecommendations: (a: number) => [number, number];
export const adversarialsimulator_new: () => number;
export const adversarialsimulator_runSecurityAudit: (a: number) => [number, number];
export const adversarialsimulator_simulateByzantine: (a: number, b: number, c: number) => [number, number];
export const adversarialsimulator_simulateDDoS: (a: number, b: number, c: bigint) => [number, number];
export const adversarialsimulator_simulateDoubleSpend: (a: number, b: bigint, c: number) => [number, number];
export const adversarialsimulator_simulateFreeRiding: (a: number, b: number, c: number) => [number, number];
export const adversarialsimulator_simulateResultTampering: (a: number, b: number) => [number, number];
export const adversarialsimulator_simulateSybil: (a: number, b: number, c: number) => [number, number];
export const auditlog_exportEvents: (a: number) => [number, number];
export const auditlog_getEventsBySeverity: (a: number, b: number) => number;
export const auditlog_getEventsForNode: (a: number, b: number, c: number) => number;
export const auditlog_log: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number) => void;
export const auditlog_new: () => number;
export const browserfingerprint_generate: () => any;
export const byzantinedetector_getMaxMagnitude: (a: number) => number;
export const byzantinedetector_new: (a: number, b: number) => number;
export const coherenceengine_canUseClaim: (a: number, b: number, c: number) => number;
export const coherenceengine_conflictCount: (a: number) => number;
export const coherenceengine_eventCount: (a: number) => number;
export const coherenceengine_getDrift: (a: number, b: number, c: number) => number;
export const coherenceengine_getMerkleRoot: (a: number) => [number, number];
export const coherenceengine_getQuarantineLevel: (a: number, b: number, c: number) => number;
export const coherenceengine_getStats: (a: number) => [number, number];
export const coherenceengine_hasDrifted: (a: number, b: number, c: number) => number;
export const coherenceengine_hasSufficientWitnesses: (a: number, b: number, c: number) => number;
export const coherenceengine_new: () => number;
export const coherenceengine_quarantinedCount: (a: number) => number;
export const coherenceengine_witnessCount: (a: number, b: number, c: number) => number;
export const collectivememory_consolidate: (a: number) => number;
export const collectivememory_getStats: (a: number) => [number, number];
export const collectivememory_hasPattern: (a: number, b: number, c: number) => number;
export const collectivememory_new: (a: number, b: number) => number;
export const collectivememory_patternCount: (a: number) => number;
export const collectivememory_queueSize: (a: number) => number;
export const collectivememory_search: (a: number, b: number, c: number, d: number) => [number, number];
export const contributionstream_getTotalDistributed: (a: number) => bigint;
export const contributionstream_isHealthy: (a: number) => number;
export const contributionstream_new: () => number;
export const contributionstream_processFees: (a: number, b: bigint, c: bigint) => bigint;
export const differentialprivacy_getEpsilon: (a: number) => number;
export const differentialprivacy_isEnabled: (a: number) => number;
export const differentialprivacy_new: (a: number, b: number) => number;
export const differentialprivacy_setEnabled: (a: number, b: number) => void;
export const drifttracker_getDrift: (a: number, b: number, c: number) => number;
export const drifttracker_getDriftedContexts: (a: number) => [number, number];
export const drifttracker_hasDrifted: (a: number, b: number, c: number) => number;
export const drifttracker_new: (a: number) => number;
export const economicengine_advanceEpoch: (a: number) => void;
export const economicengine_getHealth: (a: number) => number;
export const economicengine_getProtocolFund: (a: number) => bigint;
export const economicengine_getTreasury: (a: number) => bigint;
export const economicengine_isSelfSustaining: (a: number, b: number, c: bigint) => number;
export const economicengine_new: () => number;
export const economicengine_processReward: (a: number, b: bigint, c: number) => number;
export const edgenetconfig_addRelay: (a: number, b: number, c: number) => number;
export const edgenetconfig_build: (a: number) => [number, number, number];
export const edgenetconfig_cpuLimit: (a: number, b: number) => number;
export const edgenetconfig_memoryLimit: (a: number, b: number) => number;
export const edgenetconfig_minIdleTime: (a: number, b: number) => number;
export const edgenetconfig_new: (a: number, b: number) => number;
export const edgenetconfig_respectBattery: (a: number, b: number) => number;
export const edgenetnode_canUseClaim: (a: number, b: number, c: number) => number;
export const edgenetnode_checkEvents: (a: number) => [number, number];
export const edgenetnode_creditBalance: (a: number) => bigint;
export const edgenetnode_disconnect: (a: number) => [number, number];
export const edgenetnode_enableBTSP: (a: number, b: number) => number;
export const edgenetnode_enableHDC: (a: number) => number;
export const edgenetnode_enableNAO: (a: number, b: number) => number;
export const edgenetnode_getCapabilities: (a: number) => any;
export const edgenetnode_getCapabilitiesSummary: (a: number) => any;
export const edgenetnode_getClaimQuarantineLevel: (a: number, b: number, c: number) => number;
export const edgenetnode_getCoherenceEventCount: (a: number) => number;
export const edgenetnode_getCoherenceStats: (a: number) => [number, number];
export const edgenetnode_getConflictCount: (a: number) => number;
export const edgenetnode_getEconomicHealth: (a: number) => [number, number];
export const edgenetnode_getEnergyEfficiency: (a: number, b: number, c: number) => number;
export const edgenetnode_getFounderCount: (a: number) => number;
export const edgenetnode_getLearningStats: (a: number) => [number, number];
export const edgenetnode_getMerkleRoot: (a: number) => [number, number];
export const edgenetnode_getMotivation: (a: number) => [number, number];
export const edgenetnode_getMultiplier: (a: number) => number;
export const edgenetnode_getNetworkFitness: (a: number) => number;
export const edgenetnode_getOptimalPeers: (a: number, b: number) => [number, number];
export const edgenetnode_getOptimizationStats: (a: number) => [number, number];
export const edgenetnode_getPatternCount: (a: number) => number;
export const edgenetnode_getProtocolFund: (a: number) => bigint;
export const edgenetnode_getQuarantinedCount: (a: number) => number;
export const edgenetnode_getRecommendedConfig: (a: number) => [number, number];
export const edgenetnode_getStats: (a: number) => number;
export const edgenetnode_getThemedStatus: (a: number, b: number) => [number, number];
export const edgenetnode_getThrottle: (a: number) => number;
export const edgenetnode_getTimeCrystalSync: (a: number) => number;
export const edgenetnode_getTrajectoryCount: (a: number) => number;
export const edgenetnode_getTreasury: (a: number) => bigint;
export const edgenetnode_isIdle: (a: number) => number;
export const edgenetnode_isSelfSustaining: (a: number, b: number, c: bigint) => number;
export const edgenetnode_isStreamHealthy: (a: number) => number;
export const edgenetnode_lookupPatterns: (a: number, b: number, c: number, d: number) => [number, number];
export const edgenetnode_new: (a: number, b: number, c: number) => [number, number, number];
export const edgenetnode_nodeId: (a: number) => [number, number];
export const edgenetnode_pause: (a: number) => void;
export const edgenetnode_processEpoch: (a: number) => void;
export const edgenetnode_processNextTask: (a: number) => any;
export const edgenetnode_proposeNAO: (a: number, b: number, c: number) => [number, number];
export const edgenetnode_prunePatterns: (a: number, b: number, c: number) => number;
export const edgenetnode_recordLearningTrajectory: (a: number, b: number, c: number) => number;
export const edgenetnode_recordPeerInteraction: (a: number, b: number, c: number, d: number) => void;
export const edgenetnode_recordPerformance: (a: number, b: number, c: number) => void;
export const edgenetnode_recordTaskRouting: (a: number, b: number, c: number, d: number, e: number, f: bigint, g: number) => void;
export const edgenetnode_resume: (a: number) => void;
export const edgenetnode_runSecurityAudit: (a: number) => [number, number];
export const edgenetnode_shouldReplicate: (a: number) => number;
export const edgenetnode_start: (a: number) => [number, number];
export const edgenetnode_stepCapabilities: (a: number, b: number) => void;
export const edgenetnode_storePattern: (a: number, b: number, c: number) => number;
export const edgenetnode_submitTask: (a: number, b: number, c: number, d: number, e: number, f: bigint) => any;
export const edgenetnode_voteNAO: (a: number, b: number, c: number, d: number) => number;
export const entropyconsensus_converged: (a: number) => number;
export const entropyconsensus_entropy: (a: number) => number;
export const entropyconsensus_finalize_beliefs: (a: number) => void;
export const entropyconsensus_getBelief: (a: number, b: bigint) => number;
export const entropyconsensus_getDecision: (a: number) => [number, bigint];
export const entropyconsensus_getEntropyHistory: (a: number) => [number, number];
export const entropyconsensus_getEntropyThreshold: (a: number) => number;
export const entropyconsensus_getRounds: (a: number) => number;
export const entropyconsensus_getStats: (a: number) => [number, number];
export const entropyconsensus_getTemperature: (a: number) => number;
export const entropyconsensus_hasTimedOut: (a: number) => number;
export const entropyconsensus_new: () => number;
export const entropyconsensus_optionCount: (a: number) => number;
export const entropyconsensus_reset: (a: number) => void;
export const entropyconsensus_setBelief: (a: number, b: bigint, c: number) => void;
export const entropyconsensus_set_belief_raw: (a: number, b: bigint, c: number) => void;
export const entropyconsensus_withThreshold: (a: number) => number;
export const eventlog_getRoot: (a: number) => [number, number];
export const eventlog_isEmpty: (a: number) => number;
export const eventlog_len: (a: number) => number;
export const eventlog_new: () => number;
export const evolutionengine_evolve: (a: number) => void;
export const evolutionengine_getNetworkFitness: (a: number) => number;
export const evolutionengine_getRecommendedConfig: (a: number) => [number, number];
export const evolutionengine_new: () => number;
export const evolutionengine_recordPerformance: (a: number, b: number, c: number, d: number, e: number) => void;
export const evolutionengine_shouldReplicate: (a: number, b: number, c: number) => number;
export const federatedmodel_applyGradients: (a: number, b: number, c: number) => [number, number];
export const federatedmodel_getDimension: (a: number) => number;
export const federatedmodel_getParameters: (a: number) => [number, number];
export const federatedmodel_getRound: (a: number) => bigint;
export const federatedmodel_new: (a: number, b: number, c: number) => number;
export const federatedmodel_setLearningRate: (a: number, b: number) => void;
export const federatedmodel_setLocalEpochs: (a: number, b: number) => void;
export const federatedmodel_setParameters: (a: number, b: number, c: number) => [number, number];
export const foundingregistry_calculateVested: (a: number, b: bigint, c: bigint) => bigint;
export const foundingregistry_getFounderCount: (a: number) => number;
export const foundingregistry_new: () => number;
export const foundingregistry_processEpoch: (a: number, b: bigint, c: bigint) => [number, number];
export const foundingregistry_registerContributor: (a: number, b: number, c: number, d: number, e: number, f: number) => void;
export const genesiskey_create: (a: number, b: number) => [number, number, number];
export const genesiskey_exportUltraCompact: (a: number) => [number, number];
export const genesiskey_getEpoch: (a: number) => number;
export const genesiskey_getIdHex: (a: number) => [number, number];
export const genesiskey_verify: (a: number, b: number, c: number) => number;
export const genesissunset_canRetire: (a: number) => number;
export const genesissunset_getCurrentPhase: (a: number) => number;
export const genesissunset_getStatus: (a: number) => [number, number];
export const genesissunset_isReadOnly: (a: number) => number;
export const genesissunset_new: () => number;
export const genesissunset_registerGenesisNode: (a: number, b: number, c: number) => void;
export const genesissunset_shouldAcceptConnections: (a: number) => number;
export const genesissunset_updateNodeCount: (a: number, b: number) => number;
export const gradientgossip_advanceRound: (a: number) => bigint;
export const gradientgossip_configureDifferentialPrivacy: (a: number, b: number, c: number) => void;
export const gradientgossip_getAggregatedGradients: (a: number) => [number, number];
export const gradientgossip_getCompressionRatio: (a: number) => number;
export const gradientgossip_getCurrentRound: (a: number) => bigint;
export const gradientgossip_getDimension: (a: number) => number;
export const gradientgossip_getStats: (a: number) => [number, number];
export const gradientgossip_new: (a: number, b: number, c: number, d: number) => [number, number, number];
export const gradientgossip_peerCount: (a: number) => number;
export const gradientgossip_pruneStale: (a: number) => number;
export const gradientgossip_setDPEnabled: (a: number, b: number) => void;
export const gradientgossip_setLocalGradients: (a: number, b: number, c: number) => [number, number];
export const gradientgossip_setModelHash: (a: number, b: number, c: number) => [number, number];
export const init_panic_hook: () => void;
export const modelconsensusmanager_disputeCount: (a: number) => number;
export const modelconsensusmanager_getStats: (a: number) => [number, number];
export const modelconsensusmanager_modelCount: (a: number) => number;
export const modelconsensusmanager_new: (a: number) => number;
export const modelconsensusmanager_quarantinedUpdateCount: (a: number) => number;
export const multiheadattention_dim: (a: number) => number;
export const multiheadattention_new: (a: number, b: number) => number;
export const multiheadattention_numHeads: (a: number) => number;
export const networkevents_checkActiveEvents: (a: number) => [number, number];
export const networkevents_checkDiscovery: (a: number, b: number, c: number, d: number, e: number) => [number, number];
export const networkevents_checkMilestones: (a: number, b: bigint, c: number, d: number) => [number, number];
export const networkevents_getCelebrationBoost: (a: number) => number;
export const networkevents_getMotivation: (a: number, b: bigint) => [number, number];
export const networkevents_getSpecialArt: (a: number) => [number, number];
export const networkevents_getThemedStatus: (a: number, b: number, c: bigint) => [number, number];
export const networkevents_new: () => number;
export const networkevents_setCurrentTime: (a: number, b: bigint) => void;
export const networklearning_getEnergyRatio: (a: number, b: number, c: number) => number;
export const networklearning_getStats: (a: number) => [number, number];
export const networklearning_lookupPatterns: (a: number, b: number, c: number, d: number) => [number, number];
export const networklearning_new: () => number;
export const networklearning_patternCount: (a: number) => number;
export const networklearning_prune: (a: number, b: number, c: number) => number;
export const networklearning_recordTrajectory: (a: number, b: number, c: number) => number;
export const networklearning_storePattern: (a: number, b: number, c: number) => number;
export const networklearning_trajectoryCount: (a: number) => number;
export const networktopology_getOptimalPeers: (a: number, b: number, c: number, d: number) => [number, number];
export const networktopology_new: () => number;
export const networktopology_registerNode: (a: number, b: number, c: number, d: number, e: number) => void;
export const networktopology_updateConnection: (a: number, b: number, c: number, d: number, e: number, f: number) => void;
export const optimizationengine_getStats: (a: number) => [number, number];
export const optimizationengine_new: () => number;
export const optimizationengine_recordRouting: (a: number, b: number, c: number, d: number, e: number, f: bigint, g: number) => void;
export const optimizationengine_selectOptimalNode: (a: number, b: number, c: number, d: number, e: number) => [number, number];
export const pikey_createEncryptedBackup: (a: number, b: number, c: number) => [number, number, number, number];
export const pikey_exportCompact: (a: number) => [number, number];
export const pikey_generate: (a: number, b: number) => [number, number, number];
export const pikey_getGenesisFingerprint: (a: number) => [number, number];
export const pikey_getIdentity: (a: number) => [number, number];
export const pikey_getIdentityHex: (a: number) => [number, number];
export const pikey_getPublicKey: (a: number) => [number, number];
export const pikey_getShortId: (a: number) => [number, number];
export const pikey_getStats: (a: number) => [number, number];
export const pikey_restoreFromBackup: (a: number, b: number, c: number, d: number) => [number, number, number];
export const pikey_sign: (a: number, b: number, c: number) => [number, number];
export const pikey_verify: (a: number, b: number, c: number, d: number, e: number, f: number, g: number) => number;
export const pikey_verifyPiMagic: (a: number) => number;
export const qdagledger_balance: (a: number, b: number, c: number) => bigint;
export const qdagledger_createGenesis: (a: number, b: bigint, c: number, d: number) => [number, number, number, number];
export const qdagledger_createTransaction: (a: number, b: number, c: number, d: number, e: number, f: bigint, g: number, h: number, i: number, j: number, k: number) => [number, number, number, number];
export const qdagledger_exportState: (a: number) => [number, number, number, number];
export const qdagledger_importState: (a: number, b: number, c: number) => [number, number, number];
export const qdagledger_new: () => number;
export const qdagledger_stakedAmount: (a: number, b: number, c: number) => bigint;
export const qdagledger_tipCount: (a: number) => number;
export const qdagledger_totalSupply: (a: number) => bigint;
export const qdagledger_transactionCount: (a: number) => number;
export const quarantinemanager_canUse: (a: number, b: number, c: number) => number;
export const quarantinemanager_getLevel: (a: number, b: number, c: number) => number;
export const quarantinemanager_new: () => number;
export const quarantinemanager_quarantinedCount: (a: number) => number;
export const quarantinemanager_setLevel: (a: number, b: number, c: number, d: number) => void;
export const raceconomicengine_canParticipate: (a: number, b: number, c: number) => number;
export const raceconomicengine_getCombinedScore: (a: number, b: number, c: number) => number;
export const raceconomicengine_getSummary: (a: number) => [number, number];
export const raceconomicengine_new: () => number;
export const racsemanticrouter_new: () => number;
export const racsemanticrouter_peerCount: (a: number) => number;
export const ratelimiter_checkAllowed: (a: number, b: number, c: number) => number;
export const ratelimiter_getCount: (a: number, b: number, c: number) => number;
export const ratelimiter_new: (a: bigint, b: number) => number;
export const ratelimiter_reset: (a: number) => void;
export const reasoningbank_count: (a: number) => number;
export const reasoningbank_getStats: (a: number) => [number, number];
export const reasoningbank_lookup: (a: number, b: number, c: number, d: number) => [number, number];
export const reasoningbank_new: () => number;
export const reasoningbank_prune: (a: number, b: number, c: number) => number;
export const reasoningbank_store: (a: number, b: number, c: number) => number;
export const reputationmanager_averageReputation: (a: number) => number;
export const reputationmanager_getReputation: (a: number, b: number, c: number) => number;
export const reputationmanager_hasSufficientReputation: (a: number, b: number, c: number) => number;
export const reputationmanager_new: (a: number, b: bigint) => number;
export const reputationmanager_nodeCount: (a: number) => number;
export const reputationsystem_canParticipate: (a: number, b: number, c: number) => number;
export const reputationsystem_getReputation: (a: number, b: number, c: number) => number;
export const reputationsystem_new: () => number;
export const reputationsystem_recordFailure: (a: number, b: number, c: number) => void;
export const reputationsystem_recordPenalty: (a: number, b: number, c: number, d: number) => void;
export const reputationsystem_recordSuccess: (a: number, b: number, c: number) => void;
export const rewardmanager_claimableAmount: (a: number, b: number, c: number) => bigint;
export const rewardmanager_new: (a: bigint) => number;
export const rewardmanager_pendingAmount: (a: number) => bigint;
export const rewardmanager_pendingCount: (a: number) => number;
export const semanticrouter_activePeerCount: (a: number) => number;
export const semanticrouter_getStats: (a: number) => [number, number];
export const semanticrouter_new: () => number;
export const semanticrouter_peerCount: (a: number) => number;
export const semanticrouter_setMyCapabilities: (a: number, b: number, c: number) => void;
export const semanticrouter_setMyPeerId: (a: number, b: number, c: number) => void;
export const semanticrouter_topicCount: (a: number) => number;
export const semanticrouter_withParams: (a: number, b: number, c: number) => number;
export const sessionkey_create: (a: number, b: number) => [number, number, number];
export const sessionkey_decrypt: (a: number, b: number, c: number) => [number, number, number, number];
export const sessionkey_encrypt: (a: number, b: number, c: number) => [number, number, number, number];
export const sessionkey_getId: (a: number) => [number, number];
export const sessionkey_getIdHex: (a: number) => [number, number];
export const sessionkey_getParentIdentity: (a: number) => [number, number];
export const sessionkey_isExpired: (a: number) => number;
export const spikedrivenattention_energyRatio: (a: number, b: number, c: number) => number;
export const spikedrivenattention_new: () => number;
export const spikedrivenattention_withConfig: (a: number, b: number, c: number) => number;
export const spotchecker_addChallenge: (a: number, b: number, c: number, d: number, e: number, f: number, g: number) => void;
export const spotchecker_getChallenge: (a: number, b: number, c: number) => [number, number];
export const spotchecker_new: (a: number) => number;
export const spotchecker_shouldCheck: (a: number) => number;
export const spotchecker_verifyResponse: (a: number, b: number, c: number, d: number, e: number) => number;
export const stakemanager_getMinStake: (a: number) => bigint;
export const stakemanager_getStake: (a: number, b: number, c: number) => bigint;
export const stakemanager_hasSufficientStake: (a: number, b: number, c: number) => number;
export const stakemanager_new: (a: bigint) => number;
export const stakemanager_stakerCount: (a: number) => number;
export const stakemanager_totalStaked: (a: number) => bigint;
export const swarmintelligence_addPattern: (a: number, b: number, c: number) => number;
export const swarmintelligence_consolidate: (a: number) => number;
export const swarmintelligence_getConsensusDecision: (a: number, b: number, c: number) => [number, bigint];
export const swarmintelligence_getStats: (a: number) => [number, number];
export const swarmintelligence_hasConsensus: (a: number, b: number, c: number) => number;
export const swarmintelligence_negotiateBeliefs: (a: number, b: number, c: number, d: number, e: number) => number;
export const swarmintelligence_new: (a: number, b: number) => number;
export const swarmintelligence_nodeId: (a: number) => [number, number];
export const swarmintelligence_patternCount: (a: number) => number;
export const swarmintelligence_queueSize: (a: number) => number;
export const swarmintelligence_replay: (a: number) => number;
export const swarmintelligence_searchPatterns: (a: number, b: number, c: number, d: number) => [number, number];
export const swarmintelligence_setBelief: (a: number, b: number, c: number, d: bigint, e: number) => void;
export const swarmintelligence_startConsensus: (a: number, b: number, c: number, d: number) => void;
export const sybildefense_getSybilScore: (a: number, b: number, c: number) => number;
export const sybildefense_isSuspectedSybil: (a: number, b: number, c: number) => number;
export const sybildefense_new: () => number;
export const sybildefense_registerNode: (a: number, b: number, c: number, d: number, e: number) => number;
export const topksparsifier_getCompressionRatio: (a: number) => number;
export const topksparsifier_getErrorBufferSize: (a: number) => number;
export const topksparsifier_new: (a: number) => number;
export const topksparsifier_resetErrorFeedback: (a: number) => void;
export const trajectorytracker_count: (a: number) => number;
export const trajectorytracker_getStats: (a: number) => [number, number];
export const trajectorytracker_new: (a: number) => number;
export const trajectorytracker_record: (a: number, b: number, c: number) => number;
export const wasmadapterpool_adapterCount: (a: number) => number;
export const wasmadapterpool_exportAdapter: (a: number, b: number, c: number) => [number, number];
export const wasmadapterpool_forward: (a: number, b: number, c: number, d: number, e: number) => [number, number];
export const wasmadapterpool_getAdapter: (a: number, b: number, c: number) => any;
export const wasmadapterpool_getStats: (a: number) => any;
export const wasmadapterpool_importAdapter: (a: number, b: number, c: number, d: number, e: number) => number;
export const wasmadapterpool_new: (a: number, b: number) => number;
export const wasmadapterpool_routeToAdapter: (a: number, b: number, c: number) => any;
export const wasmcapabilities_adaptMicroLoRA: (a: number, b: number, c: number, d: number, e: number) => number;
export const wasmcapabilities_addNAOMember: (a: number, b: number, c: number, d: bigint) => number;
export const wasmcapabilities_applyMicroLoRA: (a: number, b: number, c: number, d: number, e: number) => [number, number];
export const wasmcapabilities_broadcastToWorkspace: (a: number, b: number, c: number, d: number, e: number) => number;
export const wasmcapabilities_competeWTA: (a: number, b: number, c: number) => number;
export const wasmcapabilities_differentiateMorphogenetic: (a: number) => void;
export const wasmcapabilities_enableBTSP: (a: number, b: number, c: number) => number;
export const wasmcapabilities_enableGlobalWorkspace: (a: number, b: number) => number;
export const wasmcapabilities_enableHDC: (a: number) => number;
export const wasmcapabilities_enableMicroLoRA: (a: number, b: number, c: number) => number;
export const wasmcapabilities_enableNAO: (a: number, b: number) => number;
export const wasmcapabilities_enableWTA: (a: number, b: number, c: number, d: number) => number;
export const wasmcapabilities_executeNAO: (a: number, b: number, c: number) => number;
export const wasmcapabilities_forwardBTSP: (a: number, b: number, c: number) => number;
export const wasmcapabilities_getCapabilities: (a: number) => any;
export const wasmcapabilities_getMorphogeneticCellCount: (a: number) => number;
export const wasmcapabilities_getMorphogeneticStats: (a: number) => any;
export const wasmcapabilities_getNAOSync: (a: number) => number;
export const wasmcapabilities_getSummary: (a: number) => any;
export const wasmcapabilities_growMorphogenetic: (a: number, b: number) => void;
export const wasmcapabilities_new: (a: number, b: number) => number;
export const wasmcapabilities_oneShotAssociate: (a: number, b: number, c: number, d: number) => number;
export const wasmcapabilities_proposeNAO: (a: number, b: number, c: number) => [number, number];
export const wasmcapabilities_retrieveHDC: (a: number, b: number, c: number, d: number) => any;
export const wasmcapabilities_tickTimeCrystal: (a: number) => any;
export const wasmcapabilities_voteNAO: (a: number, b: number, c: number, d: number) => number;
export const wasmcreditledger_balance: (a: number) => bigint;
export const wasmcreditledger_credit: (a: number, b: bigint, c: number, d: number) => [number, number];
export const wasmcreditledger_currentMultiplier: (a: number) => number;
export const wasmcreditledger_deduct: (a: number, b: bigint) => [number, number];
export const wasmcreditledger_exportEarned: (a: number) => [number, number, number, number];
export const wasmcreditledger_exportSpent: (a: number) => [number, number, number, number];
export const wasmcreditledger_merge: (a: number, b: number, c: number, d: number, e: number) => [number, number];
export const wasmcreditledger_networkCompute: (a: number) => number;
export const wasmcreditledger_new: (a: number, b: number) => [number, number, number];
export const wasmcreditledger_slash: (a: number, b: bigint) => [bigint, number, number];
export const wasmcreditledger_stake: (a: number, b: bigint) => [number, number];
export const wasmcreditledger_stakedAmount: (a: number) => bigint;
export const wasmcreditledger_totalEarned: (a: number) => bigint;
export const wasmcreditledger_totalSpent: (a: number) => bigint;
export const wasmcreditledger_unstake: (a: number, b: bigint) => [number, number];
export const wasmcreditledger_updateNetworkCompute: (a: number, b: number) => void;
export const wasmidledetector_getStatus: (a: number) => any;
export const wasmidledetector_getThrottle: (a: number) => number;
export const wasmidledetector_isIdle: (a: number) => number;
export const wasmidledetector_new: (a: number, b: number) => [number, number, number];
export const wasmidledetector_pause: (a: number) => void;
export const wasmidledetector_recordInteraction: (a: number) => void;
export const wasmidledetector_resume: (a: number) => void;
export const wasmidledetector_setBatteryStatus: (a: number, b: number) => void;
export const wasmidledetector_shouldWork: (a: number) => number;
export const wasmidledetector_start: (a: number) => [number, number];
export const wasmidledetector_stop: (a: number) => void;
export const wasmidledetector_updateFps: (a: number, b: number) => void;
export const wasmmcpbroadcast_close: (a: number) => void;
export const wasmmcpbroadcast_listen: (a: number) => [number, number];
export const wasmmcpbroadcast_new: (a: number, b: number) => [number, number, number];
export const wasmmcpbroadcast_send: (a: number, b: number, c: number) => [number, number];
export const wasmmcpbroadcast_setServer: (a: number, b: number) => void;
export const wasmmcpserver_getServerInfo: (a: number) => any;
export const wasmmcpserver_handleRequest: (a: number, b: number, c: number) => any;
export const wasmmcpserver_handleRequestJs: (a: number, b: any) => any;
export const wasmmcpserver_initLearning: (a: number) => [number, number];
export const wasmmcpserver_new: () => [number, number, number];
export const wasmmcpserver_setIdentity: (a: number, b: number) => void;
export const wasmmcpserver_withConfig: (a: any) => [number, number, number];
export const wasmmcptransport_close: (a: number) => void;
export const wasmmcptransport_fromPort: (a: any) => number;
export const wasmmcptransport_init: (a: number) => [number, number];
export const wasmmcptransport_new: (a: any) => [number, number, number];
export const wasmmcptransport_send: (a: number, b: any) => any;
export const wasmmcpworkerhandler_new: (a: number) => number;
export const wasmmcpworkerhandler_start: (a: number) => [number, number];
export const wasmnetworkmanager_activePeerCount: (a: number) => number;
export const wasmnetworkmanager_addRelay: (a: number, b: number, c: number) => void;
export const wasmnetworkmanager_getPeersWithCapability: (a: number, b: number, c: number) => [number, number];
export const wasmnetworkmanager_isConnected: (a: number) => number;
export const wasmnetworkmanager_new: (a: number, b: number) => number;
export const wasmnetworkmanager_peerCount: (a: number) => number;
export const wasmnetworkmanager_registerPeer: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: bigint) => void;
export const wasmnetworkmanager_selectWorkers: (a: number, b: number, c: number, d: number) => [number, number];
export const wasmnetworkmanager_updateReputation: (a: number, b: number, c: number, d: number) => void;
export const wasmnodeidentity_exportSecretKey: (a: number, b: number, c: number) => [number, number, number, number];
export const wasmnodeidentity_fromSecretKey: (a: number, b: number, c: number, d: number) => [number, number, number];
export const wasmnodeidentity_generate: (a: number, b: number) => [number, number, number];
export const wasmnodeidentity_getFingerprint: (a: number) => [number, number];
export const wasmnodeidentity_importSecretKey: (a: number, b: number, c: number, d: number, e: number, f: number) => [number, number, number];
export const wasmnodeidentity_nodeId: (a: number) => [number, number];
export const wasmnodeidentity_publicKeyBytes: (a: number) => [number, number];
export const wasmnodeidentity_publicKeyHex: (a: number) => [number, number];
export const wasmnodeidentity_setFingerprint: (a: number, b: number, c: number) => void;
export const wasmnodeidentity_sign: (a: number, b: number, c: number) => [number, number];
export const wasmnodeidentity_siteId: (a: number) => [number, number];
export const wasmnodeidentity_verify: (a: number, b: number, c: number, d: number, e: number) => number;
export const wasmnodeidentity_verifyFrom: (a: number, b: number, c: number, d: number, e: number, f: number) => number;
export const wasmstigmergy_deposit: (a: number, b: number, c: number, d: number, e: number, f: number, g: bigint) => void;
export const wasmstigmergy_depositWithOutcome: (a: number, b: number, c: number, d: number, e: number, f: number, g: bigint) => void;
export const wasmstigmergy_evaporate: (a: number) => void;
export const wasmstigmergy_exportState: (a: number) => [number, number];
export const wasmstigmergy_follow: (a: number, b: number, c: number) => number;
export const wasmstigmergy_getBestSpecialization: (a: number) => [number, number];
export const wasmstigmergy_getIntensity: (a: number, b: number, c: number) => number;
export const wasmstigmergy_getRankedTasks: (a: number) => [number, number];
export const wasmstigmergy_getSpecialization: (a: number, b: number, c: number) => number;
export const wasmstigmergy_getStats: (a: number) => [number, number];
export const wasmstigmergy_getSuccessRate: (a: number, b: number, c: number) => number;
export const wasmstigmergy_maybeEvaporate: (a: number) => number;
export const wasmstigmergy_merge: (a: number, b: number, c: number) => number;
export const wasmstigmergy_new: () => number;
export const wasmstigmergy_setMinStake: (a: number, b: bigint) => void;
export const wasmstigmergy_shouldAccept: (a: number, b: number, c: number) => number;
export const wasmstigmergy_updateSpecialization: (a: number, b: number, c: number, d: number) => void;
export const wasmstigmergy_withParams: (a: number, b: number, c: number) => number;
export const wasmtaskexecutor_new: (a: number) => [number, number, number];
export const wasmtaskexecutor_setTaskKey: (a: number, b: number, c: number) => [number, number];
export const wasmworkscheduler_new: () => number;
export const wasmworkscheduler_recordTaskDuration: (a: number, b: number) => void;
export const wasmworkscheduler_setPendingTasks: (a: number, b: number) => void;
export const wasmworkscheduler_tasksThisFrame: (a: number, b: number) => number;
export const witnesstracker_hasSufficientWitnesses: (a: number, b: number, c: number) => number;
export const witnesstracker_new: (a: number) => number;
export const witnesstracker_witnessConfidence: (a: number, b: number, c: number) => number;
export const witnesstracker_witnessCount: (a: number, b: number, c: number) => number;
export const wasmcapabilities_getTimeCrystalSync: (a: number) => number;
export const __wbg_set_nodeconfig_cpu_limit: (a: number, b: number) => void;
export const __wbg_set_rewarddistribution_contributor_share: (a: number, b: bigint) => void;
export const __wbg_set_rewarddistribution_founder_share: (a: number, b: bigint) => void;
export const __wbg_set_rewarddistribution_protocol_share: (a: number, b: bigint) => void;
export const __wbg_set_rewarddistribution_total: (a: number, b: bigint) => void;
export const __wbg_set_rewarddistribution_treasury_share: (a: number, b: bigint) => void;
export const genesissunset_isSelfSustaining: (a: number) => number;
export const edgenetnode_ruvBalance: (a: number) => bigint;
export const eventlog_totalEvents: (a: number) => number;
export const edgenetnode_enableGlobalWorkspace: (a: number, b: number) => number;
export const edgenetnode_enableMicroLoRA: (a: number, b: number) => number;
export const edgenetnode_enableMorphogenetic: (a: number, b: number) => number;
export const edgenetnode_enableTimeCrystal: (a: number, b: number) => number;
export const edgenetnode_enableWTA: (a: number, b: number) => number;
export const wasmcapabilities_pruneMorphogenetic: (a: number, b: number) => void;
export const wasmcapabilities_step: (a: number, b: number) => void;
export const wasmcapabilities_tickNAO: (a: number, b: number) => void;
export const wasmcapabilities_getWorkspaceContents: (a: number) => any;
export const wasmcapabilities_isTimeCrystalStable: (a: number) => number;
export const wasmcapabilities_storeHDC: (a: number, b: number, c: number) => number;
export const wasmcapabilities_enableMorphogenetic: (a: number, b: number, c: number) => number;
export const wasmcapabilities_enableTimeCrystal: (a: number, b: number, c: number) => number;
export const __wbg_get_nodeconfig_cpu_limit: (a: number) => number;
export const __wbg_get_rewarddistribution_contributor_share: (a: number) => bigint;
export const __wbg_get_rewarddistribution_founder_share: (a: number) => bigint;
export const __wbg_get_rewarddistribution_protocol_share: (a: number) => bigint;
export const __wbg_get_rewarddistribution_total: (a: number) => bigint;
export const __wbg_get_rewarddistribution_treasury_share: (a: number) => bigint;
export const __wbg_wasmworkscheduler_free: (a: number, b: number) => void;
export const __wbg_multiheadattention_free: (a: number, b: number) => void;
export const genesiskey_getId: (a: number) => [number, number];
export const wasm_bindgen__convert__closures_____invoke__h8c81ca6cba4eba00: (a: number, b: number, c: any) => void;
export const wasm_bindgen__closure__destroy__h16844f6554aa4052: (a: number, b: number) => void;
export const wasm_bindgen__convert__closures_____invoke__h9a454594a18d3e6f: (a: number, b: number, c: any) => void;
export const wasm_bindgen__closure__destroy__h5a0fd3a052925ed0: (a: number, b: number) => void;
export const wasm_bindgen__convert__closures_____invoke__h094c87b54a975e5a: (a: number, b: number, c: any, d: any) => void;
export const __wbindgen_malloc: (a: number, b: number) => number;
export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
export const __wbindgen_exn_store: (a: number) => void;
export const __externref_table_alloc: () => number;
export const __wbindgen_externrefs: WebAssembly.Table;
export const __wbindgen_free: (a: number, b: number, c: number) => void;
export const __externref_table_dealloc: (a: number) => void;
export const __externref_drop_slice: (a: number, b: number) => void;
export const __wbindgen_start: () => void;

View File

@@ -0,0 +1,595 @@
/**
* @ruvector/edge-net Secure Access Layer
*
* Uses WASM cryptographic primitives for secure network access.
* No external authentication needed - cryptographic proof of identity.
*
* Security Model:
* 1. Each node generates a PiKey (Ed25519-based) in WASM
* 2. All messages are signed with the node's private key
* 3. Other nodes verify signatures with public keys
* 4. AdaptiveSecurity provides self-learning attack detection
*
* @module @ruvector/edge-net/secure-access
*/
import { EventEmitter } from 'events';
/**
* Secure Access Manager
*
* Provides WASM-based cryptographic identity and message signing
* for secure P2P network access without external auth providers.
*/
export class SecureAccessManager extends EventEmitter {
constructor(options = {}) {
super();
/** @type {import('./ruvector_edge_net').PiKey|null} */
this.piKey = null;
/** @type {import('./ruvector_edge_net').SessionKey|null} */
this.sessionKey = null;
/** @type {import('./ruvector_edge_net').WasmNodeIdentity|null} */
this.nodeIdentity = null;
/** @type {import('./ruvector_edge_net').AdaptiveSecurity|null} */
this.security = null;
/** @type {Map<string, Uint8Array>} Known peer public keys */
this.knownPeers = new Map();
/** @type {Map<string, number>} Peer reputation scores */
this.peerReputation = new Map();
this.options = {
siteId: options.siteId || 'edge-net',
sessionTTL: options.sessionTTL || 3600, // 1 hour
backupPassword: options.backupPassword || null,
persistIdentity: options.persistIdentity !== false,
...options
};
this.wasm = null;
this.initialized = false;
}
/**
* Initialize secure access with WASM cryptography
*/
async initialize() {
if (this.initialized) return this;
console.log('🔐 Initializing WASM Secure Access...');
// Load WASM module
try {
// For Node.js, use the node-specific CJS module which auto-loads WASM
const isNode = typeof process !== 'undefined' && process.versions?.node;
if (isNode) {
// Node.js: CJS module loads WASM synchronously on import
this.wasm = await import('./node/ruvector_edge_net.cjs');
} else {
// Browser: Use ES module with WASM init
const wasmModule = await import('./ruvector_edge_net.js');
// Call default init to load WASM binary
if (wasmModule.default && typeof wasmModule.default === 'function') {
await wasmModule.default();
}
this.wasm = wasmModule;
}
} catch (err) {
console.error(' ❌ WASM load error:', err.message);
throw err;
}
// Try to restore existing identity
const restored = await this._tryRestoreIdentity();
if (!restored) {
// Generate new cryptographic identity
await this._generateIdentity();
}
// Initialize adaptive security
this.security = new this.wasm.AdaptiveSecurity();
// Create session key for encrypted communications
this.sessionKey = new this.wasm.SessionKey(this.piKey, this.options.sessionTTL);
this.initialized = true;
console.log(` 🔑 Node ID: ${this.getShortId()}`);
console.log(` 📦 Public Key: ${this.getPublicKeyHex().slice(0, 16)}...`);
console.log(` ⏱️ Session expires: ${new Date(Date.now() + this.options.sessionTTL * 1000).toISOString()}`);
this.emit('initialized', {
nodeId: this.getNodeId(),
publicKey: this.getPublicKeyHex()
});
return this;
}
/**
* Try to restore identity from localStorage or backup
*/
async _tryRestoreIdentity() {
if (!this.options.persistIdentity) return false;
try {
// Check localStorage (browser) or file (Node.js)
let stored = null;
if (typeof localStorage !== 'undefined') {
stored = localStorage.getItem('edge-net-identity');
} else if (typeof process !== 'undefined') {
const fs = await import('fs');
const path = await import('path');
const identityPath = path.join(process.cwd(), '.edge-net-identity');
if (fs.existsSync(identityPath)) {
stored = fs.readFileSync(identityPath, 'utf8');
}
}
if (stored) {
const data = JSON.parse(stored);
const encrypted = new Uint8Array(data.encrypted);
// Use default password if none provided
const password = this.options.backupPassword || 'edge-net-default-key';
this.piKey = this.wasm.PiKey.restoreFromBackup(encrypted, password);
this.nodeIdentity = this.wasm.WasmNodeIdentity.fromSecretKey(
encrypted, // Same key derivation
this.options.siteId
);
console.log(' ♻️ Restored existing identity');
return true;
}
} catch (err) {
console.log(' ⚡ Creating new identity (no backup found)');
}
return false;
}
/**
* Generate new cryptographic identity
*/
async _generateIdentity() {
// Generate Pi-Key (Ed25519-based with Pi magic)
// Constructor takes optional genesis_seed (Uint8Array or null)
const genesisSeed = this.options.genesisSeed || null;
this.piKey = new this.wasm.PiKey(genesisSeed);
// Create node identity from same site
this.nodeIdentity = new this.wasm.WasmNodeIdentity(this.options.siteId);
// Persist identity if enabled
if (this.options.persistIdentity) {
await this._persistIdentity();
}
console.log(' ✨ Generated new cryptographic identity');
}
/**
* Persist identity to storage
*/
async _persistIdentity() {
const password = this.options.backupPassword || 'edge-net-default-key';
const backup = this.piKey.createEncryptedBackup(password);
const data = JSON.stringify({
encrypted: Array.from(backup),
created: Date.now(),
siteId: this.options.siteId
});
try {
if (typeof localStorage !== 'undefined') {
localStorage.setItem('edge-net-identity', data);
} else if (typeof process !== 'undefined') {
const fs = await import('fs');
const path = await import('path');
const identityPath = path.join(process.cwd(), '.edge-net-identity');
fs.writeFileSync(identityPath, data);
}
} catch (err) {
console.warn(' ⚠️ Could not persist identity:', err.message);
}
}
// ============================================
// IDENTITY & KEYS
// ============================================
/**
* Get node ID (full)
*/
getNodeId() {
return this.piKey?.getIdentityHex() || this.nodeIdentity?.getId?.() || 'unknown';
}
/**
* Get short node ID for display
*/
getShortId() {
return this.piKey?.getShortId() || this.getNodeId().slice(0, 8);
}
/**
* Get public key as hex string
*/
getPublicKeyHex() {
return Array.from(this.piKey?.getPublicKey() || new Uint8Array(32))
.map(b => b.toString(16).padStart(2, '0'))
.join('');
}
/**
* Get public key as bytes
*/
getPublicKeyBytes() {
return this.piKey?.getPublicKey() || new Uint8Array(32);
}
// ============================================
// MESSAGE SIGNING & VERIFICATION
// ============================================
/**
* Sign a message/object
* @param {object|string|Uint8Array} message - Message to sign
* @returns {{ payload: string, signature: string, publicKey: string, timestamp: number }}
*/
signMessage(message) {
const payload = typeof message === 'string' ? message :
message instanceof Uint8Array ? new TextDecoder().decode(message) :
JSON.stringify(message);
const timestamp = Date.now();
const dataToSign = `${payload}|${timestamp}`;
const dataBytes = new TextEncoder().encode(dataToSign);
const signature = this.piKey.sign(dataBytes);
return {
payload,
signature: Array.from(signature).map(b => b.toString(16).padStart(2, '0')).join(''),
publicKey: this.getPublicKeyHex(),
timestamp,
nodeId: this.getShortId()
};
}
/**
* Verify a signed message
* @param {object} signed - Signed message object
* @returns {boolean} Whether signature is valid
*/
verifyMessage(signed) {
try {
const { payload, signature, publicKey, timestamp } = signed;
// Check timestamp (reject messages older than 5 minutes)
const age = Date.now() - timestamp;
if (age > 5 * 60 * 1000) {
console.warn('⚠️ Message too old:', age, 'ms');
return false;
}
// Convert hex strings back to bytes
const dataToVerify = `${payload}|${timestamp}`;
const dataBytes = new TextEncoder().encode(dataToVerify);
const sigBytes = new Uint8Array(signature.match(/.{2}/g).map(h => parseInt(h, 16)));
const pubKeyBytes = new Uint8Array(publicKey.match(/.{2}/g).map(h => parseInt(h, 16)));
// Verify using WASM
const valid = this.piKey.verify(dataBytes, sigBytes, pubKeyBytes);
// Update peer reputation based on verification
if (valid) {
this._updateReputation(signed.nodeId || publicKey.slice(0, 16), 0.01);
} else {
this._updateReputation(signed.nodeId || publicKey.slice(0, 16), -0.1);
this._recordSuspicious(signed.nodeId, 'invalid_signature');
}
return valid;
} catch (err) {
console.warn('⚠️ Signature verification error:', err.message);
return false;
}
}
// ============================================
// PEER MANAGEMENT
// ============================================
/**
* Register a known peer's public key
*/
registerPeer(peerId, publicKey) {
const pubKeyBytes = typeof publicKey === 'string' ?
new Uint8Array(publicKey.match(/.{2}/g).map(h => parseInt(h, 16))) :
publicKey;
this.knownPeers.set(peerId, pubKeyBytes);
this.peerReputation.set(peerId, this.peerReputation.get(peerId) || 0.5);
this.emit('peer-registered', { peerId, publicKey: this.getPublicKeyHex() });
}
/**
* Get reputation score for a peer (0-1)
*/
getPeerReputation(peerId) {
return this.peerReputation.get(peerId) || 0.5;
}
/**
* Update peer reputation
*/
_updateReputation(peerId, delta) {
const current = this.peerReputation.get(peerId) || 0.5;
const newScore = Math.max(0, Math.min(1, current + delta));
this.peerReputation.set(peerId, newScore);
// Emit warning if reputation drops too low
if (newScore < 0.2) {
this.emit('peer-suspicious', { peerId, reputation: newScore });
}
}
/**
* Record suspicious activity for learning
*/
_recordSuspicious(peerId, reason) {
if (this.security) {
// Record for adaptive security learning
const features = new Float32Array([
Date.now() / 1e12,
this.getPeerReputation(peerId),
reason === 'invalid_signature' ? 1 : 0,
reason === 'replay_attack' ? 1 : 0,
0, 0, 0, 0 // Padding
]);
this.security.recordAttackPattern(reason, features, 0.5);
}
}
// ============================================
// ENCRYPTION (SESSION-BASED)
// ============================================
/**
* Encrypt data for secure transmission
*/
encrypt(data) {
if (!this.sessionKey || this.sessionKey.isExpired()) {
// Refresh session key
this.sessionKey = new this.wasm.SessionKey(this.piKey, this.options.sessionTTL);
}
const dataBytes = typeof data === 'string' ?
new TextEncoder().encode(data) :
data instanceof Uint8Array ? data :
new TextEncoder().encode(JSON.stringify(data));
return this.sessionKey.encrypt(dataBytes);
}
/**
* Decrypt received data
*/
decrypt(encrypted) {
if (!this.sessionKey) {
throw new Error('No session key available');
}
return this.sessionKey.decrypt(encrypted);
}
// ============================================
// SECURITY ANALYSIS
// ============================================
/**
* Analyze request for potential attacks
* @returns {number} Threat score (0-1, higher = more suspicious)
*/
analyzeRequest(features) {
if (!this.security) return 0;
const featureArray = features instanceof Float32Array ?
features :
new Float32Array(Array.isArray(features) ? features : Object.values(features));
return this.security.detectAttack(featureArray);
}
/**
* Get security statistics
*/
getSecurityStats() {
if (!this.security) return null;
return JSON.parse(this.security.getStats());
}
/**
* Export security patterns for persistence
*/
exportSecurityPatterns() {
if (!this.security) return null;
return this.security.exportPatterns();
}
/**
* Import previously learned security patterns
*/
importSecurityPatterns(patterns) {
if (!this.security) return;
this.security.importPatterns(patterns);
}
// ============================================
// CHALLENGE-RESPONSE
// ============================================
/**
* Create a challenge for peer verification
*/
createChallenge() {
const challenge = crypto.getRandomValues(new Uint8Array(32));
const timestamp = Date.now();
return {
challenge: Array.from(challenge).map(b => b.toString(16).padStart(2, '0')).join(''),
timestamp,
issuer: this.getShortId()
};
}
/**
* Respond to a challenge (proves identity)
*/
respondToChallenge(challengeData) {
const challengeBytes = new Uint8Array(
challengeData.challenge.match(/.{2}/g).map(h => parseInt(h, 16))
);
const responseData = new Uint8Array([
...challengeBytes,
...new TextEncoder().encode(`|${challengeData.timestamp}|${this.getShortId()}`)
]);
const signature = this.piKey.sign(responseData);
return {
...challengeData,
response: Array.from(signature).map(b => b.toString(16).padStart(2, '0')).join(''),
responder: this.getShortId(),
publicKey: this.getPublicKeyHex()
};
}
/**
* Verify a challenge response
*/
verifyChallengeResponse(response) {
try {
const challengeBytes = new Uint8Array(
response.challenge.match(/.{2}/g).map(h => parseInt(h, 16))
);
const responseData = new Uint8Array([
...challengeBytes,
...new TextEncoder().encode(`|${response.timestamp}|${response.responder}`)
]);
const sigBytes = new Uint8Array(
response.response.match(/.{2}/g).map(h => parseInt(h, 16))
);
const pubKeyBytes = new Uint8Array(
response.publicKey.match(/.{2}/g).map(h => parseInt(h, 16))
);
const valid = this.piKey.verify(responseData, sigBytes, pubKeyBytes);
if (valid) {
// Register this peer as verified
this.registerPeer(response.responder, response.publicKey);
this._updateReputation(response.responder, 0.05);
}
return valid;
} catch (err) {
console.warn('Challenge verification failed:', err.message);
return false;
}
}
/**
* Clean up resources
*/
dispose() {
try { this.piKey?.free?.(); } catch (e) { /* already freed */ }
try { this.sessionKey?.free?.(); } catch (e) { /* already freed */ }
try { this.nodeIdentity?.free?.(); } catch (e) { /* already freed */ }
try { this.security?.free?.(); } catch (e) { /* already freed */ }
this.piKey = null;
this.sessionKey = null;
this.nodeIdentity = null;
this.security = null;
this.knownPeers.clear();
this.peerReputation.clear();
this.initialized = false;
}
}
/**
* Create a secure access manager
*/
export async function createSecureAccess(options = {}) {
const manager = new SecureAccessManager(options);
await manager.initialize();
return manager;
}
/**
* Wrap Firebase signaling with WASM security
*/
export function wrapWithSecurity(firebaseSignaling, secureAccess) {
const originalAnnounce = firebaseSignaling.announcePeer?.bind(firebaseSignaling);
const originalSendOffer = firebaseSignaling.sendOffer?.bind(firebaseSignaling);
const originalSendAnswer = firebaseSignaling.sendAnswer?.bind(firebaseSignaling);
const originalSendIceCandidate = firebaseSignaling.sendIceCandidate?.bind(firebaseSignaling);
// Wrap peer announcement with signature
if (originalAnnounce) {
firebaseSignaling.announcePeer = async (peerId, metadata = {}) => {
const signedMetadata = secureAccess.signMessage({
...metadata,
publicKey: secureAccess.getPublicKeyHex()
});
return originalAnnounce(peerId, signedMetadata);
};
}
// Wrap signaling messages with signatures
if (originalSendOffer) {
firebaseSignaling.sendOffer = async (toPeerId, offer) => {
const signed = secureAccess.signMessage({ type: 'offer', offer });
return originalSendOffer(toPeerId, signed);
};
}
if (originalSendAnswer) {
firebaseSignaling.sendAnswer = async (toPeerId, answer) => {
const signed = secureAccess.signMessage({ type: 'answer', answer });
return originalSendAnswer(toPeerId, signed);
};
}
if (originalSendIceCandidate) {
firebaseSignaling.sendIceCandidate = async (toPeerId, candidate) => {
const signed = secureAccess.signMessage({ type: 'ice', candidate });
return originalSendIceCandidate(toPeerId, signed);
};
}
// Add verification method
firebaseSignaling.verifySignedMessage = (signed) => {
return secureAccess.verifyMessage(signed);
};
firebaseSignaling.secureAccess = secureAccess;
return firebaseSignaling;
}
export default SecureAccessManager;

View File

@@ -0,0 +1,732 @@
/**
* @ruvector/edge-net WebRTC Signaling Server
*
* Real signaling server for WebRTC peer connections
* Enables true P2P connections between nodes
*
* @module @ruvector/edge-net/signaling
*/
import { EventEmitter } from 'events';
import { createServer } from 'http';
import { randomBytes, createHash } from 'crypto';
// ============================================
// SIGNALING SERVER
// ============================================
/**
* WebRTC Signaling Server
* Routes offers, answers, and ICE candidates between peers
*/
export class SignalingServer extends EventEmitter {
constructor(options = {}) {
super();
this.port = options.port || 8765;
this.server = null;
this.wss = null;
this.peers = new Map(); // peerId -> { ws, info, rooms }
this.rooms = new Map(); // roomId -> Set<peerId>
this.pendingOffers = new Map(); // offerId -> { from, to, offer }
this.stats = {
connections: 0,
messages: 0,
offers: 0,
answers: 0,
iceCandidates: 0,
};
}
/**
* Start the signaling server
*/
async start() {
return new Promise(async (resolve, reject) => {
try {
// Create HTTP server
this.server = createServer((req, res) => {
if (req.url === '/health') {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ status: 'ok', peers: this.peers.size }));
} else if (req.url === '/stats') {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(this.getStats()));
} else {
res.writeHead(404);
res.end('Not found');
}
});
// Create WebSocket server
const { WebSocketServer } = await import('ws');
this.wss = new WebSocketServer({ server: this.server });
this.wss.on('connection', (ws, req) => {
this.handleConnection(ws, req);
});
this.server.listen(this.port, () => {
console.log(`[Signaling] Server running on port ${this.port}`);
this.emit('ready', { port: this.port });
resolve(this);
});
} catch (error) {
reject(error);
}
});
}
/**
* Handle new WebSocket connection
*/
handleConnection(ws, req) {
const peerId = `peer-${randomBytes(8).toString('hex')}`;
const peerInfo = {
id: peerId,
ws,
info: {},
rooms: new Set(),
connectedAt: Date.now(),
lastSeen: Date.now(),
};
this.peers.set(peerId, peerInfo);
this.stats.connections++;
// Send welcome message
this.sendTo(peerId, {
type: 'welcome',
peerId,
serverTime: Date.now(),
});
ws.on('message', (data) => {
try {
const message = JSON.parse(data.toString());
this.handleMessage(peerId, message);
} catch (error) {
console.error('[Signaling] Invalid message:', error.message);
}
});
ws.on('close', () => {
this.handleDisconnect(peerId);
});
ws.on('error', (error) => {
console.error(`[Signaling] Peer ${peerId} error:`, error.message);
});
this.emit('peer-connected', { peerId });
}
/**
* Handle incoming message from peer
*/
handleMessage(peerId, message) {
const peer = this.peers.get(peerId);
if (!peer) return;
peer.lastSeen = Date.now();
this.stats.messages++;
switch (message.type) {
case 'register':
this.handleRegister(peerId, message);
break;
case 'join-room':
this.handleJoinRoom(peerId, message);
break;
case 'leave-room':
this.handleLeaveRoom(peerId, message);
break;
case 'offer':
this.handleOffer(peerId, message);
break;
case 'answer':
this.handleAnswer(peerId, message);
break;
case 'ice-candidate':
this.handleIceCandidate(peerId, message);
break;
case 'discover':
this.handleDiscover(peerId, message);
break;
case 'broadcast':
this.handleBroadcast(peerId, message);
break;
case 'ping':
this.sendTo(peerId, { type: 'pong', timestamp: Date.now() });
break;
default:
console.log(`[Signaling] Unknown message type: ${message.type}`);
}
}
/**
* Handle peer registration
*/
handleRegister(peerId, message) {
const peer = this.peers.get(peerId);
if (!peer) return;
peer.info = {
nodeId: message.nodeId,
capabilities: message.capabilities || [],
publicKey: message.publicKey,
region: message.region,
};
this.sendTo(peerId, {
type: 'registered',
peerId,
info: peer.info,
});
this.emit('peer-registered', { peerId, info: peer.info });
}
/**
* Handle room join
*/
handleJoinRoom(peerId, message) {
const roomId = message.roomId || 'default';
const peer = this.peers.get(peerId);
if (!peer) return;
// Create room if doesn't exist
if (!this.rooms.has(roomId)) {
this.rooms.set(roomId, new Set());
}
const room = this.rooms.get(roomId);
room.add(peerId);
peer.rooms.add(roomId);
// Get existing peers in room
const existingPeers = Array.from(room)
.filter(id => id !== peerId)
.map(id => {
const p = this.peers.get(id);
return { peerId: id, info: p?.info };
});
// Notify joining peer of existing peers
this.sendTo(peerId, {
type: 'room-joined',
roomId,
peers: existingPeers,
});
// Notify existing peers of new peer
for (const otherPeerId of room) {
if (otherPeerId !== peerId) {
this.sendTo(otherPeerId, {
type: 'peer-joined',
roomId,
peerId,
info: peer.info,
});
}
}
this.emit('room-join', { roomId, peerId });
}
/**
* Handle room leave
*/
handleLeaveRoom(peerId, message) {
const roomId = message.roomId;
const peer = this.peers.get(peerId);
if (!peer) return;
const room = this.rooms.get(roomId);
if (!room) return;
room.delete(peerId);
peer.rooms.delete(roomId);
// Notify other peers
for (const otherPeerId of room) {
this.sendTo(otherPeerId, {
type: 'peer-left',
roomId,
peerId,
});
}
// Clean up empty room
if (room.size === 0) {
this.rooms.delete(roomId);
}
}
/**
* Handle WebRTC offer
*/
handleOffer(peerId, message) {
this.stats.offers++;
const targetPeerId = message.to;
const target = this.peers.get(targetPeerId);
if (!target) {
this.sendTo(peerId, {
type: 'error',
error: 'Peer not found',
targetPeerId,
});
return;
}
// Forward offer to target
this.sendTo(targetPeerId, {
type: 'offer',
from: peerId,
offer: message.offer,
connectionId: message.connectionId,
});
this.emit('offer', { from: peerId, to: targetPeerId });
}
/**
* Handle WebRTC answer
*/
handleAnswer(peerId, message) {
this.stats.answers++;
const targetPeerId = message.to;
const target = this.peers.get(targetPeerId);
if (!target) return;
// Forward answer to target
this.sendTo(targetPeerId, {
type: 'answer',
from: peerId,
answer: message.answer,
connectionId: message.connectionId,
});
this.emit('answer', { from: peerId, to: targetPeerId });
}
/**
* Handle ICE candidate
*/
handleIceCandidate(peerId, message) {
this.stats.iceCandidates++;
const targetPeerId = message.to;
const target = this.peers.get(targetPeerId);
if (!target) return;
// Forward ICE candidate to target
this.sendTo(targetPeerId, {
type: 'ice-candidate',
from: peerId,
candidate: message.candidate,
connectionId: message.connectionId,
});
}
/**
* Handle peer discovery request
*/
handleDiscover(peerId, message) {
const capabilities = message.capabilities || [];
const limit = message.limit || 10;
const matches = [];
for (const [id, peer] of this.peers) {
if (id === peerId) continue;
// Check capability match
if (capabilities.length > 0) {
const peerCaps = peer.info.capabilities || [];
const hasMatch = capabilities.some(cap => peerCaps.includes(cap));
if (!hasMatch) continue;
}
matches.push({
peerId: id,
info: peer.info,
lastSeen: peer.lastSeen,
});
if (matches.length >= limit) break;
}
this.sendTo(peerId, {
type: 'discover-result',
peers: matches,
total: this.peers.size - 1,
});
}
/**
* Handle broadcast to room
*/
handleBroadcast(peerId, message) {
const roomId = message.roomId;
const room = this.rooms.get(roomId);
if (!room) return;
for (const otherPeerId of room) {
if (otherPeerId !== peerId) {
this.sendTo(otherPeerId, {
type: 'broadcast',
from: peerId,
roomId,
data: message.data,
});
}
}
}
/**
* Handle peer disconnect
*/
handleDisconnect(peerId) {
const peer = this.peers.get(peerId);
if (!peer) return;
// Leave all rooms
for (const roomId of peer.rooms) {
const room = this.rooms.get(roomId);
if (room) {
room.delete(peerId);
// Notify other peers
for (const otherPeerId of room) {
this.sendTo(otherPeerId, {
type: 'peer-left',
roomId,
peerId,
});
}
// Clean up empty room
if (room.size === 0) {
this.rooms.delete(roomId);
}
}
}
this.peers.delete(peerId);
this.emit('peer-disconnected', { peerId });
}
/**
* Send message to peer
*/
sendTo(peerId, message) {
const peer = this.peers.get(peerId);
if (peer && peer.ws.readyState === 1) {
peer.ws.send(JSON.stringify(message));
return true;
}
return false;
}
/**
* Get server stats
*/
getStats() {
return {
peers: this.peers.size,
rooms: this.rooms.size,
...this.stats,
uptime: Date.now() - (this.startTime || Date.now()),
};
}
/**
* Stop the server
*/
async stop() {
return new Promise((resolve) => {
// Close all peer connections
for (const [peerId, peer] of this.peers) {
peer.ws.close();
}
this.peers.clear();
this.rooms.clear();
if (this.wss) {
this.wss.close();
}
if (this.server) {
this.server.close(() => {
console.log('[Signaling] Server stopped');
resolve();
});
} else {
resolve();
}
});
}
}
// ============================================
// SIGNALING CLIENT
// ============================================
/**
* WebRTC Signaling Client
* Connects to signaling server for peer discovery and connection setup
*/
export class SignalingClient extends EventEmitter {
constructor(options = {}) {
super();
this.serverUrl = options.serverUrl || 'ws://localhost:8765';
this.nodeId = options.nodeId || `node-${randomBytes(8).toString('hex')}`;
this.capabilities = options.capabilities || [];
this.ws = null;
this.peerId = null;
this.connected = false;
this.rooms = new Set();
this.pendingConnections = new Map();
this.peerConnections = new Map();
}
/**
* Connect to signaling server
*/
async connect() {
return new Promise(async (resolve, reject) => {
try {
let WebSocket;
if (typeof globalThis.WebSocket !== 'undefined') {
WebSocket = globalThis.WebSocket;
} else {
const ws = await import('ws');
WebSocket = ws.default || ws.WebSocket;
}
this.ws = new WebSocket(this.serverUrl);
const timeout = setTimeout(() => {
reject(new Error('Connection timeout'));
}, 10000);
this.ws.onopen = () => {
clearTimeout(timeout);
this.connected = true;
this.emit('connected');
};
this.ws.onmessage = (event) => {
const message = JSON.parse(event.data);
this.handleMessage(message);
if (message.type === 'registered') {
resolve(this);
}
};
this.ws.onclose = () => {
this.connected = false;
this.emit('disconnected');
};
this.ws.onerror = (error) => {
clearTimeout(timeout);
reject(error);
};
} catch (error) {
reject(error);
}
});
}
/**
* Handle incoming message
*/
handleMessage(message) {
switch (message.type) {
case 'welcome':
this.peerId = message.peerId;
// Register with capabilities
this.send({
type: 'register',
nodeId: this.nodeId,
capabilities: this.capabilities,
});
break;
case 'registered':
this.emit('registered', message);
break;
case 'room-joined':
this.rooms.add(message.roomId);
this.emit('room-joined', message);
break;
case 'peer-joined':
this.emit('peer-joined', message);
break;
case 'peer-left':
this.emit('peer-left', message);
break;
case 'offer':
this.emit('offer', message);
break;
case 'answer':
this.emit('answer', message);
break;
case 'ice-candidate':
this.emit('ice-candidate', message);
break;
case 'discover-result':
this.emit('discover-result', message);
break;
case 'broadcast':
this.emit('broadcast', message);
break;
case 'pong':
this.emit('pong', message);
break;
default:
this.emit('message', message);
}
}
/**
* Send message to server
*/
send(message) {
if (this.connected && this.ws?.readyState === 1) {
this.ws.send(JSON.stringify(message));
return true;
}
return false;
}
/**
* Join a room
*/
joinRoom(roomId) {
return this.send({ type: 'join-room', roomId });
}
/**
* Leave a room
*/
leaveRoom(roomId) {
this.rooms.delete(roomId);
return this.send({ type: 'leave-room', roomId });
}
/**
* Send WebRTC offer to peer
*/
sendOffer(targetPeerId, offer, connectionId) {
return this.send({
type: 'offer',
to: targetPeerId,
offer,
connectionId,
});
}
/**
* Send WebRTC answer to peer
*/
sendAnswer(targetPeerId, answer, connectionId) {
return this.send({
type: 'answer',
to: targetPeerId,
answer,
connectionId,
});
}
/**
* Send ICE candidate to peer
*/
sendIceCandidate(targetPeerId, candidate, connectionId) {
return this.send({
type: 'ice-candidate',
to: targetPeerId,
candidate,
connectionId,
});
}
/**
* Discover peers with capabilities
*/
discover(capabilities = [], limit = 10) {
return this.send({
type: 'discover',
capabilities,
limit,
});
}
/**
* Broadcast to room
*/
broadcast(roomId, data) {
return this.send({
type: 'broadcast',
roomId,
data,
});
}
/**
* Ping server
*/
ping() {
return this.send({ type: 'ping' });
}
/**
* Close connection
*/
close() {
if (this.ws) {
this.ws.close();
}
}
}
// ============================================
// EXPORTS
// ============================================
export default SignalingServer;

View File

@@ -0,0 +1,799 @@
/**
* @ruvector/edge-net Hybrid Sync Service
*
* Multi-device identity and ledger synchronization using:
* - P2P sync via WebRTC (fast, direct when devices online together)
* - Firestore sync (persistent fallback, cross-session)
* - Identity linking via PiKey signatures
*
* @module @ruvector/edge-net/sync
*/
import { EventEmitter } from 'events';
import { createHash, randomBytes } from 'crypto';
// ============================================
// SYNC CONFIGURATION
// ============================================
export const SYNC_CONFIG = {
// Firestore endpoints (Genesis nodes)
firestore: {
projectId: 'ruvector-edge-net',
collection: 'ledger-sync',
identityCollection: 'identity-links',
},
// Sync intervals
intervals: {
p2pHeartbeat: 5000, // 5s P2P sync check
firestoreSync: 30000, // 30s Firestore sync
staleThreshold: 60000, // 1min before considering state stale
},
// CRDT merge settings
crdt: {
maxBatchSize: 1000, // Max entries per merge
conflictResolution: 'lww', // Last-write-wins
},
// Genesis node endpoints
genesisNodes: [
{ region: 'us-central1', url: 'https://edge-net-genesis-us.ruvector.dev' },
{ region: 'europe-west1', url: 'https://edge-net-genesis-eu.ruvector.dev' },
{ region: 'asia-east1', url: 'https://edge-net-genesis-asia.ruvector.dev' },
],
};
// ============================================
// IDENTITY LINKER
// ============================================
/**
* Links a PiKey identity across multiple devices
* Uses cryptographic challenge-response to prove ownership
*/
export class IdentityLinker extends EventEmitter {
constructor(piKey, options = {}) {
super();
this.piKey = piKey;
this.publicKeyHex = this.toHex(piKey.getPublicKey());
this.shortId = piKey.getShortId();
this.options = {
genesisUrl: options.genesisUrl || SYNC_CONFIG.genesisNodes[0].url,
...options,
};
this.linkedDevices = new Map();
this.authToken = null;
this.deviceId = this.generateDeviceId();
}
/**
* Generate unique device ID
*/
generateDeviceId() {
const platform = typeof window !== 'undefined' ? 'browser' : 'node';
const random = randomBytes(8).toString('hex');
const timestamp = Date.now().toString(36);
return `${platform}-${timestamp}-${random}`;
}
/**
* Authenticate with genesis node using PiKey signature
*/
async authenticate() {
try {
// Step 1: Request challenge
const challengeRes = await this.fetchWithTimeout(
`${this.options.genesisUrl}/api/v1/identity/challenge`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
publicKey: this.publicKeyHex,
deviceId: this.deviceId,
}),
}
);
if (!challengeRes.ok) {
throw new Error(`Challenge request failed: ${challengeRes.status}`);
}
const { challenge, nonce } = await challengeRes.json();
// Step 2: Sign challenge with PiKey
const challengeBytes = this.fromHex(challenge);
const signature = this.piKey.sign(challengeBytes);
// Step 3: Submit signature for verification
const authRes = await this.fetchWithTimeout(
`${this.options.genesisUrl}/api/v1/identity/verify`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
publicKey: this.publicKeyHex,
deviceId: this.deviceId,
nonce,
signature: this.toHex(signature),
}),
}
);
if (!authRes.ok) {
throw new Error(`Authentication failed: ${authRes.status}`);
}
const { token, expiresAt, linkedDevices } = await authRes.json();
this.authToken = token;
this.tokenExpiry = new Date(expiresAt);
// Update linked devices
for (const device of linkedDevices || []) {
this.linkedDevices.set(device.deviceId, device);
}
this.emit('authenticated', {
deviceId: this.deviceId,
linkedDevices: this.linkedDevices.size,
});
return { success: true, token, linkedDevices: this.linkedDevices.size };
} catch (error) {
// Fallback: Generate local-only token for P2P sync
console.warn('[Sync] Genesis authentication failed, using local mode:', error.message);
this.authToken = this.generateLocalToken();
this.emit('authenticated', { deviceId: this.deviceId, mode: 'local' });
return { success: true, mode: 'local' };
}
}
/**
* Generate local token for P2P-only mode
*/
generateLocalToken() {
const payload = {
sub: this.publicKeyHex,
dev: this.deviceId,
iat: Date.now(),
mode: 'local',
};
return Buffer.from(JSON.stringify(payload)).toString('base64');
}
/**
* Link a new device to this identity
*/
async linkDevice(deviceInfo) {
if (!this.authToken) {
await this.authenticate();
}
try {
const res = await this.fetchWithTimeout(
`${this.options.genesisUrl}/api/v1/identity/link`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.authToken}`,
},
body: JSON.stringify({
publicKey: this.publicKeyHex,
newDevice: deviceInfo,
}),
}
);
if (!res.ok) {
throw new Error(`Link failed: ${res.status}`);
}
const result = await res.json();
this.linkedDevices.set(deviceInfo.deviceId, deviceInfo);
this.emit('device_linked', { deviceId: deviceInfo.deviceId });
return result;
} catch (error) {
// P2P fallback: Store in local linked devices for gossip
this.linkedDevices.set(deviceInfo.deviceId, {
...deviceInfo,
linkedAt: Date.now(),
mode: 'p2p',
});
return { success: true, mode: 'p2p' };
}
}
/**
* Get all linked devices
*/
getLinkedDevices() {
return Array.from(this.linkedDevices.values());
}
/**
* Check if a device is linked to this identity
*/
isDeviceLinked(deviceId) {
return this.linkedDevices.has(deviceId);
}
// Utility methods
toHex(bytes) {
return Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join('');
}
fromHex(hex) {
const bytes = new Uint8Array(hex.length / 2);
for (let i = 0; i < hex.length; i += 2) {
bytes[i / 2] = parseInt(hex.substr(i, 2), 16);
}
return bytes;
}
async fetchWithTimeout(url, options, timeout = 10000) {
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), timeout);
try {
const response = await fetch(url, { ...options, signal: controller.signal });
clearTimeout(id);
return response;
} catch (error) {
clearTimeout(id);
throw error;
}
}
}
// ============================================
// LEDGER SYNC SERVICE
// ============================================
/**
* Hybrid sync service for credit ledger
* Combines P2P (fast) and Firestore (persistent) sync
*/
export class LedgerSyncService extends EventEmitter {
constructor(identityLinker, ledger, options = {}) {
super();
this.identity = identityLinker;
this.ledger = ledger;
this.options = {
enableP2P: true,
enableFirestore: true,
syncInterval: SYNC_CONFIG.intervals.firestoreSync,
...options,
};
// Sync state
this.lastSyncTime = 0;
this.syncInProgress = false;
this.pendingChanges = [];
this.peerStates = new Map(); // deviceId -> { earned, spent, timestamp }
this.vectorClock = new Map(); // deviceId -> counter
// P2P connections
this.p2pPeers = new Map();
// Intervals
this.syncIntervalId = null;
this.heartbeatId = null;
}
/**
* Start sync service
*/
async start() {
// Authenticate first
await this.identity.authenticate();
// Start periodic sync
if (this.options.enableFirestore) {
this.syncIntervalId = setInterval(
() => this.syncWithFirestore(),
this.options.syncInterval
);
}
// Start P2P heartbeat
if (this.options.enableP2P) {
this.heartbeatId = setInterval(
() => this.p2pHeartbeat(),
SYNC_CONFIG.intervals.p2pHeartbeat
);
}
// Initial sync
await this.fullSync();
this.emit('started', { deviceId: this.identity.deviceId });
return this;
}
/**
* Stop sync service
*/
stop() {
if (this.syncIntervalId) {
clearInterval(this.syncIntervalId);
this.syncIntervalId = null;
}
if (this.heartbeatId) {
clearInterval(this.heartbeatId);
this.heartbeatId = null;
}
this.emit('stopped');
}
/**
* Full sync - fetch from all sources and merge
*/
async fullSync() {
if (this.syncInProgress) return;
this.syncInProgress = true;
try {
const results = await Promise.allSettled([
this.options.enableFirestore ? this.fetchFromFirestore() : null,
this.options.enableP2P ? this.fetchFromP2PPeers() : null,
]);
// Merge all fetched states
for (const result of results) {
if (result.status === 'fulfilled' && result.value) {
await this.mergeState(result.value);
}
}
// Push our state
await this.pushState();
this.lastSyncTime = Date.now();
this.emit('synced', {
timestamp: this.lastSyncTime,
balance: this.ledger.balance(),
});
} catch (error) {
this.emit('sync_error', { error: error.message });
} finally {
this.syncInProgress = false;
}
}
/**
* Fetch ledger state from Firestore
*/
async fetchFromFirestore() {
if (!this.identity.authToken) return null;
try {
const res = await this.identity.fetchWithTimeout(
`${this.identity.options.genesisUrl}/api/v1/ledger/${this.identity.publicKeyHex}`,
{
method: 'GET',
headers: {
'Authorization': `Bearer ${this.identity.authToken}`,
},
}
);
if (!res.ok) {
if (res.status === 404) return null; // No state yet
throw new Error(`Firestore fetch failed: ${res.status}`);
}
const { states } = await res.json();
return states; // Array of { deviceId, earned, spent, timestamp }
} catch (error) {
console.warn('[Sync] Firestore fetch failed:', error.message);
return null;
}
}
/**
* Fetch ledger state from P2P peers
*/
async fetchFromP2PPeers() {
const states = [];
for (const [peerId, peer] of this.p2pPeers) {
try {
if (peer.dataChannel?.readyState === 'open') {
const state = await this.requestStateFromPeer(peer);
if (state) {
states.push({ deviceId: peerId, ...state });
}
}
} catch (error) {
console.warn(`[Sync] P2P fetch from ${peerId} failed:`, error.message);
}
}
return states.length > 0 ? states : null;
}
/**
* Request state from a P2P peer
*/
requestStateFromPeer(peer) {
return new Promise((resolve, reject) => {
const requestId = randomBytes(8).toString('hex');
const timeout = setTimeout(() => {
reject(new Error('P2P state request timeout'));
}, 5000);
const handler = (event) => {
try {
const msg = JSON.parse(event.data);
if (msg.type === 'ledger_state' && msg.requestId === requestId) {
clearTimeout(timeout);
peer.dataChannel.removeEventListener('message', handler);
resolve(msg.state);
}
} catch (e) { /* ignore */ }
};
peer.dataChannel.addEventListener('message', handler);
peer.dataChannel.send(JSON.stringify({
type: 'ledger_state_request',
requestId,
from: this.identity.deviceId,
}));
});
}
/**
* Merge remote state into local ledger (CRDT)
*/
async mergeState(states) {
if (!states || !Array.isArray(states)) return;
for (const state of states) {
// Skip our own state
if (state.deviceId === this.identity.deviceId) continue;
// Check vector clock for freshness
const lastSeen = this.vectorClock.get(state.deviceId) || 0;
if (state.timestamp <= lastSeen) continue;
// CRDT merge
try {
if (state.earned && state.spent) {
const earned = typeof state.earned === 'string'
? JSON.parse(state.earned)
: state.earned;
const spent = typeof state.spent === 'string'
? JSON.parse(state.spent)
: state.spent;
this.ledger.merge(
JSON.stringify(earned),
JSON.stringify(spent)
);
}
// Update vector clock
this.vectorClock.set(state.deviceId, state.timestamp);
this.peerStates.set(state.deviceId, state);
this.emit('state_merged', {
deviceId: state.deviceId,
newBalance: this.ledger.balance(),
});
} catch (error) {
console.warn(`[Sync] Merge failed for ${state.deviceId}:`, error.message);
}
}
}
/**
* Push local state to sync destinations
*/
async pushState() {
const state = this.exportState();
// Push to Firestore
if (this.options.enableFirestore && this.identity.authToken) {
await this.pushToFirestore(state);
}
// Broadcast to P2P peers
if (this.options.enableP2P) {
this.broadcastToP2P(state);
}
}
/**
* Export current ledger state
*/
exportState() {
return {
deviceId: this.identity.deviceId,
publicKey: this.identity.publicKeyHex,
earned: this.ledger.exportEarned(),
spent: this.ledger.exportSpent(),
balance: this.ledger.balance(),
totalEarned: this.ledger.totalEarned(),
totalSpent: this.ledger.totalSpent(),
timestamp: Date.now(),
};
}
/**
* Push state to Firestore
*/
async pushToFirestore(state) {
try {
const res = await this.identity.fetchWithTimeout(
`${this.identity.options.genesisUrl}/api/v1/ledger/${this.identity.publicKeyHex}`,
{
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.identity.authToken}`,
},
body: JSON.stringify({
deviceId: state.deviceId,
earned: state.earned,
spent: state.spent,
timestamp: state.timestamp,
}),
}
);
if (!res.ok) {
throw new Error(`Firestore push failed: ${res.status}`);
}
return true;
} catch (error) {
console.warn('[Sync] Firestore push failed:', error.message);
return false;
}
}
/**
* Broadcast state to P2P peers
*/
broadcastToP2P(state) {
const message = JSON.stringify({
type: 'ledger_state_broadcast',
state: {
deviceId: state.deviceId,
earned: state.earned,
spent: state.spent,
timestamp: state.timestamp,
},
});
for (const [peerId, peer] of this.p2pPeers) {
try {
if (peer.dataChannel?.readyState === 'open') {
peer.dataChannel.send(message);
}
} catch (error) {
console.warn(`[Sync] P2P broadcast to ${peerId} failed:`, error.message);
}
}
}
/**
* P2P heartbeat - discover and sync with nearby devices
*/
async p2pHeartbeat() {
// Broadcast presence to linked devices
const presence = {
type: 'presence',
deviceId: this.identity.deviceId,
publicKey: this.identity.publicKeyHex,
balance: this.ledger.balance(),
timestamp: Date.now(),
};
for (const [peerId, peer] of this.p2pPeers) {
try {
if (peer.dataChannel?.readyState === 'open') {
peer.dataChannel.send(JSON.stringify(presence));
}
} catch (error) {
// Remove stale peer
this.p2pPeers.delete(peerId);
}
}
}
/**
* Register a P2P peer for sync
*/
registerP2PPeer(peerId, dataChannel) {
this.p2pPeers.set(peerId, { dataChannel, connectedAt: Date.now() });
// Handle incoming messages
dataChannel.addEventListener('message', (event) => {
this.handleP2PMessage(peerId, event.data);
});
this.emit('peer_registered', { peerId });
}
/**
* Handle incoming P2P message
*/
async handleP2PMessage(peerId, data) {
try {
const msg = JSON.parse(data);
switch (msg.type) {
case 'ledger_state_request':
// Respond with our state
const state = this.exportState();
const peer = this.p2pPeers.get(peerId);
if (peer?.dataChannel?.readyState === 'open') {
peer.dataChannel.send(JSON.stringify({
type: 'ledger_state',
requestId: msg.requestId,
state: {
earned: state.earned,
spent: state.spent,
timestamp: state.timestamp,
},
}));
}
break;
case 'ledger_state_broadcast':
// Merge incoming state
if (msg.state) {
await this.mergeState([{ deviceId: peerId, ...msg.state }]);
}
break;
case 'presence':
// Update peer info
const existingPeer = this.p2pPeers.get(peerId);
if (existingPeer) {
existingPeer.lastSeen = Date.now();
existingPeer.balance = msg.balance;
}
break;
}
} catch (error) {
console.warn(`[Sync] P2P message handling failed:`, error.message);
}
}
/**
* Sync with Firestore (called periodically)
*/
async syncWithFirestore() {
if (this.syncInProgress) return;
try {
const states = await this.fetchFromFirestore();
if (states) {
await this.mergeState(states);
}
await this.pushToFirestore(this.exportState());
} catch (error) {
console.warn('[Sync] Periodic Firestore sync failed:', error.message);
}
}
/**
* Force immediate sync
*/
async forceSync() {
return this.fullSync();
}
/**
* Get sync status
*/
getStatus() {
return {
deviceId: this.identity.deviceId,
publicKey: this.identity.publicKeyHex,
shortId: this.identity.shortId,
linkedDevices: this.identity.getLinkedDevices().length,
p2pPeers: this.p2pPeers.size,
lastSyncTime: this.lastSyncTime,
balance: this.ledger.balance(),
totalEarned: this.ledger.totalEarned(),
totalSpent: this.ledger.totalSpent(),
syncEnabled: {
p2p: this.options.enableP2P,
firestore: this.options.enableFirestore,
},
};
}
}
// ============================================
// SYNC MANAGER (CONVENIENCE WRAPPER)
// ============================================
/**
* High-level sync manager for easy integration
*/
export class SyncManager extends EventEmitter {
constructor(piKey, ledger, options = {}) {
super();
this.identityLinker = new IdentityLinker(piKey, options);
this.syncService = new LedgerSyncService(this.identityLinker, ledger, options);
// Forward events
this.syncService.on('synced', (data) => this.emit('synced', data));
this.syncService.on('state_merged', (data) => this.emit('state_merged', data));
this.syncService.on('sync_error', (data) => this.emit('sync_error', data));
this.identityLinker.on('authenticated', (data) => this.emit('authenticated', data));
this.identityLinker.on('device_linked', (data) => this.emit('device_linked', data));
}
/**
* Start sync
*/
async start() {
await this.syncService.start();
return this;
}
/**
* Stop sync
*/
stop() {
this.syncService.stop();
}
/**
* Force sync
*/
async sync() {
return this.syncService.forceSync();
}
/**
* Register P2P peer
*/
registerPeer(peerId, dataChannel) {
this.syncService.registerP2PPeer(peerId, dataChannel);
}
/**
* Get status
*/
getStatus() {
return this.syncService.getStatus();
}
/**
* Export identity for another device
*/
exportIdentity(password) {
return this.identityLinker.piKey.createEncryptedBackup(password);
}
/**
* Link devices via QR code data
*/
generateLinkData() {
return {
publicKey: this.identityLinker.publicKeyHex,
shortId: this.identityLinker.shortId,
genesisUrl: this.identityLinker.options.genesisUrl,
timestamp: Date.now(),
};
}
}
// ============================================
// EXPORTS
// ============================================
export default SyncManager;