Files
microdao-daarion/backend/services/assignment/assignment.service.ts
Apple 7b91c8e83c feat(foundation): FOUNDATION_UPDATE implementation
## Documentation (20 files)
- DAARION Ontology Core v1 (Agent → MicroDAO → Node → District)
- User Onboarding & Identity Layer (DAIS)
- Data Model UPDATE, Event Catalog, Governance & Permissions
- Rooms Layer, City/MicroDAO/Agents/Nodes Interface Architecture
- Helper files: ontology-summary, lifecycles, event-schemas

## Database Migration (027)
- DAIS tables: dais_identities, dais_emails, dais_wallets, dais_keys
- agent_assignments table for Assignment Layer
- rooms table for Rooms Layer
- event_outbox for NATS event delivery
- New enums: agent_role, microdao_type, node_kind, node_status, etc.
- Updated agents, microdaos, nodes tables with ontology fields

## Backend
- DAIS service & routes (/api/v1/dais/*)
- Assignment service & routes (/api/v1/assignments/*)
- Domain types for DAIS and Ontology

## Frontend
- Ontology types (Agent, MicroDAO, Node, DAIS, Assignments)
- API clients for DAIS and Assignments
- UI components: DaisProfileCard, AssignmentsPanel, OntologyBadge

Non-breaking update - all existing functionality preserved.
2025-11-29 15:24:38 -08:00

227 lines
6.1 KiB
TypeScript

/**
* Agent Assignment Service
* Based on: docs/foundation/microdao_Governance_And_Permissions_v1.md
*
* Manages agent work assignments to other MicroDAO/District/City
*/
import { db } from '../../infra/db/client';
import { logger } from '../../infra/logger/logger';
import type {
AgentAssignment,
CreateAssignmentRequest,
AssignmentScope,
} from '../../domain/ontology/types';
import { v4 as uuidv4 } from 'uuid';
export class AssignmentService {
/**
* Create a new agent assignment
*/
async createAssignment(request: CreateAssignmentRequest): Promise<AgentAssignment> {
try {
const result = await db.query<AgentAssignment>(
`INSERT INTO agent_assignments
(agent_id, target_microdao_id, scope, role, metadata)
VALUES ($1, $2, $3, $4, $5)
RETURNING *`,
[
request.agentId,
request.targetMicrodaoId,
request.scope,
request.role,
JSON.stringify(request.metadata || {}),
]
);
const assignment = result.rows[0];
// Publish event to outbox
await this.publishEvent('dagion.agent.assignment_created', {
assignmentId: assignment.id,
agentId: request.agentId,
targetMicrodaoId: request.targetMicrodaoId,
scope: request.scope,
role: request.role,
timestamp: new Date().toISOString(),
});
logger.info(`Created assignment: ${assignment.id} for agent ${request.agentId}`);
return assignment;
} catch (error) {
logger.error('Failed to create assignment', error);
throw error;
}
}
/**
* End an agent assignment
*/
async endAssignment(assignmentId: string): Promise<void> {
try {
const result = await db.query<AgentAssignment>(
`UPDATE agent_assignments
SET end_ts = now()
WHERE id = $1
RETURNING *`,
[assignmentId]
);
if (result.rows.length === 0) {
throw new Error(`Assignment not found: ${assignmentId}`);
}
const assignment = result.rows[0];
// Publish event to outbox
await this.publishEvent('dagion.agent.assignment_ended', {
assignmentId,
agentId: assignment.agentId,
timestamp: new Date().toISOString(),
});
logger.info(`Ended assignment: ${assignmentId}`);
} catch (error) {
logger.error(`Failed to end assignment: ${assignmentId}`, error);
throw error;
}
}
/**
* Get all active assignments for an agent
*/
async getAgentAssignments(agentId: string): Promise<AgentAssignment[]> {
try {
const result = await db.query<AgentAssignment>(
`SELECT * FROM agent_assignments
WHERE agent_id = $1 AND end_ts IS NULL
ORDER BY created_at DESC`,
[agentId]
);
return result.rows;
} catch (error) {
logger.error(`Failed to get assignments for agent: ${agentId}`, error);
throw error;
}
}
/**
* Get all assignments for a MicroDAO
*/
async getMicrodaoAssignments(microdaoId: string): Promise<AgentAssignment[]> {
try {
const result = await db.query<AgentAssignment>(
`SELECT a.*, ag.name as agent_name
FROM agent_assignments a
JOIN agents ag ON ag.id = a.agent_id
WHERE a.target_microdao_id = $1 AND a.end_ts IS NULL
ORDER BY a.created_at DESC`,
[microdaoId]
);
return result.rows;
} catch (error) {
logger.error(`Failed to get assignments for microdao: ${microdaoId}`, error);
throw error;
}
}
/**
* Get citywide assignments (DAARION108)
*/
async getCitywideAssignments(): Promise<AgentAssignment[]> {
try {
const result = await db.query<AgentAssignment>(
`SELECT a.*, ag.name as agent_name
FROM agent_assignments a
JOIN agents ag ON ag.id = a.agent_id
WHERE a.scope = 'city' AND a.end_ts IS NULL
ORDER BY a.created_at DESC`,
[]
);
return result.rows;
} catch (error) {
logger.error('Failed to get citywide assignments', error);
throw error;
}
}
/**
* Check if agent has assignment to target
*/
async hasAssignment(agentId: string, targetMicrodaoId: string): Promise<boolean> {
try {
const result = await db.query(
`SELECT 1 FROM agent_assignments
WHERE agent_id = $1 AND target_microdao_id = $2 AND end_ts IS NULL
LIMIT 1`,
[agentId, targetMicrodaoId]
);
return result.rows.length > 0;
} catch (error) {
logger.error('Failed to check assignment', error);
throw error;
}
}
/**
* Get agent's effective scope (home + assignments)
*/
async getAgentScope(agentId: string): Promise<{
homeMicrodaoId: string | null;
assignments: AgentAssignment[];
effectiveScope: AssignmentScope;
}> {
try {
// Get agent's home MicroDAO
const agent = await db.query(
`SELECT home_microdao_id, agent_service_scope FROM agents WHERE id = $1`,
[agentId]
);
if (agent.rows.length === 0) {
throw new Error(`Agent not found: ${agentId}`);
}
const assignments = await this.getAgentAssignments(agentId);
// Determine effective scope
let effectiveScope: AssignmentScope = 'microdao';
if (agent.rows[0].agent_service_scope === 'city') {
effectiveScope = 'city';
} else if (assignments.some(a => a.scope === 'city')) {
effectiveScope = 'city';
} else if (assignments.some(a => a.scope === 'district')) {
effectiveScope = 'district';
}
return {
homeMicrodaoId: agent.rows[0].home_microdao_id,
assignments,
effectiveScope,
};
} catch (error) {
logger.error(`Failed to get agent scope: ${agentId}`, error);
throw error;
}
}
/**
* Publish event to outbox for NATS
*/
private async publishEvent(eventType: string, payload: Record<string, unknown>): Promise<void> {
await db.query(
`INSERT INTO event_outbox (event_type, subject, payload)
VALUES ($1, $2, $3)`,
[eventType, eventType, JSON.stringify(payload)]
);
}
}
export const assignmentService = new AssignmentService();