@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
JavaScript
/**
* 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