1. P0 SECURITY: git/index.ts run_git used execSync(`git ${args.join(' ')}`)
with LLM-controlled args (commit messages, branch names, ranges) —
classic command injection. Replaced with execFileSync('git', args, ...)
which uses argv array (no shell parsing).
2. P1 CORRECTNESS: RuntimeApp constructor created TWO Scheduler instances:
- Line 39: Scheduler({...}) without worker_manager
- Line 55: Scheduler({...}, worker_manager) replacing the first
First instance was leaked (allocated then overwritten). Removed the
duplicate, kept only the wired version.
Verification:
- 169/169 tests pass
- tsc --noEmit: 0 errors
- depcruise: 0 violations
- grep 'new Scheduler' RuntimeApp.ts → 1 match (was 2)
- grep 'execSync' git/index.ts → 0 matches (was 1, with LLM-controlled args)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
96 lines
3.1 KiB
TypeScript
Executable File
96 lines
3.1 KiB
TypeScript
Executable File
/**
|
|
* 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<void> {
|
|
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<void> {
|
|
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)
|
|
}
|