import type Database from 'better-sqlite3'; import { EventJournal } from './application/events/event-journal.js'; import { registerOperationRoutes } from './interface/http/operation-routes.js'; import { operationCatalogRegistry } from './application/operations/operation-catalog-data.js'; import { SecureOperationExecution, secureOperationRegistry, } from './application/operations/secure-operation-execution.js'; import type { FastifyInstance, FastifyRequest } from 'fastify'; import { buildApp, type BuildAppOptions } from './app.js'; import { ConnectionProbe, type ConnectionTransport, } from './application/connections/connection-probe.js'; import { InstanceCredentialResolver } from './application/connections/instance-credential-resolver.js'; import { InstanceLoginService } from './application/connections/instance-login-service.js'; import { InstanceSessionStore, UpstreamSessionClient, type UpstreamSessionClientOptions, } from './application/connections/upstream-session-client.js'; import { InstanceService } from './application/instances/instance-service.js'; import { DeleteInstanceOperation } from './application/operations/delete-instance-operation.js'; import type { SecretStore } from './infrastructure/secrets/secret-store.js'; import { registerInstanceRoutes } from './interface/http/instance-routes.js'; import { registerEventRoutes } from './interface/http/event-routes.js'; import { JobQueryService } from './application/jobs/job-query-service.js'; import { registerJobRoutes } from './interface/http/job-routes.js'; import { AuditQueryService } from './application/audit/audit-query-service.js'; import { registerAuditRoutes } from './interface/http/audit-routes.js'; import { InstanceResourceService } from './application/resources/instance-resource-service.js'; import { ConsoleAuthService } from './application/auth/console-auth-service.js'; import { registerConsoleAuth } from './interface/http/console-auth-routes.js'; import { InstanceMessageService } from './application/messages/instance-message-service.js'; export interface SafeControlPlaneUpstream extends ConnectionTransport { request: UpstreamSessionClientOptions['request']; postNetworkRegisterAuto(origin: string): Promise<{ readonly status: number }>; } export interface ControlPlaneOptions { readonly db: Database.Database; readonly store: SecretStore; readonly upstream: SafeControlPlaneUpstream; readonly now?: () => Date; readonly authenticateEventStream?: (request: FastifyRequest) => boolean; readonly app?: Omit; } export interface ControlPlaneApp extends FastifyInstance { retryPendingSecretCleanup(): Promise; } export function buildControlPlaneApp(options: ControlPlaneOptions): ControlPlaneApp { const eventJournal = new EventJournal(options.db); const jobs = new JobQueryService(options.db); const audit = new AuditQueryService(options.db); const instances = options.now ? new InstanceService({ db: options.db, store: options.store, now: options.now }) : new InstanceService({ db: options.db, store: options.store }); const connections = options.now ? new ConnectionProbe({ db: options.db, instances, transport: options.upstream, now: options.now, }) : new ConnectionProbe({ db: options.db, instances, transport: options.upstream }); const sessions = new InstanceSessionStore(); const client = new UpstreamSessionClient({ sessions, request: options.upstream.request }); const resources = new InstanceResourceService({ instances, sessions, request: options.upstream.request, }); const messages = new InstanceMessageService({ instances, sessions, request: options.upstream.request, }); const resolver = new InstanceCredentialResolver({ db: options.db, store: options.store }); const login = options.now ? new InstanceLoginService({ db: options.db, client, resolver, now: options.now }) : new InstanceLoginService({ db: options.db, client, resolver }); const deletion = options.now ? new DeleteInstanceOperation({ db: options.db, instances, now: options.now }) : new DeleteInstanceOperation({ db: options.db, instances }); deletion.reconcileInterruptedJobs(); const secureExecution = new SecureOperationExecution({ db: options.db, registry: secureOperationRegistry, transport: { request: async ({ origin }) => { const response = await options.upstream.postNetworkRegisterAuto(origin); return { status: response.status }; }, }, ...(options.now ? { now: options.now } : {}), }); secureExecution.reconcileInterruptedJobs(); const auth = new ConsoleAuthService({ db: options.db, ...(options.now ? { now: options.now } : {}), }); const app = buildApp({ ...options.app, registerRoutes: (app) => { registerConsoleAuth(app, auth); registerInstanceRoutes(app, { instances, connections, login, deletion, resources, messages, registerDeletionPreparationRoute: false, }); registerOperationRoutes(app, operationCatalogRegistry, secureExecution, deletion); registerJobRoutes(app, { jobs }); registerAuditRoutes(app, { audit }); registerEventRoutes(app, { journal: eventJournal, ...(options.authenticateEventStream ? { authenticate: options.authenticateEventStream } : {}), }); }, }); Object.assign(app, { retryPendingSecretCleanup: () => instances.retryPendingSecretCleanup(), }); return app as ControlPlaneApp; }