/** * Incidents Routes * Based on: docs/foundation/Agent_Governance_Protocol_v1.md */ import { Router, Request, Response } from 'express'; import { incidentsService } from '../services/governance/incidents.service'; import { IncidentStatus, IncidentPriority, EscalationLevel, TargetScopeType, } from '../domain/governance/types'; import { logger } from '../infra/logger/logger'; const router = Router(); /** * POST /api/v1/incidents * Create a new incident */ router.post('/', async (req: Request, res: Response) => { try { const { createdByDaisId, targetScopeType, targetScopeId, priority, title, description, metadata, } = req.body; if (!createdByDaisId || !targetScopeType || !targetScopeId || !title) { return res.status(400).json({ error: 'Missing required fields: createdByDaisId, targetScopeType, targetScopeId, title', }); } const incident = await incidentsService.createIncident({ createdByDaisId, targetScopeType: targetScopeType as TargetScopeType, targetScopeId, priority: priority as IncidentPriority | undefined, title, description, metadata, }); res.status(201).json(incident); } catch (error) { logger.error('Error creating incident', error); res.status(500).json({ error: 'Failed to create incident' }); } }); /** * GET /api/v1/incidents * List incidents with filters */ router.get('/', async (req: Request, res: Response) => { try { const { status, priority, escalationLevel, targetScopeType, targetScopeId, assignedToDaisId, limit, offset, } = req.query; const result = await incidentsService.listIncidents({ status: status as IncidentStatus | undefined, priority: priority as IncidentPriority | undefined, escalationLevel: escalationLevel as EscalationLevel | undefined, targetScopeType: targetScopeType as TargetScopeType | undefined, targetScopeId: targetScopeId as string | undefined, assignedToDaisId: assignedToDaisId as string | undefined, limit: limit ? parseInt(limit as string, 10) : 50, offset: offset ? parseInt(offset as string, 10) : 0, }); res.json(result); } catch (error) { logger.error('Error listing incidents', error); res.status(500).json({ error: 'Failed to list incidents' }); } }); /** * GET /api/v1/incidents/count * Get open incidents count by level */ router.get('/count', async (_req: Request, res: Response) => { try { const counts = await incidentsService.getOpenIncidentsCount(); res.json(counts); } catch (error) { logger.error('Error getting incidents count', error); res.status(500).json({ error: 'Failed to get incidents count' }); } }); /** * GET /api/v1/incidents/:id * Get incident by ID */ router.get('/:id', async (req: Request, res: Response) => { try { const { id } = req.params; const incident = await incidentsService.getIncident(id); if (!incident) { return res.status(404).json({ error: 'Incident not found' }); } res.json(incident); } catch (error) { logger.error('Error getting incident', error); res.status(500).json({ error: 'Failed to get incident' }); } }); /** * GET /api/v1/incidents/:id/history * Get incident history */ router.get('/:id/history', async (req: Request, res: Response) => { try { const { id } = req.params; const history = await incidentsService.getIncidentHistory(id); res.json(history); } catch (error) { logger.error('Error getting incident history', error); res.status(500).json({ error: 'Failed to get incident history' }); } }); /** * POST /api/v1/incidents/:id/assign * Assign incident to an agent */ router.post('/:id/assign', async (req: Request, res: Response) => { try { const { id } = req.params; const { assignedToDaisId, actorDaisId } = req.body; if (!assignedToDaisId || !actorDaisId) { return res.status(400).json({ error: 'Missing required fields: assignedToDaisId, actorDaisId', }); } const result = await incidentsService.assignIncident({ incidentId: id, assignedToDaisId, actorDaisId, }); if (!result.success) { return res.status(400).json({ error: result.error }); } res.json({ success: true, message: 'Incident assigned' }); } catch (error) { logger.error('Error assigning incident', error); res.status(500).json({ error: 'Failed to assign incident' }); } }); /** * POST /api/v1/incidents/:id/escalate * Escalate incident to higher level */ router.post('/:id/escalate', async (req: Request, res: Response) => { try { const { id } = req.params; const { newLevel, actorDaisId, reason } = req.body; if (!newLevel || !actorDaisId) { return res.status(400).json({ error: 'Missing required fields: newLevel, actorDaisId', }); } const result = await incidentsService.escalateIncident({ incidentId: id, newLevel: newLevel as EscalationLevel, actorDaisId, reason, }); if (!result.success) { return res.status(400).json({ error: result.error }); } res.json({ success: true, message: `Incident escalated to ${newLevel}` }); } catch (error) { logger.error('Error escalating incident', error); res.status(500).json({ error: 'Failed to escalate incident' }); } }); /** * POST /api/v1/incidents/:id/resolve * Resolve incident */ router.post('/:id/resolve', async (req: Request, res: Response) => { try { const { id } = req.params; const { resolution, actorDaisId } = req.body; if (!resolution || !actorDaisId) { return res.status(400).json({ error: 'Missing required fields: resolution, actorDaisId', }); } const result = await incidentsService.resolveIncident({ incidentId: id, resolution, actorDaisId, }); if (!result.success) { return res.status(400).json({ error: result.error }); } res.json({ success: true, message: 'Incident resolved' }); } catch (error) { logger.error('Error resolving incident', error); res.status(500).json({ error: 'Failed to resolve incident' }); } }); /** * POST /api/v1/incidents/:id/close * Close incident */ router.post('/:id/close', async (req: Request, res: Response) => { try { const { id } = req.params; const { actorDaisId } = req.body; if (!actorDaisId) { return res.status(400).json({ error: 'Missing required field: actorDaisId' }); } const result = await incidentsService.closeIncident(id, actorDaisId); if (!result.success) { return res.status(400).json({ error: result.error }); } res.json({ success: true, message: 'Incident closed' }); } catch (error) { logger.error('Error closing incident', error); res.status(500).json({ error: 'Failed to close incident' }); } }); /** * POST /api/v1/incidents/:id/comment * Add comment to incident */ router.post('/:id/comment', async (req: Request, res: Response) => { try { const { id } = req.params; const { actorDaisId, comment } = req.body; if (!actorDaisId || !comment) { return res.status(400).json({ error: 'Missing required fields: actorDaisId, comment', }); } const result = await incidentsService.addComment(id, actorDaisId, comment); if (!result.success) { return res.status(400).json({ error: result.error }); } res.json({ success: true, message: 'Comment added' }); } catch (error) { logger.error('Error adding comment to incident', error); res.status(500).json({ error: 'Failed to add comment' }); } }); export default router;