Phase 7 of the roadmap. On a ticket's automatic transition to HUMAN_ESCALATION, routing resolves 006's hierarchy nodes and capability-eligibility lookup directly (never a second matching algorithm), and a pluggable strategy (ROUND_ROBIN, concurrency-safe via atomic Redis INCR; LEAST_LOADED; SKILL_BASED) selects exactly one eligible agent, persisted as a version-row-per-period Assignment plus an append-only AssignmentHistory event log. MANUAL/DIRECT are never auto-selected — only an explicit admin-supplied agentId reaches them. On success the ticket moves HUMAN_ESCALATION -> IN_PROGRESS through 003's existing state machine. A ticket's "required skill" comes from its most recent AI diagnosis's problemType (005) when one exists, unioned with any matching hierarchy node's skills (006); when neither exists, there's no skill constraint (every active agent eligible), never zero. Found and fixed two real, latent bugs in the shared event-bus infrastructure while building this feature's own tests: (1) EventBus.publish was built on EventEmitter.emit(), which never awaits async listeners, so a caller had no guarantee any subscriber (005's AI-session-ending hook, now also this feature's orchestration hook) had actually finished — rewritten to track subscribers directly and await them via Promise.all, same per-handler error isolation as before. (2) registerDomainEventHandlers() was only called from server.ts's production startup path, never from buildApp() — meaning every integration test in this codebase had zero domain-event subscribers registered at all. Now called (idempotently) from buildApp() itself, since domain-event wiring is synchronous application behavior, not a background-worker concern like the queue. Adds 8 unit tests (each strategy's pure selection/tie-break logic), a dedicated round-robin concurrency test verifying no two concurrent selections collide under real parallel load, and 2 integration test files covering all five user stories. Full regression (every pre-existing 002-006 integration test plus every new 007 test) run together against real Postgres/Redis/MinIO: 124 passed, 9 skipped (005's AI-key-gated tests, unrelated), 0 failed. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
46 lines
1.2 KiB
TypeScript
46 lines
1.2 KiB
TypeScript
import { buildApp } from './app';
|
|
import { env } from '@/config';
|
|
import { logger } from '@/infrastructure/observability';
|
|
import {
|
|
bootstrapDatabase,
|
|
bootstrapRedis,
|
|
bootstrapQueue,
|
|
bootstrapStorage,
|
|
setupGracefulShutdown,
|
|
} from '@/bootstrap';
|
|
|
|
async function startServer(): Promise<void> {
|
|
try {
|
|
logger.info('🚀 Bootstrapping SupportHub API Enterprise Modular Monolith...');
|
|
|
|
// Initialize Infrastructure Connections
|
|
await bootstrapDatabase();
|
|
await bootstrapRedis();
|
|
await bootstrapQueue();
|
|
await bootstrapStorage();
|
|
|
|
// Create Fastify Instance — registers domain event handlers itself, see app.ts.
|
|
const app = await buildApp();
|
|
|
|
// Register Graceful Shutdown Processors
|
|
setupGracefulShutdown(app);
|
|
|
|
// Listen on Configured Host and Port
|
|
const address = await app.listen({
|
|
port: env.PORT,
|
|
host: env.HOST,
|
|
});
|
|
|
|
logger.info(
|
|
{ port: env.PORT, host: env.HOST, env: env.NODE_ENV },
|
|
`🟢 SupportHub API running at ${address}`,
|
|
);
|
|
logger.info(`📚 Swagger OpenAPI documentation available at ${address}/docs`);
|
|
} catch (error) {
|
|
logger.error({ error }, '❌ Fatal error during server startup.');
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
startServer();
|