### Backend (city-service) - Node Registry + Self-Healing API (migration 039) - Improved get_all_nodes() with robust fallback for node_registry/node_cache - Agent Prompts Runtime API for DAGI Router integration - DAGI Router Audit endpoints (phantom/stale detection) - Node Agents API (Guardian/Steward) - Node metrics extended (CPU/GPU/RAM/Disk) ### Frontend (apps/web) - Node Directory with improved error handling - Node Cabinet with metrics cards - DAGI Router Card component - Node Metrics Card component - useDAGIAudit hook ### Scripts - check-invariants.py - deploy verification - node-bootstrap.sh - node self-registration - node-guardian-loop.py - continuous self-healing - dagi_agent_audit.py - DAGI audit utility ### Migrations - 034: Agent prompts seed - 035: Agent DAGI audit - 036: Node metrics extended - 037: Node agents complete - 038: Agent prompts full coverage - 039: Node registry self-healing ### Tests - test_infra_smoke.py - test_agent_prompts_runtime.py - test_dagi_router_api.py ### Documentation - DEPLOY_CHECKLIST_2024_11_30.md - Multiple TASK_PHASE docs
7.8 KiB
7.8 KiB
TASK_PHASE_NODE_SELF_HEALING_v1
Проєкт
DAARION.city — Nodes / Node Cabinet / DAGI Router
Фаза
Self-healing нод (автоматична реєстрація, відновлення та синхронізація)
Статус
✅ COMPLETED
Мета
Зробити так, щоб:
- Ноди ніколи не "зникали" з Node Directory, якщо фізично існують і шлють heartbeat
- Реєстрація/оновлення нод виконувалась агентами ноди, а не ручними діями
- Node Directory → Node Cabinet → Node Metrics → DAGI Router були повністю узгоджені
Problem Statement
Симптом
/nodes(Node Directory) показує:- «Знайдено нод: 0»
- «Помилка завантаження нод»
- Хоча:
- насправді NODE1/NODE2 є в
node_cache - метрики, DAGI Router, агенти ноди працюють
- насправді NODE1/NODE2 є в
Причини
- Node Directory фронт дивився на іншу структуру даних
- Реєстрація ноди не відпрацьовувала після деплою
- Немає самовідновлюваної логіки на рівні нод
Рішення
1. Node Registry — єдине джерело істини
Таблиця: node_registry
CREATE TABLE node_registry (
id text PRIMARY KEY, -- node_id
name text NOT NULL, -- Людська назва
hostname text, -- Hostname
environment text NOT NULL, -- production/development/staging
roles text[] NOT NULL DEFAULT '{}', -- ['gpu', 'ai_runtime', ...]
description text,
is_active boolean NOT NULL DEFAULT true,
registered_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
last_self_registration timestamptz, -- Остання самореєстрація
self_registration_count integer DEFAULT 0
);
View для Node Directory:
CREATE VIEW v_nodes_directory AS
SELECT
r.*,
c.cpu_model, c.gpu_model, c.ram_total, ...
c.last_heartbeat,
c.agent_count_router,
c.agent_count_system,
CASE
WHEN c.last_heartbeat < NOW() - INTERVAL '10 minutes' THEN 'stale'
ELSE 'online'
END AS connection_status
FROM node_registry r
LEFT JOIN node_cache c ON c.node_id = r.id
WHERE r.is_active = true;
2. Self-Registration API
| Endpoint | Метод | Опис |
|---|---|---|
/internal/nodes/register-or-update |
POST | Самореєстрація ноди |
/internal/node/{node_id}/heartbeat |
POST | Heartbeat з метриками |
/internal/node/{node_id}/directory-check |
GET | Перевірка видимості |
/internal/node/{node_id}/self-healing/status |
GET | Статус self-healing |
/internal/node/{node_id}/self-healing/trigger |
POST | Тригер self-healing |
/internal/nodes/needing-healing |
GET | Список нод для healing |
3. Node Bootstrap Script
Файл: scripts/node-bootstrap.sh
# Використання при старті ноди
NODE_ID=node-2-macbook-m4max \
NODE_NAME="MacBook Pro M4 Max" \
NODE_ENVIRONMENT=development \
NODE_ROLES=gpu,ai_runtime,development \
./scripts/node-bootstrap.sh
Що робить:
- Відправляє POST на
/internal/nodes/register-or-update - При успіху — відправляє початковий heartbeat
- При помилці — retry до 5 разів
4. Node Guardian Self-Healing Loop
Файл: scripts/node-guardian-loop.py
# Запуск як фоновий процес
NODE_ID=node-2-macbook-m4max \
NODE_NAME="NODE2" \
python scripts/node-guardian-loop.py --interval 60
# Одноразова перевірка
python scripts/node-guardian-loop.py --node-id node-2-macbook-m4max --once
Що перевіряє:
- Чи нода видима в Node Directory
- Чи є heartbeat
- Чи є Guardian/Steward агенти
- Чи є агенти в router
Self-healing дії:
- Якщо не видима — виконує self-registration
- Якщо heartbeat старий — відправляє новий
- Якщо статус error — тригерить healing через API
Файли
| Файл | Опис |
|---|---|
migrations/039_node_registry_self_healing.sql |
Міграція для node_registry |
services/city-service/repo_city.py |
Функції для self-healing |
services/city-service/routes_city.py |
API endpoints |
scripts/node-bootstrap.sh |
Bootstrap скрипт |
scripts/node-guardian-loop.py |
Self-healing loop |
Інваріанти Self-Healing
| Умова | Дія |
|---|---|
| Нода не в node_registry | → self_register() |
| heartbeat > 10 хв | → send_heartbeat() |
| agent_count_router = 0 | → alert + try reinstall |
| guardian_agent_id = NULL | → alert |
| self_healing_status = error | → trigger_healing() |
Використання
При першому деплої ноди
# 1. Запустити міграцію
psql -d daarion < migrations/039_node_registry_self_healing.sql
# 2. Запустити bootstrap
NODE_ID=node-2-macbook-m4max \
NODE_NAME="MacBook Pro M4 Max" \
NODE_ENVIRONMENT=development \
./scripts/node-bootstrap.sh
Запуск Guardian Loop
# Через systemd
[Unit]
Description=DAARION Node Guardian
After=network.target
[Service]
Environment=NODE_ID=node-2-macbook-m4max
Environment=NODE_NAME=NODE2
Environment=CITY_SERVICE_URL=http://localhost:7001
ExecStart=/usr/bin/python3 /path/to/scripts/node-guardian-loop.py
Restart=always
[Install]
WantedBy=multi-user.target
Через Docker Compose
services:
node-guardian:
image: python:3.11-slim
environment:
- NODE_ID=node-2-macbook-m4max
- NODE_NAME=NODE2
- CITY_SERVICE_URL=http://city-service:7001
command: python /app/scripts/node-guardian-loop.py
volumes:
- ./scripts:/app/scripts
depends_on:
- city-service
Self-Healing сценарії
Сценарій 1: Нода зникла з Directory після деплою
1. Node Guardian запускається
2. check_visibility() → false
3. self_register() → успіх
4. check_visibility() → true
5. ✅ Нода знову в Directory
Сценарій 2: Heartbeat застарів
1. Node Guardian перевіряє статус
2. self_healing_status = "stale_heartbeat"
3. send_heartbeat() → успіх
4. ✅ Heartbeat оновлено
Сценарій 3: Agent count = 0
1. Node Guardian бачить agent_count_router = 0
2. Логує попередження
3. (Опційно) trigger_healing() для перевірки DAGI Router
4. ⚠️ Потребує уваги адміністратора
Acceptance Criteria
| Критерій | Статус |
|---|---|
| node_registry таблиця створена | ✅ |
| API self-registration працює | ✅ |
| node-bootstrap.sh виконує реєстрацію | ✅ |
| node-guardian-loop.py запускається | ✅ |
| Ноди видимі в /nodes після реєстрації | ✅ |
| Self-healing при зникненні | ✅ |
| Heartbeat оновлює статус | ✅ |
Наступні кроки
- Автоматичний DAGI Router reinstall при
agent_count_router = 0 - NATS events для node healing (
node.selfhealing.*) - Prometheus metrics для self-healing
- Alert rules для критичних станів
- Node Federation — з'єднання нод між собою