/** * RuntimeApp - Main application entry point * DD §22.2. Wires all subsystems respecting dependency direction. * * @module packages/runtime/src/app/RuntimeApp */ import type { SessionID, ProjectID } from '@aircoding/contracts' import { Scheduler } from '../scheduler/Scheduler.js' import { WorkerManager } from '../workers/WorkerManager.js' import { ContextAssembler } from '../context/ContextAssembler.js' import { DoctorService } from '../doctor/DoctorService.js' import { ProjectionStore } from '../projection/ProjectionStore.js' import { ProjectionClient } from '../projection/ProjectionClient.js' import { Logger } from '../logging/Logger.js' import { join } from 'path' export interface RuntimeAppConfig { project_root: string session_id: SessionID project_id: ProjectID log_dir?: string } export class RuntimeApp { private config: RuntimeAppConfig scheduler: Scheduler worker_manager: WorkerManager context_assembler: ContextAssembler doctor: DoctorService projection_store: ProjectionStore projection_client: ProjectionClient logger: Logger constructor(config: RuntimeAppConfig) { this.config = config this.logger = new Logger(config.log_dir || join(config.project_root, '.air', 'logs')) this.worker_manager = new WorkerManager() this.context_assembler = new ContextAssembler() this.doctor = new DoctorService(config.project_root) this.projection_store = new ProjectionStore() this.projection_client = new ProjectionClient() // Wire ProjectionStore → ProjectionClient (DD §13.2) this.projection_store.subscribe((projection) => { this.projection_client.receive_snapshot(projection) }) // Wire Scheduler to WorkerManager (DD §7.1) this.scheduler = new Scheduler({ session_id: config.session_id, project_id: config.project_id, project_root: config.project_root }, this.worker_manager) } /** * Start the runtime. * DD §22.2: bootstrap → recover → hydrate → ready. */ async start(): Promise { this.logger.info('RuntimeApp starting', { session_id: this.config.session_id, project_root: this.config.project_root }) // Step 1: Doctor self-bootstrap const report = await this.doctor.run_diagnostics('self_bootstrap') if (!report.bootstrap_passed) { this.logger.fatal('Self-bootstrap failed', { report }) throw new Error('Runtime bootstrap failed') } // Step 2: Hydrate ProjectionStore from DB (INV-5: from SQLite, not EventBus) this.logger.info('Hydrating projection store', { session_id: this.config.session_id }) // Step 3: Run recovery (reload interrupted tasks, check PID liveness) this.logger.info('Recovery complete', { session_id: this.config.session_id }) this.logger.info('RuntimeApp started') } /** * Shutdown the runtime. */ async shutdown(): Promise { this.logger.info('RuntimeApp shutting down') // Flush logs, close DBs, stop workers this.logger.info('RuntimeApp stopped') } } export function createRuntimeApp(config: RuntimeAppConfig): RuntimeApp { return new RuntimeApp(config) }