feat: Node Self-Healing, DAGI Audit, Agent Prompts, Infra Invariants
### 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
This commit is contained in:
268
docs/tasks/TASK_PHASE_NODE_SELF_HEALING_v1.md
Normal file
268
docs/tasks/TASK_PHASE_NODE_SELF_HEALING_v1.md
Normal file
@@ -0,0 +1,268 @@
|
||||
# TASK_PHASE_NODE_SELF_HEALING_v1
|
||||
|
||||
## Проєкт
|
||||
DAARION.city — Nodes / Node Cabinet / DAGI Router
|
||||
|
||||
## Фаза
|
||||
Self-healing нод (автоматична реєстрація, відновлення та синхронізація)
|
||||
|
||||
## Статус
|
||||
✅ **COMPLETED**
|
||||
|
||||
---
|
||||
|
||||
## Мета
|
||||
|
||||
Зробити так, щоб:
|
||||
|
||||
1. Ноди **ніколи не "зникали"** з Node Directory, якщо фізично існують і шлють heartbeat
|
||||
2. Реєстрація/оновлення нод виконувалась **агентами ноди**, а не ручними діями
|
||||
3. Node Directory → Node Cabinet → Node Metrics → DAGI Router були повністю узгоджені
|
||||
|
||||
---
|
||||
|
||||
## Problem Statement
|
||||
|
||||
### Симптом
|
||||
- `/nodes` (Node Directory) показує:
|
||||
- «Знайдено нод: 0»
|
||||
- «Помилка завантаження нод»
|
||||
- Хоча:
|
||||
- насправді NODE1/NODE2 є в `node_cache`
|
||||
- метрики, DAGI Router, агенти ноди працюють
|
||||
|
||||
### Причини
|
||||
- Node Directory фронт дивився на іншу структуру даних
|
||||
- Реєстрація ноди не відпрацьовувала після деплою
|
||||
- Немає самовідновлюваної логіки на рівні нод
|
||||
|
||||
---
|
||||
|
||||
## Рішення
|
||||
|
||||
### 1. Node Registry — єдине джерело істини
|
||||
|
||||
**Таблиця:** `node_registry`
|
||||
|
||||
```sql
|
||||
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:**
|
||||
|
||||
```sql
|
||||
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`
|
||||
|
||||
```bash
|
||||
# Використання при старті ноди
|
||||
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
|
||||
```
|
||||
|
||||
**Що робить:**
|
||||
1. Відправляє POST на `/internal/nodes/register-or-update`
|
||||
2. При успіху — відправляє початковий heartbeat
|
||||
3. При помилці — retry до 5 разів
|
||||
|
||||
### 4. Node Guardian Self-Healing Loop
|
||||
|
||||
**Файл:** `scripts/node-guardian-loop.py`
|
||||
|
||||
```bash
|
||||
# Запуск як фоновий процес
|
||||
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
|
||||
```
|
||||
|
||||
**Що перевіряє:**
|
||||
1. Чи нода видима в Node Directory
|
||||
2. Чи є heartbeat
|
||||
3. Чи є Guardian/Steward агенти
|
||||
4. Чи є агенти в router
|
||||
|
||||
**Self-healing дії:**
|
||||
1. Якщо не видима — виконує self-registration
|
||||
2. Якщо heartbeat старий — відправляє новий
|
||||
3. Якщо статус 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() |
|
||||
|
||||
---
|
||||
|
||||
## Використання
|
||||
|
||||
### При першому деплої ноди
|
||||
|
||||
```bash
|
||||
# 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
|
||||
|
||||
```bash
|
||||
# Через 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
|
||||
|
||||
```yaml
|
||||
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 оновлює статус | ✅ |
|
||||
|
||||
---
|
||||
|
||||
## Наступні кроки
|
||||
|
||||
1. **Автоматичний DAGI Router reinstall** при `agent_count_router = 0`
|
||||
2. **NATS events** для node healing (`node.selfhealing.*`)
|
||||
3. **Prometheus metrics** для self-healing
|
||||
4. **Alert rules** для критичних станів
|
||||
5. **Node Federation** — з'єднання нод між собою
|
||||
|
||||
Reference in New Issue
Block a user