/** * Governance Service * Based on: docs/foundation/Agent_Governance_Protocol_v1.md * * Handles agent promotion, demotion, and role management */ import { db } from '../../infra/db/client'; import { logger } from '../../infra/logger/logger'; import { v4 as uuidv4 } from 'uuid'; import { AgentGovLevel, AgentStatus, PromoteAgentRequest, GovernanceEvent, GovernanceEventType, GovernanceScope, GOV_LEVEL_TO_NUM, } from '../../domain/governance/types'; import { permissionEngine, buildContext } from './permissions'; export class GovernanceService { /** * Promote an agent to a new level */ async promoteAgent(request: PromoteAgentRequest): Promise<{ success: boolean; error?: string }> { const context = await buildContext(request.actorId, request.scope); // Check permission const canPromote = await permissionEngine.canPromoteAgent( context, request.targetId, request.newLevel ); if (!canPromote.allowed) { logger.warn(`Promotion denied: ${request.actorId} → ${request.targetId}`, { reason: canPromote.reason, }); return { success: false, error: canPromote.reason }; } try { // Get current level for event const currentLevel = await permissionEngine.getAgentLevel(request.targetId); // Update agent level await db.query( `UPDATE agents SET gov_level = $1, updated_at = now() WHERE id = $2`, [request.newLevel, request.targetId] ); // If promoting to orchestrator, update DAIS trust level if (request.newLevel === 'orchestrator' || request.newLevel === 'district_lead' || request.newLevel === 'city_governance') { await db.query( `UPDATE dais_identities SET trust_level = 'orchestrator', updated_at = now() WHERE id = (SELECT dais_identity_id FROM agents WHERE id = $1)`, [request.targetId] ); } // Log governance event await this.logEvent('agent.promoted', request.actorId, request.targetId, request.scope, { previousLevel: currentLevel, newLevel: request.newLevel, reason: request.reason, }); logger.info(`Agent promoted: ${request.targetId} → ${request.newLevel}`, { actorId: request.actorId, previousLevel: currentLevel, }); return { success: true }; } catch (error) { logger.error('Failed to promote agent', error); throw error; } } /** * Demote an agent to a lower level */ async demoteAgent( actorId: string, targetId: string, newLevel: AgentGovLevel, scope: GovernanceScope, reason?: string ): Promise<{ success: boolean; error?: string }> { const context = await buildContext(actorId, scope); const targetLevel = await permissionEngine.getAgentLevel(targetId); const actorLevelNum = GOV_LEVEL_TO_NUM[context.actorLevel]; const targetLevelNum = GOV_LEVEL_TO_NUM[targetLevel]; const newLevelNum = GOV_LEVEL_TO_NUM[newLevel]; // Verify demotion is valid if (targetLevelNum >= actorLevelNum) { return { success: false, error: 'Cannot demote agent at same or higher level' }; } if (newLevelNum >= targetLevelNum) { return { success: false, error: 'New level must be lower than current level' }; } if (actorLevelNum < 4) { return { success: false, error: 'Only Core-team and above can demote agents' }; } try { await db.query( `UPDATE agents SET gov_level = $1, updated_at = now() WHERE id = $2`, [newLevel, targetId] ); // Update DAIS trust level if demoted from orchestrator if (['orchestrator', 'district_lead', 'city_governance'].includes(targetLevel)) { await db.query( `UPDATE dais_identities SET trust_level = 'verified', updated_at = now() WHERE id = (SELECT dais_identity_id FROM agents WHERE id = $1)`, [targetId] ); } await this.logEvent('agent.demoted', actorId, targetId, scope, { previousLevel: targetLevel, newLevel, reason, }); logger.info(`Agent demoted: ${targetId} → ${newLevel}`, { actorId }); return { success: true }; } catch (error) { logger.error('Failed to demote agent', error); throw error; } } /** * Get agent roles and permissions */ async getAgentRoles(agentId: string): Promise<{ level: AgentGovLevel; status: AgentStatus; powers: string[]; assignments: Array<{ microdaoId: string; role: string; scope: string; }>; }> { const level = await permissionEngine.getAgentLevel(agentId); const powers = permissionEngine.getPowersForLevel(level); const agentData = await db.query<{ status: AgentStatus }>( `SELECT status FROM agents WHERE id = $1`, [agentId] ); const assignments = await db.query<{ target_microdao_id: string; role: string; scope: string; }>( `SELECT target_microdao_id, role, scope FROM agent_assignments WHERE agent_id = $1 AND end_ts IS NULL`, [agentId] ); return { level, status: agentData.rows[0]?.status || 'active', powers, assignments: assignments.rows.map(a => ({ microdaoId: a.target_microdao_id, role: a.role, scope: a.scope, })), }; } /** * Get agents by governance level */ async getAgentsByLevel(level: AgentGovLevel, limit = 50): Promise> { const result = await db.query<{ id: string; name: string; gov_level: AgentGovLevel; status: AgentStatus; home_microdao_id: string; }>( `SELECT id, name, gov_level, status, home_microdao_id FROM agents WHERE gov_level = $1 AND status = 'active' ORDER BY updated_at DESC LIMIT $2`, [level, limit] ); return result.rows.map(r => ({ id: r.id, name: r.name, level: r.gov_level, status: r.status, homeMicrodaoId: r.home_microdao_id, })); } /** * Get city governance agents */ async getCityGovernanceAgents(): Promise> { const result = await db.query<{ id: string; name: string; }>( `SELECT id, name FROM agents WHERE gov_level = 'city_governance' AND status = 'active'` ); const roleMap: Record = { daarwizz: 'Mayor', dario: 'Community', daria: 'Tech Governance', }; return result.rows.map(r => ({ id: r.id, name: r.name, role: roleMap[r.id] || 'City Agent', })); } /** * Get district lead agents */ async getDistrictLeadAgents(districtId?: string): Promise> { let query = ` SELECT a.id as agent_id, a.name as agent_name, m.id as district_id, m.name as district_name FROM agents a JOIN microdaos m ON m.primary_orchestrator_agent_id = a.id WHERE m.dao_type = 'district' AND a.status = 'active' `; const params: string[] = []; if (districtId) { query += ` AND m.id = $1`; params.push(districtId); } const result = await db.query<{ agent_id: string; agent_name: string; district_id: string; district_name: string; }>(query, params); return result.rows.map(r => ({ agentId: r.agent_id, agentName: r.agent_name, districtId: r.district_id, districtName: r.district_name, })); } /** * Log governance event to event_outbox */ async logEvent( eventType: GovernanceEventType, actorId: string, targetId: string, scope: GovernanceScope, payload: Record ): Promise { const eventId = uuidv4(); const subject = `dagion.governance.${eventType}`; await db.query( `INSERT INTO event_outbox (id, event_type, subject, actor_id, target_id, scope, payload, version) VALUES ($1, $2, $3, $4, $5, $6, $7, '1.0')`, [eventId, eventType, subject, actorId, targetId, scope, JSON.stringify(payload)] ); logger.debug(`Logged governance event: ${eventType}`, { eventId, actorId, targetId }); } } export const governanceService = new GovernanceService();