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.
This commit is contained in:
Apple
2025-11-29 15:24:38 -08:00
parent deeaf26b0b
commit 7b91c8e83c
43 changed files with 5733 additions and 47 deletions

View File

@@ -0,0 +1,134 @@
/**
* Agent Assignments Panel Component
* Displays agent's assignments to other MicroDAOs
*/
import React from 'react';
import { useQuery } from '@tanstack/react-query';
import { getAgentAssignments, getAgentScope, assignmentKeys } from '../../../api/assignments';
import type { AgentAssignment, AssignmentScope } from '../../../types/ontology';
interface AssignmentsPanelProps {
agentId: string;
}
const scopeColors: Record<AssignmentScope, string> = {
microdao: 'bg-blue-500/20 text-blue-400 border-blue-500/30',
district: 'bg-purple-500/20 text-purple-400 border-purple-500/30',
city: 'bg-yellow-500/20 text-yellow-400 border-yellow-500/30',
};
const scopeLabels: Record<AssignmentScope, string> = {
microdao: 'MicroDAO',
district: 'District',
city: 'City',
};
const roleLabels: Record<string, string> = {
advisor: '💡 Advisor',
security: '🔒 Security',
mentor: '🎓 Mentor',
ops: '⚙️ Ops',
'core-team': '⭐ Core Team',
member: '👤 Member',
};
export function AssignmentsPanel({ agentId }: AssignmentsPanelProps) {
const { data: assignments, isLoading: assignmentsLoading } = useQuery({
queryKey: assignmentKeys.agent(agentId),
queryFn: () => getAgentAssignments(agentId),
});
const { data: scope, isLoading: scopeLoading } = useQuery({
queryKey: assignmentKeys.scope(agentId),
queryFn: () => getAgentScope(agentId),
});
if (assignmentsLoading || scopeLoading) {
return (
<div className="bg-gray-800 rounded-lg p-4 animate-pulse">
<div className="h-4 bg-gray-700 rounded w-3/4 mb-2"></div>
<div className="h-3 bg-gray-700 rounded w-1/2"></div>
</div>
);
}
return (
<div className="bg-gray-800 border border-gray-700 rounded-lg p-4">
<div className="flex items-center justify-between mb-4">
<h3 className="text-white font-medium">Призначення агента</h3>
{scope && (
<span
className={`px-2 py-0.5 rounded-full text-xs border ${
scopeColors[scope.effectiveScope]
}`}
>
Scope: {scopeLabels[scope.effectiveScope]}
</span>
)}
</div>
{/* Home MicroDAO */}
{scope?.homeMicrodaoId && (
<div className="mb-4 p-3 bg-gray-700/50 rounded-lg">
<div className="text-xs text-gray-400 mb-1">Домашня MicroDAO</div>
<div className="text-white font-medium">{scope.homeMicrodaoId}</div>
</div>
)}
{/* Assignments List */}
<div className="space-y-2">
{assignments && assignments.length > 0 ? (
assignments.map((assignment) => (
<AssignmentCard key={assignment.id} assignment={assignment} />
))
) : (
<div className="text-gray-400 text-sm text-center py-4">
Немає активних призначень
</div>
)}
</div>
{/* Add Assignment Button */}
<button className="mt-4 w-full py-2 border border-dashed border-gray-600 rounded-lg text-gray-400 hover:text-gray-300 hover:border-gray-500 transition-colors text-sm">
+ Додати призначення
</button>
</div>
);
}
function AssignmentCard({ assignment }: { assignment: AgentAssignment }) {
return (
<div className="p-3 bg-gray-700/30 rounded-lg border border-gray-600/50 hover:border-gray-500/50 transition-colors">
<div className="flex items-center justify-between mb-2">
<div className="flex items-center gap-2">
<span
className={`px-2 py-0.5 rounded text-xs border ${
scopeColors[assignment.scope]
}`}
>
{scopeLabels[assignment.scope]}
</span>
<span className="text-white font-medium text-sm">
{assignment.targetMicrodaoId}
</span>
</div>
<span className="text-xs text-gray-400">
{roleLabels[assignment.role] || assignment.role}
</span>
</div>
<div className="flex items-center justify-between text-xs text-gray-500">
<span>
Від: {new Date(assignment.startTs).toLocaleDateString('uk-UA')}
</span>
{assignment.endTs && (
<span>
До: {new Date(assignment.endTs).toLocaleDateString('uk-UA')}
</span>
)}
</div>
</div>
);
}

View File

@@ -0,0 +1,138 @@
/**
* DAIS Profile Card Component
* Displays DAIS identity information
*/
import React from 'react';
import { useQuery } from '@tanstack/react-query';
import { getDaisByAgent, daisKeys } from '../../../api/dais';
import type { DaisProfile, DaisTrustLevel } from '../../../types/ontology';
interface DaisProfileCardProps {
agentId: string;
}
const trustLevelColors: Record<DaisTrustLevel, string> = {
guest: 'bg-gray-500',
agent: 'bg-blue-500',
verified: 'bg-green-500',
orchestrator: 'bg-purple-500',
operator: 'bg-yellow-500',
};
const trustLevelLabels: Record<DaisTrustLevel, string> = {
guest: 'Гість',
agent: 'Агент',
verified: 'Верифікований',
orchestrator: 'Оркестратор',
operator: 'Оператор',
};
export function DaisProfileCard({ agentId }: DaisProfileCardProps) {
const { data: profile, isLoading, error } = useQuery({
queryKey: daisKeys.byAgent(agentId),
queryFn: () => getDaisByAgent(agentId),
retry: false,
});
if (isLoading) {
return (
<div className="bg-gray-800 rounded-lg p-4 animate-pulse">
<div className="h-4 bg-gray-700 rounded w-3/4 mb-2"></div>
<div className="h-3 bg-gray-700 rounded w-1/2"></div>
</div>
);
}
if (error || !profile) {
return (
<div className="bg-gray-800/50 border border-gray-700 rounded-lg p-4">
<p className="text-gray-400 text-sm">DAIS профіль не знайдено</p>
<button className="mt-2 text-blue-400 hover:text-blue-300 text-sm">
+ Створити DAIS ідентичність
</button>
</div>
);
}
return (
<div className="bg-gray-800 border border-gray-700 rounded-lg p-4">
<div className="flex items-center justify-between mb-3">
<h3 className="text-white font-medium">DAIS Ідентичність</h3>
<span
className={`px-2 py-0.5 rounded-full text-xs text-white ${
trustLevelColors[profile.identity.trustLevel]
}`}
>
{trustLevelLabels[profile.identity.trustLevel]}
</span>
</div>
<div className="space-y-2 text-sm">
{/* DID */}
<div className="flex items-center justify-between">
<span className="text-gray-400">DID</span>
<code className="text-gray-300 text-xs bg-gray-700 px-2 py-0.5 rounded">
{profile.identity.did.slice(0, 24)}...
</code>
</div>
{/* Matrix Handle */}
{profile.identity.matrixHandle && (
<div className="flex items-center justify-between">
<span className="text-gray-400">Matrix</span>
<span className="text-green-400">{profile.identity.matrixHandle}</span>
</div>
)}
{/* Emails */}
<div className="mt-3">
<div className="flex items-center justify-between mb-1">
<span className="text-gray-400">Email</span>
<span className="text-gray-500 text-xs">{profile.emails.length}</span>
</div>
{profile.emails.map((email) => (
<div
key={email.id}
className="flex items-center justify-between bg-gray-700/50 px-2 py-1 rounded"
>
<span className="text-gray-300 text-xs">{email.email}</span>
{email.verified ? (
<span className="text-green-400 text-xs"></span>
) : (
<span className="text-yellow-400 text-xs"></span>
)}
</div>
))}
</div>
{/* Wallets */}
<div className="mt-3">
<div className="flex items-center justify-between mb-1">
<span className="text-gray-400">Гаманці</span>
<span className="text-gray-500 text-xs">{profile.wallets.length}</span>
</div>
{profile.wallets.map((wallet) => (
<div
key={wallet.id}
className="flex items-center justify-between bg-gray-700/50 px-2 py-1 rounded"
>
<div className="flex items-center gap-2">
<span className="text-xs text-gray-500 uppercase">{wallet.network}</span>
<span className="text-gray-300 text-xs">
{wallet.walletAddress.slice(0, 6)}...{wallet.walletAddress.slice(-4)}
</span>
</div>
{wallet.verified ? (
<span className="text-green-400 text-xs"></span>
) : (
<span className="text-yellow-400 text-xs"></span>
)}
</div>
))}
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,156 @@
/**
* Ontology Badge Components
* Displays badges for MicroDAO type, Node status, Agent role, etc.
*/
import React from 'react';
import type {
MicrodaoType,
NodeStatus,
NodeKind,
AgentRole,
ServiceScope
} from '../../../types/ontology';
// ============================================================================
// MicroDAO Type Badge
// ============================================================================
const microdaoTypeConfig: Record<MicrodaoType, { label: string; color: string; icon: string }> = {
root: { label: 'Root', color: 'bg-yellow-500/20 text-yellow-400 border-yellow-500/30', icon: '🏛️' },
standard: { label: 'Standard', color: 'bg-blue-500/20 text-blue-400 border-blue-500/30', icon: '🏠' },
district: { label: 'District', color: 'bg-purple-500/20 text-purple-400 border-purple-500/30', icon: '🏙️' },
};
export function MicrodaoTypeBadge({ type }: { type: MicrodaoType }) {
const config = microdaoTypeConfig[type];
return (
<span className={`inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-xs border ${config.color}`}>
<span>{config.icon}</span>
<span>{config.label}</span>
</span>
);
}
// ============================================================================
// Node Status Badge
// ============================================================================
const nodeStatusConfig: Record<NodeStatus, { label: string; color: string; icon: string }> = {
provisioning: { label: 'Provisioning', color: 'bg-yellow-500/20 text-yellow-400 border-yellow-500/30', icon: '⏳' },
active: { label: 'Active', color: 'bg-green-500/20 text-green-400 border-green-500/30', icon: '✅' },
draining: { label: 'Draining', color: 'bg-orange-500/20 text-orange-400 border-orange-500/30', icon: '🔄' },
retired: { label: 'Retired', color: 'bg-gray-500/20 text-gray-400 border-gray-500/30', icon: '⛔' },
};
export function NodeStatusBadge({ status }: { status: NodeStatus }) {
const config = nodeStatusConfig[status];
return (
<span className={`inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-xs border ${config.color}`}>
<span>{config.icon}</span>
<span>{config.label}</span>
</span>
);
}
// ============================================================================
// Node Kind Badge
// ============================================================================
const nodeKindConfig: Record<NodeKind, { label: string; icon: string }> = {
smartphone: { label: 'Smartphone', icon: '📱' },
laptop: { label: 'Laptop', icon: '💻' },
edge: { label: 'Edge', icon: '📡' },
datacenter: { label: 'Datacenter', icon: '🖥️' },
iot: { label: 'IoT', icon: '🔌' },
'gpu-cluster': { label: 'GPU Cluster', icon: '🎮' },
};
export function NodeKindBadge({ kind }: { kind: NodeKind }) {
const config = nodeKindConfig[kind];
return (
<span className="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-xs bg-gray-700 text-gray-300 border border-gray-600">
<span>{config.icon}</span>
<span>{config.label}</span>
</span>
);
}
// ============================================================================
// Agent Role Badge
// ============================================================================
const agentRoleConfig: Record<AgentRole, { label: string; color: string; icon: string }> = {
regular: { label: 'Regular', color: 'bg-blue-500/20 text-blue-400 border-blue-500/30', icon: '👤' },
orchestrator: { label: 'Orchestrator', color: 'bg-purple-500/20 text-purple-400 border-purple-500/30', icon: '🎭' },
};
export function AgentRoleBadge({ role }: { role: AgentRole }) {
const config = agentRoleConfig[role];
return (
<span className={`inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-xs border ${config.color}`}>
<span>{config.icon}</span>
<span>{config.label}</span>
</span>
);
}
// ============================================================================
// Service Scope Badge
// ============================================================================
const serviceScopeConfig: Record<ServiceScope, { label: string; color: string; icon: string }> = {
microdao: { label: 'MicroDAO', color: 'bg-blue-500/20 text-blue-400 border-blue-500/30', icon: '🏠' },
district: { label: 'District', color: 'bg-purple-500/20 text-purple-400 border-purple-500/30', icon: '🏙️' },
city: { label: 'City', color: 'bg-yellow-500/20 text-yellow-400 border-yellow-500/30', icon: '🌆' },
};
export function ServiceScopeBadge({ scope }: { scope: ServiceScope }) {
const config = serviceScopeConfig[scope];
return (
<span className={`inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-xs border ${config.color}`}>
<span>{config.icon}</span>
<span>{config.label}</span>
</span>
);
}
// ============================================================================
// Ontology Hierarchy Display
// ============================================================================
interface OntologyPathProps {
agentName?: string;
microdaoName?: string;
nodeName?: string;
districtName?: string;
}
export function OntologyPath({ agentName, microdaoName, nodeName, districtName }: OntologyPathProps) {
return (
<div className="flex items-center gap-1 text-xs text-gray-400">
{agentName && (
<>
<span className="text-blue-400">👤 {agentName}</span>
<span></span>
</>
)}
{microdaoName && (
<>
<span className="text-purple-400">🏠 {microdaoName}</span>
{(nodeName || districtName) && <span></span>}
</>
)}
{nodeName && (
<>
<span className="text-green-400">🖥 {nodeName}</span>
{districtName && <span></span>}
</>
)}
{districtName && (
<span className="text-yellow-400">🏙 {districtName}</span>
)}
</div>
);
}