## 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.
227 lines
6.1 KiB
TypeScript
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();
|
|
|