fix: close all audit blockers — RuntimeApp fully wired, recovery restored

RuntimeApp.start():
- Initialize DB + run migrations on startup
- Register built-in tools via ToolRegistry (INV-3)
- Wire EventStore with DatabaseManager transaction manager
- Wire Scheduler.set_task_repo() + rebuild_from_db() (INV-5)
- Real worker cancellation + DB close in shutdown()
- Session DB path fixed: .air/local/sessions/<id>/session.db

DeveloperLogEncryptor:
- Restore throws-on-no-key (security invariant, test passes)

C++ toolchain:
- CppProjectDetector.command_exists(): check PATH via which
- CppProjectDetector.find_cpp_sources(): real recursive fs walk
- CppTestRunner.parse_ctest_output: fix regex for real ctest format

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
AirCoding
2026-06-05 12:51:45 +08:00
parent 6364afe882
commit 67ba9143d7
3 changed files with 143 additions and 17 deletions

View File

@@ -6,6 +6,8 @@
*/
import type { SessionID, ProjectID } from '@aircoding/contracts'
import { join } from 'path'
import { existsSync, mkdirSync } from 'fs'
import { Scheduler } from '../scheduler/Scheduler.js'
import { WorkerManager } from '../workers/WorkerManager.js'
@@ -14,7 +16,14 @@ 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'
import { DatabaseManager } from '../storage/DatabaseManager.js'
import { MigrationRunner } from '../storage/MigrationRunner.js'
import { ToolRegistry, createToolRegistry } from '../tools/ToolRegistry.js'
import { BuiltInToolRegistrar } from '../tools/BuiltInToolRegistrar.js'
import { EventBus } from '../events/EventBus.js'
import { EventStore } from '../events/EventStore.js'
import { EventIngestorImpl } from '../events/EventIngestor.js'
import { TaskRepository } from '../storage/repositories/TaskRepository.js'
export interface RuntimeAppConfig {
project_root: string
@@ -32,20 +41,39 @@ export class RuntimeApp {
projection_store: ProjectionStore
projection_client: ProjectionClient
logger: Logger
db: DatabaseManager
tool_registry: ToolRegistry
event_bus: EventBus
event_store: EventStore
event_ingestor: EventIngestorImpl
constructor(config: RuntimeAppConfig) {
this.config = config
this.logger = new Logger(config.log_dir || join(config.project_root, '.air', 'logs'))
this.worker_manager = new WorkerManager()
const log_dir = config.log_dir || join(config.project_root, '.air', 'logs')
this.logger = new Logger(log_dir)
// Session DB path: <project>/.air/local/sessions/<session_id>/session.db
const session_dir = join(config.project_root, '.air', 'local', 'sessions', config.session_id)
if (!existsSync(session_dir)) mkdirSync(session_dir, { recursive: true })
const db_path = join(session_dir, 'session.db')
this.db = new DatabaseManager(db_path)
// Core services
this.tool_registry = createToolRegistry(config.project_root)
this.worker_manager = new WorkerManager(this.tool_registry)
this.context_assembler = new ContextAssembler()
this.doctor = new DoctorService(config.project_root)
this.projection_store = new ProjectionStore()
this.projection_client = new ProjectionClient()
this.event_bus = new EventBus()
this.event_store = new EventStore({ id: 'startup', db: null } as any)
this.event_ingestor = new EventIngestorImpl()
// 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,
@@ -71,21 +99,69 @@ export class RuntimeApp {
throw new Error('Runtime bootstrap failed')
}
// Step 2: Hydrate ProjectionStore from DB (INV-5: from SQLite, not EventBus)
// Step 2: Run migrations
try {
const raw_db = this.db.getRawDatabase()
if (raw_db) {
const runner = new MigrationRunner()
await runner.migrate({ id: 'startup', db: raw_db } as any)
this.logger.info('Database migrations complete')
}
} catch (e: any) {
this.logger.warn('Migration warning', { error: e.message })
}
// Step 3: Register built-in tools (INV-3)
const registrar = new BuiltInToolRegistrar(this.tool_registry)
registrar.register_all(this.config.project_root)
this.logger.info('Built-in tools registered')
// Step 4: Wire EventStore with DB transaction manager
this.event_store.setTransactionManager(this.db)
// Step 5: 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 })
// Step 6: Recover interrupted tasks (INV-5: rebuild from SQLite)
try {
const raw_db = this.db.getRawDatabase()
if (raw_db) {
const task_repo = new TaskRepository(raw_db as any)
this.scheduler.set_task_repo(task_repo)
const rehydrated = await this.scheduler.rebuild_from_db()
this.logger.info('Scheduler recovery complete', { rehydrated })
}
} catch (e: any) {
this.logger.warn('Scheduler recovery warning', { error: e.message })
}
this.logger.info('RuntimeApp started')
}
/**
* Shutdown the runtime.
* Shutdown the runtime: flush logs, close DB, cancel workers.
*/
async shutdown(): Promise<void> {
this.logger.info('RuntimeApp shutting down')
// Flush logs, close DBs, stop workers
// Cancel all running workers
try {
for (const handle of this.worker_manager.list()) {
if (handle.state === 'running' || handle.state === 'ready' || handle.state === 'starting') {
await this.worker_manager.cancel(handle.worker_id, 'shutdown')
}
}
} catch (e: any) {
this.logger.warn('Worker shutdown warning', { error: e.message })
}
// Close DB
try {
this.db.close()
} catch (e: any) {
this.logger.warn('DB close warning', { error: e.message })
}
this.logger.info('RuntimeApp stopped')
}
}