/** * Governance Routes * Based on: docs/foundation/Agent_Governance_Protocol_v1.md */ import { Router, Request, Response } from 'express'; import { governanceService } from '../services/governance/governance.service'; import { revocationService } from '../services/governance/revocation.service'; import { permissionEngine, buildContext } from '../services/governance/permissions'; import { AgentGovLevel, GovernanceScope, RevocationType } from '../domain/governance/types'; import { logger } from '../infra/logger/logger'; const router = Router(); // ============================================================================ // AGENT PROMOTION/DEMOTION // ============================================================================ /** * POST /api/v1/governance/agent/promote * Promote an agent to a higher level */ router.post('/agent/promote', async (req: Request, res: Response) => { try { const { actorId, targetId, newLevel, scope, reason } = req.body; if (!actorId || !targetId || !newLevel || !scope) { return res.status(400).json({ error: 'Missing required fields: actorId, targetId, newLevel, scope' }); } const result = await governanceService.promoteAgent({ actorId, targetId, newLevel: newLevel as AgentGovLevel, scope: scope as GovernanceScope, reason, }); if (!result.success) { return res.status(403).json({ error: result.error }); } res.json({ success: true, message: `Agent ${targetId} promoted to ${newLevel}` }); } catch (error) { logger.error('Error promoting agent', error); res.status(500).json({ error: 'Failed to promote agent' }); } }); /** * POST /api/v1/governance/agent/demote * Demote an agent to a lower level */ router.post('/agent/demote', async (req: Request, res: Response) => { try { const { actorId, targetId, newLevel, scope, reason } = req.body; if (!actorId || !targetId || !newLevel || !scope) { return res.status(400).json({ error: 'Missing required fields' }); } const result = await governanceService.demoteAgent( actorId, targetId, newLevel as AgentGovLevel, scope as GovernanceScope, reason ); if (!result.success) { return res.status(403).json({ error: result.error }); } res.json({ success: true, message: `Agent ${targetId} demoted to ${newLevel}` }); } catch (error) { logger.error('Error demoting agent', error); res.status(500).json({ error: 'Failed to demote agent' }); } }); // ============================================================================ // AGENT REVOCATION // ============================================================================ /** * POST /api/v1/governance/agent/revoke * Revoke an agent */ router.post('/agent/revoke', async (req: Request, res: Response) => { try { const { actorId, targetId, reason, scope, revocationType } = req.body; if (!actorId || !targetId || !reason || !scope) { return res.status(400).json({ error: 'Missing required fields: actorId, targetId, reason, scope' }); } const result = await revocationService.revokeAgent({ actorId, targetId, reason, scope: scope as GovernanceScope, revocationType: (revocationType || 'soft') as RevocationType, }); if (!result.success) { return res.status(403).json({ error: result.error }); } res.json({ success: true, revocationId: result.revocationId, message: `Agent ${targetId} revoked` }); } catch (error) { logger.error('Error revoking agent', error); res.status(500).json({ error: 'Failed to revoke agent' }); } }); /** * POST /api/v1/governance/agent/suspend * Temporarily suspend an agent */ router.post('/agent/suspend', async (req: Request, res: Response) => { try { const { actorId, targetId, reason, scope, durationHours } = req.body; if (!actorId || !targetId || !reason || !scope) { return res.status(400).json({ error: 'Missing required fields' }); } const result = await revocationService.suspendAgent( actorId, targetId, reason, scope as GovernanceScope, durationHours ); if (!result.success) { return res.status(403).json({ error: result.error }); } res.json({ success: true, message: `Agent ${targetId} suspended` }); } catch (error) { logger.error('Error suspending agent', error); res.status(500).json({ error: 'Failed to suspend agent' }); } }); /** * POST /api/v1/governance/agent/reinstate * Reinstate a revoked/suspended agent */ router.post('/agent/reinstate', async (req: Request, res: Response) => { try { const { actorId, targetId, scope, reason } = req.body; if (!actorId || !targetId || !scope) { return res.status(400).json({ error: 'Missing required fields' }); } const result = await revocationService.reinstateAgent( actorId, targetId, scope as GovernanceScope, reason ); if (!result.success) { return res.status(403).json({ error: result.error }); } res.json({ success: true, message: `Agent ${targetId} reinstated` }); } catch (error) { logger.error('Error reinstating agent', error); res.status(500).json({ error: 'Failed to reinstate agent' }); } }); // ============================================================================ // AGENT ROLES & PERMISSIONS // ============================================================================ /** * GET /api/v1/governance/agent/:id/roles * Get agent roles and permissions */ router.get('/agent/:id/roles', async (req: Request, res: Response) => { try { const { id } = req.params; const roles = await governanceService.getAgentRoles(id); res.json(roles); } catch (error) { logger.error('Error getting agent roles', error); res.status(500).json({ error: 'Failed to get agent roles' }); } }); /** * GET /api/v1/governance/agent/:id/permissions * Get agent permissions for a specific target */ router.get('/agent/:id/permissions', async (req: Request, res: Response) => { try { const { id } = req.params; const { targetType, targetId, action } = req.query; if (targetType && targetId && action) { // Get DAIS ID for agent const context = await buildContext(id, 'city'); const hasPermission = await permissionEngine.hasExplicitPermission( context.actorDaisId, targetType as any, targetId as string, action as any ); return res.json({ hasPermission }); } // Return general permissions const level = await permissionEngine.getAgentLevel(id); const powers = permissionEngine.getPowersForLevel(level); res.json({ level, powers }); } catch (error) { logger.error('Error getting agent permissions', error); res.status(500).json({ error: 'Failed to get agent permissions' }); } }); // ============================================================================ // PERMISSION CHECKS // ============================================================================ /** * POST /api/v1/governance/check * Check if an agent can perform an action */ router.post('/check', async (req: Request, res: Response) => { try { const { actorId, action, targetId, scope, roomType } = req.body; if (!actorId || !action) { return res.status(400).json({ error: 'Missing required fields: actorId, action' }); } const context = await buildContext(actorId, (scope || 'city') as GovernanceScope); let result; switch (action) { case 'create_microdao': result = await permissionEngine.canCreateMicrodao(context); break; case 'create_district': result = await permissionEngine.canCreateDistrict(context); break; case 'register_node': result = await permissionEngine.canRegisterNode(context); break; case 'create_room': result = await permissionEngine.canCreateRoom(context, roomType || 'dao-room'); break; case 'create_front_portal': result = await permissionEngine.canCreateFrontPortal(context, targetId); break; case 'moderate_room': result = await permissionEngine.canModerateRoom(context, targetId); break; default: return res.status(400).json({ error: `Unknown action: ${action}` }); } res.json(result); } catch (error) { logger.error('Error checking permission', error); res.status(500).json({ error: 'Failed to check permission' }); } }); // ============================================================================ // GOVERNANCE AGENTS LISTS // ============================================================================ /** * GET /api/v1/governance/agents/city * Get city governance agents */ router.get('/agents/city', async (_req: Request, res: Response) => { try { const agents = await governanceService.getCityGovernanceAgents(); res.json(agents); } catch (error) { logger.error('Error getting city governance agents', error); res.status(500).json({ error: 'Failed to get city governance agents' }); } }); /** * GET /api/v1/governance/agents/district-leads * Get district lead agents */ router.get('/agents/district-leads', async (req: Request, res: Response) => { try { const { districtId } = req.query; const agents = await governanceService.getDistrictLeadAgents(districtId as string); res.json(agents); } catch (error) { logger.error('Error getting district lead agents', error); res.status(500).json({ error: 'Failed to get district lead agents' }); } }); /** * GET /api/v1/governance/agents/by-level/:level * Get agents by governance level */ router.get('/agents/by-level/:level', async (req: Request, res: Response) => { try { const { level } = req.params; const { limit } = req.query; const agents = await governanceService.getAgentsByLevel( level as AgentGovLevel, limit ? parseInt(limit as string, 10) : 50 ); res.json(agents); } catch (error) { logger.error('Error getting agents by level', error); res.status(500).json({ error: 'Failed to get agents by level' }); } }); // ============================================================================ // DAIS KEY REVOCATION // ============================================================================ /** * POST /api/v1/governance/dais/keys/revoke * Revoke DAIS keys */ router.post('/dais/keys/revoke', async (req: Request, res: Response) => { try { const { actorId, daisId, reason } = req.body; if (!actorId || !daisId || !reason) { return res.status(400).json({ error: 'Missing required fields: actorId, daisId, reason' }); } const result = await revocationService.revokeDaisKeys(actorId, daisId, reason); if (!result.success) { return res.status(403).json({ error: result.error }); } res.json({ success: true, keysRevoked: result.keysRevoked, message: `${result.keysRevoked} keys revoked for DAIS ${daisId}` }); } catch (error) { logger.error('Error revoking DAIS keys', error); res.status(500).json({ error: 'Failed to revoke DAIS keys' }); } }); /** * GET /api/v1/governance/agent/:id/revocations * Get revocation history for an agent */ router.get('/agent/:id/revocations', async (req: Request, res: Response) => { try { const { id } = req.params; const history = await revocationService.getRevocationHistory(id); res.json(history); } catch (error) { logger.error('Error getting revocation history', error); res.status(500).json({ error: 'Failed to get revocation history' }); } }); export default router;