"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); /** * ABE — composition root. * Wires all modules together and starts the HTTP + WebSocket server. */ const http_1 = __importDefault(require("http")); const socket_io_1 = require("socket.io"); const Config_1 = require("./shared/infrastructure/Config"); const Logger_1 = require("./shared/infrastructure/Logger"); const DatabaseConnection_1 = require("./shared/infrastructure/DatabaseConnection"); const InProcessEventBus_1 = require("./shared/infrastructure/InProcessEventBus"); const migrator_1 = require("./db/migrator"); // Crawling module const KyselyCrawlSessionRepository_1 = require("./modules/crawling/infrastructure/repositories/KyselyCrawlSessionRepository"); const KyselyStateRepository_1 = require("./modules/crawling/infrastructure/repositories/KyselyStateRepository"); const StartCrawlCommand_1 = require("./modules/crawling/application/commands/StartCrawlCommand"); const StopCrawlCommand_1 = require("./modules/crawling/application/commands/StopCrawlCommand"); const GetSessionQuery_1 = require("./modules/crawling/application/queries/GetSessionQuery"); const ListSessionsQuery_1 = require("./modules/crawling/application/queries/ListSessionsQuery"); // Findings module const KyselyFindingRepository_1 = require("./modules/findings/infrastructure/repositories/KyselyFindingRepository"); const CreateFindingCommand_1 = require("./modules/findings/application/commands/CreateFindingCommand"); const EnrichFindingCommand_1 = require("./modules/findings/application/commands/EnrichFindingCommand"); const ResolveFindingCommand_1 = require("./modules/findings/application/commands/ResolveFindingCommand"); const GetFindingQuery_1 = require("./modules/findings/application/queries/GetFindingQuery"); const ListFindingsQuery_1 = require("./modules/findings/application/queries/ListFindingsQuery"); const FindingStatsQuery_1 = require("./modules/findings/application/queries/FindingStatsQuery"); const OnAnomalyDetected_1 = require("./modules/findings/application/event-handlers/OnAnomalyDetected"); const NullAIEnricher_1 = require("./modules/findings/infrastructure/NullAIEnricher"); // Fuzzing module const FuzzingEngineAdapter_1 = require("./modules/fuzzing/infrastructure/adapters/FuzzingEngineAdapter"); const RunFuzzCommand_1 = require("./modules/fuzzing/application/commands/RunFuzzCommand"); const OnActionExecuted_1 = require("./modules/fuzzing/application/event-handlers/OnActionExecuted"); const InMemoryFuzzSessionRepository_1 = require("./modules/fuzzing/infrastructure/repositories/InMemoryFuzzSessionRepository"); // Auth module const KyselyUserRepository_1 = require("./modules/auth/infrastructure/repositories/KyselyUserRepository"); const KyselyOrganizationRepository_1 = require("./modules/auth/infrastructure/repositories/KyselyOrganizationRepository"); const KyselyApiKeyRepository_1 = require("./modules/auth/infrastructure/repositories/KyselyApiKeyRepository"); const KyselySessionRepository_1 = require("./modules/auth/infrastructure/repositories/KyselySessionRepository"); const RegisterCommand_1 = require("./modules/auth/application/commands/RegisterCommand"); const LoginCommand_1 = require("./modules/auth/application/commands/LoginCommand"); const CreateOrganizationCommand_1 = require("./modules/auth/application/commands/CreateOrganizationCommand"); const InviteMemberCommand_1 = require("./modules/auth/application/commands/InviteMemberCommand"); const CreateApiKeyCommand_1 = require("./modules/auth/application/commands/CreateApiKeyCommand"); const GetUserQuery_1 = require("./modules/auth/application/queries/GetUserQuery"); const ListOrgMembersQuery_1 = require("./modules/auth/application/queries/ListOrgMembersQuery"); const PasswordService_1 = require("./modules/auth/infrastructure/auth/PasswordService"); // Reporting module const KyselyReportRepository_1 = require("./modules/reporting/infrastructure/repositories/KyselyReportRepository"); const GenerateReportCommand_1 = require("./modules/reporting/application/commands/GenerateReportCommand"); // Integrations module const KyselyIntegrationRepository_1 = require("./modules/integrations/infrastructure/repositories/KyselyIntegrationRepository"); const KyselyWebhookEndpointRepository_1 = require("./modules/integrations/infrastructure/repositories/KyselyWebhookEndpointRepository"); const WebhookDispatcher_1 = require("./modules/integrations/infrastructure/webhooks/WebhookDispatcher"); const OnFindingCreated_1 = require("./modules/integrations/application/event-handlers/OnFindingCreated"); // Licensing module const RSALicenseValidator_1 = require("./modules/licensing/infrastructure/validators/RSALicenseValidator"); const LicenseService_1 = require("./modules/licensing/application/LicenseService"); // Visual regression module const KyselyVisualRepository_1 = require("./modules/visual-regression/infrastructure/repositories/KyselyVisualRepository"); const VisualRegressionAdapter_1 = require("./modules/visual-regression/infrastructure/adapters/VisualRegressionAdapter"); const ApproveBaselineCommand_1 = require("./modules/visual-regression/application/commands/ApproveBaselineCommand"); const RejectComparisonCommand_1 = require("./modules/visual-regression/application/commands/RejectComparisonCommand"); const ApproveAllNewStatesCommand_1 = require("./modules/visual-regression/application/commands/ApproveAllNewStatesCommand"); const ListComparisonsQuery_1 = require("./modules/visual-regression/application/queries/ListComparisonsQuery"); const StorageProvider_1 = require("./shared/infrastructure/StorageProvider"); const path_1 = __importDefault(require("path")); // Scheduling module const KyselyScheduleRepository_1 = require("./modules/scheduling/infrastructure/repositories/KyselyScheduleRepository"); const CreateScheduleCommand_1 = require("./modules/scheduling/application/commands/CreateScheduleCommand"); const ToggleScheduleCommand_1 = require("./modules/scheduling/application/commands/ToggleScheduleCommand"); const DeleteScheduleCommand_1 = require("./modules/scheduling/application/commands/DeleteScheduleCommand"); const ListSchedulesQuery_1 = require("./modules/scheduling/application/queries/ListSchedulesQuery"); const SchedulingService_1 = require("./modules/scheduling/application/SchedulingService"); // Job queue const SQLiteJobQueue_1 = require("./jobs/SQLiteJobQueue"); const ExplorationWorker_1 = require("./jobs/workers/ExplorationWorker"); const ReportWorker_1 = require("./jobs/workers/ReportWorker"); // API + Realtime const server_1 = require("./api/server"); const SocketGateway_1 = require("./realtime/SocketGateway"); async function bootstrap() { // 1. Config const config = (0, Config_1.loadConfig)(); // 2. Logger const logger = (0, Logger_1.createLogger)({ level: config.log.level, nodeEnv: config.nodeEnv }); logger.info({ port: config.port, env: config.nodeEnv }, 'Starting ABE...'); // 3. Database + migrations const db = (0, DatabaseConnection_1.createDatabase)(config.db); await (0, migrator_1.runMigrations)(db); logger.info('Database migrations applied'); // 4. Event bus const eventBus = new InProcessEventBus_1.InProcessEventBus(logger); // 5. Repositories const sessionRepo = new KyselyCrawlSessionRepository_1.KyselyCrawlSessionRepository(db); const stateRepo = new KyselyStateRepository_1.KyselyStateRepository(db); const findingRepo = new KyselyFindingRepository_1.KyselyFindingRepository(db); const reportRepo = new KyselyReportRepository_1.KyselyReportRepository(db); const fuzzRepo = new InMemoryFuzzSessionRepository_1.InMemoryFuzzSessionRepository(); // Suppress unused warning for stateRepo — used by crawling infrastructure void stateRepo; // 6. Crawling use cases const startCrawl = new StartCrawlCommand_1.StartCrawlCommand(sessionRepo, eventBus); const stopCrawl = new StopCrawlCommand_1.StopCrawlCommand(sessionRepo, eventBus); const getSession = new GetSessionQuery_1.GetSessionQuery(sessionRepo); const listSessions = new ListSessionsQuery_1.ListSessionsQuery(sessionRepo); // 7. Findings use cases const createFinding = new CreateFindingCommand_1.CreateFindingCommand(findingRepo, eventBus); const enricher = new NullAIEnricher_1.NullAIEnricher(); const enrichFinding = new EnrichFindingCommand_1.EnrichFindingCommand(findingRepo, enricher, eventBus); const resolveFinding = new ResolveFindingCommand_1.ResolveFindingCommand(findingRepo, eventBus); const getFinding = new GetFindingQuery_1.GetFindingQuery(findingRepo); const listFindings = new ListFindingsQuery_1.ListFindingsQuery(findingRepo); const findingStats = new FindingStatsQuery_1.FindingStatsQuery(findingRepo); // 8. Fuzzing use cases const fuzzerEngine = new FuzzingEngineAdapter_1.FuzzingEngineAdapter({ intensity: 'low', seed: 42 }); const runFuzz = new RunFuzzCommand_1.RunFuzzCommand(fuzzerEngine, fuzzRepo, eventBus); // 9. Event handlers — subscribe to EventBus const onAnomalyDetected = new OnAnomalyDetected_1.OnAnomalyDetected(createFinding); eventBus.subscribe('crawling.anomaly_detected', onAnomalyDetected); const onActionExecuted = new OnActionExecuted_1.OnActionExecuted(runFuzz); eventBus.subscribe('crawling.action_executed', onActionExecuted); // 10. Auth module const userRepo = new KyselyUserRepository_1.KyselyUserRepository(db); const orgRepo = new KyselyOrganizationRepository_1.KyselyOrganizationRepository(db); const apiKeyRepo = new KyselyApiKeyRepository_1.KyselyApiKeyRepository(db); const authSessionRepo = new KyselySessionRepository_1.KyselySessionRepository(db); const registerCommand = new RegisterCommand_1.RegisterCommand(userRepo, eventBus, PasswordService_1.hashPassword); const loginCommand = new LoginCommand_1.LoginCommand(userRepo, authSessionRepo, eventBus, PasswordService_1.verifyPassword); const createOrgCommand = new CreateOrganizationCommand_1.CreateOrganizationCommand(orgRepo, userRepo, eventBus); const inviteMemberCommand = new InviteMemberCommand_1.InviteMemberCommand(orgRepo, userRepo, eventBus); const createApiKeyCommand = new CreateApiKeyCommand_1.CreateApiKeyCommand(apiKeyRepo, userRepo); const getUserQuery = new GetUserQuery_1.GetUserQuery(userRepo); const listOrgMembersQuery = new ListOrgMembersQuery_1.ListOrgMembersQuery(orgRepo, userRepo); // 11. Reporting use cases const generateReport = new GenerateReportCommand_1.GenerateReportCommand(reportRepo, eventBus); // 11b. Licensing const licenseValidator = new RSALicenseValidator_1.RSALicenseValidator(); const licenseService = new LicenseService_1.LicenseService(licenseValidator); // 11c. Integrations (moved from 11d) const integrationRepo = new KyselyIntegrationRepository_1.KyselyIntegrationRepository(db); const webhookRepo = new KyselyWebhookEndpointRepository_1.KyselyWebhookEndpointRepository(db); const webhookDispatcher = new WebhookDispatcher_1.WebhookDispatcher(webhookRepo, logger); const onFindingCreated = new OnFindingCreated_1.OnFindingCreated(integrationRepo, webhookRepo, webhookDispatcher, logger); eventBus.subscribe('findings.finding_created', onFindingCreated); // 12. Job queue (created before HTTP server so it can be injected) const jobQueue = new SQLiteJobQueue_1.SQLiteJobQueue(db, logger, config.jobs.pollIntervalMs); jobQueue.registerHandler(ExplorationWorker_1.EXPLORATION_JOB_TYPE, (0, ExplorationWorker_1.createExplorationJobHandler)({ sessionRepo, eventBus, logger })); jobQueue.registerHandler(ReportWorker_1.REPORT_JOB_TYPE, (0, ReportWorker_1.createReportJobHandler)({ logger, reportRepository: reportRepo, findingRepository: findingRepo })); jobQueue.start(); // 11d. Visual regression module const storageBasePath = path_1.default.join(process.cwd(), 'data'); const storageProvider = new StorageProvider_1.LocalStorageProvider(storageBasePath); const visualBaselineRepo = new KyselyVisualRepository_1.KyselyVisualBaselineRepository(db); const visualComparisonRepo = new KyselyVisualRepository_1.KyselyVisualComparisonRepository(db); const visualRegressionAdapter = new VisualRegressionAdapter_1.VisualRegressionAdapter(storageProvider, visualBaselineRepo, visualComparisonRepo, eventBus); void visualRegressionAdapter; // used by ExplorationOrchestrator in crawling infra const listComparisons = new ListComparisonsQuery_1.ListComparisonsQuery(visualComparisonRepo); const approveBaseline = new ApproveBaselineCommand_1.ApproveBaselineCommand(visualComparisonRepo, visualBaselineRepo, eventBus); const rejectComparison = new RejectComparisonCommand_1.RejectComparisonCommand(visualComparisonRepo); const approveAllNewStates = new ApproveAllNewStatesCommand_1.ApproveAllNewStatesCommand(visualComparisonRepo, visualBaselineRepo, eventBus); // 12b. Scheduling module (after job queue, since it enqueues jobs) const scheduleRepo = new KyselyScheduleRepository_1.KyselyScheduleRepository(db); const createSchedule = new CreateScheduleCommand_1.CreateScheduleCommand(scheduleRepo, eventBus); const toggleSchedule = new ToggleScheduleCommand_1.ToggleScheduleCommand(scheduleRepo, eventBus); const deleteSchedule = new DeleteScheduleCommand_1.DeleteScheduleCommand(scheduleRepo, eventBus); const listSchedules = new ListSchedulesQuery_1.ListSchedulesQuery(scheduleRepo); const schedulingService = new SchedulingService_1.SchedulingService(scheduleRepo, jobQueue, eventBus, logger); await schedulingService.start(); // 13. HTTP server const app = (0, server_1.createServer)({ config, logger, db, crawlingDeps: { startCrawl, stopCrawl, getSession, listSessions }, findingsDeps: { getFinding, listFindings, findingStats, resolveFinding, enrichFinding }, fuzzingDeps: { runFuzz, repository: fuzzRepo }, reportingDeps: { generateReport, reportRepository: reportRepo, jobQueue }, integrationsDeps: { integrationRepo, webhookRepo }, schedulingDeps: { createSchedule, toggleSchedule, deleteSchedule, listSchedules, schedulingService, scheduleRepo }, visualRegressionDeps: { listComparisons, approveBaseline, rejectComparison, approveAllNewStates }, licenseService, authDeps: { registerCommand, loginCommand, createOrgCommand, inviteMemberCommand, createApiKeyCommand, getUserQuery, listOrgMembersQuery, sessionRepository: authSessionRepo, apiKeyRepository: apiKeyRepo, userRepository: userRepo, }, }); const httpServer = http_1.default.createServer(app); // 12. Socket.io + gateway const io = new socket_io_1.Server(httpServer, { cors: { origin: config.cors.origin, credentials: true }, }); const gateway = new SocketGateway_1.SocketGateway(io, eventBus, logger); gateway.start(); // 13. Start listening await new Promise((resolve) => { httpServer.listen(config.port, config.host, resolve); }); logger.info({ port: config.port, host: config.host }, 'ABE server ready'); // 14. Graceful shutdown let shuttingDown = false; async function shutdown(signal) { if (shuttingDown) return; shuttingDown = true; logger.info({ signal }, 'Shutting down...'); // Stop accepting new connections httpServer.close(); // Close socket.io io.close(); // Stop scheduling service schedulingService.stop(); // Stop job queue and wait for active jobs jobQueue.pause(); await jobQueue.waitForActive(30000); // Close database try { await db.destroy(); } catch (err) { logger.warn({ err }, 'Error closing database'); } logger.info('Shutdown complete'); process.exit(0); } // Force-exit if graceful shutdown takes too long function forceExit(signal) { void shutdown(signal).catch(() => { process.exit(1); }); setTimeout(() => { logger.error('Forced shutdown after 30s'); process.exit(1); }, 30000).unref(); } process.on('SIGTERM', () => forceExit('SIGTERM')); process.on('SIGINT', () => forceExit('SIGINT')); } bootstrap().catch((err) => { console.error('Fatal: failed to start ABE', err); process.exit(1); });