Files
Autonomous-Bug-Explorer/.ralph/specs/phase-02-shared-infrastructure.md

4.3 KiB

Phase 2: Shared Infrastructure

Config.ts

Usa Zod para validar TODAS las env vars al arranque. Si falla → crash inmediato con mensaje claro.

import { z } from 'zod';
import dotenv from 'dotenv';
dotenv.config();

const configSchema = z.object({
  port: z.coerce.number().default(3001),
  host: z.string().default('0.0.0.0'),
  nodeEnv: z.enum(['development', 'production', 'test']).default('development'),
  db: z.object({
    driver: z.enum(['sqlite', 'postgres']).default('sqlite'),
    path: z.string().default('./data/abe.db'),
    url: z.string().optional(),
  }),
  auth: z.object({
    secret: z.string().min(16).default('abe-dev-secret-change-in-prod'),
    sessionMaxAge: z.coerce.number().default(86400),
  }),
  storage: z.object({
    driver: z.enum(['local', 's3']).default('local'),
    path: z.string().default('./data/storage'),
  }),
  cors: z.object({ origin: z.string().default('http://localhost:5173') }),
  log: z.object({ level: z.enum(['debug','info','warn','error']).default('info') }),
  api: z.object({
    key: z.string().default('abe-dev-key-123'),
    rateLimitWindowMs: z.coerce.number().default(900000),
    rateLimitMax: z.coerce.number().default(100),
  }),
  ai: z.object({
    provider: z.enum(['claude','openai','ollama','none']).default('none'),
    apiKey: z.string().default(''),
    autoEnrich: z.coerce.boolean().default(false),
    minSeverity: z.enum(['low','medium','high','critical']).default('high'),
  }),
  jobs: z.object({
    maxConcurrentSessions: z.coerce.number().default(3),
    pollIntervalMs: z.coerce.number().default(1000),
  }),
  license: z.object({ key: z.string().default('') }),
});

export type AppConfig = z.infer<typeof configSchema>;

export function loadConfig(): AppConfig {
  // Map env vars to schema shape, parse
}

Logger.ts

import pino from 'pino';

export function createLogger(config: { level: string; nodeEnv: string }): pino.Logger {
  return pino({
    level: config.level,
    transport: config.nodeEnv === 'development'
      ? { target: 'pino-pretty', options: { colorize: true, translateTime: 'HH:MM:ss' } }
      : undefined,
  });
}
export type Logger = pino.Logger;

DatabaseConnection.ts

import { Kysely, SqliteDialect } from 'kysely';
import SQLite from 'better-sqlite3';

// Define Database interface con todas las tablas
export interface Database {
  sessions: SessionTable;
  states: StateTable;
  actions: ActionTable;
  anomalies: AnomalyTable;
  // ... más tablas se añaden en fases posteriores
}

export function createDatabase(config: { driver: string; path: string; url?: string }): Kysely<Database> {
  if (config.driver === 'postgres') {
    // Import dinámico de pg para no requerir en SQLite
    const { Pool } = require('pg');
    const { PostgresDialect } = require('kysely');
    return new Kysely<Database>({
      dialect: new PostgresDialect({ pool: new Pool({ connectionString: config.url }) }),
    });
  }
  
  // Crear directorio data/ si no existe
  const path = require('path');
  const fs = require('fs');
  fs.mkdirSync(path.dirname(config.path), { recursive: true });
  
  return new Kysely<Database>({
    dialect: new SqliteDialect({ database: new SQLite(config.path) }),
  });
}

InProcessEventBus.ts

import { EventEmitter } from 'events';
// Implements EventBus interface from shared/application
// Logging de cada evento publicado
// Catch errors en handlers (log pero no crash)
// setMaxListeners(50)

StorageProvider.ts

export interface IStorageProvider {
  save(relativePath: string, data: Buffer): Promise<string>;
  get(relativePath: string): Promise<Buffer | null>;
  delete(relativePath: string): Promise<void>;
  exists(relativePath: string): Promise<boolean>;
}

// LocalStorageProvider: usa fs.promises, base path = config.storage.path
// Crea directorios automáticamente con mkdir recursive

Migración 001

Crea las tablas que ya existen en el schema actual (sessions, states, actions, anomalies, notifications). Usar CREATE TABLE IF NOT EXISTS para idempotencia. Los tipos de columna deben coincidir con lo que ya tiene better-sqlite3.

IMPORTANTE

  • Config DEBE fallar rápido si hay env vars inválidas
  • Logger NUNCA debe usar console.log
  • Database factory NUNCA importa pg a menos que driver sea postgres
  • EventBus handlers que fallan se loguean pero NO crashean el bus