UNPKG

@hivetechs/hive-ai

Version:

Real-time streaming AI consensus platform with HTTP+SSE MCP integration for Claude Code, VS Code, Cursor, and Windsurf - powered by OpenRouter's unified API

286 lines (285 loc) 12.7 kB
/** * Secure Pipeline Configuration with SQLite Storage * * Enhanced pipeline configuration that uses secure SQLite storage * for pipeline profiles with encryption and audit logging. */ import { z } from "zod"; import * as crypto from 'crypto'; import * as fs from 'fs'; import * as path from 'path'; import * as os from 'os'; import { getDatabase, createPipelineProfileWithInternalIds, getAllPipelineProfiles, getPipelineProfile, setDefaultPipelineProfile, getDefaultPipelineProfile } from '../storage/unified-database.js'; // Pipeline stage schema const PipelineStageSchema = z.object({ provider_name: z.string(), model: z.string(), temperature: z.number().min(0).max(2).optional() }); // Pipeline profile schema export const PipelineProfileSchema = z.object({ id: z.string(), name: z.string(), is_default: z.boolean().default(false), generator: PipelineStageSchema, refiner: PipelineStageSchema, validator: PipelineStageSchema, curator: PipelineStageSchema.optional(), created_at: z.string(), updated_at: z.string() }); export class SecurePipelineConfig { db; configDir; encryptionKey = null; isInitialized = false; initPromise = null; constructor() { this.configDir = path.join(os.homedir(), '.hive-ai'); this.ensureConfigDir(); this.initPromise = this.initDatabase().catch(error => { console.error('SecurePipelineConfig initialization failed:', error); throw error; }); } async ensureInitialized() { if (!this.isInitialized && this.initPromise) { await this.initPromise; } } ensureConfigDir() { if (!fs.existsSync(this.configDir)) { fs.mkdirSync(this.configDir, { recursive: true, mode: 0o700 }); } // Ensure secure permissions on config directory fs.chmodSync(this.configDir, 0o700); // rwx------ } async initDatabase() { // Use unified database instead of separate database this.db = await getDatabase(); // Create audit logging table if needed (unified database has pipeline_profiles already) await this.createAuditTable(); await this.migrateTables(); // Handle schema changes await this.logAudit('database_init', undefined, true, 'Pipeline database initialized with unified storage'); this.isInitialized = true; } deriveEncryptionKey() { // Use system-specific identifiers to derive a consistent key const systemInfo = `${os.hostname()}:${os.userInfo().username}:hive-pipeline-v1`; const hash = crypto.createHash('sha256').update(systemInfo).digest('hex'); return hash.substring(0, 64); // 32-byte key as hex } async createAuditTable() { // Only create audit table - pipeline_profiles already exists in unified database await this.db.exec(` CREATE TABLE IF NOT EXISTS pipeline_audit_log ( id INTEGER PRIMARY KEY AUTOINCREMENT, action TEXT NOT NULL, profile_name TEXT, timestamp TEXT NOT NULL, success INTEGER NOT NULL, details TEXT ); CREATE INDEX IF NOT EXISTS idx_pipeline_audit_timestamp ON pipeline_audit_log(timestamp); `); } async migrateTables() { try { // Migration is handled by the unified database system // Only ensure audit table exists console.log('🔄 Checking pipeline audit table...'); } catch (error) { console.warn('Pipeline database migration warning:', error); // Continue anyway - the table creation will handle it } } // Legacy encryption/database methods removed - now using unified database async logAudit(action, profile_name, success = true, details) { await this.db.run(`INSERT INTO pipeline_audit_log (action, profile_name, timestamp, success, details) VALUES (?, ?, ?, ?, ?)`, action, profile_name, new Date().toISOString(), success ? 1 : 0, details); } /** * Get internal ID for a model by OpenRouter ID */ async getModelInternalId(openrouterId) { try { const result = await this.db.get('SELECT internal_id FROM openrouter_models WHERE openrouter_id = ? AND is_active = 1', [openrouterId]); return result?.internal_id || null; } catch (error) { console.error(`Error getting internal ID for model ${openrouterId}:`, error); return null; } } async configurePipeline(name, generator, refiner, validator, curator) { await this.ensureInitialized(); try { // Parse stage commands const generatorStage = this.parseStageCommand(generator); const refinerStage = this.parseStageCommand(refiner); const validatorStage = this.parseStageCommand(validator); const curatorStage = curator ? this.parseStageCommand(curator) : undefined; // Get internal IDs for all models const generatorInternalId = await this.getModelInternalId(generatorStage.model); const refinerInternalId = await this.getModelInternalId(refinerStage.model); const validatorInternalId = await this.getModelInternalId(validatorStage.model); const curatorInternalId = await this.getModelInternalId(curatorStage?.model || 'anthropic/claude-3-5-sonnet-20241022'); if (!generatorInternalId || !refinerInternalId || !validatorInternalId || !curatorInternalId) { const missing = []; if (!generatorInternalId) missing.push(`Generator: ${generatorStage.model}`); if (!refinerInternalId) missing.push(`Refiner: ${refinerStage.model}`); if (!validatorInternalId) missing.push(`Validator: ${validatorStage.model}`); if (!curatorInternalId) missing.push(`Curator: ${curatorStage?.model || 'anthropic/claude-3-5-sonnet-20241022'}`); throw new Error(`Models not found in database: ${missing.join(', ')}. Run 'hive models update' to sync latest models.`); } // Use unified database functions to create profile with bulletproof internal IDs const unifiedProfile = await createPipelineProfileWithInternalIds(name, generatorInternalId, generatorStage.temperature || 0.7, refinerInternalId, refinerStage.temperature || 0.7, validatorInternalId, validatorStage.temperature || 0.7, curatorInternalId, curatorStage?.temperature || 0.7); // Convert unified database format to secure pipeline format const profile = { id: unifiedProfile.id, name: unifiedProfile.name, is_default: unifiedProfile.is_default, generator: generatorStage, refiner: refinerStage, validator: validatorStage, curator: curatorStage, created_at: unifiedProfile.created_at, updated_at: unifiedProfile.updated_at }; await this.logAudit('configure_pipeline', name, true); return profile; } catch (error) { await this.logAudit('configure_pipeline', name, false, error instanceof Error ? error.message : 'Unknown error'); throw error; } } async getProfiles() { await this.ensureInitialized(); const unifiedProfiles = await getAllPipelineProfiles(); return unifiedProfiles.map(profile => this.convertUnifiedToSecure(profile)); } async getProfile(identifier) { await this.ensureInitialized(); const unifiedProfile = await getPipelineProfile(identifier); if (!unifiedProfile) { return null; } await this.logAudit('access_profile', unifiedProfile.name, true); return this.convertUnifiedToSecure(unifiedProfile); } async getDefaultProfile() { await this.ensureInitialized(); const unifiedProfile = await getDefaultPipelineProfile(); return unifiedProfile ? this.convertUnifiedToSecure(unifiedProfile) : null; } async setDefaultProfile(identifier) { await this.ensureInitialized(); try { const success = await setDefaultPipelineProfile(identifier); if (!success) { await this.logAudit('set_default_profile', identifier, false, 'Profile not found'); return null; } const profile = await this.getProfile(identifier); await this.logAudit('set_default_profile', profile?.name || identifier, true); return profile; } catch (error) { await this.logAudit('set_default_profile', identifier, false, error instanceof Error ? error.message : 'Unknown error'); throw error; } } /** * Convert unified database profile format to secure pipeline format */ convertUnifiedToSecure(unifiedProfile) { return { id: unifiedProfile.id, name: unifiedProfile.name, is_default: unifiedProfile.is_default, generator: { provider_name: unifiedProfile.generator_provider, model: unifiedProfile.generator_model, temperature: unifiedProfile.generator_temperature }, refiner: { provider_name: unifiedProfile.refiner_provider, model: unifiedProfile.refiner_model, temperature: unifiedProfile.refiner_temperature }, validator: { provider_name: unifiedProfile.validator_provider, model: unifiedProfile.validator_model, temperature: unifiedProfile.validator_temperature }, curator: { provider_name: unifiedProfile.curator_provider, model: unifiedProfile.curator_model, temperature: unifiedProfile.curator_temperature }, created_at: unifiedProfile.created_at, updated_at: unifiedProfile.updated_at }; } // Removed rowToProfile - now using convertUnifiedToSecure parseStageCommand(stageCommand) { // Support both | and : separators for backward compatibility const separator = stageCommand.includes('|') ? '|' : ':'; const parts = stageCommand.trim().split(separator); if (parts.length < 2) { throw new Error(`Invalid stage format: ${stageCommand}. Expected "PROVIDER_NAME${separator}MODEL_NAME[${separator}TEMPERATURE]"`); } const provider_name = parts[0]; const model = parts[1]; let temperature = undefined; if (parts.length > 2) { temperature = parseFloat(parts[2]); if (isNaN(temperature) || temperature < 0 || temperature > 2) { throw new Error(`Invalid temperature: ${parts[2]}. Must be between 0 and 2.`); } } return { provider_name, model, temperature }; } async getAuditLog(limit = 100) { await this.ensureInitialized(); return await this.db.all(` SELECT * FROM pipeline_audit_log ORDER BY timestamp DESC LIMIT ? `, limit); } async getSecurityStats() { await this.ensureInitialized(); const profileCount = (await this.db.get('SELECT COUNT(*) as count FROM pipeline_profiles')).count; const accessTotal = 0; // Access count not tracked in unified database const auditCount = (await this.db.get('SELECT COUNT(*) as count FROM pipeline_audit_log WHERE timestamp > datetime(\'now\', \'-7 days\')')).count; const lastActivity = (await this.db.get('SELECT MAX(timestamp) as last FROM pipeline_audit_log')).last; return { totalProfiles: profileCount, totalAccesses: accessTotal, recentAuditEntries: auditCount, lastActivity, backend: 'unified_database', securityFeatures: [ 'Unified SQLite database', 'Pipeline audit logging', 'Foreign key constraints', 'ACID compliance', 'Normalized schema' ] }; } async close() { await this.ensureInitialized(); await this.logAudit('database_close', undefined, true); await this.db.close(); } } // Export singleton instance export const securePipelineConfig = new SecurePipelineConfig(); //# sourceMappingURL=secure-pipeline-config.js.map